From c0ab4dc2f547a7427620d068f352419c89bdaceb Mon Sep 17 00:00:00 2001 From: rk0n Date: Tue, 17 May 2022 19:41:54 +0200 Subject: [PATCH 001/102] Add profile for Creality Ender 3 S1 Pro --- resources/profiles/Creality.idx | 2 ++ resources/profiles/Creality.ini | 28 ++++++++++++++++++ .../Creality/ENDER3S1PRO_thumbnail.png | Bin 0 -> 43801 bytes 3 files changed, 30 insertions(+) create mode 100644 resources/profiles/Creality/ENDER3S1PRO_thumbnail.png diff --git a/resources/profiles/Creality.idx b/resources/profiles/Creality.idx index 1ff148aad..8f7dd0e8c 100644 --- a/resources/profiles/Creality.idx +++ b/resources/profiles/Creality.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 2.5.0-alpha0 +0.1.5 Added Ender-3 S1 Pro min_slic3r_version = 2.4.1 0.1.4 Added Ender-3 Pro. Added M25 support for some printers. min_slic3r_version = 2.4.0-rc diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 0fa14d8a8..80ca6313e 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -59,6 +59,15 @@ bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:ENDER3S1PRO] +name = Creality Ender-3 S1 Pro +variants = 0.4 +technology = FFF +family = ENDER +bed_model = ender3v2_bed.stl +bed_texture = ender3v2.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY + [printer_model:ENDER3MAX] name = Creality Ender-3 Max variants = 0.4 @@ -842,6 +851,18 @@ filament_cost = 27.44 filament_density = 1.29 filament_colour = #C7F935 +[filament:Verbatim PLA @CREALITY] +inherits = *PLA* +filament_vendor = Verbatim +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 210 +first_layer_bed_temperature = 60 +filament_cost = 22.99 +filament_density = 1.24 +filament_colour = #001ca8 +filament_spool_weight = 500 + # Common printer preset [printer:*common*] printer_technology = FFF @@ -992,6 +1013,13 @@ max_print_height = 270 printer_model = ENDER3S1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_ENDER3S1 +[printer:Creality Ender-3 S1 Pro] +inherits = *common*; *pauseprint*; *spriteextruder* +bed_shape = 5x0,215x0,215x220,5x220 +max_print_height = 270 +printer_model = ENDER3S1PRO +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_ENDER3S1PRO + [printer:Creality Ender-3 Max] inherits = *common*; *pauseprint* retract_length = 6 diff --git a/resources/profiles/Creality/ENDER3S1PRO_thumbnail.png b/resources/profiles/Creality/ENDER3S1PRO_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..199f2edf3194306253a6f6203cd4ecf3286084ab GIT binary patch literal 43801 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 K?K(|V#Qy-IPD2j> literal 0 HcmV?d00001 From adaed9fdece421dc65a4f2b958c6e31f4ed98e08 Mon Sep 17 00:00:00 2001 From: rk0n Date: Wed, 18 May 2022 00:46:40 +0200 Subject: [PATCH 002/102] Change Alpha to Beta Badge --- .../Creality/ENDER3S1PRO_thumbnail.png | Bin 43801 -> 43931 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/profiles/Creality/ENDER3S1PRO_thumbnail.png b/resources/profiles/Creality/ENDER3S1PRO_thumbnail.png index 199f2edf3194306253a6f6203cd4ecf3286084ab..d11396b05784e53915028604ffad6d6afe8e422f 100644 GIT binary patch literal 43931 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 NfomGvtx|s){y&GM@8kdg literal 43801 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 K?K(|V#Qy-IPD2j> From f6661f4217121342c6743eb99c5fb9274bcd905a Mon Sep 17 00:00:00 2001 From: rk0n Date: Wed, 18 May 2022 00:51:02 +0200 Subject: [PATCH 003/102] Add Verbatim PLA to all Creality printers --- resources/profiles/Creality.ini | 70 ++++++++++++++++----------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 80ca6313e..b537976d1 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -21,7 +21,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3BLTOUCH] name = Creality Ender-3 BLTouch @@ -30,7 +30,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3PRO] name = Creality Ender-3 Pro @@ -39,7 +39,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3V2] name = Creality Ender-3 V2 @@ -48,7 +48,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3S1] name = Creality Ender-3 S1 @@ -57,7 +57,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3S1PRO] name = Creality Ender-3 S1 Pro @@ -75,7 +75,7 @@ technology = FFF family = ENDER bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER4] name = Creality Ender-4 @@ -84,7 +84,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER5] name = Creality Ender-5 @@ -93,7 +93,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER5PLUS] name = Creality Ender-5 Plus @@ -102,7 +102,7 @@ technology = FFF family = ENDER bed_model = ender5plus_bed.stl bed_texture = ender5plus.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER6] name = Creality Ender-6 @@ -111,7 +111,7 @@ technology = FFF family = ENDER bed_model = ender6_bed.stl bed_texture = ender6.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER7] name = Creality Ender-7 @@ -120,7 +120,7 @@ technology = FFF family = ENDER bed_model = ender7_bed.stl bed_texture = ender7.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER2] name = Creality Ender-2 @@ -129,7 +129,7 @@ technology = FFF family = ENDER bed_model = ender2_bed.stl bed_texture = ender2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER2PRO] name = Creality Ender-2 Pro @@ -138,7 +138,7 @@ technology = FFF family = ENDER bed_model = ender2pro_bed.stl bed_texture = ender2pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR5PRO] name = Creality CR-5 Pro @@ -147,7 +147,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR5PROH] name = Creality CR-5 Pro H @@ -156,7 +156,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR6SE] name = Creality CR-6 SE @@ -165,7 +165,7 @@ technology = FFF family = CR bed_model = cr6se_bed.stl bed_texture = cr6se.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR6MAX] name = Creality CR-6 Max @@ -174,7 +174,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10SMART] name = Creality CR-10 SMART @@ -183,7 +183,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10MINI] name = Creality CR-10 Mini @@ -192,7 +192,7 @@ technology = FFF family = CR bed_model = cr10mini_bed.stl bed_texture = cr10mini.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10MAX] name = Creality CR-10 Max @@ -201,7 +201,7 @@ technology = FFF family = CR bed_model = cr10max_bed.stl bed_texture = cr10max.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10] name = Creality CR-10 @@ -210,7 +210,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10V2] name = Creality CR-10 V2 @@ -219,7 +219,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10V3] name = Creality CR-10 V3 @@ -228,7 +228,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10S] name = Creality CR-10 S @@ -237,7 +237,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10SPRO] name = Creality CR-10 S Pro @@ -246,7 +246,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10SPROV2] name = Creality CR-10 S Pro V2 @@ -255,7 +255,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10S4] name = Creality CR-10 S4 @@ -264,7 +264,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10S5] name = Creality CR-10 S5 @@ -273,7 +273,7 @@ technology = FFF family = CR bed_model = cr10s5_bed.stl bed_texture = cr10s5.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR20] name = Creality CR-20 @@ -282,7 +282,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR20PRO] name = Creality CR-20 Pro @@ -291,7 +291,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR200B] name = Creality CR-200B @@ -300,7 +300,7 @@ technology = FFF family = CR bed_model = cr200b_bed.stl bed_texture = cr200b.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR8] name = Creality CR-8 @@ -309,7 +309,7 @@ technology = FFF family = CR bed_model = cr8_bed.stl bed_texture = cr8.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY #[printer_model:CRX] #name = Creality CR-X @@ -318,7 +318,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY #[printer_model:CRXPRO] #name = Creality CR-X Pro @@ -327,7 +327,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:SERMOOND1] name = Creality Sermoon-D1 @@ -336,7 +336,7 @@ technology = FFF family = SERMOON bed_model = sermoond1_bed.stl bed_texture = sermoond1.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. From 1cf75100fc34177db09a8c674e73b334f425e946 Mon Sep 17 00:00:00 2001 From: rk0n Date: Wed, 18 May 2022 00:51:19 +0200 Subject: [PATCH 004/102] Remove filament spool weight from Verbatim PLA --- resources/profiles/Creality.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index b537976d1..66a793415 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -861,7 +861,6 @@ first_layer_bed_temperature = 60 filament_cost = 22.99 filament_density = 1.24 filament_colour = #001ca8 -filament_spool_weight = 500 # Common printer preset [printer:*common*] From 4e3369bbfcdadd8c63f06f6e2d2646356fc5c3f4 Mon Sep 17 00:00:00 2001 From: MarkMan0 Date: Wed, 4 May 2022 13:19:47 +0200 Subject: [PATCH 005/102] Improve Proton X profiles, Add Proton XE-750 printer --- resources/profiles/INAT.idx | 3 +- resources/profiles/INAT.ini | 375 +++++++++++++++--- .../profiles/INAT/PROTON_XE750_thumbnail.png | Bin 0 -> 36819 bytes 3 files changed, 320 insertions(+), 58 deletions(-) create mode 100644 resources/profiles/INAT/PROTON_XE750_thumbnail.png diff --git a/resources/profiles/INAT.idx b/resources/profiles/INAT.idx index a756b34b5..6087bae62 100644 --- a/resources/profiles/INAT.idx +++ b/resources/profiles/INAT.idx @@ -1,4 +1,5 @@ -min_slic3r_version = 2.3.1-beta +min_slic3r_version = 2.4.1 +0.0.4 Improve Proton X profiles, Add Proton XE-750 printer 0.0.3 Set default filament profile. 0.0.2 Improved start gcode, changed filename format 0.0.1 Initial version diff --git a/resources/profiles/INAT.ini b/resources/profiles/INAT.ini index 3c1a753b5..0bba8c976 100644 --- a/resources/profiles/INAT.ini +++ b/resources/profiles/INAT.ini @@ -3,7 +3,7 @@ [vendor] # Vendor name will be shown by the Config Wizard. name = INAT -config_version = 0.0.3 +config_version = 0.0.4 config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/INAT/ ### @@ -24,9 +24,16 @@ technology = FFF family = Proton default_materials = PLA @PROTON_X +[printer_model:PROTON_XE750] +name = INAT Proton XE-750 +variants = 0.4 +technology = FFF +family = Proton +default_materials = PLA @PROTON_XE750 + ### -### QUALITY DEFINITIONS +### COMMON QUALITY DEFINITIONS ### [print:*common*] @@ -35,14 +42,15 @@ layer_height = 0.2 first_layer_height = 0.2 perimeters = 3 spiral_vase = 0 -top_solid_layers = 4 +top_solid_layers = 5 bottom_solid_layers = 3 -top_solid_min_thickness = 0.8 +top_solid_min_thickness = 1 bottom_solid_min_thickness = 0.6 extra_perimeters = 1 ensure_vertical_shell_thickness = 1 avoid_crossing_perimeters = 0 thin_walls = 0 +thick_bridges = 0 overhangs = 1 seam_position = aligned external_perimeters_first = 0 @@ -76,7 +84,9 @@ support_material_auto = 1 support_material_threshold = 0 support_material_enforce_layers = 0 raft_layers = 0 -support_material_contact_distance = 0.2 +support_material_style = grid +support_material_contact_distance = 0.25 +support_material_bottom_contact_distance = 0.3 support_material_pattern = rectilinear support_material_with_sheath = 0 support_material_spacing = 5 @@ -92,8 +102,8 @@ perimeter_speed = 60 small_perimeter_speed = 75% external_perimeter_speed = 50% infill_speed = 80 -solid_infill_speed = 100% -top_solid_infill_speed = 30 +solid_infill_speed = 80% +top_solid_infill_speed = 20 support_material_speed = 80 support_material_interface_speed = 100% bridge_speed = 60 @@ -126,7 +136,7 @@ infill_overlap = 25% bridge_flow_ratio = 1 slice_closing_radius = 0.049 resolution = 0 -xy_size_compensation = 0 +xy_size_compensation = -0.05 elefant_foot_compensation = 0.3 clip_multipart_objects = 0 #output @@ -138,47 +148,50 @@ gcode_label_objects = 0 output_filename_format = {input_filename_base}_{filament_type[0]}.gcode -[print:0.2mm Standard @PROTON_X] +[print:*common 0.2mm Standard @INAT*] inherits = *common* -[print:0.2mm Strong @PROTON_X] +[print:*common 0.2mm Strong @INAT*] inherits = *common* fill_density = 50% perimeters = 6 -[print:0.2mm Advanced Material @PROTON_X] +[print:*common 0.2mm Advanced Material @INAT*] inherits = *common* bottom_solid_layers = 5 top_solid_layers = 6 skirts = 0 -brim_width = 30 +brim_width = 20 infill_speed = 60 support_material_speed = 60 travel_speed = 100 first_layer_speed = 20 elefant_foot_compensation = 0 -[print:0.12mm Fine @PROTON_X] +[print:*common 0.12mm Fine @INAT*] inherits = *common* +layer_height = 0.12 bottom_solid_layers = 7 top_solid_layers = 7 infill_every_layers = 2 perimeter_speed = 50 infill_speed = 50 -[print:0.32mm Draft @PROTON_X] +[print:*common 0.32mm Draft @INAT*] inherits = *common* +layer_height = 0.32 perimeter_speed = 80 external_perimeter_speed = 75% infill_speed = 100 top_solid_infill_speed = 60 fill_density = 15% +support_material_style = snug ### -### PRINTER DEFINITIONS +### COMMON PRINTER DEFINITIONS ### -[printer:*common*] +[printer:*proton_x_common*] printer_vendor = INAT s.r.o. default_filament_profile = "PLA @PROTON_X" #general @@ -206,14 +219,14 @@ machine_max_feedrate_z = 10,10 machine_max_feedrate_e = 100,100 machine_max_acceleration_x = 500,500 machine_max_acceleration_y = 500,500 -machine_max_acceleration_z = 100,100 -machine_max_acceleration_e = 2000,2000 +machine_max_acceleration_z = 200,200 +machine_max_acceleration_e = 8000,8000 machine_max_acceleration_extruding = 1000,1000 -machine_max_acceleration_retracting = 1500,1500 +machine_max_acceleration_retracting = 8000,8000 machine_max_jerk_x = 8,8 machine_max_jerk_y = 8,8 -machine_max_jerk_z = 1,1 -machine_max_jerk_e = 2.5,2.5 +machine_max_jerk_z = 3,3 +machine_max_jerk_e = 10,10 machine_min_extruding_rate = 5 #extruder 1 nozzle_diameter = 0.4 @@ -233,24 +246,65 @@ wipe = 1 retract_before_wipe = 100% -[printer:Proton X Rail] -inherits = *common* -printer_model = PROTON_X_RAIL -printer_variant = 0.4 -default_print_profile = 0.2mm Standard @PROTON_X -gcode_flavor = marlin -machine_max_acceleration_y = 800,800 - -[printer:Proton X Rod] -inherits = *common* -printer_model = PROTON_X_ROD -printer_variant = 0.4 -default_print_profile = 0.2mm Standard @PROTON_X +[printer:*proton_xe750_common*] +printer_vendor = INAT s.r.o. +default_filament_profile = "PLA @PROTON_XE750" +#general +printer_technology = FFF +bed_shape = 0x0,600x0,600x500,0x500 +max_print_height = 750 +z_offset = 0 +extruders_count = 2 gcode_flavor = marlin +silent_mode = 0 +remaining_times = 1 +use_relative_e_distances = 0 +use_firmware_retraction = 0 +use_volumetric_e = 0 +variable_layer_height = 1 +#gcodes +start_gcode = G28 ;Home\nG0 Z10 F1000\nG29\nG0 X0 Y0 Z30 F6000\nM84 E\nM0\nG1 Z15.0 F6000 ;Move the platform down 15mm\n +end_gcode = M400\nM104 S0\nM140 S0\nM107\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG28 X R5\nG0 Y300 F2000\nM84\nG4 S180\nM81 S30\n +color_change_gcode = M600 +#limits +machine_limits_usage = emit_to_gcode +machine_max_feedrate_x = 200,200 +machine_max_feedrate_y = 200,200 +machine_max_feedrate_z = 10,10 +machine_max_feedrate_e = 100,100 +machine_max_acceleration_x = 1500,1500 +machine_max_acceleration_y = 1500,1500 +machine_max_acceleration_z = 500,500 +machine_max_acceleration_e = 20000,20000 +machine_max_acceleration_extruding = 2000,2000 +machine_max_acceleration_retracting = 8000,8000 +machine_max_jerk_x = 12,12 +machine_max_jerk_y = 12,12 +machine_max_jerk_z = 3,3 +machine_max_jerk_e = 20,20 +machine_min_extruding_rate = 5 +#extruder 1 +nozzle_diameter = 0.4,0.4 +min_layer_height = 0.05,0.05 +max_layer_height = 0.33,0.33 +extruder_offset = 0x0,0x0 +retract_length = 1.5,1.5 +retract_lift = 0.6,0.6 +retract_lift_above = 0,0 +retract_lift_below = 0,0 +retract_speed = 45,45 +deretract_speed = 0,0 +retract_restart_extra = 0,0 +retract_before_travel = 2,2 +retract_layer_change = 0,0 +wipe = 1,1 +retract_before_wipe = 100%,100% +retract_length_toolchange = 37,37 +extruder_colour = #33CC33;#3399FF ### -### MATERIAL DEFINITIONS +### COMMON MATERIAL DEFINITIONS ### [filament:*common*] @@ -272,7 +326,7 @@ min_print_speed = 10 filament_soluble = 0 -[filament:PLA @PROTON_X] +[filament:*common PLA @INAT*] inherits = *common* temperature = 210 bed_temperature = 60 @@ -281,11 +335,12 @@ first_layer_bed_temperature = 60 filament_type = PLA filament_cost = 20 filament_density = 1.25 +fan_always_on = 1 min_fan_speed = 50 max_fan_speed = 100 -[filament:PETG @PROTON_X] +[filament:*common PETG @INAT*] inherits = *common* temperature = 240 bed_temperature = 80 @@ -294,10 +349,11 @@ first_layer_bed_temperature = 80 filament_type = PETG filament_cost = 25 filament_density = 1.27 -min_fan_speed = 0 +fan_always_on = 1 +min_fan_speed = 25 max_fan_speed = 50 -[filament:ABS @PROTON_X] +[filament:*common ABS @INAT*] inherits = *common* temperature = 235 bed_temperature = 100 @@ -309,7 +365,7 @@ filament_density = 1.01 cooling = 0 bridge_fan_speed = 0 -[filament:ASA @PROTON_X] +[filament:*common ASA @INAT*] inherits = *common* temperature = 240 bed_temperature = 110 @@ -320,7 +376,7 @@ filament_cost = 22 filament_density = 1.07 cooling = 0 -[filament:TPE @PROTON_X] +[filament:*common TPE @INAT*] inherits = *common* temperature = 220 bed_temperature = 40 @@ -334,7 +390,7 @@ max_fan_speed = 50 filament_retract_length = 0.8 filament_retract_speed = 25 -[filament:HIPS @PROTON_X] +[filament:*common HIPS @INAT*] inherits = *common* temperature = 245 bed_temperature = 100 @@ -347,7 +403,7 @@ min_fan_speed = 0 max_fan_speed = 50 filament_soluble = 1 -[filament:Nylon @PROTON_X] +[filament:*common Nylon @INAT*] inherits = *common* temperature = 235 bed_temperature = 130 @@ -359,19 +415,19 @@ filament_density = 1.01 cooling = 0 bridge_fan_speed = 0 -[filament:PC @PROTON_X] +[filament:*common PC @INAT*] inherits = *common* temperature = 270 -bed_temperature = 130 +bed_temperature = 115 first_layer_temperature = 270 -first_layer_bed_temperature = 130 +first_layer_bed_temperature = 115 filament_type = PC filament_cost = 65 filament_density = 1.19 cooling = 0 bridge_fan_speed = 0 -[filament:CPE @PROTON_X] +[filament:*common CPE @INAT*] inherits = *common* temperature = 280 bed_temperature = 90 @@ -383,7 +439,7 @@ filament_density = 1.27 cooling = 0 bridge_fan_speed = 0 -[filament:PEEK @PROTON_X] +[filament:*common PEEK @INAT*] inherits = *common* temperature = 440 bed_temperature = 150 @@ -395,7 +451,7 @@ filament_density = 1.3 cooling = 0 bridge_fan_speed = 0 -[filament:PEI @PROTON_X] +[filament:*common PEI @INAT*] inherits = *common* temperature = 400 bed_temperature = 150 @@ -407,7 +463,7 @@ filament_density = 1.27 cooling = 0 bridge_fan_speed = 0 -[filament:Polymaker PolyMide CoPA @PROTON_X] +[filament:*common Polymaker PolyMide CoPA @INAT*] inherits = *common* filament_vendor = Polymaker temperature = 265 @@ -419,7 +475,7 @@ filament_cost = 93 filament_density = 1.12 cooling = 0 -[filament:Polymaker PolyMide PA6-CF @PROTON_X] +[filament:*common Polymaker PolyMide PA6-CF @INAT*] inherits = *common* filament_vendor = Polymaker temperature = 300 @@ -431,7 +487,7 @@ filament_cost = 95 filament_density = 1.17 cooling = 0 -[filament:Polymaker PolyMide PA6-GF @PROTON_X] +[filament:*common Polymaker PolyMide PA6-GF @INAT*] inherits = *common* filament_vendor = Polymaker temperature = 300 @@ -443,20 +499,21 @@ filament_cost = 95 filament_density = 1.2 cooling = 0 -[filament:Devil Design PETG @PROTON_X] +[filament:*common Devil Design PETG @INAT*] inherits = *common* filament_vendor = Devil Design -temperature = 250 +temperature = 245 bed_temperature = 80 -first_layer_temperature = 250 +first_layer_temperature = 245 first_layer_bed_temperature = 80 filament_type = PETG filament_cost = 22 filament_density = 1.23 -min_fan_speed = 0 +fan_always_on = 1 +min_fan_speed = 25 max_fan_speed = 50 -[filament:Filament PM PETG FRJet @PROTON_X] +[filament:*common Filament PM PETG FRJet @INAT*] inherits = *common* filament_vendor = Filament PM temperature = 250 @@ -467,3 +524,207 @@ filament_type = PETG filament_cost = 45.5 filament_density = 1.27 cooling = 0 + + +###### +###### PROTON X PRINTERS +###### + +[printer:Proton X Rail] +inherits = *proton_x_common* +printer_model = PROTON_X_RAIL +printer_variant = 0.4 +default_print_profile = 0.2mm Standard @PROTON_X +gcode_flavor = marlin +machine_max_acceleration_x = 800,800 +machine_max_acceleration_y = 800,800 +machine_max_jerk_x = 10,10 +machine_max_jerk_y = 10,10 + +[printer:Proton X Rod] +inherits = *proton_x_common* +printer_model = PROTON_X_ROD +printer_variant = 0.4 +default_print_profile = 0.2mm Standard @PROTON_X +gcode_flavor = marlin + +[print:0.2mm Standard @PROTON_X] +inherits = *common 0.2mm Standard @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[print:0.2mm Strong @PROTON_X] +inherits = *common 0.2mm Strong @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[print:0.2mm Advanced Material @PROTON_X] +inherits = *common 0.2mm Advanced Material @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[print:0.12mm Fine @PROTON_X] +inherits = *common 0.12mm Fine @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[print:0.32mm Draft @PROTON_X] +inherits = *common 0.32mm Draft @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + + + +[filament:PLA @PROTON_X] +inherits =*common PLA @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:PETG @PROTON_X] +inherits =*common PETG @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:ABS @PROTON_X] +inherits =*common ABS @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:ASA @PROTON_X] +inherits =*common ASA @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:TPE @PROTON_X] +inherits =*common TPE @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:HIPS @PROTON_X] +inherits =*common HIPS @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Nylon @PROTON_X] +inherits =*common Nylon @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:PC @PROTON_X] +inherits =*common PC @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:CPE @PROTON_X] +inherits =*common CPE @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:PEEK @PROTON_X] +inherits =*common PEEK @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:PEI @PROTON_X] +inherits =*common PEI @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Polymaker PolyMide CoPA @PROTON_X] +inherits =*common Polymaker PolyMide CoPA @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Polymaker PolyMide PA6-CF @PROTON_X] +inherits =*common Polymaker PolyMide PA6-CF @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Polymaker PolyMide PA6-GF @PROTON_X] +inherits =*common Polymaker PolyMide PA6-GF @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Devil Design PETG @PROTON_X] +inherits =*common Devil Design PETG @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Filament PM PETG FRJet @PROTON_X] +inherits =*common Filament PM PETG FRJet @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + + + + +######### ######### +######### Proton XE 750 ######### +######### ######### + +[printer:Proton XE-750] +inherits = *proton_xe750_common* +printer_model = PROTON_XE750 +printer_variant = 0.4 +default_print_profile = 0.2mm Standard @PROTON_XE750 +gcode_flavor = marlin + + +[print:0.2mm Standard @PROTON_XE750] +inherits = *common 0.2mm Standard @INAT* +compatible_printers = "Proton XE-750" + +[print:0.2mm Strong @PROTON_XE750] +inherits = *common 0.2mm Strong @INAT* +compatible_printers = "Proton XE-750" + +[print:0.2mm Advanced Material @PROTON_XE750] +inherits = *common 0.2mm Advanced Material @INAT* +compatible_printers = "Proton XE-750" + +[print:0.12mm Fine @PROTON_XE750] +inherits = *common 0.12mm Fine @INAT* +compatible_printers = "Proton XE-750" + +[print:0.32mm Draft @PROTON_XE750] +inherits = *common 0.32mm Draft @INAT* +compatible_printers = "Proton XE-750" + + + + + + +[filament:*start_end_gcode @PROTON_XE750*] +start_filament_gcode = "; Filament start gcode BEGIN\nM104 S[temperature[current_extruder]]\nG4 S20\n; Filament start gcode END\n" +end_filament_gcode = "; Filament end gcode BEGIN\nG0 X-5 Y250 F10000\nM104 S{temperature[current_extruder] - 50}\n; Filament end gcode END\n" +compatible_printers = "Proton XE-750" + + + +[filament:PLA @PROTON_XE750] +inherits =*common PLA @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:PETG @PROTON_XE750] +inherits =*common PETG @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:ABS @PROTON_XE750] +inherits =*common ABS @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:ASA @PROTON_XE750] +inherits =*common ASA @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:TPE @PROTON_XE750] +inherits =*common TPE @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:HIPS @PROTON_XE750] +inherits =*common HIPS @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Nylon @PROTON_XE750] +inherits =*common Nylon @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:PC @PROTON_XE750] +inherits =*common PC @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:CPE @PROTON_XE750] +inherits =*common CPE @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:PEEK @PROTON_XE750] +inherits =*common PEEK @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:PEI @PROTON_XE750] +inherits =*common PEI @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Polymaker PolyMide CoPA @PROTON_XE750] +inherits =*common Polymaker PolyMide CoPA @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Polymaker PolyMide PA6-CF @PROTON_XE750] +inherits =*common Polymaker PolyMide PA6-CF @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Polymaker PolyMide PA6-GF @PROTON_XE750] +inherits =*common Polymaker PolyMide PA6-GF @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Devil Design PETG @PROTON_XE750] +inherits =*common Devil Design PETG @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Filament PM PETG FRJet @PROTON_XE750] +inherits =*common Filament PM PETG FRJet @INAT*; *start_end_gcode @PROTON_XE750* diff --git a/resources/profiles/INAT/PROTON_XE750_thumbnail.png b/resources/profiles/INAT/PROTON_XE750_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..114247ade2a5a7628fc1ffda97ea9a2bb4510cb7 GIT binary patch literal 36819 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 literal 0 HcmV?d00001 From 4dee789e9e8aa55e3d9e9c03619767b500b74d18 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 31 May 2022 08:39:15 +0200 Subject: [PATCH 006/102] Follow-up of b00c5504639bf8dae84f24e41438a3a964b6e466 - More robust fix for: Do not show legend and bottom slider when loading an invalid gcode file into GCodeViewer --- src/slic3r/GUI/GUI_Preview.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 6fff561ce..88bd42a66 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -954,7 +954,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); - bool gcode_preview_data_valid = !m_gcode_result->moves.empty() && !m_canvas->get_gcode_layers_zs().empty(); + bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); // Collect colors per extruder. std::vector colors; @@ -983,10 +983,11 @@ void Preview::load_print_as_fff(bool keep_z_range) if (gcode_preview_data_valid) { // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_result, colors); - m_left_sizer->Show(m_bottom_toolbar_panel); m_left_sizer->Layout(); Refresh(); zs = m_canvas->get_gcode_layers_zs(); + if (!zs.empty()) + m_left_sizer->Show(m_bottom_toolbar_panel); m_loaded = true; } else if (wxGetApp().is_editor()) { From fe9ad66e84c9ed96492911b44bedf1295253c88e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 1 Jun 2022 11:56:42 +0200 Subject: [PATCH 007/102] Disable ENABLE_OBJECT_MANIPULATOR_FOCUS --- src/libslic3r/Technologies.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 9f41196a1..16b2fda0e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -50,7 +50,7 @@ // Enable showing time estimate for travel moves in legend #define ENABLE_TRAVEL_TIME (1 && ENABLE_2_5_0_ALPHA1) // Enable not killing focus in object manipulator fields when hovering over 3D scene -#define ENABLE_OBJECT_MANIPULATOR_FOCUS (1 && ENABLE_2_5_0_ALPHA1) +#define ENABLE_OBJECT_MANIPULATOR_FOCUS (0 && ENABLE_2_5_0_ALPHA1) // Enable removal of wipe tower magic object_id equal to 1000 #define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) // Enable removal of legacy OpenGL calls From cf16dafad943b6869cc93ffa4b8c7440b6b21a09 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 1 Jun 2022 15:39:07 +0200 Subject: [PATCH 008/102] Fix PlaterWorker not calling yield from main thread Also fix UIThreadWorker not setting busy cursor --- src/slic3r/GUI/Jobs/PlaterWorker.hpp | 12 +++++++----- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 10 +++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp index 58bd1ec32..0fef3655e 100644 --- a/src/slic3r/GUI/Jobs/PlaterWorker.hpp +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -38,22 +38,24 @@ class PlaterWorker: public Worker { void update_status(int st, const std::string &msg = "") override { - wxWakeUpIdle(); ctl.update_status(st, msg); // If the worker is not using additional threads, the UI // is refreshed with this call. If the worker is running - // in it's own thread, the yield should not have any - // visible effects. - wxYieldIfNeeded(); + // in it's own thread, this will be one additional + // evaluation of the event loop which should have no visible + // effects. + call_on_main_thread([] { wxYieldIfNeeded(); }); } bool was_canceled() const override { return ctl.was_canceled(); } std::future call_on_main_thread(std::function fn) override { + auto ftr = ctl.call_on_main_thread(std::move(fn)); wxWakeUpIdle(); - return ctl.call_on_main_thread(std::move(fn)); + + return ftr; } } wctl{c}; diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index 610d205cf..91213c239 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -62,7 +62,15 @@ protected: std::future call_on_main_thread(std::function fn) override { - return std::async(std::launch::deferred, [fn]{ fn(); }); + std::future ftr = std::async(std::launch::deferred, [fn]{ fn(); }); + + // So, it seems that the destructor of std::future will not call the + // packaged function. The future needs to be accessed at least ones + // or waited upon. Calling wait() instead of get() will keep the + // returned future's state valid. + ftr.wait(); + + return ftr; } public: From ae377bd28cb7ff0fd267de106dc2a0516a090c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 23 May 2022 12:41:23 +0200 Subject: [PATCH 009/102] Fixed assert in Lightning infill (merging empty BoundingBoxes). --- src/libslic3r/Fill/Lightning/Generator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index e226fbbab..5e2838e46 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -125,7 +125,8 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined) below_outlines_bbox.merge(outlines_locator_bbox); - below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON)); + if (!current_lightning_layer.tree_roots.empty()) + below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON)); outlines_locator.set_bbox(below_outlines_bbox); outlines_locator.create(below_outlines, locator_cell_size); From e967d10788191f57058122f80eac64d6d234ebbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 24 May 2022 14:07:37 +0200 Subject: [PATCH 010/102] Added anchors for the Lightning infill to better connect the infill and perimeters. --- src/libslic3r/Fill/FillLightning.cpp | 23 +++++++++++-------- src/libslic3r/Fill/FillLightning.hpp | 9 ++++++-- .../Fill/Lightning/DistanceField.hpp | 2 +- src/libslic3r/Fill/Lightning/Generator.cpp | 9 ++------ src/libslic3r/Fill/Lightning/Layer.cpp | 5 ++-- src/libslic3r/Fill/Lightning/Layer.hpp | 2 +- src/libslic3r/Fill/Lightning/TreeNode.cpp | 10 ++++---- src/libslic3r/Fill/Lightning/TreeNode.hpp | 10 ++++---- 8 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/Fill/FillLightning.cpp b/src/libslic3r/Fill/FillLightning.cpp index 2ba6fe017..dd2189e6b 100644 --- a/src/libslic3r/Fill/FillLightning.cpp +++ b/src/libslic3r/Fill/FillLightning.cpp @@ -1,20 +1,25 @@ #include "../Print.hpp" +#include "../ShortestPath.hpp" #include "FillLightning.hpp" #include "Lightning/Generator.hpp" -#include "../Surface.hpp" - -#include -#include -#include -#include namespace Slic3r::FillLightning { -Polylines Filler::fill_surface(const Surface *surface, const FillParams ¶ms) +void Filler::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + Polylines &polylines_out) { - const Layer &layer = generator->getTreesForLayer(this->layer_id); - return layer.convertToLines(to_polygons(surface->expolygon), generator->infilll_extrusion_width()); + const Layer &layer = generator->getTreesForLayer(this->layer_id); + Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled(0.5 * this->spacing - this->overlap)); + + if (params.dont_connect() || fill_lines.size() <= 1) { + append(polylines_out, chain_polylines(std::move(fill_lines))); + } else + connect_infill(std::move(fill_lines), expolygon, polylines_out, this->spacing, params); } void GeneratorDeleter::operator()(Generator *p) { diff --git a/src/libslic3r/Fill/FillLightning.hpp b/src/libslic3r/Fill/FillLightning.hpp index 941392103..6e672783a 100644 --- a/src/libslic3r/Fill/FillLightning.hpp +++ b/src/libslic3r/Fill/FillLightning.hpp @@ -24,8 +24,13 @@ public: Generator *generator { nullptr }; protected: Fill* clone() const override { return new Filler(*this); } - // Perform the fill. - Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + + void _fill_surface_single(const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + Polylines &polylines_out) override; + // Let the G-code export reoder the infill lines. bool no_sort() const override { return false; } }; diff --git a/src/libslic3r/Fill/Lightning/DistanceField.hpp b/src/libslic3r/Fill/Lightning/DistanceField.hpp index d4a142c05..1a47ee6ca 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.hpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.hpp @@ -176,7 +176,7 @@ protected: const Point offset_loc = loc - m_grid_range.min; const size_t flat_idx = m_grid_size.x() * offset_loc.y() + offset_loc.x(); assert(offset_loc.x() >= 0 && offset_loc.y() >= 0); - assert(flat_idx < m_grid_size.y() * m_grid_size.x()); + assert(flat_idx < size_t(m_grid_size.y() * m_grid_size.x())); return flat_idx; } }; diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index 5e2838e46..4aba7202d 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -7,7 +7,6 @@ #include "../../ClipperUtils.hpp" #include "../../Layer.hpp" #include "../../Print.hpp" -#include "../../Surface.hpp" /* Possible future tasks/optimizations,etc.: * - Improve connecting heuristic to favor connecting to shorter trees @@ -54,8 +53,6 @@ Generator::Generator(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { m_overhang_per_layer.resize(print_object.layers().size()); - // FIXME: It can be adjusted to improve bonding between infill and perimeters. - const float infill_wall_offset = 0;// m_infill_extrusion_width; Polygons infill_area_above; //Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging. @@ -65,7 +62,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions()) for (const Surface& surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) - append(infill_area_here, infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); + infill_area_here.emplace_back(surface.expolygon); //Remove the part of the infill area that is already supported by the walls. Polygons overhang = diff(offset(infill_area_here, -float(m_wall_supporting_radius)), infill_area_above); @@ -84,8 +81,6 @@ const Layer& Generator::getTreesForLayer(const size_t& layer_id) const void Generator::generateTrees(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { m_lightning_layers.resize(print_object.layers().size()); - // FIXME: It can be adjusted to improve bonding between infill and perimeters. - const coord_t infill_wall_offset = 0;// m_infill_extrusion_width; std::vector infill_outlines(print_object.layers().size(), Polygons()); @@ -95,7 +90,7 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions()) for (const Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) - append(infill_outlines[layer_id], infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); + infill_outlines[layer_id].emplace_back(surface.expolygon); } // For various operations its beneficial to quickly locate nearby features on the polygon: diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index 0bd2a65c4..354623e51 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -433,15 +433,14 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan } #endif -// Returns 'added someting'. -Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const +Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_overlap) const { if (tree_roots.empty()) return {}; Polylines result_lines; for (const auto &tree : tree_roots) - tree->convertToPolylines(result_lines, line_width); + tree->convertToPolylines(result_lines, line_overlap); return intersection_pl(result_lines, limit_to_outline); } diff --git a/src/libslic3r/Fill/Lightning/Layer.hpp b/src/libslic3r/Fill/Lightning/Layer.hpp index 87431fb1c..e8c0a38b4 100644 --- a/src/libslic3r/Fill/Lightning/Layer.hpp +++ b/src/libslic3r/Fill/Lightning/Layer.hpp @@ -80,7 +80,7 @@ public: coord_t wall_supporting_radius ); - Polylines convertToLines(const Polygons& limit_to_outline, coord_t line_width) const; + Polylines convertToLines(const Polygons& limit_to_outline, coord_t line_overlap) const; coord_t getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location); diff --git a/src/libslic3r/Fill/Lightning/TreeNode.cpp b/src/libslic3r/Fill/Lightning/TreeNode.cpp index 9ef509611..982d47b10 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.cpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.cpp @@ -347,12 +347,12 @@ coord_t Node::prune(const coord_t& pruning_distance) return max_distance_pruned; } -void Node::convertToPolylines(Polylines &output, const coord_t line_width) const +void Node::convertToPolylines(Polylines &output, const coord_t line_overlap) const { Polylines result; result.emplace_back(); convertToPolylines(0, result); - removeJunctionOverlap(result, line_width); + removeJunctionOverlap(result, line_overlap); append(output, std::move(result)); } @@ -376,10 +376,10 @@ void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const } } -void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_width) const +void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_overlap) const { - const coord_t reduction = line_width / 2; // TODO make configurable? - size_t res_line_idx = 0; + const coord_t reduction = line_overlap; + size_t res_line_idx = 0; while (res_line_idx < result_lines.size()) { Polyline &polyline = result_lines[res_line_idx]; if (polyline.size() <= 1) { diff --git a/src/libslic3r/Fill/Lightning/TreeNode.hpp b/src/libslic3r/Fill/Lightning/TreeNode.hpp index 81c63f7f6..8791b4331 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.hpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.hpp @@ -46,7 +46,7 @@ public: { struct EnableMakeShared : public Node { - EnableMakeShared(Arg&&...arg) : Node(std::forward(arg)...) {} + explicit EnableMakeShared(Arg&&...arg) : Node(std::forward(arg)...) {} }; return std::make_shared(std::forward(arg)...); } @@ -179,16 +179,16 @@ public: */ bool hasOffspring(const NodeSPtr& to_be_checked) const; -protected: Node() = delete; // Don't allow empty contruction +protected: /*! * Construct a new node, either for insertion in a tree or as root. * \param p The physical location in the 2D layer that this node represents. * Connecting other nodes to this node indicates that a line segment should * be drawn between those two physical positions. */ - Node(const Point& p, const std::optional& last_grounding_location = std::nullopt); + explicit Node(const Point& p, const std::optional& last_grounding_location = std::nullopt); /*! * Copy this node and its entire sub-tree. @@ -239,7 +239,7 @@ public: * * \param output all branches in this tree connected into polylines */ - void convertToPolylines(Polylines &output, coord_t line_width) const; + void convertToPolylines(Polylines &output, coord_t line_overlap) const; /*! If this was ever a direct child of the root, it'll have a previous grounding location. * @@ -260,7 +260,7 @@ protected: */ void convertToPolylines(size_t long_line_idx, Polylines &output) const; - void removeJunctionOverlap(Polylines &polylines, coord_t line_width) const; + void removeJunctionOverlap(Polylines &polylines, coord_t line_overlap) const; bool m_is_root; Point m_p; From a47446574eb3f831907248dc841eb6f7684bbdb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 30 May 2022 15:13:42 +0200 Subject: [PATCH 011/102] Sets locales before any thread start participating in the GCode processing pipeline. Locales should be set once per any participating threads in tbb::parallel_pipeline. It should fix the issue with appearing comma instead of the decimal point in generated Gcode. --- src/libslic3r/GCode.cpp | 36 ++++++++++++++++++++++++++++++++++ src/libslic3r/LocalesUtils.hpp | 19 ++++++++++++++++++ src/libslic3r/Thread.cpp | 18 +++-------------- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index aef83f21f..1f7eaba24 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -35,6 +35,8 @@ #include "SVG.hpp" #include +#include +#include // Intel redesigned some TBB interface considerably when merging TBB with their oneAPI set of libraries, see GH #7332. // We are using quite an old TBB 2017 U7. Before we update our build servers, let's use the old API, which is deprecated in up to date TBB. @@ -1488,6 +1490,32 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); } +// For unknown reasons and in sporadic cases when GCode export is processing, some participating thread +// in tbb::parallel_pipeline has not set locales to "C", probably because this thread is newly spawned. +// So in this class method on_scheduler_entry is called for every thread before it starts participating +// in tbb::parallel_pipeline to ensure that locales are set correctly + +// For tbb::parallel_pipeline, it seems that on_scheduler_entry is called for every layer and every filter. +// We ensure using thread-local storage that locales will be set to "C" just once for any participating thread. +class TBBLocalesSetter : public tbb::task_scheduler_observer +{ +public: + TBBLocalesSetter() { this->observe(true); } + ~TBBLocalesSetter() override = default; + + void on_scheduler_entry(bool is_worker) override + { + if (bool &is_locales_sets = m_is_locales_sets.local(); !is_locales_sets) { + // Set locales of the worker thread to "C". + set_c_locales(); + is_locales_sets = true; + } + } + +private: + tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets; +}; + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. @@ -1531,6 +1559,10 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. + // Handler is unregistered when the destructor is called. + TBBLocalesSetter locales_setter; + // The pipeline elements are joined using const references, thus no copying is performed. output_stream.find_replace_supress(); if (m_spiral_vase && m_find_replace) @@ -1584,6 +1616,10 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. + // Handler is unregistered when the destructor is called. + TBBLocalesSetter locales_setter; + // The pipeline elements are joined using const references, thus no copying is performed. output_stream.find_replace_supress(); if (m_spiral_vase && m_find_replace) diff --git a/src/libslic3r/LocalesUtils.hpp b/src/libslic3r/LocalesUtils.hpp index f63c3572f..ce8030ceb 100644 --- a/src/libslic3r/LocalesUtils.hpp +++ b/src/libslic3r/LocalesUtils.hpp @@ -43,6 +43,25 @@ std::string float_to_string_decimal_point(double value, int precision = -1); //std::string float_to_string_decimal_point(float value, int precision = -1); double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr); +// Set locales to "C". +inline void set_c_locales() +{ +#ifdef _WIN32 + _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + std::setlocale(LC_ALL, "C"); +#else + // We are leaking some memory here, because the newlocale() produced memory will never be released. + // This is not a problem though, as there will be a maximum one worker thread created per physical thread. + uselocale(newlocale( +#ifdef __APPLE__ + LC_ALL_MASK +#else // some Unix / Linux / BSD + LC_ALL +#endif + , "C", nullptr)); +#endif +} + } // namespace Slic3r #endif // slic3r_LocalesUtils_hpp_ diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 4e7bd073a..c099f8de6 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -15,6 +15,7 @@ #include "Thread.hpp" #include "Utils.hpp" +#include "LocalesUtils.hpp" namespace Slic3r { @@ -234,21 +235,8 @@ void name_tbb_thread_pool_threads_set_locale() std::ostringstream name; name << "slic3r_tbb_" << range.begin(); set_current_thread_name(name.str().c_str()); - // Set locales of the worker thread to "C". -#ifdef _WIN32 - _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); - std::setlocale(LC_ALL, "C"); -#else - // We are leaking some memory here, because the newlocale() produced memory will never be released. - // This is not a problem though, as there will be a maximum one worker thread created per physical thread. - uselocale(newlocale( -#ifdef __APPLE__ - LC_ALL_MASK -#else // some Unix / Linux / BSD - LC_ALL -#endif - , "C", nullptr)); -#endif + // Set locales of the worker thread to "C". + set_c_locales(); } }); } From 11f6c67e7c3f6c346b8720afe5087fd5ded1914c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 9 May 2022 11:42:14 +0200 Subject: [PATCH 012/102] Added detection for corrupted PrusaSlicer.ini and fixed showing instructions on how to recover from it (#8217). Previously when PrusaSlicer.ini was just partly corrupted, it could happen that PrusaSlicer.ini wasn't detected as corrupted, and it could cause that instruction on how to recover from this state wasn't shown, and PrusaSlicer crashed because wrong data from PrusaSlicer.ini was read. --- src/libslic3r/AppConfig.cpp | 49 +++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 928febffd..82c0a72a6 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -230,8 +230,13 @@ static std::string appconfig_md5_hash_line(const std::string_view data) return "# MD5 checksum " + md5_digest_str + "\n"; }; +struct ConfigFileInfo { + bool correct_checksum {false}; + bool contains_null {false}; +}; + // Assume that the last line with the comment inside the config file contains a checksum and that the user didn't modify the config file. -static bool verify_config_file_checksum(boost::nowide::ifstream &ifs) +static ConfigFileInfo check_config_file_and_verify_checksum(boost::nowide::ifstream &ifs) { auto read_whole_config_file = [&ifs]() -> std::string { std::stringstream ss; @@ -240,7 +245,8 @@ static bool verify_config_file_checksum(boost::nowide::ifstream &ifs) }; ifs.seekg(0, boost::nowide::ifstream::beg); - std::string whole_config = read_whole_config_file(); + const std::string whole_config = read_whole_config_file(); + const bool contains_null = whole_config.find_first_of('\0') != std::string::npos; // The checksum should be on the last line in the config file. if (size_t last_comment_pos = whole_config.find_last_of('#'); last_comment_pos != std::string::npos) { @@ -249,9 +255,9 @@ static bool verify_config_file_checksum(boost::nowide::ifstream &ifs) // When the checksum isn't found, the checksum was not saved correctly, it was removed or it is an older config file without the checksum. // If the checksum is incorrect, then the file was either not saved correctly or modified. if (std::string_view(whole_config.c_str() + last_comment_pos, whole_config.size() - last_comment_pos) == appconfig_md5_hash_line({ whole_config.data(), last_comment_pos })) - return true; + return {true, contains_null}; } - return false; + return {false, contains_null}; } #endif @@ -269,14 +275,25 @@ std::string AppConfig::load(const std::string &path) ifs.open(path); #ifdef WIN32 // Verify the checksum of the config file without taking just for debugging purpose. - if (!verify_config_file_checksum(ifs)) - BOOST_LOG_TRIVIAL(info) << "The configuration file " << path << - " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; + const ConfigFileInfo config_file_info = check_config_file_and_verify_checksum(ifs); + if (!config_file_info.correct_checksum) + BOOST_LOG_TRIVIAL(info) + << "The configuration file " << path + << " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; + + if (!config_file_info.correct_checksum && config_file_info.contains_null) { + BOOST_LOG_TRIVIAL(info) << "The configuration file " + path + " is corrupted, because it is contains null characters."; + throw Slic3r::CriticalException("The configuration file contains null characters."); + } ifs.seekg(0, boost::nowide::ifstream::beg); #endif - pt::read_ini(ifs, tree); - } catch (pt::ptree_error& ex) { + try { + pt::read_ini(ifs, tree); + } catch (pt::ptree_error &ex) { + throw Slic3r::CriticalException(ex.what()); + } + } catch (Slic3r::CriticalException &ex) { #ifdef WIN32 // The configuration file is corrupted, try replacing it with the backup configuration. ifs.close(); @@ -284,29 +301,29 @@ std::string AppConfig::load(const std::string &path) if (boost::filesystem::exists(backup_path)) { // Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct. boost::nowide::ifstream backup_ifs(backup_path); - if (!verify_config_file_checksum(backup_ifs)) { - BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", path, backup_path); + if (const ConfigFileInfo config_file_info = check_config_file_and_verify_checksum(backup_ifs); !config_file_info.correct_checksum || config_file_info.contains_null) { + BOOST_LOG_TRIVIAL(error) << format(R"(Both "%1%" and "%2%" are corrupted. It isn't possible to restore configuration from the backup.)", path, backup_path); backup_ifs.close(); boost::filesystem::remove(backup_path); } else if (std::string error_message; copy_file(backup_path, path, error_message, false) != SUCCESS) { - BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", path, backup_path, error_message); + BOOST_LOG_TRIVIAL(error) << format(R"(Configuration file "%1%" is corrupted. Failed to restore from backup "%2%": %3%)", path, backup_path, error_message); backup_ifs.close(); boost::filesystem::remove(backup_path); } else { - BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", path, backup_path); + BOOST_LOG_TRIVIAL(info) << format(R"(Configuration file "%1%" was corrupted. It has been successfully restored from the backup "%2%".)", path, backup_path); // Try parse configuration file after restore from backup. try { ifs.open(path); pt::read_ini(ifs, tree); recovered = true; } catch (pt::ptree_error& ex) { - BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", path, ex.what()); + BOOST_LOG_TRIVIAL(info) << format(R"(Failed to parse configuration file "%1%" after it has been restored from backup: %2%)", path, ex.what()); } } } else #endif // WIN32 - BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", path, ex.what()); - if (! recovered) { + BOOST_LOG_TRIVIAL(info) << format(R"(Failed to parse configuration file "%1%": %2%)", path, ex.what()); + if (!recovered) { // Report the initial error of parsing PrusaSlicer.ini. // Error while parsing config file. We'll customize the error message and rethrow to be displayed. // ! But to avoid the use of _utf8 (related to use of wxWidgets) From ebe411aefb9fbc2d72cf23d5e191bcfc2d4fd452 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 8 Oct 2021 14:32:02 +0200 Subject: [PATCH 013/102] Tech ENABLE_WORLD_COORDINATE - 1st installment 1) Added combo to select world/local coordinate to part manipulator in sidebar 2) Gizmo move oriented in dependence of the selected coordinate system 3) Sidebar hints for position oriented in dependence of the selected coordinate system Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 61 +++++++++++++++-- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 4 ++ src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 83 ++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 26 ++++--- src/slic3r/GUI/Selection.cpp | 18 +++++ 6 files changed, 170 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 16b2fda0e..302c62879 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -69,6 +69,8 @@ #define ENABLE_SHOW_TOOLPATHS_COG (1 && ENABLE_2_5_0_ALPHA1) // Enable recalculating toolpaths when switching to/from volumetric rate visualization #define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) +// Enable editing volumes transformation in world coordinates and instances in local coordinates +#define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6ab87150b..1cc35be7f 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -444,8 +444,13 @@ void ObjectManipulation::Show(const bool show) if (show) { // Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only. - bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple; - m_word_local_combo->Show(show_world_local_combo); +#if ENABLE_WORLD_COORDINATE + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); +#else + bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple; +#endif // ENABLE_WORLD_COORDINATE + m_word_local_combo->Show(show_world_local_combo); m_empty_str->Show(!show_world_local_combo); } } @@ -529,7 +534,9 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (selection.is_single_full_instance()) { // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if !ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); +#endif // !ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. if (m_world_coordinates && ! m_uniform_scale && @@ -540,14 +547,20 @@ void ObjectManipulation::update_settings_value(const Selection& selection) } if (m_world_coordinates) { - m_new_rotate_label_string = L("Rotate"); +#if ENABLE_WORLD_COORDINATE + m_new_position = volume->get_instance_offset(); +#endif // ENABLE_WORLD_COORDINATE + m_new_rotate_label_string = L("Rotate"); m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); - m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.; + m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; } else { - m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); - m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); +#if ENABLE_WORLD_COORDINATE + m_new_position = Vec3d::Zero(); +#endif // ENABLE_WORLD_COORDINATE + m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); + m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); m_new_scale = volume->get_instance_scaling_factor() * 100.; } @@ -566,10 +579,29 @@ void ObjectManipulation::update_settings_value(const Selection& selection) else if (selection.is_single_modifier() || selection.is_single_volume()) { // the selection contains a single volume const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_WORLD_COORDINATE + if (m_world_coordinates) { + const Geometry::Transformation trafo(volume->world_matrix()); + + const Vec3d& offset = trafo.get_offset(); + const Vec3d& rotation = trafo.get_rotation(); + const Vec3d& scaling_factor = trafo.get_scaling_factor(); + const Vec3d& m = trafo.get_mirror(); + + m_new_position = offset; + m_new_rotation = rotation * (180.0 / M_PI); + m_new_scale = scaling_factor * 100.0; + m_new_size = volume->bounding_box().size().cwiseProduct(scaling_factor); + } + else { +#endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.; - m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); + m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); +#if ENABLE_WORLD_COORDINATE + } +#endif // ENABLE_WORLD_COORDINATE m_new_enabled = true; } else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { @@ -815,7 +847,11 @@ void ObjectManipulation::change_position_value(int axis, double value) auto canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); selection.setup_cache(); +#if ENABLE_WORLD_COORDINATE + selection.translate(position - m_cache.position, !m_world_coordinates); +#else selection.translate(position - m_cache.position, selection.requires_local_axes()); +#endif // ENABLE_WORLD_COORDINATE canvas->do_move(L("Set Position")); m_cache.position = position; @@ -997,6 +1033,17 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) m_uniform_scale = new_value; } +#if ENABLE_WORLD_COORDINATE +void ObjectManipulation::set_world_coordinates(const bool world_coordinates) +{ + m_world_coordinates = world_coordinates; + this->UpdateAndShow(true); + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + canvas->set_as_dirty(); + canvas->request_extra_frame(); +} +#endif // ENABLE_WORLD_COORDINATE + void ObjectManipulation::msw_rescale() { const int em = wxGetApp().em_unit(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index a15c72fb8..6d0383038 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -183,7 +183,11 @@ public: void set_uniform_scaling(const bool uniform_scale); bool get_uniform_scaling() const { return m_uniform_scale; } // Does the object manipulation panel work in World or Local coordinates? +#if ENABLE_WORLD_COORDINATE + void set_world_coordinates(const bool world_coordinates); +#else void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); } +#endif // ENABLE_WORLD_COORDINATE bool get_world_coordinates() const { return m_world_coordinates; } void reset_cache() { m_cache.reset(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 9dfb8bc9e..22d777d2c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -2,6 +2,9 @@ #include "GLGizmoMove.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" +#if ENABLE_WORLD_COORDINATE +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -22,15 +25,15 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filenam std::string GLGizmoMove3D::get_tooltip() const { const Selection& selection = m_parent.get_selection(); - bool show_position = selection.is_single_full_instance(); + const bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); if (m_hover_id == 0 || m_grabbers[0].dragging) - return "X: " + format(show_position ? position(0) : m_displacement(0), 2); + return "X: " + format(show_position ? position.x() : m_displacement.x(), 2); else if (m_hover_id == 1 || m_grabbers[1].dragging) - return "Y: " + format(show_position ? position(1) : m_displacement(1), 2); + return "Y: " + format(show_position ? position.y() : m_displacement.y(), 2); else if (m_hover_id == 2 || m_grabbers[2].dragging) - return "Z: " + format(show_position ? position(2) : m_displacement(2), 2); + return "Z: " + format(show_position ? position.z() : m_displacement.z(), 2); else return ""; } @@ -80,10 +83,18 @@ void GLGizmoMove3D::on_start_dragging() m_displacement = Vec3d::Zero(); const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); +#if ENABLE_WORLD_COORDINATE + const Vec3d center = box.center(); + m_starting_drag_position = center + m_grabbers[m_hover_id].center; + m_starting_box_center = center; + m_starting_box_bottom_center = center; + m_starting_box_bottom_center.z() = box.min.z(); +#else m_starting_drag_position = m_grabbers[m_hover_id].center; m_starting_box_center = box.center(); m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center(2) = box.min(2); + m_starting_box_bottom_center.z() = box.min.z(); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_stop_dragging() @@ -119,7 +130,25 @@ void GLGizmoMove3D::on_render() const BoundingBoxf3& box = selection.get_bounding_box(); const Vec3d& center = box.center(); +#if ENABLE_WORLD_COORDINATE + glsafe(::glPushMatrix()); + transform_to_local(selection); + const Vec3d zero = Vec3d::Zero(); + const Vec3d half_box_size = 0.5 * box.size(); + + // x axis + m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 }; + m_grabbers[0].color = AXES_COLOR[0]; + + // y axis + m_grabbers[1].center = { 0.0, half_box_size.y() + Offset, 0.0 }; + m_grabbers[1].color = AXES_COLOR[1]; + + // z axis + m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset }; + m_grabbers[2].color = AXES_COLOR[2]; +#else // x axis m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; m_grabbers[0].color = AXES_COLOR[0]; @@ -131,6 +160,7 @@ void GLGizmoMove3D::on_render() // z axis m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset }; m_grabbers[2].color = AXES_COLOR[2]; +#endif // ENABLE_WORLD_COORDINATE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); @@ -183,7 +213,11 @@ void GLGizmoMove3D::on_render() if (m_grabbers[i].enabled) { glsafe(::glColor4fv(AXES_COLOR[i].data())); ::glBegin(GL_LINES); +#if ENABLE_WORLD_COORDINATE + ::glVertex3dv(zero.data()); +#else ::glVertex3dv(center.data()); +#endif // ENABLE_WORLD_COORDINATE ::glVertex3dv(m_grabbers[i].center.data()); glsafe(::glEnd()); } @@ -225,7 +259,11 @@ void GLGizmoMove3D::on_render() #else glsafe(::glColor4fv(AXES_COLOR[m_hover_id].data())); ::glBegin(GL_LINES); +#if ENABLE_WORLD_COORDINATE + ::glVertex3dv(zero.data()); +#else ::glVertex3dv(center.data()); +#endif // ENABLE_WORLD_COORDINATE ::glVertex3dv(m_grabbers[m_hover_id].center.data()); glsafe(::glEnd()); @@ -243,36 +281,50 @@ void GLGizmoMove3D::on_render() render_grabber_extension((Axis)m_hover_id, box, false); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR } + +#if ENABLE_WORLD_COORDINATE + glsafe(::glPopMatrix()); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_WORLD_COORDINATE + const Selection& selection = m_parent.get_selection(); + const BoundingBoxf3& box = selection.get_bounding_box(); + glsafe(::glPushMatrix()); + transform_to_local(selection); +#else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); +#endif // ENABLE_WORLD_COORDINATE render_grabbers_for_picking(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(X, box, true); render_grabber_extension(Y, box, true); render_grabber_extension(Z, box, true); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE + glsafe(::glPopMatrix()); +#endif // ENABLE_WORLD_COORDINATE } double GLGizmoMove3D::calc_projection(const UpdateData& data) const { double projection = 0.0; - Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; - double len_starting_vec = starting_vec.norm(); + const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; + const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting_drag_position; + const Vec3d inters_vec = inters - m_starting_drag_position; // finds projection of the vector along the staring direction projection = inters_vec.dot(starting_vec.normalized()); @@ -345,5 +397,18 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box } #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE +void GLGizmoMove3D::transform_to_local(const Selection& selection) const +{ + const Vec3d center = selection.get_bounding_box().center(); + glsafe(::glTranslated(center.x(), center.y(), center.z())); + + if (!wxGetApp().obj_manipul()->get_world_coordinates()) { + const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +} +#endif // ENABLE_WORLD_COORDINATE + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 6a618c3e4..db9d3ac74 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -7,6 +7,10 @@ namespace Slic3r { namespace GUI { +#if ENABLE_WORLD_COORDINATE + class Selection; +#endif // ENABLE_WORLD_COORDINATE + class GLGizmoMove3D : public GLGizmoBase { static const double Offset; @@ -51,17 +55,23 @@ public: void data_changed() override; protected: - bool on_init() override; - std::string on_get_name() const override; - bool on_is_activable() const override; - void on_start_dragging() override; - void on_stop_dragging() override; - void on_dragging(const UpdateData& data) override; - void on_render() override; - void on_render_for_picking() override; + virtual bool on_init() override; + virtual std::string on_get_name() const override; + virtual bool on_is_activable() const override; + virtual void on_start_dragging() override; + virtual void on_stop_dragging() override; +#if !ENABLE_WORLD_COORDINATE + virtual void on_start_dragging() override; +#endif // !ENABLE_WORLD_COORDINATE + virtual void on_dragging(const UpdateData& data) override; + virtual void on_render() override; + virtual void on_render_for_picking() override; private: double calc_projection(const UpdateData& data) const; +#if ENABLE_WORLD_COORDINATE + void transform_to_local(const Selection& selection) const; +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index e7c4e1763..ae261fc7a 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -699,13 +699,31 @@ void Selection::translate(const Vec3d& displacement, bool local) if (local) v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); else { +#if ENABLE_WORLD_COORDINATE + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Vec3d local_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix() * volume_data.get_instance_mirror_matrix()).inverse() * displacement; + v.set_volume_offset(volume_data.get_volume_position() + local_displacement); +#else const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); +#endif // ENABLE_WORLD_COORDINATE } } else if (m_mode == Instance) { +#if ENABLE_WORLD_COORDINATE + if (is_from_fully_selected_instance(i)) { + if (local) { + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; + v.set_instance_offset(volume_data.get_instance_position() + world_displacement); + } + else + v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); + } +#else if (is_from_fully_selected_instance(i)) v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); +#endif // ENABLE_WORLD_COORDINATE else { const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); From 51e80f7049f32d71fc8eaa835a4da9fd38ea856f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 8 Oct 2021 14:43:46 +0200 Subject: [PATCH 014/102] Fixed syntax error introduced with 116f928903725485d0d5c690c4906fb083807dea Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index db9d3ac74..2c9e94f07 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -8,7 +8,7 @@ namespace Slic3r { namespace GUI { #if ENABLE_WORLD_COORDINATE - class Selection; +class Selection; #endif // ENABLE_WORLD_COORDINATE class GLGizmoMove3D : public GLGizmoBase From 61e7eb4adec03b5353378b5c025a6af51d395bd7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 11 Oct 2021 09:52:39 +0200 Subject: [PATCH 015/102] Tech ENABLE_WORLD_COORDINATE - Modified text of tooltips for Gizmo Move Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 22d777d2c..2e943bbd8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -26,6 +26,25 @@ std::string GLGizmoMove3D::get_tooltip() const { const Selection& selection = m_parent.get_selection(); const bool show_position = selection.is_single_full_instance(); +#if ENABLE_WORLD_COORDINATE + const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); + Vec3d position = Vec3d::Zero(); + if (!world_coordinates) { + if (selection.is_single_modifier() || selection.is_single_volume() || selection.is_wipe_tower()) + position = selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_offset(); + } + else + position = selection.get_bounding_box().center(); + + if (m_hover_id == 0) + return m_grabbers[0].dragging ? "DX: " + format(m_displacement.x(), 2) : "X: " + format(position.x(), 2); + else if (m_hover_id == 1) + return m_grabbers[1].dragging ? "DY: " + format(m_displacement.y(), 2) : "Y: " + format(position.y(), 2); + else if (m_hover_id == 2) + return m_grabbers[2].dragging ? "DZ: " + format(m_displacement.z(), 2) : "Z: " + format(position.z(), 2); + else + return ""; +#else const Vec3d& position = selection.get_bounding_box().center(); if (m_hover_id == 0 || m_grabbers[0].dragging) @@ -36,6 +55,7 @@ std::string GLGizmoMove3D::get_tooltip() const return "Z: " + format(show_position ? position.z() : m_displacement.z(), 2); else return ""; +#endif // ENABLE_WORLD_COORDINATE } bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) { @@ -317,7 +337,7 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); + const Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) From ca5742c40140ab3b02bcad1ed1be66841ece2e7f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 Oct 2021 11:07:31 +0200 Subject: [PATCH 016/102] Tech ENABLE_WORLD_COORDINATE - Gizmo rotate oriented in dependence of the selected coordinate system Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 2 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 48 +++++++++++++----- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 19 ++------ src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 3 -- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 59 ++++++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 7 +++ src/slic3r/GUI/Selection.cpp | 58 ++++++++++++++++++---- src/slic3r/GUI/Selection.hpp | 10 ++-- 9 files changed, 155 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 6eae83277..3c35f6bbd 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -619,7 +619,7 @@ Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - const Vec3d axis = angle_axis.axis(); + const Vec3d& axis = angle_axis.axis(); const double angle = angle_axis.angle(); #ifndef NDEBUG if (std::abs(angle) > 1e-8) { diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 1cc35be7f..3ed3d4bb7 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -278,7 +278,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : return; // Update mirroring at the GLVolumes. - selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL); selection.synchronize_unselected_volumes(); // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. canvas->do_mirror(L("Set Mirror")); @@ -379,7 +379,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : return; // Update rotation at the GLVolumes. - selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. canvas->do_rotate(L("Reset Rotation")); @@ -551,17 +551,23 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_instance_offset(); #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); - m_new_rotation = Vec3d::Zero(); - m_new_size = selection.get_scaled_instance_bounding_box().size(); +#if ENABLE_WORLD_COORDINATE + m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); +#else + m_new_rotation = Vec3d::Zero(); +#endif // ENABLE_WORLD_COORDINATE + m_new_size = selection.get_scaled_instance_bounding_box().size(); m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; } else { #if ENABLE_WORLD_COORDINATE m_new_position = Vec3d::Zero(); -#endif // ENABLE_WORLD_COORDINATE + m_new_rotation = Vec3d::Zero(); +#else m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); +#endif // ENABLE_WORLD_COORDINATE m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); - m_new_scale = volume->get_instance_scaling_factor() * 100.; + m_new_scale = volume->get_instance_scaling_factor() * 100.0; } m_new_enabled = true; @@ -570,7 +576,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const BoundingBoxf3& box = selection.get_bounding_box(); m_new_position = box.center(); m_new_rotation = Vec3d::Zero(); - m_new_scale = Vec3d(100., 100., 100.); + m_new_scale = Vec3d(100.0, 100.0, 100.0); m_new_size = box.size(); m_new_rotate_label_string = L("Rotate"); m_new_scale_label_string = L("Scale"); @@ -586,7 +592,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const Vec3d& offset = trafo.get_offset(); const Vec3d& rotation = trafo.get_rotation(); const Vec3d& scaling_factor = trafo.get_scaling_factor(); - const Vec3d& m = trafo.get_mirror(); +// const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; m_new_rotation = rotation * (180.0 / M_PI); @@ -596,8 +602,8 @@ void ObjectManipulation::update_settings_value(const Selection& selection) else { #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); - m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); - m_new_scale = volume->get_volume_scaling_factor() * 100.; + m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); + m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); #if ENABLE_WORLD_COORDINATE } @@ -712,11 +718,20 @@ void ObjectManipulation::update_reset_buttons_visibility() if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_WORLD_COORDINATE + Vec3d rotation = Vec3d::Zero(); + Vec3d scale = Vec3d::Ones(); +#else Vec3d rotation; Vec3d scale; - double min_z = 0.; +#endif // ENABLE_WORLD_COORDINATE + double min_z = 0.0; +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance() && m_world_coordinates) { +#else if (selection.is_single_full_instance()) { +#endif // ENABLE_WORLD_COORDINATE rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z(); @@ -728,7 +743,11 @@ void ObjectManipulation::update_reset_buttons_visibility() } show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); +#if ENABLE_WORLD_COORDINATE + show_drop_to_bed = min_z > EPSILON; +#else show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; +#endif // ENABLE_WORLD_COORDINATE } wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { @@ -1042,6 +1061,13 @@ void ObjectManipulation::set_world_coordinates(const bool world_coordinates) canvas->set_as_dirty(); canvas->request_extra_frame(); } + +bool ObjectManipulation::get_world_coordinates() const +{ + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + return wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()) ? + m_world_coordinates : true; +} #endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::msw_rescale() diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 6d0383038..a4f826fea 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -185,10 +185,11 @@ public: // Does the object manipulation panel work in World or Local coordinates? #if ENABLE_WORLD_COORDINATE void set_world_coordinates(const bool world_coordinates); + bool get_world_coordinates() const; #else void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); } -#endif // ENABLE_WORLD_COORDINATE bool get_world_coordinates() const { return m_world_coordinates; } +#endif // ENABLE_WORLD_COORDINATE void reset_cache() { m_cache.reset(); } #ifndef __APPLE__ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 2e943bbd8..8d44e87d5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -24,27 +24,18 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filenam std::string GLGizmoMove3D::get_tooltip() const { - const Selection& selection = m_parent.get_selection(); - const bool show_position = selection.is_single_full_instance(); #if ENABLE_WORLD_COORDINATE - const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - Vec3d position = Vec3d::Zero(); - if (!world_coordinates) { - if (selection.is_single_modifier() || selection.is_single_volume() || selection.is_wipe_tower()) - position = selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_offset(); - } - else - position = selection.get_bounding_box().center(); - if (m_hover_id == 0) - return m_grabbers[0].dragging ? "DX: " + format(m_displacement.x(), 2) : "X: " + format(position.x(), 2); + return "X: " + format(m_displacement.x(), 2); else if (m_hover_id == 1) - return m_grabbers[1].dragging ? "DY: " + format(m_displacement.y(), 2) : "Y: " + format(position.y(), 2); + return "Y: " + format(m_displacement.y(), 2); else if (m_hover_id == 2) - return m_grabbers[2].dragging ? "DZ: " + format(m_displacement.z(), 2) : "Z: " + format(position.z(), 2); + return "Z: " + format(m_displacement.z(), 2); else return ""; #else + const Selection& selection = m_parent.get_selection(); + const bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); if (m_hover_id == 0 || m_grabbers[0].dragging) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 2c9e94f07..fbc9ca0df 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -60,9 +60,6 @@ protected: virtual bool on_is_activable() const override; virtual void on_start_dragging() override; virtual void on_stop_dragging() override; -#if !ENABLE_WORLD_COORDINATE - virtual void on_start_dragging() override; -#endif // !ENABLE_WORLD_COORDINATE virtual void on_dragging(const UpdateData& data) override; virtual void on_render() override; virtual void on_render_for_picking() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 640199019..b6bffac5f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -2,15 +2,18 @@ #include "GLGizmoRotate.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" - -#include +#if ENABLE_WORLD_COORDINATE +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#endif // ENABLE_WORLD_COORDINATE #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp" + #include "libslic3r/PresetBundle.hpp" -#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp" +#include namespace Slic3r { namespace GUI { @@ -99,6 +102,9 @@ bool GLGizmoRotate::on_init() void GLGizmoRotate::on_start_dragging() { +#if ENABLE_WORLD_COORDINATE + init_data_from_selection(m_parent.get_selection()); +#else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_center = box.center(); m_radius = Offset + box.radius(); @@ -106,6 +112,7 @@ void GLGizmoRotate::on_start_dragging() m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; m_snap_fine_in_radius = m_radius; m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoRotate::on_dragging(const UpdateData &data) @@ -154,12 +161,16 @@ void GLGizmoRotate::on_render() const BoundingBoxf3& box = selection.get_bounding_box(); if (m_hover_id != 0 && !m_grabbers.front().dragging) { +#if ENABLE_WORLD_COORDINATE + init_data_from_selection(selection); +#else m_center = box.center(); m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; m_snap_fine_in_radius = m_radius; m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth); +#endif // ENABLE_WORLD_COORDINATE } const double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); @@ -257,6 +268,24 @@ void GLGizmoRotate::on_render_for_picking() #endif // !ENABLE_GL_SHADERS_ATTRIBUTES } +#if ENABLE_WORLD_COORDINATE +void GLGizmoRotate::init_data_from_selection(const Selection& selection) +{ + const BoundingBoxf3& box = selection.get_bounding_box(); + m_center = box.center(); + m_radius = Offset + box.radius(); + m_snap_coarse_in_radius = m_radius / 3.0f; + m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_fine_in_radius = m_radius; + m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; + + if (wxGetApp().obj_manipul()->get_world_coordinates()) + m_orient_matrix = Transform3d::Identity(); + else + m_orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); +} +#endif // ENABLE_WORLD_COORDINATE + void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit) { if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) @@ -317,10 +346,10 @@ void GLGizmoRotate::render_circle() const #else ::glBegin(GL_LINE_LOOP); for (unsigned int i = 0; i < ScaleStepsCount; ++i) { - float angle = (float)i * ScaleStepRad; - float x = ::cos(angle) * m_radius; - float y = ::sin(angle) * m_radius; - float z = 0.0f; + const float angle = (float)i * ScaleStepRad; + const float x = ::cos(angle) * m_radius; + const float y = ::sin(angle) * m_radius; + const float z = 0.0f; ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); } glsafe(::glEnd()); @@ -519,10 +548,10 @@ void GLGizmoRotate::render_angle() const #else ::glBegin(GL_LINE_STRIP); for (unsigned int i = 0; i <= AngleResolution; ++i) { - float angle = (float)i * step_angle; - float x = ::cos(angle) * ex_radius; - float y = ::sin(angle) * ex_radius; - float z = 0.0f; + const float angle = (float)i * step_angle; + const float x = ::cos(angle) * ex_radius; + const float y = ::sin(angle) * ex_radius; + const float z = 0.0f; ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); } glsafe(::glEnd()); @@ -687,10 +716,14 @@ void GLGizmoRotate::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); +#if ENABLE_WORLD_COORDINATE + glsafe(::glMultMatrixd(m_orient_matrix.data())); +#else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) { const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } +#endif // ENABLE_WORLD_COORDINATE switch (m_axis) { @@ -744,8 +777,12 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons } } +#if ENABLE_WORLD_COORDINATE + m = m * m_orient_matrix.inverse(); +#else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse(); +#endif // ENABLE_WORLD_COORDINATE m.translate(-m_center); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index f4594bc33..0e01b4beb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -34,6 +34,9 @@ private: float m_snap_coarse_out_radius{ 0.0f }; float m_snap_fine_in_radius{ 0.0f }; float m_snap_fine_out_radius{ 0.0f }; +#if ENABLE_WORLD_COORDINATE + Transform3d m_orient_matrix{ Transform3d::Identity() }; +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR GLModel m_cone; @@ -119,6 +122,10 @@ private: // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const; + +#if ENABLE_WORLD_COORDINATE + void init_data_from_selection(const Selection& selection); +#endif // ENABLE_WORLD_COORDINATE }; class GLGizmoRotate3D : public GLGizmoBase diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index ae261fc7a..1aacd41bb 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -734,7 +734,7 @@ void Selection::translate(const Vec3d& displacement, bool local) #if !DISABLE_INSTANCES_SYNCH if (translation_type == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); + synchronize_unselected_instances(SyncRotationType::NONE); else if (translation_type == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -791,7 +791,18 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } else { // extracts rotations from the composed transformation - Vec3d new_rotation = transformation_type.world() ? +#if ENABLE_WORLD_COORDINATE + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Vec3d new_rotation = transformation_type.world() ? + Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()) : + transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m); + if (rot_axis_max == 2 && transformation_type.world() && transformation_type.joint()) { + // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); + volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + } +#else + const Vec3d new_rotation = transformation_type.world() ? Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); if (rot_axis_max == 2 && transformation_type.joint()) { @@ -799,6 +810,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); } +#endif // ENABLE_WORLD_COORDINATE else if (!(m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center).isApprox(Vec3d::Zero())) volume.set_instance_offset(m_cache.dragging_center + Geometry::assemble_transform(Vec3d::Zero(), new_rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); @@ -826,8 +838,8 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ rotate_instance(v, i); else if (m_mode == Volume) { // extracts rotations from the composed transformation - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); if (transformation_type.joint()) { const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); @@ -840,11 +852,24 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } #if !DISABLE_INSTANCES_SYNCH +#if ENABLE_WORLD_COORDINATE + if (m_mode == Instance) { + SyncRotationType synch; + if (transformation_type.world() && rot_axis_max == 2) + synch = SyncRotationType::NONE; + else if (transformation_type.local()) + synch = SyncRotationType::FULL; + else + synch = SyncRotationType::GENERAL; + synchronize_unselected_instances(synch); + } +#else if (m_mode == Instance) synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); +#endif // ENABLE_WORLD_COORDINATE else if (m_mode == Volume) synchronize_unselected_volumes(); - #endif // !DISABLE_INSTANCES_SYNCH +#endif // !DISABLE_INSTANCES_SYNCH } else { // it's the wipe tower that's selected and being rotated GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection @@ -887,7 +912,7 @@ void Selection::flattening_rotate(const Vec3d& normal) // Apply the same transformation also to other instances, // but respect their possibly diffrent z-rotation. if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_GENERAL); + synchronize_unselected_instances(SyncRotationType::GENERAL); #endif // !DISABLE_INSTANCES_SYNCH this->set_bounding_boxes_dirty(); @@ -950,7 +975,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); + synchronize_unselected_instances(SyncRotationType::NONE); else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1063,7 +1088,7 @@ void Selection::mirror(Axis axis) #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); + synchronize_unselected_instances(SyncRotationType::NONE); else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -2528,7 +2553,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); switch (sync_rotation_type) { - case SYNC_ROTATION_NONE: { + case SyncRotationType::NONE: { // z only rotation -> synch instance z // The X,Y rotations should be synchronized from start to end of the rotation. assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); @@ -2536,12 +2561,25 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ v->set_instance_offset(Z, volume->get_instance_offset().z()); break; } - case SYNC_ROTATION_GENERAL: + case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; } +#if ENABLE_WORLD_COORDINATE + case SyncRotationType::FULL: { + // generic rotation -> update instance z with the delta of the rotation. + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation())); + const Vec3d& axis = angle_axis.axis(); + const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ? + angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); + + v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); + break; + } +#endif // ENABLE_WORLD_COORDINATE + } v->set_instance_scaling_factor(scaling_factor); v->set_instance_mirror(mirror); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c9e55cf82..8cd6cdb6f 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -389,11 +389,15 @@ private: #endif // ENABLE_GL_SHADERS_ATTRIBUTES public: - enum SyncRotationType { + enum class SyncRotationType { // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. - SYNC_ROTATION_NONE = 0, + NONE = 0, // Synchronize after rotation by an axis not parallel with Z. - SYNC_ROTATION_GENERAL = 1, + GENERAL = 1, +#if ENABLE_WORLD_COORDINATE + // Fully synchronize rotation. + FULL = 2, +#endif // ENABLE_WORLD_COORDINATE }; void synchronize_unselected_instances(SyncRotationType sync_rotation_type); void synchronize_unselected_volumes(); From c968ba05fb7f2b04c015d0975c7ff1ed14feb712 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 Oct 2021 13:42:55 +0200 Subject: [PATCH 017/102] Tech ENABLE_WORLD_COORDINATE - Resize Move and Rotate gizmos in dependence of the selected coordinate system Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 32 +++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 33 ++++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 1 + src/slic3r/GUI/Selection.cpp | 4 +-- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 8d44e87d5..6e001fefc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -134,14 +134,23 @@ void GLGizmoMove3D::on_render() m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 18.0)); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR - const Selection& selection = m_parent.get_selection(); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - const BoundingBoxf3& box = selection.get_bounding_box(); - const Vec3d& center = box.center(); + const Selection& selection = m_parent.get_selection(); + #if ENABLE_WORLD_COORDINATE + BoundingBoxf3 box; + if (wxGetApp().obj_manipul()->get_world_coordinates()) + box = selection.get_bounding_box(); + else { + const Selection::IndicesList& ids = selection.get_volume_idxs(); + for (unsigned int id : ids) { + const GLVolume* v = selection.get_volume(id); + box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + } + } + glsafe(::glPushMatrix()); transform_to_local(selection); @@ -160,6 +169,8 @@ void GLGizmoMove3D::on_render() m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset }; m_grabbers[2].color = AXES_COLOR[2]; #else + const Selection& selection = m_parent.get_selection(); + const BoundingBoxf3& box = selection.get_bounding_box(); // x axis m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; m_grabbers[0].color = AXES_COLOR[0]; @@ -176,10 +187,19 @@ void GLGizmoMove3D::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE + auto render_grabber_connection = [this, &zero](unsigned int id) { +#else auto render_grabber_connection = [this, ¢er](unsigned int id) { +#endif // ENABLE_WORLD_COORDINATE if (m_grabbers[id].enabled) { +#if ENABLE_WORLD_COORDINATE + if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(m_grabbers[id].center)) { + m_grabber_connections[id].old_center = m_grabbers[id].center; +#else if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(center)) { m_grabber_connections[id].old_center = center; +#endif // ENABLE_WORLD_COORDINATE m_grabber_connections[id].model.reset(); GLModel::Geometry init_data; @@ -189,7 +209,11 @@ void GLGizmoMove3D::on_render() init_data.reserve_indices(2); // vertices +#if ENABLE_WORLD_COORDINATE + init_data.add_vertex((Vec3f)zero.cast()); +#else init_data.add_vertex((Vec3f)center.cast()); +#endif // ENABLE_WORLD_COORDINATE init_data.add_vertex((Vec3f)m_grabbers[id].center.cast()); // indices diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b6bffac5f..ec75d3093 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -158,7 +158,9 @@ void GLGizmoRotate::on_render() #endif // !ENABLE_GIZMO_GRABBER_REFACTOR const Selection& selection = m_parent.get_selection(); +#if !ENABLE_WORLD_COORDINATE const BoundingBoxf3& box = selection.get_bounding_box(); +#endif // !ENABLE_WORLD_COORDINATE if (m_hover_id != 0 && !m_grabbers.front().dragging) { #if ENABLE_WORLD_COORDINATE @@ -234,10 +236,17 @@ void GLGizmoRotate::on_render() render_angle(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE + render_grabber(m_bounding_box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR + render_grabber_extension(m_bounding_box, false); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#else render_grabber(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(box, false); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); @@ -257,11 +266,18 @@ void GLGizmoRotate::on_render_for_picking() transform_to_local(selection); #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_WORLD_COORDINATE + render_grabbers_for_picking(m_bounding_box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR + render_grabber_extension(m_bounding_box, true); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#else const BoundingBoxf3& box = selection.get_bounding_box(); render_grabbers_for_picking(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(box, true); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); @@ -271,9 +287,20 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { - const BoundingBoxf3& box = selection.get_bounding_box(); - m_center = box.center(); - m_radius = Offset + box.radius(); + m_bounding_box.reset(); + if (wxGetApp().obj_manipul()->get_world_coordinates()) { + m_bounding_box = selection.get_bounding_box(); + m_center = m_bounding_box.center(); + } + else { + const Selection::IndicesList& ids = selection.get_volume_idxs(); + for (unsigned int id : ids) { + const GLVolume* v = selection.get_volume(id); + m_bounding_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + } + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix() * m_bounding_box.center(); + } + m_radius = Offset + m_bounding_box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; m_snap_fine_in_radius = m_radius; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 0e01b4beb..5fc24ed90 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -35,6 +35,7 @@ private: float m_snap_fine_in_radius{ 0.0f }; float m_snap_fine_out_radius{ 0.0f }; #if ENABLE_WORLD_COORDINATE + BoundingBoxf3 m_bounding_box; Transform3d m_orient_matrix{ Transform3d::Identity() }; #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 1aacd41bb..efeba89aa 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -851,7 +851,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } } - #if !DISABLE_INSTANCES_SYNCH +#if !DISABLE_INSTANCES_SYNCH #if ENABLE_WORLD_COORDINATE if (m_mode == Instance) { SyncRotationType synch; @@ -877,7 +877,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ // make sure the wipe tower rotates around its center, not origin // we can assume that only Z rotation changes const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); - const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local; + const Vec3d center_local_new = Eigen::AngleAxisd(rotation.z()-volume.get_volume_rotation().z(), Vec3d(0.0, 0.0, 1.0)) * center_local; volume.set_volume_rotation(rotation); volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); } From e89dc34b3acb84ef7b6737736d1b2ac183756023 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 Oct 2021 15:33:03 +0200 Subject: [PATCH 018/102] Tech ENABLE_WORLD_COORDINATE - Fixed drop to bed button behavior --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 39 ++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 3ed3d4bb7..ef4db56d7 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -328,27 +328,58 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : Selection& selection = canvas->get_selection(); if (selection.is_single_volume() || selection.is_single_modifier()) { +#if ENABLE_WORLD_COORDINATE const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const double min_z = get_volume_min_z(*volume); + if (!m_world_coordinates) { + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); - const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); - const Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(*volume)); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); + } + else { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(2, m_cache.position.z() - min_z); + } +#else + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (get_volume_min_z(*volume) * Vec3d::UnitZ()); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); change_position_value(1, diff.y()); change_position_value(2, diff.z()); +#endif // ENABLE_WORLD_COORDINATE } else if (selection.is_single_full_instance()) { +#if ENABLE_WORLD_COORDINATE + const double min_z = selection.get_scaled_instance_bounding_box().min.z(); + if (!m_world_coordinates) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); + } + else { +#else const ModelObjectPtrs& objects = wxGetApp().model().objects; const int idx = selection.get_object_idx(); if (0 <= idx && idx < static_cast(objects.size())) { const ModelObject* mo = wxGetApp().model().objects[idx]; const double min_z = mo->bounding_box().min.z(); if (std::abs(min_z) > SINKING_Z_THRESHOLD) { +#endif // ENABLE_WORLD_COORDINATE Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(2, m_cache.position.z() - min_z); } +#if !ENABLE_WORLD_COORDINATE } +#endif // !ENABLE_WORLD_COORDINATE } }); editors_grid_sizer->Add(m_drop_to_bed_button); @@ -734,7 +765,7 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); - min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z(); + min_z = selection.get_scaled_instance_bounding_box().min.z(); } else { rotation = volume->get_volume_rotation(); @@ -744,7 +775,7 @@ void ObjectManipulation::update_reset_buttons_visibility() show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); #if ENABLE_WORLD_COORDINATE - show_drop_to_bed = min_z > EPSILON; + show_drop_to_bed = min_z < SINKING_Z_THRESHOLD; #else show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; #endif // ENABLE_WORLD_COORDINATE From 345ee7cf28442980009ed790edc44c7bf40e7a11 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Jun 2022 10:55:59 +0200 Subject: [PATCH 019/102] Let's not call yield in PlaterWorker Not worth the risk, needs further investigation --- src/slic3r/GUI/Jobs/PlaterWorker.hpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp index 0fef3655e..37b18b3b8 100644 --- a/src/slic3r/GUI/Jobs/PlaterWorker.hpp +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -39,13 +39,7 @@ class PlaterWorker: public Worker { void update_status(int st, const std::string &msg = "") override { ctl.update_status(st, msg); - - // If the worker is not using additional threads, the UI - // is refreshed with this call. If the worker is running - // in it's own thread, this will be one additional - // evaluation of the event loop which should have no visible - // effects. - call_on_main_thread([] { wxYieldIfNeeded(); }); + wxWakeUpIdle(); } bool was_canceled() const override { return ctl.was_canceled(); } From bd58b1c1c5cf11d189096eb67affd8612e4a351a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 Oct 2021 15:41:02 +0200 Subject: [PATCH 020/102] Fixed build when tech ENABLE_WORLD_COORDINATE is disabled Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index efeba89aa..cacda0cd5 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -865,7 +865,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } #else if (m_mode == Instance) - synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); + synchronize_unselected_instances((rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL); #endif // ENABLE_WORLD_COORDINATE else if (m_mode == Volume) synchronize_unselected_volumes(); @@ -877,7 +877,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ // make sure the wipe tower rotates around its center, not origin // we can assume that only Z rotation changes const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); - const Vec3d center_local_new = Eigen::AngleAxisd(rotation.z()-volume.get_volume_rotation().z(), Vec3d(0.0, 0.0, 1.0)) * center_local; + const Vec3d center_local_new = Eigen::AngleAxisd(rotation.z()-volume.get_volume_rotation().z(), Vec3d::UnitZ()) * center_local; volume.set_volume_rotation(rotation); volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); } From 30a02466100f4020c858984ad3f3f742b321d193 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 09:03:35 +0200 Subject: [PATCH 021/102] Tech ENABLE_WORLD_COORDINATE - Fixes in Gizmo Move behavior Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 75 ++++++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 1 + 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 6e001fefc..c51196137 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -90,23 +90,30 @@ bool GLGizmoMove3D::on_is_activable() const void GLGizmoMove3D::on_start_dragging() { - assert(m_hover_id != -1); - - m_displacement = Vec3d::Zero(); - const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); + if (m_hover_id != -1) { + m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE - const Vec3d center = box.center(); - m_starting_drag_position = center + m_grabbers[m_hover_id].center; - m_starting_box_center = center; - m_starting_box_bottom_center = center; - m_starting_box_bottom_center.z() = box.min.z(); + const BoundingBoxf3 box = get_selection_box(); + Vec3d center; + if (wxGetApp().obj_manipul()->get_world_coordinates()) + center = box.center(); + else { + const Selection& selection = m_parent.get_selection(); + center = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix() * box.center(); + } + m_starting_drag_position = center + m_grabbers[m_hover_id].center; + m_starting_box_center = center; + m_starting_box_bottom_center = center; + m_starting_box_bottom_center.z() = box.min.z(); #else - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box_center = box.center(); - m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center.z() = box.min.z(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); + m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_box_center = box.center(); + m_starting_box_bottom_center = box.center(); + m_starting_box_bottom_center.z() = box.min.z(); #endif // ENABLE_WORLD_COORDINATE -} + } + } void GLGizmoMove3D::on_stop_dragging() { @@ -140,21 +147,11 @@ void GLGizmoMove3D::on_render() const Selection& selection = m_parent.get_selection(); #if ENABLE_WORLD_COORDINATE - BoundingBoxf3 box; - if (wxGetApp().obj_manipul()->get_world_coordinates()) - box = selection.get_bounding_box(); - else { - const Selection::IndicesList& ids = selection.get_volume_idxs(); - for (unsigned int id : ids) { - const GLVolume* v = selection.get_volume(id); - box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); - } - } - glsafe(::glPushMatrix()); - transform_to_local(selection); + transform_to_local(m_parent.get_selection()); const Vec3d zero = Vec3d::Zero(); + BoundingBoxf3 box = get_selection_box(); const Vec3d half_box_size = 0.5 * box.size(); // x axis @@ -171,6 +168,8 @@ void GLGizmoMove3D::on_render() #else const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); + const Vec3d& center = box.center(); + // x axis m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; m_grabbers[0].color = AXES_COLOR[0]; @@ -308,7 +307,8 @@ void GLGizmoMove3D::on_render() shader->start_using(); shader->set_uniform("emission_factor", 0.1f); // draw grabber - const float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); + const Vec3d box_size = box.size(); + const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0); m_grabbers[m_hover_id].render(true, mean_size); shader->stop_using(); } @@ -327,10 +327,9 @@ void GLGizmoMove3D::on_render_for_picking() glsafe(::glDisable(GL_DEPTH_TEST)); #if ENABLE_WORLD_COORDINATE - const Selection& selection = m_parent.get_selection(); - const BoundingBoxf3& box = selection.get_bounding_box(); glsafe(::glPushMatrix()); - transform_to_local(selection); + transform_to_local(m_parent.get_selection()); + const BoundingBoxf3 box = get_selection_box(); #else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); #endif // ENABLE_WORLD_COORDINATE @@ -443,6 +442,22 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const glsafe(::glMultMatrixd(orient_matrix.data())); } } + +BoundingBoxf3 GLGizmoMove3D::get_selection_box() +{ + const Selection& selection = m_parent.get_selection(); + BoundingBoxf3 box; + if (wxGetApp().obj_manipul()->get_world_coordinates()) + box = selection.get_bounding_box(); + else { + const Selection::IndicesList& ids = selection.get_volume_idxs(); + for (unsigned int id : ids) { + const GLVolume* v = selection.get_volume(id); + box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + } + } + return box; +} #endif // ENABLE_WORLD_COORDINATE } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index fbc9ca0df..e97d1c896 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -68,6 +68,7 @@ private: double calc_projection(const UpdateData& data) const; #if ENABLE_WORLD_COORDINATE void transform_to_local(const Selection& selection) const; + BoundingBoxf3 get_selection_box(); #endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); From e76b5875b7b1d098f9ed1c49b300bf83c5809ec0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 13:11:59 +0200 Subject: [PATCH 022/102] Tech ENABLE_WORLD_COORDINATE - Fixed Move and Rotate Gizmo size when the selected instance is scaled Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index c51196137..6e999ef07 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -453,7 +453,7 @@ BoundingBoxf3 GLGizmoMove3D::get_selection_box() const Selection::IndicesList& ids = selection.get_volume_idxs(); for (unsigned int id : ids) { const GLVolume* v = selection.get_volume(id); - box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); } } return box; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index ec75d3093..b10c9bb19 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -296,9 +296,9 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) const Selection::IndicesList& ids = selection.get_volume_idxs(); for (unsigned int id : ids) { const GLVolume* v = selection.get_volume(id); - m_bounding_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + m_bounding_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); } - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix() * m_bounding_box.center(); + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } m_radius = Offset + m_bounding_box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; From 567162a64778fa7ad9db43a9851c10cb5c391289 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 14:00:00 +0200 Subject: [PATCH 023/102] Refactoring into GLGizmoScale3D Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 46 +++++++++++--------------- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 12 +++---- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 26c9251b4..c44fc41c7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -14,7 +14,7 @@ namespace Slic3r { namespace GUI { -const float GLGizmoScale3D::Offset = 5.0f; +const double GLGizmoScale3D::Offset = 5.0; GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -40,14 +40,11 @@ std::string GLGizmoScale3D::get_tooltip() const { const Selection& selection = m_parent.get_selection(); - bool single_instance = selection.is_single_full_instance(); - bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); - - Vec3f scale = 100.0f * Vec3f::Ones(); - if (single_instance) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast(); - else if (single_volume) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast(); + Vec3d scale = 100.0 * Vec3d::Ones(); + if (selection.is_single_full_instance()) + scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor(); + else if (selection.is_single_modifier() || selection.is_single_volume()) + scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor(); if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) return "X: " + format(scale.x(), 4) + "%"; @@ -116,12 +113,12 @@ bool GLGizmoScale3D::on_init() double half_pi = 0.5 * (double)PI; // x axis - m_grabbers[0].angles(1) = half_pi; - m_grabbers[1].angles(1) = half_pi; + m_grabbers[0].angles.y() = half_pi; + m_grabbers[1].angles.y() = half_pi; // y axis - m_grabbers[2].angles(0) = half_pi; - m_grabbers[3].angles(0) = half_pi; + m_grabbers[2].angles.x() = half_pi; + m_grabbers[3].angles.x() = half_pi; m_shortcut_key = WXK_CONTROL_S; @@ -175,9 +172,6 @@ void GLGizmoScale3D::on_render() { const Selection& selection = m_parent.get_selection(); - bool single_instance = selection.is_single_full_instance(); - bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -188,7 +182,7 @@ void GLGizmoScale3D::on_render() m_offsets_transform = Transform3d::Identity(); Vec3d angles = Vec3d::Zero(); - if (single_instance) { + if (selection.is_single_full_instance()) { // calculate bounding box in instance local reference system const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int idx : idxs) { @@ -205,7 +199,7 @@ void GLGizmoScale3D::on_render() offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); m_offsets_transform = offsets_transform; } - else if (single_volume) { + else if (selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); m_box = v->bounding_box(); m_transform = v->world_matrix(); @@ -511,7 +505,7 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { - double ratio = calc_ratio(data); + const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; if (m_starting.ctrl_down) { @@ -537,7 +531,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) { - double ratio = calc_ratio(data); + const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale = m_starting.scale * ratio; m_offset = Vec3d::Zero(); @@ -548,22 +542,22 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; - Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); + const Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); - Vec3d starting_vec = m_starting.drag_position - pivot; - double len_starting_vec = starting_vec.norm(); + const Vec3d starting_vec = m_starting.drag_position - pivot; + const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + const Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting.drag_position; + const Vec3d inters_vec = inters - m_starting.drag_position; // finds projection of the vector along the staring direction - double proj = inters_vec.dot(starting_vec.normalized()); + const double proj = inters_vec.dot(starting_vec.normalized()); ratio = (len_starting_vec + proj) / len_starting_vec; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index f4efe052a..40c7a1d38 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -11,17 +11,15 @@ namespace GUI { class GLGizmoScale3D : public GLGizmoBase { - static const float Offset; + static const double Offset; struct StartingData { - Vec3d scale; - Vec3d drag_position; + bool ctrl_down{ false }; + Vec3d scale{ Vec3d::Ones() }; + Vec3d drag_position{ Vec3d::Zero() }; BoundingBoxf3 box; - Vec3d pivots[6]; - bool ctrl_down; - - StartingData() : scale(Vec3d::Ones()), drag_position(Vec3d::Zero()), ctrl_down(false) { for (int i = 0; i < 5; ++i) { pivots[i] = Vec3d::Zero(); } } + std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; }; mutable BoundingBoxf3 m_box; From d50ce6c69c3ddfaa19a0c868e577b9280b02a9d2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 14:15:20 +0200 Subject: [PATCH 024/102] Another small refactoring into GLGizmoScale3D Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index c44fc41c7..cdec33a1e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -212,35 +212,31 @@ void GLGizmoScale3D::on_render() m_box = selection.get_bounding_box(); const Vec3d& center = m_box.center(); - const Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); - const Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); - const Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); - const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); // x axis - m_grabbers[0].center = m_transform * Vec3d(m_box.min.x(), center.y(), center.z()) - offset_x; + m_grabbers[0].center = m_transform * Vec3d(m_box.min.x() - Offset, center.y(), center.z()); m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; - m_grabbers[1].center = m_transform * Vec3d(m_box.max.x(), center.y(), center.z()) + offset_x; + m_grabbers[1].center = m_transform * Vec3d(m_box.max.x() + Offset, center.y(), center.z()); m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; // y axis - m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y(), center.z()) - offset_y; + m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y() - Offset, center.z()); m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; - m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y(), center.z()) + offset_y; + m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y() + Offset, center.z()); m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; // z axis - m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z()) - offset_z; + m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z() - Offset); m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; - m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z()) + offset_z; + m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z() + Offset); m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; // uniform - m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; - m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; - m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; - m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; + m_grabbers[6].center = m_transform * Vec3d(m_box.min.x() - Offset, m_box.min.y() - Offset, center.z()); + m_grabbers[7].center = m_transform * Vec3d(m_box.max.x() + Offset, m_box.min.y() - Offset, center.z()); + m_grabbers[8].center = m_transform * Vec3d(m_box.max.x() + Offset, m_box.max.y() + Offset, center.z()); + m_grabbers[9].center = m_transform * Vec3d(m_box.min.x() - Offset, m_box.max.y() + Offset, center.z()); for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } @@ -547,7 +543,7 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const const Vec3d starting_vec = m_starting.drag_position - pivot; const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); + const Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) From 4946466633e8c3f49d78416b7bd8b343d47e791e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 14:23:51 +0200 Subject: [PATCH 025/102] Fixed color of the line connecting the grabbers while hovering one grabber and pressing CTRL key in Gizmo Scale --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index cdec33a1e..73cd649ef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -318,7 +318,7 @@ void GLGizmoScale3D::on_render() shader = wxGetApp().get_shader("gouraud_light"); #else // draw connection - glsafe(::glColor4fv(m_grabbers[0].color.data())); + glsafe(::glColor4fv(AXES_COLOR[0].data())); render_grabbers_connection(0, 1); // draw grabbers @@ -351,7 +351,7 @@ void GLGizmoScale3D::on_render() shader = wxGetApp().get_shader("gouraud_light"); #else // draw connection - glsafe(::glColor4fv(m_grabbers[2].color.data())); + glsafe(::glColor4fv(AXES_COLOR[1].data())); render_grabbers_connection(2, 3); // draw grabbers @@ -384,7 +384,7 @@ void GLGizmoScale3D::on_render() shader = wxGetApp().get_shader("gouraud_light"); #else // draw connection - glsafe(::glColor4fv(m_grabbers[4].color.data())); + glsafe(::glColor4fv(AXES_COLOR[2].data())); render_grabbers_connection(4, 5); // draw grabbers From c4ad8bc41aa467e99a29dca2c6ea7550460324e0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 14 Oct 2021 13:59:27 +0200 Subject: [PATCH 026/102] Other refactoring plus some fixes into GLGizmoScale3D Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 43 ++++++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 6 ++-- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 73cd649ef..1ef63536f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -179,15 +179,14 @@ void GLGizmoScale3D::on_render() m_transform = Transform3d::Identity(); // Transforms grabbers' offsets to world refefence system Transform3d offsets_transform = Transform3d::Identity(); - m_offsets_transform = Transform3d::Identity(); Vec3d angles = Vec3d::Zero(); if (selection.is_single_full_instance()) { // calculate bounding box in instance local reference system const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int idx : idxs) { - const GLVolume* vol = selection.get_volume(idx); - m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix())); + const GLVolume* v = selection.get_volume(idx); + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); } // gets transform from first selected volume @@ -197,7 +196,6 @@ void GLGizmoScale3D::on_render() angles = v->get_instance_rotation(); // consider rotation+mirror only components of the transform for offsets offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); - m_offsets_transform = offsets_transform; } else if (selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); @@ -206,37 +204,40 @@ void GLGizmoScale3D::on_render() angles = Geometry::extract_euler_angles(m_transform); // consider rotation+mirror only components of the transform for offsets offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); - m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v->get_volume_rotation(), Vec3d::Ones(), v->get_volume_mirror()); } else m_box = selection.get_bounding_box(); const Vec3d& center = m_box.center(); - const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); + Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); + Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); + Vec3d offset_z = offsets_transform * (Offset * Vec3d::UnitZ()); + + bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); // x axis - m_grabbers[0].center = m_transform * Vec3d(m_box.min.x() - Offset, center.y(), center.z()); + m_grabbers[0].center = m_transform * Vec3d(m_box.min.x(), center.y(), center.z()) - offset_x; m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; - m_grabbers[1].center = m_transform * Vec3d(m_box.max.x() + Offset, center.y(), center.z()); + m_grabbers[1].center = m_transform * Vec3d(m_box.max.x(), center.y(), center.z()) + offset_x; m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; // y axis - m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y() - Offset, center.z()); + m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y(), center.z()) - offset_y; m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; - m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y() + Offset, center.z()); + m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y(), center.z()) + offset_y; m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; // z axis - m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z() - Offset); + m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z()) - offset_z; m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; - m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z() + Offset); + m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z()) + offset_z; m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; // uniform - m_grabbers[6].center = m_transform * Vec3d(m_box.min.x() - Offset, m_box.min.y() - Offset, center.z()); - m_grabbers[7].center = m_transform * Vec3d(m_box.max.x() + Offset, m_box.min.y() - Offset, center.z()); - m_grabbers[8].center = m_transform * Vec3d(m_box.max.x() + Offset, m_box.max.y() + Offset, center.z()); - m_grabbers[9].center = m_transform * Vec3d(m_box.min.x() - Offset, m_box.max.y() + Offset, center.z()); + m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; + m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; + m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; + m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } @@ -505,7 +506,10 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; if (m_starting.ctrl_down) { - double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); + double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); + if (!m_parent.get_selection().is_single_full_instance()) + local_offset *= m_starting.scale(axis); + if (m_hover_id == 2 * axis) local_offset *= -1.0; @@ -518,7 +522,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: break; } - m_offset = m_offsets_transform * local_offset_vec; + m_offset = local_offset_vec; } else m_offset = Vec3d::Zero(); @@ -538,10 +542,11 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; - const Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); + const Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); const Vec3d starting_vec = m_starting.drag_position - pivot; const double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) { const Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 40c7a1d38..d664a099e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -22,10 +22,8 @@ class GLGizmoScale3D : public GLGizmoBase std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; }; - mutable BoundingBoxf3 m_box; - mutable Transform3d m_transform; - // Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes) - mutable Transform3d m_offsets_transform; + BoundingBoxf3 m_box; + Transform3d m_transform; Vec3d m_scale{ Vec3d::Ones() }; Vec3d m_offset{ Vec3d::Zero() }; double m_snap_step{ 0.05 }; From b2a7c84c85db077064882aff75a2ea851627b19d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 15 Oct 2021 11:58:32 +0200 Subject: [PATCH 027/102] Tech ENABLE_WORLD_COORDINATE - Fixed gizmo Scale in world coordinates Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 161 ++++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 15 ++- 2 files changed, 161 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 1ef63536f..5d459f747 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -2,6 +2,9 @@ #include "GLGizmoScale.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" +#if ENABLE_WORLD_COORDINATE +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -110,6 +113,7 @@ bool GLGizmoScale3D::on_init() m_grabbers.push_back(Grabber()); } +#if !ENABLE_WORLD_COORDINATE double half_pi = 0.5 * (double)PI; // x axis @@ -119,6 +123,7 @@ bool GLGizmoScale3D::on_init() // y axis m_grabbers[2].angles.x() = half_pi; m_grabbers[3].angles.x() = half_pi; +#endif // !ENABLE_WORLD_COORDINATE m_shortcut_key = WXK_CONTROL_S; @@ -138,18 +143,36 @@ bool GLGizmoScale3D::on_is_activable() const void GLGizmoScale3D::on_start_dragging() { - assert(m_hover_id != -1); - m_starting.drag_position = m_grabbers[m_hover_id].center; - m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); - m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box(); + if (m_hover_id != -1) { + m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); +#if ENABLE_WORLD_COORDINATE + m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; + m_starting.box = m_box; + m_starting.center = m_center; - const Vec3d& center = m_starting.box.center(); - m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); - m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); - m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); - m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); - m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); + if (m_starting.ctrl_down) { + const Vec3d center = m_starting.box.center(); + const Transform3d trafo = wxGetApp().obj_manipul()->get_world_coordinates() ? Transform3d::Identity() : m_transform; + m_starting.pivots[0] = trafo * Vec3d(m_starting.box.max.x(), center.y(), center.z()); + m_starting.pivots[1] = trafo * Vec3d(m_starting.box.min.x(), center.y(), center.z()); + m_starting.pivots[2] = trafo * Vec3d(center.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[3] = trafo * Vec3d(center.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[4] = trafo * Vec3d(center.x(), center.y(), m_starting.box.max.z()); + m_starting.pivots[5] = trafo * Vec3d(center.x(), center.y(), m_starting.box.min.z()); + } +#else + m_starting.drag_position = m_grabbers[m_hover_id].center; + m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_box : m_parent.get_selection().get_bounding_box(); + + const Vec3d center = m_starting.box.center(); + m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); + m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); + m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); + m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); +#endif // ENABLE_WORLD_COORDINATE + } } void GLGizmoScale3D::on_stop_dragging() { @@ -177,81 +200,156 @@ void GLGizmoScale3D::on_render() m_box.reset(); m_transform = Transform3d::Identity(); +#if ENABLE_WORLD_COORDINATE + m_grabbers_transform = Transform3d::Identity(); + + if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#else // Transforms grabbers' offsets to world refefence system Transform3d offsets_transform = Transform3d::Identity(); Vec3d angles = Vec3d::Zero(); if (selection.is_single_full_instance()) { +#endif // !ENABLE_WORLD_COORDINATE + // calculate bounding box in instance local reference system const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int idx : idxs) { const GLVolume* v = selection.get_volume(idx); +#if ENABLE_WORLD_COORDINATE + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); +#else m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); +#endif // ENABLE_WORLD_COORDINATE } // gets transform from first selected volume const GLVolume* v = selection.get_volume(*idxs.begin()); m_transform = v->get_instance_transformation().get_matrix(); +#if ENABLE_WORLD_COORDINATE + m_grabbers_transform = v->get_instance_transformation().get_matrix(true, true, false, true); +#else // gets angles from first selected volume angles = v->get_instance_rotation(); // consider rotation+mirror only components of the transform for offsets offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); +#endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + else if ((selection.is_single_modifier() || selection.is_single_volume()) && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#else else if (selection.is_single_modifier() || selection.is_single_volume()) { +#endif // ENABLE_WORLD_COORDINATE const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_WORLD_COORDINATE + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); +#else m_box = v->bounding_box(); +#endif // ENABLE_WORLD_COORDINATE m_transform = v->world_matrix(); +#if ENABLE_WORLD_COORDINATE + m_grabbers_transform = m_transform; +#else angles = Geometry::extract_euler_angles(m_transform); // consider rotation+mirror only components of the transform for offsets offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); +#endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + else { + m_box = selection.get_bounding_box(); + m_transform = Geometry::assemble_transform(m_box.center()); + m_grabbers_transform = m_transform; + m_center = selection.is_single_full_instance() ? selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_offset() : m_box.center(); + } +#else else m_box = selection.get_bounding_box(); - const Vec3d& center = m_box.center(); Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); Vec3d offset_z = offsets_transform * (Offset * Vec3d::UnitZ()); +#endif // ENABLE_WORLD_COORDINATE - bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); + bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); // x axis +#if ENABLE_WORLD_COORDINATE + const Vec3d box_half_size = 0.5 * m_box.size(); + bool use_constrain = ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); + + m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 }; + m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + m_grabbers[1].center = { box_half_size.x() + Offset, 0.0, 0.0 }; + m_grabbers[1].color = (use_constrain && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; +#else + const Vec3d center = m_box.center(); + m_grabbers[0].center = m_transform * Vec3d(m_box.min.x(), center.y(), center.z()) - offset_x; m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; m_grabbers[1].center = m_transform * Vec3d(m_box.max.x(), center.y(), center.z()) + offset_x; m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; +#endif // ENABLE_WORLD_COORDINATE // y axis +#if ENABLE_WORLD_COORDINATE + m_grabbers[2].center = { 0.0, -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[2].color = (use_constrain && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + m_grabbers[3].center = { 0.0, box_half_size.y() + Offset, 0.0 }; + m_grabbers[3].color = (use_constrain && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; +#else m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y(), center.z()) - offset_y; m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y(), center.z()) + offset_y; m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; +#endif // ENABLE_WORLD_COORDINATE // z axis +#if ENABLE_WORLD_COORDINATE + m_grabbers[4].center = { 0.0, 0.0, -(box_half_size.z() + Offset) }; + m_grabbers[4].color = (use_constrain && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; + m_grabbers[5].center = { 0.0, 0.0, box_half_size.z() + Offset }; + m_grabbers[5].color = (use_constrain && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; +#else m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z()) - offset_z; m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z()) + offset_z; m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; +#endif // ENABLE_WORLD_COORDINATE // uniform +#if ENABLE_WORLD_COORDINATE + m_grabbers[6].center = { -(box_half_size.x() + Offset), -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[7].center = { box_half_size.x() + Offset, -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[8].center = { box_half_size.x() + Offset, box_half_size.y() + Offset, 0.0 }; + m_grabbers[9].center = { -(box_half_size.x() + Offset), box_half_size.y() + Offset, 0.0 }; +#else m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; +#endif // ENABLE_WORLD_COORDINATE for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } +#if !ENABLE_WORLD_COORDINATE // sets grabbers orientation for (int i = 0; i < 10; ++i) { m_grabbers[i].angles = angles; } +#endif // !ENABLE_WORLD_COORDINATE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); +#if ENABLE_WORLD_COORDINATE + glsafe(::glPushMatrix()); + transform_to_local(selection); + const float grabber_mean_size = (float)((m_box.size().x() + m_box.size().y() + m_box.size().z()) / 3.0); +#else const BoundingBoxf3& selection_box = selection.get_bounding_box(); - const float grabber_mean_size = (float)((selection_box.size().x() + selection_box.size().y() + selection_box.size().z()) / 3.0); +#endif // ENABLE_WORLD_COORDINATE if (m_hover_id == -1) { #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -439,12 +537,23 @@ void GLGizmoScale3D::on_render() shader->stop_using(); } } + +#if ENABLE_WORLD_COORDINATE + glsafe(::glPopMatrix()); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoScale3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_WORLD_COORDINATE + glsafe(::glPushMatrix()); + transform_to_local(m_parent.get_selection()); + render_grabbers_for_picking(m_box); + glsafe(::glPopMatrix()); +#else render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); +#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -502,13 +611,24 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { +#if ENABLE_WORLD_COORDINATE + double ratio = calc_ratio(data); +#else const double ratio = calc_ratio(data); +#endif // ENABLE_WORLD_COORDINATE if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; if (m_starting.ctrl_down) { +#if ENABLE_WORLD_COORDINATE + const double len_starting_vec = std::abs(m_starting.box.center()(axis) - m_starting.pivots[m_hover_id](axis)); + const double len_center_vec = std::abs(m_starting.center(axis) - m_starting.pivots[m_hover_id](axis)); + const double inner_ratio = len_center_vec / len_starting_vec; + double local_offset = inner_ratio * 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); +#else double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); if (!m_parent.get_selection().is_single_full_instance()) local_offset *= m_starting.scale(axis); +#endif // ENABLE_WORLD_COORDINATE if (m_hover_id == 2 * axis) local_offset *= -1.0; @@ -569,5 +689,18 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const return ratio; } +#if ENABLE_WORLD_COORDINATE +void GLGizmoScale3D::transform_to_local(const Selection& selection) const +{ + const Vec3d center = selection.get_bounding_box().center(); + glsafe(::glTranslated(center.x(), center.y(), center.z())); + + if (!wxGetApp().obj_manipul()->get_world_coordinates()) { + const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +} +#endif // ENABLE_WORLD_COORDINATE + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index d664a099e..70ea91016 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -5,10 +5,13 @@ #include "libslic3r/BoundingBox.hpp" - namespace Slic3r { namespace GUI { +#if ENABLE_WORLD_COORDINATE +class Selection; +#endif // ENABLE_WORLD_COORDINATE + class GLGizmoScale3D : public GLGizmoBase { static const double Offset; @@ -18,12 +21,19 @@ class GLGizmoScale3D : public GLGizmoBase bool ctrl_down{ false }; Vec3d scale{ Vec3d::Ones() }; Vec3d drag_position{ Vec3d::Zero() }; +#if ENABLE_WORLD_COORDINATE + Vec3d center{ Vec3d::Zero() }; +#endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; }; BoundingBoxf3 m_box; Transform3d m_transform; +#if ENABLE_WORLD_COORDINATE + Transform3d m_grabbers_transform; + Vec3d m_center{ Vec3d::Zero() }; +#endif // ENABLE_WORLD_COORDINATE Vec3d m_scale{ Vec3d::Ones() }; Vec3d m_offset{ Vec3d::Zero() }; double m_snap_step{ 0.05 }; @@ -83,6 +93,9 @@ private: void do_scale_uniform(const UpdateData& data); double calc_ratio(const UpdateData& data) const; +#if ENABLE_WORLD_COORDINATE + void transform_to_local(const Selection& selection) const; +#endif // ENABLE_WORLD_COORDINATE }; From 6433d3af91d06f042a979fdb89b970f63704ad8a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 18 Oct 2021 15:13:47 +0200 Subject: [PATCH 028/102] Tech ENABLE_WORLD_COORDINATE - Fixed volumes rotation in world coordinate Added sub-tech ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET which enable showing world coordinates of volumes' offset relative to the instance containing them Show 'Drop to bed' button in sidebar whenever the selected instance or volume is not laying on the printbed Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 2 +- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 19 +++++++++++++++++-- src/slic3r/GUI/Selection.cpp | 19 +++++++++++++++---- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 3c35f6bbd..58c90d9bc 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -315,7 +315,7 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix) { - // reference: http://www.gregslabaugh.net/publications/euler.pdf + // reference: http://eecs.qmul.ac.uk/~gslabaugh/publications/euler.pdf Vec3d angles1 = Vec3d::Zero(); Vec3d angles2 = Vec3d::Zero(); if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5) { diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 302c62879..40abad362 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -71,6 +71,8 @@ #define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) +// Enable showing world coordinates of volumes' offset relative to the instance containing them +#define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ef4db56d7..d4b1a3b67 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -620,13 +620,17 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (m_world_coordinates) { const Geometry::Transformation trafo(volume->world_matrix()); +#if ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET + const Vec3d offset = trafo.get_offset() - volume->get_instance_offset(); +#else const Vec3d& offset = trafo.get_offset(); const Vec3d& rotation = trafo.get_rotation(); +#endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET const Vec3d& scaling_factor = trafo.get_scaling_factor(); // const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; - m_new_rotation = rotation * (180.0 / M_PI); + m_new_rotation = Vec3d::Zero(); m_new_scale = scaling_factor * 100.0; m_new_size = volume->bounding_box().size().cwiseProduct(scaling_factor); } @@ -775,7 +779,7 @@ void ObjectManipulation::update_reset_buttons_visibility() show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); #if ENABLE_WORLD_COORDINATE - show_drop_to_bed = min_z < SINKING_Z_THRESHOLD; + show_drop_to_bed = std::abs(min_z) > EPSILON; #else show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; #endif // ENABLE_WORLD_COORDINATE @@ -921,6 +925,16 @@ void ObjectManipulation::change_rotation_value(int axis, double value) Selection& selection = canvas->get_selection(); TransformationType transformation_type(TransformationType::World_Relative_Joint); +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance()) + transformation_type.set_independent(); + + if (!m_world_coordinates) { + //FIXME Selection::rotate() does not process absolute rotations correctly: It does not recognize the axis index, which was changed. + // transformation_type.set_absolute(); + transformation_type.set_local(); + } +#else if (selection.is_single_full_instance() || selection.requires_local_axes()) transformation_type.set_independent(); if (selection.is_single_full_instance() && ! m_world_coordinates) { @@ -928,6 +942,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) // transformation_type.set_absolute(); transformation_type.set_local(); } +#endif // ENABLE_WORLD_COORDINATE selection.setup_cache(); selection.rotate( diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index cacda0cd5..dab2b6414 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -787,7 +787,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; const Vec3d &rotation = first_volume.get_instance_rotation(); const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + volume.set_instance_rotation(Vec3d(rotation.x(), rotation.y(), rotation.z() + z_diff)); } else { // extracts rotations from the composed transformation @@ -824,16 +824,27 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ if (is_single_full_instance()) rotate_instance(v, i); else if (is_single_volume() || is_single_modifier()) { - if (transformation_type.independent()) +#if ENABLE_WORLD_COORDINATE + if (transformation_type.local()) v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); + else { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + m = m * m_cache.volumes_data[i].get_instance_rotation_matrix(); + m = m * m_cache.volumes_data[i].get_volume_rotation_matrix(); + m = m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * m; + v.set_volume_rotation(Geometry::extract_euler_angles(m)); + } +#else + if (transformation_type.independent()) + v.set_volume_rotation(v.get_volume_rotation() + rotation); else { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); v.set_volume_rotation(new_rotation); } +#endif // ENABLE_WORLD_COORDINATE } - else - { + else { if (m_mode == Instance) rotate_instance(v, i); else if (m_mode == Volume) { From 6bdaf0eaec949e1f50556c83d9e19fe94302a528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 2 Jun 2022 11:51:43 +0200 Subject: [PATCH 029/102] Follow-up of a47446574eb3f831907248dc841eb6f7684bbdb3 - Disable tbb::task_scheduler_observer in TBBLocalesSetter destructor. The base class wasn't disabling observing when tbb::task_scheduler_observer was destructed, which leads to undefined behavior. --- src/libslic3r/GCode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1f7eaba24..58951ffa5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1501,7 +1501,7 @@ class TBBLocalesSetter : public tbb::task_scheduler_observer { public: TBBLocalesSetter() { this->observe(true); } - ~TBBLocalesSetter() override = default; + ~TBBLocalesSetter() override { this->observe(false); }; void on_scheduler_entry(bool is_worker) override { @@ -1513,7 +1513,7 @@ public: } private: - tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets; + tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets{false}; }; // Process all layers of all objects (non-sequential mode) with a parallel pipeline: From ca5c04bab2c7b78959135b8ef577a817fce88549 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 18 Oct 2021 15:26:31 +0200 Subject: [PATCH 030/102] Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale() Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 67 +++++++++++++++++++++++ src/slic3r/GUI/Selection.cpp | 7 +++ 2 files changed, 74 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 21b55ea69..2b66c6263 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -227,12 +227,79 @@ void GLGizmosManager::set_hover_id(int id) void GLGizmosManager::update_data() { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//<<<<<<< HEAD +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_enabled) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//======= +// if (!m_enabled) +// return; +// +// const Selection& selection = m_parent.get_selection(); +// +// bool is_wipe_tower = selection.is_wipe_tower(); +// enable_grabber(Move, 2, !is_wipe_tower); +// enable_grabber(Rotate, 0, !is_wipe_tower); +// enable_grabber(Rotate, 1, !is_wipe_tower); +// +//#if ENABLE_WORLD_COORDINATE +// bool enable_scale_xyz = !selection.requires_uniform_scale(); +//#else +// bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); +//#endif // ENABLE_WORLD_COORDINATE +// for (unsigned int i = 0; i < 6; ++i) { +// enable_grabber(Scale, i, enable_scale_xyz); +// } +// +//>>>>>>> 8cf66f52f (Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale()) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_common_gizmos_data) m_common_gizmos_data->update(get_current() ? get_current()->get_requirements() : CommonGizmosDataID(0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//<<<<<<< HEAD +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_current != Undefined) m_gizmos[m_current]->data_changed(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//======= +// +// if (selection.is_single_full_instance()) { +// // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first +// const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +// set_scale(volume->get_instance_scaling_factor()); +// set_rotation(Vec3d::Zero()); +// ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; +// set_flattening_data(model_object); +// set_sla_support_data(model_object); +// set_painter_gizmo_data(); +// } +// else if (selection.is_single_volume() || selection.is_single_modifier()) { +// const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +// set_scale(volume->get_volume_scaling_factor()); +// set_rotation(Vec3d::Zero()); +// set_flattening_data(nullptr); +// set_sla_support_data(nullptr); +// set_painter_gizmo_data(); +// } +// else if (is_wipe_tower) { +// DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; +// set_scale(Vec3d::Ones()); +// set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value)); +// set_flattening_data(nullptr); +// set_sla_support_data(nullptr); +// set_painter_gizmo_data(); +// } +// else { +// set_scale(Vec3d::Ones()); +// set_rotation(Vec3d::Zero()); +// set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); +// set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); +// set_painter_gizmo_data(); +// } +//>>>>>>> 8cf66f52f (Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale()) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } bool GLGizmosManager::is_running() const diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index dab2b6414..17fc4a674 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -593,8 +593,15 @@ bool Selection::matches(const std::vector& volume_idxs) const bool Selection::requires_uniform_scale() const { +#if ENABLE_WORLD_COORDINATE + if (is_single_modifier() || is_single_volume()) + return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); + else if (is_single_full_instance() && wxGetApp().obj_manipul()->get_world_coordinates()) + return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); +#else if (is_single_full_instance() || is_single_modifier() || is_single_volume()) return false; +#endif // ENABLE_WORLD_COORDINATE return true; } From f89a902bbb08ce9beba4a193f921160276b0dbf6 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 19 Oct 2021 10:04:22 +0200 Subject: [PATCH 031/102] Tech ENABLE_WORLD_COORDINATE - Fixed translation in local coordinate for single instance selection Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 12 ++-- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 67 ----------------------- src/slic3r/GUI/Selection.cpp | 2 +- 3 files changed, 9 insertions(+), 72 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 6e999ef07..84ba510bc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -95,13 +95,17 @@ void GLGizmoMove3D::on_start_dragging() #if ENABLE_WORLD_COORDINATE const BoundingBoxf3 box = get_selection_box(); Vec3d center; - if (wxGetApp().obj_manipul()->get_world_coordinates()) + if (wxGetApp().obj_manipul()->get_world_coordinates()) { center = box.center(); + m_starting_drag_position = center + m_grabbers[m_hover_id].center; + } else { const Selection& selection = m_parent.get_selection(); - center = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix() * box.center(); + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const Transform3d trafo = Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()); + center = v.get_instance_offset() + trafo * box.center(); + m_starting_drag_position = center + trafo * m_grabbers[m_hover_id].center; } - m_starting_drag_position = center + m_grabbers[m_hover_id].center; m_starting_box_center = center; m_starting_box_bottom_center = center; m_starting_box_bottom_center.z() = box.min.z(); @@ -112,8 +116,8 @@ void GLGizmoMove3D::on_start_dragging() m_starting_box_bottom_center = box.center(); m_starting_box_bottom_center.z() = box.min.z(); #endif // ENABLE_WORLD_COORDINATE - } } +} void GLGizmoMove3D::on_stop_dragging() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 2b66c6263..21b55ea69 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -227,79 +227,12 @@ void GLGizmosManager::set_hover_id(int id) void GLGizmosManager::update_data() { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//<<<<<<< HEAD -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_enabled) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//======= -// if (!m_enabled) -// return; -// -// const Selection& selection = m_parent.get_selection(); -// -// bool is_wipe_tower = selection.is_wipe_tower(); -// enable_grabber(Move, 2, !is_wipe_tower); -// enable_grabber(Rotate, 0, !is_wipe_tower); -// enable_grabber(Rotate, 1, !is_wipe_tower); -// -//#if ENABLE_WORLD_COORDINATE -// bool enable_scale_xyz = !selection.requires_uniform_scale(); -//#else -// bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); -//#endif // ENABLE_WORLD_COORDINATE -// for (unsigned int i = 0; i < 6; ++i) { -// enable_grabber(Scale, i, enable_scale_xyz); -// } -// -//>>>>>>> 8cf66f52f (Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale()) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_common_gizmos_data) m_common_gizmos_data->update(get_current() ? get_current()->get_requirements() : CommonGizmosDataID(0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//<<<<<<< HEAD -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_current != Undefined) m_gizmos[m_current]->data_changed(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//======= -// -// if (selection.is_single_full_instance()) { -// // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first -// const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); -// set_scale(volume->get_instance_scaling_factor()); -// set_rotation(Vec3d::Zero()); -// ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; -// set_flattening_data(model_object); -// set_sla_support_data(model_object); -// set_painter_gizmo_data(); -// } -// else if (selection.is_single_volume() || selection.is_single_modifier()) { -// const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); -// set_scale(volume->get_volume_scaling_factor()); -// set_rotation(Vec3d::Zero()); -// set_flattening_data(nullptr); -// set_sla_support_data(nullptr); -// set_painter_gizmo_data(); -// } -// else if (is_wipe_tower) { -// DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; -// set_scale(Vec3d::Ones()); -// set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value)); -// set_flattening_data(nullptr); -// set_sla_support_data(nullptr); -// set_painter_gizmo_data(); -// } -// else { -// set_scale(Vec3d::Ones()); -// set_rotation(Vec3d::Zero()); -// set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); -// set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); -// set_painter_gizmo_data(); -// } -//>>>>>>> 8cf66f52f (Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale()) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } bool GLGizmosManager::is_running() const diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 17fc4a674..0d29fdaae 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -721,7 +721,7 @@ void Selection::translate(const Vec3d& displacement, bool local) if (is_from_fully_selected_instance(i)) { if (local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; + const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; v.set_instance_offset(volume_data.get_instance_position() + world_displacement); } else From 856e0caea62055a8e94fa705170c8f0ae619e2ae Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 19 Oct 2021 10:26:51 +0200 Subject: [PATCH 032/102] Tech ENABLE_WORLD_COORDINATE - Fixed rotation in local coordinate for single instance selection Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 0d29fdaae..edbffacd1 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -803,7 +803,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const Vec3d new_rotation = transformation_type.world() ? Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()) : transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m); - if (rot_axis_max == 2 && transformation_type.world() && transformation_type.joint()) { + if (rot_axis_max == 2 && transformation_type.joint()) { // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); From d5a02e617a1cf427703e0e94d784bec932f4f333 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 20 Oct 2021 12:29:27 +0200 Subject: [PATCH 033/102] Partial revert of 7e5c214b91ae14740fc188413948818a1b49928a to restore code mistakenly removed and needed when tech ENABLE_WORLD_COORDINATE is disabled Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 11 ++++++++--- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 5d459f747..14bbd5102 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -207,6 +207,7 @@ void GLGizmoScale3D::on_render() #else // Transforms grabbers' offsets to world refefence system Transform3d offsets_transform = Transform3d::Identity(); + m_offsets_transform = Transform3d::Identity(); Vec3d angles = Vec3d::Zero(); if (selection.is_single_full_instance()) { @@ -233,6 +234,7 @@ void GLGizmoScale3D::on_render() angles = v->get_instance_rotation(); // consider rotation+mirror only components of the transform for offsets offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); + m_offsets_transform = offsets_transform; #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE @@ -253,6 +255,7 @@ void GLGizmoScale3D::on_render() angles = Geometry::extract_euler_angles(m_transform); // consider rotation+mirror only components of the transform for offsets offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); + m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v->get_volume_rotation(), Vec3d::Ones(), v->get_volume_mirror()); #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE @@ -625,9 +628,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) const double inner_ratio = len_center_vec / len_starting_vec; double local_offset = inner_ratio * 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); #else - double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); - if (!m_parent.get_selection().is_single_full_instance()) - local_offset *= m_starting.scale(axis); + double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); #endif // ENABLE_WORLD_COORDINATE if (m_hover_id == 2 * axis) @@ -642,7 +643,11 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: break; } +#if ENABLE_WORLD_COORDINATE m_offset = local_offset_vec; +#else + m_offset = m_offsets_transform * local_offset_vec; +#endif // ENABLE_WORLD_COORDINATE } else m_offset = Vec3d::Zero(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 70ea91016..0792fcc7a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -33,6 +33,9 @@ class GLGizmoScale3D : public GLGizmoBase #if ENABLE_WORLD_COORDINATE Transform3d m_grabbers_transform; Vec3d m_center{ Vec3d::Zero() }; +#else + // Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes) + Transform3d m_offsets_transform; #endif // ENABLE_WORLD_COORDINATE Vec3d m_scale{ Vec3d::Ones() }; Vec3d m_offset{ Vec3d::Zero() }; From d7753fc476d74187b638d13c7552ca1b9dd29770 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 08:23:13 +0200 Subject: [PATCH 034/102] Tech ENABLE_WORLD_COORDINATE - Fixed constrained non-uniform scaling in world coordinates for rotated instances Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 19 ++++++++++++++++++- src/slic3r/GUI/Selection.cpp | 10 +++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 14bbd5102..34fe723b0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -616,11 +616,28 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { #if ENABLE_WORLD_COORDINATE double ratio = calc_ratio(data); + if (ratio > 0.0) { + Vec3d curr_scale = m_scale; + Vec3d starting_scale = m_starting.scale; + const Selection& selection = m_parent.get_selection(); + const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); + if (selection.is_single_full_instance() && world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + curr_scale = (m * curr_scale).cwiseAbs(); + starting_scale = (m * starting_scale).cwiseAbs(); + } + + curr_scale(axis) = starting_scale(axis) * ratio; + + if (selection.is_single_full_instance() && world_coordinates) + m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); + else + m_scale = curr_scale; #else const double ratio = calc_ratio(data); -#endif // ENABLE_WORLD_COORDINATE if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; +#endif // ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { #if ENABLE_WORLD_COORDINATE const double len_starting_vec = std::abs(m_starting.box.center()(axis) - m_starting.pivots[m_hover_id](axis)); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index edbffacd1..28e8674c4 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -945,16 +945,19 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type GLVolume &v = *(*m_volumes)[i]; if (is_single_full_instance()) { if (transformation_type.relative()) { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + const Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); if (transformation_type.joint()) v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); v.set_instance_scaling_factor(new_scale); } else { +#if ENABLE_WORLD_COORDINATE + v.set_instance_scaling_factor(scale); +#else if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { // Non-uniform scaling. Transform the scaling factors into the local coordinate system. // This is only possible, if the instance rotation is mulitples of ninety degrees. @@ -963,6 +966,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type } else v.set_instance_scaling_factor(scale); +#endif // ENABLE_WORLD_COORDINATE } } else if (is_single_volume() || is_single_modifier()) From 1191ab42cb1e0a2046237f892b6d404415256dfd Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 09:32:49 +0200 Subject: [PATCH 035/102] Tech ENABLE_WORLD_COORDINATE - Added constrained uniform scaling in world coordinates for instances Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 23 ++++++++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 3 ++- src/slic3r/GUI/Selection.cpp | 4 +++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index d4b1a3b67..d55fd96f8 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1104,6 +1104,7 @@ void ObjectManipulation::set_world_coordinates(const bool world_coordinates) m_world_coordinates = world_coordinates; this->UpdateAndShow(true); GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + canvas->get_gizmos_manager().update_data(); canvas->set_as_dirty(); canvas->request_extra_frame(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 34fe723b0..fac348cdd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -159,6 +159,10 @@ void GLGizmoScale3D::on_start_dragging() m_starting.pivots[3] = trafo * Vec3d(center.x(), m_starting.box.min.y(), center.z()); m_starting.pivots[4] = trafo * Vec3d(center.x(), center.y(), m_starting.box.max.z()); m_starting.pivots[5] = trafo * Vec3d(center.x(), center.y(), m_starting.box.min.z()); + m_starting.pivots[6] = trafo * Vec3d(m_starting.box.max.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[7] = trafo * Vec3d(m_starting.box.min.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[8] = trafo * Vec3d(m_starting.box.min.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[9] = trafo * Vec3d(m_starting.box.max.x(), m_starting.box.min.y(), center.z()); } #else m_starting.drag_position = m_grabbers[m_hover_id].center; @@ -323,18 +327,22 @@ void GLGizmoScale3D::on_render() // uniform #if ENABLE_WORLD_COORDINATE m_grabbers[6].center = { -(box_half_size.x() + Offset), -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[6].color = (use_constrain && m_hover_id == 8) ? CONSTRAINED_COLOR : m_highlight_color; m_grabbers[7].center = { box_half_size.x() + Offset, -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[7].color = (use_constrain && m_hover_id == 9) ? CONSTRAINED_COLOR : m_highlight_color; m_grabbers[8].center = { box_half_size.x() + Offset, box_half_size.y() + Offset, 0.0 }; + m_grabbers[8].color = (use_constrain && m_hover_id == 6) ? CONSTRAINED_COLOR : m_highlight_color; m_grabbers[9].center = { -(box_half_size.x() + Offset), box_half_size.y() + Offset, 0.0 }; + m_grabbers[9].color = (use_constrain && m_hover_id == 7) ? CONSTRAINED_COLOR : m_highlight_color; #else m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; -#endif // ENABLE_WORLD_COORDINATE for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_WORLD_COORDINATE // sets grabbers orientation @@ -676,7 +684,20 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale = m_starting.scale * ratio; +#if ENABLE_WORLD_COORDINATE + if (m_starting.ctrl_down) { + m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); + if (m_hover_id == 6 || m_hover_id == 9) + m_offset.x() *= -1.0; + if (m_hover_id == 6 || m_hover_id == 7) + m_offset.y() *= -1.0; + } + else { +#endif // ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); +#if ENABLE_WORLD_COORDINATE + } +#endif // ENABLE_WORLD_COORDINATE } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 0792fcc7a..1429cf633 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -25,7 +25,8 @@ class GLGizmoScale3D : public GLGizmoBase Vec3d center{ Vec3d::Zero() }; #endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; - std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; + std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), + Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(),Vec3d::Zero() }; }; BoundingBoxf3 m_box; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 28e8674c4..897d153f9 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -598,12 +598,14 @@ bool Selection::requires_uniform_scale() const return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); else if (is_single_full_instance() && wxGetApp().obj_manipul()->get_world_coordinates()) return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); + + return false; #else if (is_single_full_instance() || is_single_modifier() || is_single_volume()) return false; -#endif // ENABLE_WORLD_COORDINATE return true; +#endif // ENABLE_WORLD_COORDINATE } int Selection::get_object_idx() const From 90744071962a091caef19e12aadd48f5d212c66d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 11:27:36 +0200 Subject: [PATCH 036/102] Follow-up of 86b44b48005e3ddb96e851e19f8e2ccd1a38b667 - Constrained uniform scaling in world coordinates for rotated instances --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 7 +++---- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index fac348cdd..6cadae77d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -159,10 +159,6 @@ void GLGizmoScale3D::on_start_dragging() m_starting.pivots[3] = trafo * Vec3d(center.x(), m_starting.box.min.y(), center.z()); m_starting.pivots[4] = trafo * Vec3d(center.x(), center.y(), m_starting.box.max.z()); m_starting.pivots[5] = trafo * Vec3d(center.x(), center.y(), m_starting.box.min.z()); - m_starting.pivots[6] = trafo * Vec3d(m_starting.box.max.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[7] = trafo * Vec3d(m_starting.box.min.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[8] = trafo * Vec3d(m_starting.box.min.x(), m_starting.box.min.y(), center.z()); - m_starting.pivots[9] = trafo * Vec3d(m_starting.box.max.x(), m_starting.box.min.y(), center.z()); } #else m_starting.drag_position = m_grabbers[m_hover_id].center; @@ -687,10 +683,13 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) #if ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); + if (m_hover_id == 6 || m_hover_id == 9) m_offset.x() *= -1.0; if (m_hover_id == 6 || m_hover_id == 7) m_offset.y() *= -1.0; + + m_offset += (ratio - 1.0) * (m_starting.center - m_starting.box.center()); } else { #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 1429cf633..0792fcc7a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -25,8 +25,7 @@ class GLGizmoScale3D : public GLGizmoBase Vec3d center{ Vec3d::Zero() }; #endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; - std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), - Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(),Vec3d::Zero() }; + std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; }; BoundingBoxf3 m_box; From 0eaa4c5dea87d9afd2c402236a19ea7226fa7094 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 12:16:59 +0200 Subject: [PATCH 037/102] Tech ENABLE_WORLD_COORDINATE - Fixed unconstrained scaling in world coordinates for volumes Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 6 ++++++ src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 12 ++++++++++++ src/slic3r/GUI/Selection.cpp | 12 ++++++------ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index d55fd96f8..b7fc1282d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1014,12 +1014,18 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d scaling_factor = scale; +#if ENABLE_WORLD_COORDINATE + TransformationType transformation_type; + if (!m_world_coordinates) + transformation_type.set_local(); +#else TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { transformation_type.set_absolute(); if (! m_world_coordinates) transformation_type.set_local(); } +#endif // ENABLE_WORLD_COORDINATE if (m_uniform_scale || selection.requires_uniform_scale()) scaling_factor = scale(axis) * Vec3d::Ones(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 6cadae77d..e5eead56c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -630,11 +630,23 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) curr_scale = (m * curr_scale).cwiseAbs(); starting_scale = (m * starting_scale).cwiseAbs(); } + else if ((selection.is_single_volume() || selection.is_single_modifier()) && world_coordinates) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d m = mi * mv; + curr_scale = (m * curr_scale).cwiseAbs(); + starting_scale = (m * starting_scale).cwiseAbs(); + } curr_scale(axis) = starting_scale(axis) * ratio; if (selection.is_single_full_instance() && world_coordinates) m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); + else if ((selection.is_single_volume() || selection.is_single_modifier()) && world_coordinates) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + m_scale = (mv * mi * curr_scale).cwiseAbs(); + } else m_scale = curr_scale; #else diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 897d153f9..7db608a48 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -974,22 +974,22 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type else if (is_single_volume() || is_single_modifier()) v.set_volume_scaling_factor(scale); else { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); if (m_mode == Instance) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + const Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); if (transformation_type.joint()) v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); v.set_instance_scaling_factor(new_scale); } else if (m_mode == Volume) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); + const Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); if (transformation_type.joint()) { - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); v.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); } v.set_volume_scaling_factor(new_scale); From 4aac01a2214ba379a362da215f2085f14434eaac Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 14:05:56 +0200 Subject: [PATCH 038/102] Tech ENABLE_WORLD_COORDINATE - Fixed scaling using the Part Manipulation fields in world coordinates for volumes Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 22 ++++++++++++++++++---- src/slic3r/GUI/Selection.cpp | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index b7fc1282d..14fc331dc 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -626,13 +626,12 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const Vec3d& offset = trafo.get_offset(); const Vec3d& rotation = trafo.get_rotation(); #endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET - const Vec3d& scaling_factor = trafo.get_scaling_factor(); // const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; m_new_rotation = Vec3d::Zero(); - m_new_scale = scaling_factor * 100.0; - m_new_size = volume->bounding_box().size().cwiseProduct(scaling_factor); + m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); + m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; } else { #endif // ENABLE_WORLD_COORDINATE @@ -1012,12 +1011,27 @@ void ObjectManipulation::change_size_value(int axis, double value) void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const { Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); +#if !ENABLE_WORLD_COORDINATE Vec3d scaling_factor = scale; +#endif // !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; if (!m_world_coordinates) transformation_type.set_local(); + + bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); + Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; + + if (!uniform_scale && m_world_coordinates) { + if (selection.is_single_full_instance()) + scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); + else if (selection.is_single_volume() || selection.is_single_modifier()) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); + } + } #else TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { @@ -1025,10 +1039,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const if (! m_world_coordinates) transformation_type.set_local(); } -#endif // ENABLE_WORLD_COORDINATE if (m_uniform_scale || selection.requires_uniform_scale()) scaling_factor = scale(axis) * Vec3d::Ones(); +#endif // ENABLE_WORLD_COORDINATE selection.setup_cache(); selection.scale(scaling_factor, transformation_type); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 7db608a48..cffd05c0c 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -599,7 +599,7 @@ bool Selection::requires_uniform_scale() const else if (is_single_full_instance() && wxGetApp().obj_manipul()->get_world_coordinates()) return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); - return false; + return true; #else if (is_single_full_instance() || is_single_modifier() || is_single_volume()) return false; @@ -678,7 +678,7 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const const GLVolume& volume = *(*m_volumes)[i]; if (volume.is_modifier) continue; - Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix(); + Transform3d trafo = volume.get_instance_transformation().get_matrix() * volume.get_volume_transformation().get_matrix(); trafo.translation().z() += volume.get_sla_shift_z(); (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); } From ad6dcf3f1068948729324a66c9218e4090057170 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Oct 2021 11:39:29 +0200 Subject: [PATCH 039/102] Tech ENABLE_WORLD_COORDINATE - Fixed constrained scaling of instances in local coordinates Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 12 ++-- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 86 ++++++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 3 + src/slic3r/GUI/Selection.cpp | 10 ++- 4 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 14fc331dc..8a0df5552 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -761,11 +761,15 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE double min_z = 0.0; -#if ENABLE_WORLD_COORDINATE - if (selection.is_single_full_instance() && m_world_coordinates) { -#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#if ENABLE_WORLD_COORDINATE +// if (selection.is_single_full_instance() && m_world_coordinates) { +//#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (selection.is_single_full_instance()) { -#endif // ENABLE_WORLD_COORDINATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#endif // ENABLE_WORLD_COORDINATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); min_z = selection.get_scaled_instance_bounding_box().min.z(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index e5eead56c..8096ce260 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -149,17 +149,7 @@ void GLGizmoScale3D::on_start_dragging() m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; m_starting.box = m_box; m_starting.center = m_center; - - if (m_starting.ctrl_down) { - const Vec3d center = m_starting.box.center(); - const Transform3d trafo = wxGetApp().obj_manipul()->get_world_coordinates() ? Transform3d::Identity() : m_transform; - m_starting.pivots[0] = trafo * Vec3d(m_starting.box.max.x(), center.y(), center.z()); - m_starting.pivots[1] = trafo * Vec3d(m_starting.box.min.x(), center.y(), center.z()); - m_starting.pivots[2] = trafo * Vec3d(center.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[3] = trafo * Vec3d(center.x(), m_starting.box.min.y(), center.z()); - m_starting.pivots[4] = trafo * Vec3d(center.x(), center.y(), m_starting.box.max.z()); - m_starting.pivots[5] = trafo * Vec3d(center.x(), center.y(), m_starting.box.min.z()); - } + m_starting.transform = wxGetApp().obj_manipul()->get_world_coordinates() ? Transform3d::Identity() : m_transform; #else m_starting.drag_position = m_grabbers[m_hover_id].center; m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_box : m_parent.get_selection().get_bounding_box(); @@ -202,8 +192,8 @@ void GLGizmoScale3D::on_render() m_transform = Transform3d::Identity(); #if ENABLE_WORLD_COORDINATE m_grabbers_transform = Transform3d::Identity(); - - if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { + bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); + if (selection.is_single_full_instance() && !world_coordinates) { #else // Transforms grabbers' offsets to world refefence system Transform3d offsets_transform = Transform3d::Identity(); @@ -211,25 +201,31 @@ void GLGizmoScale3D::on_render() Vec3d angles = Vec3d::Zero(); if (selection.is_single_full_instance()) { -#endif // !ENABLE_WORLD_COORDINATE - +#endif // ENABLE_WORLD_COORDINATE // calculate bounding box in instance local reference system const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int idx : idxs) { const GLVolume* v = selection.get_volume(idx); #if ENABLE_WORLD_COORDINATE - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); #else m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); #endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + m_box = m_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); +#endif // ENABLE_WORLD_COORDINATE + // gets transform from first selected volume const GLVolume* v = selection.get_volume(*idxs.begin()); - m_transform = v->get_instance_transformation().get_matrix(); #if ENABLE_WORLD_COORDINATE - m_grabbers_transform = v->get_instance_transformation().get_matrix(true, true, false, true); + m_transform = v->get_instance_transformation().get_matrix(false, false, true); + m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_box.center()); + m_center = v->get_instance_offset(); #else + m_transform = v->get_instance_transformation().get_matrix(); + // gets angles from first selected volume angles = v->get_instance_rotation(); // consider rotation+mirror only components of the transform for offsets @@ -238,7 +234,7 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE - else if ((selection.is_single_modifier() || selection.is_single_volume()) && !wxGetApp().obj_manipul()->get_world_coordinates()) { + else if ((selection.is_single_modifier() || selection.is_single_volume()) && !world_coordinates) { #else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE @@ -272,14 +268,14 @@ void GLGizmoScale3D::on_render() Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); Vec3d offset_z = offsets_transform * (Offset * Vec3d::UnitZ()); -#endif // ENABLE_WORLD_COORDINATE bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); +#endif // ENABLE_WORLD_COORDINATE // x axis #if ENABLE_WORLD_COORDINATE const Vec3d box_half_size = 0.5 * m_box.size(); - bool use_constrain = ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); + bool use_constrain = wxGetKeyState(WXK_CONTROL) && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 }; m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; @@ -656,10 +652,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #endif // ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { #if ENABLE_WORLD_COORDINATE - const double len_starting_vec = std::abs(m_starting.box.center()(axis) - m_starting.pivots[m_hover_id](axis)); - const double len_center_vec = std::abs(m_starting.center(axis) - m_starting.pivots[m_hover_id](axis)); - const double inner_ratio = len_center_vec / len_starting_vec; - double local_offset = inner_ratio * 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); + double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); #else double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); #endif // ENABLE_WORLD_COORDINATE @@ -667,6 +660,23 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (m_hover_id == 2 * axis) local_offset *= -1.0; +#if ENABLE_WORLD_COORDINATE + Vec3d center_offset = m_starting.center - m_starting.transform * m_starting.box.center(); + if (selection.is_single_full_instance() && !world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + center_offset = m * center_offset; + } + + local_offset += (ratio - 1.0) * center_offset(axis); + + switch (axis) + { + case X: { m_offset = local_offset * Vec3d::UnitX(); break; } + case Y: { m_offset = local_offset * Vec3d::UnitY(); break; } + case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } + default: { m_offset = Vec3d::Zero(); break; } + } +#else Vec3d local_offset_vec; switch (axis) { @@ -676,9 +686,6 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: break; } -#if ENABLE_WORLD_COORDINATE - m_offset = local_offset_vec; -#else m_offset = m_offsets_transform * local_offset_vec; #endif // ENABLE_WORLD_COORDINATE } @@ -701,24 +708,33 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) if (m_hover_id == 6 || m_hover_id == 7) m_offset.y() *= -1.0; - m_offset += (ratio - 1.0) * (m_starting.center - m_starting.box.center()); + const Selection& selection = m_parent.get_selection(); + const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); + Vec3d center_offset = m_starting.center - m_starting.transform * m_starting.box.center(); + if (selection.is_single_full_instance() && !world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + center_offset = m * center_offset; + } + + m_offset += (ratio - 1.0) * center_offset; } - else { + else #endif // ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); -#if ENABLE_WORLD_COORDINATE - } -#endif // ENABLE_WORLD_COORDINATE - } +} } double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; +#if ENABLE_WORLD_COORDINATE + const Vec3d starting_vec = m_starting.drag_position - m_starting.center; +#else const Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); - const Vec3d starting_vec = m_starting.drag_position - pivot; +#endif // ENABLE_WORLD_COORDINATE + const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 0792fcc7a..16ecdbb6c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -23,9 +23,12 @@ class GLGizmoScale3D : public GLGizmoBase Vec3d drag_position{ Vec3d::Zero() }; #if ENABLE_WORLD_COORDINATE Vec3d center{ Vec3d::Zero() }; + Transform3d transform; #endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; +#if !ENABLE_WORLD_COORDINATE std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; +#endif // !ENABLE_WORLD_COORDINATE }; BoundingBoxf3 m_box; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index cffd05c0c..ad1241212 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -596,8 +596,12 @@ bool Selection::requires_uniform_scale() const #if ENABLE_WORLD_COORDINATE if (is_single_modifier() || is_single_volume()) return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); - else if (is_single_full_instance() && wxGetApp().obj_manipul()->get_world_coordinates()) - return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); + else if (is_single_full_instance()) { + if (wxGetApp().obj_manipul()->get_world_coordinates()) + return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); + else + return false; + } return true; #else @@ -606,7 +610,7 @@ bool Selection::requires_uniform_scale() const return true; #endif // ENABLE_WORLD_COORDINATE -} + } int Selection::get_object_idx() const { From 9e0bb83041e849605b0ed0be3bd342d998fc86b7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Oct 2021 11:55:11 +0200 Subject: [PATCH 040/102] Follow-up of 9ddf2ba41ccc59650c277d5a5b4604afa8702f3b - Code cleanup Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 8 -------- src/slic3r/GUI/Selection.cpp | 11 ++++------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8a0df5552..238f26441 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -761,15 +761,7 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE double min_z = 0.0; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#if ENABLE_WORLD_COORDINATE -// if (selection.is_single_full_instance() && m_world_coordinates) { -//#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (selection.is_single_full_instance()) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#endif // ENABLE_WORLD_COORDINATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); min_z = selection.get_scaled_instance_bounding_box().min.z(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index ad1241212..8a6022c3e 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -596,12 +596,9 @@ bool Selection::requires_uniform_scale() const #if ENABLE_WORLD_COORDINATE if (is_single_modifier() || is_single_volume()) return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); - else if (is_single_full_instance()) { - if (wxGetApp().obj_manipul()->get_world_coordinates()) - return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); - else - return false; - } + else if (is_single_full_instance()) + return wxGetApp().obj_manipul()->get_world_coordinates() ? + !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; return true; #else @@ -610,7 +607,7 @@ bool Selection::requires_uniform_scale() const return true; #endif // ENABLE_WORLD_COORDINATE - } +} int Selection::get_object_idx() const { From cf90ad699feda863782f18add5ae560269fcd57a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Oct 2021 13:58:53 +0200 Subject: [PATCH 041/102] Tech ENABLE_WORLD_COORDINATE - Fixed unconstrained scaling of volumes in local coordinates Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 ++++ src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 238f26441..fdd83fe27 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -709,7 +709,11 @@ void ObjectManipulation::update_if_dirty() if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); +#if ENABLE_WORLD_COORDINATE + m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection or non axis-aligned objects/parts")); +#else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); +#endif // ENABLE_WORLD_COORDINATE m_lock_bnt->disable(); } else { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 8096ce260..7c466137a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -240,14 +240,16 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix(true, true, false, true))); #else m_box = v->bounding_box(); #endif // ENABLE_WORLD_COORDINATE - m_transform = v->world_matrix(); #if ENABLE_WORLD_COORDINATE - m_grabbers_transform = m_transform; + m_transform = v->get_volume_transformation().get_matrix(false, false, true); + m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * m_transform * Geometry::assemble_transform(m_box.center()); + m_center = v->world_matrix() * m_box.center(); #else + m_transform = v->world_matrix(); angles = Geometry::extract_euler_angles(m_transform); // consider rotation+mirror only components of the transform for offsets offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); @@ -766,7 +768,9 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const glsafe(::glTranslated(center.x(), center.y(), center.z())); if (!wxGetApp().obj_manipul()->get_world_coordinates()) { - const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + if (selection.is_single_volume() || selection.is_single_modifier()) + orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } } From f944595d3d71dff9e97b3b005f32f3eb916cdd3e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Oct 2021 14:49:47 +0200 Subject: [PATCH 042/102] Tech ENABLE_WORLD_COORDINATE - Fixed constrained scaling of volumes in local coordinates --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 7c466137a..39ef9e6d2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -245,8 +245,8 @@ void GLGizmoScale3D::on_render() m_box = v->bounding_box(); #endif // ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE - m_transform = v->get_volume_transformation().get_matrix(false, false, true); - m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * m_transform * Geometry::assemble_transform(m_box.center()); + m_transform = v->world_matrix(); + m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * v->get_volume_transformation().get_matrix(false, false, true); m_center = v->world_matrix() * m_box.center(); #else m_transform = v->world_matrix(); @@ -678,6 +678,11 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } default: { m_offset = Vec3d::Zero(); break; } } + + if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + m_offset = m * m_offset; + } #else Vec3d local_offset_vec; switch (axis) @@ -719,6 +724,11 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) } m_offset += (ratio - 1.0) * center_offset; + + if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + m_offset = m * m_offset; + } } else #endif // ENABLE_WORLD_COORDINATE From 23b5860e350af70c0020ede01e1e2f20f8c85d73 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 Oct 2021 13:29:50 +0200 Subject: [PATCH 043/102] Tech ENABLE_WORLD_COORDINATE - Fixed visualization of sidebar hints when editing values in Object manipulation fields while using an MMU printer --- src/slic3r/GUI/GUI_ObjectList.cpp | 6 ++++++ src/slic3r/GUI/GUI_ObjectManipulation.hpp | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4370a2f64..477c13b31 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2545,7 +2545,13 @@ void ObjectList::part_selection_changed() Sidebar& panel = wxGetApp().sidebar(); panel.Freeze(); +#if ENABLE_WORLD_COORDINATE + const ManipulationEditor* const editor = wxGetApp().obj_manipul()->get_focused_editor(); + const std::string opt_key = (editor != nullptr) ? editor->get_full_opt_name() : ""; + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, !opt_key.empty()); +#else wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); +#endif // ENABLE_WORLD_COORDINATE wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index a4f826fea..849a443f4 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -57,6 +57,10 @@ public: void set_value(const wxString& new_value); void kill_focus(ObjectManipulation *parent); +#if ENABLE_WORLD_COORDINATE + const std::string& get_full_opt_name() const { return m_full_opt_name; } +#endif // ENABLE_WORLD_COORDINATE + private: double get_value(); }; @@ -152,10 +156,15 @@ private: ScalableBitmap m_manifold_warning_bmp; wxStaticBitmap* m_fix_throught_netfab_bitmap; +#if ENABLE_WORLD_COORDINATE + // Currently focused editor (nullptr if none) + ManipulationEditor* m_focused_editor{ nullptr }; +#else #ifndef __APPLE__ // Currently focused editor (nullptr if none) ManipulationEditor* m_focused_editor {nullptr}; #endif // __APPLE__ +#endif // ENABLE_WORLD_COORDINATE wxFlexGridSizer* m_main_grid_sizer; wxFlexGridSizer* m_labels_grid_sizer; @@ -205,11 +214,19 @@ public: void sys_color_changed(); void on_change(const std::string& opt_key, int axis, double new_value); void set_focused_editor(ManipulationEditor* focused_editor) { +#if ENABLE_WORLD_COORDINATE + m_focused_editor = focused_editor; +#else #ifndef __APPLE__ m_focused_editor = focused_editor; #endif // __APPLE__ +#endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + ManipulationEditor* get_focused_editor() { return m_focused_editor; } +#endif // ENABLE_WORLD_COORDINATE + private: void reset_settings_value(); void update_settings_value(const Selection& selection); From df552a92268e4a853c550935761740271da0675c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 2 Jun 2022 13:45:06 +0200 Subject: [PATCH 044/102] Fixed an undefined symbol when mold linker was used for linking slic3rutils_tests. --- tests/slic3rutils/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index 256e6efd6..e9fcf84f1 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -4,7 +4,8 @@ add_executable(${_TEST_NAME}_tests slic3r_jobs_tests.cpp ) -target_link_libraries(${_TEST_NAME}_tests test_common libslic3r_gui libslic3r) +# mold linker for successful linking needs also to link TBB library and link it before libslic3r. +target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb libslic3r_gui libslic3r) if (MSVC) target_link_libraries(${_TEST_NAME}_tests Setupapi.lib) endif () From 679f8b0111f56996afdb9282cd6d846a061b98f3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 3 Nov 2021 12:03:13 +0100 Subject: [PATCH 045/102] Tech ENABLE_WORLD_COORDINATE - Fixed center of Move and Scale gizmos Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 71 +++++---- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 201 ++++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 7 +- 5 files changed, 158 insertions(+), 132 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 84ba510bc..11be47619 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -93,22 +93,16 @@ void GLGizmoMove3D::on_start_dragging() if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE - const BoundingBoxf3 box = get_selection_box(); - Vec3d center; - if (wxGetApp().obj_manipul()->get_world_coordinates()) { - center = box.center(); - m_starting_drag_position = center + m_grabbers[m_hover_id].center; - } + if (wxGetApp().obj_manipul()->get_world_coordinates()) + m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else { const Selection& selection = m_parent.get_selection(); const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); - const Transform3d trafo = Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()); - center = v.get_instance_offset() + trafo * box.center(); - m_starting_drag_position = center + trafo * m_grabbers[m_hover_id].center; + m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; } - m_starting_box_center = center; - m_starting_box_bottom_center = center; - m_starting_box_bottom_center.z() = box.min.z(); + m_starting_box_center = m_center; + m_starting_box_bottom_center = m_center; + m_starting_box_bottom_center.z() = m_bounding_box.min.z(); #else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_starting_drag_position = m_grabbers[m_hover_id].center; @@ -152,11 +146,11 @@ void GLGizmoMove3D::on_render() #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); + calc_selection_box_and_center(); transform_to_local(m_parent.get_selection()); const Vec3d zero = Vec3d::Zero(); - BoundingBoxf3 box = get_selection_box(); - const Vec3d half_box_size = 0.5 * box.size(); + const Vec3d half_box_size = 0.5 * m_bounding_box.size(); // x axis m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 }; @@ -268,6 +262,15 @@ void GLGizmoMove3D::on_render() #endif // ENABLE_LEGACY_OPENGL_REMOVAL // draw grabbers +#if ENABLE_WORLD_COORDINATE + render_grabbers(m_bounding_box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR + for (unsigned int i = 0; i < 3; ++i) { + if (m_grabbers[i].enabled) + render_grabber_extension((Axis)i, m_bounding_box, false); + } +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#else render_grabbers(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR for (unsigned int i = 0; i < 3; ++i) { @@ -275,6 +278,7 @@ void GLGizmoMove3D::on_render() render_grabber_extension((Axis)i, box, false); } #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#endif // ENABLE_WORLD_COORDINATE } else { // draw axis @@ -311,13 +315,21 @@ void GLGizmoMove3D::on_render() shader->start_using(); shader->set_uniform("emission_factor", 0.1f); // draw grabber +#if ENABLE_WORLD_COORDINATE + const Vec3d box_size = m_bounding_box.size(); +#else const Vec3d box_size = box.size(); +#endif // ENABLE_WORLD_COORDINATE const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0); m_grabbers[m_hover_id].render(true, mean_size); shader->stop_using(); } #if !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE + render_grabber_extension((Axis)m_hover_id, m_bounding_box, false); +#else render_grabber_extension((Axis)m_hover_id, box, false); +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_GIZMO_GRABBER_REFACTOR } @@ -333,18 +345,21 @@ void GLGizmoMove3D::on_render_for_picking() #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); transform_to_local(m_parent.get_selection()); - const BoundingBoxf3 box = get_selection_box(); + render_grabbers_for_picking(m_bounding_box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR + render_grabber_extension(X, m_bounding_box, true); + render_grabber_extension(Y, m_bounding_box, true); + render_grabber_extension(Z, m_bounding_box, true); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR + glsafe(::glPopMatrix()); #else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); -#endif // ENABLE_WORLD_COORDINATE render_grabbers_for_picking(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(X, box, true); render_grabber_extension(Y, box, true); render_grabber_extension(Z, box, true); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR -#if ENABLE_WORLD_COORDINATE - glsafe(::glPopMatrix()); #endif // ENABLE_WORLD_COORDINATE } @@ -438,8 +453,7 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box #if ENABLE_WORLD_COORDINATE void GLGizmoMove3D::transform_to_local(const Selection& selection) const { - const Vec3d center = selection.get_bounding_box().center(); - glsafe(::glTranslated(center.x(), center.y(), center.z())); + glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); if (!wxGetApp().obj_manipul()->get_world_coordinates()) { const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); @@ -447,20 +461,23 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const } } -BoundingBoxf3 GLGizmoMove3D::get_selection_box() +void GLGizmoMove3D::calc_selection_box_and_center() { const Selection& selection = m_parent.get_selection(); - BoundingBoxf3 box; - if (wxGetApp().obj_manipul()->get_world_coordinates()) - box = selection.get_bounding_box(); + if (wxGetApp().obj_manipul()->get_world_coordinates()) { + m_bounding_box = selection.get_bounding_box(); + m_center = m_bounding_box.center(); + } else { + m_bounding_box.reset(); const Selection::IndicesList& ids = selection.get_volume_idxs(); for (unsigned int id : ids) { - const GLVolume* v = selection.get_volume(id); - box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); + const GLVolume& v = *selection.get_volume(id); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } - return box; } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index e97d1c896..c1368cf62 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -16,6 +16,10 @@ class GLGizmoMove3D : public GLGizmoBase static const double Offset; Vec3d m_displacement{ Vec3d::Zero() }; +#if ENABLE_WORLD_COORDINATE + Vec3d m_center{ Vec3d::Zero() }; + BoundingBoxf3 m_bounding_box; +#endif // ENABLE_WORLD_COORDINATE double m_snap_step{ 1.0 }; Vec3d m_starting_drag_position{ Vec3d::Zero() }; Vec3d m_starting_box_center{ Vec3d::Zero() }; @@ -68,7 +72,7 @@ private: double calc_projection(const UpdateData& data) const; #if ENABLE_WORLD_COORDINATE void transform_to_local(const Selection& selection) const; - BoundingBoxf3 get_selection_box(); + void calc_selection_box_and_center(); #endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b10c9bb19..d4200dbb8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,17 +287,18 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { - m_bounding_box.reset(); if (wxGetApp().obj_manipul()->get_world_coordinates()) { m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } else { + m_bounding_box.reset(); const Selection::IndicesList& ids = selection.get_volume_idxs(); for (unsigned int id : ids) { const GLVolume* v = selection.get_volume(id); - m_bounding_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); + m_bounding_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); } + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } m_radius = Offset + m_bounding_box.radius(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 39ef9e6d2..11ebf093c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -147,12 +147,12 @@ void GLGizmoScale3D::on_start_dragging() m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); #if ENABLE_WORLD_COORDINATE m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; - m_starting.box = m_box; + m_starting.box = m_bounding_box; m_starting.center = m_center; - m_starting.transform = wxGetApp().obj_manipul()->get_world_coordinates() ? Transform3d::Identity() : m_transform; + m_starting.instance_center = m_instance_center; #else m_starting.drag_position = m_grabbers[m_hover_id].center; - m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_box : m_parent.get_selection().get_bounding_box(); + m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_bounding_box : m_parent.get_selection().get_bounding_box(); const Vec3d center = m_starting.box.center(); m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); @@ -188,13 +188,15 @@ void GLGizmoScale3D::on_render() glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - m_box.reset(); - m_transform = Transform3d::Identity(); + m_bounding_box.reset(); #if ENABLE_WORLD_COORDINATE m_grabbers_transform = Transform3d::Identity(); + m_center = Vec3d::Zero(); + m_instance_center = Vec3d::Zero(); bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); if (selection.is_single_full_instance() && !world_coordinates) { #else + m_transform = Transform3d::Identity(); // Transforms grabbers' offsets to world refefence system Transform3d offsets_transform = Transform3d::Identity(); m_offsets_transform = Transform3d::Identity(); @@ -205,31 +207,31 @@ void GLGizmoScale3D::on_render() // calculate bounding box in instance local reference system const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int idx : idxs) { - const GLVolume* v = selection.get_volume(idx); + const GLVolume& v = *selection.get_volume(idx); #if ENABLE_WORLD_COORDINATE - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); #else - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE - m_box = m_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); #endif // ENABLE_WORLD_COORDINATE // gets transform from first selected volume - const GLVolume* v = selection.get_volume(*idxs.begin()); + const GLVolume& v = *selection.get_volume(*idxs.begin()); #if ENABLE_WORLD_COORDINATE - m_transform = v->get_instance_transformation().get_matrix(false, false, true); - m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_box.center()); - m_center = v->get_instance_offset(); + m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); + m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); + m_instance_center = v.get_instance_offset(); #else - m_transform = v->get_instance_transformation().get_matrix(); + m_transform = v.get_instance_transformation().get_matrix(); // gets angles from first selected volume - angles = v->get_instance_rotation(); + angles = v.get_instance_rotation(); // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror()); m_offsets_transform = offsets_transform; #endif // ENABLE_WORLD_COORDINATE } @@ -238,34 +240,31 @@ void GLGizmoScale3D::on_render() #else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix(true, true, false, true))); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); + Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true) * v.get_volume_transformation().get_matrix(true, false, true)); + trafo.set_offset(v.world_matrix().translation()); + m_grabbers_transform = trafo.get_matrix(); + m_center = v.world_matrix() * m_bounding_box.center(); + m_instance_center = m_center; + } + else { + m_bounding_box = selection.get_bounding_box(); + m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center()); + m_center = m_bounding_box.center(); + m_instance_center = selection.is_single_full_instance() ? selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_offset() : m_center; + } #else - m_box = v->bounding_box(); -#endif // ENABLE_WORLD_COORDINATE -#if ENABLE_WORLD_COORDINATE - m_transform = v->world_matrix(); - m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * v->get_volume_transformation().get_matrix(false, false, true); - m_center = v->world_matrix() * m_box.center(); -#else - m_transform = v->world_matrix(); + m_bounding_box = v.bounding_box(); + m_transform = v.world_matrix(); angles = Geometry::extract_euler_angles(m_transform); // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); - m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v->get_volume_rotation(), Vec3d::Ones(), v->get_volume_mirror()); -#endif // ENABLE_WORLD_COORDINATE + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror()); + m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation(), Vec3d::Ones(), v.get_volume_mirror()); } -#if ENABLE_WORLD_COORDINATE - else { - m_box = selection.get_bounding_box(); - m_transform = Geometry::assemble_transform(m_box.center()); - m_grabbers_transform = m_transform; - m_center = selection.is_single_full_instance() ? selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_offset() : m_box.center(); - } -#else else - m_box = selection.get_bounding_box(); + m_bounding_box = selection.get_bounding_box(); Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); @@ -274,52 +273,29 @@ void GLGizmoScale3D::on_render() bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); #endif // ENABLE_WORLD_COORDINATE - // x axis #if ENABLE_WORLD_COORDINATE - const Vec3d box_half_size = 0.5 * m_box.size(); + // x axis + const Vec3d box_half_size = 0.5 * m_bounding_box.size(); bool use_constrain = wxGetKeyState(WXK_CONTROL) && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 }; m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; m_grabbers[1].center = { box_half_size.x() + Offset, 0.0, 0.0 }; m_grabbers[1].color = (use_constrain && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; -#else - const Vec3d center = m_box.center(); - - m_grabbers[0].center = m_transform * Vec3d(m_box.min.x(), center.y(), center.z()) - offset_x; - m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; - m_grabbers[1].center = m_transform * Vec3d(m_box.max.x(), center.y(), center.z()) + offset_x; - m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; -#endif // ENABLE_WORLD_COORDINATE // y axis -#if ENABLE_WORLD_COORDINATE m_grabbers[2].center = { 0.0, -(box_half_size.y() + Offset), 0.0 }; m_grabbers[2].color = (use_constrain && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; m_grabbers[3].center = { 0.0, box_half_size.y() + Offset, 0.0 }; m_grabbers[3].color = (use_constrain && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; -#else - m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y(), center.z()) - offset_y; - m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; - m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y(), center.z()) + offset_y; - m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; -#endif // ENABLE_WORLD_COORDINATE // z axis -#if ENABLE_WORLD_COORDINATE m_grabbers[4].center = { 0.0, 0.0, -(box_half_size.z() + Offset) }; m_grabbers[4].color = (use_constrain && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; m_grabbers[5].center = { 0.0, 0.0, box_half_size.z() + Offset }; m_grabbers[5].color = (use_constrain && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; -#else - m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z()) - offset_z; - m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; - m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z()) + offset_z; - m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; -#endif // ENABLE_WORLD_COORDINATE // uniform -#if ENABLE_WORLD_COORDINATE m_grabbers[6].center = { -(box_half_size.x() + Offset), -(box_half_size.y() + Offset), 0.0 }; m_grabbers[6].color = (use_constrain && m_hover_id == 8) ? CONSTRAINED_COLOR : m_highlight_color; m_grabbers[7].center = { box_half_size.x() + Offset, -(box_half_size.y() + Offset), 0.0 }; @@ -329,28 +305,47 @@ void GLGizmoScale3D::on_render() m_grabbers[9].center = { -(box_half_size.x() + Offset), box_half_size.y() + Offset, 0.0 }; m_grabbers[9].color = (use_constrain && m_hover_id == 7) ? CONSTRAINED_COLOR : m_highlight_color; #else - m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; - m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; - m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; - m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; + // x axis + const Vec3d center = m_bounding_box.center(); + + m_grabbers[0].center = m_transform * Vec3d(m_bounding_box.min.x(), center.y(), center.z()) - offset_x; + m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + m_grabbers[1].center = m_transform * Vec3d(m_bounding_box.max.x(), center.y(), center.z()) + offset_x; + m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + + // y axis + m_grabbers[2].center = m_transform * Vec3d(center.x(), m_bounding_box.min.y(), center.z()) - offset_y; + m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + m_grabbers[3].center = m_transform * Vec3d(center.x(), m_bounding_box.max.y(), center.z()) + offset_y; + m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + + // z axis + m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.min.z()) - offset_z; + m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; + m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.max.z()) + offset_z; + m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; + + // uniform + m_grabbers[6].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.min.y(), center.z()) - offset_x - offset_y; + m_grabbers[7].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.min.y(), center.z()) + offset_x - offset_y; + m_grabbers[8].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.max.y(), center.z()) + offset_x + offset_y; + m_grabbers[9].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.max.y(), center.z()) - offset_x + offset_y; for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } -#endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_WORLD_COORDINATE // sets grabbers orientation for (int i = 0; i < 10; ++i) { m_grabbers[i].angles = angles; } -#endif // !ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); transform_to_local(selection); - const float grabber_mean_size = (float)((m_box.size().x() + m_box.size().y() + m_box.size().z()) / 3.0); + float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0); #else const BoundingBoxf3& selection_box = selection.get_bounding_box(); const float grabber_mean_size = (float)((selection_box.size().x() + selection_box.size().y() + selection_box.size().z()) / 3.0); @@ -554,7 +549,7 @@ void GLGizmoScale3D::on_render_for_picking() #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); transform_to_local(m_parent.get_selection()); - render_grabbers_for_picking(m_box); + render_grabbers_for_picking(m_bounding_box); glsafe(::glPopMatrix()); #else render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); @@ -623,27 +618,33 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) Vec3d starting_scale = m_starting.scale; const Selection& selection = m_parent.get_selection(); const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - if (selection.is_single_full_instance() && world_coordinates) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); - curr_scale = (m * curr_scale).cwiseAbs(); - starting_scale = (m * starting_scale).cwiseAbs(); - } - else if ((selection.is_single_volume() || selection.is_single_modifier()) && world_coordinates) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - const Transform3d m = mi * mv; - curr_scale = (m * curr_scale).cwiseAbs(); - starting_scale = (m * starting_scale).cwiseAbs(); + if (world_coordinates) { + if (selection.is_single_full_instance()) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + curr_scale = (m * curr_scale).cwiseAbs(); + starting_scale = (m * starting_scale).cwiseAbs(); + } + else if (selection.is_single_volume() || selection.is_single_modifier()) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d m = mi * mv; + curr_scale = (m * curr_scale).cwiseAbs(); + starting_scale = (m * starting_scale).cwiseAbs(); + } } curr_scale(axis) = starting_scale(axis) * ratio; - if (selection.is_single_full_instance() && world_coordinates) - m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); - else if ((selection.is_single_volume() || selection.is_single_modifier()) && world_coordinates) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); - m_scale = (mv * mi * curr_scale).cwiseAbs(); + if (world_coordinates) { + if (selection.is_single_full_instance()) + m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); + else if (selection.is_single_volume() || selection.is_single_modifier()) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + m_scale = (mv * mi * curr_scale).cwiseAbs(); + } + else + m_scale = curr_scale; } else m_scale = curr_scale; @@ -663,7 +664,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) local_offset *= -1.0; #if ENABLE_WORLD_COORDINATE - Vec3d center_offset = m_starting.center - m_starting.transform * m_starting.box.center(); + Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && !world_coordinates) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; @@ -680,8 +681,9 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) } if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - m_offset = m * m_offset; + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); + m_offset = mv * mi * m_offset; } #else Vec3d local_offset_vec; @@ -717,23 +719,25 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) const Selection& selection = m_parent.get_selection(); const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - Vec3d center_offset = m_starting.center - m_starting.transform * m_starting.box.center(); + Vec3d center_offset = m_starting.instance_center - m_starting.center; + if (selection.is_single_full_instance() && !world_coordinates) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } - + m_offset += (ratio - 1.0) * center_offset; if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - m_offset = m * m_offset; + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); + m_offset = mv * mi * m_offset; } } else #endif // ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); -} + } } double GLGizmoScale3D::calc_ratio(const UpdateData& data) const @@ -774,8 +778,7 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const #if ENABLE_WORLD_COORDINATE void GLGizmoScale3D::transform_to_local(const Selection& selection) const { - const Vec3d center = selection.get_bounding_box().center(); - glsafe(::glTranslated(center.x(), center.y(), center.z())); + glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); if (!wxGetApp().obj_manipul()->get_world_coordinates()) { Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 16ecdbb6c..afaf6cdcd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -23,7 +23,7 @@ class GLGizmoScale3D : public GLGizmoBase Vec3d drag_position{ Vec3d::Zero() }; #if ENABLE_WORLD_COORDINATE Vec3d center{ Vec3d::Zero() }; - Transform3d transform; + Vec3d instance_center{ Vec3d::Zero() }; #endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; #if !ENABLE_WORLD_COORDINATE @@ -31,12 +31,13 @@ class GLGizmoScale3D : public GLGizmoBase #endif // !ENABLE_WORLD_COORDINATE }; - BoundingBoxf3 m_box; - Transform3d m_transform; + BoundingBoxf3 m_bounding_box; #if ENABLE_WORLD_COORDINATE Transform3d m_grabbers_transform; Vec3d m_center{ Vec3d::Zero() }; + Vec3d m_instance_center{ Vec3d::Zero() }; #else + Transform3d m_transform; // Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes) Transform3d m_offsets_transform; #endif // ENABLE_WORLD_COORDINATE From 79bdcefbde098bbfe9b1497bab22be57aefc26d8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 3 Nov 2021 14:50:30 +0100 Subject: [PATCH 046/102] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - 1st installment: introduction of instance reference system in part manipulation Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GUI_ObjectList.cpp | 4 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 169 ++++++++++++++++++++-- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 32 +++- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 12 ++ src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 8 + src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 34 ++++- src/slic3r/GUI/Plater.cpp | 6 +- src/slic3r/GUI/Selection.cpp | 14 +- src/slic3r/GUI/Selection.hpp | 3 + 10 files changed, 264 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 40abad362..92212baef 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,6 +73,8 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) +// Enable editing instance coordinates of volumes +#define ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 477c13b31..353577944 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3269,7 +3269,11 @@ void ObjectList::update_selections() return; sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } +#if ENABLE_WORLD_COORDINATE + else if (selection.is_single_volume_or_modifier()) { +#else else if (selection.is_single_volume() || selection.is_any_modifier()) { +#endif // ENABLE_WORLD_COORDINATE const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) return; diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index fdd83fe27..c0780b1fe 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -52,10 +52,17 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent) temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::World)); + temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Instance)); + temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Local)); + temp->Select((int)ObjectManipulation::ECoordinatesType::World); +#else temp->Append(_L("World coordinates")); temp->Append(_L("Local coordinates")); temp->SetSelection(0); temp->SetValue(temp->GetString(0)); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES temp->SetToolTip(_L("Select coordinate space, in which the transformation will be performed.")); return temp; @@ -81,8 +88,14 @@ void msw_rescale_word_local_combo(choice_ctrl* combo) // Set rescaled size combo->SetSize(size); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::World)); + combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Instance)); + combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Local)); +#else combo->Append(_L("World coordinates")); combo->Append(_L("Local coordinates")); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES combo->SetValue(selection); #else @@ -101,6 +114,7 @@ static void set_font_and_background_style(wxWindow* win, const wxFont& font) static const wxString axes_color_text[] = { "#990000", "#009900", "#000099" }; static const wxString axes_color_back[] = { "#f5dcdc", "#dcf5dc", "#dcdcf5" }; + ObjectManipulation::ObjectManipulation(wxWindow* parent) : OG_Settings(parent, true) { @@ -157,7 +171,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Add world local combobox m_word_local_combo = create_word_local_combo(parent); m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + this->set_coordinates_type(evt.GetString()); +#else this->set_world_coordinates(evt.GetSelection() != 1); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES }), m_word_local_combo->GetId()); // Small trick to correct layouting in different view_mode : @@ -264,7 +282,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_volume_or_modifier()) { +#else if (selection.is_single_volume() || selection.is_single_modifier()) { +#endif // ENABLE_WORLD_COORDINATE GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); } @@ -327,11 +349,15 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - if (selection.is_single_volume() || selection.is_single_modifier()) { #if ENABLE_WORLD_COORDINATE - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + if (selection.is_single_volume_or_modifier()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const double min_z = get_volume_min_z(*volume); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) { +#else if (!m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); @@ -344,6 +370,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : change_position_value(2, m_cache.position.z() - min_z); } #else + if (selection.is_single_volume() || selection.is_single_modifier()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (get_volume_min_z(*volume) * Vec3d::UnitZ()); @@ -356,7 +383,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else if (selection.is_single_full_instance()) { #if ENABLE_WORLD_COORDINATE const double min_z = selection.get_scaled_instance_bounding_box().min.z(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) { +#else if (!m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); @@ -396,7 +427,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_volume_or_modifier()) { +#else if (selection.is_single_volume() || selection.is_single_modifier()) { +#endif // ENABLE_WORLD_COORDINATE GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); volume->set_volume_rotation(Vec3d::Zero()); } @@ -477,7 +512,19 @@ void ObjectManipulation::Show(const bool show) // Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only. #if ENABLE_WORLD_COORDINATE const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); - bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); + bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_volume_or_modifier() && m_word_local_combo->GetCount() < 3) { + m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), wxNullBitmap, 1); + m_word_local_combo->Select((int)ECoordinatesType::World); + this->set_coordinates_type(m_word_local_combo->GetStringSelection()); + } + else if (selection.is_single_full_instance() && m_word_local_combo->GetCount() > 2) { + m_word_local_combo->Delete(1); + m_word_local_combo->Select((int)ECoordinatesType::World); + this->set_coordinates_type(m_word_local_combo->GetStringSelection()); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple; #endif // ENABLE_WORLD_COORDINATE @@ -558,8 +605,10 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_rotate_label_string = L("Rotation"); m_new_scale_label_string = L("Scale factors"); +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (wxGetApp().get_mode() == comSimple) m_world_coordinates = true; +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES ObjectList* obj_list = wxGetApp().obj_list(); if (selection.is_single_full_instance()) { @@ -570,14 +619,22 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // !ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. - if (m_world_coordinates && ! m_uniform_scale && +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (is_world_coordinates() && !m_uniform_scale && +#else + if (m_world_coordinates && ! m_uniform_scale && +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES ! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling. m_uniform_scale = true; m_lock_bnt->SetLock(true); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (is_world_coordinates()) { +#else if (m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #if ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); #endif // ENABLE_WORLD_COORDINATE @@ -613,11 +670,19 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_scale_label_string = L("Scale"); m_new_enabled = true; } +#if ENABLE_WORLD_COORDINATE + else if (selection.is_single_volume_or_modifier()) { +#else else if (selection.is_single_modifier() || selection.is_single_volume()) { +#endif // ENABLE_WORLD_COORDINATE // the selection contains a single volume const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (is_world_coordinates()) { +#else if (m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Geometry::Transformation trafo(volume->world_matrix()); #if ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET @@ -722,11 +787,13 @@ void ObjectManipulation::update_if_dirty() m_lock_bnt->enable(); } - { +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + { int new_selection = m_world_coordinates ? 0 : 1; if (m_word_local_combo->GetSelection() != new_selection) m_word_local_combo->SetSelection(new_selection); } +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (m_new_enabled) m_og->enable(); @@ -754,12 +821,14 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_scale = false; bool show_drop_to_bed = false; - if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); #else + if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation; Vec3d scale; #endif // ENABLE_WORLD_COORDINATE @@ -811,8 +880,16 @@ void ObjectManipulation::update_mirror_buttons_visibility() Selection& selection = canvas->get_selection(); std::array new_states = {mbHidden, mbHidden, mbHidden}; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) { +#else if (!m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { +#else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { +#endif // ENABLE_WORLD_COORDINATE const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d mirror; @@ -877,6 +954,19 @@ void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning m_fix_throught_netfab_bitmap->SetToolTip(tooltip); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) +{ + switch (type) + { + case ECoordinatesType::World: { return _L("World coordinates"); } + case ECoordinatesType::Instance: { return _L("Instance coordinates"); } + case ECoordinatesType::Local: { return _L("Local coordinates"); } + default: { assert(false); break; } + } +} +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + void ObjectManipulation::reset_settings_value() { m_new_position = Vec3d::Zero(); @@ -901,7 +991,11 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.setup_cache(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + selection.translate(position - m_cache.position, !is_world_coordinates()); +#else selection.translate(position - m_cache.position, !m_world_coordinates); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else selection.translate(position - m_cache.position, selection.requires_local_axes()); #endif // ENABLE_WORLD_COORDINATE @@ -928,7 +1022,11 @@ void ObjectManipulation::change_rotation_value(int axis, double value) if (selection.is_single_full_instance()) transformation_type.set_independent(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) { +#else if (!m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES //FIXME Selection::rotate() does not process absolute rotations correctly: It does not recognize the axis index, which was changed. // transformation_type.set_absolute(); transformation_type.set_local(); @@ -987,7 +1085,11 @@ void ObjectManipulation::change_size_value(int axis, double value) const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d ref_size = m_cache.size; +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_volume_or_modifier()) { +#else if (selection.is_single_volume() || selection.is_single_modifier()) { +#endif // ENABLE_WORLD_COORDINATE const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); const Vec3d local_size = size.cwiseQuotient(v->get_instance_scaling_factor()); const Vec3d local_ref_size = v->bounding_box().size().cwiseProduct(v->get_volume_scaling_factor()); @@ -997,7 +1099,11 @@ void ObjectManipulation::change_size_value(int axis, double value) ref_size = Vec3d::Ones(); } else if (selection.is_single_full_instance()) - ref_size = m_world_coordinates ? +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + ref_size = is_world_coordinates() ? +#else + ref_size = m_world_coordinates ? +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); @@ -1017,16 +1123,24 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) +#else if (!m_world_coordinates) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES transformation_type.set_local(); bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!uniform_scale && is_world_coordinates()) { +#else if (!uniform_scale && m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); - else if (selection.is_single_volume() || selection.is_single_modifier()) { + else if (selection.is_single_volume_or_modifier()) { const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); @@ -1086,7 +1200,11 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool new_value) { const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); - if (selection.is_single_full_instance() && m_world_coordinates && !new_value) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_full_instance() && is_world_coordinates() && !new_value) { +#else + if (selection.is_single_full_instance() && m_world_coordinates && !new_value) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); @@ -1119,6 +1237,23 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) } #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +void ObjectManipulation::set_coordinates_type(ECoordinatesType type) +{ + if (wxGetApp().get_mode() == comSimple) + type = ECoordinatesType::World; + + if (m_coordinates_type == type) + return; + + m_coordinates_type = type; + this->UpdateAndShow(true); + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + canvas->get_gizmos_manager().update_data(); + canvas->set_as_dirty(); + canvas->request_extra_frame(); +} +#else void ObjectManipulation::set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; @@ -1135,6 +1270,7 @@ bool ObjectManipulation::get_world_coordinates() const return wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()) ? m_world_coordinates : true; } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::msw_rescale() @@ -1200,6 +1336,19 @@ void ObjectManipulation::sys_color_changed() m_mirror_buttons[id].first->msw_rescale(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +void ObjectManipulation::set_coordinates_type(const wxString& type_string) +{ + ECoordinatesType type = ECoordinatesType::World; + if (type_string == coordinate_type_str(ECoordinatesType::Instance)) + type = ECoordinatesType::Instance; + else if (type_string == coordinate_type_str(ECoordinatesType::Local)) + type = ECoordinatesType::Local; + + this->set_coordinates_type(type); +} +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + static const char axes[] = { 'x', 'y', 'z' }; ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 849a443f4..d0c38cc47 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -72,6 +72,15 @@ public: static const double in_to_mm; static const double mm_to_in; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + enum class ECoordinatesType : unsigned char + { + World, + Instance, + Local + }; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + private: struct Cache { @@ -148,8 +157,12 @@ private: Vec3d m_new_size; bool m_new_enabled {true}; bool m_uniform_scale {true}; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + ECoordinatesType m_coordinates_type{ ECoordinatesType::World }; +#else // Does the object manipulation panel work in World or Local coordinates? bool m_world_coordinates = true; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES LockButton* m_lock_bnt{ nullptr }; choice_ctrl* m_word_local_combo { nullptr }; @@ -191,11 +204,20 @@ public: void set_uniform_scaling(const bool uniform_scale); bool get_uniform_scaling() const { return m_uniform_scale; } - // Does the object manipulation panel work in World or Local coordinates? #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + void set_coordinates_type(ECoordinatesType type); + ECoordinatesType get_coordinates_type() const { return m_coordinates_type; } + bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; } + bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; } + bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; } +#else + // Does the object manipulation panel work in World or Local coordinates? void set_world_coordinates(const bool world_coordinates); bool get_world_coordinates() const; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else + // Does the object manipulation panel work in World or Local coordinates? void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); } bool get_world_coordinates() const { return m_world_coordinates; } #endif // ENABLE_WORLD_COORDINATE @@ -227,6 +249,10 @@ public: ManipulationEditor* get_focused_editor() { return m_focused_editor; } #endif // ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + static wxString coordinate_type_str(ECoordinatesType type); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + private: void reset_settings_value(); void update_settings_value(const Selection& selection); @@ -242,6 +268,10 @@ private: void change_scale_value(int axis, double value); void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; + +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + void set_coordinates_type(const wxString& type_string); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES }; }} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 11be47619..64d8299f5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -93,7 +93,11 @@ void GLGizmoMove3D::on_start_dragging() if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (wxGetApp().obj_manipul()->is_world_coordinates()) +#else if (wxGetApp().obj_manipul()->get_world_coordinates()) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else { const Selection& selection = m_parent.get_selection(); @@ -455,7 +459,11 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (!wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } @@ -464,7 +472,11 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const void GLGizmoMove3D::calc_selection_box_and_center() { const Selection& selection = m_parent.get_selection(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index d4200dbb8..c74493a2f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,7 +287,11 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } @@ -307,7 +311,11 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_snap_fine_in_radius = m_radius; m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (wxGetApp().obj_manipul()->is_world_coordinates()) +#else if (wxGetApp().obj_manipul()->get_world_coordinates()) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_orient_matrix = Transform3d::Identity(); else m_orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 11ebf093c..70faad820 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -46,7 +46,11 @@ std::string GLGizmoScale3D::get_tooltip() const Vec3d scale = 100.0 * Vec3d::Ones(); if (selection.is_single_full_instance()) scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor(); +#if ENABLE_WORLD_COORDINATE + else if (selection.is_single_volume_or_modifier()) +#else else if (selection.is_single_modifier() || selection.is_single_volume()) +#endif // ENABLE_WORLD_COORDINATE scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor(); if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) @@ -193,7 +197,11 @@ void GLGizmoScale3D::on_render() m_grabbers_transform = Transform3d::Identity(); m_center = Vec3d::Zero(); m_instance_center = Vec3d::Zero(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); +#else bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && !world_coordinates) { #else m_transform = Transform3d::Identity(); @@ -236,7 +244,7 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE - else if ((selection.is_single_modifier() || selection.is_single_volume()) && !world_coordinates) { + else if (selection.is_single_volume_or_modifier() && !world_coordinates) { #else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE @@ -276,7 +284,7 @@ void GLGizmoScale3D::on_render() #if ENABLE_WORLD_COORDINATE // x axis const Vec3d box_half_size = 0.5 * m_bounding_box.size(); - bool use_constrain = wxGetKeyState(WXK_CONTROL) && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); + bool use_constrain = wxGetKeyState(WXK_CONTROL) && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 }; m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; @@ -617,14 +625,18 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) Vec3d curr_scale = m_scale; Vec3d starting_scale = m_starting.scale; const Selection& selection = m_parent.get_selection(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + const bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); +#else const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (world_coordinates) { if (selection.is_single_full_instance()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); curr_scale = (m * curr_scale).cwiseAbs(); starting_scale = (m * starting_scale).cwiseAbs(); } - else if (selection.is_single_volume() || selection.is_single_modifier()) { + else if (selection.is_single_volume_or_modifier()) { const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d m = mi * mv; @@ -638,7 +650,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (world_coordinates) { if (selection.is_single_full_instance()) m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); - else if (selection.is_single_volume() || selection.is_single_modifier()) { + else if (selection.is_single_volume_or_modifier()) { const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); m_scale = (mv * mi * curr_scale).cwiseAbs(); @@ -680,7 +692,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: { m_offset = Vec3d::Zero(); break; } } - if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { + if (selection.is_single_volume_or_modifier() && !world_coordinates) { const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); m_offset = mv * mi * m_offset; @@ -718,7 +730,11 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset.y() *= -1.0; const Selection& selection = m_parent.get_selection(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + const bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); +#else const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && !world_coordinates) { @@ -728,7 +744,7 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset += (ratio - 1.0) * center_offset; - if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { + if (selection.is_single_volume_or_modifier() && !world_coordinates) { const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); m_offset = mv * mi * m_offset; @@ -780,9 +796,13 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (!wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); - if (selection.is_single_volume() || selection.is_single_modifier()) + if (selection.is_single_volume_or_modifier()) orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 30c30d2ec..94bae6708 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4448,8 +4448,12 @@ void Plater::priv::on_right_click(RBtnEvent& evt) const bool is_some_full_instances = selection.is_single_full_instance() || selection.is_single_full_object() || selection.is_multiple_full_instance(); +#if ENABLE_WORLD_COORDINATE + const bool is_part = selection.is_single_volume_or_modifier(); +#else const bool is_part = selection.is_single_volume() || selection.is_single_modifier(); - menu = is_some_full_instances ? menus.object_menu() : +#endif // ENABLE_WORLD_COORDINATE + menu = is_some_full_instances ? menus.object_menu() : is_part ? menus.part_menu() : menus.multi_selection_menu(); } } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 8a6022c3e..286d0e87b 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -594,10 +594,14 @@ bool Selection::matches(const std::vector& volume_idxs) const bool Selection::requires_uniform_scale() const { #if ENABLE_WORLD_COORDINATE - if (is_single_modifier() || is_single_volume()) + if (is_single_volume_or_modifier()) return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); else if (is_single_full_instance()) +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + return wxGetApp().obj_manipul()->is_world_coordinates() ? +#else return wxGetApp().obj_manipul()->get_world_coordinates() ? +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; return true; @@ -972,7 +976,11 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type #endif // ENABLE_WORLD_COORDINATE } } +#if ENABLE_WORLD_COORDINATE + else if (is_single_volume_or_modifier()) +#else else if (is_single_volume() || is_single_modifier()) +#endif // ENABLE_WORLD_COORDINATE v.set_volume_scaling_factor(scale); else { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); @@ -1424,7 +1432,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else const Vec3d& center = get_bounding_box().center(); #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glTranslated(center.x(), center.y(), center.z())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 8cd6cdb6f..8cf4fa1c7 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -290,6 +290,9 @@ public: bool is_from_single_object() const; bool is_sla_compliant() const; bool is_instance_mode() const { return m_mode == Instance; } +#if ENABLE_WORLD_COORDINATE + bool is_single_volume_or_modifier() const { return is_single_volume() || is_single_modifier(); } +#endif // ENABLE_WORLD_COORDINATE bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); } // returns true if the selection contains all the given indices From fdc8a51d3ceb1d512bb16390211de34e1e81ac6e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 3 Nov 2021 15:01:24 +0100 Subject: [PATCH 047/102] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Fixed orientation for sidebar hints in 3D scene for part manipulation in instance and local systems Fixed conflicts during rebase with master --- src/slic3r/GUI/Plater.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 94bae6708..da167f7d3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1514,6 +1514,11 @@ void Sidebar::update_mode() wxWindowUpdateLocker noUpdates(this); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (m_mode == comSimple) + p->object_manipulation->set_coordinates_type(ObjectManipulation::ECoordinatesType::World); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + p->object_list->get_sizer()->Show(m_mode > comSimple); p->object_list->unselect_objects(); From 4f1df273092be5c9e4f052ef954830e6b322bf58 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 3 Nov 2021 15:37:43 +0100 Subject: [PATCH 048/102] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Modified part manipulation fields to show the proper values in dependence of the selected reference system --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c0780b1fe..66741b0c5 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -689,21 +689,33 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const Vec3d offset = trafo.get_offset() - volume->get_instance_offset(); #else const Vec3d& offset = trafo.get_offset(); - const Vec3d& rotation = trafo.get_rotation(); #endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET // const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; - m_new_rotation = Vec3d::Zero(); + m_new_rotation = trafo.get_rotation() * (180.0 / M_PI); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (is_local_coordinates()) { + m_new_position = Vec3d::Zero(); + m_new_rotation = Vec3d::Zero(); + m_new_scale = volume->get_volume_scaling_factor() * 100.0; + m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); + m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; +#else m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #if ENABLE_WORLD_COORDINATE } #endif // ENABLE_WORLD_COORDINATE From 5e5fdc4844c692014b701184ca94bad632a8e25a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 4 Nov 2021 10:07:11 +0100 Subject: [PATCH 049/102] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Volumes translation in all reference systems using Move gizmo and part manipulator fields Fixed conflicts during rebase with master --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_Geometry.cpp | 9 +++ src/slic3r/GUI/GUI_Geometry.hpp | 72 +++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 12 ++-- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 12 +--- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 31 ++++++++-- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 5 +- src/slic3r/GUI/Selection.cpp | 19 ++++++ src/slic3r/GUI/Selection.hpp | 9 +++ 10 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 src/slic3r/GUI/GUI_Geometry.cpp create mode 100644 src/slic3r/GUI/GUI_Geometry.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 78e73ba9a..02d134b29 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -85,6 +85,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_App.hpp GUI/GUI_Utils.cpp GUI/GUI_Utils.hpp + GUI/GUI_Geometry.cpp + GUI/GUI_Geometry.hpp GUI/I18N.cpp GUI/I18N.hpp GUI/MainFrame.cpp diff --git a/src/slic3r/GUI/GUI_Geometry.cpp b/src/slic3r/GUI/GUI_Geometry.cpp new file mode 100644 index 000000000..b0ed0e04f --- /dev/null +++ b/src/slic3r/GUI/GUI_Geometry.cpp @@ -0,0 +1,9 @@ +#include "libslic3r/libslic3r.h" +#include "GUI_Geometry.hpp" + +namespace Slic3r { +namespace GUI { + + +} // namespace Slic3r +} // namespace GUI diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp new file mode 100644 index 000000000..23dd36c39 --- /dev/null +++ b/src/slic3r/GUI/GUI_Geometry.hpp @@ -0,0 +1,72 @@ +#ifndef slic3r_GUI_Geometry_hpp_ +#define slic3r_GUI_Geometry_hpp_ + +namespace Slic3r { +namespace GUI { + +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +enum class ECoordinatesType : unsigned char +{ + World, + Instance, + Local +}; + +class TransformationType +{ +public: + enum Enum { + // Transforming in a world coordinate system + World = 0, + // Transforming in a local coordinate system + Local = 1, + // Absolute transformations, allowed in local coordinate system only. + Absolute = 0, + // Relative transformations, allowed in both local and world coordinate system. + Relative = 2, + // For group selection, the transformation is performed as if the group made a single solid body. + Joint = 0, + // For group selection, the transformation is performed on each object independently. + Independent = 4, + + World_Relative_Joint = World | Relative | Joint, + World_Relative_Independent = World | Relative | Independent, + Local_Absolute_Joint = Local | Absolute | Joint, + Local_Absolute_Independent = Local | Absolute | Independent, + Local_Relative_Joint = Local | Relative | Joint, + Local_Relative_Independent = Local | Relative | Independent, + }; + + TransformationType() : m_value(World) {} + TransformationType(Enum value) : m_value(value) {} + TransformationType& operator=(Enum value) { m_value = value; return *this; } + + Enum operator()() const { return m_value; } + bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } + + void set_world() { this->remove(Local); } + void set_local() { this->add(Local); } + void set_absolute() { this->remove(Relative); } + void set_relative() { this->add(Relative); } + void set_joint() { this->remove(Independent); } + void set_independent() { this->add(Independent); } + + bool world() const { return !this->has(Local); } + bool local() const { return this->has(Local); } + bool absolute() const { return !this->has(Relative); } + bool relative() const { return this->has(Relative); } + bool joint() const { return !this->has(Independent); } + bool independent() const { return this->has(Independent); } + +private: + void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); } + void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); } + + Enum m_value; +}; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + +} // namespace Slic3r +} // namespace GUI + +#endif // slic3r_GUI_Geometry_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 66741b0c5..3b644098f 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -53,10 +53,10 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent) if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::World)); - temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Instance)); - temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Local)); - temp->Select((int)ObjectManipulation::ECoordinatesType::World); + temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); + temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); + temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); + temp->Select((int)ECoordinatesType::World); #else temp->Append(_L("World coordinates")); temp->Append(_L("Local coordinates")); @@ -974,7 +974,7 @@ wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) case ECoordinatesType::World: { return _L("World coordinates"); } case ECoordinatesType::Instance: { return _L("Instance coordinates"); } case ECoordinatesType::Local: { return _L("Local coordinates"); } - default: { assert(false); break; } + default: { assert(false); return _L("Unknown"); } } } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES @@ -1004,7 +1004,7 @@ void ObjectManipulation::change_position_value(int axis, double value) selection.setup_cache(); #if ENABLE_WORLD_COORDINATE #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - selection.translate(position - m_cache.position, !is_world_coordinates()); + selection.translate(position - m_cache.position, get_coordinates_type()); #else selection.translate(position - m_cache.position, !m_world_coordinates); #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index d0c38cc47..6a074f339 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -5,6 +5,9 @@ #include "GUI_ObjectSettings.hpp" #include "GUI_ObjectList.hpp" +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#include "GUI_Geometry.hpp" +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #include "libslic3r/Point.hpp" #include @@ -72,15 +75,6 @@ public: static const double in_to_mm; static const double mm_to_in; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - enum class ECoordinatesType : unsigned char - { - World, - Instance, - Local - }; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - private: struct Cache { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 64d8299f5..532814576 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -94,13 +94,20 @@ void GLGizmoMove3D::on_start_dragging() m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (wxGetApp().obj_manipul()->is_world_coordinates()) + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + const Selection& selection = m_parent.get_selection(); + if (coordinates_type == ECoordinatesType::World) #else if (wxGetApp().obj_manipul()->get_world_coordinates()) #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { - const Selection& selection = m_parent.get_selection(); const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; } @@ -461,25 +468,39 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) + orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } #else if (!wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } void GLGizmoMove3D::calc_selection_box_and_center() { const Selection& selection = m_parent.get_selection(); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (wxGetApp().obj_manipul()->is_world_coordinates()) { + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) { #else if (wxGetApp().obj_manipul()->get_world_coordinates()) { #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); + m_center = v.world_matrix() * m_bounding_box.center(); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box.reset(); const Selection::IndicesList& ids = selection.get_volume_idxs(); @@ -490,7 +511,7 @@ void GLGizmoMove3D::calc_selection_box_and_center() m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } -} + } #endif // ENABLE_WORLD_COORDINATE } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 70faad820..905ad4d27 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -81,7 +81,7 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) Selection &selection = m_parent.get_selection(); selection.scale(get_scale(), transformation_type); - if (mouse_event.CmdDown()) selection.translate(m_offset, true); + if (mouse_event.CmdDown()) selection.translate(m_offset, ECoordinatesType::Local); } } return use_grabbers(mouse_event); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index da167f7d3..adfee1b26 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -57,6 +57,9 @@ #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#include "GUI_Geometry.hpp" +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #include "GUI_Factories.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -1516,7 +1519,7 @@ void Sidebar::update_mode() #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (m_mode == comSimple) - p->object_manipulation->set_coordinates_type(ObjectManipulation::ECoordinatesType::World); + p->object_manipulation->set_coordinates_type(ECoordinatesType::World); #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES p->object_list->get_sizer()->Show(m_mode > comSimple); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 286d0e87b..7c422ecb7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -700,7 +700,11 @@ void Selection::setup_cache() set_caches(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +void Selection::translate(const Vec3d& displacement, ECoordinatesType type) +#else void Selection::translate(const Vec3d& displacement, bool local) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES { if (!m_valid) return; @@ -710,8 +714,19 @@ void Selection::translate(const Vec3d& displacement, bool local) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (m_mode == Volume || v.is_wipe_tower) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (type == ECoordinatesType::Instance) +#else if (local) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (type == ECoordinatesType::Local) { + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Vec3d local_displacement = (volume_data.get_volume_rotation_matrix() * volume_data.get_volume_scale_matrix() * volume_data.get_volume_mirror_matrix()) * displacement; + v.set_volume_offset(volume_data.get_volume_position() + local_displacement); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { #if ENABLE_WORLD_COORDINATE const VolumeCache& volume_data = m_cache.volumes_data[i]; @@ -726,7 +741,11 @@ void Selection::translate(const Vec3d& displacement, bool local) else if (m_mode == Instance) { #if ENABLE_WORLD_COORDINATE if (is_from_fully_selected_instance(i)) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (type == ECoordinatesType::Local) { +#else if (local) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const VolumeCache& volume_data = m_cache.volumes_data[i]; const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; v.set_instance_offset(volume_data.get_instance_position() + world_displacement); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 8cf4fa1c7..53dc71e32 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -2,6 +2,9 @@ #define slic3r_GUI_Selection_hpp_ #include "libslic3r/Geometry.hpp" +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#include "slic3r/GUI/GUI_Geometry.hpp" +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #include "GLModel.hpp" #include @@ -24,6 +27,7 @@ using ModelObjectPtrs = std::vector; namespace GUI { +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES class TransformationType { public: @@ -76,6 +80,7 @@ private: Enum m_value; }; +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES class Selection { @@ -326,7 +331,11 @@ public: void setup_cache(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + void translate(const Vec3d& displacement, ECoordinatesType type = ECoordinatesType::World); +#else void translate(const Vec3d& displacement, bool local = false); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES void rotate(const Vec3d& rotation, TransformationType transformation_type); void flattening_rotate(const Vec3d& normal); void scale(const Vec3d& scale, TransformationType transformation_type); From 5767feecabc1b41b32dcc26321bcb910ed81e443 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 4 Nov 2021 10:31:24 +0100 Subject: [PATCH 050/102] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Attempt to fix build on non-Windows OSs --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 3b644098f..ad7e6d3f9 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -89,9 +89,9 @@ void msw_rescale_word_local_combo(choice_ctrl* combo) combo->SetSize(size); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::World)); - combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Instance)); - combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Local)); + combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); + combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); + combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); #else combo->Append(_L("World coordinates")); combo->Append(_L("Local coordinates")); @@ -515,14 +515,18 @@ void ObjectManipulation::Show(const bool show) bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_volume_or_modifier() && m_word_local_combo->GetCount() < 3) { +#ifdef __linux__ + m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), 1); +#else m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), wxNullBitmap, 1); +#endif // __linux__ m_word_local_combo->Select((int)ECoordinatesType::World); - this->set_coordinates_type(m_word_local_combo->GetStringSelection()); + this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection())); } else if (selection.is_single_full_instance() && m_word_local_combo->GetCount() > 2) { m_word_local_combo->Delete(1); m_word_local_combo->Select((int)ECoordinatesType::World); - this->set_coordinates_type(m_word_local_combo->GetStringSelection()); + this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection())); } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else From 65adbd5b0d3b17bc9ef15e5b7c967aff15146fe8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 4 Nov 2021 12:46:22 +0100 Subject: [PATCH 051/102] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Volumes rotation in all reference systems using Rotate gizmo and part manipulator fields Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_Geometry.hpp | 20 +++++++++++++------ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 ++++ src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 24 ++++++++++++++++++----- src/slic3r/GUI/Selection.cpp | 21 ++++++++++++++++---- 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp index 23dd36c39..029f01fb5 100644 --- a/src/slic3r/GUI/GUI_Geometry.hpp +++ b/src/slic3r/GUI/GUI_Geometry.hpp @@ -18,19 +18,25 @@ public: enum Enum { // Transforming in a world coordinate system World = 0, + // Transforming in a instance coordinate system + Instance = 1, // Transforming in a local coordinate system - Local = 1, + Local = 2, // Absolute transformations, allowed in local coordinate system only. Absolute = 0, // Relative transformations, allowed in both local and world coordinate system. - Relative = 2, + Relative = 4, // For group selection, the transformation is performed as if the group made a single solid body. Joint = 0, // For group selection, the transformation is performed on each object independently. - Independent = 4, + Independent = 8, World_Relative_Joint = World | Relative | Joint, World_Relative_Independent = World | Relative | Independent, + Instance_Absolute_Joint = Instance | Absolute | Joint, + Instance_Absolute_Independent = Instance | Absolute | Independent, + Instance_Relative_Joint = Instance | Relative | Joint, + Instance_Relative_Independent = Instance | Relative | Independent, Local_Absolute_Joint = Local | Absolute | Joint, Local_Absolute_Independent = Local | Absolute | Independent, Local_Relative_Joint = Local | Relative | Joint, @@ -44,14 +50,16 @@ public: Enum operator()() const { return m_value; } bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } - void set_world() { this->remove(Local); } - void set_local() { this->add(Local); } + void set_world() { this->remove(Instance); this->remove(Local); } + void set_instance() { this->remove(Local); this->add(Instance); } + void set_local() { this->remove(Instance); this->add(Local); } void set_absolute() { this->remove(Relative); } void set_relative() { this->add(Relative); } void set_joint() { this->remove(Independent); } void set_independent() { this->add(Independent); } - bool world() const { return !this->has(Local); } + bool world() const { return !this->has(Instance) && !this->has(Local); } + bool instance() const { return this->has(Instance); } bool local() const { return this->has(Local); } bool absolute() const { return !this->has(Relative); } bool relative() const { return this->has(Relative); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ad7e6d3f9..abb28e621 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -838,7 +838,11 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (m_coordinates_type != ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { +#else if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index c74493a2f..1c1a65869 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -288,23 +288,32 @@ void GLGizmoRotate::on_render_for_picking() void GLGizmoRotate::init_data_from_selection(const Selection& selection) { #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (wxGetApp().obj_manipul()->is_world_coordinates()) { + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) { #else if (wxGetApp().obj_manipul()->get_world_coordinates()) { #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); + m_center = v.world_matrix() * m_bounding_box.center(); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box.reset(); const Selection::IndicesList& ids = selection.get_volume_idxs(); for (unsigned int id : ids) { - const GLVolume* v = selection.get_volume(id); - m_bounding_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + const GLVolume& v = *selection.get_volume(id); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } + m_radius = Offset + m_bounding_box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; @@ -312,13 +321,18 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (wxGetApp().obj_manipul()->is_world_coordinates()) + if (coordinates_type == ECoordinatesType::World) + m_orient_matrix = Transform3d::Identity(); + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true); + } #else if (wxGetApp().obj_manipul()->get_world_coordinates()) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_orient_matrix = Transform3d::Identity(); else m_orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 7c422ecb7..a6b67b502 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -602,7 +602,7 @@ bool Selection::requires_uniform_scale() const #else return wxGetApp().obj_manipul()->get_world_coordinates() ? #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; + !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; return true; #else @@ -829,10 +829,11 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const Vec3d new_rotation = transformation_type.world() ? Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()) : transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m); - if (rot_axis_max == 2 && transformation_type.joint()) { + const Vec3d relative_instance_offset = m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center; + if (rot_axis_max == 2 && transformation_type.joint() && !relative_instance_offset.isApprox(Vec3d::Zero())) { // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); - volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * relative_instance_offset); } #else const Vec3d new_rotation = transformation_type.world() ? @@ -856,10 +857,21 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ GLVolume &v = *(*m_volumes)[i]; if (is_single_full_instance()) rotate_instance(v, i); - else if (is_single_volume() || is_single_modifier()) { #if ENABLE_WORLD_COORDINATE + else if (is_single_volume_or_modifier()) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (transformation_type.local()) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + v.set_volume_rotation(Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m)); + } + else if (transformation_type.instance()) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + v.set_volume_rotation(Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix())); + } +#else if (transformation_type.local()) v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); m = m * m_cache.volumes_data[i].get_instance_rotation_matrix(); @@ -868,6 +880,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ v.set_volume_rotation(Geometry::extract_euler_angles(m)); } #else + else if (is_single_volume() || is_single_modifier()) { if (transformation_type.independent()) v.set_volume_rotation(v.get_volume_rotation() + rotation); else { From f9fb7f947d12ed76ce00a4a8bedb6d14cd056aa0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Jun 2022 17:44:51 +0200 Subject: [PATCH 052/102] Revamped A* algorithm with extended test suite --- src/libslic3r/AStar.hpp | 169 +++++++++------- tests/libslic3r/test_astar.cpp | 352 ++++++++++++++++++++++++++++++++- 2 files changed, 450 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/AStar.hpp b/src/libslic3r/AStar.hpp index 052d0e814..b3c8b9c5f 100644 --- a/src/libslic3r/AStar.hpp +++ b/src/libslic3r/AStar.hpp @@ -1,11 +1,11 @@ #ifndef ASTAR_HPP #define ASTAR_HPP +#include + #include "libslic3r/Point.hpp" #include "libslic3r/MutablePriorityQueue.hpp" -#include - namespace Slic3r { namespace astar { // Input interface for the Astar algorithm. Specialize this struct for a @@ -34,6 +34,8 @@ template struct TracerTraits_ // Get the estimated distance heuristic from node 'n' to the destination. // This is referred to as the h value in AStar context. // If node 'n' is the goal, this function should return a negative value. + // Note that this heuristic should be admissible (never bigger than the real + // cost) in order for Astar to work. static float goal_heuristic(const T &tracer, const Node &n) { return tracer.goal_heuristic(n); @@ -81,100 +83,133 @@ size_t unique_id(const T &tracer, const TracerNodeT &n) } // namespace astar_detail +constexpr size_t UNASSIGNED = size_t(-1); + +template +struct QNode // Queue node. Keeps track of scores g, and h +{ + TracerNodeT node; // The actual node itself + size_t queue_id; // Position in the open queue or UNASSIGNED if closed + size_t parent; // unique id of the parent or UNASSIGNED + + float g, h; + float f() const { return g + h; } + + QNode(TracerNodeT n = {}, + size_t p = UNASSIGNED, + float gval = std::numeric_limits::infinity(), + float hval = 0.f) + : node{std::move(n)}, parent{p}, queue_id{UNASSIGNED}, g{gval}, h{hval} + {} +}; + // Run the AStar algorithm on a tracer implementation. // The 'tracer' argument encapsulates the domain (grid, point cloud, etc...) // The 'source' argument is the starting node. // The 'out' argument is the output iterator into which the output nodes are -// written. -// Note that no destination node is given. The tracer's goal_heuristic() method -// should return a negative value if a node is a destination node. -template -bool search_route(const Tracer &tracer, const TracerNodeT &source, It out) +// written. For performance reasons, the order is reverse, from the destination +// to the source -- (destination included, source is not). +// The 'cached_nodes' argument is an optional associative container to hold a +// QNode entry for each visited node. Any compatible container can be used +// (like std::map or maps with different allocators, even a sufficiently large +// std::vector). +// +// Note that no destination node is given in the signature. The tracer's +// goal_heuristic() method should return a negative value if a node is a +// destination node. +template>> +bool search_route(const Tracer &tracer, + const TracerNodeT &source, + It out, + NodeMap &&cached_nodes = {}) { using namespace detail; - using Node = TracerNodeT; - enum class QueueType { Open, Closed, None }; + using Node = TracerNodeT; + using QNode = QNode; - struct QNode // Queue node. Keeps track of scores g, and h - { - Node node; // The actual node itself - QueueType qtype = QueueType::None; // Which queue holds this node - - float g = 0.f, h = 0.f; - float f() const { return g + h; } - }; - - // TODO: apply a linear memory allocator - using QMap = std::unordered_map; - - // The traversed nodes are stored here encapsulated in QNodes - QMap cached_nodes; - - struct LessPred { // Comparison functor needed by MutablePriorityQueue - QMap &m; + struct LessPred { // Comparison functor needed by the priority queue + NodeMap &m; bool operator ()(size_t node_a, size_t node_b) { - auto ait = m.find(node_a); - auto bit = m.find(node_b); - assert (ait != m.end() && bit != m.end()); - - return ait->second.f() < bit->second.f(); + return m[node_a].f() < m[node_b].f(); } }; - auto qopen = - make_mutable_priority_queue([](size_t, size_t){}, - LessPred{cached_nodes}); + auto qopen = make_mutable_priority_queue( + [&cached_nodes](size_t el, size_t qidx) { + cached_nodes[el].queue_id = qidx; + }, + LessPred{cached_nodes}); - auto qclosed = - make_mutable_priority_queue([](size_t, size_t){}, - LessPred{cached_nodes}); + QNode initial{source, /*parent = */ UNASSIGNED, /*g = */0.f}; + size_t source_id = unique_id(tracer, source); + cached_nodes[source_id] = initial; + qopen.push(source_id); - QNode initial{source, QueueType::Open}; - cached_nodes.insert({unique_id(tracer, source), initial}); - qopen.push(unique_id(tracer, source)); + size_t goal_id = goal_heuristic(tracer, source) < 0.f ? source_id : + UNASSIGNED; - bool goal_reached = false; - - while (!goal_reached && !qopen.empty()) { + while (goal_id == UNASSIGNED && !qopen.empty()) { size_t q_id = qopen.top(); qopen.pop(); - QNode q = cached_nodes.at(q_id); + QNode &q = cached_nodes[q_id]; - foreach_reachable(tracer, q.node, [&](const Node &nd) { - if (goal_reached) return goal_reached; + // This should absolutely be initialized in the cache already + assert(!std::isinf(q.g)); + + foreach_reachable(tracer, q.node, [&](const Node &succ_nd) { + if (goal_id != UNASSIGNED) + return true; + + float h = goal_heuristic(tracer, succ_nd); + float dst = trace_distance(tracer, q.node, succ_nd); + size_t succ_id = unique_id(tracer, succ_nd); + QNode qsucc_nd{succ_nd, q_id, q.g + dst, h}; - float h = goal_heuristic(tracer, nd); if (h < 0.f) { - goal_reached = true; + goal_id = succ_id; + cached_nodes[succ_id] = qsucc_nd; } else { - float dst = trace_distance(tracer, q.node, nd); - QNode qnd{nd, QueueType::None, q.g + dst, h}; - size_t qnd_id = unique_id(tracer, nd); + // If succ_id is not in cache, it gets created with g = infinity + QNode &prev_nd = cached_nodes[succ_id]; - auto it = cached_nodes.find(qnd_id); + if (qsucc_nd.g < prev_nd.g) { + // new route is better, apply it: - if (it == cached_nodes.end() || - (it->second.qtype != QueueType::None && qnd.f() < it->second.f())) { - qnd.qtype = QueueType::Open; - cached_nodes.insert_or_assign(qnd_id, qnd); - qopen.push(qnd_id); + // Save the old queue id, it would be lost after the next line + size_t queue_id = prev_nd.queue_id; + + // The cache needs to be updated either way + prev_nd = qsucc_nd; + + if (queue_id == UNASSIGNED) + // was in closed or unqueued, rescheduling + qopen.push(succ_id); + else // was in open, updating + qopen.update(queue_id); } } - return goal_reached; + return goal_id != UNASSIGNED; }); - - q.qtype = QueueType::Closed; - cached_nodes.insert_or_assign(q_id, q); - qclosed.push(q_id); - - // write the output - *out = q.node; - ++out; } - return goal_reached; + // Write the output, do not reverse. Clients can do so if they need to. + if (goal_id != UNASSIGNED) { + const QNode *q = &cached_nodes[goal_id]; + while (!std::isinf(q->g) && q->parent != UNASSIGNED) { + *out = q->node; + ++out; + q = &cached_nodes[q->parent]; + } + + if (std::isinf(q->g)) // Something went wrong + goal_id = UNASSIGNED; + } + + return goal_id != UNASSIGNED; } }} // namespace Slic3r::astar diff --git a/tests/libslic3r/test_astar.cpp b/tests/libslic3r/test_astar.cpp index f673ad9fe..6c0f7ab42 100644 --- a/tests/libslic3r/test_astar.cpp +++ b/tests/libslic3r/test_astar.cpp @@ -7,12 +7,42 @@ using namespace Slic3r; -struct PointGridTracer { +TEST_CASE("Testing basic invariants of AStar", "[AStar]") { + struct DummyTracer { + using Node = int; + + int goal = 0; + + float distance(int a, int b) const { return a - b; } + + float goal_heuristic(int n) const { return n == goal ? -1.f : 0.f; } + + size_t unique_id(int n) const { return n; } + + void foreach_reachable(int, std::function) const {} + }; + + std::vector out; + + SECTION("Output is empty when source is also the destination") { + bool found = astar::search_route(DummyTracer{}, 0, std::back_inserter(out)); + REQUIRE(out.empty()); + REQUIRE(found); + } + + SECTION("Return false when there is no route to destination") { + bool found = astar::search_route(DummyTracer{}, 1, std::back_inserter(out)); + REQUIRE(!found); + REQUIRE(out.empty()); + } +} + +struct PointGridTracer3D { using Node = size_t; const PointGrid &grid; size_t final; - PointGridTracer(const PointGrid &g, size_t goal) : + PointGridTracer3D(const PointGrid &g, size_t goal) : grid{g}, final{goal} {} template @@ -49,14 +79,328 @@ struct PointGridTracer { size_t unique_id(size_t n) const { return n; } }; +template> +bool has_duplicates(const std::vector &res, Cmp cmp = {}) +{ + auto cpy = res; + std::sort(cpy.begin(), cpy.end(), cmp); + auto it = std::unique(cpy.begin(), cpy.end()); + return it != cpy.end(); +} + TEST_CASE("astar algorithm test over 3D point grid", "[AStar]") { auto vol = BoundingBox3Base{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}}; auto pgrid = point_grid(ex_seq, vol, {0.1f, 0.1f, 0.1f}); - PointGridTracer pgt{pgrid, pgrid.point_count() - 1}; + size_t target = pgrid.point_count() - 1; + + PointGridTracer3D pgt{pgrid, target}; std::vector out; - bool found = astar::search_route(pgt, size_t(0), std::back_inserter(out)); + bool found = astar::search_route(pgt, 0, std::back_inserter(out)); REQUIRE(found); + REQUIRE(!out.empty()); + REQUIRE(out.front() == target); + +#ifndef NDEBUG + std::cout << "Route taken: "; + for (auto it = out.rbegin(); it != out.rend(); ++it) { + std::cout << "(" << pgrid.get_coord(*it).transpose() << ") "; + } + std::cout << std::endl; +#endif + + REQUIRE(!has_duplicates(out)); // No duplicates in output +} + +enum CellValue {ON, OFF}; + +struct CellGridTracer2D_AllDirs { + using Node = Vec2i; + + static constexpr auto Cols = size_t(5); + static constexpr auto Rows = size_t(8); + static constexpr size_t GridSize = Cols * Rows; + + const std::array, Rows> &grid; + Vec2i goal; + + CellGridTracer2D_AllDirs(const std::array, Rows> &g, + const Vec2i &goal_) + : grid{g}, goal{goal_} + {} + + template + void foreach_reachable(const Vec2i &src, Fn &&fn) const + { + auto is_inside = [](const Vec2i& v) { return v.x() >= 0 && v.x() < Cols && v.y() >= 0 && v.y() < Rows; }; + if (Vec2i crd = src + Vec2i{0, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{1, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{0, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{-1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{-1, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{1, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{-1, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + } + + float distance(const Vec2i & a, const Vec2i & b) const { return (a - b).squaredNorm(); } + + float goal_heuristic(const Vec2i & n) const { return n == goal ? -1.f : (n - goal).squaredNorm(); } + + size_t unique_id(const Vec2i & n) const { return n.y() * Cols + n.x(); } +}; + +struct CellGridTracer2D_Axis { + using Node = Vec2i; + + static constexpr auto Cols = size_t(5); + static constexpr auto Rows = size_t(8); + static constexpr size_t GridSize = Cols * Rows; + + const std::array, Rows> &grid; + Vec2i goal; + + CellGridTracer2D_Axis( + const std::array, Rows> &g, + const Vec2i &goal_) + : grid{g}, goal{goal_} + {} + + template + void foreach_reachable(const Vec2i &src, Fn &&fn) const + { + auto is_inside = [](const Vec2i& v) { return v.x() >= 0 && v.x() < Cols && v.y() >= 0 && v.y() < Rows; }; + if (Vec2i crd = src + Vec2i{0, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{0, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{-1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + } + + float distance(const Vec2i & a, const Vec2i & b) const { return (a - b).squaredNorm(); } + + float goal_heuristic(const Vec2i &n) const + { + int manhattan_dst = std::abs(n.x() - goal.x()) + + std::abs(n.y() - goal.y()); + + return n == goal ? -1.f : manhattan_dst; + } + + size_t unique_id(const Vec2i & n) const { return n.y() * Cols + n.x(); } +}; + +using TestClasses = std::tuple< CellGridTracer2D_AllDirs, CellGridTracer2D_Axis >; + +TEMPLATE_LIST_TEST_CASE("Astar should avoid simple barrier", "[AStar]", TestClasses) { + + std::array, 8> grid = {{ + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON}, + {ON , OFF, OFF, OFF, ON}, + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON} + }}; + + Vec2i dst = {2, 0}; + TestType cgt{grid, dst}; + + std::vector out; + bool found = astar::search_route(cgt, {2, 7}, std::back_inserter(out)); + + REQUIRE(found); + REQUIRE(!out.empty()); + REQUIRE(out.front() == dst); + REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) { + return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x(); + })); + +#ifndef NDEBUG + std::cout << "Route taken: "; + for (auto it = out.rbegin(); it != out.rend(); ++it) { + std::cout << "(" << it->transpose() << ") "; + } + std::cout << std::endl; +#endif +} + +TEMPLATE_LIST_TEST_CASE("Astar should manage to avoid arbitrary barriers", "[AStar]", TestClasses) { + + std::array, 8> grid = {{ + {ON , ON , ON , ON , ON}, + {ON , ON , ON , OFF, ON}, + {OFF, OFF, ON , OFF, ON}, + {ON , ON , ON , OFF, ON}, + {ON , OFF, ON , OFF, ON}, + {ON , OFF, ON , ON , ON}, + {ON , OFF, ON , OFF, ON}, + {ON , ON , ON , ON , ON} + }}; + + Vec2i dst = {0, 0}; + TestType cgt{grid, dst}; + + std::vector out; + bool found = astar::search_route(cgt, {0, 7}, std::back_inserter(out)); + + REQUIRE(found); + REQUIRE(!out.empty()); + REQUIRE(out.front() == dst); + REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) { + return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x(); + })); + +#ifndef NDEBUG + std::cout << "Route taken: "; + for (auto it = out.rbegin(); it != out.rend(); ++it) { + std::cout << "(" << it->transpose() << ") "; + } + std::cout << std::endl; +#endif +} + +TEMPLATE_LIST_TEST_CASE("Astar should find the way out of a labyrinth", "[AStar]", TestClasses) { + + std::array, 8> grid = {{ + {ON , ON , ON , ON , ON }, + {ON , OFF, OFF, OFF, OFF}, + {ON , ON , ON , ON , ON }, + {OFF, OFF, OFF, OFF, ON }, + {ON , ON , ON , ON , ON }, + {ON , OFF, OFF, OFF, OFF}, + {ON , ON , ON , ON , ON }, + {OFF, OFF, OFF, OFF, ON } + }}; + + Vec2i dst = {4, 0}; + TestType cgt{grid, dst}; + + std::vector out; + bool found = astar::search_route(cgt, {4, 7}, std::back_inserter(out)); + + REQUIRE(found); + REQUIRE(!out.empty()); + REQUIRE(out.front() == dst); + REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) { + return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x(); + })); + +#ifndef NDEBUG + std::cout << "Route taken: "; + for (auto it = out.rbegin(); it != out.rend(); ++it) { + std::cout << "(" << it->transpose() << ") "; + } + std::cout << std::endl; +#endif +} + +TEST_CASE("Zero heuristic function should result in dijsktra's algo", "[AStar]") +{ + struct GraphTracer { + using Node = size_t; + using QNode = astar::QNode; + + struct Edge + { + size_t to_id = size_t(-1); + float cost = 0.f; + bool operator <(const Edge &e) const { return to_id < e.to_id; } + }; + + struct ENode: public QNode { + std::vector edges; + + ENode(size_t node_id, std::initializer_list edgelist) + : QNode{node_id}, edges(edgelist) + {} + + ENode &operator=(const QNode &q) + { + assert(node == q.node); + g = q.g; + h = q.h; + parent = q.parent; + queue_id = q.queue_id; + + return *this; + } + }; + + // Example graph from + // https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/?ref=lbp + std::vector nodes = { + {0, {{1, 4.f}, {7, 8.f}}}, + {1, {{0, 4.f}, {2, 8.f}, {7, 11.f}}}, + {2, {{1, 8.f}, {3, 7.f}, {5, 4.f}, {8, 2.f}}}, + {3, {{2, 7.f}, {4, 9.f}, {5, 14.f}}}, + {4, {{3, 9.f}, {5, 10.f}}}, + {5, {{2, 4.f}, {3, 14.f}, {4, 10.f}, {6, 2.f}}}, + {6, {{5, 2.f}, {7, 1.f}, {8, 6.f}}}, + {7, {{0, 8.f}, {1, 11.f}, {6, 1.f}, {8, 7.f}}}, + {8, {{2, 2.f}, {6, 6.f}, {7, 7.f}}} + }; + + float distance(size_t a, size_t b) const { + float ret = std::numeric_limits::infinity(); + if (a < nodes.size()) { + auto it = std::lower_bound(nodes[a].edges.begin(), + nodes[a].edges.end(), + Edge{b, 0.f}); + + if (it != nodes[a].edges.end()) { + ret = it->cost; + } + } + + return ret; + } + + float goal_heuristic(size_t) const { return 0.f; } + + size_t unique_id(size_t n) const { return n; } + + void foreach_reachable(size_t n, std::function fn) const + { + if (n < nodes.size()) { + for (const Edge &e : nodes[n].edges) + fn(e.to_id); + } + } + } graph; + + std::vector out; + + // 'graph.nodes' is able to be a node cache (it simulates an associative container) + bool found = astar::search_route(graph, size_t(0), std::back_inserter(out), graph.nodes); + + // But should not crash or loop infinitely. + REQUIRE(!found); + + // Without a destination, there is no output. But the algorithm should halt. + REQUIRE(out.empty()); + + // Source node should have it's parent unset + REQUIRE(graph.nodes[0].parent == astar::UNASSIGNED); + + // All other nodes should have their parents set + for (size_t i = 1; i < graph.nodes.size(); ++i) + REQUIRE(graph.nodes[i].parent != astar::UNASSIGNED); + + std::array ref_distances = {0.f, 4.f, 12.f, 19.f, 21.f, + 11.f, 9.f, 8.f, 14.f}; + + // Try to trace each node back to the source node. Each of them should + // arrive to the source within less hops than the full number of nodes. + for (size_t i = 0, k = 0; i < graph.nodes.size(); ++i, k = 0) { + GraphTracer::QNode *q = &graph.nodes[i]; + REQUIRE(q->g == Approx(ref_distances[i])); + while (k++ < graph.nodes.size() && q->parent != astar::UNASSIGNED) + q = &graph.nodes[q->parent]; + + REQUIRE(q->parent == astar::UNASSIGNED); + } } From fb31bcd0f0128be24e092582583569715934388d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 11 Nov 2021 15:25:37 +0100 Subject: [PATCH 053/102] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Volumes scaling in all reference systems using Scale gizmo and part manipulator fields Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 25 +++++++++ src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 55 +++++++++++++++++--- src/slic3r/GUI/Selection.cpp | 62 +++++++++++++++++++++-- src/slic3r/GUI/Selection.hpp | 12 +++++ 5 files changed, 144 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index abb28e621..28d5953f9 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -788,10 +788,35 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + Selection::EUniformScaleRequiredReason reason; + if (selection.requires_uniform_scale(&reason)) { +#else if (selection.requires_uniform_scale()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_lock_bnt->SetLock(true); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + wxString tooltip; + if (selection.is_single_volume_or_modifier()) { + if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) + tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the instance local axes"); + else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_World) + tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the printer axes"); + } + else if (selection.is_single_full_instance()) { + if (reason == Selection::EUniformScaleRequiredReason::InstanceNotAxisAligned_World) + tooltip = _L("You cannot use non-uniform scaling mode for instances non aligned with the printer axes"); + else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) + tooltip = _L("You cannot use non-uniform scaling mode for instances containing non locally axis-aligned parts"); + } + else + tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); + + m_lock_bnt->SetToolTip(tooltip); +#else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection or non axis-aligned objects/parts")); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 532814576..f024bcbe4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -93,9 +93,9 @@ void GLGizmoMove3D::on_start_dragging() if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE + const Selection& selection = m_parent.get_selection(); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); - const Selection& selection = m_parent.get_selection(); if (coordinates_type == ECoordinatesType::World) #else if (wxGetApp().obj_manipul()->get_world_coordinates()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 905ad4d27..2f2b4b153 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -198,11 +198,11 @@ void GLGizmoScale3D::on_render() m_center = Vec3d::Zero(); m_instance_center = Vec3d::Zero(); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); + if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { #else bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && !world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else m_transform = Transform3d::Identity(); // Transforms grabbers' offsets to world refefence system @@ -244,19 +244,39 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { +#else else if (selection.is_single_volume_or_modifier() && !world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true))); + Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true)); +#else m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true) * v.get_volume_transformation().get_matrix(true, false, true)); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); m_instance_center = m_center; } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); + Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true)); + trafo.set_offset(v.world_matrix().translation()); + m_grabbers_transform = trafo.get_matrix(); + m_center = v.world_matrix() * m_bounding_box.center(); + m_instance_center = m_center; + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box = selection.get_bounding_box(); m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center()); @@ -626,11 +646,12 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) Vec3d starting_scale = m_starting.scale; const Selection& selection = m_parent.get_selection(); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - const bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) { #else const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); curr_scale = (m * curr_scale).cwiseAbs(); @@ -647,7 +668,11 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) curr_scale(axis) = starting_scale(axis) * ratio; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (coordinates_type == ECoordinatesType::World) { +#else if (world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { @@ -677,7 +702,11 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #if ENABLE_WORLD_COORDINATE Vec3d center_offset = m_starting.instance_center - m_starting.center; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { +#else if (selection.is_single_full_instance() && !world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -692,11 +721,13 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: { m_offset = Vec3d::Zero(); break; } } +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_volume_or_modifier() && !world_coordinates) { const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); m_offset = mv * mi * m_offset; } +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else Vec3d local_offset_vec; switch (axis) @@ -730,25 +761,29 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset.y() *= -1.0; const Selection& selection = m_parent.get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - const bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); -#else +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Vec3d center_offset = m_starting.instance_center - m_starting.center; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (selection.is_single_full_instance() && !world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } m_offset += (ratio - 1.0) * center_offset; +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_volume_or_modifier() && !world_coordinates) { const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); m_offset = mv * mi * m_offset; } +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } else #endif // ENABLE_WORLD_COORDINATE @@ -802,7 +837,11 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const if (!wxGetApp().obj_manipul()->get_world_coordinates()) { #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) +#else if (selection.is_single_volume_or_modifier()) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a6b67b502..033f79869 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -591,18 +591,74 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +bool Selection::requires_uniform_scale(EUniformScaleRequiredReason* reason) const +#else bool Selection::requires_uniform_scale() const +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES { #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (is_single_volume_or_modifier()) +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + { + if (coord_type == ECoordinatesType::World) { + if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_World; + return true; + } + } + else if (coord_type == ECoordinatesType::Instance) { + if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_volume_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; + return true; + } + } + return false; + } +#else return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (is_single_full_instance()) #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - return wxGetApp().obj_manipul()->is_world_coordinates() ? + if (coord_type == ECoordinatesType::World) { + if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::InstanceNotAxisAligned_World; + return true; + } + else { + for (unsigned int i : m_list) { + if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; + return true; + } + } + } + return false; + } + else { + for (unsigned int i : m_list) { + if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; + return true; + } + } + return false; + } + + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::MultipleSelection; #else return wxGetApp().obj_manipul()->get_world_coordinates() ? + !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; return true; #else @@ -723,7 +779,7 @@ void Selection::translate(const Vec3d& displacement, bool local) #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (type == ECoordinatesType::Local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d local_displacement = (volume_data.get_volume_rotation_matrix() * volume_data.get_volume_scale_matrix() * volume_data.get_volume_mirror_matrix()) * displacement; + const Vec3d local_displacement = (volume_data.get_volume_rotation_matrix() * volume_data.get_volume_mirror_matrix()) * displacement; v.set_volume_offset(volume_data.get_volume_position() + local_displacement); } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 53dc71e32..2ac690e5e 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -307,7 +307,19 @@ public: // returns true if the selection contains all and only the given indices bool matches(const std::vector& volume_idxs) const; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + enum class EUniformScaleRequiredReason : unsigned char + { + NotRequired, + InstanceNotAxisAligned_World, + VolumeNotAxisAligned_World, + VolumeNotAxisAligned_Instance, + MultipleSelection, + }; + bool requires_uniform_scale(EUniformScaleRequiredReason* reason = nullptr) const; +#else bool requires_uniform_scale() const; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES // Returns the the object id if the selection is from a single object, otherwise is -1 int get_object_idx() const; From 6e92b4fc3b5304812e691d1df63598aed2db17b0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 15 Nov 2021 13:00:12 +0100 Subject: [PATCH 054/102] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Mirror transform in local system for volumes and a few fixes in rotation Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 ++++ src/slic3r/GUI/Selection.cpp | 6 ++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 28d5953f9..8a100f66c 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -926,7 +926,7 @@ void ObjectManipulation::update_mirror_buttons_visibility() std::array new_states = {mbHidden, mbHidden, mbHidden}; #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (!is_world_coordinates()) { + if (is_local_coordinates()) { #else if (!m_world_coordinates) { #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 1c1a65869..38a09976d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -327,6 +327,10 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true); } + else { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); + } #else if (wxGetApp().obj_manipul()->get_world_coordinates()) m_orient_matrix = Transform3d::Identity(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 033f79869..2ee0086ab 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -779,7 +779,7 @@ void Selection::translate(const Vec3d& displacement, bool local) #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (type == ECoordinatesType::Local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d local_displacement = (volume_data.get_volume_rotation_matrix() * volume_data.get_volume_mirror_matrix()) * displacement; + const Vec3d local_displacement = volume_data.get_volume_rotation_matrix() * displacement; v.set_volume_offset(volume_data.get_volume_position() + local_displacement); } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES @@ -799,11 +799,13 @@ void Selection::translate(const Vec3d& displacement, bool local) if (is_from_fully_selected_instance(i)) { #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (type == ECoordinatesType::Local) { + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement; #else if (local) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const VolumeCache& volume_data = m_cache.volumes_data[i]; const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES v.set_instance_offset(volume_data.get_instance_position() + world_displacement); } else From cd4704e493171f2dd1066044ca1073e6d078d457 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 14 Jan 2022 14:14:16 +0100 Subject: [PATCH 055/102] Fixed warnings Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 2 -- src/slic3r/GUI/Selection.cpp | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index f024bcbe4..bed9f8d11 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -153,8 +153,6 @@ void GLGizmoMove3D::on_render() glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - const Selection& selection = m_parent.get_selection(); - #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); calc_selection_box_and_center(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2ee0086ab..9ec69001f 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -623,7 +623,7 @@ bool Selection::requires_uniform_scale() const #else return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - else if (is_single_full_instance()) + else if (is_single_full_instance()) { #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (coord_type == ECoordinatesType::World) { if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation())) { @@ -652,12 +652,14 @@ bool Selection::requires_uniform_scale() const } return false; } + } if (reason != nullptr) *reason = EUniformScaleRequiredReason::MultipleSelection; #else return wxGetApp().obj_manipul()->get_world_coordinates() ? - !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; + !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; + } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES return true; From 90e54e58218826db950924a475f9d5495ff6fe06 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 14 Feb 2022 11:06:16 +0100 Subject: [PATCH 056/102] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES merged into tech ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/GUI_Geometry.hpp | 5 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 129 ++++++---------------- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 22 ++-- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 21 +--- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 13 --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 66 +---------- src/slic3r/GUI/Plater.cpp | 8 +- src/slic3r/GUI/Selection.cpp | 100 +++++------------ src/slic3r/GUI/Selection.hpp | 16 +-- 10 files changed, 85 insertions(+), 297 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 92212baef..40abad362 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,8 +73,6 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) -// Enable editing instance coordinates of volumes -#define ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp index 029f01fb5..0d6cf7f4b 100644 --- a/src/slic3r/GUI/GUI_Geometry.hpp +++ b/src/slic3r/GUI/GUI_Geometry.hpp @@ -4,7 +4,7 @@ namespace Slic3r { namespace GUI { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE enum class ECoordinatesType : unsigned char { World, @@ -72,7 +72,8 @@ private: Enum m_value; }; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + +#endif // ENABLE_WORLD_COORDINATE } // namespace Slic3r } // namespace GUI diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8a100f66c..849f4884d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -52,7 +52,7 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent) temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); @@ -62,7 +62,7 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent) temp->Append(_L("Local coordinates")); temp->SetSelection(0); temp->SetValue(temp->GetString(0)); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE temp->SetToolTip(_L("Select coordinate space, in which the transformation will be performed.")); return temp; @@ -88,14 +88,14 @@ void msw_rescale_word_local_combo(choice_ctrl* combo) // Set rescaled size combo->SetSize(size); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); #else combo->Append(_L("World coordinates")); combo->Append(_L("Local coordinates")); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE combo->SetValue(selection); #else @@ -171,12 +171,12 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Add world local combobox m_word_local_combo = create_word_local_combo(parent); m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE this->set_coordinates_type(evt.GetString()); #else this->set_world_coordinates(evt.GetSelection() != 1); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - }), m_word_local_combo->GetId()); +#endif // ENABLE_WORLD_COORDINATE + }), m_word_local_combo->GetId()); // Small trick to correct layouting in different view_mode : // Show empty string of a same height as a m_word_local_combo, when m_word_local_combo is hidden @@ -353,11 +353,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : if (selection.is_single_volume_or_modifier()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const double min_z = get_volume_min_z(*volume); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!is_world_coordinates()) { -#else - if (!m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); @@ -383,11 +379,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else if (selection.is_single_full_instance()) { #if ENABLE_WORLD_COORDINATE const double min_z = selection.get_scaled_instance_bounding_box().min.z(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!is_world_coordinates()) { -#else - if (!m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); @@ -513,7 +505,6 @@ void ObjectManipulation::Show(const bool show) #if ENABLE_WORLD_COORDINATE const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_volume_or_modifier() && m_word_local_combo->GetCount() < 3) { #ifdef __linux__ m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), 1); @@ -528,7 +519,6 @@ void ObjectManipulation::Show(const bool show) m_word_local_combo->Select((int)ECoordinatesType::World); this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection())); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple; #endif // ENABLE_WORLD_COORDINATE @@ -609,10 +599,10 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_rotate_label_string = L("Rotation"); m_new_scale_label_string = L("Scale factors"); -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if !ENABLE_WORLD_COORDINATE if (wxGetApp().get_mode() == comSimple) m_world_coordinates = true; -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // !ENABLE_WORLD_COORDINATE ObjectList* obj_list = wxGetApp().obj_list(); if (selection.is_single_full_instance()) { @@ -623,29 +613,25 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // !ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (is_world_coordinates() && !m_uniform_scale && #else if (m_world_coordinates && ! m_uniform_scale && -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE ! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling. m_uniform_scale = true; m_lock_bnt->SetLock(true); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { -#else - if (m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES -#if ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); -#endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); -#if ENABLE_WORLD_COORDINATE m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); #else + if (m_world_coordinates) { + m_new_rotate_label_string = L("Rotate"); m_new_rotation = Vec3d::Zero(); #endif // ENABLE_WORLD_COORDINATE m_new_size = selection.get_scaled_instance_bounding_box().size(); @@ -682,11 +668,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) // the selection contains a single volume const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (is_world_coordinates()) { -#else - if (m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Geometry::Transformation trafo(volume->world_matrix()); #if ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET @@ -701,27 +683,23 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (is_local_coordinates()) { m_new_position = Vec3d::Zero(); m_new_rotation = Vec3d::Zero(); m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); - m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; +#if ENABLE_WORLD_COORDINATE + m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); + m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; + } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES -#if ENABLE_WORLD_COORDINATE - } #endif // ENABLE_WORLD_COORDINATE m_new_enabled = true; } @@ -788,15 +766,14 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE Selection::EUniformScaleRequiredReason reason; if (selection.requires_uniform_scale(&reason)) { #else if (selection.requires_uniform_scale()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE m_lock_bnt->SetLock(true); #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES wxString tooltip; if (selection.is_single_volume_or_modifier()) { if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) @@ -814,9 +791,6 @@ void ObjectManipulation::update_if_dirty() tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); m_lock_bnt->SetToolTip(tooltip); -#else - m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection or non axis-aligned objects/parts")); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); #endif // ENABLE_WORLD_COORDINATE @@ -828,13 +802,13 @@ void ObjectManipulation::update_if_dirty() m_lock_bnt->enable(); } -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if !ENABLE_WORLD_COORDINATE { int new_selection = m_world_coordinates ? 0 : 1; if (m_word_local_combo->GetSelection() != new_selection) m_word_local_combo->SetSelection(new_selection); } -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // !ENABLE_WORLD_COORDINATE if (m_new_enabled) m_og->enable(); @@ -863,11 +837,7 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (m_coordinates_type != ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { -#else - if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); @@ -925,11 +895,11 @@ void ObjectManipulation::update_mirror_buttons_visibility() Selection& selection = canvas->get_selection(); std::array new_states = {mbHidden, mbHidden, mbHidden}; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (is_local_coordinates()) { #else if (!m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { #else @@ -999,7 +969,7 @@ void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning m_fix_throught_netfab_bitmap->SetToolTip(tooltip); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) { switch (type) @@ -1010,7 +980,7 @@ wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) default: { assert(false); return _L("Unknown"); } } } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::reset_settings_value() { @@ -1036,11 +1006,7 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.setup_cache(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES selection.translate(position - m_cache.position, get_coordinates_type()); -#else - selection.translate(position - m_cache.position, !m_world_coordinates); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else selection.translate(position - m_cache.position, selection.requires_local_axes()); #endif // ENABLE_WORLD_COORDINATE @@ -1067,11 +1033,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) if (selection.is_single_full_instance()) transformation_type.set_independent(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!is_world_coordinates()) { -#else - if (!m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES //FIXME Selection::rotate() does not process absolute rotations correctly: It does not recognize the axis index, which was changed. // transformation_type.set_absolute(); transformation_type.set_local(); @@ -1144,11 +1106,11 @@ void ObjectManipulation::change_size_value(int axis, double value) ref_size = Vec3d::Ones(); } else if (selection.is_single_full_instance()) -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE ref_size = is_world_coordinates() ? #else ref_size = m_world_coordinates ? -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); @@ -1168,21 +1130,13 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!is_world_coordinates()) -#else - if (!m_world_coordinates) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES transformation_type.set_local(); bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!uniform_scale && is_world_coordinates()) { -#else - if (!uniform_scale && m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { @@ -1245,11 +1199,11 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool new_value) { const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance() && is_world_coordinates() && !new_value) { #else if (selection.is_single_full_instance() && m_world_coordinates && !new_value) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); @@ -1282,7 +1236,6 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) } #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES void ObjectManipulation::set_coordinates_type(ECoordinatesType type) { if (wxGetApp().get_mode() == comSimple) @@ -1298,24 +1251,6 @@ void ObjectManipulation::set_coordinates_type(ECoordinatesType type) canvas->set_as_dirty(); canvas->request_extra_frame(); } -#else -void ObjectManipulation::set_world_coordinates(const bool world_coordinates) -{ - m_world_coordinates = world_coordinates; - this->UpdateAndShow(true); - GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); - canvas->get_gizmos_manager().update_data(); - canvas->set_as_dirty(); - canvas->request_extra_frame(); -} - -bool ObjectManipulation::get_world_coordinates() const -{ - const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); - return wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()) ? - m_world_coordinates : true; -} -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::msw_rescale() @@ -1381,7 +1316,7 @@ void ObjectManipulation::sys_color_changed() m_mirror_buttons[id].first->msw_rescale(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE void ObjectManipulation::set_coordinates_type(const wxString& type_string) { ECoordinatesType type = ECoordinatesType::World; @@ -1392,7 +1327,7 @@ void ObjectManipulation::set_coordinates_type(const wxString& type_string) this->set_coordinates_type(type); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE static const char axes[] = { 'x', 'y', 'z' }; ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 6a074f339..19d2c1808 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -5,9 +5,9 @@ #include "GUI_ObjectSettings.hpp" #include "GUI_ObjectList.hpp" -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE #include "GUI_Geometry.hpp" -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE #include "libslic3r/Point.hpp" #include @@ -151,12 +151,12 @@ private: Vec3d m_new_size; bool m_new_enabled {true}; bool m_uniform_scale {true}; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE ECoordinatesType m_coordinates_type{ ECoordinatesType::World }; #else // Does the object manipulation panel work in World or Local coordinates? bool m_world_coordinates = true; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE LockButton* m_lock_bnt{ nullptr }; choice_ctrl* m_word_local_combo { nullptr }; @@ -199,17 +199,11 @@ public: void set_uniform_scaling(const bool uniform_scale); bool get_uniform_scaling() const { return m_uniform_scale; } #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES void set_coordinates_type(ECoordinatesType type); ECoordinatesType get_coordinates_type() const { return m_coordinates_type; } bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; } bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; } bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; } -#else - // Does the object manipulation panel work in World or Local coordinates? - void set_world_coordinates(const bool world_coordinates); - bool get_world_coordinates() const; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else // Does the object manipulation panel work in World or Local coordinates? void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); } @@ -243,9 +237,9 @@ public: ManipulationEditor* get_focused_editor() { return m_focused_editor; } #endif // ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE static wxString coordinate_type_str(ECoordinatesType type); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE private: void reset_settings_value(); @@ -263,9 +257,9 @@ private: void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE void set_coordinates_type(const wxString& type_string); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE }; }} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index bed9f8d11..14c41ce54 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -94,19 +94,13 @@ void GLGizmoMove3D::on_start_dragging() m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE const Selection& selection = m_parent.get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) -#else - if (wxGetApp().obj_manipul()->get_world_coordinates()) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; @@ -464,7 +458,6 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!wxGetApp().obj_manipul()->is_world_coordinates()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); @@ -472,33 +465,21 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } -#else - if (!wxGetApp().obj_manipul()->get_world_coordinates()) { - const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); - } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } void GLGizmoMove3D::calc_selection_box_and_center() { const Selection& selection = m_parent.get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { -#else - if (wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); m_center = v.world_matrix() * m_bounding_box.center(); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box.reset(); const Selection::IndicesList& ids = selection.get_volume_idxs(); @@ -509,7 +490,7 @@ void GLGizmoMove3D::calc_selection_box_and_center() m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } - } +} #endif // ENABLE_WORLD_COORDINATE } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 38a09976d..5a0c99b76 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,22 +287,16 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { -#else - if (wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); m_center = v.world_matrix() * m_bounding_box.center(); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box.reset(); const Selection::IndicesList& ids = selection.get_volume_idxs(); @@ -320,7 +314,6 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_snap_fine_in_radius = m_radius; m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (coordinates_type == ECoordinatesType::World) m_orient_matrix = Transform3d::Identity(); else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { @@ -331,12 +324,6 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); } -#else - if (wxGetApp().obj_manipul()->get_world_coordinates()) - m_orient_matrix = Transform3d::Identity(); - else - m_orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 2f2b4b153..36e5b0217 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -197,12 +197,7 @@ void GLGizmoScale3D::on_render() m_grabbers_transform = Transform3d::Identity(); m_center = Vec3d::Zero(); m_instance_center = Vec3d::Zero(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { -#else - bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - if (selection.is_single_full_instance() && !world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else m_transform = Transform3d::Identity(); // Transforms grabbers' offsets to world refefence system @@ -233,6 +228,8 @@ void GLGizmoScale3D::on_render() m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); m_instance_center = v.get_instance_offset(); + } + else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { #else m_transform = v.get_instance_transformation().get_matrix(); @@ -241,32 +238,18 @@ void GLGizmoScale3D::on_render() // consider rotation+mirror only components of the transform for offsets offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror()); m_offsets_transform = offsets_transform; -#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { -#else - else if (selection.is_single_volume_or_modifier() && !world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES -#else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true))); Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true)); -#else - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); - Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true) * v.get_volume_transformation().get_matrix(true, false, true)); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); m_instance_center = m_center; } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); @@ -276,7 +259,6 @@ void GLGizmoScale3D::on_render() m_center = v.world_matrix() * m_bounding_box.center(); m_instance_center = m_center; } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box = selection.get_bounding_box(); m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center()); @@ -645,13 +627,8 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) Vec3d curr_scale = m_scale; Vec3d starting_scale = m_starting.scale; const Selection& selection = m_parent.get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { -#else - const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - if (world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); curr_scale = (m * curr_scale).cwiseAbs(); @@ -668,11 +645,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) curr_scale(axis) = starting_scale(axis) * ratio; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (coordinates_type == ECoordinatesType::World) { -#else - if (world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { @@ -702,11 +675,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #if ENABLE_WORLD_COORDINATE Vec3d center_offset = m_starting.instance_center - m_starting.center; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { -#else - if (selection.is_single_full_instance() && !world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -720,14 +689,6 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } default: { m_offset = Vec3d::Zero(); break; } } - -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (selection.is_single_volume_or_modifier() && !world_coordinates) { - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); - m_offset = mv * mi * m_offset; - } -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else Vec3d local_offset_vec; switch (axis) @@ -761,29 +722,14 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset.y() *= -1.0; const Selection& selection = m_parent.get_selection(); -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Vec3d center_offset = m_starting.instance_center - m_starting.center; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { -#else - if (selection.is_single_full_instance() && !world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } m_offset += (ratio - 1.0) * center_offset; - -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (selection.is_single_volume_or_modifier() && !world_coordinates) { - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); - m_offset = mv * mi * m_offset; - } -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } else #endif // ENABLE_WORLD_COORDINATE @@ -831,17 +777,9 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!wxGetApp().obj_manipul()->is_world_coordinates()) { -#else - if (!wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) -#else - if (selection.is_single_volume_or_modifier()) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index adfee1b26..cd9f221b4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -57,9 +57,9 @@ #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE #include "GUI_Geometry.hpp" -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE #include "GUI_Factories.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -1517,10 +1517,10 @@ void Sidebar::update_mode() wxWindowUpdateLocker noUpdates(this); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (m_mode == comSimple) p->object_manipulation->set_coordinates_type(ECoordinatesType::World); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE p->object_list->get_sizer()->Show(m_mode > comSimple); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 9ec69001f..87d651aa1 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -591,19 +591,15 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE bool Selection::requires_uniform_scale(EUniformScaleRequiredReason* reason) const #else bool Selection::requires_uniform_scale() const -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE { #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (is_single_volume_or_modifier()) -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - { + if (is_single_volume_or_modifier()) { if (coord_type == ECoordinatesType::World) { if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation())) { if (reason != nullptr) @@ -620,11 +616,7 @@ bool Selection::requires_uniform_scale() const } return false; } -#else - return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (is_single_full_instance()) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (coord_type == ECoordinatesType::World) { if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation())) { if (reason != nullptr) @@ -656,11 +648,6 @@ bool Selection::requires_uniform_scale() const if (reason != nullptr) *reason = EUniformScaleRequiredReason::MultipleSelection; -#else - return wxGetApp().obj_manipul()->get_world_coordinates() ? - !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; - } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES return true; #else @@ -758,11 +745,11 @@ void Selection::setup_cache() set_caches(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE void Selection::translate(const Vec3d& displacement, ECoordinatesType type) #else void Selection::translate(const Vec3d& displacement, bool local) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE { if (!m_valid) return; @@ -772,19 +759,19 @@ void Selection::translate(const Vec3d& displacement, bool local) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (m_mode == Volume || v.is_wipe_tower) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (type == ECoordinatesType::Instance) #else if (local) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE else if (type == ECoordinatesType::Local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; const Vec3d local_displacement = volume_data.get_volume_rotation_matrix() * displacement; v.set_volume_offset(volume_data.get_volume_position() + local_displacement); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE else { #if ENABLE_WORLD_COORDINATE const VolumeCache& volume_data = m_cache.volumes_data[i]; @@ -799,15 +786,9 @@ void Selection::translate(const Vec3d& displacement, bool local) else if (m_mode == Instance) { #if ENABLE_WORLD_COORDINATE if (is_from_fully_selected_instance(i)) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (type == ECoordinatesType::Local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement; -#else - if (local) { - const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES v.set_instance_offset(volume_data.get_instance_position() + world_displacement); } else @@ -919,7 +900,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ rotate_instance(v, i); #if ENABLE_WORLD_COORDINATE else if (is_single_volume_or_modifier()) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (transformation_type.local()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); v.set_volume_rotation(Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m)); @@ -928,10 +908,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); v.set_volume_rotation(Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix())); } -#else - if (transformation_type.local()) - v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); m = m * m_cache.volumes_data[i].get_instance_rotation_matrix(); @@ -1524,11 +1500,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else const Vec3d& center = get_bounding_box().center(); #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { -#else - if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glTranslated(center.x(), center.y(), center.z())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES @@ -1749,8 +1721,7 @@ std::vector Selection::get_unselected_volume_idxs_from(const std:: { std::vector idxs; - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (m_list.find(i) == m_list.end()) idxs.push_back(i); } @@ -1768,8 +1739,7 @@ void Selection::update_type() m_cache.content.clear(); m_type = Mixed; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { const GLVolume* volume = (*m_volumes)[i]; int obj_idx = volume->object_idx(); int inst_idx = volume->instance_idx(); @@ -1784,27 +1754,22 @@ void Selection::update_type() if (!m_valid) m_type = Invalid; - else - { + else { if (m_list.empty()) m_type = Empty; - else if (m_list.size() == 1) - { + else if (m_list.size() == 1) { const GLVolume* first = (*m_volumes)[*m_list.begin()]; if (first->is_wipe_tower) m_type = WipeTower; - else if (first->is_modifier) - { + else if (first->is_modifier) { m_type = SingleModifier; requires_disable = true; } - else - { + else { const ModelObject* model_object = m_model->objects[first->object_idx()]; unsigned int volumes_count = (unsigned int)model_object->volumes.size(); unsigned int instances_count = (unsigned int)model_object->instances.size(); - if (volumes_count * instances_count == 1) - { + if (volumes_count * instances_count == 1) { m_type = SingleFullObject; // ensures the correct mode is selected m_mode = Instance; @@ -1815,15 +1780,13 @@ void Selection::update_type() // ensures the correct mode is selected m_mode = Instance; } - else - { + else { m_type = SingleVolume; requires_disable = true; } } } - else - { + else { unsigned int sla_volumes_count = 0; // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is for (unsigned int i : m_list) { @@ -1838,25 +1801,20 @@ void Selection::update_type() unsigned int instances_count = (unsigned int)model_object->instances.size(); unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); - if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) - { + if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullObject; // ensures the correct mode is selected m_mode = Instance; } - else if (selected_instances_count == 1) - { - if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) - { + else if (selected_instances_count == 1) { + if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullInstance; // ensures the correct mode is selected m_mode = Instance; } - else - { + else { unsigned int modifiers_count = 0; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if ((*m_volumes)[i]->is_modifier) ++modifiers_count; } @@ -1869,25 +1827,21 @@ void Selection::update_type() requires_disable = true; } } - else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())) - { + else if (selected_instances_count > 1 && selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = MultipleFullInstance; // ensures the correct mode is selected m_mode = Instance; } } - else - { + else { unsigned int sels_cntr = 0; - for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) - { + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) { const ModelObject* model_object = m_model->objects[it->first]; unsigned int volumes_count = (unsigned int)model_object->volumes.size(); unsigned int instances_count = (unsigned int)model_object->instances.size(); sels_cntr += volumes_count * instances_count; } - if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) - { + if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) { m_type = MultipleFullObject; // ensures the correct mode is selected m_mode = Instance; diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 2ac690e5e..9d036d3c6 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -2,9 +2,9 @@ #define slic3r_GUI_Selection_hpp_ #include "libslic3r/Geometry.hpp" -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE #include "slic3r/GUI/GUI_Geometry.hpp" -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE #include "GLModel.hpp" #include @@ -27,7 +27,7 @@ using ModelObjectPtrs = std::vector; namespace GUI { -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if !ENABLE_WORLD_COORDINATE class TransformationType { public: @@ -80,7 +80,7 @@ private: Enum m_value; }; -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // !ENABLE_WORLD_COORDINATE class Selection { @@ -307,7 +307,7 @@ public: // returns true if the selection contains all and only the given indices bool matches(const std::vector& volume_idxs) const; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE enum class EUniformScaleRequiredReason : unsigned char { NotRequired, @@ -319,7 +319,7 @@ public: bool requires_uniform_scale(EUniformScaleRequiredReason* reason = nullptr) const; #else bool requires_uniform_scale() const; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE // Returns the the object id if the selection is from a single object, otherwise is -1 int get_object_idx() const; @@ -343,11 +343,11 @@ public: void setup_cache(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE void translate(const Vec3d& displacement, ECoordinatesType type = ECoordinatesType::World); #else void translate(const Vec3d& displacement, bool local = false); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE void rotate(const Vec3d& rotation, TransformationType transformation_type); void flattening_rotate(const Vec3d& normal); void scale(const Vec3d& scale, TransformationType transformation_type); From c29bb039a6e11a43dbe793e52dcb13b70acf3a64 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 14 Feb 2022 12:06:11 +0100 Subject: [PATCH 057/102] Tech ENABLE_WORLD_COORDINATE - Revisited rotation of single instance --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 24 +++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 849f4884d..935264d69 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -420,17 +420,14 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : Selection& selection = canvas->get_selection(); #if ENABLE_WORLD_COORDINATE - if (selection.is_single_volume_or_modifier()) { + if (selection.is_single_volume_or_modifier()) #else - if (selection.is_single_volume() || selection.is_single_modifier()) { + if (selection.is_single_volume() || selection.is_single_modifier()) #endif // ENABLE_WORLD_COORDINATE - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); - volume->set_volume_rotation(Vec3d::Zero()); - } + const_cast(selection.get_volume(*selection.get_volume_idxs().begin()))->set_volume_rotation(Vec3d::Zero()); else if (selection.is_single_full_instance()) { for (unsigned int idx : selection.get_volume_idxs()) { - GLVolume* volume = const_cast(selection.get_volume(idx)); - volume->set_instance_rotation(Vec3d::Zero()); + const_cast(selection.get_volume(idx))->set_instance_rotation(Vec3d::Zero()); } } else @@ -628,22 +625,20 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (is_world_coordinates()) { m_new_position = volume->get_instance_offset(); m_new_rotate_label_string = L("Rotate"); - m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); #else if (m_world_coordinates) { m_new_rotate_label_string = L("Rotate"); - m_new_rotation = Vec3d::Zero(); #endif // ENABLE_WORLD_COORDINATE + m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; } else { #if ENABLE_WORLD_COORDINATE + m_new_move_label_string = L("Translate"); m_new_position = Vec3d::Zero(); - m_new_rotation = Vec3d::Zero(); -#else - m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); #endif // ENABLE_WORLD_COORDINATE + m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); m_new_scale = volume->get_instance_scaling_factor() * 100.0; } @@ -837,7 +832,7 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE - if (m_coordinates_type != ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { + if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); @@ -1034,9 +1029,8 @@ void ObjectManipulation::change_rotation_value(int axis, double value) transformation_type.set_independent(); if (!is_world_coordinates()) { - //FIXME Selection::rotate() does not process absolute rotations correctly: It does not recognize the axis index, which was changed. - // transformation_type.set_absolute(); transformation_type.set_local(); + transformation_type.set_absolute(); } #else if (selection.is_single_full_instance() || selection.requires_local_axes()) From d9ed45be56e2ef5b9836817e98ef7c074e0568b9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 3 Jun 2022 10:08:11 +0200 Subject: [PATCH 058/102] Apply remarks from code review with additional cosmethics --- src/libslic3r/AStar.hpp | 40 +++++++++++++++++----------------- tests/libslic3r/test_astar.cpp | 8 +++---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/AStar.hpp b/src/libslic3r/AStar.hpp index b3c8b9c5f..4a652a107 100644 --- a/src/libslic3r/AStar.hpp +++ b/src/libslic3r/AStar.hpp @@ -1,6 +1,7 @@ #ifndef ASTAR_HPP #define ASTAR_HPP +#include // std::isinf() is here #include #include "libslic3r/Point.hpp" @@ -83,23 +84,23 @@ size_t unique_id(const T &tracer, const TracerNodeT &n) } // namespace astar_detail -constexpr size_t UNASSIGNED = size_t(-1); +constexpr size_t Unassigned = size_t(-1); template struct QNode // Queue node. Keeps track of scores g, and h { - TracerNodeT node; // The actual node itself - size_t queue_id; // Position in the open queue or UNASSIGNED if closed - size_t parent; // unique id of the parent or UNASSIGNED + TracerNodeT node; // The actual node itself + size_t queue_id; // Position in the open queue or Unassigned if closed + size_t parent; // unique id of the parent or Unassigned - float g, h; + float g, h; float f() const { return g + h; } - QNode(TracerNodeT n = {}, - size_t p = UNASSIGNED, + QNode(TracerNodeT n = {}, + size_t p = Unassigned, float gval = std::numeric_limits::infinity(), float hval = 0.f) - : node{std::move(n)}, parent{p}, queue_id{UNASSIGNED}, g{gval}, h{hval} + : node{std::move(n)}, parent{p}, queue_id{Unassigned}, g{gval}, h{hval} {} }; @@ -143,15 +144,15 @@ bool search_route(const Tracer &tracer, }, LessPred{cached_nodes}); - QNode initial{source, /*parent = */ UNASSIGNED, /*g = */0.f}; + QNode initial{source, /*parent = */ Unassigned, /*g = */0.f}; size_t source_id = unique_id(tracer, source); cached_nodes[source_id] = initial; qopen.push(source_id); size_t goal_id = goal_heuristic(tracer, source) < 0.f ? source_id : - UNASSIGNED; + Unassigned; - while (goal_id == UNASSIGNED && !qopen.empty()) { + while (goal_id == Unassigned && !qopen.empty()) { size_t q_id = qopen.top(); qopen.pop(); QNode &q = cached_nodes[q_id]; @@ -160,7 +161,7 @@ bool search_route(const Tracer &tracer, assert(!std::isinf(q.g)); foreach_reachable(tracer, q.node, [&](const Node &succ_nd) { - if (goal_id != UNASSIGNED) + if (goal_id != Unassigned) return true; float h = goal_heuristic(tracer, succ_nd); @@ -184,7 +185,7 @@ bool search_route(const Tracer &tracer, // The cache needs to be updated either way prev_nd = qsucc_nd; - if (queue_id == UNASSIGNED) + if (queue_id == decltype(qopen)::invalid_id()) // was in closed or unqueued, rescheduling qopen.push(succ_id); else // was in open, updating @@ -192,24 +193,23 @@ bool search_route(const Tracer &tracer, } } - return goal_id != UNASSIGNED; + return goal_id != Unassigned; }); } // Write the output, do not reverse. Clients can do so if they need to. - if (goal_id != UNASSIGNED) { + if (goal_id != Unassigned) { const QNode *q = &cached_nodes[goal_id]; - while (!std::isinf(q->g) && q->parent != UNASSIGNED) { + while (q->parent != Unassigned) { + assert(!std::isinf(q->g)); // Uninitialized nodes are NOT allowed + *out = q->node; ++out; q = &cached_nodes[q->parent]; } - - if (std::isinf(q->g)) // Something went wrong - goal_id = UNASSIGNED; } - return goal_id != UNASSIGNED; + return goal_id != Unassigned; } }} // namespace Slic3r::astar diff --git a/tests/libslic3r/test_astar.cpp b/tests/libslic3r/test_astar.cpp index 6c0f7ab42..867f5be47 100644 --- a/tests/libslic3r/test_astar.cpp +++ b/tests/libslic3r/test_astar.cpp @@ -384,11 +384,11 @@ TEST_CASE("Zero heuristic function should result in dijsktra's algo", "[AStar]") REQUIRE(out.empty()); // Source node should have it's parent unset - REQUIRE(graph.nodes[0].parent == astar::UNASSIGNED); + REQUIRE(graph.nodes[0].parent == astar::Unassigned); // All other nodes should have their parents set for (size_t i = 1; i < graph.nodes.size(); ++i) - REQUIRE(graph.nodes[i].parent != astar::UNASSIGNED); + REQUIRE(graph.nodes[i].parent != astar::Unassigned); std::array ref_distances = {0.f, 4.f, 12.f, 19.f, 21.f, 11.f, 9.f, 8.f, 14.f}; @@ -398,9 +398,9 @@ TEST_CASE("Zero heuristic function should result in dijsktra's algo", "[AStar]") for (size_t i = 0, k = 0; i < graph.nodes.size(); ++i, k = 0) { GraphTracer::QNode *q = &graph.nodes[i]; REQUIRE(q->g == Approx(ref_distances[i])); - while (k++ < graph.nodes.size() && q->parent != astar::UNASSIGNED) + while (k++ < graph.nodes.size() && q->parent != astar::Unassigned) q = &graph.nodes[q->parent]; - REQUIRE(q->parent == astar::UNASSIGNED); + REQUIRE(q->parent == astar::Unassigned); } } From 1d3c8ac5b4eec299786e3b4bb87ff03593d32d69 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 3 Jun 2022 10:17:05 +0200 Subject: [PATCH 059/102] Further refactor Reduce code size for astar --- src/libslic3r/AStar.hpp | 53 +++++++++-------------------------------- 1 file changed, 11 insertions(+), 42 deletions(-) diff --git a/src/libslic3r/AStar.hpp b/src/libslic3r/AStar.hpp index 4a652a107..61c82157b 100644 --- a/src/libslic3r/AStar.hpp +++ b/src/libslic3r/AStar.hpp @@ -53,37 +53,6 @@ template struct TracerTraits_ template using TracerNodeT = typename TracerTraits_>::Node; -namespace detail { -// Helper functions dispatching calls through the TracerTraits_ interface - -template using TracerTraits = TracerTraits_>; - -template -void foreach_reachable(const T &tracer, const TracerNodeT &from, Fn &&fn) -{ - TracerTraits::foreach_reachable(tracer, from, fn); -} - -template -float trace_distance(const T &tracer, const TracerNodeT &a, const TracerNodeT &b) -{ - return TracerTraits::distance(tracer, a, b); -} - -template -float goal_heuristic(const T &tracer, const TracerNodeT &n) -{ - return TracerTraits::goal_heuristic(tracer, n); -} - -template -size_t unique_id(const T &tracer, const TracerNodeT &n) -{ - return TracerTraits::unique_id(tracer, n); -} - -} // namespace astar_detail - constexpr size_t Unassigned = size_t(-1); template @@ -126,10 +95,9 @@ bool search_route(const Tracer &tracer, It out, NodeMap &&cached_nodes = {}) { - using namespace detail; - - using Node = TracerNodeT; - using QNode = QNode; + using Node = TracerNodeT; + using QNode = QNode; + using TracerTraits = TracerTraits_>; struct LessPred { // Comparison functor needed by the priority queue NodeMap &m; @@ -145,12 +113,13 @@ bool search_route(const Tracer &tracer, LessPred{cached_nodes}); QNode initial{source, /*parent = */ Unassigned, /*g = */0.f}; - size_t source_id = unique_id(tracer, source); + size_t source_id = TracerTraits::unique_id(tracer, source); cached_nodes[source_id] = initial; qopen.push(source_id); - size_t goal_id = goal_heuristic(tracer, source) < 0.f ? source_id : - Unassigned; + size_t goal_id = TracerTraits::goal_heuristic(tracer, source) < 0.f ? + source_id : + Unassigned; while (goal_id == Unassigned && !qopen.empty()) { size_t q_id = qopen.top(); @@ -160,13 +129,13 @@ bool search_route(const Tracer &tracer, // This should absolutely be initialized in the cache already assert(!std::isinf(q.g)); - foreach_reachable(tracer, q.node, [&](const Node &succ_nd) { + TracerTraits::foreach_reachable(tracer, q.node, [&](const Node &succ_nd) { if (goal_id != Unassigned) return true; - float h = goal_heuristic(tracer, succ_nd); - float dst = trace_distance(tracer, q.node, succ_nd); - size_t succ_id = unique_id(tracer, succ_nd); + float h = TracerTraits::goal_heuristic(tracer, succ_nd); + float dst = TracerTraits::distance(tracer, q.node, succ_nd); + size_t succ_id = TracerTraits::unique_id(tracer, succ_nd); QNode qsucc_nd{succ_nd, q_id, q.g + dst, h}; if (h < 0.f) { From 9706f16e69b0f9cfb97862520fb69593e9cf8b3e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 14 Feb 2022 12:48:51 +0100 Subject: [PATCH 060/102] Tech ENABLE_WORLD_COORDINATE - Revisited rotation of single volume Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 16 ++++++++-------- src/slic3r/GUI/Selection.cpp | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 935264d69..dd7256d2a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -563,8 +563,7 @@ void ObjectManipulation::update_ui_from_settings() } m_check_inch->SetValue(m_imperial_units); - if (m_use_colors != (wxGetApp().app_config->get("color_mapinulation_panel") == "1")) - { + if (m_use_colors != (wxGetApp().app_config->get("color_mapinulation_panel") == "1")) { m_use_colors = wxGetApp().app_config->get("color_mapinulation_panel") == "1"; // update colors for edit-boxes int axis_id = 0; @@ -624,11 +623,10 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { m_new_position = volume->get_instance_offset(); - m_new_rotate_label_string = L("Rotate"); #else if (m_world_coordinates) { - m_new_rotate_label_string = L("Rotate"); #endif // ENABLE_WORLD_COORDINATE + m_new_rotate_label_string = L("Rotate"); m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; @@ -671,23 +669,25 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #else const Vec3d& offset = trafo.get_offset(); #endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET -// const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; - m_new_rotation = trafo.get_rotation() * (180.0 / M_PI); + m_new_rotate_label_string = L("Rotate"); + m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; } else if (is_local_coordinates()) { + m_new_move_label_string = L("Translate"); m_new_position = Vec3d::Zero(); - m_new_rotation = Vec3d::Zero(); + m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()); } else { #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); - m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); + m_new_rotate_label_string = L("Rotate"); + m_new_rotation = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 87d651aa1..4931448ba 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -902,7 +902,8 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ else if (is_single_volume_or_modifier()) { if (transformation_type.local()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - v.set_volume_rotation(Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m)); + const Vec3d new_rotation = transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m); + v.set_volume_rotation(new_rotation); } else if (transformation_type.instance()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); From 912d7814464a331f51c59f6d9b9d03a7ab2be3d3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 15 Feb 2022 09:10:41 +0100 Subject: [PATCH 061/102] Tech ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX - Render the selection bounding box in the current reference system Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/Selection.cpp | 63 +++++++++++++++++++++++++++++++++- src/slic3r/GUI/Selection.hpp | 4 +++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 40abad362..303243966 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,6 +73,8 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) +// Enable rendering the selection bounding box in the current reference system +#define ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 4931448ba..cc37ebaa7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -919,7 +919,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ #else else if (is_single_volume() || is_single_modifier()) { if (transformation_type.independent()) - v.set_volume_rotation(v.get_volume_rotation() + rotation); + v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); else { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); @@ -1409,7 +1409,33 @@ void Selection::render(float scale_factor) m_scale_factor = scale_factor; // render cumulative bounding box of selected volumes #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + BoundingBoxf3 box; + Transform3d trafo; + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) { + box = get_bounding_box(); + trafo = Transform3d::Identity(); + } + else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { + const GLVolume& v = *get_volume(*get_volume_idxs().begin()); + box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); + trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); + } + else { + const Selection::IndicesList& ids = get_volume_idxs(); + for (unsigned int id : ids) { + const GLVolume& v = *get_volume(id); + box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); + } + box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); + trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false); + } + + render_bounding_box(box, trafo, ColorRGB::WHITE()); +#else render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else render_selected_volumes(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -2020,6 +2046,12 @@ void Selection::render_synchronized_volumes() float color[3] = { 1.0f, 1.0f, 0.0f }; #endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + BoundingBoxf3 box; + Transform3d trafo; +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + for (unsigned int i : m_list) { const GLVolume& volume = *(*m_volumes)[i]; int object_idx = volume.object_idx(); @@ -2033,7 +2065,23 @@ void Selection::render_synchronized_volumes() continue; #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + if (coordinates_type == ECoordinatesType::World) { + box = v.transformed_convex_hull_bounding_box(); + trafo = Transform3d::Identity(); + } + else if (coordinates_type == ECoordinatesType::Local) { + box = v.bounding_box(); + trafo = v.world_matrix(); + } + else { + box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()); + trafo = v.get_instance_transformation().get_matrix(); + } + render_bounding_box(box, trafo, ColorRGB::YELLOW()); +#else render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW()); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else render_bounding_box(v.transformed_convex_hull_bounding_box(), color); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -2042,7 +2090,11 @@ void Selection::render_synchronized_volumes() } #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +void Selection::render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color) +#else void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color) +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX { #else void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const @@ -2146,6 +2198,11 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con if (shader == nullptr) return; +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo.data())); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); @@ -2155,6 +2212,10 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con m_box.set_color(to_rgba(color)); m_box.render(); shader->stop_using(); + +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + glsafe(::glPopMatrix()); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else ::glBegin(GL_LINES); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 9d036d3c6..4030a0585 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -395,7 +395,11 @@ private: void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } void render_synchronized_volumes(); #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + void render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color); +#else void render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else void render_selected_volumes() const; void render_bounding_box(const BoundingBoxf3& box, float* color) const; From 558bccec48ab18797b347aaa334e01214d11db17 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 16 Feb 2022 12:36:41 +0100 Subject: [PATCH 062/102] Tech ENABLE_WORLD_COORDINATE_SHOW_AXES - Show axes of the current reference system when sidebar hints are active for non-world reference systems Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/3DBed.cpp | 10 +++++ src/slic3r/GUI/3DBed.hpp | 10 +++++ src/slic3r/GUI/CoordAxes.cpp | 79 ++++++++++++++++++++++++++++++++++ src/slic3r/GUI/CoordAxes.hpp | 59 +++++++++++++++++++++++++ src/slic3r/GUI/Selection.cpp | 6 +++ src/slic3r/GUI/Selection.hpp | 13 +++++- 8 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 src/slic3r/GUI/CoordAxes.cpp create mode 100644 src/slic3r/GUI/CoordAxes.hpp diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 303243966..ada35b5ec 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -75,6 +75,8 @@ #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) // Enable rendering the selection bounding box in the current reference system #define ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX (1 && ENABLE_WORLD_COORDINATE) +// Enable showing the axes of the current reference system when sidebar hints are active +#define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 02d134b29..29b8b7e73 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -131,6 +131,8 @@ set(SLIC3R_GUI_SOURCES GUI/2DBed.hpp GUI/3DBed.cpp GUI/3DBed.hpp + GUI/CoordAxes.cpp + GUI/CoordAxes.hpp GUI/Camera.cpp GUI/Camera.hpp GUI/wxExtensions.cpp diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 1ce1af741..132245b4d 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -102,6 +102,7 @@ const float* GeometryBuffer::get_vertices_data() const } #endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#if !ENABLE_WORLD_COORDINATE_SHOW_AXES const float Bed3D::Axes::DefaultStemRadius = 0.5f; const float Bed3D::Axes::DefaultStemLength = 25.0f; const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius; @@ -179,6 +180,7 @@ void Bed3D::Axes::render() glsafe(::glDisable(GL_DEPTH_TEST)); } +#endif // !ENABLE_WORLD_COORDINATE_SHOW_AXES bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { @@ -341,7 +343,11 @@ BoundingBoxf3 Bed3D::calc_extended_bounding_box() const out.max.z() = 0.0; // extend to contain axes out.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + out.merge(out.min + Vec3d(-m_axes.get_tip_radius(), -m_axes.get_tip_radius(), out.max.z())); +#else out.merge(out.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, out.max.z())); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES // extend to contain model, if any BoundingBoxf3 model_bb = m_model.get_bounding_box(); if (model_bb.defined) { @@ -539,7 +545,11 @@ std::tuple Bed3D::detect_type(const Point void Bed3D::render_axes() { if (m_build_volume.valid()) +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + m_axes.render(0.25f); +#else m_axes.render(); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } #if ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 708d186a4..61f01f021 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,7 +3,11 @@ #include "GLTexture.hpp" #include "3DScene.hpp" +#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#include "CoordAxes.hpp" +#else #include "GLModel.hpp" +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #include "libslic3r/BuildVolume.hpp" #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -44,6 +48,7 @@ public: class Bed3D { +#if !ENABLE_WORLD_COORDINATE_SHOW_AXES class Axes { public: @@ -67,6 +72,7 @@ class Bed3D float get_total_length() const { return m_stem_length + DefaultTipLength; } void render(); }; +#endif // !ENABLE_WORLD_COORDINATE_SHOW_AXES public: enum class Type : unsigned char @@ -107,7 +113,11 @@ private: #if !ENABLE_LEGACY_OPENGL_REMOVAL unsigned int m_vbo_id{ 0 }; #endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + CoordAxes m_axes; +#else Axes m_axes; +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES float m_scale_factor{ 1.0f }; diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp new file mode 100644 index 000000000..c3cced2f9 --- /dev/null +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -0,0 +1,79 @@ +#include "libslic3r/libslic3r.h" + +#include "CoordAxes.hpp" +#include "GUI_App.hpp" +#include "3DScene.hpp" + +#include + +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + +namespace Slic3r { +namespace GUI { + +const float CoordAxes::DefaultStemRadius = 0.5f; +const float CoordAxes::DefaultStemLength = 25.0f; +const float CoordAxes::DefaultTipRadius = 2.5f * CoordAxes::DefaultStemRadius; +const float CoordAxes::DefaultTipLength = 5.0f; + +void CoordAxes::render(float emission_factor) +{ + auto render_axis = [this](const Transform3f& transform) { + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixf(transform.data())); + m_arrow.render(); + glsafe(::glPopMatrix()); + }; + + if (!m_arrow.is_initialized()) + m_arrow.init_from(stilized_arrow(16, m_tip_radius, m_tip_length, m_stem_radius, m_stem_length)); + + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light"); + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + if (shader_differs) { + if (curr_shader != nullptr) + curr_shader->stop_using(); + shader->start_using(); + } + shader->set_uniform("emission_factor", emission_factor); + + // x axis +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(ColorRGBA::X()); +#else + m_arrow.set_color(-1, ColorRGBA::X()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); + + // y axis +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(ColorRGBA::Y()); +#else + m_arrow.set_color(-1, ColorRGBA::Y()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); + + // z axis +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(ColorRGBA::Z()); +#else + m_arrow.set_color(-1, ColorRGBA::Z()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + render_axis(Geometry::assemble_transform(m_origin).cast()); + + if (shader_differs) { + shader->stop_using(); + if (curr_shader != nullptr) + curr_shader->start_using(); + } +} + +} // GUI +} // Slic3r + +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES diff --git a/src/slic3r/GUI/CoordAxes.hpp b/src/slic3r/GUI/CoordAxes.hpp new file mode 100644 index 000000000..de5472b13 --- /dev/null +++ b/src/slic3r/GUI/CoordAxes.hpp @@ -0,0 +1,59 @@ +#ifndef slic3r_CoordAxes_hpp_ +#define slic3r_CoordAxes_hpp_ + +#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#include "GLModel.hpp" + +namespace Slic3r { +namespace GUI { + +class CoordAxes +{ +public: + static const float DefaultStemRadius; + static const float DefaultStemLength; + static const float DefaultTipRadius; + static const float DefaultTipLength; + +private: + Vec3d m_origin{ Vec3d::Zero() }; + float m_stem_radius{ DefaultStemRadius }; + float m_stem_length{ DefaultStemLength }; + float m_tip_radius{ DefaultTipRadius }; + float m_tip_length{ DefaultTipLength }; + GLModel m_arrow; + +public: + const Vec3d& get_origin() const { return m_origin; } + void set_origin(const Vec3d& origin) { m_origin = origin; } + void set_stem_radius(float radius) { + m_stem_radius = radius; + m_arrow.reset(); + } + void set_stem_length(float length) { + m_stem_length = length; + m_arrow.reset(); + } + void set_tip_radius(float radius) { + m_tip_radius = radius; + m_arrow.reset(); + } + void set_tip_length(float length) { + m_tip_length = length; + m_arrow.reset(); + } + + float get_stem_radius() const { return m_stem_radius; } + float get_stem_length() const { return m_stem_length; } + float get_tip_radius() const { return m_tip_radius; } + float get_tip_length() const { return m_tip_length; } + float get_total_length() const { return m_stem_length + m_tip_length; } + void render(float emission_factor = 0.0f); +}; + +} // GUI +} // Slic3r + +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES + +#endif // slic3r_CoordAxes_hpp_ diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index cc37ebaa7..b1e48ffe4 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -117,6 +117,12 @@ Selection::Selection() , m_scale_factor(1.0f) { this->set_bounding_boxes_dirty(); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + m_axes.set_stem_radius(0.15f); + m_axes.set_stem_length(3.0f); + m_axes.set_tip_radius(0.45f); + m_axes.set_tip_length(1.5f); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 4030a0585..a6dffb033 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -3,9 +3,15 @@ #include "libslic3r/Geometry.hpp" #if ENABLE_WORLD_COORDINATE -#include "slic3r/GUI/GUI_Geometry.hpp" -#endif // ENABLE_WORLD_COORDINATE +#include "GUI_Geometry.hpp" +#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#include "CoordAxes.hpp" +#else #include "GLModel.hpp" +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#else +#include "GLModel.hpp" +#endif // ENABLE_WORLD_COORDINATE #include #include @@ -221,6 +227,9 @@ private: GLModel m_vbo_sphere; #endif // ENABLE_RENDER_SELECTION_CENTER +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + CoordAxes m_axes; +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES GLModel m_arrow; GLModel m_curved_arrow; #if ENABLE_LEGACY_OPENGL_REMOVAL From 622796e9e3fcda63f9d6ec7ea97e43707883500f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 18 Feb 2022 08:57:26 +0100 Subject: [PATCH 063/102] Tech ENABLE_WORLD_COORDINATE_SCALE_REVISITED - Alternate implementation of manipulating scale for instances and volumes using gizmo scale and sidebar object manipulator fields - 1st installment Fixed conflicts during rebase with master --- src/libslic3r/Model.cpp | 2 + src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 111 ++++++++++++++++++++-- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 6 ++ src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 2 +- src/slic3r/GUI/Selection.cpp | 73 +++++++++++++- src/slic3r/GUI/Selection.hpp | 9 ++ src/slic3r/GUI/wxExtensions.cpp | 4 + 9 files changed, 199 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3376cc888..0f48f2bb2 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1412,9 +1412,11 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) assert(instance_idx < this->instances.size()); const Geometry::Transformation reference_trafo = this->instances[instance_idx]->get_transformation(); +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (Geometry::is_rotation_ninety_degrees(reference_trafo.get_rotation())) // nothing to do, scaling in the world coordinate space is possible in the representation of Geometry::Transformation. return; +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED bool left_handed = reference_trafo.is_left_handed(); bool has_mirrorring = ! reference_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.)); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index ada35b5ec..6a73aaf1e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -77,6 +77,8 @@ #define ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX (1 && ENABLE_WORLD_COORDINATE) // Enable showing the axes of the current reference system when sidebar hints are active #define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) +// Enable alternate implementation of manipulating scale for instances and volumes +#define ENABLE_WORLD_COORDINATE_SCALE_REVISITED (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index dd7256d2a..60609b7eb 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -627,10 +627,17 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (m_world_coordinates) { #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale_label_string = L("Scale"); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); - m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; - } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else + m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + } else { #if ENABLE_WORLD_COORDINATE m_new_move_label_string = L("Translate"); @@ -672,9 +679,16 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = offset; m_new_rotate_label_string = L("Rotate"); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale_label_string = L("Scale"); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); @@ -687,10 +701,17 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotate_label_string = L("Rotate"); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale_label_string = L("Scale"); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_rotation = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; @@ -767,20 +788,26 @@ void ObjectManipulation::update_if_dirty() #else if (selection.requires_uniform_scale()) { #endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_lock_bnt->SetLock(true); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED #if ENABLE_WORLD_COORDINATE wxString tooltip; if (selection.is_single_volume_or_modifier()) { +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the instance local axes"); else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_World) tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the printer axes"); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else if (selection.is_single_full_instance()) { +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (reason == Selection::EUniformScaleRequiredReason::InstanceNotAxisAligned_World) tooltip = _L("You cannot use non-uniform scaling mode for instances non aligned with the printer axes"); else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) tooltip = _L("You cannot use non-uniform scaling mode for instances containing non locally axis-aligned parts"); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); @@ -789,7 +816,9 @@ void ObjectManipulation::update_if_dirty() #else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); #endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_lock_bnt->disable(); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else { m_lock_bnt->SetLock(m_uniform_scale); @@ -1108,7 +1137,11 @@ void ObjectManipulation::change_size_value(int axis, double value) selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + this->do_size(axis, size.cwiseQuotient(ref_size)); +#else this->do_scale(axis, size.cwiseQuotient(ref_size)); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; @@ -1124,8 +1157,18 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + if (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + + if (!is_local_coordinates()) + transformation_type.set_relative(); +#else if (!is_world_coordinates()) transformation_type.set_local(); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; @@ -1156,6 +1199,36 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +void ObjectManipulation::do_size(int axis, const Vec3d& scale) const +{ + Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + + TransformationType transformation_type; + if (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + + bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); + Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; + + if (!uniform_scale && is_world_coordinates()) { + if (selection.is_single_full_instance()) + scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); + else if (selection.is_single_volume_or_modifier()) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); + } + } + + selection.setup_cache(); + selection.scale(scaling_factor, transformation_type); + wxGetApp().plater()->canvas3D()->do_scale(L("Set Size")); +} +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value) { if (!m_cache.is_valid()) @@ -1190,21 +1263,37 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double } } -void ObjectManipulation::set_uniform_scaling(const bool new_value) +void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) { const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); -#if ENABLE_WORLD_COORDINATE - if (selection.is_single_full_instance() && is_world_coordinates() && !new_value) { +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + if (!use_uniform_scale) { + int res = selection.bake_transform_if_needed(false); + if (res == -1) { + // Enforce uniform scaling. + m_lock_bnt->SetLock(true); + return; + } + else if (res == 0) { + // Recalculate cached values at this panel, refresh the screen. + this->UpdateAndShow(true); + } + } + + m_uniform_scale = use_uniform_scale; #else - if (selection.is_single_full_instance() && m_world_coordinates && !new_value) { +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance() && is_world_coordinates() && !use_uniform_scale) { +#else + if (selection.is_single_full_instance() && m_world_coordinates && !use_uniform_scale) { #endif // ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); // Is the angle close to a multiple of 90 degrees? - if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { + if (!Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Cannot apply scaling in the world coordinate system. - //wxMessageDialog dlg(GUI::wxGetApp().mainframe, + //wxMessageDialog dlg(GUI::wxGetApp().mainframe, MessageDialog dlg(GUI::wxGetApp().mainframe, _L("The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n" "Non-uniform scaling of tilted objects is only possible in the World coordinate system,\n" @@ -1212,7 +1301,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) _L("This operation is irreversible.\n" "Do you want to proceed?"), SLIC3R_APP_NAME, - wxYES_NO | wxCANCEL | wxCANCEL_DEFAULT | wxICON_QUESTION); + wxYES_NO | wxCANCEL | wxCANCEL_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() != wxID_YES) { // Enforce uniform scaling. m_lock_bnt->SetLock(true); @@ -1226,7 +1315,9 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) this->UpdateAndShow(true); } } - m_uniform_scale = new_value; + + m_uniform_scale = use_uniform_scale; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED } #if ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 19d2c1808..289485dad 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -196,7 +196,7 @@ public: // Called from the App to update the UI if dirty. void update_if_dirty(); - void set_uniform_scaling(const bool uniform_scale); + void set_uniform_scaling(const bool use_uniform_scale); bool get_uniform_scaling() const { return m_uniform_scale; } #if ENABLE_WORLD_COORDINATE void set_coordinates_type(ECoordinatesType type); @@ -256,6 +256,9 @@ private: void change_scale_value(int axis, double value); void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + void do_size(int axis, const Vec3d& scale) const; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #if ENABLE_WORLD_COORDINATE void set_coordinates_type(const wxString& type_string); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index afaf6cdcd..e279e53e2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -69,6 +69,12 @@ public: const Vec3d& get_scale() const { return m_scale; } void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + const Vec3d& get_starting_scale() const { return m_starting.scale; } +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + + const Vec3d& get_offset() const { return m_offset; } + std::string get_tooltip() const override; /// diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 187afd889..2e9e6bb65 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -205,7 +205,7 @@ public: bool handle_shortcut(int key); bool is_dragging() const; - + ClippingPlane get_clipping_plane() const; bool wants_reslice_supports_on_undo() const; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b1e48ffe4..dfd543a82 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -7,9 +7,15 @@ #include "GUI.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectList.hpp" -#include "Gizmos/GLGizmoBase.hpp" #include "Camera.hpp" #include "Plater.hpp" + +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#include "MsgDialog.hpp" +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + +#include "Gizmos/GLGizmoBase.hpp" + #include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/LocalesUtils.hpp" @@ -604,6 +610,9 @@ bool Selection::requires_uniform_scale() const #endif // ENABLE_WORLD_COORDINATE { #if ENABLE_WORLD_COORDINATE + if (is_empty()) + return false; + ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (is_single_volume_or_modifier()) { if (coord_type == ECoordinatesType::World) { @@ -638,7 +647,6 @@ bool Selection::requires_uniform_scale() const } } } - return false; } else { for (unsigned int i : m_list) { @@ -648,8 +656,8 @@ bool Selection::requires_uniform_scale() const return true; } } - return false; } + return false; } if (reason != nullptr) @@ -1302,6 +1310,65 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co this->set_bounding_boxes_dirty(); } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +int Selection::bake_transform_if_needed(bool apply_scale) const +{ + if ((is_single_full_instance() && wxGetApp().obj_manipul()->is_world_coordinates()) || + (is_single_volume_or_modifier() && !wxGetApp().obj_manipul()->is_local_coordinates())) { + // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. + // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one + const GLVolume& volume = *get_volume(*get_volume_idxs().begin()); + bool needs_baking = false; + if (is_single_full_instance()) { + // Is the instance angle close to a multiple of 90 degrees? + needs_baking |= !Geometry::is_rotation_ninety_degrees(volume.get_instance_rotation()); + // Are all volumes angles close to a multiple of 90 degrees? + for (unsigned int id : get_volume_idxs()) { + if (needs_baking) + break; + needs_baking |= !Geometry::is_rotation_ninety_degrees(get_volume(id)->get_volume_rotation()); + } + } + else if (is_single_volume_or_modifier()) { + // is the volume angle close to a multiple of 90 degrees? + needs_baking |= !Geometry::is_rotation_ninety_degrees(volume.get_volume_rotation()); + if (wxGetApp().obj_manipul()->is_world_coordinates()) + // Is the instance angle close to a multiple of 90 degrees? + needs_baking |= !Geometry::is_rotation_ninety_degrees(volume.get_instance_rotation()); + } + + if (needs_baking) { + MessageDialog dlg((wxWindow*)wxGetApp().mainframe, + _L("The currently manipulated object is tilted or contains tilted parts (rotation angles are not multiples of 90°). " + "Non-uniform scaling of tilted objects is only possible in non-local coordinate systems, " + "once the rotation is embedded into the object coordinates.") + "\n" + + _L("This operation is irreversible.") + "\n" + + _L("Do you want to proceed?"), + SLIC3R_APP_NAME, + wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + if (dlg.ShowModal() != wxID_YES) + return -1; + + if (apply_scale) { + wxGetApp().plater()->get_current_canvas3D()->do_scale(L("Scale + Bake transform")); + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + wxGetApp().plater()->take_snapshot(_("Bake transform")); + } + else + wxGetApp().plater()->take_snapshot(_("Bake transform")); + + // Bake the rotation into the meshes of the object. + wxGetApp().model().objects[volume.composite_id.object_id]->bake_xy_rotation_into_meshes(volume.composite_id.instance_id); + // Update the 3D scene, selections etc. + wxGetApp().plater()->update(); + return 0; + } + } + + return 1; +} +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + void Selection::erase() { if (!m_valid) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index a6dffb033..5e9b504be 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -366,6 +366,15 @@ public: void translate(unsigned int object_idx, const Vec3d& displacement); void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + // returns: + // -1 if the user refused to proceed with baking when asked + // 0 if the baking was performed + // 1 if no baking was needed + // if apply_scale == true the scaling of the current selection is applied + int bake_transform_if_needed(bool apply_scale) const; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + void erase(); void render(float scale_factor = 1.0); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index dc177ade3..bacdb1f9c 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -581,8 +581,12 @@ void LockButton::OnButton(wxCommandEvent& event) if (m_disabled) return; +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + SetLock(!m_is_pushed); +#else m_is_pushed = !m_is_pushed; update_button_bitmaps(); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED event.Skip(); } From e86cbf0d8c0631b94586d3c98a2b085c9ef4060e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 18 Feb 2022 14:40:43 +0100 Subject: [PATCH 064/102] Tech ENABLE_WORLD_COORDINATE_SCALE_REVISITED - Detection of required transformation baking done on mouse dragging event in place of mouse up event for gizmo scale Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 ++--- src/slic3r/GUI/Selection.cpp | 10 ++-------- src/slic3r/GUI/Selection.hpp | 3 +-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 60609b7eb..48934e165 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1268,16 +1268,15 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (!use_uniform_scale) { - int res = selection.bake_transform_if_needed(false); + int res = selection.bake_transform_if_needed(); if (res == -1) { // Enforce uniform scaling. m_lock_bnt->SetLock(true); return; } - else if (res == 0) { + else if (res == 0) // Recalculate cached values at this panel, refresh the screen. this->UpdateAndShow(true); - } } m_uniform_scale = use_uniform_scale; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index dfd543a82..685e0af8c 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1311,7 +1311,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co } #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED -int Selection::bake_transform_if_needed(bool apply_scale) const +int Selection::bake_transform_if_needed() const { if ((is_single_full_instance() && wxGetApp().obj_manipul()->is_world_coordinates()) || (is_single_volume_or_modifier() && !wxGetApp().obj_manipul()->is_local_coordinates())) { @@ -1349,13 +1349,7 @@ int Selection::bake_transform_if_needed(bool apply_scale) const if (dlg.ShowModal() != wxID_YES) return -1; - if (apply_scale) { - wxGetApp().plater()->get_current_canvas3D()->do_scale(L("Scale + Bake transform")); - Plater::SuppressSnapshots suppress(wxGetApp().plater()); - wxGetApp().plater()->take_snapshot(_("Bake transform")); - } - else - wxGetApp().plater()->take_snapshot(_("Bake transform")); + wxGetApp().plater()->take_snapshot(_("Bake transform")); // Bake the rotation into the meshes of the object. wxGetApp().model().objects[volume.composite_id.object_id]->bake_xy_rotation_into_meshes(volume.composite_id.instance_id); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 5e9b504be..95a73f8f7 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -371,8 +371,7 @@ public: // -1 if the user refused to proceed with baking when asked // 0 if the baking was performed // 1 if no baking was needed - // if apply_scale == true the scaling of the current selection is applied - int bake_transform_if_needed(bool apply_scale) const; + int bake_transform_if_needed() const; #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED void erase(); From 2b002da8ce98d1eb2c861e411f07bb6c639b82c2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 23 Feb 2022 15:51:56 +0100 Subject: [PATCH 065/102] Tech ENABLE_WORLD_COORDINATE - Fixed rotation performed from sidebar manipulation fields --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 48934e165..f5abe805c 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1057,10 +1057,13 @@ void ObjectManipulation::change_rotation_value(int axis, double value) if (selection.is_single_full_instance()) transformation_type.set_independent(); - if (!is_world_coordinates()) { + if (is_local_coordinates()) { transformation_type.set_local(); transformation_type.set_absolute(); } + + if (is_instance_coordinates()) + transformation_type.set_instance(); #else if (selection.is_single_full_instance() || selection.requires_local_axes()) transformation_type.set_independent(); From 45335ee26bbf059c1243e038e612f27d82d3ae3f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 24 Feb 2022 14:09:43 +0100 Subject: [PATCH 066/102] Tech ENABLE_OBJECT_MANIPULATOR_FOCUS - Fixed kill focus handling for sidebar object manipulator fields --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index f5abe805c..8a0d52bbb 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1457,8 +1457,8 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, parent->set_focused_editor(nullptr); #if ENABLE_OBJECT_MANIPULATOR_FOCUS - // if the widgets exchanging focus are both manipulator fields, call kill_focus - if (dynamic_cast(e.GetEventObject()) != nullptr && dynamic_cast(e.GetWindow()) != nullptr) + // if the widgets loosing focus is a manipulator field, call kill_focus + if (dynamic_cast(e.GetEventObject()) != nullptr) #else if (!m_enter_pressed) #endif // ENABLE_OBJECT_MANIPULATOR_FOCUS From 47a1989fdcd2551bd2ad1c6b2c25107a1ca76c38 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 25 Feb 2022 12:12:23 +0100 Subject: [PATCH 067/102] Tech ENABLE_WORLD_COORDINATE - ObjectManipulation::update_reset_buttons_visibility() changed logic for drop to bed button --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8a0d52bbb..96429c924 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -861,6 +861,14 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE + if ((m_coordinates_type == ECoordinatesType::World && selection.is_single_full_instance()) || + (m_coordinates_type == ECoordinatesType::Instance && selection.is_single_volume_or_modifier())) { + const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : + get_volume_min_z(*selection.get_volume(*selection.get_volume_idxs().begin())); + + show_drop_to_bed = std::abs(min_z) > EPSILON; + } + if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); @@ -870,26 +878,28 @@ void ObjectManipulation::update_reset_buttons_visibility() const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation; Vec3d scale; -#endif // ENABLE_WORLD_COORDINATE double min_z = 0.0; +#endif // ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance()) { rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); +#if !ENABLE_WORLD_COORDINATE min_z = selection.get_scaled_instance_bounding_box().min.z(); +#endif // !ENABLE_WORLD_COORDINATE } else { rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); +#if !ENABLE_WORLD_COORDINATE min_z = get_volume_min_z(*volume); +#endif // !ENABLE_WORLD_COORDINATE } show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); -#if ENABLE_WORLD_COORDINATE - show_drop_to_bed = std::abs(min_z) > EPSILON; -#else +#if !ENABLE_WORLD_COORDINATE show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; -#endif // ENABLE_WORLD_COORDINATE +#endif // !ENABLE_WORLD_COORDINATE } wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { From 9acf181dda31c2a793c4e9368af547f258a13337 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Mar 2022 13:56:33 +0100 Subject: [PATCH 068/102] Tech ENABLE_GL_SHADERS_ATTRIBUTES - Fixed rendering of bed axes --- src/slic3r/GUI/CoordAxes.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp index c3cced2f9..2ec303c61 100644 --- a/src/slic3r/GUI/CoordAxes.cpp +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -3,6 +3,10 @@ #include "CoordAxes.hpp" #include "GUI_App.hpp" #include "3DScene.hpp" +#if ENABLE_GL_SHADERS_ATTRIBUTES +#include "Plater.hpp" +#include "Camera.hpp" +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #include @@ -18,20 +22,38 @@ const float CoordAxes::DefaultTipLength = 5.0f; void CoordAxes::render(float emission_factor) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + auto render_axis = [this](GLShaderProgram& shader, const Transform3d& transform) { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d matrix = camera.get_view_matrix() * transform; + shader.set_uniform("view_model_matrix", matrix); + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); + shader.set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_arrow.render(); +#else auto render_axis = [this](const Transform3f& transform) { glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(transform.data())); m_arrow.render(); glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES }; if (!m_arrow.is_initialized()) m_arrow.init_from(stilized_arrow(16, m_tip_radius, m_tip_length, m_stem_radius, m_stem_length)); GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light_attr"); +#else bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -48,7 +70,11 @@ void CoordAxes::render(float emission_factor) #else m_arrow.set_color(-1, ColorRGBA::X()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_axis(*shader, Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 })); +#else render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES // y axis #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -56,7 +82,11 @@ void CoordAxes::render(float emission_factor) #else m_arrow.set_color(-1, ColorRGBA::Y()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_axis(*shader, Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 })); +#else render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES // z axis #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -64,7 +94,11 @@ void CoordAxes::render(float emission_factor) #else m_arrow.set_color(-1, ColorRGBA::Z()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_axis(*shader, Geometry::assemble_transform(m_origin)); +#else render_axis(Geometry::assemble_transform(m_origin).cast()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader_differs) { shader->stop_using(); From 155c3e88954f4075a08d51157d2fd029b92f5d1a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Mar 2022 14:03:50 +0100 Subject: [PATCH 069/102] Tech ENABLE_GL_SHADERS_ATTRIBUTES - Fixed rendering of selection bounding box Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 126 ++++++++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 685e0af8c..50d4677c0 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1516,7 +1516,11 @@ void Selection::render_center(bool gizmo_is_dragging) return; #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -1561,7 +1565,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) return; #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat_attr" : "gouraud_light_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -1576,28 +1584,51 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) shader->start_using(); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - } +} #endif // ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glEnable(GL_DEPTH_TEST)); #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES Transform3d orient_matrix = Transform3d::Identity(); #else glsafe(::glPushMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES + +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + const Vec3d center = get_bounding_box().center(); + Vec3d axes_center = center; +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES if (!boost::starts_with(sidebar_field, "layer")) { #if ENABLE_GL_SHADERS_ATTRIBUTES shader->set_uniform("emission_factor", 0.05f); -#else - const Vec3d& center = get_bounding_box().center(); #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + const Vec3d& center = get_bounding_box().center(); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES +#else + if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + Transform3d orient_matrix = Transform3d::Identity(); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); +#else + glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#else if (!boost::starts_with(sidebar_field, "position")) { #if !ENABLE_GL_SHADERS_ATTRIBUTES Transform3d orient_matrix = Transform3d::Identity(); @@ -1618,24 +1649,56 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glMultMatrixd(orient_matrix.data())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } + } +#endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + else if (is_single_volume_or_modifier()) { +#else else if (is_single_volume() || is_single_modifier()) { +#endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + glsafe(::glTranslated(center.x(), center.y(), center.z())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + Transform3d orient_matrix = Transform3d::Identity(); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + if (wxGetApp().obj_manipul()->is_local_coordinates()) { + const GLVolume* v = (*m_volumes)[*m_list.begin()]; + orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation(); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES + } + else { + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); + } +#else + } +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES + } +#else #if ENABLE_GL_SHADERS_ATTRIBUTES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #else - glsafe(::glTranslated(center.x(), center.y(), center.z())); Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (!boost::starts_with(sidebar_field, "position")) orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glMultMatrixd(orient_matrix.data())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_WORLD_COORDINATE } else { -#if ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES if (requires_local_axes()) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #else @@ -1644,15 +1707,24 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } -#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES + } } - } #if ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + if (!boost::starts_with(sidebar_field, "layer")) { + shader->set_uniform("emission_factor", 0.1f); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(center.x(), center.y(), center.z())); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES + #if ENABLE_GL_SHADERS_ATTRIBUTES if (boost::starts_with(sidebar_field, "position")) render_sidebar_position_hints(sidebar_field, *shader, base_matrix * orient_matrix); @@ -1672,9 +1744,23 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field); - glsafe(::glPopMatrix()); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + if (!boost::starts_with(sidebar_field, "layer")) { + glsafe(::glPopMatrix()); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(axes_center.x(), axes_center.y(), axes_center.z())); + glsafe(::glMultMatrixd(orient_matrix.data())); + if (!wxGetApp().obj_manipul()->is_world_coordinates()) + m_axes.render(0.25f); + glsafe(::glPopMatrix()); + } +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES + #if !ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) #endif // !ENABLE_LEGACY_OPENGL_REMOVAL @@ -2259,21 +2345,29 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glLineWidth(2.0f * m_scale_factor)); - +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; #if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo); +#else shader->set_uniform("view_model_matrix", camera.get_view_matrix()); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES m_box.set_color(to_rgba(color)); @@ -2281,7 +2375,9 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con shader->stop_using(); #if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else ::glBegin(GL_LINES); From 8c95f47d4a509b6d520f89d4b99d453d16a6611b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Mar 2022 14:42:05 +0100 Subject: [PATCH 070/102] Tech ENABLE_GL_SHADERS_ATTRIBUTES - Fixed rendering of sidebar hints reference system Fixed conflicts during rebase with master --- src/slic3r/GUI/3DBed.cpp | 4 ++++ src/slic3r/GUI/CoordAxes.cpp | 10 +++++++--- src/slic3r/GUI/CoordAxes.hpp | 5 +++++ src/slic3r/GUI/Selection.cpp | 18 +++++++++++++++--- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 132245b4d..7b34d59bf 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -546,7 +546,11 @@ void Bed3D::render_axes() { if (m_build_volume.valid()) #if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_GL_SHADERS_ATTRIBUTES + m_axes.render(Transform3d::Identity(), 0.25f); +#else m_axes.render(0.25f); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #else m_axes.render(); #endif // ENABLE_WORLD_COORDINATE_SHOW_AXES diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp index 2ec303c61..75038e23c 100644 --- a/src/slic3r/GUI/CoordAxes.cpp +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -20,7 +20,11 @@ const float CoordAxes::DefaultStemLength = 25.0f; const float CoordAxes::DefaultTipRadius = 2.5f * CoordAxes::DefaultStemRadius; const float CoordAxes::DefaultTipLength = 5.0f; +#if ENABLE_GL_SHADERS_ATTRIBUTES +void CoordAxes::render(const Transform3d& trafo, float emission_factor) +#else void CoordAxes::render(float emission_factor) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES { #if ENABLE_GL_SHADERS_ATTRIBUTES auto render_axis = [this](GLShaderProgram& shader, const Transform3d& transform) { @@ -71,7 +75,7 @@ void CoordAxes::render(float emission_factor) m_arrow.set_color(-1, ColorRGBA::X()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES - render_axis(*shader, Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 })); + render_axis(*shader, trafo * Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 })); #else render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -83,7 +87,7 @@ void CoordAxes::render(float emission_factor) m_arrow.set_color(-1, ColorRGBA::Y()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES - render_axis(*shader, Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 })); + render_axis(*shader, trafo * Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 })); #else render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -95,7 +99,7 @@ void CoordAxes::render(float emission_factor) m_arrow.set_color(-1, ColorRGBA::Z()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES - render_axis(*shader, Geometry::assemble_transform(m_origin)); + render_axis(*shader, trafo * Geometry::assemble_transform(m_origin)); #else render_axis(Geometry::assemble_transform(m_origin).cast()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/CoordAxes.hpp b/src/slic3r/GUI/CoordAxes.hpp index de5472b13..d2e38e184 100644 --- a/src/slic3r/GUI/CoordAxes.hpp +++ b/src/slic3r/GUI/CoordAxes.hpp @@ -48,7 +48,12 @@ public: float get_tip_radius() const { return m_tip_radius; } float get_tip_length() const { return m_tip_length; } float get_total_length() const { return m_stem_length + m_tip_length; } + +#if ENABLE_GL_SHADERS_ATTRIBUTES + void render(const Transform3d& trafo, float emission_factor = 0.0f); +#else void render(float emission_factor = 0.0f); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES }; } // GUI diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 50d4677c0..3bb2db1ac 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1584,7 +1584,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) shader->start_using(); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); -} + } #endif // ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glEnable(GL_DEPTH_TEST)); @@ -1719,9 +1719,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if ENABLE_WORLD_COORDINATE_SHOW_AXES if (!boost::starts_with(sidebar_field, "layer")) { shader->set_uniform("emission_factor", 0.1f); +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPushMatrix()); glsafe(::glTranslated(center.x(), center.y(), center.z())); glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES } #endif // ENABLE_WORLD_COORDINATE_SHOW_AXES @@ -1734,6 +1736,12 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field, *shader); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + if (!boost::starts_with(sidebar_field, "layer")) { + if (!wxGetApp().obj_manipul()->is_world_coordinates()) + m_axes.render(Geometry::assemble_transform(axes_center) * orient_matrix, 0.25f); + } +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #else if (boost::starts_with(sidebar_field, "position")) render_sidebar_position_hints(sidebar_field); @@ -1757,9 +1765,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #if !ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) @@ -2345,6 +2355,8 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glLineWidth(2.0f * m_scale_factor)); + #if ENABLE_GL_SHADERS_ATTRIBUTES GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); #else From a4c0d99616579058d70904022cd6585904d71e8d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 11 Mar 2022 08:43:07 +0100 Subject: [PATCH 071/102] Tech ENABLE_GL_SHADERS_ATTRIBUTES - Fixed rendering of gizmo rotate Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 26 +++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 5a0c99b76..c9947ee18 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -190,7 +190,11 @@ void GLGizmoRotate::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader != nullptr) { shader->start_using(); @@ -652,7 +656,11 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick const double size = m_dragging ? double(m_grabbers.front().get_dragging_half_size(mean_size)) : double(m_grabbers.front().get_half_size(mean_size)); #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat_attr" : "gouraud_light_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -747,10 +755,14 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const } } +#if ENABLE_WORLD_COORDINATE + return Geometry::assemble_transform(m_center) * m_orient_matrix * ret; +#else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) ret = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true) * ret; return Geometry::assemble_transform(m_center) * ret; +#endif // ENABLE_WORLD_COORDINATE } #else void GLGizmoRotate::transform_to_local(const Selection& selection) const @@ -844,8 +856,18 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) { if (mouse_event.Dragging() && m_dragging) { // Apply new temporary rotations - TransformationType transformation_type( - TransformationType::World_Relative_Joint); +#if ENABLE_WORLD_COORDINATE + TransformationType transformation_type; + switch (wxGetApp().obj_manipul()->get_coordinates_type()) + { + default: + case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } + case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; } + case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } + } +#else + TransformationType transformation_type(TransformationType::World_Relative_Joint); +#endif // ENABLE_WORLD_COORDINATE if (mouse_event.AltDown()) transformation_type.set_independent(); m_parent.get_selection().rotate(get_rotation(), transformation_type); } From 7e729632936836cf38d59910e5e11c1244cd6ee5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 29 Apr 2022 13:51:58 +0200 Subject: [PATCH 072/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - 1st installment. Geometry::Transformation modified to store data in a single matrix, without store the matrix components Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 182 ++ src/libslic3r/Geometry.hpp | 116 +- src/libslic3r/Model.cpp | 69 +- src/libslic3r/Model.hpp | 2466 +++++++-------- src/libslic3r/Point.hpp | 1134 +++---- src/libslic3r/PrintApply.cpp | 2897 +++++++++--------- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/3DScene.hpp | 1594 +++++----- src/slic3r/GUI/GUI_ObjectList.cpp | 8 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 8 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 878 +++--- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 954 +++--- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 1980 ++++++------ src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 134 +- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 4 + src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 2790 ++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 30 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 63 +- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 4 + src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2744 ++++++++--------- src/slic3r/GUI/MeshUtils.cpp | 748 ++--- src/slic3r/GUI/Plater.cpp | 10 + src/slic3r/GUI/Selection.cpp | 48 +- 23 files changed, 9768 insertions(+), 9095 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 58c90d9bc..fba1b2378 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -313,6 +313,34 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, return transform; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void rotation_transform(Transform3d& transform, const Vec3d& rotation) +{ + transform = Transform3d::Identity(); + transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX())); +} + +Transform3d rotation_transform(const Vec3d& rotation) +{ + Transform3d transform; + rotation_transform(transform, rotation); + return transform; +} + +void scale_transform(Transform3d& transform, const Vec3d& scale) +{ + transform = Transform3d::Identity(); + transform.scale(scale); +} + +Transform3d scale_transform(const Vec3d& scale) +{ + Transform3d transform; + scale_transform(transform, scale); + return transform; +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix) { // reference: http://eecs.qmul.ac.uk/~gslabaugh/publications/euler.pdf @@ -363,6 +391,46 @@ Vec3d extract_euler_angles(const Transform3d& transform) return extract_euler_angles(m); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +Transform3d Transformation::get_offset_matrix() const +{ + return assemble_transform(get_offset()); +} + +static Transform3d extract_rotation(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return Transform3d(rotation); +} + +static Transform3d extract_scale(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return Transform3d(scale); +} + +static std::pair extract_rotation_scale(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return { Transform3d(rotation), Transform3d(scale) }; +} + +Vec3d Transformation::get_rotation() const +{ + return extract_euler_angles(extract_rotation(m_matrix)); +} + +Transform3d Transformation::get_rotation_matrix() const +{ + return extract_rotation(m_matrix); +} +#else bool Transformation::Flags::needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { return (this->dont_translate != dont_translate) || (this->dont_rotate != dont_rotate) || (this->dont_scale != dont_scale) || (this->dont_mirror != dont_mirror); @@ -400,12 +468,19 @@ void Transformation::set_offset(Axis axis, double offset) m_dirty = true; } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Transformation::set_rotation(const Vec3d& rotation) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d offset = get_offset(); + m_matrix = rotation_transform(rotation) * extract_scale(m_matrix); + m_matrix.translation() = offset; +#else set_rotation(X, rotation.x()); set_rotation(Y, rotation.y()); set_rotation(Z, rotation.z()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } void Transformation::set_rotation(Axis axis, double rotation) @@ -414,32 +489,106 @@ void Transformation::set_rotation(Axis axis, double rotation) if (is_approx(std::abs(rotation), 2.0 * double(PI))) rotation = 0.0; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + auto [curr_rotation, scale] = extract_rotation_scale(m_matrix); + Vec3d angles = extract_euler_angles(curr_rotation); + angles[axis] = rotation; + + const Vec3d offset = get_offset(); + m_matrix = rotation_transform(angles) * scale; + m_matrix.translation() = offset; +#else if (m_rotation(axis) != rotation) { m_rotation(axis) = rotation; m_dirty = true; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +Vec3d Transformation::get_scaling_factor() const +{ + const Transform3d scale = extract_scale(m_matrix); + return { scale(0, 0), scale(1, 1), scale(2, 2) }; +} + +Transform3d Transformation::get_scaling_factor_matrix() const +{ + return extract_scale(m_matrix); +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + void Transformation::set_scaling_factor(const Vec3d& scaling_factor) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + assert(scaling_factor.x() > 0.0 && scaling_factor.y() > 0.0 && scaling_factor.z() > 0.0); + + const Vec3d offset = get_offset(); + m_matrix = extract_rotation(m_matrix) * scale_transform(scaling_factor); + m_matrix.translation() = offset; +#else set_scaling_factor(X, scaling_factor.x()); set_scaling_factor(Y, scaling_factor.y()); set_scaling_factor(Z, scaling_factor.z()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } void Transformation::set_scaling_factor(Axis axis, double scaling_factor) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + assert(scaling_factor > 0.0); + auto [rotation, scale] = extract_rotation_scale(m_matrix); + scale(axis, axis) = scaling_factor; + + const Vec3d offset = get_offset(); + m_matrix = rotation * scale; + m_matrix.translation() = offset; +#else if (m_scaling_factor(axis) != std::abs(scaling_factor)) { m_scaling_factor(axis) = std::abs(scaling_factor); m_dirty = true; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +Vec3d Transformation::get_mirror() const +{ + const Transform3d scale = extract_scale(m_matrix); + return { scale(0, 0) / std::abs(scale(0, 0)), scale(1, 1) / std::abs(scale(1, 1)), scale(2, 2) / std::abs(scale(2, 2)) }; +} + +Transform3d Transformation::get_mirror_matrix() const +{ + const Vec3d scale = get_scaling_factor(); + return scale_transform({ scale.x() / std::abs(scale.x()), scale.y() / std::abs(scale.y()), scale.z() / std::abs(scale.z()) }); +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + void Transformation::set_mirror(const Vec3d& mirror) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d copy(mirror); + const Vec3d abs_mirror = copy.cwiseAbs(); + for (int i = 0; i < 3; ++i) { + if (abs_mirror(i) == 0.0) + copy(i) = 1.0; + else if (abs_mirror(i) != 1.0) + copy(i) /= abs_mirror(i); + } + + const Vec3d curr_scale = get_scaling_factor(); + const Vec3d signs = curr_scale.cwiseProduct(copy); + set_scaling_factor({ + signs.x() < 0.0 ? std::abs(curr_scale.x()) * copy.x() : curr_scale.x(), + signs.y() < 0.0 ? std::abs(curr_scale.y()) * copy.y() : curr_scale.y(), + signs.z() < 0.0 ? std::abs(curr_scale.z()) * copy.z() : curr_scale.z() + }); +#else set_mirror(X, mirror.x()); set_mirror(Y, mirror.y()); set_mirror(Z, mirror.z()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } void Transformation::set_mirror(Axis axis, double mirror) @@ -450,12 +599,19 @@ void Transformation::set_mirror(Axis axis, double mirror) else if (abs_mirror != 1.0) mirror /= abs_mirror; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const double curr_scale = get_scaling_factor(axis); + const double sign = curr_scale * mirror; + set_scaling_factor(axis, sign < 0.0 ? std::abs(curr_scale) * mirror : curr_scale); +#else if (m_mirror(axis) != mirror) { m_mirror(axis) = mirror; m_dirty = true; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES void Transformation::set_from_transform(const Transform3d& transform) { // offset @@ -493,17 +649,38 @@ void Transformation::set_from_transform(const Transform3d& transform) // if (!m_matrix.isApprox(transform)) // std::cout << "something went wrong in extracting data from matrix" << std::endl; } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void Transformation::reset() { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES m_offset = Vec3d::Zero(); m_rotation = Vec3d::Zero(); m_scaling_factor = Vec3d::Ones(); m_mirror = Vec3d::Ones(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_matrix = Transform3d::Identity(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES m_dirty = false; +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +Transform3d Transformation::get_matrix_no_offset() const +{ + Transformation copy(*this); + copy.reset_offset(); + return copy.get_matrix(); +} + +Transform3d Transformation::get_matrix_no_scaling_factor() const +{ + Transformation copy(*this); + copy.reset_scaling_factor(); + copy.reset_mirror(); + return copy.get_matrix(); +} +#else const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) { @@ -520,6 +697,7 @@ const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rot return m_matrix; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Transformation Transformation::operator * (const Transformation& other) const { @@ -533,7 +711,11 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation if (instance_transformation.is_scaling_uniform()) { // No need to run the non-linear least squares fitting for uniform scaling. // Just set the inverse. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + out = instance_transformation.get_matrix_no_offset().inverse(); +#else out.set_from_transform(instance_transformation.get_matrix(true).inverse()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) { // Anisotropic scaling, rotation by multiples of ninety degrees. diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 2ca4ef884..ffe96cd3a 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -334,6 +334,26 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d // 6) translate Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +// Sets the given transform by assembling the given rotations in the following order: +// 1) rotate X +// 2) rotate Y +// 3) rotate Z +void rotation_transform(Transform3d& transform, const Vec3d& rotation); + +// Returns the transform obtained by assembling the given rotations in the following order: +// 1) rotate X +// 2) rotate Y +// 3) rotate Z +Transform3d rotation_transform(const Vec3d& rotation); + +// Sets the given transform by assembling the given scale factors +void scale_transform(Transform3d& transform, const Vec3d& scale); + +// Returns the transform obtained by assembling the given scale factors +Transform3d scale_transform(const Vec3d& scale); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + // Returns the euler angles extracted from the given rotation matrix // Warning -> The matrix should not contain any scale or shear !!! Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix); @@ -344,6 +364,9 @@ Vec3d extract_euler_angles(const Transform3d& transform); class Transformation { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d m_matrix{ Transform3d::Identity() }; +#else struct Flags { bool dont_translate{ true }; @@ -363,42 +386,104 @@ class Transformation mutable Transform3d m_matrix{ Transform3d::Identity() }; mutable Flags m_flags; mutable bool m_dirty{ false }; +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES public: +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transformation() = default; + explicit Transformation(const Transform3d & transform) : m_matrix(transform) {} +#else Transformation(); - explicit Transformation(const Transform3d& transform); + explicit Transformation(const Transform3d & transform); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transformation& operator = (const Transform3d& transform) { m_matrix = transform; return *this; } + + Vec3d get_offset() const { return m_matrix.translation(); } + double get_offset(Axis axis) const { return get_offset()[axis]; } + + Transform3d get_offset_matrix() const; + + void set_offset(const Vec3d& offset) { m_matrix.translation() = offset; } + void set_offset(Axis axis, double offset) { m_matrix.translation()[axis] = offset; } +#else const Vec3d& get_offset() const { return m_offset; } double get_offset(Axis axis) const { return m_offset(axis); } void set_offset(const Vec3d& offset); void set_offset(Axis axis, double offset); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_rotation() const; + double get_rotation(Axis axis) const { return get_rotation()[axis]; } + + Transform3d get_rotation_matrix() const; +#else const Vec3d& get_rotation() const { return m_rotation; } double get_rotation(Axis axis) const { return m_rotation(axis); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_scaling_factor() const; + double get_scaling_factor(Axis axis) const { return get_scaling_factor()[axis]; } + + Transform3d get_scaling_factor_matrix() const; + + bool is_scaling_uniform() const { + const Vec3d scale = get_scaling_factor(); + return std::abs(scale.x() - scale.y()) < 1e-8 && std::abs(scale.x() - scale.z()) < 1e-8; + } +#else const Vec3d& get_scaling_factor() const { return m_scaling_factor; } double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void set_scaling_factor(const Vec3d& scaling_factor); void set_scaling_factor(Axis axis, double scaling_factor); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_mirror() const; + double get_mirror(Axis axis) const { return get_mirror()[axis]; } + + Transform3d get_mirror_matrix() const; + + bool is_left_handed() const { + const Vec3d mirror = get_mirror(); + return mirror.x() * mirror.y() * mirror.z() < 0.0; + } +#else bool is_scaling_uniform() const { return std::abs(m_scaling_factor.x() - m_scaling_factor.y()) < 1e-8 && std::abs(m_scaling_factor.x() - m_scaling_factor.z()) < 1e-8; } const Vec3d& get_mirror() const { return m_mirror; } double get_mirror(Axis axis) const { return m_mirror(axis); } bool is_left_handed() const { return m_mirror.x() * m_mirror.y() * m_mirror.z() < 0.; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void set_mirror(const Vec3d& mirror); void set_mirror(Axis axis, double mirror); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES void set_from_transform(const Transform3d& transform); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void reset(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void reset_offset() { set_offset(Vec3d::Zero()); } + void reset_rotation() { set_rotation(Vec3d::Zero()); } + void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); } + void reset_mirror() { set_mirror(Vec3d::Ones()); } + const Transform3d& get_matrix() const { return m_matrix; } + Transform3d get_matrix_no_offset() const; + Transform3d get_matrix_no_scaling_factor() const; +#else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Transformation operator * (const Transformation& other) const; @@ -408,15 +493,26 @@ public: static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); private: - friend class cereal::access; - template void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } - explicit Transformation(int) : m_dirty(true) {} - template static void load_and_construct(Archive &ar, cereal::construct &construct) - { - // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. - construct(1); - ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); - } + friend class cereal::access; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + template void serialize(Archive& ar) { ar(m_matrix); } + explicit Transformation(int) {} + template static void load_and_construct(Archive& ar, cereal::construct& construct) + { + // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. + construct(1); + ar(construct.ptr()->m_matrix); + } +#else + template void serialize(Archive& ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } + explicit Transformation(int) : m_dirty(true) {} + template static void load_and_construct(Archive& ar, cereal::construct& construct) + { + // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. + construct(1); + ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); + } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES }; // For parsing a transformation matrix from 3MF / AMF. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0f48f2bb2..7fb7ed9f1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -945,7 +945,11 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const if (this->instances.empty()) throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances"); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inst_matrix = this->instances.front()->get_transformation().get_matrix_no_offset(); +#else const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES for (const ModelVolume *v : this->volumes) if (v->is_model_part()) m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); @@ -957,9 +961,15 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_translate) const { BoundingBoxf3 bb; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inst_matrix = dont_translate ? + this->instances[instance_idx]->get_transformation().get_matrix_no_offset() : + this->instances[instance_idx]->get_transformation().get_matrix(); + +#else const Transform3d& inst_matrix = this->instances[instance_idx]->get_transformation().get_matrix(dont_translate); - for (ModelVolume *v : this->volumes) - { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + for (ModelVolume *v : this->volumes) { if (v->is_model_part()) bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); } @@ -1368,9 +1378,12 @@ void ModelObject::split(ModelObjectPtrs* new_objects) new_object->add_instance(*model_instance); ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh)); - for (ModelInstance* model_instance : new_object->instances) - { + for (ModelInstance* model_instance : new_object->instances) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d shift = model_instance->get_transformation().get_matrix_no_offset() * new_vol->get_offset(); +#else Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES model_instance->set_offset(model_instance->get_offset() + shift); } @@ -1434,8 +1447,18 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) // Adjust the meshes. // Transformation to be applied to the meshes. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Geometry::Transformation reference_trafo_mod = reference_trafo; + reference_trafo_mod.reset_offset(); + if (uniform_scaling) + reference_trafo_mod.reset_scaling_factor(); + if (!has_mirrorring) + reference_trafo_mod.reset_mirror(); + Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0); +#else Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); - Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); for (ModelVolume *model_volume : this->volumes) { const Geometry::Transformation volume_trafo = model_volume->get_transformation(); bool volume_left_handed = volume_trafo.is_left_handed(); @@ -1444,7 +1467,17 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().z()) < EPSILON; double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.; // Transform the mesh. - Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Geometry::Transformation volume_trafo_mod = volume_trafo; + volume_trafo_mod.reset_offset(); + if (volume_uniform_scaling) + volume_trafo_mod.reset_scaling_factor(); + if (!volume_has_mirrorring) + volume_trafo_mod.reset_mirror(); + Eigen::Matrix3d volume_trafo_3x3 = volume_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0); +#else + Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Following method creates a new shared_ptr model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); // Reset the rotation, scaling and mirroring. @@ -1491,7 +1524,11 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const double min_z = DBL_MAX; const ModelInstance* inst = instances[instance_idx]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d& mi = inst->get_matrix_no_offset(); +#else const Transform3d& mi = inst->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1512,7 +1549,11 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const double max_z = -DBL_MAX; const ModelInstance* inst = instances[instance_idx]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d& mi = inst->get_matrix_no_offset(); +#else const Transform3d& mi = inst->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1938,14 +1979,22 @@ void ModelVolume::convert_from_meters() void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix()); +#else mesh->transform(get_matrix(dont_translate)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const { // Rotate around mesh origin. TriangleMesh copy(mesh); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + copy.transform(get_transformation().get_rotation_matrix()); +#else copy.transform(get_matrix(true, false, true, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES BoundingBoxf3 bbox = copy.bounding_box(); if (!empty(bbox)) { @@ -1970,12 +2019,20 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mes BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + return bbox.transformed(dont_translate ? get_matrix_no_offset() : get_matrix()); +#else return bbox.transformed(get_matrix(dont_translate)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + return dont_translate ? get_matrix_no_offset() * v : get_matrix() * v; +#else return get_matrix(dont_translate) * v; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } void ModelInstance::transform_polygon(Polygon* polygon) const diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0c95d98c0..f2844abcb 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1,1212 +1,1254 @@ -#ifndef slic3r_Model_hpp_ -#define slic3r_Model_hpp_ - -#include "libslic3r.h" -#include "enum_bitmask.hpp" -#include "Geometry.hpp" -#include "ObjectID.hpp" -#include "Point.hpp" -#include "PrintConfig.hpp" -#include "Slicing.hpp" -#include "SLA/SupportPoint.hpp" -#include "SLA/Hollowing.hpp" -#include "TriangleMesh.hpp" -#include "Arrange.hpp" -#include "CustomGCode.hpp" -#include "enum_bitmask.hpp" - -#include -#include -#include -#include -#include - -namespace cereal { - class BinaryInputArchive; - class BinaryOutputArchive; - template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr); - template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr); - template void load_by_value(BinaryInputArchive &ar, T &obj); - template void save_by_value(BinaryOutputArchive &ar, const T &obj); -} - -namespace Slic3r { -enum class ConversionType; - -class BuildVolume; -class Model; -class ModelInstance; -class ModelMaterial; -class ModelObject; -class ModelVolume; -class ModelWipeTower; -class Print; -class SLAPrint; -class TriangleSelector; - -namespace UndoRedo { - class StackImpl; -} - -class ModelConfigObject : public ObjectBase, public ModelConfig -{ -private: - friend class cereal::access; - friend class UndoRedo::StackImpl; - friend class ModelObject; - friend class ModelVolume; - friend class ModelMaterial; - - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - explicit ModelConfigObject() = default; - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - explicit ModelConfigObject(int) : ObjectBase(-1) {} - // Copy constructor copies the ID. - explicit ModelConfigObject(const ModelConfigObject &cfg) = default; - // Move constructor copies the ID. - explicit ModelConfigObject(ModelConfigObject &&cfg) = default; - - Timestamp timestamp() const throw() override { return this->ModelConfig::timestamp(); } - bool object_id_and_timestamp_match(const ModelConfigObject &rhs) const throw() { return this->id() == rhs.id() && this->timestamp() == rhs.timestamp(); } - - // called by ModelObject::assign_copy() - ModelConfigObject& operator=(const ModelConfigObject &rhs) = default; - ModelConfigObject& operator=(ModelConfigObject &&rhs) = default; - - template void serialize(Archive &ar) { - ar(cereal::base_class(this)); - } -}; - -namespace Internal { - template - class StaticSerializationWrapper - { - public: - StaticSerializationWrapper(T &wrap) : wrapped(wrap) {} - private: - friend class cereal::access; - friend class UndoRedo::StackImpl; - template void load(Archive &ar) { cereal::load_by_value(ar, wrapped); } - template void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); } - T& wrapped; - }; -} - -typedef std::string t_model_material_id; -typedef std::string t_model_material_attribute; -typedef std::map t_model_material_attributes; - -typedef std::map ModelMaterialMap; -typedef std::vector ModelObjectPtrs; -typedef std::vector ModelVolumePtrs; -typedef std::vector ModelInstancePtrs; - -#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ - /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ - /* to make a private copy for background processing. */ \ - static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \ - static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \ - static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \ - static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \ - TYPE& assign_copy(const TYPE &rhs); \ - TYPE& assign_copy(TYPE &&rhs); \ - /* Copy a TYPE, generate new IDs. The front end will use this call. */ \ - static TYPE* new_clone(const TYPE &rhs) { \ - /* Default constructor assigning an invalid ID. */ \ - auto obj = new TYPE(-1); \ - obj->assign_clone(rhs); \ - assert(obj->id().valid() && obj->id() != rhs.id()); \ - return obj; \ - } \ - TYPE make_clone(const TYPE &rhs) { \ - /* Default constructor assigning an invalid ID. */ \ - TYPE obj(-1); \ - obj.assign_clone(rhs); \ - assert(obj.id().valid() && obj.id() != rhs.id()); \ - return obj; \ - } \ - TYPE& assign_clone(const TYPE &rhs) { \ - this->assign_copy(rhs); \ - assert(this->id().valid() && this->id() == rhs.id()); \ - this->assign_new_unique_ids_recursive(); \ - assert(this->id().valid() && this->id() != rhs.id()); \ - return *this; \ - } - -// Material, which may be shared across multiple ModelObjects of a single Model. -class ModelMaterial final : public ObjectBase -{ -public: - // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. - t_model_material_attributes attributes; - // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. - ModelConfigObject config; - - Model* get_model() const { return m_model; } - void apply(const t_model_material_attributes &attributes) - { this->attributes.insert(attributes.begin(), attributes.end()); } - -private: - // Parent, owning this material. - Model *m_model; - - // To be accessed by the Model. - friend class Model; - // Constructor, which assigns a new unique ID to the material and to its config. - ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); } - // Copy constructor copies the IDs of the ModelMaterial and its config, and m_model! - ModelMaterial(const ModelMaterial &rhs) = default; - void set_model(Model *model) { m_model = model; } - void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } - - // To be accessed by the serialization and Undo/Redo code. - friend class cereal::access; - friend class UndoRedo::StackImpl; - // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config. - ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } - template void serialize(Archive &ar) { - assert(this->id().invalid()); assert(this->config.id().invalid()); - Internal::StaticSerializationWrapper config_wrapper(config); - ar(attributes, config_wrapper); - // assert(this->id().valid()); assert(this->config.id().valid()); - } - - // Disabled methods. - ModelMaterial(ModelMaterial &&rhs) = delete; - ModelMaterial& operator=(const ModelMaterial &rhs) = delete; - ModelMaterial& operator=(ModelMaterial &&rhs) = delete; -}; - -class LayerHeightProfile final : public ObjectWithTimestamp { -public: - // Assign the content if the timestamp differs, don't assign an ObjectID. - void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } - void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } - - std::vector get() const throw() { return m_data; } - bool empty() const throw() { return m_data.empty(); } - void set(const std::vector &data) { if (m_data != data) { m_data = data; this->touch(); } } - void set(std::vector &&data) { if (m_data != data) { m_data = std::move(data); this->touch(); } } - void clear() { m_data.clear(); this->touch(); } - - template void serialize(Archive &ar) - { - ar(cereal::base_class(this), m_data); - } - -private: - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - explicit LayerHeightProfile() = default; - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - explicit LayerHeightProfile(int) : ObjectWithTimestamp(-1) {} - // Copy constructor copies the ID. - explicit LayerHeightProfile(const LayerHeightProfile &rhs) = default; - // Move constructor copies the ID. - explicit LayerHeightProfile(LayerHeightProfile &&rhs) = default; - - // called by ModelObject::assign_copy() - LayerHeightProfile& operator=(const LayerHeightProfile &rhs) = default; - LayerHeightProfile& operator=(LayerHeightProfile &&rhs) = default; - - std::vector m_data; - - // to access set_new_unique_id() when copy / pasting an object - friend class ModelObject; -}; - -// Declared outside of ModelVolume, so it could be forward declared. -enum class ModelVolumeType : int { - INVALID = -1, - MODEL_PART = 0, - NEGATIVE_VOLUME, - PARAMETER_MODIFIER, - SUPPORT_BLOCKER, - SUPPORT_ENFORCER, -}; - -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower }; -using ModelObjectCutAttributes = enum_bitmask; -ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); - -// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), -// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. -// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, -// different rotation and different uniform scaling. -class ModelObject final : public ObjectBase -{ -public: - std::string name; - std::string input_file; // XXX: consider fs::path - // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling. - // Instances are owned by this ModelObject. - ModelInstancePtrs instances; - // Printable and modifier volumes, each with its material ID and a set of override parameters. - // ModelVolumes are owned by this ModelObject. - ModelVolumePtrs volumes; - // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. - ModelConfigObject config; - // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides. - t_layer_config_ranges layer_config_ranges; - // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. - // The pairs of are packed into a 1D array. - LayerHeightProfile layer_height_profile; - // Whether or not this object is printable - bool printable; - - // This vector holds position of selected support points for SLA. The data are - // saved in mesh coordinates to allow using them for several instances. - // The format is (x, y, z, point_size, supports_island) - sla::SupportPoints sla_support_points; - // To keep track of where the points came from (used for synchronization between - // the SLA gizmo and the backend). - sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints; - - // Holes to be drilled into the object so resin can flow out - sla::DrainHoles sla_drain_holes; - - /* This vector accumulates the total translation applied to the object by the - center_around_origin() method. Callers might want to apply the same translation - to new volumes before adding them to this object in order to preserve alignment - when user expects that. */ - Vec3d origin_translation; - - Model* get_model() { return m_model; } - const Model* get_model() const { return m_model; } - - ModelVolume* add_volume(const TriangleMesh &mesh); - ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART); - ModelVolume* add_volume(const ModelVolume &volume, ModelVolumeType type = ModelVolumeType::INVALID); - ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh); - void delete_volume(size_t idx); - void clear_volumes(); - void sort_volumes(bool full_sort); - bool is_multiparts() const { return volumes.size() > 1; } - // Checks if any of object volume is painted using the fdm support painting gizmo. - bool is_fdm_support_painted() const; - // Checks if any of object volume is painted using the seam painting gizmo. - bool is_seam_painted() const; - // Checks if any of object volume is painted using the multi-material painting gizmo. - bool is_mm_painted() const; - - ModelInstance* add_instance(); - ModelInstance* add_instance(const ModelInstance &instance); - ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror); - void delete_instance(size_t idx); - void delete_last_instance(); - void clear_instances(); - - // Returns the bounding box of the transformed instances. - // This bounding box is approximate and not snug. - // This bounding box is being cached. - const BoundingBoxf3& bounding_box() const; - void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } - - // A mesh containing all transformed instances of this object. - TriangleMesh mesh() const; - // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. - // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater. - TriangleMesh raw_mesh() const; - // The same as above, but producing a lightweight indexed_triangle_set. - indexed_triangle_set raw_indexed_triangle_set() const; - // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. - // This bounding box is only used for the actual slicing. - const BoundingBoxf3& raw_bounding_box() const; - // A snug bounding box around the transformed non-modifier object volumes. - BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; - // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. - const BoundingBoxf3& raw_mesh_bounding_box() const; - // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. - BoundingBoxf3 full_raw_mesh_bounding_box() const; - - // Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane. - // This method is cheap in that it does not make any unnecessary copy of the volume meshes. - // This method is used by the auto arrange function. - Polygon convex_hull_2d(const Transform3d &trafo_instance) const; - - void center_around_origin(bool include_modifiers = true); - void ensure_on_bed(bool allow_negative_z = false); - - void translate_instances(const Vec3d& vector); - void translate_instance(size_t instance_idx, const Vec3d& vector); - void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); } - void translate(double x, double y, double z); - void scale(const Vec3d &versor); - void scale(const double s) { this->scale(Vec3d(s, s, s)); } - void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); } - /// Scale the current ModelObject to fit by altering the scaling factor of ModelInstances. - /// It operates on the total size by duplicating the object according to all the instances. - /// \param size Sizef3 the size vector - void scale_to_fit(const Vec3d &size); - void rotate(double angle, Axis axis); - void rotate(double angle, const Vec3d& axis); - void mirror(Axis axis); - - // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_mesh_after_creation(const float scale); - void convert_units(ModelObjectPtrs&new_objects, ConversionType conv_type, std::vector volume_idxs); - - size_t materials_count() const; - size_t facets_count() const; - size_t parts_count() const; - ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); - void split(ModelObjectPtrs* new_objects); - void merge(); - // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, - // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. - // This situation is solved by baking in the instance transformation into the mesh vertices. - // Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well. - void bake_xy_rotation_into_meshes(size_t instance_idx); - - double get_min_z() const; - double get_max_z() const; - double get_instance_min_z(size_t instance_idx) const; - double get_instance_max_z(size_t instance_idx) const; - - // Print object statistics to console. - void print_info() const; - - std::string get_export_filename() const; - - // Get full stl statistics for all object's meshes - TriangleMeshStats get_object_stl_stats() const; - // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) - int get_repaired_errors_count(const int vol_idx = -1) const; - -private: - friend class Model; - // This constructor assigns new ID to this ModelObject and its config. - explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()), - m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) - { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - } - explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) - { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->layer_height_profile.id().invalid()); - } - ~ModelObject(); - void assign_new_unique_ids_recursive() override; - - // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" - // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). - ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(rhs.m_model) { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->layer_height_profile.id().invalid()); - assert(rhs.id() != rhs.config.id()); - assert(rhs.id() != rhs.layer_height_profile.id()); - this->assign_copy(rhs); - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->layer_height_profile.id()); - assert(this->id() == rhs.id()); - assert(this->config.id() == rhs.config.id()); - assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); - } - explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1) { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->layer_height_profile.id().invalid()); - assert(rhs.id() != rhs.config.id()); - assert(rhs.id() != rhs.layer_height_profile.id()); - this->assign_copy(std::move(rhs)); - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->layer_height_profile.id()); - assert(this->id() == rhs.id()); - assert(this->config.id() == rhs.config.id()); - assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); - } - ModelObject& operator=(const ModelObject &rhs) { - this->assign_copy(rhs); - m_model = rhs.m_model; - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->layer_height_profile.id()); - assert(this->id() == rhs.id()); - assert(this->config.id() == rhs.config.id()); - assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); - return *this; - } - ModelObject& operator=(ModelObject &&rhs) { - this->assign_copy(std::move(rhs)); - m_model = rhs.m_model; - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->layer_height_profile.id()); - assert(this->id() == rhs.id()); - assert(this->config.id() == rhs.config.id()); - assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); - return *this; - } - void set_new_unique_id() { - ObjectBase::set_new_unique_id(); - this->config.set_new_unique_id(); - this->layer_height_profile.set_new_unique_id(); - } - - OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) - - // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. - Model *m_model = nullptr; - - // Bounding box, cached. - mutable BoundingBoxf3 m_bounding_box; - mutable bool m_bounding_box_valid; - mutable BoundingBoxf3 m_raw_bounding_box; - mutable bool m_raw_bounding_box_valid; - mutable BoundingBoxf3 m_raw_mesh_bounding_box; - mutable bool m_raw_mesh_bounding_box_valid; - - // Called by Print::apply() to set the model pointer after making a copy. - friend class Print; - friend class SLAPrint; - void set_model(Model *model) { m_model = model; } - - // Undo / Redo through the cereal serialization library - friend class cereal::access; - friend class UndoRedo::StackImpl; - // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config. - ModelObject() : - ObjectBase(-1), config(-1), layer_height_profile(-1), - m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->layer_height_profile.id().invalid()); - } - template void serialize(Archive &ar) { - ar(cereal::base_class(this)); - Internal::StaticSerializationWrapper config_wrapper(config); - Internal::StaticSerializationWrapper layer_heigth_profile_wrapper(layer_height_profile); - ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, - sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, - m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); - } - - // Called by Print::validate() from the UI thread. - unsigned int update_instances_print_volume_state(const BuildVolume &build_volume); -}; - -enum class EnforcerBlockerType : int8_t { - // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. - NONE = 0, - ENFORCER = 1, - BLOCKER = 2, - // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. - Extruder1 = ENFORCER, - Extruder2 = BLOCKER, - Extruder3, - Extruder4, - Extruder5, - Extruder6, - Extruder7, - Extruder8, - Extruder9, - Extruder10, - Extruder11, - Extruder12, - Extruder13, - Extruder14, - Extruder15, -}; - -enum class ConversionType : int { - CONV_TO_INCH, - CONV_FROM_INCH, - CONV_TO_METER, - CONV_FROM_METER, -}; - -class FacetsAnnotation final : public ObjectWithTimestamp { -public: - // Assign the content if the timestamp differs, don't assign an ObjectID. - void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } - void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } - const std::pair>, std::vector>& get_data() const throw() { return m_data; } - bool set(const TriangleSelector& selector); - indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; - bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - bool empty() const { return m_data.first.empty(); } - - // Following method clears the config and increases its timestamp, so the deleted - // state is considered changed from perspective of the undo/redo stack. - void reset(); - - // Serialize triangle into string, for serialization into 3MF/AMF. - std::string get_triangle_as_string(int i) const; - - // Before deserialization, reserve space for n_triangles. - void reserve(int n_triangles) { m_data.first.reserve(n_triangles); } - // Deserialize triangles one by one, with strictly increasing triangle_id. - void set_triangle_from_string(int triangle_id, const std::string& str); - // After deserializing the last triangle, shrink data to fit. - void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); } - -private: - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - explicit FacetsAnnotation() = default; - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {} - // Copy constructor copies the ID. - explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default; - // Move constructor copies the ID. - explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default; - - // called by ModelVolume::assign_copy() - FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default; - FacetsAnnotation& operator=(FacetsAnnotation &&rhs) = default; - - friend class cereal::access; - friend class UndoRedo::StackImpl; - - template void serialize(Archive &ar) - { - ar(cereal::base_class(this), m_data); - } - - std::pair>, std::vector> m_data; - - // To access set_new_unique_id() when copy / pasting a ModelVolume. - friend class ModelVolume; -}; - -// An object STL, or a modifier volume, over which a different set of parameters shall be applied. -// ModelVolume instances are owned by a ModelObject. -class ModelVolume final : public ObjectBase -{ -public: - std::string name; - // struct used by reload from disk command to recover data from disk - struct Source - { - std::string input_file; - int object_idx{ -1 }; - int volume_idx{ -1 }; - Vec3d mesh_offset{ Vec3d::Zero() }; - Geometry::Transformation transform; - bool is_converted_from_inches{ false }; - bool is_converted_from_meters{ false }; - bool is_from_builtin_objects{ false }; - - template void serialize(Archive& ar) { - //FIXME Vojtech: Serialize / deserialize only if the Source is set. - // likely testing input_file or object_idx would be sufficient. - ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects); - } - }; - Source source; - - // The triangular model. - const TriangleMesh& mesh() const { return *m_mesh.get(); } - void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } - void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } - void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared(mesh); } - void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } - void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } - void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } - void reset_mesh() { m_mesh = std::make_shared(); } - // Configuration parameters specific to an object model geometry or a modifier volume, - // overriding the global Slic3r settings and the ModelObject settings. - ModelConfigObject config; - - // List of mesh facets to be supported/unsupported. - FacetsAnnotation supported_facets; - - // List of seam enforcers/blockers. - FacetsAnnotation seam_facets; - - // List of mesh facets painted for MMU segmentation. - FacetsAnnotation mmu_segmentation_facets; - - // A parent object owning this modifier volume. - ModelObject* get_object() const { return this->object; } - ModelVolumeType type() const { return m_type; } - void set_type(const ModelVolumeType t) { m_type = t; } - bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; } - bool is_negative_volume() const { return m_type == ModelVolumeType::NEGATIVE_VOLUME; } - bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; } - bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; } - bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } - bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } - t_model_material_id material_id() const { return m_material_id; } - void set_material_id(t_model_material_id material_id); - ModelMaterial* material() const; - void set_material(t_model_material_id material_id, const ModelMaterial &material); - // Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config. - // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes). - int extruder_id() const; - - bool is_splittable() const; - - // Split this volume, append the result to the object owning this volume. - // Return the number of volumes created from this one. - // This is useful to assign different materials to different volumes of an object. - size_t split(unsigned int max_extruders); - void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); } - void translate(const Vec3d& displacement); - void scale(const Vec3d& scaling_factors); - void scale(double x, double y, double z) { scale(Vec3d(x, y, z)); } - void scale(double s) { scale(Vec3d(s, s, s)); } - void rotate(double angle, Axis axis); - void rotate(double angle, const Vec3d& axis); - void mirror(Axis axis); - - // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_geometry_after_creation(const Vec3f &versor); - void scale_geometry_after_creation(const float scale) { this->scale_geometry_after_creation(Vec3f(scale, scale, scale)); } - - // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. - // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! - void center_geometry_after_creation(bool update_source_offset = true); - - void calculate_convex_hull(); - const TriangleMesh& get_convex_hull() const; - const std::shared_ptr& get_convex_hull_shared_ptr() const { return m_convex_hull; } - // Get count of errors in the mesh - int get_repaired_errors_count() const; - - // Helpers for loading / storing into AMF / 3MF files. - static ModelVolumeType type_from_string(const std::string &s); - static std::string type_to_string(const ModelVolumeType t); - - const Geometry::Transformation& get_transformation() const { return m_transformation; } - void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } - void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } - - const Vec3d& get_offset() const { return m_transformation.get_offset(); } - double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } - - void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } - void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } - - const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } - double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } - - void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } - void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } - - Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } - double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } - - void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } - void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } - - const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } - double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } - bool is_left_handed() const { return m_transformation.is_left_handed(); } - - void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } - void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } - void convert_from_imperial_units(); - void convert_from_meters(); - - const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - - void set_new_unique_id() { - ObjectBase::set_new_unique_id(); - this->config.set_new_unique_id(); - this->supported_facets.set_new_unique_id(); - this->seam_facets.set_new_unique_id(); - this->mmu_segmentation_facets.set_new_unique_id(); - } - - bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } - bool is_seam_painted() const { return !this->seam_facets.empty(); } - bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } - -protected: - friend class Print; - friend class SLAPrint; - friend class Model; - friend class ModelObject; - friend void model_volume_list_update_supports(ModelObject& model_object_dst, const ModelObject& model_object_new); - - // Copies IDs of both the ModelVolume and its config. - explicit ModelVolume(const ModelVolume &rhs) = default; - void set_model_object(ModelObject *model_object) { object = model_object; } - void assign_new_unique_ids_recursive() override; - void transform_this_mesh(const Transform3d& t, bool fix_left_handed); - void transform_this_mesh(const Matrix3d& m, bool fix_left_handed); - -private: - // Parent object owning this ModelVolume. - ModelObject* object; - // The triangular model. - std::shared_ptr m_mesh; - // Is it an object to be printed, or a modifier volume? - ModelVolumeType m_type; - t_model_material_id m_material_id; - // The convex hull of this model's mesh. - std::shared_ptr m_convex_hull; - Geometry::Transformation m_transformation; - - // flag to optimize the checking if the volume is splittable - // -1 -> is unknown value (before first cheking) - // 0 -> is not splittable - // 1 -> is splittable - mutable int m_is_splittable{ -1 }; - - ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) : m_mesh(new TriangleMesh(mesh)), m_type(type), object(object) - { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->supported_facets.id().valid()); - assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->supported_facets.id()); - assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); - if (mesh.facets_count() > 1) - calculate_convex_hull(); - } - ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) : - m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->supported_facets.id().valid()); - assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->supported_facets.id()); - assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); - } - - // Copying an existing volume, therefore this volume will get a copy of the ID assigned. - ModelVolume(ModelObject *object, const ModelVolume &other) : - ObjectBase(other), - name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), - config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), - supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets) - { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->supported_facets.id().valid()); - assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->supported_facets.id()); - assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); - assert(this->id() == other.id()); - assert(this->config.id() == other.config.id()); - assert(this->supported_facets.id() == other.supported_facets.id()); - assert(this->seam_facets.id() == other.seam_facets.id()); - assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id()); - this->set_material_id(other.material_id()); - } - // Providing a new mesh, therefore this volume will get a new unique ID assigned. - ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) : - name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation) - { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->supported_facets.id().valid()); - assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->supported_facets.id()); - assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); - assert(this->id() != other.id()); - assert(this->config.id() == other.config.id()); - this->set_material_id(other.material_id()); - this->config.set_new_unique_id(); - if (m_mesh->facets_count() > 1) - calculate_convex_hull(); - assert(this->config.id().valid()); - assert(this->config.id() != other.config.id()); - assert(this->supported_facets.id() != other.supported_facets.id()); - assert(this->seam_facets.id() != other.seam_facets.id()); - assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id()); - assert(this->id() != this->config.id()); - assert(this->supported_facets.empty()); - assert(this->seam_facets.empty()); - assert(this->mmu_segmentation_facets.empty()); - } - - ModelVolume& operator=(ModelVolume &rhs) = delete; - - friend class cereal::access; - friend class UndoRedo::StackImpl; - // Used for deserialization, therefore no IDs are allocated. - ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->supported_facets.id().invalid()); - assert(this->seam_facets.id().invalid()); - assert(this->mmu_segmentation_facets.id().invalid()); - } - template void load(Archive &ar) { - bool has_convex_hull; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); - cereal::load_by_value(ar, supported_facets); - cereal::load_by_value(ar, seam_facets); - cereal::load_by_value(ar, mmu_segmentation_facets); - cereal::load_by_value(ar, config); - assert(m_mesh); - if (has_convex_hull) { - cereal::load_optional(ar, m_convex_hull); - if (! m_convex_hull && ! m_mesh->empty()) - // The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it. - this->calculate_convex_hull(); - } else - m_convex_hull.reset(); - } - template void save(Archive &ar) const { - bool has_convex_hull = m_convex_hull.get() != nullptr; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); - cereal::save_by_value(ar, supported_facets); - cereal::save_by_value(ar, seam_facets); - cereal::save_by_value(ar, mmu_segmentation_facets); - cereal::save_by_value(ar, config); - if (has_convex_hull) - cereal::save_optional(ar, m_convex_hull); - } -}; - -inline void model_volumes_sort_by_id(ModelVolumePtrs &model_volumes) -{ - std::sort(model_volumes.begin(), model_volumes.end(), [](const ModelVolume *l, const ModelVolume *r) { return l->id() < r->id(); }); -} - -inline const ModelVolume* model_volume_find_by_id(const ModelVolumePtrs &model_volumes, const ObjectID id) -{ - auto it = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [id](const ModelVolume *mv) { return mv->id() < id; }); - return it != model_volumes.end() && (*it)->id() == id ? *it : nullptr; -} - -enum ModelInstanceEPrintVolumeState : unsigned char -{ - ModelInstancePVS_Inside, - ModelInstancePVS_Partly_Outside, - ModelInstancePVS_Fully_Outside, - ModelInstanceNum_BedStates -}; - -// A single instance of a ModelObject. -// Knows the affine transformation of an object. -class ModelInstance final : public ObjectBase -{ -private: - Geometry::Transformation m_transformation; - -public: - // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) - ModelInstanceEPrintVolumeState print_volume_state; - // Whether or not this instance is printable - bool printable; - - ModelObject* get_object() const { return this->object; } - - const Geometry::Transformation& get_transformation() const { return m_transformation; } - void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } - - const Vec3d& get_offset() const { return m_transformation.get_offset(); } - double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } - - void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } - void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } - - const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } - double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } - - void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } - void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } - - const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } - double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } - - void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } - void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } - - const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } - double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } - bool is_left_handed() const { return m_transformation.is_left_handed(); } - - void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } - void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } - - // To be called on an external mesh - void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; - // Calculate a bounding box of a transformed mesh. To be called on an external mesh. - BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const; - // Transform an external bounding box. - BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; - // Transform an external vector. - Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const; - // To be called on an external polygon. It does not translate the polygon, only rotates and scales. - void transform_polygon(Polygon* polygon) const; - - const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - - bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } - - // Getting the input polygon for arrange - arrangement::ArrangePolygon get_arrange_polygon() const; - - // Apply the arrange result on the ModelInstance - void apply_arrange_result(const Vec2d& offs, double rotation) - { - // write the transformation data into the model instance - set_rotation(Z, rotation); - set_offset(X, unscale(offs(X))); - set_offset(Y, unscale(offs(Y))); - this->object->invalidate_bounding_box(); - } - -protected: - friend class Print; - friend class SLAPrint; - friend class Model; - friend class ModelObject; - - explicit ModelInstance(const ModelInstance &rhs) = default; - void set_model_object(ModelObject *model_object) { object = model_object; } - -private: - // Parent object, owning this instance. - ModelObject* object; - - // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); } - // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); } - - explicit ModelInstance(ModelInstance &&rhs) = delete; - ModelInstance& operator=(const ModelInstance &rhs) = delete; - ModelInstance& operator=(ModelInstance &&rhs) = delete; - - friend class cereal::access; - friend class UndoRedo::StackImpl; - // Used for deserialization, therefore no IDs are allocated. - ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } - template void serialize(Archive &ar) { - ar(m_transformation, print_volume_state, printable); - } -}; - - -class ModelWipeTower final : public ObjectBase -{ -public: - Vec2d position; - double rotation; - -private: - friend class cereal::access; - friend class UndoRedo::StackImpl; - friend class Model; - - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - explicit ModelWipeTower() {} - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - explicit ModelWipeTower(int) : ObjectBase(-1) {} - // Copy constructor copies the ID. - explicit ModelWipeTower(const ModelWipeTower &cfg) = default; - - // Disabled methods. - ModelWipeTower(ModelWipeTower &&rhs) = delete; - ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete; - ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete; - - // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object. - template void serialize(Archive &ar) { ar(position, rotation); } -}; - -// The print bed content. -// Description of a triangular model with multiple materials, multiple instances with various affine transformations -// and with multiple modifier meshes. -// A model groups multiple objects, each object having possibly multiple instances, -// all objects may share mutliple materials. -class Model final : public ObjectBase -{ -public: - // Materials are owned by a model and referenced by objects through t_model_material_id. - // Single material may be shared by multiple models. - ModelMaterialMap materials; - // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). - ModelObjectPtrs objects; - // Wipe tower object. - ModelWipeTower wipe_tower; - - // Extensions for color print - CustomGCode::Info custom_gcode_per_print_z; - - // Default constructor assigns a new ID to the model. - Model() { assert(this->id().valid()); } - ~Model() { this->clear_objects(); this->clear_materials(); } - - /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ - /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); } - explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); } - Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } - Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } - - OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) - - enum class LoadAttribute : int { - AddDefaultInstances, - CheckVersion - }; - using LoadAttributes = enum_bitmask; - - static Model read_from_file( - const std::string& input_file, - DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr, - LoadAttributes options = LoadAttribute::AddDefaultInstances); - static Model read_from_archive( - const std::string& input_file, - DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, - LoadAttributes options = LoadAttribute::AddDefaultInstances); - - // Add a new ModelObject to this Model, generate a new ID for this ModelObject. - ModelObject* add_object(); - ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); - ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); - ModelObject* add_object(const ModelObject &other); - void delete_object(size_t idx); - bool delete_object(ObjectID id); - bool delete_object(ModelObject* object); - void clear_objects(); - - ModelMaterial* add_material(t_model_material_id material_id); - ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other); - ModelMaterial* get_material(t_model_material_id material_id) { - ModelMaterialMap::iterator i = this->materials.find(material_id); - return (i == this->materials.end()) ? nullptr : i->second; - } - - void delete_material(t_model_material_id material_id); - void clear_materials(); - bool add_default_instances(); - // Returns approximate axis aligned bounding box of this model - BoundingBoxf3 bounding_box() const; - // Set the print_volume_state of PrintObject::instances, - // return total number of printable objects. - unsigned int update_print_volume_state(const BuildVolume &build_volume); - // Returns true if any ModelObject was modified. - bool center_instances_around_point(const Vec2d &point); - void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } - TriangleMesh mesh() const; - - // Croaks if the duplicated objects do not fit the print bed. - void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); - - bool looks_like_multipart_object() const; - void convert_multipart_object(unsigned int max_extruders); - bool looks_like_imperial_units() const; - void convert_from_imperial_units(bool only_small_volumes); - bool looks_like_saved_in_meters() const; - void convert_from_meters(bool only_small_volumes); - int removed_objects_with_zero_volume(); - - // Ensures that the min z of the model is not negative - void adjust_min_z(); - - void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } - - // Propose an output file name & path based on the first printable object's name and source input file's path. - std::string propose_export_file_name_and_path() const; - // Propose an output path, replace extension. The new_extension shall contain the initial dot. - std::string propose_export_file_name_and_path(const std::string &new_extension) const; - - // Checks if any of objects is painted using the fdm support painting gizmo. - bool is_fdm_support_painted() const; - // Checks if any of objects is painted using the seam painting gizmo. - bool is_seam_painted() const; - // Checks if any of objects is painted using the multi-material painting gizmo. - bool is_mm_painted() const; - -private: - explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); } - void assign_new_unique_ids_recursive(); - void update_links_bottom_up_recursive(); - - friend class cereal::access; - friend class UndoRedo::StackImpl; - template void serialize(Archive &ar) { - Internal::StaticSerializationWrapper wipe_tower_wrapper(wipe_tower); - ar(materials, objects, wipe_tower_wrapper); - } -}; - -ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute) - -#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE -#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE - -// Test whether the two models contain the same number of ModelObjects with the same set of IDs -// ordered in the same order. In that case it is not necessary to kill the background processing. -bool model_object_list_equal(const Model &model_old, const Model &model_new); - -// Test whether the new model is just an extension of the old model (new objects were added -// to the end of the original list. In that case it is not necessary to kill the background processing. -bool model_object_list_extended(const Model &model_old, const Model &model_new); - -// Test whether the new ModelObject contains a different set of volumes (or sorted in a different order) -// than the old ModelObject. -bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type); -bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const std::initializer_list &types); - -// Test whether the now ModelObject has newer custom supports data than the old one. -// The function assumes that volumes list is synchronized. -bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new); - -// Test whether the now ModelObject has newer custom seam data than the old one. -// The function assumes that volumes list is synchronized. -bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new); - -// Test whether the now ModelObject has newer MMU segmentation data than the old one. -// The function assumes that volumes list is synchronized. -extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new); - -// If the model has multi-part objects, then it is currently not supported by the SLA mode. -// Either the model cannot be loaded, or a SLA printer has to be activated. -bool model_has_multi_part_objects(const Model &model); -// If the model has advanced features, then it cannot be processed in simple mode. -bool model_has_advanced_features(const Model &model); - -#ifndef NDEBUG -// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. -void check_model_ids_validity(const Model &model); -void check_model_ids_equal(const Model &model1, const Model &model2); -#endif /* NDEBUG */ - -static const float SINKING_Z_THRESHOLD = -0.001f; -static const double SINKING_MIN_Z_THRESHOLD = 0.05; - -} // namespace Slic3r - -namespace cereal -{ - template struct specialize {}; - template struct specialize {}; -} - -#endif /* slic3r_Model_hpp_ */ +#ifndef slic3r_Model_hpp_ +#define slic3r_Model_hpp_ + +#include "libslic3r.h" +#include "enum_bitmask.hpp" +#include "Geometry.hpp" +#include "ObjectID.hpp" +#include "Point.hpp" +#include "PrintConfig.hpp" +#include "Slicing.hpp" +#include "SLA/SupportPoint.hpp" +#include "SLA/Hollowing.hpp" +#include "TriangleMesh.hpp" +#include "Arrange.hpp" +#include "CustomGCode.hpp" +#include "enum_bitmask.hpp" + +#include +#include +#include +#include +#include + +namespace cereal { + class BinaryInputArchive; + class BinaryOutputArchive; + template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr); + template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr); + template void load_by_value(BinaryInputArchive &ar, T &obj); + template void save_by_value(BinaryOutputArchive &ar, const T &obj); +} + +namespace Slic3r { +enum class ConversionType; + +class BuildVolume; +class Model; +class ModelInstance; +class ModelMaterial; +class ModelObject; +class ModelVolume; +class ModelWipeTower; +class Print; +class SLAPrint; +class TriangleSelector; + +namespace UndoRedo { + class StackImpl; +} + +class ModelConfigObject : public ObjectBase, public ModelConfig +{ +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + friend class ModelObject; + friend class ModelVolume; + friend class ModelMaterial; + + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit ModelConfigObject() = default; + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit ModelConfigObject(int) : ObjectBase(-1) {} + // Copy constructor copies the ID. + explicit ModelConfigObject(const ModelConfigObject &cfg) = default; + // Move constructor copies the ID. + explicit ModelConfigObject(ModelConfigObject &&cfg) = default; + + Timestamp timestamp() const throw() override { return this->ModelConfig::timestamp(); } + bool object_id_and_timestamp_match(const ModelConfigObject &rhs) const throw() { return this->id() == rhs.id() && this->timestamp() == rhs.timestamp(); } + + // called by ModelObject::assign_copy() + ModelConfigObject& operator=(const ModelConfigObject &rhs) = default; + ModelConfigObject& operator=(ModelConfigObject &&rhs) = default; + + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + } +}; + +namespace Internal { + template + class StaticSerializationWrapper + { + public: + StaticSerializationWrapper(T &wrap) : wrapped(wrap) {} + private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + template void load(Archive &ar) { cereal::load_by_value(ar, wrapped); } + template void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); } + T& wrapped; + }; +} + +typedef std::string t_model_material_id; +typedef std::string t_model_material_attribute; +typedef std::map t_model_material_attributes; + +typedef std::map ModelMaterialMap; +typedef std::vector ModelObjectPtrs; +typedef std::vector ModelVolumePtrs; +typedef std::vector ModelInstancePtrs; + +#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ + /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ + /* to make a private copy for background processing. */ \ + static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \ + static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \ + TYPE& assign_copy(const TYPE &rhs); \ + TYPE& assign_copy(TYPE &&rhs); \ + /* Copy a TYPE, generate new IDs. The front end will use this call. */ \ + static TYPE* new_clone(const TYPE &rhs) { \ + /* Default constructor assigning an invalid ID. */ \ + auto obj = new TYPE(-1); \ + obj->assign_clone(rhs); \ + assert(obj->id().valid() && obj->id() != rhs.id()); \ + return obj; \ + } \ + TYPE make_clone(const TYPE &rhs) { \ + /* Default constructor assigning an invalid ID. */ \ + TYPE obj(-1); \ + obj.assign_clone(rhs); \ + assert(obj.id().valid() && obj.id() != rhs.id()); \ + return obj; \ + } \ + TYPE& assign_clone(const TYPE &rhs) { \ + this->assign_copy(rhs); \ + assert(this->id().valid() && this->id() == rhs.id()); \ + this->assign_new_unique_ids_recursive(); \ + assert(this->id().valid() && this->id() != rhs.id()); \ + return *this; \ + } + +// Material, which may be shared across multiple ModelObjects of a single Model. +class ModelMaterial final : public ObjectBase +{ +public: + // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. + t_model_material_attributes attributes; + // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. + ModelConfigObject config; + + Model* get_model() const { return m_model; } + void apply(const t_model_material_attributes &attributes) + { this->attributes.insert(attributes.begin(), attributes.end()); } + +private: + // Parent, owning this material. + Model *m_model; + + // To be accessed by the Model. + friend class Model; + // Constructor, which assigns a new unique ID to the material and to its config. + ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); } + // Copy constructor copies the IDs of the ModelMaterial and its config, and m_model! + ModelMaterial(const ModelMaterial &rhs) = default; + void set_model(Model *model) { m_model = model; } + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } + + // To be accessed by the serialization and Undo/Redo code. + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config. + ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } + template void serialize(Archive &ar) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + Internal::StaticSerializationWrapper config_wrapper(config); + ar(attributes, config_wrapper); + // assert(this->id().valid()); assert(this->config.id().valid()); + } + + // Disabled methods. + ModelMaterial(ModelMaterial &&rhs) = delete; + ModelMaterial& operator=(const ModelMaterial &rhs) = delete; + ModelMaterial& operator=(ModelMaterial &&rhs) = delete; +}; + +class LayerHeightProfile final : public ObjectWithTimestamp { +public: + // Assign the content if the timestamp differs, don't assign an ObjectID. + void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } + void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } + + std::vector get() const throw() { return m_data; } + bool empty() const throw() { return m_data.empty(); } + void set(const std::vector &data) { if (m_data != data) { m_data = data; this->touch(); } } + void set(std::vector &&data) { if (m_data != data) { m_data = std::move(data); this->touch(); } } + void clear() { m_data.clear(); this->touch(); } + + template void serialize(Archive &ar) + { + ar(cereal::base_class(this), m_data); + } + +private: + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit LayerHeightProfile() = default; + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit LayerHeightProfile(int) : ObjectWithTimestamp(-1) {} + // Copy constructor copies the ID. + explicit LayerHeightProfile(const LayerHeightProfile &rhs) = default; + // Move constructor copies the ID. + explicit LayerHeightProfile(LayerHeightProfile &&rhs) = default; + + // called by ModelObject::assign_copy() + LayerHeightProfile& operator=(const LayerHeightProfile &rhs) = default; + LayerHeightProfile& operator=(LayerHeightProfile &&rhs) = default; + + std::vector m_data; + + // to access set_new_unique_id() when copy / pasting an object + friend class ModelObject; +}; + +// Declared outside of ModelVolume, so it could be forward declared. +enum class ModelVolumeType : int { + INVALID = -1, + MODEL_PART = 0, + NEGATIVE_VOLUME, + PARAMETER_MODIFIER, + SUPPORT_BLOCKER, + SUPPORT_ENFORCER, +}; + +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower }; +using ModelObjectCutAttributes = enum_bitmask; +ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); + +// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), +// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. +// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, +// different rotation and different uniform scaling. +class ModelObject final : public ObjectBase +{ +public: + std::string name; + std::string input_file; // XXX: consider fs::path + // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling. + // Instances are owned by this ModelObject. + ModelInstancePtrs instances; + // Printable and modifier volumes, each with its material ID and a set of override parameters. + // ModelVolumes are owned by this ModelObject. + ModelVolumePtrs volumes; + // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. + ModelConfigObject config; + // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides. + t_layer_config_ranges layer_config_ranges; + // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. + // The pairs of are packed into a 1D array. + LayerHeightProfile layer_height_profile; + // Whether or not this object is printable + bool printable; + + // This vector holds position of selected support points for SLA. The data are + // saved in mesh coordinates to allow using them for several instances. + // The format is (x, y, z, point_size, supports_island) + sla::SupportPoints sla_support_points; + // To keep track of where the points came from (used for synchronization between + // the SLA gizmo and the backend). + sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints; + + // Holes to be drilled into the object so resin can flow out + sla::DrainHoles sla_drain_holes; + + /* This vector accumulates the total translation applied to the object by the + center_around_origin() method. Callers might want to apply the same translation + to new volumes before adding them to this object in order to preserve alignment + when user expects that. */ + Vec3d origin_translation; + + Model* get_model() { return m_model; } + const Model* get_model() const { return m_model; } + + ModelVolume* add_volume(const TriangleMesh &mesh); + ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART); + ModelVolume* add_volume(const ModelVolume &volume, ModelVolumeType type = ModelVolumeType::INVALID); + ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh); + void delete_volume(size_t idx); + void clear_volumes(); + void sort_volumes(bool full_sort); + bool is_multiparts() const { return volumes.size() > 1; } + // Checks if any of object volume is painted using the fdm support painting gizmo. + bool is_fdm_support_painted() const; + // Checks if any of object volume is painted using the seam painting gizmo. + bool is_seam_painted() const; + // Checks if any of object volume is painted using the multi-material painting gizmo. + bool is_mm_painted() const; + + ModelInstance* add_instance(); + ModelInstance* add_instance(const ModelInstance &instance); + ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror); + void delete_instance(size_t idx); + void delete_last_instance(); + void clear_instances(); + + // Returns the bounding box of the transformed instances. + // This bounding box is approximate and not snug. + // This bounding box is being cached. + const BoundingBoxf3& bounding_box() const; + void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } + + // A mesh containing all transformed instances of this object. + TriangleMesh mesh() const; + // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. + // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater. + TriangleMesh raw_mesh() const; + // The same as above, but producing a lightweight indexed_triangle_set. + indexed_triangle_set raw_indexed_triangle_set() const; + // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. + // This bounding box is only used for the actual slicing. + const BoundingBoxf3& raw_bounding_box() const; + // A snug bounding box around the transformed non-modifier object volumes. + BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; + // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. + const BoundingBoxf3& raw_mesh_bounding_box() const; + // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. + BoundingBoxf3 full_raw_mesh_bounding_box() const; + + // Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane. + // This method is cheap in that it does not make any unnecessary copy of the volume meshes. + // This method is used by the auto arrange function. + Polygon convex_hull_2d(const Transform3d &trafo_instance) const; + + void center_around_origin(bool include_modifiers = true); + void ensure_on_bed(bool allow_negative_z = false); + + void translate_instances(const Vec3d& vector); + void translate_instance(size_t instance_idx, const Vec3d& vector); + void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); } + void translate(double x, double y, double z); + void scale(const Vec3d &versor); + void scale(const double s) { this->scale(Vec3d(s, s, s)); } + void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); } + /// Scale the current ModelObject to fit by altering the scaling factor of ModelInstances. + /// It operates on the total size by duplicating the object according to all the instances. + /// \param size Sizef3 the size vector + void scale_to_fit(const Vec3d &size); + void rotate(double angle, Axis axis); + void rotate(double angle, const Vec3d& axis); + void mirror(Axis axis); + + // This method could only be called before the meshes of this ModelVolumes are not shared! + void scale_mesh_after_creation(const float scale); + void convert_units(ModelObjectPtrs&new_objects, ConversionType conv_type, std::vector volume_idxs); + + size_t materials_count() const; + size_t facets_count() const; + size_t parts_count() const; + ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); + void split(ModelObjectPtrs* new_objects); + void merge(); + // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, + // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. + // This situation is solved by baking in the instance transformation into the mesh vertices. + // Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well. + void bake_xy_rotation_into_meshes(size_t instance_idx); + + double get_min_z() const; + double get_max_z() const; + double get_instance_min_z(size_t instance_idx) const; + double get_instance_max_z(size_t instance_idx) const; + + // Print object statistics to console. + void print_info() const; + + std::string get_export_filename() const; + + // Get full stl statistics for all object's meshes + TriangleMeshStats get_object_stl_stats() const; + // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) + int get_repaired_errors_count(const int vol_idx = -1) const; + +private: + friend class Model; + // This constructor assigns new ID to this ModelObject and its config. + explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()), + m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + } + explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->layer_height_profile.id().invalid()); + } + ~ModelObject(); + void assign_new_unique_ids_recursive() override; + + // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" + // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). + ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(rhs.m_model) { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->layer_height_profile.id().invalid()); + assert(rhs.id() != rhs.config.id()); + assert(rhs.id() != rhs.layer_height_profile.id()); + this->assign_copy(rhs); + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->layer_height_profile.id()); + assert(this->id() == rhs.id()); + assert(this->config.id() == rhs.config.id()); + assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); + } + explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1) { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->layer_height_profile.id().invalid()); + assert(rhs.id() != rhs.config.id()); + assert(rhs.id() != rhs.layer_height_profile.id()); + this->assign_copy(std::move(rhs)); + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->layer_height_profile.id()); + assert(this->id() == rhs.id()); + assert(this->config.id() == rhs.config.id()); + assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); + } + ModelObject& operator=(const ModelObject &rhs) { + this->assign_copy(rhs); + m_model = rhs.m_model; + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->layer_height_profile.id()); + assert(this->id() == rhs.id()); + assert(this->config.id() == rhs.config.id()); + assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); + return *this; + } + ModelObject& operator=(ModelObject &&rhs) { + this->assign_copy(std::move(rhs)); + m_model = rhs.m_model; + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->layer_height_profile.id()); + assert(this->id() == rhs.id()); + assert(this->config.id() == rhs.config.id()); + assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); + return *this; + } + void set_new_unique_id() { + ObjectBase::set_new_unique_id(); + this->config.set_new_unique_id(); + this->layer_height_profile.set_new_unique_id(); + } + + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) + + // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. + Model *m_model = nullptr; + + // Bounding box, cached. + mutable BoundingBoxf3 m_bounding_box; + mutable bool m_bounding_box_valid; + mutable BoundingBoxf3 m_raw_bounding_box; + mutable bool m_raw_bounding_box_valid; + mutable BoundingBoxf3 m_raw_mesh_bounding_box; + mutable bool m_raw_mesh_bounding_box_valid; + + // Called by Print::apply() to set the model pointer after making a copy. + friend class Print; + friend class SLAPrint; + void set_model(Model *model) { m_model = model; } + + // Undo / Redo through the cereal serialization library + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config. + ModelObject() : + ObjectBase(-1), config(-1), layer_height_profile(-1), + m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->layer_height_profile.id().invalid()); + } + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + Internal::StaticSerializationWrapper config_wrapper(config); + Internal::StaticSerializationWrapper layer_heigth_profile_wrapper(layer_height_profile); + ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, + sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, + m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); + } + + // Called by Print::validate() from the UI thread. + unsigned int update_instances_print_volume_state(const BuildVolume &build_volume); +}; + +enum class EnforcerBlockerType : int8_t { + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. + NONE = 0, + ENFORCER = 1, + BLOCKER = 2, + // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. + Extruder1 = ENFORCER, + Extruder2 = BLOCKER, + Extruder3, + Extruder4, + Extruder5, + Extruder6, + Extruder7, + Extruder8, + Extruder9, + Extruder10, + Extruder11, + Extruder12, + Extruder13, + Extruder14, + Extruder15, +}; + +enum class ConversionType : int { + CONV_TO_INCH, + CONV_FROM_INCH, + CONV_TO_METER, + CONV_FROM_METER, +}; + +class FacetsAnnotation final : public ObjectWithTimestamp { +public: + // Assign the content if the timestamp differs, don't assign an ObjectID. + void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } + void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } + const std::pair>, std::vector>& get_data() const throw() { return m_data; } + bool set(const TriangleSelector& selector); + indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; + indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; + bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; + bool empty() const { return m_data.first.empty(); } + + // Following method clears the config and increases its timestamp, so the deleted + // state is considered changed from perspective of the undo/redo stack. + void reset(); + + // Serialize triangle into string, for serialization into 3MF/AMF. + std::string get_triangle_as_string(int i) const; + + // Before deserialization, reserve space for n_triangles. + void reserve(int n_triangles) { m_data.first.reserve(n_triangles); } + // Deserialize triangles one by one, with strictly increasing triangle_id. + void set_triangle_from_string(int triangle_id, const std::string& str); + // After deserializing the last triangle, shrink data to fit. + void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); } + +private: + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit FacetsAnnotation() = default; + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {} + // Copy constructor copies the ID. + explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default; + // Move constructor copies the ID. + explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default; + + // called by ModelVolume::assign_copy() + FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default; + FacetsAnnotation& operator=(FacetsAnnotation &&rhs) = default; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + + template void serialize(Archive &ar) + { + ar(cereal::base_class(this), m_data); + } + + std::pair>, std::vector> m_data; + + // To access set_new_unique_id() when copy / pasting a ModelVolume. + friend class ModelVolume; +}; + +// An object STL, or a modifier volume, over which a different set of parameters shall be applied. +// ModelVolume instances are owned by a ModelObject. +class ModelVolume final : public ObjectBase +{ +public: + std::string name; + // struct used by reload from disk command to recover data from disk + struct Source + { + std::string input_file; + int object_idx{ -1 }; + int volume_idx{ -1 }; + Vec3d mesh_offset{ Vec3d::Zero() }; + Geometry::Transformation transform; + bool is_converted_from_inches{ false }; + bool is_converted_from_meters{ false }; + bool is_from_builtin_objects{ false }; + + template void serialize(Archive& ar) { + //FIXME Vojtech: Serialize / deserialize only if the Source is set. + // likely testing input_file or object_idx would be sufficient. + ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects); + } + }; + Source source; + + // The triangular model. + const TriangleMesh& mesh() const { return *m_mesh.get(); } + void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } + void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } + void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared(mesh); } + void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } + void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } + void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } + void reset_mesh() { m_mesh = std::make_shared(); } + // Configuration parameters specific to an object model geometry or a modifier volume, + // overriding the global Slic3r settings and the ModelObject settings. + ModelConfigObject config; + + // List of mesh facets to be supported/unsupported. + FacetsAnnotation supported_facets; + + // List of seam enforcers/blockers. + FacetsAnnotation seam_facets; + + // List of mesh facets painted for MMU segmentation. + FacetsAnnotation mmu_segmentation_facets; + + // A parent object owning this modifier volume. + ModelObject* get_object() const { return this->object; } + ModelVolumeType type() const { return m_type; } + void set_type(const ModelVolumeType t) { m_type = t; } + bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; } + bool is_negative_volume() const { return m_type == ModelVolumeType::NEGATIVE_VOLUME; } + bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; } + bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; } + bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } + bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } + t_model_material_id material_id() const { return m_material_id; } + void set_material_id(t_model_material_id material_id); + ModelMaterial* material() const; + void set_material(t_model_material_id material_id, const ModelMaterial &material); + // Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config. + // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes). + int extruder_id() const; + + bool is_splittable() const; + + // Split this volume, append the result to the object owning this volume. + // Return the number of volumes created from this one. + // This is useful to assign different materials to different volumes of an object. + size_t split(unsigned int max_extruders); + void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); } + void translate(const Vec3d& displacement); + void scale(const Vec3d& scaling_factors); + void scale(double x, double y, double z) { scale(Vec3d(x, y, z)); } + void scale(double s) { scale(Vec3d(s, s, s)); } + void rotate(double angle, Axis axis); + void rotate(double angle, const Vec3d& axis); + void mirror(Axis axis); + + // This method could only be called before the meshes of this ModelVolumes are not shared! + void scale_geometry_after_creation(const Vec3f &versor); + void scale_geometry_after_creation(const float scale) { this->scale_geometry_after_creation(Vec3f(scale, scale, scale)); } + + // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. + // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! + void center_geometry_after_creation(bool update_source_offset = true); + + void calculate_convex_hull(); + const TriangleMesh& get_convex_hull() const; + const std::shared_ptr& get_convex_hull_shared_ptr() const { return m_convex_hull; } + // Get count of errors in the mesh + int get_repaired_errors_count() const; + + // Helpers for loading / storing into AMF / 3MF files. + static ModelVolumeType type_from_string(const std::string &s); + static std::string type_to_string(const ModelVolumeType t); + + const Geometry::Transformation& get_transformation() const { return m_transformation; } + void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_transformation(const Transform3d& trafo) { m_transformation = trafo; } +#else + void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_offset() const { return m_transformation.get_offset(); } +#else + const Vec3d& get_offset() const { return m_transformation.get_offset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } + + void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } + void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_rotation() const { return m_transformation.get_rotation(); } +#else + const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } + + void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } + void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } + + Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } + double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } + + void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } + void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_mirror() const { return m_transformation.get_mirror(); } +#else + const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } + bool is_left_handed() const { return m_transformation.is_left_handed(); } + + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } + void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } + void convert_from_imperial_units(); + void convert_from_meters(); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d& get_matrix() const { return m_transformation.get_matrix(); } + Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); } +#else + const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + void set_new_unique_id() { + ObjectBase::set_new_unique_id(); + this->config.set_new_unique_id(); + this->supported_facets.set_new_unique_id(); + this->seam_facets.set_new_unique_id(); + this->mmu_segmentation_facets.set_new_unique_id(); + } + + bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } + bool is_seam_painted() const { return !this->seam_facets.empty(); } + bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } + +protected: + friend class Print; + friend class SLAPrint; + friend class Model; + friend class ModelObject; + friend void model_volume_list_update_supports(ModelObject& model_object_dst, const ModelObject& model_object_new); + + // Copies IDs of both the ModelVolume and its config. + explicit ModelVolume(const ModelVolume &rhs) = default; + void set_model_object(ModelObject *model_object) { object = model_object; } + void assign_new_unique_ids_recursive() override; + void transform_this_mesh(const Transform3d& t, bool fix_left_handed); + void transform_this_mesh(const Matrix3d& m, bool fix_left_handed); + +private: + // Parent object owning this ModelVolume. + ModelObject* object; + // The triangular model. + std::shared_ptr m_mesh; + // Is it an object to be printed, or a modifier volume? + ModelVolumeType m_type; + t_model_material_id m_material_id; + // The convex hull of this model's mesh. + std::shared_ptr m_convex_hull; + Geometry::Transformation m_transformation; + + // flag to optimize the checking if the volume is splittable + // -1 -> is unknown value (before first cheking) + // 0 -> is not splittable + // 1 -> is splittable + mutable int m_is_splittable{ -1 }; + + ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) : m_mesh(new TriangleMesh(mesh)), m_type(type), object(object) + { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->supported_facets.id().valid()); + assert(this->seam_facets.id().valid()); + assert(this->mmu_segmentation_facets.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->seam_facets.id()); + assert(this->id() != this->mmu_segmentation_facets.id()); + if (mesh.facets_count() > 1) + calculate_convex_hull(); + } + ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) : + m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->supported_facets.id().valid()); + assert(this->seam_facets.id().valid()); + assert(this->mmu_segmentation_facets.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->seam_facets.id()); + assert(this->id() != this->mmu_segmentation_facets.id()); + } + + // Copying an existing volume, therefore this volume will get a copy of the ID assigned. + ModelVolume(ModelObject *object, const ModelVolume &other) : + ObjectBase(other), + name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), + config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), + supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets) + { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->supported_facets.id().valid()); + assert(this->seam_facets.id().valid()); + assert(this->mmu_segmentation_facets.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->seam_facets.id()); + assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() == other.id()); + assert(this->config.id() == other.config.id()); + assert(this->supported_facets.id() == other.supported_facets.id()); + assert(this->seam_facets.id() == other.seam_facets.id()); + assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id()); + this->set_material_id(other.material_id()); + } + // Providing a new mesh, therefore this volume will get a new unique ID assigned. + ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) : + name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation) + { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->supported_facets.id().valid()); + assert(this->seam_facets.id().valid()); + assert(this->mmu_segmentation_facets.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->seam_facets.id()); + assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() != other.id()); + assert(this->config.id() == other.config.id()); + this->set_material_id(other.material_id()); + this->config.set_new_unique_id(); + if (m_mesh->facets_count() > 1) + calculate_convex_hull(); + assert(this->config.id().valid()); + assert(this->config.id() != other.config.id()); + assert(this->supported_facets.id() != other.supported_facets.id()); + assert(this->seam_facets.id() != other.seam_facets.id()); + assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id()); + assert(this->id() != this->config.id()); + assert(this->supported_facets.empty()); + assert(this->seam_facets.empty()); + assert(this->mmu_segmentation_facets.empty()); + } + + ModelVolume& operator=(ModelVolume &rhs) = delete; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization, therefore no IDs are allocated. + ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->supported_facets.id().invalid()); + assert(this->seam_facets.id().invalid()); + assert(this->mmu_segmentation_facets.id().invalid()); + } + template void load(Archive &ar) { + bool has_convex_hull; + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::load_by_value(ar, supported_facets); + cereal::load_by_value(ar, seam_facets); + cereal::load_by_value(ar, mmu_segmentation_facets); + cereal::load_by_value(ar, config); + assert(m_mesh); + if (has_convex_hull) { + cereal::load_optional(ar, m_convex_hull); + if (! m_convex_hull && ! m_mesh->empty()) + // The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it. + this->calculate_convex_hull(); + } else + m_convex_hull.reset(); + } + template void save(Archive &ar) const { + bool has_convex_hull = m_convex_hull.get() != nullptr; + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::save_by_value(ar, supported_facets); + cereal::save_by_value(ar, seam_facets); + cereal::save_by_value(ar, mmu_segmentation_facets); + cereal::save_by_value(ar, config); + if (has_convex_hull) + cereal::save_optional(ar, m_convex_hull); + } +}; + +inline void model_volumes_sort_by_id(ModelVolumePtrs &model_volumes) +{ + std::sort(model_volumes.begin(), model_volumes.end(), [](const ModelVolume *l, const ModelVolume *r) { return l->id() < r->id(); }); +} + +inline const ModelVolume* model_volume_find_by_id(const ModelVolumePtrs &model_volumes, const ObjectID id) +{ + auto it = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [id](const ModelVolume *mv) { return mv->id() < id; }); + return it != model_volumes.end() && (*it)->id() == id ? *it : nullptr; +} + +enum ModelInstanceEPrintVolumeState : unsigned char +{ + ModelInstancePVS_Inside, + ModelInstancePVS_Partly_Outside, + ModelInstancePVS_Fully_Outside, + ModelInstanceNum_BedStates +}; + +// A single instance of a ModelObject. +// Knows the affine transformation of an object. +class ModelInstance final : public ObjectBase +{ +private: + Geometry::Transformation m_transformation; + +public: + // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) + ModelInstanceEPrintVolumeState print_volume_state; + // Whether or not this instance is printable + bool printable; + + ModelObject* get_object() const { return this->object; } + + const Geometry::Transformation& get_transformation() const { return m_transformation; } + void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_offset() const { return m_transformation.get_offset(); } +#else + const Vec3d& get_offset() const { return m_transformation.get_offset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } + + void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } + void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_rotation() const { return m_transformation.get_rotation(); } +#else + const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } + + void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } + void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } +#else + const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } + + void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } + void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_mirror() const { return m_transformation.get_mirror(); } +#else + const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } + bool is_left_handed() const { return m_transformation.is_left_handed(); } + + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } + void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } + + // To be called on an external mesh + void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; + // Calculate a bounding box of a transformed mesh. To be called on an external mesh. + BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const; + // Transform an external bounding box. + BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; + // Transform an external vector. + Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const; + // To be called on an external polygon. It does not translate the polygon, only rotates and scales. + void transform_polygon(Polygon* polygon) const; + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d& get_matrix() const { return m_transformation.get_matrix(); } + Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); } +#else + const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } + + // Getting the input polygon for arrange + arrangement::ArrangePolygon get_arrange_polygon() const; + + // Apply the arrange result on the ModelInstance + void apply_arrange_result(const Vec2d& offs, double rotation) + { + // write the transformation data into the model instance + set_rotation(Z, rotation); + set_offset(X, unscale(offs(X))); + set_offset(Y, unscale(offs(Y))); + this->object->invalidate_bounding_box(); + } + +protected: + friend class Print; + friend class SLAPrint; + friend class Model; + friend class ModelObject; + + explicit ModelInstance(const ModelInstance &rhs) = default; + void set_model_object(ModelObject *model_object) { object = model_object; } + +private: + // Parent object, owning this instance. + ModelObject* object; + + // Constructor, which assigns a new unique ID. + explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); } + // Constructor, which assigns a new unique ID. + explicit ModelInstance(ModelObject *object, const ModelInstance &other) : + m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); } + + explicit ModelInstance(ModelInstance &&rhs) = delete; + ModelInstance& operator=(const ModelInstance &rhs) = delete; + ModelInstance& operator=(ModelInstance &&rhs) = delete; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization, therefore no IDs are allocated. + ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } + template void serialize(Archive &ar) { + ar(m_transformation, print_volume_state, printable); + } +}; + + +class ModelWipeTower final : public ObjectBase +{ +public: + Vec2d position; + double rotation; + +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + friend class Model; + + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit ModelWipeTower() {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit ModelWipeTower(int) : ObjectBase(-1) {} + // Copy constructor copies the ID. + explicit ModelWipeTower(const ModelWipeTower &cfg) = default; + + // Disabled methods. + ModelWipeTower(ModelWipeTower &&rhs) = delete; + ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete; + ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete; + + // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object. + template void serialize(Archive &ar) { ar(position, rotation); } +}; + +// The print bed content. +// Description of a triangular model with multiple materials, multiple instances with various affine transformations +// and with multiple modifier meshes. +// A model groups multiple objects, each object having possibly multiple instances, +// all objects may share mutliple materials. +class Model final : public ObjectBase +{ +public: + // Materials are owned by a model and referenced by objects through t_model_material_id. + // Single material may be shared by multiple models. + ModelMaterialMap materials; + // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). + ModelObjectPtrs objects; + // Wipe tower object. + ModelWipeTower wipe_tower; + + // Extensions for color print + CustomGCode::Info custom_gcode_per_print_z; + + // Default constructor assigns a new ID to the model. + Model() { assert(this->id().valid()); } + ~Model() { this->clear_objects(); this->clear_materials(); } + + /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ + /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ + Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); } + explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); } + Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } + Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } + + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) + + enum class LoadAttribute : int { + AddDefaultInstances, + CheckVersion + }; + using LoadAttributes = enum_bitmask; + + static Model read_from_file( + const std::string& input_file, + DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr, + LoadAttributes options = LoadAttribute::AddDefaultInstances); + static Model read_from_archive( + const std::string& input_file, + DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, + LoadAttributes options = LoadAttribute::AddDefaultInstances); + + // Add a new ModelObject to this Model, generate a new ID for this ModelObject. + ModelObject* add_object(); + ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); + ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); + ModelObject* add_object(const ModelObject &other); + void delete_object(size_t idx); + bool delete_object(ObjectID id); + bool delete_object(ModelObject* object); + void clear_objects(); + + ModelMaterial* add_material(t_model_material_id material_id); + ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other); + ModelMaterial* get_material(t_model_material_id material_id) { + ModelMaterialMap::iterator i = this->materials.find(material_id); + return (i == this->materials.end()) ? nullptr : i->second; + } + + void delete_material(t_model_material_id material_id); + void clear_materials(); + bool add_default_instances(); + // Returns approximate axis aligned bounding box of this model + BoundingBoxf3 bounding_box() const; + // Set the print_volume_state of PrintObject::instances, + // return total number of printable objects. + unsigned int update_print_volume_state(const BuildVolume &build_volume); + // Returns true if any ModelObject was modified. + bool center_instances_around_point(const Vec2d &point); + void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } + TriangleMesh mesh() const; + + // Croaks if the duplicated objects do not fit the print bed. + void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); + + bool looks_like_multipart_object() const; + void convert_multipart_object(unsigned int max_extruders); + bool looks_like_imperial_units() const; + void convert_from_imperial_units(bool only_small_volumes); + bool looks_like_saved_in_meters() const; + void convert_from_meters(bool only_small_volumes); + int removed_objects_with_zero_volume(); + + // Ensures that the min z of the model is not negative + void adjust_min_z(); + + void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } + + // Propose an output file name & path based on the first printable object's name and source input file's path. + std::string propose_export_file_name_and_path() const; + // Propose an output path, replace extension. The new_extension shall contain the initial dot. + std::string propose_export_file_name_and_path(const std::string &new_extension) const; + + // Checks if any of objects is painted using the fdm support painting gizmo. + bool is_fdm_support_painted() const; + // Checks if any of objects is painted using the seam painting gizmo. + bool is_seam_painted() const; + // Checks if any of objects is painted using the multi-material painting gizmo. + bool is_mm_painted() const; + +private: + explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); } + void assign_new_unique_ids_recursive(); + void update_links_bottom_up_recursive(); + + friend class cereal::access; + friend class UndoRedo::StackImpl; + template void serialize(Archive &ar) { + Internal::StaticSerializationWrapper wipe_tower_wrapper(wipe_tower); + ar(materials, objects, wipe_tower_wrapper); + } +}; + +ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute) + +#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE +#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE + +// Test whether the two models contain the same number of ModelObjects with the same set of IDs +// ordered in the same order. In that case it is not necessary to kill the background processing. +bool model_object_list_equal(const Model &model_old, const Model &model_new); + +// Test whether the new model is just an extension of the old model (new objects were added +// to the end of the original list. In that case it is not necessary to kill the background processing. +bool model_object_list_extended(const Model &model_old, const Model &model_new); + +// Test whether the new ModelObject contains a different set of volumes (or sorted in a different order) +// than the old ModelObject. +bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type); +bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const std::initializer_list &types); + +// Test whether the now ModelObject has newer custom supports data than the old one. +// The function assumes that volumes list is synchronized. +bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new); + +// Test whether the now ModelObject has newer custom seam data than the old one. +// The function assumes that volumes list is synchronized. +bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new); + +// Test whether the now ModelObject has newer MMU segmentation data than the old one. +// The function assumes that volumes list is synchronized. +extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new); + +// If the model has multi-part objects, then it is currently not supported by the SLA mode. +// Either the model cannot be loaded, or a SLA printer has to be activated. +bool model_has_multi_part_objects(const Model &model); +// If the model has advanced features, then it cannot be processed in simple mode. +bool model_has_advanced_features(const Model &model); + +#ifndef NDEBUG +// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. +void check_model_ids_validity(const Model &model); +void check_model_ids_equal(const Model &model1, const Model &model2); +#endif /* NDEBUG */ + +static const float SINKING_Z_THRESHOLD = -0.001f; +static const double SINKING_MIN_Z_THRESHOLD = 0.05; + +} // namespace Slic3r + +namespace cereal +{ + template struct specialize {}; + template struct specialize {}; +} + +#endif /* slic3r_Model_hpp_ */ diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 84152ee9c..4fc76800c 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -1,565 +1,569 @@ -#ifndef slic3r_Point_hpp_ -#define slic3r_Point_hpp_ - -#include "libslic3r.h" -#include -#include -#include -#include -#include -#include - -#include - -#include "LocalesUtils.hpp" - -namespace Slic3r { - -class BoundingBox; -class BoundingBoxf; -class Line; -class MultiPoint; -class Point; -using Vector = Point; - -// Base template for eigen derived vectors -template -using Mat = Eigen::Matrix; - -template using Vec = Mat; - -template -using DynVec = Eigen::Matrix; - -// Eigen types, to replace the Slic3r's own types in the future. -// Vector types with a fixed point coordinate base type. -using Vec2crd = Eigen::Matrix; -using Vec3crd = Eigen::Matrix; -using Vec2i = Eigen::Matrix; -using Vec3i = Eigen::Matrix; -using Vec4i = Eigen::Matrix; -using Vec2i32 = Eigen::Matrix; -using Vec2i64 = Eigen::Matrix; -using Vec3i32 = Eigen::Matrix; -using Vec3i64 = Eigen::Matrix; - -// Vector types with a double coordinate base type. -using Vec2f = Eigen::Matrix; -using Vec3f = Eigen::Matrix; -using Vec2d = Eigen::Matrix; -using Vec3d = Eigen::Matrix; - -using Points = std::vector; -using PointPtrs = std::vector; -using PointConstPtrs = std::vector; -using Points3 = std::vector; -using Pointfs = std::vector; -using Vec2ds = std::vector; -using Pointf3s = std::vector; - -using Matrix2f = Eigen::Matrix; -using Matrix2d = Eigen::Matrix; -using Matrix3f = Eigen::Matrix; -using Matrix3d = Eigen::Matrix; -using Matrix4f = Eigen::Matrix; -using Matrix4d = Eigen::Matrix; - -template -using Transform = Eigen::Transform; - -using Transform2f = Eigen::Transform; -using Transform2d = Eigen::Transform; -using Transform3f = Eigen::Transform; -using Transform3d = Eigen::Transform; - -// I don't know why Eigen::Transform::Identity() return a const object... -template Transform identity() { return Transform::Identity(); } -inline const auto &identity3f = identity<3, float>; -inline const auto &identity3d = identity<3, double>; - -inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y()); } - -// Cross product of two 2D vectors. -// None of the vectors may be of int32_t type as the result would overflow. -template -inline typename Derived::Scalar cross2(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) -{ - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); - static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); - static_assert(! std::is_same::value, "cross2(): Scalar type must not be int32_t, otherwise the cross product would overflow."); - static_assert(std::is_same::value, "cross2(): Scalar types of 1st and 2nd operand must be equal."); - return v1.x() * v2.y() - v1.y() * v2.x(); -} - -// 2D vector perpendicular to the argument. -template -inline Eigen::Matrix perp(const Eigen::MatrixBase &v) -{ - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "perp(): parameter is not a 2D vector"); - return { - v.y(), v.x() }; -} - -// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>. -template -inline double angle(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) { - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector"); - static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector"); - auto v1d = v1.template cast(); - auto v2d = v2.template cast(); - return atan2(cross2(v1d, v2d), v1d.dot(v2d)); -} - -template -Eigen::Matrix to_2d(const Eigen::MatrixBase> &ptN) { return { ptN.x(), ptN.y() }; } - -template -Eigen::Matrix to_3d(const Eigen::MatrixBase> & pt, const T z) { return { pt.x(), pt.y(), z }; } - -inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); } -inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } -inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } -inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale(x), unscale(y), unscale(z)); } -inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } -inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } - -inline std::string to_string(const Vec2crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } -inline std::string to_string(const Vec2d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } -inline std::string to_string(const Vec3crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } -inline std::string to_string(const Vec3d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } - -std::vector transform(const std::vector& points, const Transform3f& t); -Pointf3s transform(const Pointf3s& points, const Transform3d& t); - -template using Vec = Eigen::Matrix; - -class Point : public Vec2crd -{ -public: - using coord_type = coord_t; - - Point() : Vec2crd(0, 0) {} - Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {} - Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} - Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {} - Point(const Point &rhs) { *this = rhs; } - explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {} - // This constructor allows you to construct Point from Eigen expressions - template - Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} - static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } - static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } - static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } - - // This method allows you to assign Eigen expressions to MyVectorType - template - Point& operator=(const Eigen::MatrixBase &other) - { - this->Vec2crd::operator=(other); - return *this; - } - - Point& operator+=(const Point& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); return *this; } - Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; } - Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; } - Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); } - - void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } - void rotate(double cos_a, double sin_a) { - double cur_x = (double)this->x(); - double cur_y = (double)this->y(); - this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y); - this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x); - } - - void rotate(double angle, const Point ¢er); - Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } - Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } - Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } - int nearest_point_index(const Points &points) const; - int nearest_point_index(const PointConstPtrs &points) const; - int nearest_point_index(const PointPtrs &points) const; - bool nearest_point(const Points &points, Point* point) const; - Point projection_onto(const MultiPoint &poly) const; - Point projection_onto(const Line &line) const; -}; - -inline bool operator<(const Point &l, const Point &r) -{ - return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); -} - -inline Point operator* (const Point& l, const double &r) -{ - return {coord_t(l.x() * r), coord_t(l.y() * r)}; -} - -inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON)) -{ - Point d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon; -} - -inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON)) -{ - Vec2f d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon; -} - -inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON) -{ - Vec2d d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon; -} - -inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON)) -{ - Vec3f d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; -} - -inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON) -{ - Vec3d d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; -} - -inline Point lerp(const Point &a, const Point &b, double t) -{ - assert((t >= -EPSILON) && (t <= 1. + EPSILON)); - return ((1. - t) * a.cast() + t * b.cast()).cast(); -} - -BoundingBox get_extents(const Points &pts); -BoundingBox get_extents(const std::vector &pts); -BoundingBoxf get_extents(const std::vector &pts); - -// Test for duplicate points in a vector of points. -// The points are copied, sorted and checked for duplicates globally. -bool has_duplicate_points(std::vector &&pts); -inline bool has_duplicate_points(const std::vector &pts) -{ - std::vector cpy = pts; - return has_duplicate_points(std::move(cpy)); -} - -// Test for duplicate points in a vector of points. -// Only successive points are checked for equality. -inline bool has_duplicate_successive_points(const std::vector &pts) -{ - for (size_t i = 1; i < pts.size(); ++ i) - if (pts[i - 1] == pts[i]) - return true; - return false; -} - -// Test for duplicate points in a vector of points. -// Only successive points are checked for equality. Additionally, first and last points are compared for equality. -inline bool has_duplicate_successive_points_closed(const std::vector &pts) -{ - return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back()); -} - -namespace int128 { - // Exact orientation predicate, - // returns +1: CCW, 0: collinear, -1: CW. - int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3); - // Exact orientation predicate, - // returns +1: CCW, 0: collinear, -1: CW. - int cross(const Vec2crd &v1, const Vec2crd &v2); -} - -// To be used by std::unordered_map, std::unordered_multimap and friends. -struct PointHash { - size_t operator()(const Vec2crd &pt) const { - return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y()); - } -}; - -// A generic class to search for a closest Point in a given radius. -// It uses std::unordered_multimap to implement an efficient 2D spatial hashing. -// The PointAccessor has to return const Point*. -// If a nullptr is returned, it is ignored by the query. -template class ClosestPointInRadiusLookup -{ -public: - ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) : - m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0) - { - // Resolution of a grid, twice the search radius + some epsilon. - coord_t gridres = 2 * m_search_radius + 4; - m_grid_resolution = gridres; - assert(m_grid_resolution > 0); - assert(m_grid_resolution < (coord_t(1) << 30)); - // Compute m_grid_log2 = log2(m_grid_resolution) - if (m_grid_resolution > 32767) { - m_grid_resolution >>= 16; - m_grid_log2 += 16; - } - if (m_grid_resolution > 127) { - m_grid_resolution >>= 8; - m_grid_log2 += 8; - } - if (m_grid_resolution > 7) { - m_grid_resolution >>= 4; - m_grid_log2 += 4; - } - if (m_grid_resolution > 1) { - m_grid_resolution >>= 2; - m_grid_log2 += 2; - } - if (m_grid_resolution > 0) - ++ m_grid_log2; - m_grid_resolution = 1 << m_grid_log2; - assert(m_grid_resolution >= gridres); - assert(gridres > m_grid_resolution / 2); - } - - void insert(const ValueType &value) { - const Vec2crd *pt = m_point_accessor(value); - if (pt != nullptr) - m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), value)); - } - - void insert(ValueType &&value) { - const Vec2crd *pt = m_point_accessor(value); - if (pt != nullptr) - m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value))); - } - - // Erase a data point equal to value. (ValueType has to declare the operator==). - // Returns true if the data point equal to value was found and removed. - bool erase(const ValueType &value) { - const Point *pt = m_point_accessor(value); - if (pt != nullptr) { - // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Point((*pt).x()>>m_grid_log2, (*pt).y()>>m_grid_log2)); - // Remove the first item. - for (auto it = range.first; it != range.second; ++ it) { - if (it->second == value) { - m_map.erase(it); - return true; - } - } - } - return false; - } - - // Return a pair of - std::pair find(const Vec2crd &pt) { - // Iterate over 4 closest grid cells around pt, - // find the closest start point inside these cells to pt. - const ValueType *value_min = nullptr; - double dist_min = std::numeric_limits::max(); - // Round pt to a closest grid_cell corner. - Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); - // For four neighbors of grid_corner: - for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { - for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { - // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); - // Find the map entry closest to pt. - for (auto it = range.first; it != range.second; ++it) { - const ValueType &value = it->second; - const Vec2crd *pt2 = m_point_accessor(value); - if (pt2 != nullptr) { - const double d2 = (pt - *pt2).cast().squaredNorm(); - if (d2 < dist_min) { - dist_min = d2; - value_min = &value; - } - } - } - } - } - return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ? - std::make_pair(value_min, dist_min) : - std::make_pair(nullptr, std::numeric_limits::max()); - } - - // Returns all pairs of values and squared distances. - std::vector> find_all(const Vec2crd &pt) { - // Iterate over 4 closest grid cells around pt, - // Round pt to a closest grid_cell corner. - Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); - // For four neighbors of grid_corner: - std::vector> out; - const double r2 = double(m_search_radius) * m_search_radius; - for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { - for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { - // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); - // Find the map entry closest to pt. - for (auto it = range.first; it != range.second; ++it) { - const ValueType &value = it->second; - const Vec2crd *pt2 = m_point_accessor(value); - if (pt2 != nullptr) { - const double d2 = (pt - *pt2).cast().squaredNorm(); - if (d2 <= r2) - out.emplace_back(&value, d2); - } - } - } - } - return out; - } - -private: - using map_type = typename std::unordered_multimap; - PointAccessor m_point_accessor; - map_type m_map; - coord_t m_search_radius; - coord_t m_grid_resolution; - coord_t m_grid_log2; -}; - -std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf); - - -// ///////////////////////////////////////////////////////////////////////////// -// Type safe conversions to and from scaled and unscaled coordinates -// ///////////////////////////////////////////////////////////////////////////// - -// Semantics are the following: -// Upscaling (scaled()): only from floating point types (or Vec) to either -// floating point or integer 'scaled coord' coordinates. -// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only - -// Conversion definition from unscaled to floating point scaled -template> -inline constexpr FloatingOnly scaled(const Tin &v) noexcept -{ - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion definition from unscaled to integer 'scaled coord'. -// TODO: is the rounding necessary? Here it is commented out to show that -// it can be different for integers but it does not have to be. Using -// std::round means loosing noexcept and constexpr modifiers -template> -inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept -{ - //return static_cast(std::round(v / SCALING_FACTOR)); - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion for Eigen vectors (N dimensional points) -template, - int...EigenArgs> -inline Eigen::Matrix, N, EigenArgs...> -scaled(const Eigen::Matrix &v) -{ - return (v / SCALING_FACTOR).template cast(); -} - -// Conversion from arithmetic scaled type to floating point unscaled -template, - class = FloatingOnly> -inline constexpr Tout unscaled(const Tin &v) noexcept -{ - return Tout(v) * Tout(SCALING_FACTOR); -} - -// Unscaling for Eigen vectors. Input base type can be arithmetic, output base -// type can only be floating point. -template, - class = FloatingOnly, - int...EigenArgs> -inline constexpr Eigen::Matrix -unscaled(const Eigen::Matrix &v) noexcept -{ - return v.template cast() * Tout(SCALING_FACTOR); -} - -// Align a coordinate to a grid. The coordinate may be negative, -// the aligned value will never be bigger than the original one. -inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) { - // Current C++ standard defines the result of integer division to be rounded to zero, - // for both positive and negative numbers. Here we want to round down for negative - // numbers as well. - coord_t aligned = (coord < 0) ? - ((coord - spacing + 1) / spacing) * spacing : - (coord / spacing) * spacing; - assert(aligned <= coord); - return aligned; -} -inline Point align_to_grid(Point coord, Point spacing) - { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); } -inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) - { return base + align_to_grid(coord - base, spacing); } -inline Point align_to_grid(Point coord, Point spacing, Point base) - { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } - -} // namespace Slic3r - -// start Boost -#include -#include -namespace boost { namespace polygon { - template <> - struct geometry_concept { using type = point_concept; }; - - template <> - struct point_traits { - using coordinate_type = coord_t; - - static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { - return static_cast(point((orient == HORIZONTAL) ? 0 : 1)); - } - }; - - template <> - struct point_mutable_traits { - using coordinate_type = coord_t; - static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { - point((orient == HORIZONTAL) ? 0 : 1) = value; - } - static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { - return Slic3r::Point(x_value, y_value); - } - }; -} } -// end Boost - -// Serialization through the Cereal library -namespace cereal { -// template void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); } -// template void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); } - template void serialize(Archive& archive, Slic3r::Vec2i &v) { archive(v.x(), v.y()); } - template void serialize(Archive& archive, Slic3r::Vec3i &v) { archive(v.x(), v.y(), v.z()); } -// template void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); } -// template void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); } - template void serialize(Archive& archive, Slic3r::Vec2f &v) { archive(v.x(), v.y()); } - template void serialize(Archive& archive, Slic3r::Vec3f &v) { archive(v.x(), v.y(), v.z()); } - template void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); } - template void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); } - - template void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } - template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } -} - -// To be able to use Vec<> and Mat<> in range based for loops: -namespace Eigen { -template -T* begin(Slic3r::Mat &mat) { return mat.data(); } - -template -T* end(Slic3r::Mat &mat) { return mat.data() + N * M; } - -template -const T* begin(const Slic3r::Mat &mat) { return mat.data(); } - -template -const T* end(const Slic3r::Mat &mat) { return mat.data() + N * M; } -} // namespace Eigen - -#endif +#ifndef slic3r_Point_hpp_ +#define slic3r_Point_hpp_ + +#include "libslic3r.h" +#include +#include +#include +#include +#include +#include + +#include + +#include "LocalesUtils.hpp" + +namespace Slic3r { + +class BoundingBox; +class BoundingBoxf; +class Line; +class MultiPoint; +class Point; +using Vector = Point; + +// Base template for eigen derived vectors +template +using Mat = Eigen::Matrix; + +template using Vec = Mat; + +template +using DynVec = Eigen::Matrix; + +// Eigen types, to replace the Slic3r's own types in the future. +// Vector types with a fixed point coordinate base type. +using Vec2crd = Eigen::Matrix; +using Vec3crd = Eigen::Matrix; +using Vec2i = Eigen::Matrix; +using Vec3i = Eigen::Matrix; +using Vec4i = Eigen::Matrix; +using Vec2i32 = Eigen::Matrix; +using Vec2i64 = Eigen::Matrix; +using Vec3i32 = Eigen::Matrix; +using Vec3i64 = Eigen::Matrix; + +// Vector types with a double coordinate base type. +using Vec2f = Eigen::Matrix; +using Vec3f = Eigen::Matrix; +using Vec2d = Eigen::Matrix; +using Vec3d = Eigen::Matrix; + +using Points = std::vector; +using PointPtrs = std::vector; +using PointConstPtrs = std::vector; +using Points3 = std::vector; +using Pointfs = std::vector; +using Vec2ds = std::vector; +using Pointf3s = std::vector; + +using Matrix2f = Eigen::Matrix; +using Matrix2d = Eigen::Matrix; +using Matrix3f = Eigen::Matrix; +using Matrix3d = Eigen::Matrix; +using Matrix4f = Eigen::Matrix; +using Matrix4d = Eigen::Matrix; + +template +using Transform = Eigen::Transform; + +using Transform2f = Eigen::Transform; +using Transform2d = Eigen::Transform; +using Transform3f = Eigen::Transform; +using Transform3d = Eigen::Transform; + +// I don't know why Eigen::Transform::Identity() return a const object... +template Transform identity() { return Transform::Identity(); } +inline const auto &identity3f = identity<3, float>; +inline const auto &identity3d = identity<3, double>; + +inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y()); } + +// Cross product of two 2D vectors. +// None of the vectors may be of int32_t type as the result would overflow. +template +inline typename Derived::Scalar cross2(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); + static_assert(! std::is_same::value, "cross2(): Scalar type must not be int32_t, otherwise the cross product would overflow."); + static_assert(std::is_same::value, "cross2(): Scalar types of 1st and 2nd operand must be equal."); + return v1.x() * v2.y() - v1.y() * v2.x(); +} + +// 2D vector perpendicular to the argument. +template +inline Eigen::Matrix perp(const Eigen::MatrixBase &v) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "perp(): parameter is not a 2D vector"); + return { - v.y(), v.x() }; +} + +// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>. +template +inline double angle(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) { + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector"); + auto v1d = v1.template cast(); + auto v2d = v2.template cast(); + return atan2(cross2(v1d, v2d), v1d.dot(v2d)); +} + +template +Eigen::Matrix to_2d(const Eigen::MatrixBase> &ptN) { return { ptN.x(), ptN.y() }; } + +template +Eigen::Matrix to_3d(const Eigen::MatrixBase> & pt, const T z) { return { pt.x(), pt.y(), z }; } + +inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); } +inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } +inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } +inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale(x), unscale(y), unscale(z)); } +inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } +inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } + +inline std::string to_string(const Vec2crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } +inline std::string to_string(const Vec2d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } +inline std::string to_string(const Vec3crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } +inline std::string to_string(const Vec3d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } + +std::vector transform(const std::vector& points, const Transform3f& t); +Pointf3s transform(const Pointf3s& points, const Transform3d& t); + +template using Vec = Eigen::Matrix; + +class Point : public Vec2crd +{ +public: + using coord_type = coord_t; + + Point() : Vec2crd(0, 0) {} + Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {} + Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} + Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {} + Point(const Point &rhs) { *this = rhs; } + explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {} + // This constructor allows you to construct Point from Eigen expressions + template + Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} + static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } + static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } + static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } + + // This method allows you to assign Eigen expressions to MyVectorType + template + Point& operator=(const Eigen::MatrixBase &other) + { + this->Vec2crd::operator=(other); + return *this; + } + + Point& operator+=(const Point& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); return *this; } + Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; } + Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; } + Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); } + + void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } + void rotate(double cos_a, double sin_a) { + double cur_x = (double)this->x(); + double cur_y = (double)this->y(); + this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y); + this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x); + } + + void rotate(double angle, const Point ¢er); + Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } + Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } + Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } + int nearest_point_index(const Points &points) const; + int nearest_point_index(const PointConstPtrs &points) const; + int nearest_point_index(const PointPtrs &points) const; + bool nearest_point(const Points &points, Point* point) const; + Point projection_onto(const MultiPoint &poly) const; + Point projection_onto(const Line &line) const; +}; + +inline bool operator<(const Point &l, const Point &r) +{ + return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); +} + +inline Point operator* (const Point& l, const double &r) +{ + return {coord_t(l.x() * r), coord_t(l.y() * r)}; +} + +inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON)) +{ + Point d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon; +} + +inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON)) +{ + Vec2f d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon; +} + +inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON) +{ + Vec2d d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon; +} + +inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON)) +{ + Vec3f d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; +} + +inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON) +{ + Vec3d d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; +} + +inline Point lerp(const Point &a, const Point &b, double t) +{ + assert((t >= -EPSILON) && (t <= 1. + EPSILON)); + return ((1. - t) * a.cast() + t * b.cast()).cast(); +} + +BoundingBox get_extents(const Points &pts); +BoundingBox get_extents(const std::vector &pts); +BoundingBoxf get_extents(const std::vector &pts); + +// Test for duplicate points in a vector of points. +// The points are copied, sorted and checked for duplicates globally. +bool has_duplicate_points(std::vector &&pts); +inline bool has_duplicate_points(const std::vector &pts) +{ + std::vector cpy = pts; + return has_duplicate_points(std::move(cpy)); +} + +// Test for duplicate points in a vector of points. +// Only successive points are checked for equality. +inline bool has_duplicate_successive_points(const std::vector &pts) +{ + for (size_t i = 1; i < pts.size(); ++ i) + if (pts[i - 1] == pts[i]) + return true; + return false; +} + +// Test for duplicate points in a vector of points. +// Only successive points are checked for equality. Additionally, first and last points are compared for equality. +inline bool has_duplicate_successive_points_closed(const std::vector &pts) +{ + return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back()); +} + +namespace int128 { + // Exact orientation predicate, + // returns +1: CCW, 0: collinear, -1: CW. + int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3); + // Exact orientation predicate, + // returns +1: CCW, 0: collinear, -1: CW. + int cross(const Vec2crd &v1, const Vec2crd &v2); +} + +// To be used by std::unordered_map, std::unordered_multimap and friends. +struct PointHash { + size_t operator()(const Vec2crd &pt) const { + return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y()); + } +}; + +// A generic class to search for a closest Point in a given radius. +// It uses std::unordered_multimap to implement an efficient 2D spatial hashing. +// The PointAccessor has to return const Point*. +// If a nullptr is returned, it is ignored by the query. +template class ClosestPointInRadiusLookup +{ +public: + ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) : + m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0) + { + // Resolution of a grid, twice the search radius + some epsilon. + coord_t gridres = 2 * m_search_radius + 4; + m_grid_resolution = gridres; + assert(m_grid_resolution > 0); + assert(m_grid_resolution < (coord_t(1) << 30)); + // Compute m_grid_log2 = log2(m_grid_resolution) + if (m_grid_resolution > 32767) { + m_grid_resolution >>= 16; + m_grid_log2 += 16; + } + if (m_grid_resolution > 127) { + m_grid_resolution >>= 8; + m_grid_log2 += 8; + } + if (m_grid_resolution > 7) { + m_grid_resolution >>= 4; + m_grid_log2 += 4; + } + if (m_grid_resolution > 1) { + m_grid_resolution >>= 2; + m_grid_log2 += 2; + } + if (m_grid_resolution > 0) + ++ m_grid_log2; + m_grid_resolution = 1 << m_grid_log2; + assert(m_grid_resolution >= gridres); + assert(gridres > m_grid_resolution / 2); + } + + void insert(const ValueType &value) { + const Vec2crd *pt = m_point_accessor(value); + if (pt != nullptr) + m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), value)); + } + + void insert(ValueType &&value) { + const Vec2crd *pt = m_point_accessor(value); + if (pt != nullptr) + m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value))); + } + + // Erase a data point equal to value. (ValueType has to declare the operator==). + // Returns true if the data point equal to value was found and removed. + bool erase(const ValueType &value) { + const Point *pt = m_point_accessor(value); + if (pt != nullptr) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Point((*pt).x()>>m_grid_log2, (*pt).y()>>m_grid_log2)); + // Remove the first item. + for (auto it = range.first; it != range.second; ++ it) { + if (it->second == value) { + m_map.erase(it); + return true; + } + } + } + return false; + } + + // Return a pair of + std::pair find(const Vec2crd &pt) { + // Iterate over 4 closest grid cells around pt, + // find the closest start point inside these cells to pt. + const ValueType *value_min = nullptr; + double dist_min = std::numeric_limits::max(); + // Round pt to a closest grid_cell corner. + Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); + // For four neighbors of grid_corner: + for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { + for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); + // Find the map entry closest to pt. + for (auto it = range.first; it != range.second; ++it) { + const ValueType &value = it->second; + const Vec2crd *pt2 = m_point_accessor(value); + if (pt2 != nullptr) { + const double d2 = (pt - *pt2).cast().squaredNorm(); + if (d2 < dist_min) { + dist_min = d2; + value_min = &value; + } + } + } + } + } + return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ? + std::make_pair(value_min, dist_min) : + std::make_pair(nullptr, std::numeric_limits::max()); + } + + // Returns all pairs of values and squared distances. + std::vector> find_all(const Vec2crd &pt) { + // Iterate over 4 closest grid cells around pt, + // Round pt to a closest grid_cell corner. + Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); + // For four neighbors of grid_corner: + std::vector> out; + const double r2 = double(m_search_radius) * m_search_radius; + for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { + for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); + // Find the map entry closest to pt. + for (auto it = range.first; it != range.second; ++it) { + const ValueType &value = it->second; + const Vec2crd *pt2 = m_point_accessor(value); + if (pt2 != nullptr) { + const double d2 = (pt - *pt2).cast().squaredNorm(); + if (d2 <= r2) + out.emplace_back(&value, d2); + } + } + } + } + return out; + } + +private: + using map_type = typename std::unordered_multimap; + PointAccessor m_point_accessor; + map_type m_map; + coord_t m_search_radius; + coord_t m_grid_resolution; + coord_t m_grid_log2; +}; + +std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf); + + +// ///////////////////////////////////////////////////////////////////////////// +// Type safe conversions to and from scaled and unscaled coordinates +// ///////////////////////////////////////////////////////////////////////////// + +// Semantics are the following: +// Upscaling (scaled()): only from floating point types (or Vec) to either +// floating point or integer 'scaled coord' coordinates. +// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only + +// Conversion definition from unscaled to floating point scaled +template> +inline constexpr FloatingOnly scaled(const Tin &v) noexcept +{ + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion definition from unscaled to integer 'scaled coord'. +// TODO: is the rounding necessary? Here it is commented out to show that +// it can be different for integers but it does not have to be. Using +// std::round means loosing noexcept and constexpr modifiers +template> +inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept +{ + //return static_cast(std::round(v / SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion for Eigen vectors (N dimensional points) +template, + int...EigenArgs> +inline Eigen::Matrix, N, EigenArgs...> +scaled(const Eigen::Matrix &v) +{ + return (v / SCALING_FACTOR).template cast(); +} + +// Conversion from arithmetic scaled type to floating point unscaled +template, + class = FloatingOnly> +inline constexpr Tout unscaled(const Tin &v) noexcept +{ + return Tout(v) * Tout(SCALING_FACTOR); +} + +// Unscaling for Eigen vectors. Input base type can be arithmetic, output base +// type can only be floating point. +template, + class = FloatingOnly, + int...EigenArgs> +inline constexpr Eigen::Matrix +unscaled(const Eigen::Matrix &v) noexcept +{ + return v.template cast() * Tout(SCALING_FACTOR); +} + +// Align a coordinate to a grid. The coordinate may be negative, +// the aligned value will never be bigger than the original one. +inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) { + // Current C++ standard defines the result of integer division to be rounded to zero, + // for both positive and negative numbers. Here we want to round down for negative + // numbers as well. + coord_t aligned = (coord < 0) ? + ((coord - spacing + 1) / spacing) * spacing : + (coord / spacing) * spacing; + assert(aligned <= coord); + return aligned; +} +inline Point align_to_grid(Point coord, Point spacing) + { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); } +inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) + { return base + align_to_grid(coord - base, spacing); } +inline Point align_to_grid(Point coord, Point spacing, Point base) + { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } + +} // namespace Slic3r + +// start Boost +#include +#include +namespace boost { namespace polygon { + template <> + struct geometry_concept { using type = point_concept; }; + + template <> + struct point_traits { + using coordinate_type = coord_t; + + static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { + return static_cast(point((orient == HORIZONTAL) ? 0 : 1)); + } + }; + + template <> + struct point_mutable_traits { + using coordinate_type = coord_t; + static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { + point((orient == HORIZONTAL) ? 0 : 1) = value; + } + static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { + return Slic3r::Point(x_value, y_value); + } + }; +} } +// end Boost + +// Serialization through the Cereal library +namespace cereal { +// template void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); } +// template void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2i &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3i &v) { archive(v.x(), v.y(), v.z()); } +// template void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); } +// template void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2f &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3f &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); } + + template void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } + template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + template void load(Archive& archive, Slic3r::Transform3d& m) { archive.loadBinary((char*)m.data(), sizeof(double) * 16); } + template void save(Archive& archive, const Slic3r::Transform3d& m) { archive.saveBinary((char*)m.data(), sizeof(double) * 16); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +} + +// To be able to use Vec<> and Mat<> in range based for loops: +namespace Eigen { +template +T* begin(Slic3r::Mat &mat) { return mat.data(); } + +template +T* end(Slic3r::Mat &mat) { return mat.data() + N * M; } + +template +const T* begin(const Slic3r::Mat &mat) { return mat.data(); } + +template +const T* end(const Slic3r::Mat &mat) { return mat.data() + N * M; } +} // namespace Eigen + +#endif diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 34e3129d9..b88ae9e50 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1,1444 +1,1453 @@ -#include "Model.hpp" -#include "Print.hpp" - -#include - -namespace Slic3r { - -// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new -// in the exact order and with the same IDs. -// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. -// Friend to ModelVolume to allow copying. -// static is not accepted by gcc if declared as a friend of ModelObject. -/* static */ void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_new) -{ - typedef std::pair ModelVolumeWithStatus; - std::vector old_volumes; - old_volumes.reserve(model_object_dst.volumes.size()); - for (const ModelVolume *model_volume : model_object_dst.volumes) - old_volumes.emplace_back(ModelVolumeWithStatus(model_volume, false)); - auto model_volume_lower = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() < mv2.first->id(); }; - auto model_volume_equal = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() == mv2.first->id(); }; - std::sort(old_volumes.begin(), old_volumes.end(), model_volume_lower); - model_object_dst.volumes.clear(); - model_object_dst.volumes.reserve(model_object_new.volumes.size()); - for (const ModelVolume *model_volume_src : model_object_new.volumes) { - ModelVolumeWithStatus key(model_volume_src, false); - auto it = std::lower_bound(old_volumes.begin(), old_volumes.end(), key, model_volume_lower); - if (it != old_volumes.end() && model_volume_equal(*it, key)) { - // The volume was found in the old list. Just copy it. - assert(! it->second); // not consumed yet - it->second = true; - ModelVolume *model_volume_dst = const_cast(it->first); - // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. - assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type()); - model_object_dst.volumes.emplace_back(model_volume_dst); - if (model_volume_dst->is_support_modifier()) { - // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. - model_volume_dst->set_type(model_volume_src->type()); - model_volume_dst->set_transformation(model_volume_src->get_transformation()); - } - assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix())); - } else { - // The volume was not found in the old list. Create a new copy. - assert(model_volume_src->is_support_modifier()); - model_object_dst.volumes.emplace_back(new ModelVolume(*model_volume_src)); - model_object_dst.volumes.back()->set_model_object(&model_object_dst); - } - } - // Release the non-consumed old volumes (those were deleted from the new list). - for (ModelVolumeWithStatus &mv_with_status : old_volumes) - if (! mv_with_status.second) - delete mv_with_status.first; -} - -static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type) -{ - size_t i_src, i_dst; - for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) { - const ModelVolume &mv_src = *model_object_src.volumes[i_src]; - ModelVolume &mv_dst = *model_object_dst.volumes[i_dst]; - if (mv_src.type() != type) { - ++ i_src; - continue; - } - if (mv_dst.type() != type) { - ++ i_dst; - continue; - } - assert(mv_src.id() == mv_dst.id()); - // Copy the ModelVolume data. - mv_dst.name = mv_src.name; - mv_dst.config.assign_config(mv_src.config); - assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id()); - mv_dst.supported_facets.assign(mv_src.supported_facets); - assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); - mv_dst.seam_facets.assign(mv_src.seam_facets); - assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id()); - mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets); - //FIXME what to do with the materials? - // mv_dst.m_material_id = mv_src.m_material_id; - ++ i_src; - ++ i_dst; - } -} - -static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) -{ - assert(lr_dst.size() == lr_src.size()); - auto it_src = lr_src.cbegin(); - for (auto &kvp_dst : lr_dst) { - const auto &kvp_src = *it_src ++; - assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON); - assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); - // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile. - // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON); - kvp_dst.second = kvp_src.second; - } -} - -static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) -{ - typedef Transform3d::Scalar T; - const T *lv = lhs.data(); - const T *rv = rhs.data(); - for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) { - if (*lv < *rv) - return true; - else if (*lv > *rv) - return false; - } - return false; -} - -static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs) -{ - typedef Transform3d::Scalar T; - const T *lv = lhs.data(); - const T *rv = rhs.data(); - for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) - if (*lv != *rv) - return false; - return true; -} - -struct PrintObjectTrafoAndInstances -{ - Transform3d trafo; - PrintInstances instances; - bool operator<(const PrintObjectTrafoAndInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); } -}; - -// Generate a list of trafos and XY offsets for instances of a ModelObject -static std::vector print_objects_from_model_object(const ModelObject &model_object) -{ - std::set trafos; - PrintObjectTrafoAndInstances trafo; - for (ModelInstance *model_instance : model_object.instances) - if (model_instance->is_printable()) { - trafo.trafo = model_instance->get_matrix(); - auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); - // Reset the XY axes of the transformation. - trafo.trafo.data()[12] = 0; - trafo.trafo.data()[13] = 0; - // Search or insert a trafo. - auto it = trafos.emplace(trafo).first; - const_cast(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift }); - } - return std::vector(trafos.begin(), trafos.end()); -} - -// Compare just the layer ranges and their layer heights, not the associated configs. -// Ignore the layer heights if check_layer_heights is false. -static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) -{ - if (lr1.size() != lr2.size()) - return false; - auto it2 = lr2.begin(); - for (const auto &kvp1 : lr1) { - const auto &kvp2 = *it2 ++; - if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON || - std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || - (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) - return false; - } - return true; -} - -// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored. -static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector &va, const std::vector &vb) -{ - auto it_a = va.begin(); - auto it_b = vb.begin(); - while (it_a != va.end() || it_b != vb.end()) { - if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. - ++ it_a; - continue; - } - if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. - ++ it_b; - continue; - } - if (it_a == va.end() || it_b == vb.end()) - // va or vb contains more Tool Changes than the other. - return true; - assert(it_a->type == CustomGCode::ToolChange); - assert(it_b->type == CustomGCode::ToolChange); - if (*it_a != *it_b) - // The two Tool Changes differ. - return true; - ++ it_a; - ++ it_b; - } - // There is no change in custom Tool Changes. - return false; -} - -// Collect changes to print config, account for overrides of extruder retract values by filament presets. -static t_config_option_keys print_config_diffs( - const PrintConfig ¤t_config, - const DynamicPrintConfig &new_full_config, - DynamicPrintConfig &filament_overrides) -{ - const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); - const std::string filament_prefix = "filament_"; - t_config_option_keys print_diff; - for (const t_config_option_key &opt_key : current_config.keys()) { - const ConfigOption *opt_old = current_config.option(opt_key); - assert(opt_old != nullptr); - const ConfigOption *opt_new = new_full_config.option(opt_key); - // assert(opt_new != nullptr); - if (opt_new == nullptr) - //FIXME This may happen when executing some test cases. - continue; - const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; - if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { - // An extruder retract override is available at some of the filament presets. - bool overriden = opt_new->overriden_by(opt_new_filament); - if (overriden || *opt_old != *opt_new) { - auto opt_copy = opt_new->clone(); - opt_copy->apply_override(opt_new_filament); - bool changed = *opt_old != *opt_copy; - if (changed) - print_diff.emplace_back(opt_key); - if (changed || overriden) { - // filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config. - filament_overrides.set_key_value(opt_key, opt_copy); - } else - delete opt_copy; - } - } else if (*opt_new != *opt_old) - print_diff.emplace_back(opt_key); - } - - return print_diff; -} - -// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser. -static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig ¤t_full_config, const DynamicPrintConfig &new_full_config) -{ - t_config_option_keys full_config_diff; - for (const t_config_option_key &opt_key : new_full_config.keys()) { - const ConfigOption *opt_old = current_full_config.option(opt_key); - const ConfigOption *opt_new = new_full_config.option(opt_key); - if (opt_old == nullptr || *opt_new != *opt_old) - full_config_diff.emplace_back(opt_key); - } - return full_config_diff; -} - -// Repository for solving partial overlaps of ModelObject::layer_config_ranges. -// Here the const DynamicPrintConfig* point to the config in ModelObject::layer_config_ranges. -class LayerRanges -{ -public: - struct LayerRange { - t_layer_height_range layer_height_range; - // Config is owned by the associated ModelObject. - const DynamicPrintConfig* config { nullptr }; - - bool operator<(const LayerRange &rhs) const throw() { return this->layer_height_range < rhs.layer_height_range; } - }; - - LayerRanges() = default; - LayerRanges(const t_layer_config_ranges &in) { this->assign(in); } - - // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. - void assign(const t_layer_config_ranges &in) { - m_ranges.clear(); - m_ranges.reserve(in.size()); - // Input ranges are sorted lexicographically. First range trims the other ranges. - coordf_t last_z = 0; - for (const std::pair &range : in) - if (range.first.second > last_z) { - coordf_t min_z = std::max(range.first.first, 0.); - if (min_z > last_z + EPSILON) { - m_ranges.push_back({ t_layer_height_range(last_z, min_z) }); - last_z = min_z; - } - if (range.first.second > last_z + EPSILON) { - const DynamicPrintConfig *cfg = &range.second.get(); - m_ranges.push_back({ t_layer_height_range(last_z, range.first.second), cfg }); - last_z = range.first.second; - } - } - if (m_ranges.empty()) - m_ranges.push_back({ t_layer_height_range(0, DBL_MAX) }); - else if (m_ranges.back().config == nullptr) - m_ranges.back().layer_height_range.second = DBL_MAX; - else - m_ranges.push_back({ t_layer_height_range(m_ranges.back().layer_height_range.second, DBL_MAX) }); - } - - const DynamicPrintConfig* config(const t_layer_height_range &range) const { - auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), LayerRange{ { range.first - EPSILON, range.second - EPSILON } }); - // #ys_FIXME_COLOR - // assert(it != m_ranges.end()); - // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); - // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); - if (it == m_ranges.end() || - std::abs(it->layer_height_range.first - range.first) > EPSILON || - std::abs(it->layer_height_range.second - range.second) > EPSILON ) - return nullptr; // desired range doesn't found - return it == m_ranges.end() ? nullptr : it->config; - } - - std::vector::const_iterator begin() const { return m_ranges.cbegin(); } - std::vector::const_iterator end () const { return m_ranges.cend(); } - size_t size () const { return m_ranges.size(); } - -private: - // Layer ranges with their config overrides and list of volumes with their snug bounding boxes in a given layer range. - std::vector m_ranges; -}; - -// To track Model / ModelObject updates between the front end and back end, including layer height ranges, their configs, -// and snug bounding boxes of ModelVolumes. -struct ModelObjectStatus { - enum Status { - Unknown, - Old, - New, - Moved, - Deleted, - }; - - enum class PrintObjectRegionsStatus { - Invalid, - Valid, - PartiallyValid, - }; - - ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} - ~ModelObjectStatus() { if (print_object_regions) print_object_regions->ref_cnt_dec(); } - - // Key of the set. - ObjectID id; - // Status of this ModelObject with id on apply(). - Status status; - // PrintObjects to be generated for this ModelObject including their base transformation. - std::vector print_instances; - // Regions shared by the associated PrintObjects. - PrintObjectRegions *print_object_regions { nullptr }; - // Status of the above. - PrintObjectRegionsStatus print_object_regions_status { PrintObjectRegionsStatus::Invalid }; - - // Search by id. - bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } -}; - -struct ModelObjectStatusDB -{ - void add(const ModelObject &model_object, const ModelObjectStatus::Status status) { - assert(db.find(ModelObjectStatus(model_object.id())) == db.end()); - db.emplace(model_object.id(), status); - } - - bool add_if_new(const ModelObject &model_object, const ModelObjectStatus::Status status) { - auto it = db.find(ModelObjectStatus(model_object.id())); - if (it == db.end()) { - db.emplace_hint(it, model_object.id(), status); - return true; - } - return false; - } - - const ModelObjectStatus& get(const ModelObject &model_object) { - auto it = db.find(ModelObjectStatus(model_object.id())); - assert(it != db.end()); - return *it; - } - - const ModelObjectStatus& reuse(const ModelObject &model_object) { - const ModelObjectStatus &result = this->get(model_object); - assert(result.status != ModelObjectStatus::Deleted); - return result; - } - - std::set db; -}; - -struct PrintObjectStatus { - enum Status { - Unknown, - Deleted, - Reused, - New - }; - - PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : - id(print_object->model_object()->id()), - print_object(print_object), - trafo(print_object->trafo()), - status(status) {} - PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} - - // ID of the ModelObject & PrintObject - ObjectID id; - // Pointer to the old PrintObject - PrintObject *print_object; - // Trafo generated with model_object->world_matrix(true) - Transform3d trafo; - Status status; - - // Search by id. - bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } -}; - -class PrintObjectStatusDB { -public: - using iterator = std::multiset::iterator; - using const_iterator = std::multiset::const_iterator; - - PrintObjectStatusDB(const PrintObjectPtrs &print_objects) { - for (PrintObject *print_object : print_objects) - m_db.emplace(PrintObjectStatus(print_object)); - } - - struct iterator_range : std::pair - { - using std::pair::pair; - iterator_range(const std::pair in) : std::pair(in) {} - - const_iterator begin() throw() { return this->first; } - const_iterator end() throw() { return this->second; } - }; - - iterator_range get_range(const ModelObject &model_object) const { - return m_db.equal_range(PrintObjectStatus(model_object.id())); - } - - iterator_range get_range(const ModelObjectStatus &model_object_status) const { - return m_db.equal_range(PrintObjectStatus(model_object_status.id)); - } - - size_t count(const ModelObject &model_object) { - return m_db.count(PrintObjectStatus(model_object.id())); - } - - std::multiset::iterator begin() { return m_db.begin(); } - std::multiset::iterator end() { return m_db.end(); } - - void clear() { - m_db.clear(); - } - -private: - std::multiset m_db; -}; - -static inline bool model_volume_solid_or_modifier(const ModelVolume &mv) -{ - ModelVolumeType type = mv.type(); - return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER; -} - -static inline Transform3f trafo_for_bbox(const Transform3d &object_trafo, const Transform3d &volume_trafo) -{ - Transform3d m = object_trafo * volume_trafo; - m.translation().x() = 0.; - m.translation().y() = 0.; - return m.cast(); -} - -static inline bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d &t1, const Transform3d &t2) -{ - if (std::abs(t1.translation().z() - t2.translation().z()) > EPSILON) - // One of the object is higher than the other above the build plate (or below the build plate). - return false; - Matrix3d m1 = t1.matrix().block<3, 3>(0, 0); - Matrix3d m2 = t2.matrix().block<3, 3>(0, 0); - Matrix3d m = m2.inverse() * m1; - Vec3d z = m.block<3, 1>(0, 2); - if (std::abs(z.x()) > EPSILON || std::abs(z.y()) > EPSILON || std::abs(z.z() - 1.) > EPSILON) - // Z direction or length changed. - return false; - // Z still points in the same direction and it has the same length. - Vec3d x = m.block<3, 1>(0, 0); - Vec3d y = m.block<3, 1>(0, 1); - if (std::abs(x.z()) > EPSILON || std::abs(y.z()) > EPSILON) - return false; - double lx2 = x.squaredNorm(); - double ly2 = y.squaredNorm(); - if (lx2 - 1. > EPSILON * EPSILON || ly2 - 1. > EPSILON * EPSILON) - return false; - // Verify whether the vectors x, y are still perpendicular. - double d = x.dot(y); - return std::abs(d * d) < EPSILON * lx2 * ly2; -} - -static PrintObjectRegions::BoundingBox transformed_its_bbox2d(const indexed_triangle_set &its, const Transform3f &m, float offset) -{ - assert(! its.indices.empty()); - - PrintObjectRegions::BoundingBox bbox(m * its.vertices[its.indices.front()(0)]); - for (const stl_triangle_vertex_indices &tri : its.indices) - for (int i = 0; i < 3; ++ i) - bbox.extend(m * its.vertices[tri(i)]); - bbox.min() -= Vec3f(offset, offset, float(EPSILON)); - bbox.max() += Vec3f(offset, offset, float(EPSILON)); - return bbox; -} - -static void transformed_its_bboxes_in_z_ranges( - const indexed_triangle_set &its, - const Transform3f &m, - const std::vector &z_ranges, - std::vector> &bboxes, - const float offset) -{ - bboxes.assign(z_ranges.size(), std::make_pair(PrintObjectRegions::BoundingBox(), false)); - for (const stl_triangle_vertex_indices &tri : its.indices) { - const Vec3f pts[3] = { m * its.vertices[tri(0)], m * its.vertices[tri(1)], m * its.vertices[tri(2)] }; - for (size_t irange = 0; irange < z_ranges.size(); ++ irange) { - const t_layer_height_range &z_range = z_ranges[irange]; - std::pair &bbox = bboxes[irange]; - auto bbox_extend = [&bbox](const Vec3f& p) { - if (bbox.second) { - bbox.first.extend(p); - } else { - bbox.first.min() = bbox.first.max() = p; - bbox.second = true; - } - }; - int iprev = 2; - for (int iedge = 0; iedge < 3; ++ iedge) { - const Vec3f *p1 = &pts[iprev]; - const Vec3f *p2 = &pts[iedge]; - // Sort the edge points by Z. - if (p1->z() > p2->z()) - std::swap(p1, p2); - if (p2->z() <= z_range.first || p1->z() >= z_range.second) { - // Out of this slab. - } else if (p1->z() < z_range.first) { - if (p1->z() > z_range.second) { - // Two intersections. - float zspan = p2->z() - p1->z(); - float t1 = (z_range.first - p1->z()) / zspan; - float t2 = (z_range.second - p1->z()) / zspan; - Vec2f p = to_2d(*p1); - Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); - bbox_extend(to_3d((p + v * t1).eval(), float(z_range.first))); - bbox_extend(to_3d((p + v * t2).eval(), float(z_range.second))); - } else { - // Single intersection with the lower limit. - float t = (z_range.first - p1->z()) / (p2->z() - p1->z()); - Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); - bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.first))); - bbox_extend(*p2); - } - } else if (p2->z() > z_range.second) { - // Single intersection with the upper limit. - float t = (z_range.second - p1->z()) / (p2->z() - p1->z()); - Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); - bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.second))); - bbox_extend(*p1); - } else { - // Both points are inside. - bbox_extend(*p1); - bbox_extend(*p2); - } - iprev = iedge; - } - } - } - - for (std::pair &bbox : bboxes) { - bbox.first.min() -= Vec3f(offset, offset, float(EPSILON)); - bbox.first.max() += Vec3f(offset, offset, float(EPSILON)); - } -} - -// Last PrintObject for this print_object_regions has been fully invalidated (deleted). -// Keep print_object_regions, but delete those volumes, which were either removed from new_volumes, or which rotated or scaled, so they need -// their bounding boxes to be recalculated. -void print_objects_regions_invalidate_keep_some_volumes(PrintObjectRegions &print_object_regions, ModelVolumePtrs old_volumes, ModelVolumePtrs new_volumes) -{ - print_object_regions.all_regions.clear(); - - model_volumes_sort_by_id(old_volumes); - model_volumes_sort_by_id(new_volumes); - - size_t i_cached_volume = 0; - size_t last_cached_volume = 0; - size_t i_old = 0; - for (size_t i_new = 0; i_new < new_volumes.size(); ++ i_new) - if (model_volume_solid_or_modifier(*new_volumes[i_new])) { - for (; i_old < old_volumes.size(); ++ i_old) - if (old_volumes[i_old]->id() >= new_volumes[i_new]->id()) - break; - if (i_old != old_volumes.size() && old_volumes[i_old]->id() == new_volumes[i_new]->id()) { - if (old_volumes[i_old]->get_matrix().isApprox(new_volumes[i_new]->get_matrix())) { - // Reuse the volume. - for (; print_object_regions.cached_volume_ids[i_cached_volume] < old_volumes[i_old]->id(); ++ i_cached_volume) - assert(i_cached_volume < print_object_regions.cached_volume_ids.size()); - assert(i_cached_volume < print_object_regions.cached_volume_ids.size() && print_object_regions.cached_volume_ids[i_cached_volume] == old_volumes[i_old]->id()); - print_object_regions.cached_volume_ids[last_cached_volume ++] = print_object_regions.cached_volume_ids[i_cached_volume ++]; - } else { - // Don't reuse the volume. - } - } - } - print_object_regions.cached_volume_ids.erase(print_object_regions.cached_volume_ids.begin() + last_cached_volume, print_object_regions.cached_volume_ids.end()); -} - -// Find a bounding box of a volume's part intersecting layer_range. Such a bounding box will likely be smaller in XY than the full bounding box, -// thus it will intersect with lower number of other volumes. -const PrintObjectRegions::BoundingBox* find_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const ModelVolume &volume) -{ - auto it = lower_bound_by_predicate(layer_range.volumes.begin(), layer_range.volumes.end(), [&volume](const PrintObjectRegions::VolumeExtents &l){ return l.volume_id < volume.id(); }); - return it != layer_range.volumes.end() && it->volume_id == volume.id() ? &it->bbox : nullptr; -} - -// Find a bounding box of a topmost printable volume referenced by this modifier given this_region_id. -PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const int this_region_id) -{ - // Find the top-most printable volume of this modifier, or the printable volume itself. - const PrintObjectRegions::VolumeRegion &this_region = layer_range.volume_regions[this_region_id]; - const PrintObjectRegions::BoundingBox *this_extents = find_volume_extents(layer_range, *this_region.model_volume); - assert(this_extents); - PrintObjectRegions::BoundingBox out { *this_extents }; - if (! this_region.model_volume->is_model_part()) - for (int parent_region_id = this_region.parent;;) { - assert(parent_region_id >= 0); - const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; - const PrintObjectRegions::BoundingBox *parent_extents = find_volume_extents(layer_range, *parent_region.model_volume); - assert(parent_extents); - out.extend(*parent_extents); - if (parent_region.model_volume->is_model_part()) - break; - parent_region_id = parent_region.parent; - } - return out; -} - -PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders); - -void print_region_ref_inc(PrintRegion &r) { ++ r.m_ref_cnt; } -void print_region_ref_reset(PrintRegion &r) { r.m_ref_cnt = 0; } -int print_region_ref_cnt(const PrintRegion &r) { return r.m_ref_cnt; } - -// Verify whether the PrintRegions of a PrintObject are still valid, possibly after updating the region configs. -// Before region configs are updated, callback_invalidate() is called to possibly stop background processing. -// Returns false if this object needs to be resliced because regions were merged or split. -bool verify_update_print_object_regions( - ModelVolumePtrs model_volumes, - const PrintRegionConfig &default_region_config, - size_t num_extruders, - const std::vector &painting_extruders, - PrintObjectRegions &print_object_regions, - const std::function &callback_invalidate) -{ - // Sort by ModelVolume ID. - model_volumes_sort_by_id(model_volumes); - - for (std::unique_ptr ®ion : print_object_regions.all_regions) - print_region_ref_reset(*region); - - // Verify and / or update PrintRegions produced by ModelVolumes, layer range modifiers, modifier volumes. - for (PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { - // Each modifier ModelVolume intersecting this layer_range shall be referenced here at least once if it intersects some - // printable ModelVolume at this layer_range even if it does not modify its overlapping printable ModelVolume configuration yet. - // VolumeRegions reference ModelVolumes in layer_range.volume_regions the order they are stored in ModelObject volumes. - // Remember whether a given modifier ModelVolume was visited already. - auto it_model_volume_modifier_last = model_volumes.end(); - for (PrintObjectRegions::VolumeRegion ®ion : layer_range.volume_regions) - if (region.model_volume->is_model_part() || region.model_volume->is_modifier()) { - auto it_model_volume = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [®ion](const ModelVolume *l){ return l->id() < region.model_volume->id(); }); - assert(it_model_volume != model_volumes.end() && (*it_model_volume)->id() == region.model_volume->id()); - if (region.model_volume->is_modifier() && it_model_volume != it_model_volume_modifier_last) { - // A modifier ModelVolume is visited for the first time. - // A visited modifier may not have had parent volume_regions created overlapping with some model parts or modifiers, - // if the visited modifier did not modify their properties. Now the visited modifier's configuration may have changed, - // which may require new regions to be created. - it_model_volume_modifier_last = it_model_volume; - int next_region_id = int(®ion - layer_range.volume_regions.data()); - const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, *region.model_volume); - assert(bbox); - for (int parent_region_id = next_region_id - 1; parent_region_id >= 0; -- parent_region_id) { - const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; - assert(parent_region.model_volume != region.model_volume); - if (parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) { - // volume_regions are produced in decreasing order of parent volume_regions ids. - // Some regions may not have been generated the last time by generate_print_object_regions(). - assert(next_region_id == int(layer_range.volume_regions.size()) || - layer_range.volume_regions[next_region_id].model_volume != region.model_volume || - layer_range.volume_regions[next_region_id].parent <= parent_region_id); - if (next_region_id < int(layer_range.volume_regions.size()) && - layer_range.volume_regions[next_region_id].model_volume == region.model_volume && - layer_range.volume_regions[next_region_id].parent == parent_region_id) { - // A parent region is already overridden. - ++ next_region_id; - } else if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) - // Such parent region does not exist. If it is needed, then we need to reslice. - // Only create new region for a modifier, which actually modifies config of it's parent. - if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders); - config != parent_region.region->config()) - // This modifier newly overrides a region, which it did not before. We need to reslice. - return false; - } - } - } - PrintRegionConfig cfg = region.parent == -1 ? - region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders) : - region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders); - if (cfg != region.region->config()) { - // Region configuration changed. - if (print_region_ref_cnt(*region.region) == 0) { - // Region is referenced for the first time. Just change its parameters. - // Stop the background process before assigning new configuration to the regions. - t_config_option_keys diff = region.region->config().diff(cfg); - callback_invalidate(region.region->config(), cfg, diff); - region.region->config_apply_only(cfg, diff, false); - } else { - // Region is referenced multiple times, thus the region is being split. We need to reslice. - return false; - } - } - print_region_ref_inc(*region.region); - } - } - - // Verify and / or update PrintRegions produced by color painting. - for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) - for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) { - const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent]; - PrintRegionConfig cfg = parent_region.region->config(); - cfg.perimeter_extruder.value = region.extruder_id; - cfg.solid_infill_extruder.value = region.extruder_id; - cfg.infill_extruder.value = region.extruder_id; - if (cfg != region.region->config()) { - // Region configuration changed. - if (print_region_ref_cnt(*region.region) == 0) { - // Region is referenced for the first time. Just change its parameters. - // Stop the background process before assigning new configuration to the regions. - t_config_option_keys diff = region.region->config().diff(cfg); - callback_invalidate(region.region->config(), cfg, diff); - region.region->config_apply_only(cfg, diff, false); - } else { - // Region is referenced multiple times, thus the region is being split. We need to reslice. - return false; - } - } - print_region_ref_inc(*region.region); - } - - // Lastly verify, whether some regions were not merged. - { - std::vector regions; - regions.reserve(print_object_regions.all_regions.size()); - for (std::unique_ptr ®ion : print_object_regions.all_regions) { - assert(print_region_ref_cnt(*region) > 0); - regions.emplace_back(&(*region.get())); - } - std::sort(regions.begin(), regions.end(), [](const PrintRegion *l, const PrintRegion *r){ return l->config_hash() < r->config_hash(); }); - for (size_t i = 0; i < regions.size(); ++ i) { - size_t hash = regions[i]->config_hash(); - size_t j = i; - for (++ j; j < regions.size() && regions[j]->config_hash() == hash; ++ j) - if (regions[i]->config() == regions[j]->config()) { - // Regions were merged. We need to reslice. - return false; - } - } - } - - return true; -} - -// Update caches of volume bounding boxes. -void update_volume_bboxes( - std::vector &layer_ranges, - std::vector &cached_volume_ids, - ModelVolumePtrs model_volumes, - const Transform3d &object_trafo, - const float offset) -{ - // output will be sorted by the order of model_volumes sorted by their ObjectIDs. - model_volumes_sort_by_id(model_volumes); - - if (layer_ranges.size() == 1) { - PrintObjectRegions::LayerRangeRegions &layer_range = layer_ranges.front(); - std::vector volumes_old(std::move(layer_range.volumes)); - layer_range.volumes.reserve(model_volumes.size()); - for (const ModelVolume *model_volume : model_volumes) - if (model_volume_solid_or_modifier(*model_volume)) { - if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) { - auto it = lower_bound_by_predicate(volumes_old.begin(), volumes_old.end(), [model_volume](PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); }); - if (it != volumes_old.end() && it->volume_id == model_volume->id()) - layer_range.volumes.emplace_back(*it); - } else - layer_range.volumes.push_back({ model_volume->id(), - transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) }); - } - } else { - std::vector> volumes_old; - if (cached_volume_ids.empty()) - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) - layer_range.volumes.clear(); - else { - volumes_old.reserve(layer_ranges.size()); - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) - volumes_old.emplace_back(std::move(layer_range.volumes)); - } - - std::vector> bboxes; - std::vector ranges; - ranges.reserve(layer_ranges.size()); - for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) { - t_layer_height_range r = layer_range.layer_height_range; - r.first -= EPSILON; - r.second += EPSILON; - ranges.emplace_back(r); - } - for (const ModelVolume *model_volume : model_volumes) - if (model_volume_solid_or_modifier(*model_volume)) { - if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) { - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) { - const auto &vold = volumes_old[&layer_range - layer_ranges.data()]; - auto it = lower_bound_by_predicate(vold.begin(), vold.end(), [model_volume](const PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); }); - if (it != vold.end() && it->volume_id == model_volume->id()) - layer_range.volumes.emplace_back(*it); - } - } else { - transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset); - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) - if (auto &bbox = bboxes[&layer_range - layer_ranges.data()]; bbox.second) - layer_range.volumes.push_back({ model_volume->id(), bbox.first }); - } - } - } - - cached_volume_ids.clear(); - cached_volume_ids.reserve(model_volumes.size()); - for (const ModelVolume *v : model_volumes) - if (model_volume_solid_or_modifier(*v)) - cached_volume_ids.emplace_back(v->id()); -} - -// Either a fresh PrintObject, or PrintObject regions were invalidated (merged, split). -// Generate PrintRegions from scratch. -static PrintObjectRegions* generate_print_object_regions( - PrintObjectRegions *print_object_regions_old, - const ModelVolumePtrs &model_volumes, - const LayerRanges &model_layer_ranges, - const PrintRegionConfig &default_region_config, - const Transform3d &trafo, - size_t num_extruders, - const float xy_size_compensation, - const std::vector &painting_extruders) -{ - // Reuse the old object or generate a new one. - auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique(); - auto &all_regions = out->all_regions; - auto &layer_ranges_regions = out->layer_ranges; - - all_regions.clear(); - - bool reuse_old = print_object_regions_old && !print_object_regions_old->layer_ranges.empty(); - - if (reuse_old) { - // Reuse old bounding boxes of some ModelVolumes and their ranges. - // Verify that the old ranges match the new ranges. - assert(model_layer_ranges.size() == layer_ranges_regions.size()); - for (const auto &range : model_layer_ranges) { - PrintObjectRegions::LayerRangeRegions &r = layer_ranges_regions[&range - &*model_layer_ranges.begin()]; - assert(range.layer_height_range == r.layer_height_range); - // If model::assign_copy() is called, layer_ranges_regions is copied thus the pointers to configs are lost. - r.config = range.config; - r.volume_regions.clear(); - r.painted_regions.clear(); - } - } else { - out->trafo_bboxes = trafo; - layer_ranges_regions.reserve(model_layer_ranges.size()); - for (const auto &range : model_layer_ranges) - layer_ranges_regions.push_back({ range.layer_height_range, range.config }); - } - - const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); - update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation)); - - std::vector region_set; - auto get_create_region = [®ion_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* { - size_t hash = config.hash(); - auto it = Slic3r::lower_bound_by_predicate(region_set.begin(), region_set.end(), [&config, hash](const PrintRegion* l) { - return l->config_hash() < hash || (l->config_hash() == hash && l->config() < config); }); - if (it != region_set.end() && (*it)->config_hash() == hash && (*it)->config() == config) - return *it; - // Insert into a sorted array, it has O(n) complexity, but the calling algorithm has an O(n^2*log(n)) complexity anyways. - all_regions.emplace_back(std::make_unique(std::move(config), hash, int(all_regions.size()))); - PrintRegion *region = all_regions.back().get(); - region_set.emplace(it, region); - return region; - }; - - // Chain the regions in the order they are stored in the volumes list. - for (int volume_id = 0; volume_id < int(model_volumes.size()); ++ volume_id) { - const ModelVolume &volume = *model_volumes[volume_id]; - if (model_volume_solid_or_modifier(volume)) { - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) - if (const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, volume); bbox) { - if (volume.is_model_part()) { - // Add a model volume, assign an existing region or generate a new one. - layer_range.volume_regions.push_back({ - &volume, -1, - get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders)), - bbox - }); - } else if (volume.is_negative_volume()) { - // Add a negative (subtractor) volume. Such volume has neither region nor parent volume assigned. - layer_range.volume_regions.push_back({ &volume, -1, nullptr, bbox }); - } else { - assert(volume.is_modifier()); - // Modifiers may be chained one over the other. Check for overlap, merge DynamicPrintConfigs. - bool added = false; - int parent_model_part_id = -1; - for (int parent_region_id = int(layer_range.volume_regions.size()) - 1; parent_region_id >= 0; -- parent_region_id) { - const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; - const ModelVolume &parent_volume = *parent_region.model_volume; - if (parent_volume.is_model_part() || parent_volume.is_modifier()) - if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) { - // Only create new region for a modifier, which actually modifies config of it's parent. - if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders); - config != parent_region.region->config()) { - added = true; - layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox }); - } else if (parent_model_part_id == -1 && parent_volume.is_model_part()) - parent_model_part_id = parent_region_id; - } - } - if (! added && parent_model_part_id >= 0) - // This modifier does not override any printable volume's configuration, however it may in the future. - // Store it so that verify_update_print_object_regions() will handle this modifier correctly if its configuration changes. - layer_range.volume_regions.push_back({ &volume, parent_model_part_id, layer_range.volume_regions[parent_model_part_id].region, bbox }); - } - } - } - } - - // Finally add painting regions. - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) { - for (unsigned int painted_extruder_id : painting_extruders) - for (int parent_region_id = 0; parent_region_id < int(layer_range.volume_regions.size()); ++ parent_region_id) - if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; - parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) { - PrintRegionConfig cfg = parent_region.region->config(); - cfg.perimeter_extruder.value = painted_extruder_id; - cfg.solid_infill_extruder.value = painted_extruder_id; - cfg.infill_extruder.value = painted_extruder_id; - layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))}); - } - // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation. - std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) { - int lid = layer_range.volume_regions[l.parent].region->print_object_region_id(); - int rid = layer_range.volume_regions[r.parent].region->print_object_region_id(); - return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); }); - } - - return out.release(); -} - -Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) -{ -#ifdef _DEBUG - check_model_ids_validity(model); -#endif /* _DEBUG */ - - // Normalize the config. - new_full_config.option("print_settings_id", true); - new_full_config.option("filament_settings_id", true); - new_full_config.option("printer_settings_id", true); - new_full_config.option("physical_printer_settings_id", true); - new_full_config.normalize_fdm(); - - // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. - DynamicPrintConfig filament_overrides; - t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides); - t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config); - // Collect changes to object and region configs. - t_config_option_keys object_diff = m_default_object_config.diff(new_full_config); - t_config_option_keys region_diff = m_default_region_config.diff(new_full_config); - - // Do not use the ApplyStatus as we will use the max function when updating apply_status. - unsigned int apply_status = APPLY_STATUS_UNCHANGED; - auto update_apply_status = [&apply_status](bool invalidated) - { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; - if (! (print_diff.empty() && object_diff.empty() && region_diff.empty())) - update_apply_status(false); - - // Grab the lock for the Print / PrintObject milestones. - std::scoped_lock lock(this->state_mutex()); - - // The following call may stop the background processing. - if (! print_diff.empty()) - update_apply_status(this->invalidate_state_by_config_options(new_full_config, print_diff)); - - // Apply variables to placeholder parser. The placeholder parser is used by G-code export, - // which should be stopped if print_diff is not empty. - size_t num_extruders = m_config.nozzle_diameter.size(); - bool num_extruders_changed = false; - if (! full_config_diff.empty()) { - update_apply_status(this->invalidate_step(psGCodeExport)); - m_placeholder_parser.clear_config(); - // Set the profile aliases for the PrintBase::output_filename() - m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); - m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); - m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); - m_placeholder_parser.set("physical_printer_preset", new_full_config.option("physical_printer_settings_id")->clone()); - // We want the filament overrides to be applied over their respective extruder parameters by the PlaceholderParser. - // see "Placeholders do not respect filament overrides." GH issue #3649 - m_placeholder_parser.apply_config(filament_overrides); - // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. - m_config.apply_only(new_full_config, print_diff, true); - //FIXME use move semantics once ConfigBase supports it. - // Some filament_overrides may contain values different from new_full_config, but equal to m_config. - // As long as these config options don't reallocate memory when copying, we are safe overriding a value, which is in use by a worker thread. - m_config.apply(filament_overrides); - // Handle changes to object config defaults - m_default_object_config.apply_only(new_full_config, object_diff, true); - // Handle changes to regions config defaults - m_default_region_config.apply_only(new_full_config, region_diff, true); - m_full_print_config = std::move(new_full_config); - if (num_extruders != m_config.nozzle_diameter.size()) { - num_extruders = m_config.nozzle_diameter.size(); - num_extruders_changed = true; - } - } - - ModelObjectStatusDB model_object_status_db; - - // 1) Synchronize model objects. - bool print_regions_reshuffled = false; - if (model.id() != m_model.id()) { - // Kill everything, initialize from scratch. - // Stop background processing. - this->call_cancel_callback(); - update_apply_status(this->invalidate_all_steps()); - for (PrintObject *object : m_objects) { - model_object_status_db.add(*object->model_object(), ModelObjectStatus::Deleted); - update_apply_status(object->invalidate_all_steps()); - delete object; - } - m_objects.clear(); - print_regions_reshuffled = true; - m_model.assign_copy(model); - for (const ModelObject *model_object : m_model.objects) - model_object_status_db.add(*model_object, ModelObjectStatus::New); - } else { - if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { - update_apply_status(num_extruders_changed || - // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. - //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable - // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. - (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ? - // The Tool Ordering and the Wipe Tower are no more valid. - this->invalidate_steps({ psWipeTower, psGCodeExport }) : - // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering. - this->invalidate_step(psGCodeExport)); - m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; - } - if (model_object_list_equal(m_model, model)) { - // The object list did not change. - for (const ModelObject *model_object : m_model.objects) - model_object_status_db.add(*model_object, ModelObjectStatus::Old); - } else if (model_object_list_extended(m_model, model)) { - // Add new objects. Their volumes and configs will be synchronized later. - update_apply_status(this->invalidate_step(psGCodeExport)); - for (const ModelObject *model_object : m_model.objects) - model_object_status_db.add(*model_object, ModelObjectStatus::Old); - for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { - model_object_status_db.add(*model.objects[i], ModelObjectStatus::New); - m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i])); - m_model.objects.back()->set_model(&m_model); - } - } else { - // Reorder the objects, add new objects. - // First stop background processing before shuffling or deleting the PrintObjects in the object list. - this->call_cancel_callback(); - update_apply_status(this->invalidate_step(psGCodeExport)); - // Second create a new list of objects. - std::vector model_objects_old(std::move(m_model.objects)); - m_model.objects.clear(); - m_model.objects.reserve(model.objects.size()); - auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); }; - std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower); - for (const ModelObject *mobj : model.objects) { - auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower); - if (it == model_objects_old.end() || (*it)->id() != mobj->id()) { - // New ModelObject added. - m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); - m_model.objects.back()->set_model(&m_model); - model_object_status_db.add(*mobj, ModelObjectStatus::New); - } else { - // Existing ModelObject re-added (possibly moved in the list). - m_model.objects.emplace_back(*it); - model_object_status_db.add(*mobj, ModelObjectStatus::Moved); - } - } - bool deleted_any = false; - for (ModelObject *&model_object : model_objects_old) - if (model_object_status_db.add_if_new(*model_object, ModelObjectStatus::Deleted)) - deleted_any = true; - else - // Do not delete this ModelObject instance. - model_object = nullptr; - if (deleted_any) { - // Delete PrintObjects of the deleted ModelObjects. - PrintObjectPtrs print_objects_old = std::move(m_objects); - m_objects.clear(); - m_objects.reserve(print_objects_old.size()); - for (PrintObject *print_object : print_objects_old) { - const ModelObjectStatus &status = model_object_status_db.get(*print_object->model_object()); - if (status.status == ModelObjectStatus::Deleted) { - update_apply_status(print_object->invalidate_all_steps()); - delete print_object; - } else - m_objects.emplace_back(print_object); - } - for (ModelObject *model_object : model_objects_old) - delete model_object; - print_regions_reshuffled = true; - } - } - } - - // 2) Map print objects including their transformation matrices. - PrintObjectStatusDB print_object_status_db(m_objects); - - // 3) Synchronize ModelObjects & PrintObjects. - const std::initializer_list solid_or_modifier_types { ModelVolumeType::MODEL_PART, ModelVolumeType::NEGATIVE_VOLUME, ModelVolumeType::PARAMETER_MODIFIER }; - for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { - ModelObject &model_object = *m_model.objects[idx_model_object]; - ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(model_object)); - const ModelObject &model_object_new = *model.objects[idx_model_object]; - if (model_object_status.status == ModelObjectStatus::New) - // PrintObject instances will be added in the next loop. - continue; - // Update the ModelObject instance, possibly invalidate the linked PrintObjects. - assert(model_object_status.status == ModelObjectStatus::Old || model_object_status.status == ModelObjectStatus::Moved); - // Check whether a model part volume was added or removed, their transformations or order changed. - // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. - bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) || - model_mmu_segmentation_data_changed(model_object, model_object_new) || - (model_object_new.is_mm_painted() && num_extruders_changed); - bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || - model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); - bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty()); - bool model_origin_translation_differ = model_object.origin_translation != model_object_new.origin_translation; - auto print_objects_range = print_object_status_db.get_range(model_object); - // The list actually can be empty if all instances are out of the print bed. - //assert(print_objects_range.begin() != print_objects_range.end()); - // All PrintObjects in print_objects_range shall point to the same prints_objects_regions - if (print_objects_range.begin() != print_objects_range.end()) { - model_object_status.print_object_regions = print_objects_range.begin()->print_object->m_shared_regions; - model_object_status.print_object_regions->ref_cnt_inc(); - } - if (solid_or_modifier_differ || model_origin_translation_differ || layer_height_ranges_differ || - ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile)) { - // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. - model_object_status.print_object_regions_status = - model_object_status.print_object_regions == nullptr || model_origin_translation_differ || layer_height_ranges_differ ? - // Drop print_objects_regions. - ModelObjectStatus::PrintObjectRegionsStatus::Invalid : - // Reuse bounding boxes of print_objects_regions for ModelVolumes with unmodified transformation. - ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; - for (const PrintObjectStatus &print_object_status : print_objects_range) { - update_apply_status(print_object_status.print_object->invalidate_all_steps()); - const_cast(print_object_status).status = PrintObjectStatus::Deleted; - } - if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid) - // Drop everything from PrintObjectRegions but those VolumeExtents (of their particular ModelVolumes) that are still valid. - print_objects_regions_invalidate_keep_some_volumes(*model_object_status.print_object_regions, model_object.volumes, model_object_new.volumes); - else if (model_object_status.print_object_regions != nullptr) - model_object_status.print_object_regions->clear(); - // Copy content of the ModelObject including its ID, do not change the parent. - model_object.assign_copy(model_object_new); - } else { - model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Valid; - if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) { - // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. - if (supports_differ) { - this->call_cancel_callback(); - update_apply_status(false); - } - // Invalidate just the supports step. - for (const PrintObjectStatus &print_object_status : print_objects_range) - update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial)); - if (supports_differ) { - // Copy just the support volumes. - model_volume_list_update_supports(model_object, model_object_new); - } - } else if (model_custom_seam_data_changed(model_object, model_object_new)) { - update_apply_status(this->invalidate_step(psGCodeExport)); - } - } - if (! solid_or_modifier_differ) { - // Synchronize Object's config. - bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config); - if (object_config_changed) - model_object.config.assign_config(model_object_new.config); - if (! object_diff.empty() || object_config_changed || num_extruders_changed) { - PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); - for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) { - t_config_option_keys diff = print_object_status.print_object->config().diff(new_config); - if (! diff.empty()) { - update_apply_status(print_object_status.print_object->invalidate_state_by_config_options(print_object_status.print_object->config(), new_config, diff)); - print_object_status.print_object->config_apply_only(new_config, diff, true); - } - } - } - // Synchronize (just copy) the remaining data of ModelVolumes (name, config, custom supports data). - //FIXME What to do with m_material_id? - model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); - model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); - layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); - // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. - model_object.name = model_object_new.name; - model_object.input_file = model_object_new.input_file; - // Only refresh ModelInstances if there is any change. - if (model_object.instances.size() != model_object_new.instances.size() || - ! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), [](auto l, auto r){ return l->id() == r->id(); })) { - // G-code generator accesses model_object.instances to generate sequential print ordering matching the Plater object list. - update_apply_status(this->invalidate_step(psGCodeExport)); - model_object.clear_instances(); - model_object.instances.reserve(model_object_new.instances.size()); - for (const ModelInstance *model_instance : model_object_new.instances) { - model_object.instances.emplace_back(new ModelInstance(*model_instance)); - model_object.instances.back()->set_model_object(&model_object); - } - } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), - [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable && - l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) { - // If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid. - // This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only. - model_object.invalidate_bounding_box(); - // Synchronize the content of instances. - auto new_instance = model_object_new.instances.begin(); - for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) { - (*old_instance)->set_transformation((*new_instance)->get_transformation()); - (*old_instance)->print_volume_state = (*new_instance)->print_volume_state; - (*old_instance)->printable = (*new_instance)->printable; - } - } - } - } - - // 4) Generate PrintObjects from ModelObjects and their instances. - { - PrintObjectPtrs print_objects_new; - print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); - bool new_objects = false; - // Walk over all new model objects and check, whether there are matching PrintObjects. - for (ModelObject *model_object : m_model.objects) { - ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(*model_object)); - model_object_status.print_instances = print_objects_from_model_object(*model_object); - std::vector old; - old.reserve(print_object_status_db.count(*model_object)); - for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object)) - if (print_object_status.status != PrintObjectStatus::Deleted) - old.emplace_back(&print_object_status); - // Generate a list of trafos and XY offsets for instances of a ModelObject - // Producing the config for PrintObject on demand, caching it at print_object_last. - const PrintObject *print_object_last = nullptr; - auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders](PrintObject *print_object) { - print_object->config_apply(print_object_last ? - print_object_last->config() : - PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders)); - print_object_last = print_object; - }; - if (old.empty()) { - // Simple case, just generate new instances. - for (PrintObjectTrafoAndInstances &print_instances : model_object_status.print_instances) { - PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances)); - print_object_apply_config(print_object); - print_objects_new.emplace_back(print_object); - // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); - new_objects = true; - } - continue; - } - // Complex case, try to merge the two lists. - // Sort the old lexicographically by their trafos. - std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); }); - // Merge the old / new lists. - auto it_old = old.begin(); - for (PrintObjectTrafoAndInstances &new_instances : model_object_status.print_instances) { - for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old); - if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { - // This is a new instance (or a set of instances with the same trafo). Just add it. - PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances)); - print_object_apply_config(print_object); - print_objects_new.emplace_back(print_object); - // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); - new_objects = true; - if (it_old != old.end()) - const_cast(*it_old)->status = PrintObjectStatus::Deleted; - } else { - // The PrintObject already exists and the copies differ. - PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances)); - if (status != PrintBase::APPLY_STATUS_UNCHANGED) - update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED); - print_objects_new.emplace_back((*it_old)->print_object); - const_cast(*it_old)->status = PrintObjectStatus::Reused; - } - } - } - if (m_objects != print_objects_new) { - this->call_cancel_callback(); - update_apply_status(this->invalidate_all_steps()); - m_objects = print_objects_new; - // Delete the PrintObjects marked as Unknown or Deleted. - bool deleted_objects = false; - for (const PrintObjectStatus &pos : print_object_status_db) - if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { - update_apply_status(pos.print_object->invalidate_all_steps()); - delete pos.print_object; - deleted_objects = true; - } - if (new_objects || deleted_objects) - update_apply_status(this->invalidate_steps({ psSkirtBrim, psWipeTower, psGCodeExport })); - if (new_objects) - update_apply_status(false); - print_regions_reshuffled = true; - } - print_object_status_db.clear(); - } - - // All regions now have distinct settings. - // Check whether applying the new region config defaults we would get different regions, - // update regions or create regions from scratch. - for (auto it_print_object = m_objects.begin(); it_print_object != m_objects.end();) { - // Find the range of PrintObjects sharing the same associated ModelObject. - auto it_print_object_end = it_print_object; - PrintObject &print_object = *(*it_print_object); - const ModelObject &model_object = *print_object.model_object(); - ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(model_object)); - PrintObjectRegions *print_object_regions = model_object_status.print_object_regions; - for (++ it_print_object_end; it_print_object_end != m_objects.end() && (*it_print_object)->model_object() == (*it_print_object_end)->model_object(); ++ it_print_object_end) - assert((*it_print_object_end)->m_shared_regions == nullptr || (*it_print_object_end)->m_shared_regions == print_object_regions); - if (print_object_regions == nullptr) { - print_object_regions = new PrintObjectRegions{}; - model_object_status.print_object_regions = print_object_regions; - print_object_regions->ref_cnt_inc(); - } - std::vector painting_extruders; - if (const auto &volumes = print_object.model_object()->volumes; - num_extruders > 1 && - std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) { - //FIXME be more specific! Don't enumerate extruders that are not used for painting! - painting_extruders.assign(num_extruders, 0); - std::iota(painting_extruders.begin(), painting_extruders.end(), 1); - } - if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { - // Verify that the trafo for regions & volume bounding boxes thus for regions is still applicable. - auto invalidate = [it_print_object, it_print_object_end, update_apply_status]() { - for (auto it = it_print_object; it != it_print_object_end; ++ it) - if ((*it)->m_shared_regions != nullptr) - update_apply_status((*it)->invalidate_all_steps()); - }; - if (print_object_regions && ! trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(print_object_regions->trafo_bboxes, model_object_status.print_instances.front().trafo)) { - invalidate(); - print_object_regions->clear(); - model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Invalid; - print_regions_reshuffled = true; - } else if (print_object_regions && - verify_update_print_object_regions( - print_object.model_object()->volumes, - m_default_region_config, - num_extruders, - painting_extruders, - *print_object_regions, - [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { - for (auto it = it_print_object; it != it_print_object_end; ++it) - if ((*it)->m_shared_regions != nullptr) - update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys)); - })) { - // Regions are valid, just keep them. - } else { - // Regions were reshuffled. - invalidate(); - // At least reuse layer ranges and bounding boxes of ModelVolumes. - model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; - print_regions_reshuffled = true; - } - } - if (print_object_regions == nullptr || model_object_status.print_object_regions_status != ModelObjectStatus::PrintObjectRegionsStatus::Valid) { - // Layer ranges with their associated configurations. Remove overlaps between the ranges - // and create the regions from scratch. - print_object_regions = generate_print_object_regions( - print_object_regions, - print_object.model_object()->volumes, - LayerRanges(print_object.model_object()->layer_config_ranges), - m_default_region_config, - model_object_status.print_instances.front().trafo, - num_extruders, - print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value), - painting_extruders); - } - for (auto it = it_print_object; it != it_print_object_end; ++it) - if ((*it)->m_shared_regions) { - assert((*it)->m_shared_regions == print_object_regions); - } else { - (*it)->m_shared_regions = print_object_regions; - print_object_regions->ref_cnt_inc(); - } - it_print_object = it_print_object_end; - } - - if (print_regions_reshuffled) { - // Update Print::m_print_regions from objects. - struct cmp { bool operator() (const PrintRegion *l, const PrintRegion *r) const { return l->config_hash() == r->config_hash() && l->config() == r->config(); } }; - std::set region_set; - m_print_regions.clear(); - PrintObjectRegions *print_object_regions = nullptr; - for (PrintObject *print_object : m_objects) - if (print_object_regions != print_object->m_shared_regions) { - print_object_regions = print_object->m_shared_regions; - for (std::unique_ptr &print_region : print_object_regions->all_regions) - if (auto it = region_set.find(print_region.get()); it == region_set.end()) { - int print_region_id = int(m_print_regions.size()); - m_print_regions.emplace_back(print_region.get()); - print_region->m_print_region_id = print_region_id; - } else { - print_region->m_print_region_id = (*it)->print_region_id(); - } - } - } - - // Update SlicingParameters for each object where the SlicingParameters is not valid. - // If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use - // (posSlicing and posSupportMaterial was invalidated). - for (PrintObject *object : m_objects) - object->update_slicing_parameters(); - -#ifdef _DEBUG - check_model_ids_equal(m_model, model); -#endif /* _DEBUG */ - - return static_cast(apply_status); -} - -} // namespace Slic3r +#include "Model.hpp" +#include "Print.hpp" + +#include + +namespace Slic3r { + +// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new +// in the exact order and with the same IDs. +// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. +// Friend to ModelVolume to allow copying. +// static is not accepted by gcc if declared as a friend of ModelObject. +/* static */ void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_new) +{ + typedef std::pair ModelVolumeWithStatus; + std::vector old_volumes; + old_volumes.reserve(model_object_dst.volumes.size()); + for (const ModelVolume *model_volume : model_object_dst.volumes) + old_volumes.emplace_back(ModelVolumeWithStatus(model_volume, false)); + auto model_volume_lower = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() < mv2.first->id(); }; + auto model_volume_equal = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() == mv2.first->id(); }; + std::sort(old_volumes.begin(), old_volumes.end(), model_volume_lower); + model_object_dst.volumes.clear(); + model_object_dst.volumes.reserve(model_object_new.volumes.size()); + for (const ModelVolume *model_volume_src : model_object_new.volumes) { + ModelVolumeWithStatus key(model_volume_src, false); + auto it = std::lower_bound(old_volumes.begin(), old_volumes.end(), key, model_volume_lower); + if (it != old_volumes.end() && model_volume_equal(*it, key)) { + // The volume was found in the old list. Just copy it. + assert(! it->second); // not consumed yet + it->second = true; + ModelVolume *model_volume_dst = const_cast(it->first); + // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. + assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type()); + model_object_dst.volumes.emplace_back(model_volume_dst); + if (model_volume_dst->is_support_modifier()) { + // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. + model_volume_dst->set_type(model_volume_src->type()); + model_volume_dst->set_transformation(model_volume_src->get_transformation()); + } + assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix())); + } else { + // The volume was not found in the old list. Create a new copy. + assert(model_volume_src->is_support_modifier()); + model_object_dst.volumes.emplace_back(new ModelVolume(*model_volume_src)); + model_object_dst.volumes.back()->set_model_object(&model_object_dst); + } + } + // Release the non-consumed old volumes (those were deleted from the new list). + for (ModelVolumeWithStatus &mv_with_status : old_volumes) + if (! mv_with_status.second) + delete mv_with_status.first; +} + +static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type) +{ + size_t i_src, i_dst; + for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) { + const ModelVolume &mv_src = *model_object_src.volumes[i_src]; + ModelVolume &mv_dst = *model_object_dst.volumes[i_dst]; + if (mv_src.type() != type) { + ++ i_src; + continue; + } + if (mv_dst.type() != type) { + ++ i_dst; + continue; + } + assert(mv_src.id() == mv_dst.id()); + // Copy the ModelVolume data. + mv_dst.name = mv_src.name; + mv_dst.config.assign_config(mv_src.config); + assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id()); + mv_dst.supported_facets.assign(mv_src.supported_facets); + assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); + mv_dst.seam_facets.assign(mv_src.seam_facets); + assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id()); + mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets); + //FIXME what to do with the materials? + // mv_dst.m_material_id = mv_src.m_material_id; + ++ i_src; + ++ i_dst; + } +} + +static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) +{ + assert(lr_dst.size() == lr_src.size()); + auto it_src = lr_src.cbegin(); + for (auto &kvp_dst : lr_dst) { + const auto &kvp_src = *it_src ++; + assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON); + assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); + // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile. + // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON); + kvp_dst.second = kvp_src.second; + } +} + +static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) { + if (*lv < *rv) + return true; + else if (*lv > *rv) + return false; + } + return false; +} + +static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) + if (*lv != *rv) + return false; + return true; +} + +struct PrintObjectTrafoAndInstances +{ + Transform3d trafo; + PrintInstances instances; + bool operator<(const PrintObjectTrafoAndInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); } +}; + +// Generate a list of trafos and XY offsets for instances of a ModelObject +static std::vector print_objects_from_model_object(const ModelObject &model_object) +{ + std::set trafos; + PrintObjectTrafoAndInstances trafo; + for (ModelInstance *model_instance : model_object.instances) + if (model_instance->is_printable()) { + trafo.trafo = model_instance->get_matrix(); + auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); + // Reset the XY axes of the transformation. + trafo.trafo.data()[12] = 0; + trafo.trafo.data()[13] = 0; + // Search or insert a trafo. + auto it = trafos.emplace(trafo).first; + const_cast(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift }); + } + return std::vector(trafos.begin(), trafos.end()); +} + +// Compare just the layer ranges and their layer heights, not the associated configs. +// Ignore the layer heights if check_layer_heights is false. +static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) +{ + if (lr1.size() != lr2.size()) + return false; + auto it2 = lr2.begin(); + for (const auto &kvp1 : lr1) { + const auto &kvp2 = *it2 ++; + if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON || + std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || + (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) + return false; + } + return true; +} + +// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored. +static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector &va, const std::vector &vb) +{ + auto it_a = va.begin(); + auto it_b = vb.begin(); + while (it_a != va.end() || it_b != vb.end()) { + if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) { + // Skip any CustomGCode items, which are not tool changes. + ++ it_a; + continue; + } + if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) { + // Skip any CustomGCode items, which are not tool changes. + ++ it_b; + continue; + } + if (it_a == va.end() || it_b == vb.end()) + // va or vb contains more Tool Changes than the other. + return true; + assert(it_a->type == CustomGCode::ToolChange); + assert(it_b->type == CustomGCode::ToolChange); + if (*it_a != *it_b) + // The two Tool Changes differ. + return true; + ++ it_a; + ++ it_b; + } + // There is no change in custom Tool Changes. + return false; +} + +// Collect changes to print config, account for overrides of extruder retract values by filament presets. +static t_config_option_keys print_config_diffs( + const PrintConfig ¤t_config, + const DynamicPrintConfig &new_full_config, + DynamicPrintConfig &filament_overrides) +{ + const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); + const std::string filament_prefix = "filament_"; + t_config_option_keys print_diff; + for (const t_config_option_key &opt_key : current_config.keys()) { + const ConfigOption *opt_old = current_config.option(opt_key); + assert(opt_old != nullptr); + const ConfigOption *opt_new = new_full_config.option(opt_key); + // assert(opt_new != nullptr); + if (opt_new == nullptr) + //FIXME This may happen when executing some test cases. + continue; + const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; + if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { + // An extruder retract override is available at some of the filament presets. + bool overriden = opt_new->overriden_by(opt_new_filament); + if (overriden || *opt_old != *opt_new) { + auto opt_copy = opt_new->clone(); + opt_copy->apply_override(opt_new_filament); + bool changed = *opt_old != *opt_copy; + if (changed) + print_diff.emplace_back(opt_key); + if (changed || overriden) { + // filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config. + filament_overrides.set_key_value(opt_key, opt_copy); + } else + delete opt_copy; + } + } else if (*opt_new != *opt_old) + print_diff.emplace_back(opt_key); + } + + return print_diff; +} + +// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser. +static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig ¤t_full_config, const DynamicPrintConfig &new_full_config) +{ + t_config_option_keys full_config_diff; + for (const t_config_option_key &opt_key : new_full_config.keys()) { + const ConfigOption *opt_old = current_full_config.option(opt_key); + const ConfigOption *opt_new = new_full_config.option(opt_key); + if (opt_old == nullptr || *opt_new != *opt_old) + full_config_diff.emplace_back(opt_key); + } + return full_config_diff; +} + +// Repository for solving partial overlaps of ModelObject::layer_config_ranges. +// Here the const DynamicPrintConfig* point to the config in ModelObject::layer_config_ranges. +class LayerRanges +{ +public: + struct LayerRange { + t_layer_height_range layer_height_range; + // Config is owned by the associated ModelObject. + const DynamicPrintConfig* config { nullptr }; + + bool operator<(const LayerRange &rhs) const throw() { return this->layer_height_range < rhs.layer_height_range; } + }; + + LayerRanges() = default; + LayerRanges(const t_layer_config_ranges &in) { this->assign(in); } + + // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. + void assign(const t_layer_config_ranges &in) { + m_ranges.clear(); + m_ranges.reserve(in.size()); + // Input ranges are sorted lexicographically. First range trims the other ranges. + coordf_t last_z = 0; + for (const std::pair &range : in) + if (range.first.second > last_z) { + coordf_t min_z = std::max(range.first.first, 0.); + if (min_z > last_z + EPSILON) { + m_ranges.push_back({ t_layer_height_range(last_z, min_z) }); + last_z = min_z; + } + if (range.first.second > last_z + EPSILON) { + const DynamicPrintConfig *cfg = &range.second.get(); + m_ranges.push_back({ t_layer_height_range(last_z, range.first.second), cfg }); + last_z = range.first.second; + } + } + if (m_ranges.empty()) + m_ranges.push_back({ t_layer_height_range(0, DBL_MAX) }); + else if (m_ranges.back().config == nullptr) + m_ranges.back().layer_height_range.second = DBL_MAX; + else + m_ranges.push_back({ t_layer_height_range(m_ranges.back().layer_height_range.second, DBL_MAX) }); + } + + const DynamicPrintConfig* config(const t_layer_height_range &range) const { + auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), LayerRange{ { range.first - EPSILON, range.second - EPSILON } }); + // #ys_FIXME_COLOR + // assert(it != m_ranges.end()); + // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); + // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); + if (it == m_ranges.end() || + std::abs(it->layer_height_range.first - range.first) > EPSILON || + std::abs(it->layer_height_range.second - range.second) > EPSILON ) + return nullptr; // desired range doesn't found + return it == m_ranges.end() ? nullptr : it->config; + } + + std::vector::const_iterator begin() const { return m_ranges.cbegin(); } + std::vector::const_iterator end () const { return m_ranges.cend(); } + size_t size () const { return m_ranges.size(); } + +private: + // Layer ranges with their config overrides and list of volumes with their snug bounding boxes in a given layer range. + std::vector m_ranges; +}; + +// To track Model / ModelObject updates between the front end and back end, including layer height ranges, their configs, +// and snug bounding boxes of ModelVolumes. +struct ModelObjectStatus { + enum Status { + Unknown, + Old, + New, + Moved, + Deleted, + }; + + enum class PrintObjectRegionsStatus { + Invalid, + Valid, + PartiallyValid, + }; + + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ~ModelObjectStatus() { if (print_object_regions) print_object_regions->ref_cnt_dec(); } + + // Key of the set. + ObjectID id; + // Status of this ModelObject with id on apply(). + Status status; + // PrintObjects to be generated for this ModelObject including their base transformation. + std::vector print_instances; + // Regions shared by the associated PrintObjects. + PrintObjectRegions *print_object_regions { nullptr }; + // Status of the above. + PrintObjectRegionsStatus print_object_regions_status { PrintObjectRegionsStatus::Invalid }; + + // Search by id. + bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } +}; + +struct ModelObjectStatusDB +{ + void add(const ModelObject &model_object, const ModelObjectStatus::Status status) { + assert(db.find(ModelObjectStatus(model_object.id())) == db.end()); + db.emplace(model_object.id(), status); + } + + bool add_if_new(const ModelObject &model_object, const ModelObjectStatus::Status status) { + auto it = db.find(ModelObjectStatus(model_object.id())); + if (it == db.end()) { + db.emplace_hint(it, model_object.id(), status); + return true; + } + return false; + } + + const ModelObjectStatus& get(const ModelObject &model_object) { + auto it = db.find(ModelObjectStatus(model_object.id())); + assert(it != db.end()); + return *it; + } + + const ModelObjectStatus& reuse(const ModelObject &model_object) { + const ModelObjectStatus &result = this->get(model_object); + assert(result.status != ModelObjectStatus::Deleted); + return result; + } + + std::set db; +}; + +struct PrintObjectStatus { + enum Status { + Unknown, + Deleted, + Reused, + New + }; + + PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : + id(print_object->model_object()->id()), + print_object(print_object), + trafo(print_object->trafo()), + status(status) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + + // ID of the ModelObject & PrintObject + ObjectID id; + // Pointer to the old PrintObject + PrintObject *print_object; + // Trafo generated with model_object->world_matrix(true) + Transform3d trafo; + Status status; + + // Search by id. + bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } +}; + +class PrintObjectStatusDB { +public: + using iterator = std::multiset::iterator; + using const_iterator = std::multiset::const_iterator; + + PrintObjectStatusDB(const PrintObjectPtrs &print_objects) { + for (PrintObject *print_object : print_objects) + m_db.emplace(PrintObjectStatus(print_object)); + } + + struct iterator_range : std::pair + { + using std::pair::pair; + iterator_range(const std::pair in) : std::pair(in) {} + + const_iterator begin() throw() { return this->first; } + const_iterator end() throw() { return this->second; } + }; + + iterator_range get_range(const ModelObject &model_object) const { + return m_db.equal_range(PrintObjectStatus(model_object.id())); + } + + iterator_range get_range(const ModelObjectStatus &model_object_status) const { + return m_db.equal_range(PrintObjectStatus(model_object_status.id)); + } + + size_t count(const ModelObject &model_object) { + return m_db.count(PrintObjectStatus(model_object.id())); + } + + std::multiset::iterator begin() { return m_db.begin(); } + std::multiset::iterator end() { return m_db.end(); } + + void clear() { + m_db.clear(); + } + +private: + std::multiset m_db; +}; + +static inline bool model_volume_solid_or_modifier(const ModelVolume &mv) +{ + ModelVolumeType type = mv.type(); + return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER; +} + +static inline Transform3f trafo_for_bbox(const Transform3d &object_trafo, const Transform3d &volume_trafo) +{ + Transform3d m = object_trafo * volume_trafo; + m.translation().x() = 0.; + m.translation().y() = 0.; + return m.cast(); +} + +static inline bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d &t1, const Transform3d &t2) +{ + if (std::abs(t1.translation().z() - t2.translation().z()) > EPSILON) + // One of the object is higher than the other above the build plate (or below the build plate). + return false; + Matrix3d m1 = t1.matrix().block<3, 3>(0, 0); + Matrix3d m2 = t2.matrix().block<3, 3>(0, 0); + Matrix3d m = m2.inverse() * m1; + Vec3d z = m.block<3, 1>(0, 2); + if (std::abs(z.x()) > EPSILON || std::abs(z.y()) > EPSILON || std::abs(z.z() - 1.) > EPSILON) + // Z direction or length changed. + return false; + // Z still points in the same direction and it has the same length. + Vec3d x = m.block<3, 1>(0, 0); + Vec3d y = m.block<3, 1>(0, 1); + if (std::abs(x.z()) > EPSILON || std::abs(y.z()) > EPSILON) + return false; + double lx2 = x.squaredNorm(); + double ly2 = y.squaredNorm(); + if (lx2 - 1. > EPSILON * EPSILON || ly2 - 1. > EPSILON * EPSILON) + return false; + // Verify whether the vectors x, y are still perpendicular. + double d = x.dot(y); + return std::abs(d * d) < EPSILON * lx2 * ly2; +} + +static PrintObjectRegions::BoundingBox transformed_its_bbox2d(const indexed_triangle_set &its, const Transform3f &m, float offset) +{ + assert(! its.indices.empty()); + + PrintObjectRegions::BoundingBox bbox(m * its.vertices[its.indices.front()(0)]); + for (const stl_triangle_vertex_indices &tri : its.indices) + for (int i = 0; i < 3; ++ i) + bbox.extend(m * its.vertices[tri(i)]); + bbox.min() -= Vec3f(offset, offset, float(EPSILON)); + bbox.max() += Vec3f(offset, offset, float(EPSILON)); + return bbox; +} + +static void transformed_its_bboxes_in_z_ranges( + const indexed_triangle_set &its, + const Transform3f &m, + const std::vector &z_ranges, + std::vector> &bboxes, + const float offset) +{ + bboxes.assign(z_ranges.size(), std::make_pair(PrintObjectRegions::BoundingBox(), false)); + for (const stl_triangle_vertex_indices &tri : its.indices) { + const Vec3f pts[3] = { m * its.vertices[tri(0)], m * its.vertices[tri(1)], m * its.vertices[tri(2)] }; + for (size_t irange = 0; irange < z_ranges.size(); ++ irange) { + const t_layer_height_range &z_range = z_ranges[irange]; + std::pair &bbox = bboxes[irange]; + auto bbox_extend = [&bbox](const Vec3f& p) { + if (bbox.second) { + bbox.first.extend(p); + } else { + bbox.first.min() = bbox.first.max() = p; + bbox.second = true; + } + }; + int iprev = 2; + for (int iedge = 0; iedge < 3; ++ iedge) { + const Vec3f *p1 = &pts[iprev]; + const Vec3f *p2 = &pts[iedge]; + // Sort the edge points by Z. + if (p1->z() > p2->z()) + std::swap(p1, p2); + if (p2->z() <= z_range.first || p1->z() >= z_range.second) { + // Out of this slab. + } else if (p1->z() < z_range.first) { + if (p1->z() > z_range.second) { + // Two intersections. + float zspan = p2->z() - p1->z(); + float t1 = (z_range.first - p1->z()) / zspan; + float t2 = (z_range.second - p1->z()) / zspan; + Vec2f p = to_2d(*p1); + Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); + bbox_extend(to_3d((p + v * t1).eval(), float(z_range.first))); + bbox_extend(to_3d((p + v * t2).eval(), float(z_range.second))); + } else { + // Single intersection with the lower limit. + float t = (z_range.first - p1->z()) / (p2->z() - p1->z()); + Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); + bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.first))); + bbox_extend(*p2); + } + } else if (p2->z() > z_range.second) { + // Single intersection with the upper limit. + float t = (z_range.second - p1->z()) / (p2->z() - p1->z()); + Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); + bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.second))); + bbox_extend(*p1); + } else { + // Both points are inside. + bbox_extend(*p1); + bbox_extend(*p2); + } + iprev = iedge; + } + } + } + + for (std::pair &bbox : bboxes) { + bbox.first.min() -= Vec3f(offset, offset, float(EPSILON)); + bbox.first.max() += Vec3f(offset, offset, float(EPSILON)); + } +} + +// Last PrintObject for this print_object_regions has been fully invalidated (deleted). +// Keep print_object_regions, but delete those volumes, which were either removed from new_volumes, or which rotated or scaled, so they need +// their bounding boxes to be recalculated. +void print_objects_regions_invalidate_keep_some_volumes(PrintObjectRegions &print_object_regions, ModelVolumePtrs old_volumes, ModelVolumePtrs new_volumes) +{ + print_object_regions.all_regions.clear(); + + model_volumes_sort_by_id(old_volumes); + model_volumes_sort_by_id(new_volumes); + + size_t i_cached_volume = 0; + size_t last_cached_volume = 0; + size_t i_old = 0; + for (size_t i_new = 0; i_new < new_volumes.size(); ++ i_new) + if (model_volume_solid_or_modifier(*new_volumes[i_new])) { + for (; i_old < old_volumes.size(); ++ i_old) + if (old_volumes[i_old]->id() >= new_volumes[i_new]->id()) + break; + if (i_old != old_volumes.size() && old_volumes[i_old]->id() == new_volumes[i_new]->id()) { + if (old_volumes[i_old]->get_matrix().isApprox(new_volumes[i_new]->get_matrix())) { + // Reuse the volume. + for (; print_object_regions.cached_volume_ids[i_cached_volume] < old_volumes[i_old]->id(); ++ i_cached_volume) + assert(i_cached_volume < print_object_regions.cached_volume_ids.size()); + assert(i_cached_volume < print_object_regions.cached_volume_ids.size() && print_object_regions.cached_volume_ids[i_cached_volume] == old_volumes[i_old]->id()); + print_object_regions.cached_volume_ids[last_cached_volume ++] = print_object_regions.cached_volume_ids[i_cached_volume ++]; + } else { + // Don't reuse the volume. + } + } + } + print_object_regions.cached_volume_ids.erase(print_object_regions.cached_volume_ids.begin() + last_cached_volume, print_object_regions.cached_volume_ids.end()); +} + +// Find a bounding box of a volume's part intersecting layer_range. Such a bounding box will likely be smaller in XY than the full bounding box, +// thus it will intersect with lower number of other volumes. +const PrintObjectRegions::BoundingBox* find_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const ModelVolume &volume) +{ + auto it = lower_bound_by_predicate(layer_range.volumes.begin(), layer_range.volumes.end(), [&volume](const PrintObjectRegions::VolumeExtents &l){ return l.volume_id < volume.id(); }); + return it != layer_range.volumes.end() && it->volume_id == volume.id() ? &it->bbox : nullptr; +} + +// Find a bounding box of a topmost printable volume referenced by this modifier given this_region_id. +PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const int this_region_id) +{ + // Find the top-most printable volume of this modifier, or the printable volume itself. + const PrintObjectRegions::VolumeRegion &this_region = layer_range.volume_regions[this_region_id]; + const PrintObjectRegions::BoundingBox *this_extents = find_volume_extents(layer_range, *this_region.model_volume); + assert(this_extents); + PrintObjectRegions::BoundingBox out { *this_extents }; + if (! this_region.model_volume->is_model_part()) + for (int parent_region_id = this_region.parent;;) { + assert(parent_region_id >= 0); + const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; + const PrintObjectRegions::BoundingBox *parent_extents = find_volume_extents(layer_range, *parent_region.model_volume); + assert(parent_extents); + out.extend(*parent_extents); + if (parent_region.model_volume->is_model_part()) + break; + parent_region_id = parent_region.parent; + } + return out; +} + +PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders); + +void print_region_ref_inc(PrintRegion &r) { ++ r.m_ref_cnt; } +void print_region_ref_reset(PrintRegion &r) { r.m_ref_cnt = 0; } +int print_region_ref_cnt(const PrintRegion &r) { return r.m_ref_cnt; } + +// Verify whether the PrintRegions of a PrintObject are still valid, possibly after updating the region configs. +// Before region configs are updated, callback_invalidate() is called to possibly stop background processing. +// Returns false if this object needs to be resliced because regions were merged or split. +bool verify_update_print_object_regions( + ModelVolumePtrs model_volumes, + const PrintRegionConfig &default_region_config, + size_t num_extruders, + const std::vector &painting_extruders, + PrintObjectRegions &print_object_regions, + const std::function &callback_invalidate) +{ + // Sort by ModelVolume ID. + model_volumes_sort_by_id(model_volumes); + + for (std::unique_ptr ®ion : print_object_regions.all_regions) + print_region_ref_reset(*region); + + // Verify and / or update PrintRegions produced by ModelVolumes, layer range modifiers, modifier volumes. + for (PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { + // Each modifier ModelVolume intersecting this layer_range shall be referenced here at least once if it intersects some + // printable ModelVolume at this layer_range even if it does not modify its overlapping printable ModelVolume configuration yet. + // VolumeRegions reference ModelVolumes in layer_range.volume_regions the order they are stored in ModelObject volumes. + // Remember whether a given modifier ModelVolume was visited already. + auto it_model_volume_modifier_last = model_volumes.end(); + for (PrintObjectRegions::VolumeRegion ®ion : layer_range.volume_regions) + if (region.model_volume->is_model_part() || region.model_volume->is_modifier()) { + auto it_model_volume = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [®ion](const ModelVolume *l){ return l->id() < region.model_volume->id(); }); + assert(it_model_volume != model_volumes.end() && (*it_model_volume)->id() == region.model_volume->id()); + if (region.model_volume->is_modifier() && it_model_volume != it_model_volume_modifier_last) { + // A modifier ModelVolume is visited for the first time. + // A visited modifier may not have had parent volume_regions created overlapping with some model parts or modifiers, + // if the visited modifier did not modify their properties. Now the visited modifier's configuration may have changed, + // which may require new regions to be created. + it_model_volume_modifier_last = it_model_volume; + int next_region_id = int(®ion - layer_range.volume_regions.data()); + const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, *region.model_volume); + assert(bbox); + for (int parent_region_id = next_region_id - 1; parent_region_id >= 0; -- parent_region_id) { + const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; + assert(parent_region.model_volume != region.model_volume); + if (parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) { + // volume_regions are produced in decreasing order of parent volume_regions ids. + // Some regions may not have been generated the last time by generate_print_object_regions(). + assert(next_region_id == int(layer_range.volume_regions.size()) || + layer_range.volume_regions[next_region_id].model_volume != region.model_volume || + layer_range.volume_regions[next_region_id].parent <= parent_region_id); + if (next_region_id < int(layer_range.volume_regions.size()) && + layer_range.volume_regions[next_region_id].model_volume == region.model_volume && + layer_range.volume_regions[next_region_id].parent == parent_region_id) { + // A parent region is already overridden. + ++ next_region_id; + } else if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) + // Such parent region does not exist. If it is needed, then we need to reslice. + // Only create new region for a modifier, which actually modifies config of it's parent. + if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders); + config != parent_region.region->config()) + // This modifier newly overrides a region, which it did not before. We need to reslice. + return false; + } + } + } + PrintRegionConfig cfg = region.parent == -1 ? + region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders) : + region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders); + if (cfg != region.region->config()) { + // Region configuration changed. + if (print_region_ref_cnt(*region.region) == 0) { + // Region is referenced for the first time. Just change its parameters. + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = region.region->config().diff(cfg); + callback_invalidate(region.region->config(), cfg, diff); + region.region->config_apply_only(cfg, diff, false); + } else { + // Region is referenced multiple times, thus the region is being split. We need to reslice. + return false; + } + } + print_region_ref_inc(*region.region); + } + } + + // Verify and / or update PrintRegions produced by color painting. + for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) + for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) { + const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent]; + PrintRegionConfig cfg = parent_region.region->config(); + cfg.perimeter_extruder.value = region.extruder_id; + cfg.solid_infill_extruder.value = region.extruder_id; + cfg.infill_extruder.value = region.extruder_id; + if (cfg != region.region->config()) { + // Region configuration changed. + if (print_region_ref_cnt(*region.region) == 0) { + // Region is referenced for the first time. Just change its parameters. + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = region.region->config().diff(cfg); + callback_invalidate(region.region->config(), cfg, diff); + region.region->config_apply_only(cfg, diff, false); + } else { + // Region is referenced multiple times, thus the region is being split. We need to reslice. + return false; + } + } + print_region_ref_inc(*region.region); + } + + // Lastly verify, whether some regions were not merged. + { + std::vector regions; + regions.reserve(print_object_regions.all_regions.size()); + for (std::unique_ptr ®ion : print_object_regions.all_regions) { + assert(print_region_ref_cnt(*region) > 0); + regions.emplace_back(&(*region.get())); + } + std::sort(regions.begin(), regions.end(), [](const PrintRegion *l, const PrintRegion *r){ return l->config_hash() < r->config_hash(); }); + for (size_t i = 0; i < regions.size(); ++ i) { + size_t hash = regions[i]->config_hash(); + size_t j = i; + for (++ j; j < regions.size() && regions[j]->config_hash() == hash; ++ j) + if (regions[i]->config() == regions[j]->config()) { + // Regions were merged. We need to reslice. + return false; + } + } + } + + return true; +} + +// Update caches of volume bounding boxes. +void update_volume_bboxes( + std::vector &layer_ranges, + std::vector &cached_volume_ids, + ModelVolumePtrs model_volumes, + const Transform3d &object_trafo, + const float offset) +{ + // output will be sorted by the order of model_volumes sorted by their ObjectIDs. + model_volumes_sort_by_id(model_volumes); + + if (layer_ranges.size() == 1) { + PrintObjectRegions::LayerRangeRegions &layer_range = layer_ranges.front(); + std::vector volumes_old(std::move(layer_range.volumes)); + layer_range.volumes.reserve(model_volumes.size()); + for (const ModelVolume *model_volume : model_volumes) + if (model_volume_solid_or_modifier(*model_volume)) { + if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) { + auto it = lower_bound_by_predicate(volumes_old.begin(), volumes_old.end(), [model_volume](PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); }); + if (it != volumes_old.end() && it->volume_id == model_volume->id()) + layer_range.volumes.emplace_back(*it); + } else +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + layer_range.volumes.push_back({ model_volume->id(), + transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), offset) }); +#else + layer_range.volumes.push_back({ model_volume->id(), + transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) }); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + } + } else { + std::vector> volumes_old; + if (cached_volume_ids.empty()) + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) + layer_range.volumes.clear(); + else { + volumes_old.reserve(layer_ranges.size()); + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) + volumes_old.emplace_back(std::move(layer_range.volumes)); + } + + std::vector> bboxes; + std::vector ranges; + ranges.reserve(layer_ranges.size()); + for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) { + t_layer_height_range r = layer_range.layer_height_range; + r.first -= EPSILON; + r.second += EPSILON; + ranges.emplace_back(r); + } + for (const ModelVolume *model_volume : model_volumes) + if (model_volume_solid_or_modifier(*model_volume)) { + if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) { + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) { + const auto &vold = volumes_old[&layer_range - layer_ranges.data()]; + auto it = lower_bound_by_predicate(vold.begin(), vold.end(), [model_volume](const PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); }); + if (it != vold.end() && it->volume_id == model_volume->id()) + layer_range.volumes.emplace_back(*it); + } + } else { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), ranges, bboxes, offset); +#else + transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) + if (auto &bbox = bboxes[&layer_range - layer_ranges.data()]; bbox.second) + layer_range.volumes.push_back({ model_volume->id(), bbox.first }); + } + } + } + + cached_volume_ids.clear(); + cached_volume_ids.reserve(model_volumes.size()); + for (const ModelVolume *v : model_volumes) + if (model_volume_solid_or_modifier(*v)) + cached_volume_ids.emplace_back(v->id()); +} + +// Either a fresh PrintObject, or PrintObject regions were invalidated (merged, split). +// Generate PrintRegions from scratch. +static PrintObjectRegions* generate_print_object_regions( + PrintObjectRegions *print_object_regions_old, + const ModelVolumePtrs &model_volumes, + const LayerRanges &model_layer_ranges, + const PrintRegionConfig &default_region_config, + const Transform3d &trafo, + size_t num_extruders, + const float xy_size_compensation, + const std::vector &painting_extruders) +{ + // Reuse the old object or generate a new one. + auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique(); + auto &all_regions = out->all_regions; + auto &layer_ranges_regions = out->layer_ranges; + + all_regions.clear(); + + bool reuse_old = print_object_regions_old && !print_object_regions_old->layer_ranges.empty(); + + if (reuse_old) { + // Reuse old bounding boxes of some ModelVolumes and their ranges. + // Verify that the old ranges match the new ranges. + assert(model_layer_ranges.size() == layer_ranges_regions.size()); + for (const auto &range : model_layer_ranges) { + PrintObjectRegions::LayerRangeRegions &r = layer_ranges_regions[&range - &*model_layer_ranges.begin()]; + assert(range.layer_height_range == r.layer_height_range); + // If model::assign_copy() is called, layer_ranges_regions is copied thus the pointers to configs are lost. + r.config = range.config; + r.volume_regions.clear(); + r.painted_regions.clear(); + } + } else { + out->trafo_bboxes = trafo; + layer_ranges_regions.reserve(model_layer_ranges.size()); + for (const auto &range : model_layer_ranges) + layer_ranges_regions.push_back({ range.layer_height_range, range.config }); + } + + const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); + update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation)); + + std::vector region_set; + auto get_create_region = [®ion_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* { + size_t hash = config.hash(); + auto it = Slic3r::lower_bound_by_predicate(region_set.begin(), region_set.end(), [&config, hash](const PrintRegion* l) { + return l->config_hash() < hash || (l->config_hash() == hash && l->config() < config); }); + if (it != region_set.end() && (*it)->config_hash() == hash && (*it)->config() == config) + return *it; + // Insert into a sorted array, it has O(n) complexity, but the calling algorithm has an O(n^2*log(n)) complexity anyways. + all_regions.emplace_back(std::make_unique(std::move(config), hash, int(all_regions.size()))); + PrintRegion *region = all_regions.back().get(); + region_set.emplace(it, region); + return region; + }; + + // Chain the regions in the order they are stored in the volumes list. + for (int volume_id = 0; volume_id < int(model_volumes.size()); ++ volume_id) { + const ModelVolume &volume = *model_volumes[volume_id]; + if (model_volume_solid_or_modifier(volume)) { + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) + if (const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, volume); bbox) { + if (volume.is_model_part()) { + // Add a model volume, assign an existing region or generate a new one. + layer_range.volume_regions.push_back({ + &volume, -1, + get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders)), + bbox + }); + } else if (volume.is_negative_volume()) { + // Add a negative (subtractor) volume. Such volume has neither region nor parent volume assigned. + layer_range.volume_regions.push_back({ &volume, -1, nullptr, bbox }); + } else { + assert(volume.is_modifier()); + // Modifiers may be chained one over the other. Check for overlap, merge DynamicPrintConfigs. + bool added = false; + int parent_model_part_id = -1; + for (int parent_region_id = int(layer_range.volume_regions.size()) - 1; parent_region_id >= 0; -- parent_region_id) { + const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; + const ModelVolume &parent_volume = *parent_region.model_volume; + if (parent_volume.is_model_part() || parent_volume.is_modifier()) + if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) { + // Only create new region for a modifier, which actually modifies config of it's parent. + if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders); + config != parent_region.region->config()) { + added = true; + layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox }); + } else if (parent_model_part_id == -1 && parent_volume.is_model_part()) + parent_model_part_id = parent_region_id; + } + } + if (! added && parent_model_part_id >= 0) + // This modifier does not override any printable volume's configuration, however it may in the future. + // Store it so that verify_update_print_object_regions() will handle this modifier correctly if its configuration changes. + layer_range.volume_regions.push_back({ &volume, parent_model_part_id, layer_range.volume_regions[parent_model_part_id].region, bbox }); + } + } + } + } + + // Finally add painting regions. + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) { + for (unsigned int painted_extruder_id : painting_extruders) + for (int parent_region_id = 0; parent_region_id < int(layer_range.volume_regions.size()); ++ parent_region_id) + if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; + parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) { + PrintRegionConfig cfg = parent_region.region->config(); + cfg.perimeter_extruder.value = painted_extruder_id; + cfg.solid_infill_extruder.value = painted_extruder_id; + cfg.infill_extruder.value = painted_extruder_id; + layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))}); + } + // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation. + std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) { + int lid = layer_range.volume_regions[l.parent].region->print_object_region_id(); + int rid = layer_range.volume_regions[r.parent].region->print_object_region_id(); + return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); }); + } + + return out.release(); +} + +Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) +{ +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + // Normalize the config. + new_full_config.option("print_settings_id", true); + new_full_config.option("filament_settings_id", true); + new_full_config.option("printer_settings_id", true); + new_full_config.option("physical_printer_settings_id", true); + new_full_config.normalize_fdm(); + + // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. + DynamicPrintConfig filament_overrides; + t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides); + t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config); + // Collect changes to object and region configs. + t_config_option_keys object_diff = m_default_object_config.diff(new_full_config); + t_config_option_keys region_diff = m_default_region_config.diff(new_full_config); + + // Do not use the ApplyStatus as we will use the max function when updating apply_status. + unsigned int apply_status = APPLY_STATUS_UNCHANGED; + auto update_apply_status = [&apply_status](bool invalidated) + { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; + if (! (print_diff.empty() && object_diff.empty() && region_diff.empty())) + update_apply_status(false); + + // Grab the lock for the Print / PrintObject milestones. + std::scoped_lock lock(this->state_mutex()); + + // The following call may stop the background processing. + if (! print_diff.empty()) + update_apply_status(this->invalidate_state_by_config_options(new_full_config, print_diff)); + + // Apply variables to placeholder parser. The placeholder parser is used by G-code export, + // which should be stopped if print_diff is not empty. + size_t num_extruders = m_config.nozzle_diameter.size(); + bool num_extruders_changed = false; + if (! full_config_diff.empty()) { + update_apply_status(this->invalidate_step(psGCodeExport)); + m_placeholder_parser.clear_config(); + // Set the profile aliases for the PrintBase::output_filename() + m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); + m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("physical_printer_preset", new_full_config.option("physical_printer_settings_id")->clone()); + // We want the filament overrides to be applied over their respective extruder parameters by the PlaceholderParser. + // see "Placeholders do not respect filament overrides." GH issue #3649 + m_placeholder_parser.apply_config(filament_overrides); + // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. + m_config.apply_only(new_full_config, print_diff, true); + //FIXME use move semantics once ConfigBase supports it. + // Some filament_overrides may contain values different from new_full_config, but equal to m_config. + // As long as these config options don't reallocate memory when copying, we are safe overriding a value, which is in use by a worker thread. + m_config.apply(filament_overrides); + // Handle changes to object config defaults + m_default_object_config.apply_only(new_full_config, object_diff, true); + // Handle changes to regions config defaults + m_default_region_config.apply_only(new_full_config, region_diff, true); + m_full_print_config = std::move(new_full_config); + if (num_extruders != m_config.nozzle_diameter.size()) { + num_extruders = m_config.nozzle_diameter.size(); + num_extruders_changed = true; + } + } + + ModelObjectStatusDB model_object_status_db; + + // 1) Synchronize model objects. + bool print_regions_reshuffled = false; + if (model.id() != m_model.id()) { + // Kill everything, initialize from scratch. + // Stop background processing. + this->call_cancel_callback(); + update_apply_status(this->invalidate_all_steps()); + for (PrintObject *object : m_objects) { + model_object_status_db.add(*object->model_object(), ModelObjectStatus::Deleted); + update_apply_status(object->invalidate_all_steps()); + delete object; + } + m_objects.clear(); + print_regions_reshuffled = true; + m_model.assign_copy(model); + for (const ModelObject *model_object : m_model.objects) + model_object_status_db.add(*model_object, ModelObjectStatus::New); + } else { + if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { + update_apply_status(num_extruders_changed || + // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. + //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable + // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. + (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ? + // The Tool Ordering and the Wipe Tower are no more valid. + this->invalidate_steps({ psWipeTower, psGCodeExport }) : + // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering. + this->invalidate_step(psGCodeExport)); + m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; + } + if (model_object_list_equal(m_model, model)) { + // The object list did not change. + for (const ModelObject *model_object : m_model.objects) + model_object_status_db.add(*model_object, ModelObjectStatus::Old); + } else if (model_object_list_extended(m_model, model)) { + // Add new objects. Their volumes and configs will be synchronized later. + update_apply_status(this->invalidate_step(psGCodeExport)); + for (const ModelObject *model_object : m_model.objects) + model_object_status_db.add(*model_object, ModelObjectStatus::Old); + for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { + model_object_status_db.add(*model.objects[i], ModelObjectStatus::New); + m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i])); + m_model.objects.back()->set_model(&m_model); + } + } else { + // Reorder the objects, add new objects. + // First stop background processing before shuffling or deleting the PrintObjects in the object list. + this->call_cancel_callback(); + update_apply_status(this->invalidate_step(psGCodeExport)); + // Second create a new list of objects. + std::vector model_objects_old(std::move(m_model.objects)); + m_model.objects.clear(); + m_model.objects.reserve(model.objects.size()); + auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); }; + std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower); + for (const ModelObject *mobj : model.objects) { + auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower); + if (it == model_objects_old.end() || (*it)->id() != mobj->id()) { + // New ModelObject added. + m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); + m_model.objects.back()->set_model(&m_model); + model_object_status_db.add(*mobj, ModelObjectStatus::New); + } else { + // Existing ModelObject re-added (possibly moved in the list). + m_model.objects.emplace_back(*it); + model_object_status_db.add(*mobj, ModelObjectStatus::Moved); + } + } + bool deleted_any = false; + for (ModelObject *&model_object : model_objects_old) + if (model_object_status_db.add_if_new(*model_object, ModelObjectStatus::Deleted)) + deleted_any = true; + else + // Do not delete this ModelObject instance. + model_object = nullptr; + if (deleted_any) { + // Delete PrintObjects of the deleted ModelObjects. + PrintObjectPtrs print_objects_old = std::move(m_objects); + m_objects.clear(); + m_objects.reserve(print_objects_old.size()); + for (PrintObject *print_object : print_objects_old) { + const ModelObjectStatus &status = model_object_status_db.get(*print_object->model_object()); + if (status.status == ModelObjectStatus::Deleted) { + update_apply_status(print_object->invalidate_all_steps()); + delete print_object; + } else + m_objects.emplace_back(print_object); + } + for (ModelObject *model_object : model_objects_old) + delete model_object; + print_regions_reshuffled = true; + } + } + } + + // 2) Map print objects including their transformation matrices. + PrintObjectStatusDB print_object_status_db(m_objects); + + // 3) Synchronize ModelObjects & PrintObjects. + const std::initializer_list solid_or_modifier_types { ModelVolumeType::MODEL_PART, ModelVolumeType::NEGATIVE_VOLUME, ModelVolumeType::PARAMETER_MODIFIER }; + for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { + ModelObject &model_object = *m_model.objects[idx_model_object]; + ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(model_object)); + const ModelObject &model_object_new = *model.objects[idx_model_object]; + if (model_object_status.status == ModelObjectStatus::New) + // PrintObject instances will be added in the next loop. + continue; + // Update the ModelObject instance, possibly invalidate the linked PrintObjects. + assert(model_object_status.status == ModelObjectStatus::Old || model_object_status.status == ModelObjectStatus::Moved); + // Check whether a model part volume was added or removed, their transformations or order changed. + // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. + bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) || + model_mmu_segmentation_data_changed(model_object, model_object_new) || + (model_object_new.is_mm_painted() && num_extruders_changed); + bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || + model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); + bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty()); + bool model_origin_translation_differ = model_object.origin_translation != model_object_new.origin_translation; + auto print_objects_range = print_object_status_db.get_range(model_object); + // The list actually can be empty if all instances are out of the print bed. + //assert(print_objects_range.begin() != print_objects_range.end()); + // All PrintObjects in print_objects_range shall point to the same prints_objects_regions + if (print_objects_range.begin() != print_objects_range.end()) { + model_object_status.print_object_regions = print_objects_range.begin()->print_object->m_shared_regions; + model_object_status.print_object_regions->ref_cnt_inc(); + } + if (solid_or_modifier_differ || model_origin_translation_differ || layer_height_ranges_differ || + ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile)) { + // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. + model_object_status.print_object_regions_status = + model_object_status.print_object_regions == nullptr || model_origin_translation_differ || layer_height_ranges_differ ? + // Drop print_objects_regions. + ModelObjectStatus::PrintObjectRegionsStatus::Invalid : + // Reuse bounding boxes of print_objects_regions for ModelVolumes with unmodified transformation. + ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; + for (const PrintObjectStatus &print_object_status : print_objects_range) { + update_apply_status(print_object_status.print_object->invalidate_all_steps()); + const_cast(print_object_status).status = PrintObjectStatus::Deleted; + } + if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid) + // Drop everything from PrintObjectRegions but those VolumeExtents (of their particular ModelVolumes) that are still valid. + print_objects_regions_invalidate_keep_some_volumes(*model_object_status.print_object_regions, model_object.volumes, model_object_new.volumes); + else if (model_object_status.print_object_regions != nullptr) + model_object_status.print_object_regions->clear(); + // Copy content of the ModelObject including its ID, do not change the parent. + model_object.assign_copy(model_object_new); + } else { + model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Valid; + if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) { + // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. + if (supports_differ) { + this->call_cancel_callback(); + update_apply_status(false); + } + // Invalidate just the supports step. + for (const PrintObjectStatus &print_object_status : print_objects_range) + update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial)); + if (supports_differ) { + // Copy just the support volumes. + model_volume_list_update_supports(model_object, model_object_new); + } + } else if (model_custom_seam_data_changed(model_object, model_object_new)) { + update_apply_status(this->invalidate_step(psGCodeExport)); + } + } + if (! solid_or_modifier_differ) { + // Synchronize Object's config. + bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config); + if (object_config_changed) + model_object.config.assign_config(model_object_new.config); + if (! object_diff.empty() || object_config_changed || num_extruders_changed) { + PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); + for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) { + t_config_option_keys diff = print_object_status.print_object->config().diff(new_config); + if (! diff.empty()) { + update_apply_status(print_object_status.print_object->invalidate_state_by_config_options(print_object_status.print_object->config(), new_config, diff)); + print_object_status.print_object->config_apply_only(new_config, diff, true); + } + } + } + // Synchronize (just copy) the remaining data of ModelVolumes (name, config, custom supports data). + //FIXME What to do with m_material_id? + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); + layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); + // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. + model_object.name = model_object_new.name; + model_object.input_file = model_object_new.input_file; + // Only refresh ModelInstances if there is any change. + if (model_object.instances.size() != model_object_new.instances.size() || + ! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), [](auto l, auto r){ return l->id() == r->id(); })) { + // G-code generator accesses model_object.instances to generate sequential print ordering matching the Plater object list. + update_apply_status(this->invalidate_step(psGCodeExport)); + model_object.clear_instances(); + model_object.instances.reserve(model_object_new.instances.size()); + for (const ModelInstance *model_instance : model_object_new.instances) { + model_object.instances.emplace_back(new ModelInstance(*model_instance)); + model_object.instances.back()->set_model_object(&model_object); + } + } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), + [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable && + l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) { + // If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid. + // This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only. + model_object.invalidate_bounding_box(); + // Synchronize the content of instances. + auto new_instance = model_object_new.instances.begin(); + for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) { + (*old_instance)->set_transformation((*new_instance)->get_transformation()); + (*old_instance)->print_volume_state = (*new_instance)->print_volume_state; + (*old_instance)->printable = (*new_instance)->printable; + } + } + } + } + + // 4) Generate PrintObjects from ModelObjects and their instances. + { + PrintObjectPtrs print_objects_new; + print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); + bool new_objects = false; + // Walk over all new model objects and check, whether there are matching PrintObjects. + for (ModelObject *model_object : m_model.objects) { + ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(*model_object)); + model_object_status.print_instances = print_objects_from_model_object(*model_object); + std::vector old; + old.reserve(print_object_status_db.count(*model_object)); + for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object)) + if (print_object_status.status != PrintObjectStatus::Deleted) + old.emplace_back(&print_object_status); + // Generate a list of trafos and XY offsets for instances of a ModelObject + // Producing the config for PrintObject on demand, caching it at print_object_last. + const PrintObject *print_object_last = nullptr; + auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders](PrintObject *print_object) { + print_object->config_apply(print_object_last ? + print_object_last->config() : + PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders)); + print_object_last = print_object; + }; + if (old.empty()) { + // Simple case, just generate new instances. + for (PrintObjectTrafoAndInstances &print_instances : model_object_status.print_instances) { + PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances)); + print_object_apply_config(print_object); + print_objects_new.emplace_back(print_object); + // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); + new_objects = true; + } + continue; + } + // Complex case, try to merge the two lists. + // Sort the old lexicographically by their trafos. + std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); }); + // Merge the old / new lists. + auto it_old = old.begin(); + for (PrintObjectTrafoAndInstances &new_instances : model_object_status.print_instances) { + for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old); + if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { + // This is a new instance (or a set of instances with the same trafo). Just add it. + PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances)); + print_object_apply_config(print_object); + print_objects_new.emplace_back(print_object); + // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); + new_objects = true; + if (it_old != old.end()) + const_cast(*it_old)->status = PrintObjectStatus::Deleted; + } else { + // The PrintObject already exists and the copies differ. + PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances)); + if (status != PrintBase::APPLY_STATUS_UNCHANGED) + update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED); + print_objects_new.emplace_back((*it_old)->print_object); + const_cast(*it_old)->status = PrintObjectStatus::Reused; + } + } + } + if (m_objects != print_objects_new) { + this->call_cancel_callback(); + update_apply_status(this->invalidate_all_steps()); + m_objects = print_objects_new; + // Delete the PrintObjects marked as Unknown or Deleted. + bool deleted_objects = false; + for (const PrintObjectStatus &pos : print_object_status_db) + if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { + update_apply_status(pos.print_object->invalidate_all_steps()); + delete pos.print_object; + deleted_objects = true; + } + if (new_objects || deleted_objects) + update_apply_status(this->invalidate_steps({ psSkirtBrim, psWipeTower, psGCodeExport })); + if (new_objects) + update_apply_status(false); + print_regions_reshuffled = true; + } + print_object_status_db.clear(); + } + + // All regions now have distinct settings. + // Check whether applying the new region config defaults we would get different regions, + // update regions or create regions from scratch. + for (auto it_print_object = m_objects.begin(); it_print_object != m_objects.end();) { + // Find the range of PrintObjects sharing the same associated ModelObject. + auto it_print_object_end = it_print_object; + PrintObject &print_object = *(*it_print_object); + const ModelObject &model_object = *print_object.model_object(); + ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(model_object)); + PrintObjectRegions *print_object_regions = model_object_status.print_object_regions; + for (++ it_print_object_end; it_print_object_end != m_objects.end() && (*it_print_object)->model_object() == (*it_print_object_end)->model_object(); ++ it_print_object_end) + assert((*it_print_object_end)->m_shared_regions == nullptr || (*it_print_object_end)->m_shared_regions == print_object_regions); + if (print_object_regions == nullptr) { + print_object_regions = new PrintObjectRegions{}; + model_object_status.print_object_regions = print_object_regions; + print_object_regions->ref_cnt_inc(); + } + std::vector painting_extruders; + if (const auto &volumes = print_object.model_object()->volumes; + num_extruders > 1 && + std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) { + //FIXME be more specific! Don't enumerate extruders that are not used for painting! + painting_extruders.assign(num_extruders, 0); + std::iota(painting_extruders.begin(), painting_extruders.end(), 1); + } + if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { + // Verify that the trafo for regions & volume bounding boxes thus for regions is still applicable. + auto invalidate = [it_print_object, it_print_object_end, update_apply_status]() { + for (auto it = it_print_object; it != it_print_object_end; ++ it) + if ((*it)->m_shared_regions != nullptr) + update_apply_status((*it)->invalidate_all_steps()); + }; + if (print_object_regions && ! trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(print_object_regions->trafo_bboxes, model_object_status.print_instances.front().trafo)) { + invalidate(); + print_object_regions->clear(); + model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Invalid; + print_regions_reshuffled = true; + } else if (print_object_regions && + verify_update_print_object_regions( + print_object.model_object()->volumes, + m_default_region_config, + num_extruders, + painting_extruders, + *print_object_regions, + [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { + for (auto it = it_print_object; it != it_print_object_end; ++it) + if ((*it)->m_shared_regions != nullptr) + update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys)); + })) { + // Regions are valid, just keep them. + } else { + // Regions were reshuffled. + invalidate(); + // At least reuse layer ranges and bounding boxes of ModelVolumes. + model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; + print_regions_reshuffled = true; + } + } + if (print_object_regions == nullptr || model_object_status.print_object_regions_status != ModelObjectStatus::PrintObjectRegionsStatus::Valid) { + // Layer ranges with their associated configurations. Remove overlaps between the ranges + // and create the regions from scratch. + print_object_regions = generate_print_object_regions( + print_object_regions, + print_object.model_object()->volumes, + LayerRanges(print_object.model_object()->layer_config_ranges), + m_default_region_config, + model_object_status.print_instances.front().trafo, + num_extruders, + print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value), + painting_extruders); + } + for (auto it = it_print_object; it != it_print_object_end; ++it) + if ((*it)->m_shared_regions) { + assert((*it)->m_shared_regions == print_object_regions); + } else { + (*it)->m_shared_regions = print_object_regions; + print_object_regions->ref_cnt_inc(); + } + it_print_object = it_print_object_end; + } + + if (print_regions_reshuffled) { + // Update Print::m_print_regions from objects. + struct cmp { bool operator() (const PrintRegion *l, const PrintRegion *r) const { return l->config_hash() == r->config_hash() && l->config() == r->config(); } }; + std::set region_set; + m_print_regions.clear(); + PrintObjectRegions *print_object_regions = nullptr; + for (PrintObject *print_object : m_objects) + if (print_object_regions != print_object->m_shared_regions) { + print_object_regions = print_object->m_shared_regions; + for (std::unique_ptr &print_region : print_object_regions->all_regions) + if (auto it = region_set.find(print_region.get()); it == region_set.end()) { + int print_region_id = int(m_print_regions.size()); + m_print_regions.emplace_back(print_region.get()); + print_region->m_print_region_id = print_region_id; + } else { + print_region->m_print_region_id = (*it)->print_region_id(); + } + } + } + + // Update SlicingParameters for each object where the SlicingParameters is not valid. + // If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use + // (posSlicing and posSupportMaterial was invalidated). + for (PrintObject *object : m_objects) + object->update_slicing_parameters(); + +#ifdef _DEBUG + check_model_ids_equal(m_model, model); +#endif /* _DEBUG */ + + return static_cast(apply_status); +} + +} // namespace Slic3r diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 6a73aaf1e..c44b914b4 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -79,6 +79,8 @@ #define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) // Enable alternate implementation of manipulating scale for instances and volumes #define ENABLE_WORLD_COORDINATE_SCALE_REVISITED (1 && ENABLE_WORLD_COORDINATE) +// Enable implementation of Geometry::Transformation using matrices only +#define ENABLE_TRANSFORMATIONS_BY_MATRICES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index a85b8092a..263d8af08 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -1,783 +1,811 @@ -#ifndef slic3r_3DScene_hpp_ -#define slic3r_3DScene_hpp_ - -#include "libslic3r/libslic3r.h" -#include "libslic3r/Point.hpp" -#include "libslic3r/Line.hpp" -#include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Geometry.hpp" -#include "libslic3r/Color.hpp" - -#include "GLModel.hpp" - -#include -#include - -#ifndef NDEBUG -#define HAS_GLSAFE -#endif // NDEBUG - -#ifdef HAS_GLSAFE - extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); - inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } - #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) - #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) -#else // HAS_GLSAFE - inline void glAssertRecentCall() { } - #define glsafe(cmd) cmd - #define glcheck() -#endif // HAS_GLSAFE - -namespace Slic3r { -class SLAPrintObject; -enum SLAPrintObjectStep : unsigned int; -class BuildVolume; -class DynamicPrintConfig; -class ExtrusionPath; -class ExtrusionMultiPath; -class ExtrusionLoop; -class ExtrusionEntity; -class ExtrusionEntityCollection; -class ModelObject; -class ModelVolume; -enum ModelInstanceEPrintVolumeState : unsigned char; - -// Return appropriate color based on the ModelVolume. -extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL -// A container for interleaved arrays of 3D vertices and normals, -// possibly indexed by triangles and / or quads. -class GLIndexedVertexArray { -public: - // Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized. - static_assert(sizeof(Eigen::AlignedBox) == 24, "Eigen::AlignedBox is not being vectorized, thus it does not need to be aligned"); - using BoundingBox = Eigen::AlignedBox; - - GLIndexedVertexArray() { m_bounding_box.setEmpty(); } - GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : - vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), - triangle_indices(rhs.triangle_indices), - quad_indices(rhs.quad_indices), - m_bounding_box(rhs.m_bounding_box) - { assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); } - GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : - vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), - triangle_indices(std::move(rhs.triangle_indices)), - quad_indices(std::move(rhs.quad_indices)), - m_bounding_box(rhs.m_bounding_box) - { assert(! rhs.has_VBOs()); } - - ~GLIndexedVertexArray() { release_geometry(); } - - GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) - { - assert(vertices_and_normals_interleaved_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - assert(quad_indices_VBO_id == 0); - assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); - assert(rhs.triangle_indices_VBO_id == 0); - assert(rhs.quad_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; - this->triangle_indices = rhs.triangle_indices; - this->quad_indices = rhs.quad_indices; - this->m_bounding_box = rhs.m_bounding_box; - this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; - this->triangle_indices_size = rhs.triangle_indices_size; - this->quad_indices_size = rhs.quad_indices_size; - return *this; - } - - GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs) - { - assert(vertices_and_normals_interleaved_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - assert(quad_indices_VBO_id == 0); - assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); - assert(rhs.triangle_indices_VBO_id == 0); - assert(rhs.quad_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); - this->triangle_indices = std::move(rhs.triangle_indices); - this->quad_indices = std::move(rhs.quad_indices); - this->m_bounding_box = rhs.m_bounding_box; - this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; - this->triangle_indices_size = rhs.triangle_indices_size; - this->quad_indices_size = rhs.quad_indices_size; - return *this; - } - - // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) - std::vector vertices_and_normals_interleaved; - std::vector triangle_indices; - std::vector quad_indices; - - // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, - // the above mentioned std::vectors are cleared and the following variables keep their original length. - size_t vertices_and_normals_interleaved_size{ 0 }; - size_t triangle_indices_size{ 0 }; - size_t quad_indices_size{ 0 }; - - // IDs of the Vertex Array Objects, into which the geometry has been loaded. - // Zero if the VBOs are not sent to GPU yet. - unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; - unsigned int triangle_indices_VBO_id{ 0 }; - unsigned int quad_indices_VBO_id{ 0 }; - -#if ENABLE_SMOOTH_NORMALS - void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false); - void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); } -#else - void load_mesh_full_shading(const TriangleMesh& mesh); - void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } -#endif // ENABLE_SMOOTH_NORMALS - - void load_its_flat_shading(const indexed_triangle_set &its); - - inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } - - inline void reserve(size_t sz) { - this->vertices_and_normals_interleaved.reserve(sz * 6); - this->triangle_indices.reserve(sz * 3); - this->quad_indices.reserve(sz * 4); - } - - inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - if (this->vertices_and_normals_interleaved_VBO_id != 0) - return; - - if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) - this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); - this->vertices_and_normals_interleaved.emplace_back(nx); - this->vertices_and_normals_interleaved.emplace_back(ny); - this->vertices_and_normals_interleaved.emplace_back(nz); - this->vertices_and_normals_interleaved.emplace_back(x); - this->vertices_and_normals_interleaved.emplace_back(y); - this->vertices_and_normals_interleaved.emplace_back(z); - - this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); - m_bounding_box.extend(Vec3f(x, y, z)); - }; - - inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { - push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz)); - } - - template - inline void push_geometry(const Eigen::MatrixBase& p, const Eigen::MatrixBase& n) { - push_geometry(float(p(0)), float(p(1)), float(p(2)), float(n(0)), float(n(1)), float(n(2))); - } - - inline void push_triangle(int idx1, int idx2, int idx3) { - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - if (this->vertices_and_normals_interleaved_VBO_id != 0) - return; - - if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) - this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); - this->triangle_indices.emplace_back(idx1); - this->triangle_indices.emplace_back(idx2); - this->triangle_indices.emplace_back(idx3); - this->triangle_indices_size = this->triangle_indices.size(); - }; - - inline void push_quad(int idx1, int idx2, int idx3, int idx4) { - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - if (this->vertices_and_normals_interleaved_VBO_id != 0) - return; - - if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) - this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); - this->quad_indices.emplace_back(idx1); - this->quad_indices.emplace_back(idx2); - this->quad_indices.emplace_back(idx3); - this->quad_indices.emplace_back(idx4); - this->quad_indices_size = this->quad_indices.size(); - }; - - // Finalize the initialization of the geometry & indices, - // upload the geometry and indices to OpenGL VBO objects - // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool opengl_initialized); - // Release the geometry data, release OpenGL VBOs. - void release_geometry(); - - void render() const; - void render(const std::pair& tverts_range, const std::pair& qverts_range) const; - - // Is there any geometry data stored? - bool empty() const { return vertices_and_normals_interleaved_size == 0; } - - void clear() { - this->vertices_and_normals_interleaved.clear(); - this->triangle_indices.clear(); - this->quad_indices.clear(); - vertices_and_normals_interleaved_size = 0; - triangle_indices_size = 0; - quad_indices_size = 0; - m_bounding_box.setEmpty(); - } - - // Shrink the internal storage to tighly fit the data stored. - void shrink_to_fit() { - this->vertices_and_normals_interleaved.shrink_to_fit(); - this->triangle_indices.shrink_to_fit(); - this->quad_indices.shrink_to_fit(); - } - - const BoundingBox& bounding_box() const { return m_bounding_box; } - - // Return an estimate of the memory consumed by this class. - size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); } - // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const - { - size_t memsize = 0; - if (this->vertices_and_normals_interleaved_VBO_id != 0) - memsize += this->vertices_and_normals_interleaved_size * 4; - if (this->triangle_indices_VBO_id != 0) - memsize += this->triangle_indices_size * 4; - if (this->quad_indices_VBO_id != 0) - memsize += this->quad_indices_size * 4; - return memsize; - } - size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } - -private: - BoundingBox m_bounding_box; -}; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -class GLVolume { -public: - static const ColorRGBA SELECTED_COLOR; - static const ColorRGBA HOVER_SELECT_COLOR; - static const ColorRGBA HOVER_DESELECT_COLOR; - static const ColorRGBA OUTSIDE_COLOR; - static const ColorRGBA SELECTED_OUTSIDE_COLOR; - static const ColorRGBA DISABLED_COLOR; - static const ColorRGBA SLA_SUPPORT_COLOR; - static const ColorRGBA SLA_PAD_COLOR; - static const ColorRGBA NEUTRAL_COLOR; - static const std::array MODEL_COLOR; - - enum EHoverState : unsigned char - { - HS_None, - HS_Hover, - HS_Select, - HS_Deselect - }; - - GLVolume(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f); - GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {} - -private: - Geometry::Transformation m_instance_transformation; - Geometry::Transformation m_volume_transformation; - - // Shift in z required by sla supports+pad - double m_sla_shift_z; - // Bounding box of this volume, in unscaled coordinates. - std::optional m_transformed_bounding_box; - // Convex hull of the volume, if any. - std::shared_ptr m_convex_hull; - // Bounding box of this volume, in unscaled coordinates. - std::optional m_transformed_convex_hull_bounding_box; - // Bounding box of the non sinking part of this volume, in unscaled coordinates. - std::optional m_transformed_non_sinking_bounding_box; - - class SinkingContours - { - static const float HalfWidth; - GLVolume& m_parent; - GUI::GLModel m_model; - BoundingBoxf3 m_old_box; - Vec3d m_shift{ Vec3d::Zero() }; - - public: - SinkingContours(GLVolume& volume) : m_parent(volume) {} - void render(); - - private: - void update(); - }; - - SinkingContours m_sinking_contours; - -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - class NonManifoldEdges - { - GLVolume& m_parent; - GUI::GLModel m_model; - bool m_update_needed{ true }; - - public: - NonManifoldEdges(GLVolume& volume) : m_parent(volume) {} - void render(); - void set_as_dirty() { m_update_needed = true; } - - private: - void update(); - }; - - NonManifoldEdges m_non_manifold_edges; -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - -public: - // Color of the triangles / quads held by this volume. - ColorRGBA color; - // Color used to render this volume. - ColorRGBA render_color; - - struct CompositeID { - CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {} - CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {} - // Object ID, which is equal to the index of the respective ModelObject in Model.objects array. - int object_id; - // Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array. - // If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject, - // and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports. - // Volume with a negative volume_id cannot be picked independently, it will pick the associated instance. - int volume_id; - // Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array. - int instance_id; - bool operator==(const CompositeID &rhs) const { return object_id == rhs.object_id && volume_id == rhs.volume_id && instance_id == rhs.instance_id; } - bool operator!=(const CompositeID &rhs) const { return ! (*this == rhs); } - bool operator< (const CompositeID &rhs) const - { return object_id < rhs.object_id || (object_id == rhs.object_id && (volume_id < rhs.volume_id || (volume_id == rhs.volume_id && instance_id < rhs.instance_id))); } - }; - CompositeID composite_id; - // Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID, - // for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(), - // and the associated ModelInstanceID. - // Valid geometry_id should always be positive. - std::pair geometry_id; - // An ID containing the extruder ID (used to select color). - int extruder_id; - - // Various boolean flags. - struct { - // Is this object selected? - bool selected : 1; - // Is this object disabled from selection? - bool disabled : 1; - // Is this object printable? - bool printable : 1; - // Whether or not this volume is active for rendering - bool is_active : 1; - // Whether or not to use this volume when applying zoom_to_volumes() - bool zoom_to_volumes : 1; - // Wheter or not this volume is enabled for outside print volume detection in shader. - bool shader_outside_printer_detection_enabled : 1; - // Wheter or not this volume is outside print volume. - bool is_outside : 1; - // Wheter or not this volume has been generated from a modifier - bool is_modifier : 1; - // Wheter or not this volume has been generated from the wipe tower - bool is_wipe_tower : 1; - // Wheter or not this volume has been generated from an extrusion path - bool is_extrusion_path : 1; - // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) - bool force_native_color : 1; - // Whether or not render this volume in neutral - bool force_neutral_color : 1; - // Whether or not to force rendering of sinking contours - bool force_sinking_contours : 1; - }; - - // Is mouse or rectangle selection over this object to select/deselect it ? - EHoverState hover; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GUI::GLModel model; -#else - // Interleaved triangles & normals with indexed triangles & quads. - GLIndexedVertexArray indexed_vertex_array; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - // Ranges of triangle and quad indices to be rendered. - std::pair tverts_range; -#if !ENABLE_LEGACY_OPENGL_REMOVAL - std::pair qverts_range; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts - // of the extrusions per layer. - std::vector print_zs; - // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. - std::vector offsets; - - // Bounding box of this volume, in unscaled coordinates. - BoundingBoxf3 bounding_box() const { -#if ENABLE_LEGACY_OPENGL_REMOVAL - return this->model.get_bounding_box(); -#else - BoundingBoxf3 out; - if (!this->indexed_vertex_array.bounding_box().isEmpty()) { - out.min = this->indexed_vertex_array.bounding_box().min().cast(); - out.max = this->indexed_vertex_array.bounding_box().max().cast(); - out.defined = true; - } - return out; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - void set_color(const ColorRGBA& rgba) { color = rgba; } - void set_render_color(const ColorRGBA& rgba) { render_color = rgba; } - // Sets render color in dependence of current state - void set_render_color(bool force_transparent); - // set color according to model volume - void set_color_from_model_volume(const ModelVolume& model_volume); - - const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } - void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } - - const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } - double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); } - - void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } - void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); } - double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); } - - void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } - void set_instance_rotation(Axis axis, double rotation) { m_instance_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } - - Vec3d get_instance_scaling_factor() const { return m_instance_transformation.get_scaling_factor(); } - double get_instance_scaling_factor(Axis axis) const { return m_instance_transformation.get_scaling_factor(axis); } - - void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } - void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); } - double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); } - - void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } - void set_instance_mirror(Axis axis, double mirror) { m_instance_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } - - const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } - void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } - - const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } - double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); } - - void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } - void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); } - double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); } - - void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } - void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } - double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); } - - void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } - void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); } - double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); } - - void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } - void set_volume_mirror(Axis axis, double mirror) { m_volume_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } - - double get_sla_shift_z() const { return m_sla_shift_z; } - void set_sla_shift_z(double z) { m_sla_shift_z = z; } - - void set_convex_hull(std::shared_ptr convex_hull) { m_convex_hull = std::move(convex_hull); } - void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared(convex_hull); } - void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared(std::move(convex_hull)); } - - int object_idx() const { return this->composite_id.object_id; } - int volume_idx() const { return this->composite_id.volume_id; } - int instance_idx() const { return this->composite_id.instance_id; } - - Transform3d world_matrix() const; - bool is_left_handed() const; - - const BoundingBoxf3& transformed_bounding_box() const; - // non-caching variant - BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; - // caching variant - const BoundingBoxf3& transformed_convex_hull_bounding_box() const; - // non-caching variant - BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const; - // caching variant - const BoundingBoxf3& transformed_non_sinking_bounding_box() const; - // convex hull - const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - bool empty() const { return this->model.is_empty(); } -#else - bool empty() const { return this->indexed_vertex_array.empty(); } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - void set_range(double low, double high); - - void render(); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } - void release_geometry() { this->indexed_vertex_array.release_geometry(); } -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - void set_bounding_boxes_as_dirty() { - m_transformed_bounding_box.reset(); - m_transformed_convex_hull_bounding_box.reset(); - m_transformed_non_sinking_bounding_box.reset(); - } - - bool is_sla_support() const; - bool is_sla_pad() const; - - bool is_sinking() const; - bool is_below_printbed() const; - void render_sinking_contours(); -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - void render_non_manifold_edges(); -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - - // Return an estimate of the memory consumed by this class. - size_t cpu_memory_used() const { -#if ENABLE_LEGACY_OPENGL_REMOVAL - return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + - this->offsets.capacity() * sizeof(size_t); - } - // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const { return this->model.gpu_memory_used(); } -#else - //FIXME what to do wih m_convex_hull? - return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t); - } - // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } -}; - -typedef std::vector GLVolumePtrs; -typedef std::pair> GLVolumeWithIdAndZ; -typedef std::vector GLVolumeWithIdAndZList; - -class GLVolumeCollection -{ -public: - enum class ERenderType : unsigned char - { - Opaque, - Transparent, - All - }; - - struct PrintVolume - { - // see: Bed3D::EShapeType - int type{ 0 }; - // data contains: - // Rectangle: - // [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y - // Circle: - // [0] = center.x, [1] = center.y, [3] = radius - std::array data; - // [0] = min z, [1] = max z - std::array zs; - }; - -private: - PrintVolume m_print_volume; - - // z range for clipping in shaders - std::array m_z_range; - - // plane coeffs for clipping in shaders - std::array m_clipping_plane; - - struct Slope - { - // toggle for slope rendering - bool active{ false }; - float normal_z; - }; - - Slope m_slope; - bool m_show_sinking_contours{ false }; -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - bool m_show_non_manifold_edges{ true }; -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - -public: - GLVolumePtrs volumes; - - GLVolumeCollection() { set_default_slope_normal_z(); } - ~GLVolumeCollection() { clear(); } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - std::vector load_object( - const ModelObject* model_object, - int obj_idx, - const std::vector& instance_idxs); - - int load_object_volume( - const ModelObject* model_object, - int obj_idx, - int volume_idx, - int instance_idx); - - // Load SLA auxiliary GLVolumes (for support trees or pad). - void load_object_auxiliary( - const SLAPrintObject* print_object, - int obj_idx, - // pairs of - const std::vector>& instances, - SLAPrintObjectStep milestone, - // Timestamp of the last change of the milestone - size_t timestamp); - -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - int load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); -#else - int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -#else - std::vector load_object( - const ModelObject *model_object, - int obj_idx, - const std::vector &instance_idxs, - bool opengl_initialized); - - int load_object_volume( - const ModelObject *model_object, - int obj_idx, - int volume_idx, - int instance_idx, - bool opengl_initialized); - - // Load SLA auxiliary GLVolumes (for support trees or pad). - void load_object_auxiliary( - const SLAPrintObject *print_object, - int obj_idx, - // pairs of - const std::vector>& instances, - SLAPrintObjectStep milestone, - // Timestamp of the last change of the milestone - size_t timestamp, - bool opengl_initialized); - -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - int load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); -#else - int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLVolume* new_toolpath_volume(const ColorRGBA& rgba); - GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba); -#else - GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); - GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // Render the volumes by OpenGL. -#if ENABLE_GL_SHADERS_ATTRIBUTES - void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, const Transform3d& projection_matrix, - std::function filter_func = std::function()) const; -#else - void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - // Finalize the initialization of the geometry & indices, - // upload the geometry and indices to OpenGL VBO objects - // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); } - // Release the geometry data assigned to the volumes. - // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. - void release_geometry() { for (auto *v : volumes) v->release_geometry(); } -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - // Clear the geometry - void clear() { for (auto *v : volumes) delete v; volumes.clear(); } - - bool empty() const { return volumes.empty(); } - void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); } - - void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } - - void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } - void set_clipping_plane(const std::array& coeffs) { m_clipping_plane = coeffs; } - - const std::array& get_z_range() const { return m_z_range; } - const std::array& get_clipping_plane() const { return m_clipping_plane; } - - bool is_slope_active() const { return m_slope.active; } - void set_slope_active(bool active) { m_slope.active = active; } - - float get_slope_normal_z() const { return m_slope.normal_z; } - void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; } - void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); } - void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; } -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; } -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - - // returns true if all the volumes are completely contained in the print volume - // returns the containment state in the given out_state, if non-null - bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const; - void reset_outside_state(); - - void update_colors_by_extruder(const DynamicPrintConfig* config); - - // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection - std::vector get_current_print_zs(bool active_only) const; - - // Return an estimate of the memory consumed by this class. - size_t cpu_memory_used() const; - // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const; - size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } - // Return CPU, GPU and total memory log line. - std::string log_memory_info() const; - -private: - GLVolumeCollection(const GLVolumeCollection &other); - GLVolumeCollection& operator=(const GLVolumeCollection &); -}; - -GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func = nullptr); - -struct _3DScene -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL - static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry); - static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); -#else - static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GLVolume& volume); - static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GLVolume& volume); - static void extrusionentity_to_verts(const Polyline& polyline, float width, float height, float print_z, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume); - static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume); - static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -}; - -} - -#endif +#ifndef slic3r_3DScene_hpp_ +#define slic3r_3DScene_hpp_ + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Point.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Color.hpp" + +#include "GLModel.hpp" + +#include +#include + +#ifndef NDEBUG +#define HAS_GLSAFE +#endif // NDEBUG + +#ifdef HAS_GLSAFE + extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); + inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } + #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) + #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) +#else // HAS_GLSAFE + inline void glAssertRecentCall() { } + #define glsafe(cmd) cmd + #define glcheck() +#endif // HAS_GLSAFE + +namespace Slic3r { +class SLAPrintObject; +enum SLAPrintObjectStep : unsigned int; +class BuildVolume; +class DynamicPrintConfig; +class ExtrusionPath; +class ExtrusionMultiPath; +class ExtrusionLoop; +class ExtrusionEntity; +class ExtrusionEntityCollection; +class ModelObject; +class ModelVolume; +enum ModelInstanceEPrintVolumeState : unsigned char; + +// Return appropriate color based on the ModelVolume. +extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL +// A container for interleaved arrays of 3D vertices and normals, +// possibly indexed by triangles and / or quads. +class GLIndexedVertexArray { +public: + // Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized. + static_assert(sizeof(Eigen::AlignedBox) == 24, "Eigen::AlignedBox is not being vectorized, thus it does not need to be aligned"); + using BoundingBox = Eigen::AlignedBox; + + GLIndexedVertexArray() { m_bounding_box.setEmpty(); } + GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : + vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), + triangle_indices(rhs.triangle_indices), + quad_indices(rhs.quad_indices), + m_bounding_box(rhs.m_bounding_box) + { assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); } + GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : + vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), + triangle_indices(std::move(rhs.triangle_indices)), + quad_indices(std::move(rhs.quad_indices)), + m_bounding_box(rhs.m_bounding_box) + { assert(! rhs.has_VBOs()); } + + ~GLIndexedVertexArray() { release_geometry(); } + + GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) + { + assert(vertices_and_normals_interleaved_VBO_id == 0); + assert(triangle_indices_VBO_id == 0); + assert(quad_indices_VBO_id == 0); + assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); + assert(rhs.triangle_indices_VBO_id == 0); + assert(rhs.quad_indices_VBO_id == 0); + this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; + this->triangle_indices = rhs.triangle_indices; + this->quad_indices = rhs.quad_indices; + this->m_bounding_box = rhs.m_bounding_box; + this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + this->triangle_indices_size = rhs.triangle_indices_size; + this->quad_indices_size = rhs.quad_indices_size; + return *this; + } + + GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs) + { + assert(vertices_and_normals_interleaved_VBO_id == 0); + assert(triangle_indices_VBO_id == 0); + assert(quad_indices_VBO_id == 0); + assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); + assert(rhs.triangle_indices_VBO_id == 0); + assert(rhs.quad_indices_VBO_id == 0); + this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); + this->triangle_indices = std::move(rhs.triangle_indices); + this->quad_indices = std::move(rhs.quad_indices); + this->m_bounding_box = rhs.m_bounding_box; + this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + this->triangle_indices_size = rhs.triangle_indices_size; + this->quad_indices_size = rhs.quad_indices_size; + return *this; + } + + // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) + std::vector vertices_and_normals_interleaved; + std::vector triangle_indices; + std::vector quad_indices; + + // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, + // the above mentioned std::vectors are cleared and the following variables keep their original length. + size_t vertices_and_normals_interleaved_size{ 0 }; + size_t triangle_indices_size{ 0 }; + size_t quad_indices_size{ 0 }; + + // IDs of the Vertex Array Objects, into which the geometry has been loaded. + // Zero if the VBOs are not sent to GPU yet. + unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; + unsigned int triangle_indices_VBO_id{ 0 }; + unsigned int quad_indices_VBO_id{ 0 }; + +#if ENABLE_SMOOTH_NORMALS + void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false); + void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); } +#else + void load_mesh_full_shading(const TriangleMesh& mesh); + void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } +#endif // ENABLE_SMOOTH_NORMALS + + void load_its_flat_shading(const indexed_triangle_set &its); + + inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } + + inline void reserve(size_t sz) { + this->vertices_and_normals_interleaved.reserve(sz * 6); + this->triangle_indices.reserve(sz * 3); + this->quad_indices.reserve(sz * 4); + } + + inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + + if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) + this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); + this->vertices_and_normals_interleaved.emplace_back(nx); + this->vertices_and_normals_interleaved.emplace_back(ny); + this->vertices_and_normals_interleaved.emplace_back(nz); + this->vertices_and_normals_interleaved.emplace_back(x); + this->vertices_and_normals_interleaved.emplace_back(y); + this->vertices_and_normals_interleaved.emplace_back(z); + + this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); + m_bounding_box.extend(Vec3f(x, y, z)); + }; + + inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { + push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz)); + } + + template + inline void push_geometry(const Eigen::MatrixBase& p, const Eigen::MatrixBase& n) { + push_geometry(float(p(0)), float(p(1)), float(p(2)), float(n(0)), float(n(1)), float(n(2))); + } + + inline void push_triangle(int idx1, int idx2, int idx3) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + + if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) + this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); + this->triangle_indices.emplace_back(idx1); + this->triangle_indices.emplace_back(idx2); + this->triangle_indices.emplace_back(idx3); + this->triangle_indices_size = this->triangle_indices.size(); + }; + + inline void push_quad(int idx1, int idx2, int idx3, int idx4) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + + if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) + this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); + this->quad_indices.emplace_back(idx1); + this->quad_indices.emplace_back(idx2); + this->quad_indices.emplace_back(idx3); + this->quad_indices.emplace_back(idx4); + this->quad_indices_size = this->quad_indices.size(); + }; + + // Finalize the initialization of the geometry & indices, + // upload the geometry and indices to OpenGL VBO objects + // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. + void finalize_geometry(bool opengl_initialized); + // Release the geometry data, release OpenGL VBOs. + void release_geometry(); + + void render() const; + void render(const std::pair& tverts_range, const std::pair& qverts_range) const; + + // Is there any geometry data stored? + bool empty() const { return vertices_and_normals_interleaved_size == 0; } + + void clear() { + this->vertices_and_normals_interleaved.clear(); + this->triangle_indices.clear(); + this->quad_indices.clear(); + vertices_and_normals_interleaved_size = 0; + triangle_indices_size = 0; + quad_indices_size = 0; + m_bounding_box.setEmpty(); + } + + // Shrink the internal storage to tighly fit the data stored. + void shrink_to_fit() { + this->vertices_and_normals_interleaved.shrink_to_fit(); + this->triangle_indices.shrink_to_fit(); + this->quad_indices.shrink_to_fit(); + } + + const BoundingBox& bounding_box() const { return m_bounding_box; } + + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const + { + size_t memsize = 0; + if (this->vertices_and_normals_interleaved_VBO_id != 0) + memsize += this->vertices_and_normals_interleaved_size * 4; + if (this->triangle_indices_VBO_id != 0) + memsize += this->triangle_indices_size * 4; + if (this->quad_indices_VBO_id != 0) + memsize += this->quad_indices_size * 4; + return memsize; + } + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + +private: + BoundingBox m_bounding_box; +}; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +class GLVolume { +public: + static const ColorRGBA SELECTED_COLOR; + static const ColorRGBA HOVER_SELECT_COLOR; + static const ColorRGBA HOVER_DESELECT_COLOR; + static const ColorRGBA OUTSIDE_COLOR; + static const ColorRGBA SELECTED_OUTSIDE_COLOR; + static const ColorRGBA DISABLED_COLOR; + static const ColorRGBA SLA_SUPPORT_COLOR; + static const ColorRGBA SLA_PAD_COLOR; + static const ColorRGBA NEUTRAL_COLOR; + static const std::array MODEL_COLOR; + + enum EHoverState : unsigned char + { + HS_None, + HS_Hover, + HS_Select, + HS_Deselect + }; + + GLVolume(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f); + GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {} + +private: + Geometry::Transformation m_instance_transformation; + Geometry::Transformation m_volume_transformation; + + // Shift in z required by sla supports+pad + double m_sla_shift_z; + // Bounding box of this volume, in unscaled coordinates. + std::optional m_transformed_bounding_box; + // Convex hull of the volume, if any. + std::shared_ptr m_convex_hull; + // Bounding box of this volume, in unscaled coordinates. + std::optional m_transformed_convex_hull_bounding_box; + // Bounding box of the non sinking part of this volume, in unscaled coordinates. + std::optional m_transformed_non_sinking_bounding_box; + + class SinkingContours + { + static const float HalfWidth; + GLVolume& m_parent; + GUI::GLModel m_model; + BoundingBoxf3 m_old_box; + Vec3d m_shift{ Vec3d::Zero() }; + + public: + SinkingContours(GLVolume& volume) : m_parent(volume) {} + void render(); + + private: + void update(); + }; + + SinkingContours m_sinking_contours; + +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + class NonManifoldEdges + { + GLVolume& m_parent; + GUI::GLModel m_model; + bool m_update_needed{ true }; + + public: + NonManifoldEdges(GLVolume& volume) : m_parent(volume) {} + void render(); + void set_as_dirty() { m_update_needed = true; } + + private: + void update(); + }; + + NonManifoldEdges m_non_manifold_edges; +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + +public: + // Color of the triangles / quads held by this volume. + ColorRGBA color; + // Color used to render this volume. + ColorRGBA render_color; + + struct CompositeID { + CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {} + CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {} + // Object ID, which is equal to the index of the respective ModelObject in Model.objects array. + int object_id; + // Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array. + // If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject, + // and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports. + // Volume with a negative volume_id cannot be picked independently, it will pick the associated instance. + int volume_id; + // Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array. + int instance_id; + bool operator==(const CompositeID &rhs) const { return object_id == rhs.object_id && volume_id == rhs.volume_id && instance_id == rhs.instance_id; } + bool operator!=(const CompositeID &rhs) const { return ! (*this == rhs); } + bool operator< (const CompositeID &rhs) const + { return object_id < rhs.object_id || (object_id == rhs.object_id && (volume_id < rhs.volume_id || (volume_id == rhs.volume_id && instance_id < rhs.instance_id))); } + }; + CompositeID composite_id; + // Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID, + // for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(), + // and the associated ModelInstanceID. + // Valid geometry_id should always be positive. + std::pair geometry_id; + // An ID containing the extruder ID (used to select color). + int extruder_id; + + // Various boolean flags. + struct { + // Is this object selected? + bool selected : 1; + // Is this object disabled from selection? + bool disabled : 1; + // Is this object printable? + bool printable : 1; + // Whether or not this volume is active for rendering + bool is_active : 1; + // Whether or not to use this volume when applying zoom_to_volumes() + bool zoom_to_volumes : 1; + // Wheter or not this volume is enabled for outside print volume detection in shader. + bool shader_outside_printer_detection_enabled : 1; + // Wheter or not this volume is outside print volume. + bool is_outside : 1; + // Wheter or not this volume has been generated from a modifier + bool is_modifier : 1; + // Wheter or not this volume has been generated from the wipe tower + bool is_wipe_tower : 1; + // Wheter or not this volume has been generated from an extrusion path + bool is_extrusion_path : 1; + // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) + bool force_native_color : 1; + // Whether or not render this volume in neutral + bool force_neutral_color : 1; + // Whether or not to force rendering of sinking contours + bool force_sinking_contours : 1; + }; + + // Is mouse or rectangle selection over this object to select/deselect it ? + EHoverState hover; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GUI::GLModel model; +#else + // Interleaved triangles & normals with indexed triangles & quads. + GLIndexedVertexArray indexed_vertex_array; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + // Ranges of triangle and quad indices to be rendered. + std::pair tverts_range; +#if !ENABLE_LEGACY_OPENGL_REMOVAL + std::pair qverts_range; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts + // of the extrusions per layer. + std::vector print_zs; + // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. + std::vector offsets; + + // Bounding box of this volume, in unscaled coordinates. + BoundingBoxf3 bounding_box() const { +#if ENABLE_LEGACY_OPENGL_REMOVAL + return this->model.get_bounding_box(); +#else + BoundingBoxf3 out; + if (!this->indexed_vertex_array.bounding_box().isEmpty()) { + out.min = this->indexed_vertex_array.bounding_box().min().cast(); + out.max = this->indexed_vertex_array.bounding_box().max().cast(); + out.defined = true; + } + return out; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + void set_color(const ColorRGBA& rgba) { color = rgba; } + void set_render_color(const ColorRGBA& rgba) { render_color = rgba; } + // Sets render color in dependence of current state + void set_render_color(bool force_transparent); + // set color according to model volume + void set_color_from_model_volume(const ModelVolume& model_volume); + + const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } + void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_instance_offset() const { return m_instance_transformation.get_offset(); } +#else + const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); } + + void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } + void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_instance_rotation() const { return m_instance_transformation.get_rotation(); } +#else + const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); } + + void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } + void set_instance_rotation(Axis axis, double rotation) { m_instance_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } + + Vec3d get_instance_scaling_factor() const { return m_instance_transformation.get_scaling_factor(); } + double get_instance_scaling_factor(Axis axis) const { return m_instance_transformation.get_scaling_factor(axis); } + + void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } + void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_instance_mirror() const { return m_instance_transformation.get_mirror(); } +#else + const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); } + + void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } + void set_instance_mirror(Axis axis, double mirror) { m_instance_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } + + const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } + void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_volume_offset() const { return m_volume_transformation.get_offset(); } +#else + const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); } + + void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } + void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_volume_rotation() const { return m_volume_transformation.get_rotation(); } +#else + const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); } + + void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } + void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } +#else + const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); } + + void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } + void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_volume_mirror() const { return m_volume_transformation.get_mirror(); } +#else + const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); } + + void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } + void set_volume_mirror(Axis axis, double mirror) { m_volume_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } + + double get_sla_shift_z() const { return m_sla_shift_z; } + void set_sla_shift_z(double z) { m_sla_shift_z = z; } + + void set_convex_hull(std::shared_ptr convex_hull) { m_convex_hull = std::move(convex_hull); } + void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared(convex_hull); } + void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared(std::move(convex_hull)); } + + int object_idx() const { return this->composite_id.object_id; } + int volume_idx() const { return this->composite_id.volume_id; } + int instance_idx() const { return this->composite_id.instance_id; } + + Transform3d world_matrix() const; + bool is_left_handed() const; + + const BoundingBoxf3& transformed_bounding_box() const; + // non-caching variant + BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; + // caching variant + const BoundingBoxf3& transformed_convex_hull_bounding_box() const; + // non-caching variant + BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const; + // caching variant + const BoundingBoxf3& transformed_non_sinking_bounding_box() const; + // convex hull + const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + bool empty() const { return this->model.is_empty(); } +#else + bool empty() const { return this->indexed_vertex_array.empty(); } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + void set_range(double low, double high); + + void render(); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } + void release_geometry() { this->indexed_vertex_array.release_geometry(); } +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + void set_bounding_boxes_as_dirty() { + m_transformed_bounding_box.reset(); + m_transformed_convex_hull_bounding_box.reset(); + m_transformed_non_sinking_bounding_box.reset(); + } + + bool is_sla_support() const; + bool is_sla_pad() const; + + bool is_sinking() const; + bool is_below_printbed() const; + void render_sinking_contours(); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + void render_non_manifold_edges(); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const { +#if ENABLE_LEGACY_OPENGL_REMOVAL + return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + + this->offsets.capacity() * sizeof(size_t); + } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const { return this->model.gpu_memory_used(); } +#else + //FIXME what to do wih m_convex_hull? + return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t); + } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } +}; + +typedef std::vector GLVolumePtrs; +typedef std::pair> GLVolumeWithIdAndZ; +typedef std::vector GLVolumeWithIdAndZList; + +class GLVolumeCollection +{ +public: + enum class ERenderType : unsigned char + { + Opaque, + Transparent, + All + }; + + struct PrintVolume + { + // see: Bed3D::EShapeType + int type{ 0 }; + // data contains: + // Rectangle: + // [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y + // Circle: + // [0] = center.x, [1] = center.y, [3] = radius + std::array data; + // [0] = min z, [1] = max z + std::array zs; + }; + +private: + PrintVolume m_print_volume; + + // z range for clipping in shaders + std::array m_z_range; + + // plane coeffs for clipping in shaders + std::array m_clipping_plane; + + struct Slope + { + // toggle for slope rendering + bool active{ false }; + float normal_z; + }; + + Slope m_slope; + bool m_show_sinking_contours{ false }; +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + bool m_show_non_manifold_edges{ true }; +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + +public: + GLVolumePtrs volumes; + + GLVolumeCollection() { set_default_slope_normal_z(); } + ~GLVolumeCollection() { clear(); } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + std::vector load_object( + const ModelObject* model_object, + int obj_idx, + const std::vector& instance_idxs); + + int load_object_volume( + const ModelObject* model_object, + int obj_idx, + int volume_idx, + int instance_idx); + + // Load SLA auxiliary GLVolumes (for support trees or pad). + void load_object_auxiliary( + const SLAPrintObject* print_object, + int obj_idx, + // pairs of + const std::vector>& instances, + SLAPrintObjectStep milestone, + // Timestamp of the last change of the milestone + size_t timestamp); + +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int load_wipe_tower_preview( + float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); +#else + int load_wipe_tower_preview( + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#else + std::vector load_object( + const ModelObject *model_object, + int obj_idx, + const std::vector &instance_idxs, + bool opengl_initialized); + + int load_object_volume( + const ModelObject *model_object, + int obj_idx, + int volume_idx, + int instance_idx, + bool opengl_initialized); + + // Load SLA auxiliary GLVolumes (for support trees or pad). + void load_object_auxiliary( + const SLAPrintObject *print_object, + int obj_idx, + // pairs of + const std::vector>& instances, + SLAPrintObjectStep milestone, + // Timestamp of the last change of the milestone + size_t timestamp, + bool opengl_initialized); + +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int load_wipe_tower_preview( + float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); +#else + int load_wipe_tower_preview( + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLVolume* new_toolpath_volume(const ColorRGBA& rgba); + GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba); +#else + GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); + GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // Render the volumes by OpenGL. +#if ENABLE_GL_SHADERS_ATTRIBUTES + void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, const Transform3d& projection_matrix, + std::function filter_func = std::function()) const; +#else + void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + // Finalize the initialization of the geometry & indices, + // upload the geometry and indices to OpenGL VBO objects + // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. + void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); } + // Release the geometry data assigned to the volumes. + // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. + void release_geometry() { for (auto *v : volumes) v->release_geometry(); } +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + // Clear the geometry + void clear() { for (auto *v : volumes) delete v; volumes.clear(); } + + bool empty() const { return volumes.empty(); } + void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); } + + void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } + + void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } + void set_clipping_plane(const std::array& coeffs) { m_clipping_plane = coeffs; } + + const std::array& get_z_range() const { return m_z_range; } + const std::array& get_clipping_plane() const { return m_clipping_plane; } + + bool is_slope_active() const { return m_slope.active; } + void set_slope_active(bool active) { m_slope.active = active; } + + float get_slope_normal_z() const { return m_slope.normal_z; } + void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; } + void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); } + void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; } +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + + // returns true if all the volumes are completely contained in the print volume + // returns the containment state in the given out_state, if non-null + bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const; + void reset_outside_state(); + + void update_colors_by_extruder(const DynamicPrintConfig* config); + + // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection + std::vector get_current_print_zs(bool active_only) const; + + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const; + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const; + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + // Return CPU, GPU and total memory log line. + std::string log_memory_info() const; + +private: + GLVolumeCollection(const GLVolumeCollection &other); + GLVolumeCollection& operator=(const GLVolumeCollection &); +}; + +GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func = nullptr); + +struct _3DScene +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry); + static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); +#else + static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GLVolume& volume); + static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GLVolume& volume); + static void extrusionentity_to_verts(const Polyline& polyline, float width, float height, float print_z, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume); + static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume); + static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +}; + +} + +#endif diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 353577944..216c2f472 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1532,7 +1532,11 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); const Geometry::Transformation inst_transform = v->get_instance_transformation(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inv_inst_transform = inst_transform.get_matrix_no_offset().inverse(); +#else const Transform3d inv_inst_transform = inst_transform.get_matrix(true).inverse(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d instance_offset = v->get_instance_offset(); for (size_t i = 0; i < input_files.size(); ++i) { @@ -1660,7 +1664,11 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) : // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - v->get_instance_offset(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->set_offset(v->get_instance_transformation().get_matrix_no_offset().inverse() * offset); +#else new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const wxString name = _L("Generic") + "-" + _(type_name); new_volume->name = into_u8(name); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 96429c924..c73f5eb4a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -354,7 +354,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const double min_z = get_volume_min_z(*volume); if (!is_world_coordinates()) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); +#else const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); @@ -381,7 +385,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const double min_z = selection.get_scaled_instance_bounding_box().min.z(); if (!is_world_coordinates()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); +#else const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 66b6dcf60..a52c85d67 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -1,437 +1,441 @@ -#include "GLGizmoFdmSupports.hpp" - -#include "libslic3r/Model.hpp" - -//#include "slic3r/GUI/3DScene.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/ImGuiWrapper.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/format.hpp" -#include "slic3r/Utils/UndoRedo.hpp" - - -#include - - -namespace Slic3r::GUI { - - - -void GLGizmoFdmSupports::on_shutdown() -{ - m_highlight_by_angle_threshold_deg = 0.f; - m_parent.use_slope(false); - m_parent.toggle_model_objects_visibility(true); -} - - - -std::string GLGizmoFdmSupports::on_get_name() const -{ - return _u8L("Paint-on supports"); -} - - - -bool GLGizmoFdmSupports::on_init() -{ - m_shortcut_key = WXK_CONTROL_L; - - m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size"] = _L("Brush size") + ": "; - m_desc["cursor_type"] = _L("Brush shape") + ": "; - m_desc["enforce_caption"] = _L("Left mouse button") + ": "; - m_desc["enforce"] = _L("Enforce supports"); - m_desc["block_caption"] = _L("Right mouse button") + ": "; - m_desc["block"] = _L("Block supports"); - m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; - m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all selection"); - m_desc["circle"] = _L("Circle"); - m_desc["sphere"] = _L("Sphere"); - m_desc["pointer"] = _L("Triangles"); - m_desc["highlight_by_angle"] = _L("Highlight overhang by angle"); - m_desc["enforce_button"] = _L("Enforce"); - m_desc["cancel"] = _L("Cancel"); - - m_desc["tool_type"] = _L("Tool type") + ": "; - m_desc["tool_brush"] = _L("Brush"); - m_desc["tool_smart_fill"] = _L("Smart fill"); - - m_desc["smart_fill_angle"] = _L("Smart fill angle"); - - m_desc["split_triangles"] = _L("Split triangles"); - m_desc["on_overhangs_only"] = _L("On overhangs only"); - - return true; -} - -void GLGizmoFdmSupports::render_painter_gizmo() -{ - const Selection& selection = m_parent.get_selection(); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - render_triangles(selection); - m_c->object_clipper()->render_cut(); - m_c->instances_hider()->render_cut(); - render_cursor(); - - glsafe(::glDisable(GL_BLEND)); -} - - - -void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit) -{ - if (! m_c->selection_info()->model_object()) - return; - - const float approx_height = m_imgui->scaled(23.f); - y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); - const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); - const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); - - const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); - - const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); - const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x; - const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x; - const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f); - const float minimal_slider_width = m_imgui->scaled(4.f); - - const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); - const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); - - const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); - const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); - - float caption_max = 0.f; - float total_text_max = 0.f; - for (const auto &t : std::array{"enforce", "block", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); - total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); - } - total_text_max += caption_max + m_imgui->scaled(1.f); - caption_max += m_imgui->scaled(1.f); - - const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); - const float slider_icon_width = m_imgui->get_slider_icon_size().x; - float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; - window_width = std::max(window_width, total_text_max); - window_width = std::max(window_width, button_width); - window_width = std::max(window_width, split_triangles_checkbox_width); - window_width = std::max(window_width, on_overhangs_only_checkbox_width); - window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); - window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); - window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); - - auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption); - ImGui::SameLine(caption_max); - m_imgui->text(text); - }; - - for (const auto &t : std::array{"enforce", "block", "remove"}) - draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); - - ImGui::Separator(); - - float position_before_text_y = ImGui::GetCursorPos().y; - ImGui::AlignTextToFramePadding(); - m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); - ImGui::AlignTextToFramePadding(); - float position_after_text_y = ImGui::GetCursorPos().y; - std::string format_str = std::string("%.f") + I18N::translate_utf8("°", - "Degree sign to use in the respective slider in FDM supports gizmo," - "placed after the number with no whitespace in between."); - ImGui::SameLine(sliders_left_width); - - float slider_height = m_imgui->get_slider_float_height(); - // Makes slider to be aligned to bottom of the multi-line text. - float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height); - ImGui::SetCursorPosY(slider_start_position_y); - - ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " - "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]); - if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) { - m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); - if (! m_parent.is_using_slope()) { - m_parent.use_slope(true); - m_parent.set_as_dirty(); - } - } - - // Restores the cursor position to be below the multi-line text. - ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y)); - - const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; - - m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); - ImGui::NewLine(); - ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); - if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { - select_facets_by_angle(m_highlight_by_angle_threshold_deg, false); - m_highlight_by_angle_threshold_deg = 0.f; - m_parent.use_slope(false); - } - ImGui::SameLine(window_width - buttons_width); - if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) { - m_highlight_by_angle_threshold_deg = 0.f; - m_parent.use_slope(false); - } - m_imgui->disabled_end(); - - - ImGui::Separator(); - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["tool_type"]); - - float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f; - ImGui::SameLine(tool_type_offset); - ImGui::PushItemWidth(tool_type_radio_brush); - if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) - m_tool_type = ToolType::BRUSH; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); - - ImGui::SameLine(tool_type_offset + tool_type_radio_brush); - ImGui::PushItemWidth(tool_type_radio_smart_fill); - if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) - m_tool_type = ToolType::SMART_FILL; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); - - m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); - if (ImGui::IsItemHovered()) - m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); - - ImGui::Separator(); - - if (m_tool_type == ToolType::BRUSH) { - m_imgui->text(m_desc.at("cursor_type")); - ImGui::NewLine(); - - float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; - ImGui::SameLine(cursor_type_offset); - ImGui::PushItemWidth(cursor_type_radio_sphere); - if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) - m_cursor_type = TriangleSelector::CursorType::SPHERE; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); - - ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); - ImGui::PushItemWidth(cursor_type_radio_circle); - - if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) - m_cursor_type = TriangleSelector::CursorType::CIRCLE; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); - - ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); - ImGui::PushItemWidth(cursor_type_radio_pointer); - - if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) - m_cursor_type = TriangleSelector::CursorType::POINTER; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); - - m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); - - m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width); - - m_imgui->disabled_end(); - } else { - assert(m_tool_type == ToolType::SMART_FILL); - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["smart_fill_angle"] + ":"); - - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) - for (auto &triangle_selector : m_triangle_selectors) { - triangle_selector->seed_fill_unselect_all_triangles(); - triangle_selector->request_update_render_data(); - } - } - - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) - m_c->object_clipper()->set_position(clp_dist, true); - - ImGui::Separator(); - if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); - ModelObject *mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume *mv : mo->volumes) - if (mv->is_model_part()) { - ++idx; - m_triangle_selectors[idx]->reset(); - m_triangle_selectors[idx]->request_update_render_data(); - } - - update_model_object(); - m_parent.set_as_dirty(); - } - - m_imgui->end(); -} - - - -void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) -{ - float threshold = (float(M_PI)/180.f)*threshold_deg; - const Selection& selection = m_parent.get_selection(); - const ModelObject* mo = m_c->selection_info()->model_object(); - const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; - - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++mesh_id; - - const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); - Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); - Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); - - float dot_limit = limit.dot(down); - - // Now calculate dot product of vert_direction and facets' normals. - int idx = 0; - const indexed_triangle_set &its = mv->mesh().its; - for (const stl_triangle_vertex_indices &face : its.indices) { - if (its_face_normal(its, face).dot(down) > dot_limit) { - m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER); - m_triangle_selectors.back()->request_update_render_data(); - } - ++ idx; - } - } - - Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle") - : _L("Add supports by angle")); - update_model_object(); - m_parent.set_as_dirty(); -} - - - -void GLGizmoFdmSupports::update_model_object() const -{ - bool updated = false; - ModelObject* mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - ++idx; - updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get()); - } - - if (updated) { - const ModelObjectPtrs& mos = wxGetApp().model().objects; - wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); - - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - } -} - - - -void GLGizmoFdmSupports::update_from_model_object() -{ - wxBusyCursor wait; - - const ModelObject* mo = m_c->selection_info()->model_object(); - m_triangle_selectors.clear(); - - int volume_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++volume_id; - - // This mesh does not account for the possible Z up SLA offset. - const TriangleMesh* mesh = &mv->mesh(); - - m_triangle_selectors.emplace_back(std::make_unique(*mesh)); - // Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize(). - m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data(), false); - m_triangle_selectors.back()->request_update_render_data(); - } -} - - - -PainterGizmoType GLGizmoFdmSupports::get_painter_type() const -{ - return PainterGizmoType::FDM_SUPPORTS; -} - -wxString GLGizmoFdmSupports::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const -{ - wxString action_name; - if (shift_down) - action_name = _L("Remove selection"); - else { - if (button_down == Button::Left) - action_name = _L("Add supports"); - else - action_name = _L("Block supports"); - } - return action_name; -} - -} // namespace Slic3r::GUI +#include "GLGizmoFdmSupports.hpp" + +#include "libslic3r/Model.hpp" + +//#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + + +#include + + +namespace Slic3r::GUI { + + + +void GLGizmoFdmSupports::on_shutdown() +{ + m_highlight_by_angle_threshold_deg = 0.f; + m_parent.use_slope(false); + m_parent.toggle_model_objects_visibility(true); +} + + + +std::string GLGizmoFdmSupports::on_get_name() const +{ + return _u8L("Paint-on supports"); +} + + + +bool GLGizmoFdmSupports::on_init() +{ + m_shortcut_key = WXK_CONTROL_L; + + m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; + m_desc["reset_direction"] = _L("Reset direction"); + m_desc["cursor_size"] = _L("Brush size") + ": "; + m_desc["cursor_type"] = _L("Brush shape") + ": "; + m_desc["enforce_caption"] = _L("Left mouse button") + ": "; + m_desc["enforce"] = _L("Enforce supports"); + m_desc["block_caption"] = _L("Right mouse button") + ": "; + m_desc["block"] = _L("Block supports"); + m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; + m_desc["remove"] = _L("Remove selection"); + m_desc["remove_all"] = _L("Remove all selection"); + m_desc["circle"] = _L("Circle"); + m_desc["sphere"] = _L("Sphere"); + m_desc["pointer"] = _L("Triangles"); + m_desc["highlight_by_angle"] = _L("Highlight overhang by angle"); + m_desc["enforce_button"] = _L("Enforce"); + m_desc["cancel"] = _L("Cancel"); + + m_desc["tool_type"] = _L("Tool type") + ": "; + m_desc["tool_brush"] = _L("Brush"); + m_desc["tool_smart_fill"] = _L("Smart fill"); + + m_desc["smart_fill_angle"] = _L("Smart fill angle"); + + m_desc["split_triangles"] = _L("Split triangles"); + m_desc["on_overhangs_only"] = _L("On overhangs only"); + + return true; +} + +void GLGizmoFdmSupports::render_painter_gizmo() +{ + const Selection& selection = m_parent.get_selection(); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + render_triangles(selection); + m_c->object_clipper()->render_cut(); + m_c->instances_hider()->render_cut(); + render_cursor(); + + glsafe(::glDisable(GL_BLEND)); +} + + + +void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit) +{ + if (! m_c->selection_info()->model_object()) + return; + + const float approx_height = m_imgui->scaled(23.f); + y = std::min(y, bottom_limit - approx_height); + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); + const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); + + const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); + + const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); + const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x; + const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x; + const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f); + const float minimal_slider_width = m_imgui->scaled(4.f); + + const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); + const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + + const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); + + float caption_max = 0.f; + float total_text_max = 0.f; + for (const auto &t : std::array{"enforce", "block", "remove"}) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); + } + total_text_max += caption_max + m_imgui->scaled(1.f); + caption_max += m_imgui->scaled(1.f); + + const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); + const float slider_icon_width = m_imgui->get_slider_icon_size().x; + float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; + window_width = std::max(window_width, total_text_max); + window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); + window_width = std::max(window_width, on_overhangs_only_checkbox_width); + window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); + window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); + window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); + + auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption); + ImGui::SameLine(caption_max); + m_imgui->text(text); + }; + + for (const auto &t : std::array{"enforce", "block", "remove"}) + draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); + + ImGui::Separator(); + + float position_before_text_y = ImGui::GetCursorPos().y; + ImGui::AlignTextToFramePadding(); + m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); + ImGui::AlignTextToFramePadding(); + float position_after_text_y = ImGui::GetCursorPos().y; + std::string format_str = std::string("%.f") + I18N::translate_utf8("°", + "Degree sign to use in the respective slider in FDM supports gizmo," + "placed after the number with no whitespace in between."); + ImGui::SameLine(sliders_left_width); + + float slider_height = m_imgui->get_slider_float_height(); + // Makes slider to be aligned to bottom of the multi-line text. + float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height); + ImGui::SetCursorPosY(slider_start_position_y); + + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " + "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]); + if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) { + m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); + if (! m_parent.is_using_slope()) { + m_parent.use_slope(true); + m_parent.set_as_dirty(); + } + } + + // Restores the cursor position to be below the multi-line text. + ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y)); + + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + + m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); + ImGui::NewLine(); + ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); + if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { + select_facets_by_angle(m_highlight_by_angle_threshold_deg, false); + m_highlight_by_angle_threshold_deg = 0.f; + m_parent.use_slope(false); + } + ImGui::SameLine(window_width - buttons_width); + if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) { + m_highlight_by_angle_threshold_deg = 0.f; + m_parent.use_slope(false); + } + m_imgui->disabled_end(); + + + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["tool_type"]); + + float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f; + ImGui::SameLine(tool_type_offset); + ImGui::PushItemWidth(tool_type_radio_brush); + if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) + m_tool_type = ToolType::BRUSH; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); + + ImGui::SameLine(tool_type_offset + tool_type_radio_brush); + ImGui::PushItemWidth(tool_type_radio_smart_fill); + if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) + m_tool_type = ToolType::SMART_FILL; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); + + m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); + + ImGui::Separator(); + + if (m_tool_type == ToolType::BRUSH) { + m_imgui->text(m_desc.at("cursor_type")); + ImGui::NewLine(); + + float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(cursor_type_offset); + ImGui::PushItemWidth(cursor_type_radio_sphere); + if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) + m_cursor_type = TriangleSelector::CursorType::SPHERE; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); + + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); + ImGui::PushItemWidth(cursor_type_radio_circle); + + if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) + m_cursor_type = TriangleSelector::CursorType::CIRCLE; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); + + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); + ImGui::PushItemWidth(cursor_type_radio_pointer); + + if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) + m_cursor_type = TriangleSelector::CursorType::POINTER; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); + + m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("cursor_size")); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); + + m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width); + + m_imgui->disabled_end(); + } else { + assert(m_tool_type == ToolType::SMART_FILL); + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["smart_fill_angle"] + ":"); + + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + } + + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("clipping_of_view")); + } + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position(-1., false); + }); + } + } + + auto clp_dist = float(m_c->object_clipper()->get_position()); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) + m_c->object_clipper()->set_position(clp_dist, true); + + ImGui::Separator(); + if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); + ModelObject *mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + m_triangle_selectors[idx]->request_update_render_data(); + } + + update_model_object(); + m_parent.set_as_dirty(); + } + + m_imgui->end(); +} + + + +void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) +{ + float threshold = (float(M_PI)/180.f)*threshold_deg; + const Selection& selection = m_parent.get_selection(); + const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++mesh_id; + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d trafo_matrix = mi->get_matrix_no_offset() * mv->get_matrix_no_offset(); +#else + const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); + Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); + + float dot_limit = limit.dot(down); + + // Now calculate dot product of vert_direction and facets' normals. + int idx = 0; + const indexed_triangle_set &its = mv->mesh().its; + for (const stl_triangle_vertex_indices &face : its.indices) { + if (its_face_normal(its, face).dot(down) > dot_limit) { + m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER); + m_triangle_selectors.back()->request_update_render_data(); + } + ++ idx; + } + } + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle") + : _L("Add supports by angle")); + update_model_object(); + m_parent.set_as_dirty(); +} + + + +void GLGizmoFdmSupports::update_model_object() const +{ + bool updated = false; + ModelObject* mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + ++idx; + updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get()); + } + + if (updated) { + const ModelObjectPtrs& mos = wxGetApp().model().objects; + wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); + + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } +} + + + +void GLGizmoFdmSupports::update_from_model_object() +{ + wxBusyCursor wait; + + const ModelObject* mo = m_c->selection_info()->model_object(); + m_triangle_selectors.clear(); + + int volume_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++volume_id; + + // This mesh does not account for the possible Z up SLA offset. + const TriangleMesh* mesh = &mv->mesh(); + + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + // Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize(). + m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data(), false); + m_triangle_selectors.back()->request_update_render_data(); + } +} + + + +PainterGizmoType GLGizmoFdmSupports::get_painter_type() const +{ + return PainterGizmoType::FDM_SUPPORTS; +} + +wxString GLGizmoFdmSupports::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const +{ + wxString action_name; + if (shift_down) + action_name = _L("Remove selection"); + else { + if (button_down == Button::Left) + action_name = _L("Add supports"); + else + action_name = _L("Block supports"); + } + return action_name; +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index aa291f623..b882570fc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,475 +1,479 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoFlatten.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#if ENABLE_LEGACY_OPENGL_REMOVAL -#include "slic3r/GUI/GUI_App.hpp" -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES -#include "slic3r/GUI/Plater.hpp" -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" - -#include "libslic3r/Geometry/ConvexHull.hpp" -#include "libslic3r/Model.hpp" - -#include - -#include - -namespace Slic3r { -namespace GUI { - -static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; -static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; - -GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{} - -bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) -{ - if (mouse_event.Moving()) { - // only for sure - m_mouse_left_down = false; - return false; - } - if (mouse_event.LeftDown()) { - if (m_hover_id != -1) { - m_mouse_left_down = true; - Selection &selection = m_parent.get_selection(); - if (selection.is_single_full_instance()) { - // Rotate the object so the normal points downward: - selection.flattening_rotate(m_planes[m_hover_id].normal); - m_parent.do_rotate(L("Gizmo-Place on Face")); - } - return true; - } - - // fix: prevent restart gizmo when reselect object - // take responsibility for left up - if (m_parent.get_first_hover_volume_idx() >= 0) m_mouse_left_down = true; - - } else if (mouse_event.LeftUp()) { - if (m_mouse_left_down) { - // responsible for mouse left up after selecting plane - m_mouse_left_down = false; - return true; - } - } else if (mouse_event.Leaving()) { - m_mouse_left_down = false; - } - return false; -} - -void GLGizmoFlatten::data_changed() -{ - const Selection & selection = m_parent.get_selection(); - const ModelObject *model_object = nullptr; - if (selection.is_single_full_instance() || - selection.is_from_single_object() ) { - model_object = selection.get_model()->objects[selection.get_object_idx()]; - } - set_flattening_data(model_object); -} - -bool GLGizmoFlatten::on_init() -{ - m_shortcut_key = WXK_CONTROL_F; - return true; -} - -void GLGizmoFlatten::on_set_state() -{ -} - -CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const -{ - return CommonGizmosDataID::SelectionInfo; -} - -std::string GLGizmoFlatten::on_get_name() const -{ - return _u8L("Place on face"); -} - -bool GLGizmoFlatten::on_is_activable() const -{ - // This is assumed in GLCanvas3D::do_rotate, do not change this - // without updating that function too. - return m_parent.get_selection().is_single_full_instance(); -} - -void GLGizmoFlatten::on_render() -{ - const Selection& selection = m_parent.get_selection(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glEnable(GL_BLEND)); - - if (selection.is_single_full_instance()) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); - glsafe(::glMultMatrixd(m.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (this->is_plane_update_necessary()) - update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); - m_planes[i].vbo.render(); -#else - glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data())); - if (m_planes[i].vbo.has_VBOs()) - m_planes[i].vbo.render(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLGizmoFlatten::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_BLEND)); - - if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); - glsafe(::glMultMatrixd(m.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (this->is_plane_update_necessary()) - update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_planes[i].vbo.set_color(picking_color_component(i)); -#else - glsafe(::glColor4fv(picking_color_component(i).data())); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_planes[i].vbo.render(); - } -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - - glsafe(::glEnable(GL_CULL_FACE)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) -{ - if (model_object != m_old_model_object) { - m_planes.clear(); - m_planes_valid = false; - } -} - -void GLGizmoFlatten::update_planes() -{ - const ModelObject* mo = m_c->selection_info()->model_object(); - TriangleMesh ch; - for (const ModelVolume* vol : mo->volumes) { - if (vol->type() != ModelVolumeType::MODEL_PART) - continue; - TriangleMesh vol_ch = vol->get_convex_hull(); - vol_ch.transform(vol->get_matrix()); - ch.merge(vol_ch); - } - ch = ch.convex_hull_3d(); - m_planes.clear(); - const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true); - - // Following constants are used for discarding too small polygons. - const float minimal_area = 5.f; // in square mm (world coordinates) - const float minimal_side = 1.f; // mm - - // Now we'll go through all the facets and append Points of facets sharing the same normal. - // This part is still performed in mesh coordinate system. - const int num_of_facets = ch.facets_count(); - const std::vector face_normals = its_face_normals(ch.its); - const std::vector face_neighbors = its_face_neighbors(ch.its); - std::vector facet_queue(num_of_facets, 0); - std::vector facet_visited(num_of_facets, false); - int facet_queue_cnt = 0; - const stl_normal* normal_ptr = nullptr; - int facet_idx = 0; - while (1) { - // Find next unvisited triangle: - for (; facet_idx < num_of_facets; ++ facet_idx) - if (!facet_visited[facet_idx]) { - facet_queue[facet_queue_cnt ++] = facet_idx; - facet_visited[facet_idx] = true; - normal_ptr = &face_normals[facet_idx]; - m_planes.emplace_back(); - break; - } - if (facet_idx == num_of_facets) - break; // Everything was visited already - - while (facet_queue_cnt > 0) { - int facet_idx = facet_queue[-- facet_queue_cnt]; - const stl_normal& this_normal = face_normals[facet_idx]; - if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { - const Vec3i face = ch.its.indices[facet_idx]; - for (int j=0; j<3; ++j) - m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast()); - - facet_visited[facet_idx] = true; - for (int j = 0; j < 3; ++ j) - if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx]) - facet_queue[facet_queue_cnt ++] = neighbor_idx; - } - } - m_planes.back().normal = normal_ptr->cast(); - - Pointf3s& verts = m_planes.back().vertices; - // Now we'll transform all the points into world coordinates, so that the areas, angles and distances - // make real sense. - verts = transform(verts, inst_matrix); - - // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): - if (verts.size() == 3 && - ((verts[0] - verts[1]).norm() < minimal_side - || (verts[0] - verts[2]).norm() < minimal_side - || (verts[1] - verts[2]).norm() < minimal_side)) - m_planes.pop_back(); - } - - // Let's prepare transformation of the normal vector from mesh to instance coordinates. - Geometry::Transformation t(inst_matrix); - Vec3d scaling = t.get_scaling_factor(); - t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); - - // Now we'll go through all the polygons, transform the points into xy plane to process them: - for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { - Pointf3s& polygon = m_planes[polygon_id].vertices; - const Vec3d& normal = m_planes[polygon_id].normal; - - // transform the normal according to the instance matrix: - Vec3d normal_transformed = t.get_matrix() * normal; - - // We are going to rotate about z and y to flatten the plane - Eigen::Quaterniond q; - Transform3d m = Transform3d::Identity(); - m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); - polygon = transform(polygon, m); - - // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since - // it works in fixed point representation, we will rescale the polygon to avoid overflows. - // And yes, it is a nasty thing to do. Whoever has time is free to refactor. - Vec3d bb_size = BoundingBoxf3(polygon).size(); - float sf = std::min(1./bb_size(0), 1./bb_size(1)); - Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); - polygon = transform(polygon, tr); - polygon = Slic3r::Geometry::convex_hull(polygon); - polygon = transform(polygon, tr.inverse()); - - // Calculate area of the polygons and discard ones that are too small - float& area = m_planes[polygon_id].area; - area = 0.f; - for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula - area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); - area = 0.5f * std::abs(area); - - bool discard = false; - if (area < minimal_area) - discard = true; - else { - // We also check the inner angles and discard polygons with angles smaller than the following threshold - const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); - - for (unsigned int i = 0; i < polygon.size(); ++i) { - const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; - const Vec3d& curr = polygon[i]; - const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; - - if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) { - discard = true; - break; - } - } - } - - if (discard) { - m_planes[polygon_id--] = std::move(m_planes.back()); - m_planes.pop_back(); - continue; - } - - // We will shrink the polygon a little bit so it does not touch the object edges: - Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); - centroid /= (double)polygon.size(); - for (auto& vertex : polygon) - vertex = 0.9f*vertex + 0.1f*centroid; - - // Polygon is now simple and convex, we'll round the corners to make them look nicer. - // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex - // towards their average (controlled by 'aggressivity'). This is repeated k times. - // In next iterations, the neighbours are not always taken at the middle (to increase the - // rounding effect at the corners, where we need it most). - const unsigned int k = 10; // number of iterations - const float aggressivity = 0.2f; // agressivity - const unsigned int N = polygon.size(); - std::vector> neighbours; - if (k != 0) { - Pointf3s points_out(2*k*N); // vector long enough to store the future vertices - for (unsigned int j=0; jvolumes) { - m_volumes_matrices.push_back(vol->get_matrix()); - m_volumes_types.push_back(vol->type()); - } - m_first_instance_scale = mo->instances.front()->get_scaling_factor(); - m_first_instance_mirror = mo->instances.front()->get_mirror(); - m_old_model_object = mo; - - // And finally create respective VBOs. The polygon is convex with - // the vertices in order, so triangulation is trivial. - for (auto& plane : m_planes) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3 }; - init_data.reserve_vertices(plane.vertices.size()); - init_data.reserve_indices(plane.vertices.size()); - // vertices + indices - for (size_t i = 0; i < plane.vertices.size(); ++i) { - init_data.add_vertex((Vec3f)plane.vertices[i].cast(), (Vec3f)plane.normal.cast()); - init_data.add_index((unsigned int)i); - } - plane.vbo.init_from(std::move(init_data)); -#else - plane.vbo.reserve(plane.vertices.size()); - for (const auto& vert : plane.vertices) - plane.vbo.push_geometry(vert, plane.normal); - for (size_t i=1; iselection_info()->model_object(); - if (m_state != On || ! mo || mo->instances.empty()) - return false; - - if (! m_planes_valid || mo != m_old_model_object - || mo->volumes.size() != m_volumes_matrices.size()) - return true; - - // We want to recalculate when the scale changes - some planes could (dis)appear. - if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) - || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) - return true; - - for (unsigned int i=0; i < mo->volumes.size(); ++i) - if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) - || mo->volumes[i]->type() != m_volumes_types[i]) - return true; - - return false; -} - -} // namespace GUI -} // namespace Slic3r +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoFlatten.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#if ENABLE_LEGACY_OPENGL_REMOVAL +#include "slic3r/GUI/GUI_App.hpp" +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES +#include "slic3r/GUI/Plater.hpp" +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" + +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Model.hpp" + +#include + +#include + +namespace Slic3r { +namespace GUI { + +static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; +static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; + +GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{} + +bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) +{ + if (mouse_event.Moving()) { + // only for sure + m_mouse_left_down = false; + return false; + } + if (mouse_event.LeftDown()) { + if (m_hover_id != -1) { + m_mouse_left_down = true; + Selection &selection = m_parent.get_selection(); + if (selection.is_single_full_instance()) { + // Rotate the object so the normal points downward: + selection.flattening_rotate(m_planes[m_hover_id].normal); + m_parent.do_rotate(L("Gizmo-Place on Face")); + } + return true; + } + + // fix: prevent restart gizmo when reselect object + // take responsibility for left up + if (m_parent.get_first_hover_volume_idx() >= 0) m_mouse_left_down = true; + + } else if (mouse_event.LeftUp()) { + if (m_mouse_left_down) { + // responsible for mouse left up after selecting plane + m_mouse_left_down = false; + return true; + } + } else if (mouse_event.Leaving()) { + m_mouse_left_down = false; + } + return false; +} + +void GLGizmoFlatten::data_changed() +{ + const Selection & selection = m_parent.get_selection(); + const ModelObject *model_object = nullptr; + if (selection.is_single_full_instance() || + selection.is_from_single_object() ) { + model_object = selection.get_model()->objects[selection.get_object_idx()]; + } + set_flattening_data(model_object); +} + +bool GLGizmoFlatten::on_init() +{ + m_shortcut_key = WXK_CONTROL_F; + return true; +} + +void GLGizmoFlatten::on_set_state() +{ +} + +CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const +{ + return CommonGizmosDataID::SelectionInfo; +} + +std::string GLGizmoFlatten::on_get_name() const +{ + return _u8L("Place on face"); +} + +bool GLGizmoFlatten::on_is_activable() const +{ + // This is assumed in GLCanvas3D::do_rotate, do not change this + // without updating that function too. + return m_parent.get_selection().is_single_full_instance(); +} + +void GLGizmoFlatten::on_render() +{ + const Selection& selection = m_parent.get_selection(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + + if (selection.is_single_full_instance()) { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); + glsafe(::glMultMatrixd(m.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (this->is_plane_update_necessary()) + update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); + m_planes[i].vbo.render(); +#else + glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data())); + if (m_planes[i].vbo.has_VBOs()) + m_planes[i].vbo.render(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLGizmoFlatten::on_render_for_picking() +{ + const Selection& selection = m_parent.get_selection(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_BLEND)); + + if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); + glsafe(::glMultMatrixd(m.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (this->is_plane_update_necessary()) + update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_planes[i].vbo.set_color(picking_color_component(i)); +#else + glsafe(::glColor4fv(picking_color_component(i).data())); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + m_planes[i].vbo.render(); + } +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + + glsafe(::glEnable(GL_CULL_FACE)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) +{ + if (model_object != m_old_model_object) { + m_planes.clear(); + m_planes_valid = false; + } +} + +void GLGizmoFlatten::update_planes() +{ + const ModelObject* mo = m_c->selection_info()->model_object(); + TriangleMesh ch; + for (const ModelVolume* vol : mo->volumes) { + if (vol->type() != ModelVolumeType::MODEL_PART) + continue; + TriangleMesh vol_ch = vol->get_convex_hull(); + vol_ch.transform(vol->get_matrix()); + ch.merge(vol_ch); + } + ch = ch.convex_hull_3d(); + m_planes.clear(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inst_matrix = mo->instances.front()->get_matrix_no_offset(); +#else + const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + // Following constants are used for discarding too small polygons. + const float minimal_area = 5.f; // in square mm (world coordinates) + const float minimal_side = 1.f; // mm + + // Now we'll go through all the facets and append Points of facets sharing the same normal. + // This part is still performed in mesh coordinate system. + const int num_of_facets = ch.facets_count(); + const std::vector face_normals = its_face_normals(ch.its); + const std::vector face_neighbors = its_face_neighbors(ch.its); + std::vector facet_queue(num_of_facets, 0); + std::vector facet_visited(num_of_facets, false); + int facet_queue_cnt = 0; + const stl_normal* normal_ptr = nullptr; + int facet_idx = 0; + while (1) { + // Find next unvisited triangle: + for (; facet_idx < num_of_facets; ++ facet_idx) + if (!facet_visited[facet_idx]) { + facet_queue[facet_queue_cnt ++] = facet_idx; + facet_visited[facet_idx] = true; + normal_ptr = &face_normals[facet_idx]; + m_planes.emplace_back(); + break; + } + if (facet_idx == num_of_facets) + break; // Everything was visited already + + while (facet_queue_cnt > 0) { + int facet_idx = facet_queue[-- facet_queue_cnt]; + const stl_normal& this_normal = face_normals[facet_idx]; + if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { + const Vec3i face = ch.its.indices[facet_idx]; + for (int j=0; j<3; ++j) + m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast()); + + facet_visited[facet_idx] = true; + for (int j = 0; j < 3; ++ j) + if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx]) + facet_queue[facet_queue_cnt ++] = neighbor_idx; + } + } + m_planes.back().normal = normal_ptr->cast(); + + Pointf3s& verts = m_planes.back().vertices; + // Now we'll transform all the points into world coordinates, so that the areas, angles and distances + // make real sense. + verts = transform(verts, inst_matrix); + + // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): + if (verts.size() == 3 && + ((verts[0] - verts[1]).norm() < minimal_side + || (verts[0] - verts[2]).norm() < minimal_side + || (verts[1] - verts[2]).norm() < minimal_side)) + m_planes.pop_back(); + } + + // Let's prepare transformation of the normal vector from mesh to instance coordinates. + Geometry::Transformation t(inst_matrix); + Vec3d scaling = t.get_scaling_factor(); + t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); + + // Now we'll go through all the polygons, transform the points into xy plane to process them: + for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { + Pointf3s& polygon = m_planes[polygon_id].vertices; + const Vec3d& normal = m_planes[polygon_id].normal; + + // transform the normal according to the instance matrix: + Vec3d normal_transformed = t.get_matrix() * normal; + + // We are going to rotate about z and y to flatten the plane + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); + polygon = transform(polygon, m); + + // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since + // it works in fixed point representation, we will rescale the polygon to avoid overflows. + // And yes, it is a nasty thing to do. Whoever has time is free to refactor. + Vec3d bb_size = BoundingBoxf3(polygon).size(); + float sf = std::min(1./bb_size(0), 1./bb_size(1)); + Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); + polygon = transform(polygon, tr); + polygon = Slic3r::Geometry::convex_hull(polygon); + polygon = transform(polygon, tr.inverse()); + + // Calculate area of the polygons and discard ones that are too small + float& area = m_planes[polygon_id].area; + area = 0.f; + for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula + area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); + area = 0.5f * std::abs(area); + + bool discard = false; + if (area < minimal_area) + discard = true; + else { + // We also check the inner angles and discard polygons with angles smaller than the following threshold + const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); + + for (unsigned int i = 0; i < polygon.size(); ++i) { + const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; + const Vec3d& curr = polygon[i]; + const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; + + if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) { + discard = true; + break; + } + } + } + + if (discard) { + m_planes[polygon_id--] = std::move(m_planes.back()); + m_planes.pop_back(); + continue; + } + + // We will shrink the polygon a little bit so it does not touch the object edges: + Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); + centroid /= (double)polygon.size(); + for (auto& vertex : polygon) + vertex = 0.9f*vertex + 0.1f*centroid; + + // Polygon is now simple and convex, we'll round the corners to make them look nicer. + // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex + // towards their average (controlled by 'aggressivity'). This is repeated k times. + // In next iterations, the neighbours are not always taken at the middle (to increase the + // rounding effect at the corners, where we need it most). + const unsigned int k = 10; // number of iterations + const float aggressivity = 0.2f; // agressivity + const unsigned int N = polygon.size(); + std::vector> neighbours; + if (k != 0) { + Pointf3s points_out(2*k*N); // vector long enough to store the future vertices + for (unsigned int j=0; jvolumes) { + m_volumes_matrices.push_back(vol->get_matrix()); + m_volumes_types.push_back(vol->type()); + } + m_first_instance_scale = mo->instances.front()->get_scaling_factor(); + m_first_instance_mirror = mo->instances.front()->get_mirror(); + m_old_model_object = mo; + + // And finally create respective VBOs. The polygon is convex with + // the vertices in order, so triangulation is trivial. + for (auto& plane : m_planes) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(plane.vertices.size()); + init_data.reserve_indices(plane.vertices.size()); + // vertices + indices + for (size_t i = 0; i < plane.vertices.size(); ++i) { + init_data.add_vertex((Vec3f)plane.vertices[i].cast(), (Vec3f)plane.normal.cast()); + init_data.add_index((unsigned int)i); + } + plane.vbo.init_from(std::move(init_data)); +#else + plane.vbo.reserve(plane.vertices.size()); + for (const auto& vert : plane.vertices) + plane.vbo.push_geometry(vert, plane.normal); + for (size_t i=1; iselection_info()->model_object(); + if (m_state != On || ! mo || mo->instances.empty()) + return false; + + if (! m_planes_valid || mo != m_old_model_object + || mo->volumes.size() != m_volumes_matrices.size()) + return true; + + // We want to recalculate when the scale changes - some planes could (dis)appear. + if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) + || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) + return true; + + for (unsigned int i=0; i < mo->volumes.size(); ++i) + if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) + || mo->volumes[i]->type() != m_volumes_types[i]) + return true; + + return false; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 88b319f25..a60da3591 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -1,988 +1,992 @@ -#include "GLGizmoHollow.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" - -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/GUI_ObjectSettings.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "libslic3r/PresetBundle.hpp" - -#include "libslic3r/Model.hpp" - - -namespace Slic3r { -namespace GUI { - -GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{ -} - - -bool GLGizmoHollow::on_init() -{ - m_shortcut_key = WXK_CONTROL_H; - m_desc["enable"] = _(L("Hollow this object")); - m_desc["preview"] = _(L("Preview hollowed and drilled model")); - m_desc["offset"] = _(L("Offset")) + ": "; - m_desc["quality"] = _(L("Quality")) + ": "; - m_desc["closing_distance"] = _(L("Closing distance")) + ": "; - m_desc["hole_diameter"] = _(L("Hole diameter")) + ": "; - m_desc["hole_depth"] = _(L("Hole depth")) + ": "; - m_desc["remove_selected"] = _(L("Remove selected holes")); - m_desc["remove_all"] = _(L("Remove all holes")); - m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; - m_desc["reset_direction"] = _(L("Reset direction")); - m_desc["show_supports"] = _(L("Show supports")); - - return true; -} - -void GLGizmoHollow::data_changed() -{ - if (! m_c->selection_info()) - return; - - const ModelObject* mo = m_c->selection_info()->model_object(); - if (m_state == On && mo) { - if (m_old_mo_id != mo->id()) { - reload_cache(); - m_old_mo_id = mo->id(); - } - if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) - m_holes_in_drilled_mesh = mo->sla_drain_holes; - } -} - - - -void GLGizmoHollow::on_render() -{ - if (!m_cylinder.is_initialized()) - m_cylinder.init_from(its_make_cylinder(1.0, 1.0)); - - const Selection& selection = m_parent.get_selection(); - const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); - - // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off - if (m_state == On - && (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()] - || sel_info->get_active_instance() != selection.get_instance_idx())) { - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); - return; - } - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - if (selection.is_from_single_instance()) - render_points(selection, false); - - m_selection_rectangle.render(m_parent); - m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); - - glsafe(::glDisable(GL_BLEND)); -} - - -void GLGizmoHollow::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); -//#if ENABLE_RENDER_PICKING_PASS -// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); -//#endif - - glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} - -void GLGizmoHollow::render_points(const Selection& selection, bool picking) -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); - ScopeGuard guard([shader]() { shader->stop_using(); }); -#else - GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); - if (shader) - shader->start_using(); - ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); - const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d& view_matrix = camera.get_view_matrix(); - const Transform3d& projection_matrix = camera.get_projection_matrix(); - - shader->set_uniform("projection_matrix", projection_matrix); -#else - const Transform3d& instance_scaling_matrix_inverse = trafo.get_matrix(true, true, false, true).inverse(); - const Transform3d& instance_matrix = trafo.get_matrix(); - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); - glsafe(::glMultMatrixd(instance_matrix.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - ColorRGBA render_color; - const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - const size_t cache_size = drain_holes.size(); - - for (size_t i = 0; i < cache_size; ++i) { - const sla::DrainHole& drain_hole = drain_holes[i]; - const bool point_selected = m_selected[i]; - - if (is_mesh_point_clipped(drain_hole.pos.cast())) - continue; - - // First decide about the color of the point. - if (picking) - render_color = picking_color_component(i); - else { - if (size_t(m_hover_id) == i) - render_color = ColorRGBA::CYAN(); - else if (m_c->hollowed_mesh() && - i < m_c->hollowed_mesh()->get_drainholes().size() && - m_c->hollowed_mesh()->get_drainholes()[i].failed) { - render_color = { 1.0f, 0.0f, 0.0f, 0.5f }; - } - else // neither hover nor picking - render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_cylinder.set_color(render_color); -#else - const_cast(&m_cylinder)->set_color(-1, render_color); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (vol->is_left_handed()) - glFrontFace(GL_CW); - - // Matrices set, we can render the point mark now. - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); - const Eigen::AngleAxisd aa(q); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * - Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); - glsafe(::glTranslated(0., 0., -drain_hole.height)); - glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_cylinder.render(); - - if (vol->is_left_handed()) - glFrontFace(GL_CCW); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - -bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const -{ - if (m_c->object_clipper()->get_position() == 0.) - return false; - - auto sel_info = m_c->selection_info(); - int active_inst = m_c->selection_info()->get_active_instance(); - const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; - const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); - - Vec3d transformed_point = trafo * point; - transformed_point(2) += sel_info->get_sla_shift(); - return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); -} - - - -// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal -// Return false if no intersection was found, true otherwise. -bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) -{ - if (! m_c->raycaster()->raycaster()) - return false; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - - double clp_dist = m_c->object_clipper()->get_position(); - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - - // The raycaster query - Vec3f hit; - Vec3f normal; - if (m_c->raycaster()->raycaster()->unproject_on_mesh( - mouse_pos, - trafo.get_matrix(), - camera, - hit, - normal, - clp_dist != 0. ? clp : nullptr)) - { - if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) { - // in this case the raycaster sees the hollowed and drilled mesh. - // if the point lies on the surface created by the hole, we want - // to ignore it. - for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) { - sla::DrainHole outer(hole); - outer.radius *= 1.001f; - outer.height *= 1.001f; - if (outer.is_inside(hit)) - return false; - } - } - - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; - } - else - return false; -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) -{ - ModelObject* mo = m_c->selection_info()->model_object(); - int active_inst = m_c->selection_info()->get_active_instance(); - - - // left down with shift - show the selection rectangle: - if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { - if (m_hover_id == -1) { - if (shift_down || alt_down) { - m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); - } - } - else { - if (m_selected[m_hover_id]) - unselect_point(m_hover_id); - else { - if (!alt_down) - select_point(m_hover_id); - } - } - - return true; - } - - // left down without selection rectangle - place point on the mesh: - if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { - // If any point is in hover state, this should initiate its move - return control back to GLCanvas: - if (m_hover_id != -1) - return false; - - // If there is some selection, don't add new point and deselect everything instead. - if (m_selection_empty) { - std::pair pos_and_normal; - if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole"))); - - mo->sla_drain_holes.emplace_back(pos_and_normal.first, - -pos_and_normal.second, m_new_hole_radius, m_new_hole_height); - m_selected.push_back(false); - assert(m_selected.size() == mo->sla_drain_holes.size()); - m_parent.set_as_dirty(); - m_wait_for_up_event = true; - } - else - return false; - } - else - select_point(NoPoints); - - return true; - } - - // left up with selection rectangle - select points inside the rectangle: - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { - // Is this a selection or deselection rectangle? - GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); - - // First collect positions of all the points in world coordinates. - Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - std::vector points; - for (unsigned int i=0; isla_drain_holes.size(); ++i) - points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast()); - - // Now ask the rectangle which of the points are inside. - std::vector points_inside; - std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); - for (size_t idx : points_idxs) - points_inside.push_back(points[idx].cast()); - - // Only select/deselect points that are actually visible - for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( - trafo, wxGetApp().plater()->get_camera(), points_inside, - m_c->object_clipper()->get_clipping_plane())) - { - if (rectangle_status == GLSelectionRectangle::EState::Deselect) - unselect_point(points_idxs[idx]); - else - select_point(points_idxs[idx]); - } - return true; - } - - // left up with no selection rectangle - if (action == SLAGizmoEventType::LeftUp) { - if (m_wait_for_up_event) { - m_wait_for_up_event = false; - return true; - } - } - - // dragging the selection rectangle: - if (action == SLAGizmoEventType::Dragging) { - if (m_wait_for_up_event) - return true; // point has been placed and the button not released yet - // this prevents GLCanvas from starting scene rotation - - if (m_selection_rectangle.is_dragging()) { - m_selection_rectangle.dragging(mouse_position); - return true; - } - - return false; - } - - if (action == SLAGizmoEventType::Delete) { - // delete key pressed - delete_selected_points(); - return true; - } - - if (action == SLAGizmoEventType::RightDown) { - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - delete_selected_points(); - return true; - } - return false; - } - - if (action == SLAGizmoEventType::SelectAll) { - select_point(AllPoints); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelUp && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelDown && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::max(0., pos - 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); - return true; - } - - return false; -} - -void GLGizmoHollow::delete_selected_points() -{ - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole"))); - sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - - for (unsigned int idx=0; idx wx/types.h - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); - Vec2d mouse_pos = mouse_coord.cast(); - - static bool pending_right_up = false; - if (mouse_event.LeftDown()) { - bool control_down = mouse_event.CmdDown(); - bool grabber_contains_mouse = (get_hover_id() != -1); - if ((!control_down || grabber_contains_mouse) && - gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) - // the gizmo got the event and took some action, there is no need - // to do anything more - return true; - } else if (mouse_event.Dragging()) { - if (m_parent.get_move_volume_id() != -1) - // don't allow dragging objects with the Sla gizmo on - return true; - - bool control_down = mouse_event.CmdDown(); - if (control_down) { - // CTRL has been pressed while already dragging -> stop current action - if (mouse_event.LeftIsDown()) - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); - else if (mouse_event.RightIsDown()) { - pending_right_up = false; - } - } else if(gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { - // the gizmo got the event and took some action, no need to do - // anything more here - m_parent.set_as_dirty(); - return true; - } - } else if (mouse_event.LeftUp()) { - if (!m_parent.is_mouse_dragging()) { - bool control_down = mouse_event.CmdDown(); - // in case gizmo is selected, we just pass the LeftUp event - // and stop processing - neither object moving or selecting is - // suppressed in that case - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); - return true; - } - } else if (mouse_event.RightDown()) { - if (m_parent.get_selection().get_object_idx() != -1 && - gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { - // we need to set the following right up as processed to avoid showing - // the context menu if the user release the mouse over the object - pending_right_up = true; - // event was taken care of by the SlaSupports gizmo - return true; - } - } else if (mouse_event.RightUp()) { - if (pending_right_up) { - pending_right_up = false; - return true; - } - } - return false; -} - -void GLGizmoHollow::hollow_mesh(bool postpone_error_messages) -{ - wxGetApp().CallAfter([this, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_hollowing( - *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - - -std::vector> -GLGizmoHollow::get_config_options(const std::vector& keys) const -{ - std::vector> out; - const ModelObject* mo = m_c->selection_info()->model_object(); - - if (! mo) - return out; - - const DynamicPrintConfig& object_cfg = mo->config.get(); - const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - std::unique_ptr default_cfg = nullptr; - - for (const std::string& key : keys) { - if (object_cfg.has(key)) - out.emplace_back(object_cfg.option(key), &object_cfg.def()->options.at(key)); // at() needed for const map - else - if (print_cfg.has(key)) - out.emplace_back(print_cfg.option(key), &print_cfg.def()->options.at(key)); - else { // we must get it from defaults - if (default_cfg == nullptr) - default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); - out.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key)); - } - } - - return out; -} - - -void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit) -{ - ModelObject* mo = m_c->selection_info()->model_object(); - if (! mo) - return; - - bool first_run = true; // This is a hack to redraw the button when all points are removed, - // so it is not delayed until the background process finishes. - - ConfigOptionMode current_mode = wxGetApp().get_mode(); - - std::vector opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}; - auto opts = get_config_options(opts_keys); - auto* offset_cfg = static_cast(opts[0].first); - float offset = offset_cfg->value; - double offset_min = opts[0].second->min; - double offset_max = opts[0].second->max; - - auto* quality_cfg = static_cast(opts[1].first); - float quality = quality_cfg->value; - double quality_min = opts[1].second->min; - double quality_max = opts[1].second->max; - ConfigOptionMode quality_mode = opts[1].second->mode; - - auto* closing_d_cfg = static_cast(opts[2].first); - float closing_d = closing_d_cfg->value; - double closing_d_min = opts[2].second->min; - double closing_d_max = opts[2].second->max; - ConfigOptionMode closing_d_mode = opts[2].second->mode; - - m_desc["offset"] = _(opts[0].second->label) + ":"; - m_desc["quality"] = _(opts[1].second->label) + ":"; - m_desc["closing_distance"] = _(opts[2].second->label) + ":"; - - -RENDER_AGAIN: - const float approx_height = m_imgui->scaled(20.0f); - y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(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 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, button_preview_width); - - if (m_imgui->button(m_desc["preview"])) - hollow_mesh(); - - bool config_changed = false; - - ImGui::Separator(); - - { - auto opts = get_config_options({"hollowing_enable"}); - m_enable_hollowing = static_cast(opts[0].first)->value; - if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) { - mo->config.set("hollowing_enable", m_enable_hollowing); - wxGetApp().obj_list()->update_and_show_object_settings_item(); - config_changed = true; - } - } - - m_imgui->disabled_begin(! m_enable_hollowing); - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("offset")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - ImGui::PushItemWidth(window_width - settings_sliders_left); - m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip)); - - bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider - bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider - bool slider_released =m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider - - if (current_mode >= quality_mode) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("quality")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip)); - - slider_clicked |= m_imgui->get_last_slider_status().clicked; - slider_edited |= m_imgui->get_last_slider_status().edited; - slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; - } - - if (current_mode >= closing_d_mode) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("closing_distance")); - 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", 1.0f, true, _L(opts[2].second->tooltip)); - - slider_clicked |= m_imgui->get_last_slider_status().clicked; - slider_edited |= m_imgui->get_last_slider_status().edited; - slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; - } - - if (slider_clicked) { - m_offset_stash = offset; - m_quality_stash = quality; - m_closing_d_stash = closing_d; - } - if (slider_edited || slider_released) { - if (slider_released) { - mo->config.set("hollowing_min_thickness", m_offset_stash); - mo->config.set("hollowing_quality", m_quality_stash); - mo->config.set("hollowing_closing_distance", m_closing_d_stash); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change"))); - } - mo->config.set("hollowing_min_thickness", offset); - mo->config.set("hollowing_quality", quality); - mo->config.set("hollowing_closing_distance", closing_d); - if (slider_released) { - wxGetApp().obj_list()->update_and_show_object_settings_item(); - config_changed = true; - } - } - - m_imgui->disabled_end(); - - bool force_refresh = false; - bool remove_selected = false; - bool remove_all = false; - - ImGui::Separator(); - - float diameter_upper_cap = 60.; - if (m_new_hole_radius * 2.f > diameter_upper_cap) - m_new_hole_radius = diameter_upper_cap / 2.f; - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("hole_diameter")); - ImGui::SameLine(diameter_slider_left, 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("##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. - diam = std::clamp(diam, 0.1f, diameter_upper_cap); - m_new_hole_radius = diam / 2.f; - bool clicked = m_imgui->get_last_slider_status().clicked; - bool edited = m_imgui->get_last_slider_status().edited; - bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit; - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["hole_depth"]); - 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); - - clicked |= m_imgui->get_last_slider_status().clicked; - edited |= m_imgui->get_last_slider_status().edited; - deactivated |= m_imgui->get_last_slider_status().deactivated_after_edit;; - - // Following is a nasty way to: - // - save the initial value of the slider before one starts messing with it - // - 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 - if (! m_selection_empty) { - if (clicked) { - m_holes_stash = mo->sla_drain_holes; - } - if (edited) { - for (size_t idx=0; idxsla_drain_holes[idx].radius = m_new_hole_radius; - mo->sla_drain_holes[idx].height = m_new_hole_height; - } - } - if (deactivated) { - // momentarily restore the old value to take snapshot - sla::DrainHoles new_holes = mo->sla_drain_holes; - mo->sla_drain_holes = m_holes_stash; - float backup_rad = m_new_hole_radius; - float backup_hei = m_new_hole_height; - for (size_t i=0; isla_drain_holes = new_holes; - } - } - - m_imgui->disabled_begin(m_selection_empty); - remove_selected = m_imgui->button(m_desc.at("remove_selected")); - m_imgui->disabled_end(); - - m_imgui->disabled_begin(mo->sla_drain_holes.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); - m_imgui->disabled_end(); - - // Following is rendered in both editing and non-editing mode: - // m_imgui->text(""); - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - 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", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - - // make sure supports are shown/hidden as appropriate - bool show_sups = m_c->instances_hider()->are_supports_shown(); - if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) { - m_c->instances_hider()->show_supports(show_sups); - force_refresh = true; - } - - m_imgui->end(); - - - if (remove_selected || remove_all) { - force_refresh = false; - m_parent.set_as_dirty(); - - if (remove_all) { - select_point(AllPoints); - delete_selected_points(); - } - if (remove_selected) - delete_selected_points(); - - if (first_run) { - first_run = false; - goto RENDER_AGAIN; - } - } - - if (force_refresh) - m_parent.set_as_dirty(); - - if (config_changed) - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); -} - -bool GLGizmoHollow::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) - return false; - - // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. - const Selection::IndicesList& list = selection.get_volume_idxs(); - for (const auto& idx : list) - if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) - return false; - - return true; -} - -bool GLGizmoHollow::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); -} - -std::string GLGizmoHollow::on_get_name() const -{ - return _u8L("Hollow and drill"); -} - - -CommonGizmosDataID GLGizmoHollow::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); -} - - -void GLGizmoHollow::on_set_state() -{ - if (m_state == m_old_state) - return; - - if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); - m_old_state = m_state; -} - - - -void GLGizmoHollow::on_start_dragging() -{ - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos; - } - else - m_hole_before_drag = Vec3f::Zero(); -} - - -void GLGizmoHollow::on_stop_dragging() -{ - sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - if (m_hover_id != -1) { - Vec3f backup = drain_holes[m_hover_id].pos; - - if (m_hole_before_drag != Vec3f::Zero() // some point was touched - && backup != m_hole_before_drag) // and it was moved, not just selected - { - drain_holes[m_hover_id].pos = m_hole_before_drag; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole"))); - drain_holes[m_hover_id].pos = backup; - } - } - m_hole_before_drag = Vec3f::Zero(); -} - - -void GLGizmoHollow::on_dragging(const UpdateData &data) -{ - assert(m_hover_id != -1); - std::pair pos_and_normal; - if (!unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) - return; - sla::DrainHoles &drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - drain_holes[m_hover_id].pos = pos_and_normal.first; - drain_holes[m_hover_id].normal = -pos_and_normal.second; -} - - -void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar) -{ - ar(m_new_hole_radius, - m_new_hole_height, - m_selected, - m_selection_empty - ); -} - - - -void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const -{ - ar(m_new_hole_radius, - m_new_hole_height, - m_selected, - m_selection_empty - ); -} - - - -void GLGizmoHollow::select_point(int i) -{ - const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - - if (i == AllPoints || i == NoPoints) { - m_selected.assign(m_selected.size(), i == AllPoints); - m_selection_empty = (i == NoPoints); - - if (i == AllPoints) { - m_new_hole_radius = drain_holes[0].radius; - m_new_hole_height = drain_holes[0].height; - } - } - else { - while (size_t(i) >= m_selected.size()) - m_selected.push_back(false); - m_selected[i] = true; - m_selection_empty = false; - m_new_hole_radius = drain_holes[i].radius; - m_new_hole_height = drain_holes[i].height; - } -} - - -void GLGizmoHollow::unselect_point(int i) -{ - m_selected[i] = false; - m_selection_empty = true; - for (const bool sel : m_selected) { - if (sel) { - m_selection_empty = false; - break; - } - } -} - -void GLGizmoHollow::reload_cache() -{ - m_selected.clear(); - m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false); -} - - -void GLGizmoHollow::on_set_hover_id() -{ - if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id) - m_hover_id = -1; -} - - - - -} // namespace GUI -} // namespace Slic3r +#include "GLGizmoHollow.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" + +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI_ObjectSettings.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "libslic3r/PresetBundle.hpp" + +#include "libslic3r/Model.hpp" + + +namespace Slic3r { +namespace GUI { + +GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{ +} + + +bool GLGizmoHollow::on_init() +{ + m_shortcut_key = WXK_CONTROL_H; + m_desc["enable"] = _(L("Hollow this object")); + m_desc["preview"] = _(L("Preview hollowed and drilled model")); + m_desc["offset"] = _(L("Offset")) + ": "; + m_desc["quality"] = _(L("Quality")) + ": "; + m_desc["closing_distance"] = _(L("Closing distance")) + ": "; + m_desc["hole_diameter"] = _(L("Hole diameter")) + ": "; + m_desc["hole_depth"] = _(L("Hole depth")) + ": "; + m_desc["remove_selected"] = _(L("Remove selected holes")); + m_desc["remove_all"] = _(L("Remove all holes")); + m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; + m_desc["reset_direction"] = _(L("Reset direction")); + m_desc["show_supports"] = _(L("Show supports")); + + return true; +} + +void GLGizmoHollow::data_changed() +{ + if (! m_c->selection_info()) + return; + + const ModelObject* mo = m_c->selection_info()->model_object(); + if (m_state == On && mo) { + if (m_old_mo_id != mo->id()) { + reload_cache(); + m_old_mo_id = mo->id(); + } + if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) + m_holes_in_drilled_mesh = mo->sla_drain_holes; + } +} + + + +void GLGizmoHollow::on_render() +{ + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0)); + + const Selection& selection = m_parent.get_selection(); + const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); + + // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off + if (m_state == On + && (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()] + || sel_info->get_active_instance() != selection.get_instance_idx())) { + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); + return; + } + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + if (selection.is_from_single_instance()) + render_points(selection, false); + + m_selection_rectangle.render(m_parent); + m_c->object_clipper()->render_cut(); + m_c->supports_clipper()->render_cut(); + + glsafe(::glDisable(GL_BLEND)); +} + + +void GLGizmoHollow::on_render_for_picking() +{ + const Selection& selection = m_parent.get_selection(); +//#if ENABLE_RENDER_PICKING_PASS +// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); +//#endif + + glsafe(::glEnable(GL_DEPTH_TEST)); + render_points(selection, true); +} + +void GLGizmoHollow::render_points(const Selection& selection, bool picking) +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); +#else + GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); + if (shader) + shader->start_using(); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_scaling_factor_matrix().inverse(); +#else + const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d& projection_matrix = camera.get_projection_matrix(); + + shader->set_uniform("projection_matrix", projection_matrix); +#else + const Transform3d& instance_scaling_matrix_inverse = trafo.get_matrix(true, true, false, true).inverse(); + const Transform3d& instance_matrix = trafo.get_matrix(); + + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); + glsafe(::glMultMatrixd(instance_matrix.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + ColorRGBA render_color; + const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + const size_t cache_size = drain_holes.size(); + + for (size_t i = 0; i < cache_size; ++i) { + const sla::DrainHole& drain_hole = drain_holes[i]; + const bool point_selected = m_selected[i]; + + if (is_mesh_point_clipped(drain_hole.pos.cast())) + continue; + + // First decide about the color of the point. + if (picking) + render_color = picking_color_component(i); + else { + if (size_t(m_hover_id) == i) + render_color = ColorRGBA::CYAN(); + else if (m_c->hollowed_mesh() && + i < m_c->hollowed_mesh()->get_drainholes().size() && + m_c->hollowed_mesh()->get_drainholes()[i].failed) { + render_color = { 1.0f, 0.0f, 0.0f, 0.5f }; + } + else // neither hover nor picking + render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_cylinder.set_color(render_color); +#else + const_cast(&m_cylinder)->set_color(-1, render_color); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); + glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (vol->is_left_handed()) + glFrontFace(GL_CW); + + // Matrices set, we can render the point mark now. + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); + const Eigen::AngleAxisd aa(q); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); + glsafe(::glTranslated(0., 0., -drain_hole.height)); + glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_cylinder.render(); + + if (vol->is_left_handed()) + glFrontFace(GL_CCW); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + +bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const +{ + if (m_c->object_clipper()->get_position() == 0.) + return false; + + auto sel_info = m_c->selection_info(); + int active_inst = m_c->selection_info()->get_active_instance(); + const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; + const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); + + Vec3d transformed_point = trafo * point; + transformed_point(2) += sel_info->get_sla_shift(); + return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); +} + + + +// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal +// Return false if no intersection was found, true otherwise. +bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) +{ + if (! m_c->raycaster()->raycaster()) + return false; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); + + double clp_dist = m_c->object_clipper()->get_position(); + const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); + + // The raycaster query + Vec3f hit; + Vec3f normal; + if (m_c->raycaster()->raycaster()->unproject_on_mesh( + mouse_pos, + trafo.get_matrix(), + camera, + hit, + normal, + clp_dist != 0. ? clp : nullptr)) + { + if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) { + // in this case the raycaster sees the hollowed and drilled mesh. + // if the point lies on the surface created by the hole, we want + // to ignore it. + for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) { + sla::DrainHole outer(hole); + outer.radius *= 1.001f; + outer.height *= 1.001f; + if (outer.is_inside(hit)) + return false; + } + } + + // Return both the point and the facet normal. + pos_and_normal = std::make_pair(hit, normal); + return true; + } + else + return false; +} + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + ModelObject* mo = m_c->selection_info()->model_object(); + int active_inst = m_c->selection_info()->get_active_instance(); + + + // left down with shift - show the selection rectangle: + if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { + if (m_hover_id == -1) { + if (shift_down || alt_down) { + m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); + } + } + else { + if (m_selected[m_hover_id]) + unselect_point(m_hover_id); + else { + if (!alt_down) + select_point(m_hover_id); + } + } + + return true; + } + + // left down without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { + // If any point is in hover state, this should initiate its move - return control back to GLCanvas: + if (m_hover_id != -1) + return false; + + // If there is some selection, don't add new point and deselect everything instead. + if (m_selection_empty) { + std::pair pos_and_normal; + if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole"))); + + mo->sla_drain_holes.emplace_back(pos_and_normal.first, + -pos_and_normal.second, m_new_hole_radius, m_new_hole_height); + m_selected.push_back(false); + assert(m_selected.size() == mo->sla_drain_holes.size()); + m_parent.set_as_dirty(); + m_wait_for_up_event = true; + } + else + return false; + } + else + select_point(NoPoints); + + return true; + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { + // Is this a selection or deselection rectangle? + GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); + + // First collect positions of all the points in world coordinates. + Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); + std::vector points; + for (unsigned int i=0; isla_drain_holes.size(); ++i) + points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast()); + + // Now ask the rectangle which of the points are inside. + std::vector points_inside; + std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); + for (size_t idx : points_idxs) + points_inside.push_back(points[idx].cast()); + + // Only select/deselect points that are actually visible + for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( + trafo, wxGetApp().plater()->get_camera(), points_inside, + m_c->object_clipper()->get_clipping_plane())) + { + if (rectangle_status == GLSelectionRectangle::EState::Deselect) + unselect_point(points_idxs[idx]); + else + select_point(points_idxs[idx]); + } + return true; + } + + // left up with no selection rectangle + if (action == SLAGizmoEventType::LeftUp) { + if (m_wait_for_up_event) { + m_wait_for_up_event = false; + return true; + } + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging) { + if (m_wait_for_up_event) + return true; // point has been placed and the button not released yet + // this prevents GLCanvas from starting scene rotation + + if (m_selection_rectangle.is_dragging()) { + m_selection_rectangle.dragging(mouse_position); + return true; + } + + return false; + } + + if (action == SLAGizmoEventType::Delete) { + // delete key pressed + delete_selected_points(); + return true; + } + + if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + delete_selected_points(); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::SelectAll) { + select_point(AllPoints); + return true; + } + + if (action == SLAGizmoEventType::MouseWheelUp && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::min(1., pos + 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + + if (action == SLAGizmoEventType::MouseWheelDown && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::max(0., pos - 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + + if (action == SLAGizmoEventType::ResetClippingPlane) { + m_c->object_clipper()->set_position(-1., false); + return true; + } + + return false; +} + +void GLGizmoHollow::delete_selected_points() +{ + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole"))); + sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + + for (unsigned int idx=0; idx wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + + static bool pending_right_up = false; + if (mouse_event.LeftDown()) { + bool control_down = mouse_event.CmdDown(); + bool grabber_contains_mouse = (get_hover_id() != -1); + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + // the gizmo got the event and took some action, there is no need + // to do anything more + return true; + } else if (mouse_event.Dragging()) { + if (m_parent.get_move_volume_id() != -1) + // don't allow dragging objects with the Sla gizmo on + return true; + + bool control_down = mouse_event.CmdDown(); + if (control_down) { + // CTRL has been pressed while already dragging -> stop current action + if (mouse_event.LeftIsDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + else if (mouse_event.RightIsDown()) { + pending_right_up = false; + } + } else if(gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { + // the gizmo got the event and took some action, no need to do + // anything more here + m_parent.set_as_dirty(); + return true; + } + } else if (mouse_event.LeftUp()) { + if (!m_parent.is_mouse_dragging()) { + bool control_down = mouse_event.CmdDown(); + // in case gizmo is selected, we just pass the LeftUp event + // and stop processing - neither object moving or selecting is + // suppressed in that case + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); + return true; + } + } else if (mouse_event.RightDown()) { + if (m_parent.get_selection().get_object_idx() != -1 && + gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { + // we need to set the following right up as processed to avoid showing + // the context menu if the user release the mouse over the object + pending_right_up = true; + // event was taken care of by the SlaSupports gizmo + return true; + } + } else if (mouse_event.RightUp()) { + if (pending_right_up) { + pending_right_up = false; + return true; + } + } + return false; +} + +void GLGizmoHollow::hollow_mesh(bool postpone_error_messages) +{ + wxGetApp().CallAfter([this, postpone_error_messages]() { + wxGetApp().plater()->reslice_SLA_hollowing( + *m_c->selection_info()->model_object(), postpone_error_messages); + }); +} + + +std::vector> +GLGizmoHollow::get_config_options(const std::vector& keys) const +{ + std::vector> out; + const ModelObject* mo = m_c->selection_info()->model_object(); + + if (! mo) + return out; + + const DynamicPrintConfig& object_cfg = mo->config.get(); + const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + std::unique_ptr default_cfg = nullptr; + + for (const std::string& key : keys) { + if (object_cfg.has(key)) + out.emplace_back(object_cfg.option(key), &object_cfg.def()->options.at(key)); // at() needed for const map + else + if (print_cfg.has(key)) + out.emplace_back(print_cfg.option(key), &print_cfg.def()->options.at(key)); + else { // we must get it from defaults + if (default_cfg == nullptr) + default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); + out.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key)); + } + } + + return out; +} + + +void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit) +{ + ModelObject* mo = m_c->selection_info()->model_object(); + if (! mo) + return; + + bool first_run = true; // This is a hack to redraw the button when all points are removed, + // so it is not delayed until the background process finishes. + + ConfigOptionMode current_mode = wxGetApp().get_mode(); + + std::vector opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}; + auto opts = get_config_options(opts_keys); + auto* offset_cfg = static_cast(opts[0].first); + float offset = offset_cfg->value; + double offset_min = opts[0].second->min; + double offset_max = opts[0].second->max; + + auto* quality_cfg = static_cast(opts[1].first); + float quality = quality_cfg->value; + double quality_min = opts[1].second->min; + double quality_max = opts[1].second->max; + ConfigOptionMode quality_mode = opts[1].second->mode; + + auto* closing_d_cfg = static_cast(opts[2].first); + float closing_d = closing_d_cfg->value; + double closing_d_min = opts[2].second->min; + double closing_d_max = opts[2].second->max; + ConfigOptionMode closing_d_mode = opts[2].second->mode; + + m_desc["offset"] = _(opts[0].second->label) + ":"; + m_desc["quality"] = _(opts[1].second->label) + ":"; + m_desc["closing_distance"] = _(opts[2].second->label) + ":"; + + +RENDER_AGAIN: + const float approx_height = m_imgui->scaled(20.0f); + y = std::min(y, bottom_limit - approx_height); + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(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 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, button_preview_width); + + if (m_imgui->button(m_desc["preview"])) + hollow_mesh(); + + bool config_changed = false; + + ImGui::Separator(); + + { + auto opts = get_config_options({"hollowing_enable"}); + m_enable_hollowing = static_cast(opts[0].first)->value; + if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) { + mo->config.set("hollowing_enable", m_enable_hollowing); + wxGetApp().obj_list()->update_and_show_object_settings_item(); + config_changed = true; + } + } + + m_imgui->disabled_begin(! m_enable_hollowing); + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("offset")); + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + ImGui::PushItemWidth(window_width - settings_sliders_left); + m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip)); + + bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider + bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider + bool slider_released =m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider + + if (current_mode >= quality_mode) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("quality")); + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip)); + + slider_clicked |= m_imgui->get_last_slider_status().clicked; + slider_edited |= m_imgui->get_last_slider_status().edited; + slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; + } + + if (current_mode >= closing_d_mode) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("closing_distance")); + 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", 1.0f, true, _L(opts[2].second->tooltip)); + + slider_clicked |= m_imgui->get_last_slider_status().clicked; + slider_edited |= m_imgui->get_last_slider_status().edited; + slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; + } + + if (slider_clicked) { + m_offset_stash = offset; + m_quality_stash = quality; + m_closing_d_stash = closing_d; + } + if (slider_edited || slider_released) { + if (slider_released) { + mo->config.set("hollowing_min_thickness", m_offset_stash); + mo->config.set("hollowing_quality", m_quality_stash); + mo->config.set("hollowing_closing_distance", m_closing_d_stash); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change"))); + } + mo->config.set("hollowing_min_thickness", offset); + mo->config.set("hollowing_quality", quality); + mo->config.set("hollowing_closing_distance", closing_d); + if (slider_released) { + wxGetApp().obj_list()->update_and_show_object_settings_item(); + config_changed = true; + } + } + + m_imgui->disabled_end(); + + bool force_refresh = false; + bool remove_selected = false; + bool remove_all = false; + + ImGui::Separator(); + + float diameter_upper_cap = 60.; + if (m_new_hole_radius * 2.f > diameter_upper_cap) + m_new_hole_radius = diameter_upper_cap / 2.f; + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("hole_diameter")); + ImGui::SameLine(diameter_slider_left, 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("##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. + diam = std::clamp(diam, 0.1f, diameter_upper_cap); + m_new_hole_radius = diam / 2.f; + bool clicked = m_imgui->get_last_slider_status().clicked; + bool edited = m_imgui->get_last_slider_status().edited; + bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["hole_depth"]); + 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); + + clicked |= m_imgui->get_last_slider_status().clicked; + edited |= m_imgui->get_last_slider_status().edited; + deactivated |= m_imgui->get_last_slider_status().deactivated_after_edit;; + + // Following is a nasty way to: + // - save the initial value of the slider before one starts messing with it + // - 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 + if (! m_selection_empty) { + if (clicked) { + m_holes_stash = mo->sla_drain_holes; + } + if (edited) { + for (size_t idx=0; idxsla_drain_holes[idx].radius = m_new_hole_radius; + mo->sla_drain_holes[idx].height = m_new_hole_height; + } + } + if (deactivated) { + // momentarily restore the old value to take snapshot + sla::DrainHoles new_holes = mo->sla_drain_holes; + mo->sla_drain_holes = m_holes_stash; + float backup_rad = m_new_hole_radius; + float backup_hei = m_new_hole_height; + for (size_t i=0; isla_drain_holes = new_holes; + } + } + + m_imgui->disabled_begin(m_selection_empty); + remove_selected = m_imgui->button(m_desc.at("remove_selected")); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(mo->sla_drain_holes.empty()); + remove_all = m_imgui->button(m_desc.at("remove_all")); + m_imgui->disabled_end(); + + // Following is rendered in both editing and non-editing mode: + // m_imgui->text(""); + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("clipping_of_view")); + } + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position(-1., false); + }); + } + } + + 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", &clp_dist, 0.f, 1.f, "%.2f")) + m_c->object_clipper()->set_position(clp_dist, true); + + // make sure supports are shown/hidden as appropriate + bool show_sups = m_c->instances_hider()->are_supports_shown(); + if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) { + m_c->instances_hider()->show_supports(show_sups); + force_refresh = true; + } + + m_imgui->end(); + + + if (remove_selected || remove_all) { + force_refresh = false; + m_parent.set_as_dirty(); + + if (remove_all) { + select_point(AllPoints); + delete_selected_points(); + } + if (remove_selected) + delete_selected_points(); + + if (first_run) { + first_run = false; + goto RENDER_AGAIN; + } + } + + if (force_refresh) + m_parent.set_as_dirty(); + + if (config_changed) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); +} + +bool GLGizmoHollow::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA + || !selection.is_from_single_instance()) + return false; + + // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. + const Selection::IndicesList& list = selection.get_volume_idxs(); + for (const auto& idx : list) + if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) + return false; + + return true; +} + +bool GLGizmoHollow::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); +} + +std::string GLGizmoHollow::on_get_name() const +{ + return _u8L("Hollow and drill"); +} + + +CommonGizmosDataID GLGizmoHollow::on_get_requirements() const +{ + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::HollowedMesh) + | int(CommonGizmosDataID::ObjectClipper) + | int(CommonGizmosDataID::SupportsClipper)); +} + + +void GLGizmoHollow::on_set_state() +{ + if (m_state == m_old_state) + return; + + if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); + m_old_state = m_state; +} + + + +void GLGizmoHollow::on_start_dragging() +{ + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos; + } + else + m_hole_before_drag = Vec3f::Zero(); +} + + +void GLGizmoHollow::on_stop_dragging() +{ + sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + if (m_hover_id != -1) { + Vec3f backup = drain_holes[m_hover_id].pos; + + if (m_hole_before_drag != Vec3f::Zero() // some point was touched + && backup != m_hole_before_drag) // and it was moved, not just selected + { + drain_holes[m_hover_id].pos = m_hole_before_drag; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole"))); + drain_holes[m_hover_id].pos = backup; + } + } + m_hole_before_drag = Vec3f::Zero(); +} + + +void GLGizmoHollow::on_dragging(const UpdateData &data) +{ + assert(m_hover_id != -1); + std::pair pos_and_normal; + if (!unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) + return; + sla::DrainHoles &drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + drain_holes[m_hover_id].pos = pos_and_normal.first; + drain_holes[m_hover_id].normal = -pos_and_normal.second; +} + + +void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar) +{ + ar(m_new_hole_radius, + m_new_hole_height, + m_selected, + m_selection_empty + ); +} + + + +void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const +{ + ar(m_new_hole_radius, + m_new_hole_height, + m_selected, + m_selection_empty + ); +} + + + +void GLGizmoHollow::select_point(int i) +{ + const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + + if (i == AllPoints || i == NoPoints) { + m_selected.assign(m_selected.size(), i == AllPoints); + m_selection_empty = (i == NoPoints); + + if (i == AllPoints) { + m_new_hole_radius = drain_holes[0].radius; + m_new_hole_height = drain_holes[0].height; + } + } + else { + while (size_t(i) >= m_selected.size()) + m_selected.push_back(false); + m_selected[i] = true; + m_selection_empty = false; + m_new_hole_radius = drain_holes[i].radius; + m_new_hole_height = drain_holes[i].height; + } +} + + +void GLGizmoHollow::unselect_point(int i) +{ + m_selected[i] = false; + m_selection_empty = true; + for (const bool sel : m_selected) { + if (sel) { + m_selection_empty = false; + break; + } + } +} + +void GLGizmoHollow::reload_cache() +{ + m_selected.clear(); + m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false); +} + + +void GLGizmoHollow::on_set_hover_id() +{ + if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id) + m_hover_id = -1; +} + + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 14c41ce54..52dd207e9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -90,32 +90,32 @@ bool GLGizmoMove3D::on_is_activable() const void GLGizmoMove3D::on_start_dragging() { - if (m_hover_id != -1) { - m_displacement = Vec3d::Zero(); + assert(m_hover_id != -1); + + m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE - const Selection& selection = m_parent.get_selection(); - const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); - if (coordinates_type == ECoordinatesType::World) - m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; - else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); - m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; - } - else { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); - m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; - } - m_starting_box_center = m_center; - m_starting_box_bottom_center = m_center; - m_starting_box_bottom_center.z() = m_bounding_box.min.z(); -#else - const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box_center = box.center(); - m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center.z() = box.min.z(); -#endif // ENABLE_WORLD_COORDINATE + const Selection& selection = m_parent.get_selection(); + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) + m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; } + else { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; + } + m_starting_box_center = m_center; + m_starting_box_bottom_center = m_center; + m_starting_box_bottom_center.z() = m_bounding_box.min.z(); +#else + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); + m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_box_center = box.center(); + m_starting_box_bottom_center = box.center(); + m_starting_box_bottom_center.z() = box.min.z(); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_stop_dragging() @@ -134,7 +134,11 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) m_displacement.z() = calc_projection(data); Selection &selection = m_parent.get_selection(); +#if ENABLE_WORLD_COORDINATE + selection.translate(m_displacement, wxGetApp().obj_manipul()->get_coordinates_type()); +#else selection.translate(m_displacement); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_render() @@ -148,9 +152,18 @@ void GLGizmoMove3D::on_render() glsafe(::glEnable(GL_DEPTH_TEST)); #if ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPushMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES calc_selection_box_and_center(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = local_transform(m_parent.get_selection()); + for (int i = 0; i < 3; ++i) { + m_grabbers[i].matrix = base_matrix; + } +#else transform_to_local(m_parent.get_selection()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES const Vec3d zero = Vec3d::Zero(); const Vec3d half_box_size = 0.5 * m_bounding_box.size(); @@ -236,7 +249,7 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -270,8 +283,12 @@ void GLGizmoMove3D::on_render() #if !ENABLE_GIZMO_GRABBER_REFACTOR for (unsigned int i = 0; i < 3; ++i) { if (m_grabbers[i].enabled) +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_grabber_extension((Axis)i, base_matrix, m_bounding_box, false); +#else render_grabber_extension((Axis)i, m_bounding_box, false); - } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } #endif // !ENABLE_GIZMO_GRABBER_REFACTOR #else render_grabbers(box); @@ -292,7 +309,7 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -329,7 +346,11 @@ void GLGizmoMove3D::on_render() } #if !ENABLE_GIZMO_GRABBER_REFACTOR #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_grabber_extension((Axis)m_hover_id, base_matrix, m_bounding_box, false); +#else render_grabber_extension((Axis)m_hover_id, m_bounding_box, false); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #else render_grabber_extension((Axis)m_hover_id, box, false); #endif // ENABLE_WORLD_COORDINATE @@ -337,7 +358,9 @@ void GLGizmoMove3D::on_render() } #if ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_WORLD_COORDINATE } @@ -346,15 +369,30 @@ void GLGizmoMove3D::on_render_for_picking() glsafe(::glDisable(GL_DEPTH_TEST)); #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = local_transform(m_parent.get_selection()); + for (int i = 0; i < 3; ++i) { + m_grabbers[i].matrix = base_matrix; + } +#else glsafe(::glPushMatrix()); transform_to_local(m_parent.get_selection()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_for_picking(m_bounding_box); +#if ENABLE_GL_SHADERS_ATTRIBUTES +#if !ENABLE_GIZMO_GRABBER_REFACTOR + render_grabber_extension(X, base_matrix, m_bounding_box, true); + render_grabber_extension(Y, base_matrix, m_bounding_box, true); + render_grabber_extension(Z, base_matrix, m_bounding_box, true); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#else #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(X, m_bounding_box, true); render_grabber_extension(Y, m_bounding_box, true); render_grabber_extension(Z, m_bounding_box, true); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); render_grabbers_for_picking(box); @@ -393,9 +431,14 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const } #if !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES +void GLGizmoMove3D::render_grabber_extension(Axis axis, const Transform3d& base_matrix, const BoundingBoxf3& box, bool picking) +#else void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) +#endif // ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES { - const float mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0); + const Vec3d box_size = box.size(); + const float mean_size = float((box_size.x() + box_size.y() + box_size.z()) / 3.0); const double size = m_dragging ? double(m_grabbers[axis].get_dragging_half_size(mean_size)) : double(m_grabbers[axis].get_half_size(mean_size)); #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -420,7 +463,7 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform(m_grabbers[axis].center); + Transform3d view_model_matrix = camera.get_view_matrix() * base_matrix * Geometry::assemble_transform(m_grabbers[axis].center); if (axis == X) view_model_matrix = view_model_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()); else if (axis == Y) @@ -454,6 +497,28 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box #endif // !ENABLE_GIZMO_GRABBER_REFACTOR #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES +Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const +{ + Transform3d ret = Geometry::assemble_transform(m_center); + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); +#else + Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); +#else + orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + ret = ret * orient_matrix; + } + return ret; +} +#else void GLGizmoMove3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); @@ -466,6 +531,7 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const glsafe(::glMultMatrixd(orient_matrix.data())); } } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES void GLGizmoMove3D::calc_selection_box_and_center() { @@ -477,7 +543,12 @@ void GLGizmoMove3D::calc_selection_box_and_center() } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box = v.transformed_convex_hull_bounding_box( + v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); +#else m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_center = v.world_matrix() * m_bounding_box.center(); } else { @@ -487,8 +558,13 @@ void GLGizmoMove3D::calc_selection_box_and_center() const GLVolume& v = *selection.get_volume(id); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center(); +#else m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index c1368cf62..85f3c4785 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -71,7 +71,11 @@ protected: private: double calc_projection(const UpdateData& data) const; #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + Transform3d local_transform(const Selection& selection) const; +#else void transform_to_local(const Selection& selection) const; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES void calc_selection_box_and_center(); #endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 0cba59c6d..c7731f1ff 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1,1383 +1,1407 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoPainterBase.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" - -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/OpenGLManager.hpp" -#include "slic3r/Utils/UndoRedo.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/TriangleMesh.hpp" - -#include -#include - -namespace Slic3r::GUI { - -#if ENABLE_LEGACY_OPENGL_REMOVAL -std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; -#else -std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{ -} - -GLGizmoPainterBase::~GLGizmoPainterBase() -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (s_sphere != nullptr) - s_sphere.reset(); -#else - if (s_sphere != nullptr && s_sphere->has_VBOs()) - s_sphere->release_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLGizmoPainterBase::data_changed() -{ - if (m_state != On) - return; - - const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; - const Selection & selection = m_parent.get_selection(); - if (mo && selection.is_from_single_instance() - && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) - { - update_from_model_object(); - m_old_mo_id = mo->id(); - m_old_volumes_size = mo->volumes.size(); - m_schedule_update = false; - } -} - -GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const -{ - ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}}; - // Take care of the clipping plane. The normal of the clipping plane is - // saved with opposite sign than we need to pass to OpenGL (FIXME) - if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) { - const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); - for (size_t i = 0; i < 3; ++i) - clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]); - clp_data_out.clp_dataf[3] = float(clp->get_data()[3]); - } - - // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) - if (m_c->get_canvas()->get_use_clipping_planes()) { - const std::array &clps = m_c->get_canvas()->get_clipping_planes(); - clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])}; - } - - return clp_data_out; -} - -void GLGizmoPainterBase::render_triangles(const Selection& selection) const -{ - auto* shader = wxGetApp().get_shader("gouraud"); - if (! shader) - return; - shader->start_using(); - shader->set_uniform("slope.actived", false); - shader->set_uniform("print_volume.type", 0); - shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf); - ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); - - const ModelObject *mo = m_c->selection_info()->model_object(); - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++mesh_id; - - const Transform3d trafo_matrix = - mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * - mv->get_matrix(); - - bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; - if (is_left_handed) - glsafe(::glFrontFace(GL_CW)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d matrix = camera.get_view_matrix() * trafo_matrix; - shader->set_uniform("view_model_matrix", matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo_matrix.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - // For printers with multiple extruders, it is necessary to pass trafo_matrix - // to the shader input variable print_box.volume_world_matrix before - // rendering the painted triangles. When this matrix is not set, the - // wrong transformation matrix is used for "Clipping of view". - shader->set_uniform("volume_world_matrix", trafo_matrix); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix); -#else - m_triangle_selectors[mesh_id]->render(m_imgui); - - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (is_left_handed) - glsafe(::glFrontFace(GL_CCW)); - } -} - -void GLGizmoPainterBase::render_cursor() -{ - // First check that the mouse pointer is on an object. - const ModelObject* mo = m_c->selection_info()->model_object(); - const Selection& selection = m_parent.get_selection(); - const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; - const Camera& camera = wxGetApp().plater()->get_camera(); - - // Precalculate transformations of individual meshes. - std::vector trafo_matrices; - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part()) - trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); - } - // Raycast and return if there's no hit. - update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices); - if (m_rr.mesh_id == -1) - return; - - if (m_tool_type == ToolType::BRUSH) { - if (m_cursor_type == TriangleSelector::SPHERE) - render_cursor_sphere(trafo_matrices[m_rr.mesh_id]); - else if (m_cursor_type == TriangleSelector::CIRCLE) - render_cursor_circle(); - } -} - -void GLGizmoPainterBase::render_cursor_circle() -{ -#if !ENABLE_GL_SHADERS_ATTRIBUTES - const Camera &camera = wxGetApp().plater()->get_camera(); - const float zoom = float(camera.get_zoom()); - const float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - const Size cnv_size = m_parent.get_canvas_size(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const float cnv_width = float(cnv_size.get_width()); - const float cnv_height = float(cnv_size.get_height()); - if (cnv_width == 0.0f || cnv_height == 0.0f) - return; - - const float cnv_inv_width = 1.0f / cnv_width; - const float cnv_inv_height = 1.0f / cnv_height; - - const Vec2d center = m_parent.get_local_mouse_position(); - const float radius = m_cursor_radius * float(wxGetApp().plater()->get_camera().get_zoom()); -#else - const float cnv_half_width = 0.5f * float(cnv_size.get_width()); - const float cnv_half_height = 0.5f * float(cnv_size.get_height()); - if (cnv_half_width == 0.0f || cnv_half_height == 0.0f) - return; - const Vec2d mouse_pos(m_parent.get_local_mouse_position().x(), m_parent.get_local_mouse_position().y()); - Vec2d center(mouse_pos.x() - cnv_half_width, cnv_half_height - mouse_pos.y()); - center = center * inv_zoom; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - glsafe(::glLineWidth(1.5f)); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - static const std::array color = { 0.f, 1.f, 0.3f }; - glsafe(::glColor3fv(color.data())); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glDisable(GL_DEPTH_TEST)); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - // ensure that the circle is renderered inside the frustrum - glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); - // ensure that the overlay fits the frustrum near z plane - const double gui_scale = camera.get_gui_scale(); - glsafe(::glScaled(gui_scale, gui_scale, 1.0)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - glsafe(::glPushAttrib(GL_ENABLE_BIT)); - glsafe(::glLineStipple(4, 0xAAAA)); - glsafe(::glEnable(GL_LINE_STIPPLE)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - radius) > EPSILON) { - m_old_cursor_radius = radius; -#else - if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - m_cursor_radius) > EPSILON) { - m_old_cursor_radius = m_cursor_radius; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_old_center = center; - m_circle.reset(); - - GLModel::Geometry init_data; - static const unsigned int StepsCount = 32; - static const float StepSize = 2.0f * float(PI) / float(StepsCount); - init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; - init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f }; - init_data.reserve_vertices(StepsCount); - init_data.reserve_indices(StepsCount); - - // vertices + indices - for (unsigned int i = 0; i < StepsCount; ++i) { - const float angle = float(i) * StepSize; -#if ENABLE_GL_SHADERS_ATTRIBUTES - init_data.add_vertex(Vec2f(2.0f * ((center.x() + ::cos(angle) * radius) * cnv_inv_width - 0.5f), - -2.0f * ((center.y() + ::sin(angle) * radius) * cnv_inv_height - 0.5f))); -#else - init_data.add_vertex(Vec2f(center.x() + ::cos(angle) * m_cursor_radius, center.y() + ::sin(angle) * m_cursor_radius)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - init_data.add_index(i); - } - - m_circle.init_from(std::move(init_data)); - } - - GLShaderProgram* shader = GUI::wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - shader->set_uniform("view_model_matrix", Transform3d::Identity()); - shader->set_uniform("projection_matrix", Transform3d::Identity()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_circle.render(); - shader->stop_using(); - } -#else - ::glBegin(GL_LINE_LOOP); - for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) - ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glPopAttrib()); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glEnable(GL_DEPTH_TEST)); -} - - -void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const -{ - if (s_sphere == nullptr) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - s_sphere = std::make_shared(); - s_sphere->init_from(its_make_sphere(1.0, double(PI) / 12.0)); -#else - s_sphere = std::make_shared(); - s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0)); - s_sphere->finalize_geometry(true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); - const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo.data())); - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glTranslatef(m_rr.hit.x(), m_rr.hit.y(), m_rr.hit.z())); - glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data())); - glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - if (is_left_handed) - glFrontFace(GL_CW); - - ColorRGBA render_color = { 0.0f, 0.0f, 0.0f, 0.25f }; - if (m_button_down == Button::Left) - render_color = this->get_cursor_sphere_left_button_color(); - else if (m_button_down == Button::Right) - render_color = this->get_cursor_sphere_right_button_color(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->start_using(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - Transform3d view_model_matrix = camera.get_view_matrix() * trafo * - Geometry::assemble_transform(m_rr.hit.cast()) * complete_scaling_matrix_inverse * - Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), m_cursor_radius * Vec3d::Ones()); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - assert(s_sphere != nullptr); - s_sphere->set_color(render_color); -#else - glsafe(::glColor4fv(render_color.data())); - - assert(s_sphere != nullptr); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - s_sphere->render(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - if (is_left_handed) - glFrontFace(GL_CCW); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - - -bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const -{ - if (m_c->object_clipper()->get_position() == 0.) - return false; - - auto sel_info = m_c->selection_info(); - Vec3d transformed_point = trafo * point; - transformed_point(2) += sel_info->get_sla_shift(); - return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); -} - -// Interpolate points between the previous and current mouse positions, which are then projected onto the object. -// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector -// with the same mesh_idx, but all items in std::vector always have the same mesh_idx. -std::vector> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector &trafo_matrices) const -{ - // List of mouse positions that will be used as seeds for painting. - std::vector mouse_positions{mouse_position}; - if (m_last_mouse_click != Vec2d::Zero()) { - // In case current mouse position is far from the last one, - // add several positions from between into the list, so there - // are no gaps in the painted region. - if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) { - const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1); - for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx) - mouse_positions.emplace_back(mouse_position + patch_idx * diff); - mouse_positions.emplace_back(m_last_mouse_click); - } - } - - const Camera &camera = wxGetApp().plater()->get_camera(); - std::vector mesh_hit_points; - mesh_hit_points.reserve(mouse_positions.size()); - - // In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't. - for (const Vec2d &mp : mouse_positions) { - update_raycast_cache(mp, camera, trafo_matrices); - mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet}); - if (m_rr.mesh_id == -1) - break; - } - - // Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx. - std::vector> mesh_hit_points_by_mesh; - for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) { - size_t next_mesh_hit_point = curr_mesh_hit_point + 1; - if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) { - mesh_hit_points_by_mesh.emplace_back(); - mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point)); - prev_mesh_hit_point = next_mesh_hit_point; - } - } - - auto on_same_facet = [](std::vector &hit_points) -> bool { - for (const ProjectedMousePosition &mesh_hit_point : hit_points) - if (mesh_hit_point.facet_idx != hit_points.front().facet_idx) - return false; - return true; - }; - - struct Plane - { - Vec3d origin; - Vec3d first_axis; - Vec3d second_axis; - }; - auto find_plane = [](std::vector &hit_points) -> std::optional { - assert(hit_points.size() >= 3); - for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) { - const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast(); - const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast(); - const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast(); - - const Vec3d first_vec = first_point - second_point; - const Vec3d second_vec = third_point - second_point; - - // If three points aren't collinear, then there exists only one plane going through all points. - if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) { - const Vec3d first_axis_vec_n = first_vec.normalized(); - // Make second_vec perpendicular to first_axis_vec_n using Gram–Schmidt orthogonalization process - const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized(); - return Plane{second_point, first_axis_vec_n, second_axis_vec_n}; - } - } - - return std::nullopt; - }; - - for(std::vector &hit_points : mesh_hit_points_by_mesh) { - assert(!hit_points.empty()); - if (hit_points.back().mesh_idx == -1) - break; - - if (hit_points.size() <= 2) - continue; - - if (on_same_facet(hit_points)) { - hit_points = {hit_points.front(), hit_points.back()}; - } else if (std::optional plane = find_plane(hit_points); plane) { - Polyline polyline; - polyline.points.reserve(hit_points.size()); - // Project hit_points into its plane to simplified them in the next step. - for (auto &hit_point : hit_points) { - const Vec3d &point = hit_point.mesh_hit.cast(); - const double x_cord = plane->first_axis.dot(point - plane->origin); - const double y_cord = plane->second_axis.dot(point - plane->origin); - polyline.points.emplace_back(scale_(x_cord), scale_(y_cord)); - } - - polyline.simplify(scale_(m_cursor_radius) / 10.); - - const int mesh_idx = hit_points.front().mesh_idx; - std::vector new_hit_points; - new_hit_points.reserve(polyline.points.size()); - // Project 2D simplified hit_points beck to 3D. - for (const Point &point : polyline.points) { - const double x_cord = unscale(point.x()); - const double y_cord = unscale(point.y()); - const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis; - const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast()); - new_hit_points.push_back({new_hit_point.cast(), mesh_idx, size_t(facet_idx)}); - } - - hit_points = new_hit_points; - } else { - hit_points = {hit_points.front(), hit_points.back()}; - } - } - - return mesh_hit_points_by_mesh; -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) -{ - if (action == SLAGizmoEventType::MouseWheelUp - || action == SLAGizmoEventType::MouseWheelDown) { - if (control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = action == SLAGizmoEventType::MouseWheelDown - ? std::max(0., pos - 0.01) - : std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - else if (alt_down) { - if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) { - m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - this->get_cursor_radius_step(), this->get_cursor_radius_min()) - : std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max()); - m_parent.set_as_dirty(); - return true; - } else if (m_tool_type == ToolType::SMART_FILL) { - m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin) - : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); - m_parent.set_as_dirty(); - if (m_rr.mesh_id != -1) { - const Selection &selection = m_parent.get_selection(); - 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); - 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; - } - return true; - } - - return false; - } - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); - return true; - } - - if (action == SLAGizmoEventType::LeftDown - || action == SLAGizmoEventType::RightDown - || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { - - if (m_triangle_selectors.empty()) - return false; - - EnforcerBlockerType new_state = EnforcerBlockerType::NONE; - if (! shift_down) { - if (action == SLAGizmoEventType::Dragging) - new_state = m_button_down == Button::Left ? this->get_left_button_state_type() : this->get_right_button_state_type(); - else - new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type(); - } - - const Camera &camera = wxGetApp().plater()->get_camera(); - const Selection &selection = m_parent.get_selection(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d instance_trafo = mi->get_transformation().get_matrix(); - const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); - - // Precalculate transformations of individual meshes. - std::vector trafo_matrices; - std::vector trafo_matrices_not_translate; - for (const ModelVolume *mv : mo->volumes) - if (mv->is_model_part()) { - trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); - trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); - } - - std::vector> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices); - m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved - - for (const std::vector &projected_mouse_positions : projected_mouse_positions_by_mesh) { - assert(!projected_mouse_positions.empty()); - const int mesh_idx = projected_mouse_positions.front().mesh_idx; - const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); - - // The mouse button click detection is enabled when there is a valid hit. - // Missing the object entirely - // shall not capture the mouse. - if (mesh_idx != -1) - if (m_button_down == Button::None) - m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); - - // In case we have no valid hit, we can return. The event will be stopped when - // dragging while painting (to prevent scene rotations and moving the object) - if (mesh_idx == -1) - return dragging_while_painting; - - const Transform3d &trafo_matrix = trafo_matrices[mesh_idx]; - const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx]; - - // Calculate direction from camera to the hit (in mesh coords): - Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); - - assert(mesh_idx < 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)) { - for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) { - assert(projected_mouse_position.mesh_idx == mesh_idx); - const Vec3f mesh_hit = projected_mouse_position.mesh_hit; - const int facet_idx = int(projected_mouse_position.facet_idx); - m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state); - if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, 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[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true); - else if (m_tool_type == ToolType::BUCKET_FILL) - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true); - - m_seed_fill_last_mesh_id = -1; - } - } else if (m_tool_type == ToolType::BRUSH) { - assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE); - - if (projected_mouse_positions.size() == 1) { - const ProjectedMousePosition &first_position = projected_mouse_positions.front(); - std::unique_ptr cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit, - camera_pos, m_cursor_radius, - m_cursor_type, trafo_matrix, clp); - m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, - m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); - } else { - for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) { - auto second_position_it = first_position_it + 1; - std::unique_ptr cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp); - m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); - } - } - } - - m_triangle_selectors[mesh_idx]->request_update_render_data(); - m_last_mouse_click = mouse_position; - } - - return true; - } - - if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) { - if (m_triangle_selectors.empty()) - return false; - - const Camera &camera = wxGetApp().plater()->get_camera(); - const Selection &selection = m_parent.get_selection(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d instance_trafo = mi->get_transformation().get_matrix(); - const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); - - // Precalculate transformations of individual meshes. - std::vector trafo_matrices; - std::vector trafo_matrices_not_translate; - for (const ModelVolume *mv : mo->volumes) - if (mv->is_model_part()) { - trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); - trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); - } - - // Now "click" into all the prepared points and spill paint around them. - update_raycast_cache(mouse_position, camera, trafo_matrices); - - auto seed_fill_unselect_all = [this]() { - for (auto &triangle_selector : m_triangle_selectors) { - triangle_selector->seed_fill_unselect_all_triangles(); - triangle_selector->request_update_render_data(); - } - }; - - if (m_rr.mesh_id == -1) { - // Clean selected by seed fill for all triangles in all meshes when a mouse isn't pointing on any mesh. - seed_fill_unselect_all(); - m_seed_fill_last_mesh_id = -1; - - // In case we have no valid hit, we can return. - return false; - } - - // The mouse moved from one object's volume to another one. So it is needed to unselect all triangles selected by seed fill. - 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, 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), 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), 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; - } - - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) - && m_button_down != Button::None) { - // Take snapshot and update ModelVolume data. - wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name, UndoRedo::SnapshotType::GizmoAction); - update_model_object(); - - m_button_down = Button::None; - m_last_mouse_click = Vec2d::Zero(); - return true; - } - - return false; -} - -bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event) -{ - // wxCoord == int --> wx/types.h - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); - Vec2d mouse_pos = mouse_coord.cast(); - - if (mouse_event.Moving()) { - gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false); - return false; - } - - // when control is down we allow scene pan and rotation even when clicking - // over some object - bool control_down = mouse_event.CmdDown(); - bool grabber_contains_mouse = (get_hover_id() != -1); - - const Selection &selection = m_parent.get_selection(); - int selected_object_idx = selection.get_object_idx(); - if (mouse_event.LeftDown()) { - if ((!control_down || grabber_contains_mouse) && - gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) - // the gizmo got the event and took some action, there is no need - // to do anything more - return true; - } else if (mouse_event.RightDown()){ - if (!control_down && selected_object_idx != -1 && - gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) - // event was taken care of - return true; - } else if (mouse_event.Dragging()) { - if (m_parent.get_move_volume_id() != -1) - // don't allow dragging objects with the Sla gizmo on - return true; - if (!control_down && gizmo_event(SLAGizmoEventType::Dragging, - mouse_pos, mouse_event.ShiftDown(), - mouse_event.AltDown(), false)) { - // the gizmo got the event and took some action, no need to do - // anything more here - m_parent.set_as_dirty(); - return true; - } - if(control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) - { - // CTRL has been pressed while already dragging -> stop current action - if (mouse_event.LeftIsDown()) - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); - else if (mouse_event.RightIsDown()) - gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); - return false; - } - } else if (mouse_event.LeftUp()) { - if (!m_parent.is_mouse_dragging()) { - // in case SLA/FDM gizmo is selected, we just pass the LeftUp - // event and stop processing - neither object moving or selecting - // is suppressed in that case - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); - return true; - } - } else if (mouse_event.RightUp()) { - if (!m_parent.is_mouse_dragging()) { - gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); - return true; - } - } - return false; -} - -void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position, - const Camera& camera, - const std::vector& trafo_matrices) const -{ - if (m_rr.mouse_position == mouse_position) { - // Same query as last time - the answer is already in the cache. - return; - } - - Vec3f normal = Vec3f::Zero(); - Vec3f hit = Vec3f::Zero(); - size_t facet = 0; - Vec3f closest_hit = Vec3f::Zero(); - double closest_hit_squared_distance = std::numeric_limits::max(); - size_t closest_facet = 0; - int closest_hit_mesh_id = -1; - - // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh - for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { - - if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( - mouse_position, - trafo_matrices[mesh_id], - camera, - hit, - normal, - m_c->object_clipper()->get_clipping_plane(), - &facet)) - { - // In case this hit is clipped, skip it. - if (is_mesh_point_clipped(hit.cast(), trafo_matrices[mesh_id])) - continue; - - // Is this hit the closest to the camera so far? - double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); - if (hit_squared_distance < closest_hit_squared_distance) { - closest_hit_squared_distance = hit_squared_distance; - closest_facet = facet; - closest_hit_mesh_id = mesh_id; - closest_hit = hit; - } - } - } - - m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet}; -} - -bool GLGizmoPainterBase::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF - || !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple) - return false; - - // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. - const Selection::IndicesList& list = selection.get_volume_idxs(); - return std::all_of(list.cbegin(), list.cend(), [&selection](unsigned int idx) { return !selection.get_volume(idx)->is_outside; }); -} - -bool GLGizmoPainterBase::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF - && wxGetApp().get_mode() != comSimple ); -} - - -CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::ObjectClipper)); -} - - -void GLGizmoPainterBase::on_set_state() -{ - if (m_state == m_old_state) - return; - - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - on_opening(); - } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - // we are actually shutting down - on_shutdown(); - m_old_mo_id = -1; - //m_iva.release_geometry(); - m_triangle_selectors.clear(); - } - m_old_state = m_state; -} - - - -void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&) -{ - // We should update the gizmo from current ModelObject, but it is not - // possible at this point. That would require having updated selection and - // common gizmos data, which is not done at this point. Instead, save - // a flag to do the update in set_painter_gizmo_data, which will be called - // soon after. - 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}); -} - -ColorRGBA TriangleSelectorGUI::get_seed_fill_color(const ColorRGBA& base_color) -{ - return saturate(base_color, 0.75f); -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void TriangleSelectorGUI::render(ImGuiWrapper* imgui, const Transform3d& matrix) -#else -void TriangleSelectorGUI::render(ImGuiWrapper* imgui) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - static const ColorRGBA enforcers_color = { 0.47f, 0.47f, 1.0f, 1.0f }; - static const ColorRGBA blockers_color = { 1.0f, 0.44f, 0.44f, 1.0f }; - - if (m_update_render_data) { - update_render_data(); - m_update_render_data = false; - } - - auto* shader = wxGetApp().get_current_shader(); - if (! shader) - return; - - assert(shader->get_name() == "gouraud"); - - for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), - std::make_pair(&m_iva_blockers, blockers_color)}) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - iva.first->set_color(iva.second); - iva.first->render(); -#else - if (iva.first->has_VBOs()) { - shader->set_uniform("uniform_color", iva.second); - iva.first->render(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (auto& iva : m_iva_seed_fills) { - size_t color_idx = &iva - &m_iva_seed_fills.front(); - const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : - color_idx == 2 ? blockers_color : - GLVolume::NEUTRAL_COLOR); - iva.set_color(color); - iva.render(); - } -#else - for (auto& iva : m_iva_seed_fills) - if (iva.has_VBOs()) { - size_t color_idx = &iva - &m_iva_seed_fills.front(); - const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : - color_idx == 2 ? blockers_color : - GLVolume::NEUTRAL_COLOR); - shader->set_uniform("uniform_color", color); - iva.render(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_paint_contour(matrix); -#else - render_paint_contour(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#else - if (m_paint_contour.has_VBO()) { - ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); - shader->stop_using(); - - auto *contour_shader = wxGetApp().get_shader("mm_contour"); - contour_shader->start_using(); - contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); - m_paint_contour.render(); - contour_shader->stop_using(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG - if (imgui) - render_debug(imgui); - else - assert(false); // If you want debug output, pass ptr to ImGuiWrapper. -#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG -} - -void TriangleSelectorGUI::update_render_data() -{ - int enf_cnt = 0; - int blc_cnt = 0; - std::vector seed_fill_cnt(m_iva_seed_fills.size(), 0); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) { - iva->reset(); - } - - for (auto& iva : m_iva_seed_fills) { - iva.reset(); - } - - GLModel::Geometry iva_enforcers_data; - iva_enforcers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - GLModel::Geometry iva_blockers_data; - iva_blockers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - std::array iva_seed_fills_data; - for (auto& data : iva_seed_fills_data) - data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; -#else - for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) - iva->release_geometry(); - - for (auto &iva : m_iva_seed_fills) - iva.release_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // small value used to offset triangles along their normal to avoid z-fighting - static const float offset = 0.001f; - - for (const Triangle &tr : m_triangles) { - if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill())) - continue; - - int tr_state = int(tr.get_state()); -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data : - iva_blockers_data; -#else - GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : - m_iva_blockers; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : - blc_cnt; - const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v; - const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v; - const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v; - //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort - // or the current implementation may be more cache friendly. - const Vec3f n = (v1 - v0).cross(v2 - v1).normalized(); - // small value used to offset triangles along their normal to avoid z-fighting - const Vec3f offset_n = offset * n; -#if ENABLE_LEGACY_OPENGL_REMOVAL - iva.add_vertex(v0 + offset_n, n); - iva.add_vertex(v1 + offset_n, n); - iva.add_vertex(v2 + offset_n, n); - iva.add_triangle((unsigned int)cnt, (unsigned int)cnt + 1, (unsigned int)cnt + 2); -#else - iva.push_geometry(v0 + offset_n, n); - iva.push_geometry(v1 + offset_n, n); - iva.push_geometry(v2 + offset_n, n); - iva.push_triangle(cnt, cnt + 1, cnt + 2); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - cnt += 3; - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!iva_enforcers_data.is_empty()) - m_iva_enforcers.init_from(std::move(iva_enforcers_data)); - if (!iva_blockers_data.is_empty()) - m_iva_blockers.init_from(std::move(iva_blockers_data)); - for (size_t i = 0; i < m_iva_seed_fills.size(); ++i) { - if (!iva_seed_fills_data[i].is_empty()) - m_iva_seed_fills[i].init_from(std::move(iva_seed_fills_data[i])); - } - - update_paint_contour(); -#else - for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) - iva->finalize_geometry(true); - - for (auto &iva : m_iva_seed_fills) - iva.finalize_geometry(true); - - m_paint_contour.release_geometry(); - std::vector contour_edges = this->get_seed_fill_contour(); - m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); - for (const Vec2i &edge : contour_edges) { - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z()); - - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z()); - } - - m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0); - std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0); - m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); - - m_paint_contour.finalize_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -#if !ENABLE_LEGACY_OPENGL_REMOVAL -void GLPaintContour::render() const -{ - assert(this->m_contour_VBO_id != 0); - assert(this->m_contour_EBO_id != 0); - - glsafe(::glLineWidth(4.0f)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - - if (this->contour_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); - glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -} - -void GLPaintContour::finalize_geometry() -{ - assert(this->m_contour_VBO_id == 0); - assert(this->m_contour_EBO_id == 0); - - if (!this->contour_vertices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - this->contour_vertices.clear(); - } - - if (!this->contour_indices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_EBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - this->contour_indices.clear(); - } -} - -void GLPaintContour::release_geometry() -{ - if (this->m_contour_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id)); - this->m_contour_VBO_id = 0; - } - if (this->m_contour_EBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id)); - this->m_contour_EBO_id = 0; - } - this->clear(); -} -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG -void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) -{ - imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - static float edge_limit = 1.f; - imgui->text("Edge limit (mm): "); - imgui->slider_float("", &edge_limit, 0.1f, 8.f); - set_edge_limit(edge_limit); - imgui->checkbox("Show split triangles: ", m_show_triangles); - imgui->checkbox("Show invalid triangles: ", m_show_invalid); - - int valid_triangles = m_triangles.size() - m_invalid_triangles; - imgui->text("Valid triangles: " + std::to_string(valid_triangles) + - "/" + std::to_string(m_triangles.size())); - imgui->text("Vertices: " + std::to_string(m_vertices.size())); - if (imgui->button("Force garbage collection")) - garbage_collect(); - - if (imgui->button("Serialize - deserialize")) { - auto map = serialize(); - deserialize(map); - } - - imgui->end(); - - if (! m_show_triangles) - return; - - enum vtype { - ORIGINAL = 0, - SPLIT, - INVALID - }; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (auto& va : m_varrays) - va.reset(); -#else - for (auto& va : m_varrays) - va.release_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - std::array cnts; - - ::glScalef(1.01f, 1.01f, 1.01f); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - std::array varrays_data; - for (auto& data : varrays_data) - data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - for (int tr_id=0; tr_idadd_vertex(m_vertices[tr.verts_idxs[i]].v, Vec3f(0.0f, 0.0f, 1.0f)); - } - va->add_uint_triangle((unsigned int)*cnt, (unsigned int)*cnt + 1, (unsigned int)*cnt + 2); -#else - for (int i = 0; i < 3; ++i) - va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), - double(m_vertices[tr.verts_idxs[i]].v[1]), - double(m_vertices[tr.verts_idxs[i]].v[2]), - 0., 0., 1.); - va->push_triangle(*cnt, - *cnt + 1, - *cnt + 2); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - *cnt += 3; - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (int i = 0; i < 3; ++i) { - if (!varrays_data[i].is_empty()) - m_varrays[i].init_from(std::move(varrays_data[i])); - } -#else -// for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) -// iva->finalize_geometry(true); -// -// for (auto& iva : m_iva_seed_fills) -// iva.finalize_geometry(true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); - if (curr_shader != nullptr) - curr_shader->stop_using(); - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - for (vtype i : {ORIGINAL, SPLIT, INVALID}) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLModel& va = m_varrays[i]; - switch (i) { - case ORIGINAL: va.set_color({ 0.0f, 0.0f, 1.0f, 1.0f }); break; - case SPLIT: va.set_color({ 1.0f, 0.0f, 0.0f, 1.0f }); break; - case INVALID: va.set_color({ 1.0f, 1.0f, 0.0f, 1.0f }); break; - } - va.render(); -#else - GLIndexedVertexArray& va = m_varrays[i]; - va.finalize_geometry(true); - if (va.has_VBOs()) { - switch (i) { - case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; - case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; - case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; - } - va.render(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); - } - - if (curr_shader != nullptr) - curr_shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} -#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void TriangleSelectorGUI::update_paint_contour() -{ - m_paint_contour.reset(); - - GLModel::Geometry init_data; - const std::vector contour_edges = this->get_seed_fill_contour(); - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(2 * contour_edges.size()); - init_data.reserve_indices(2 * contour_edges.size()); -#if ENABLE_GL_SHADERS_ATTRIBUTES - init_data.color = ColorRGBA::WHITE(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -// - // vertices + indices - unsigned int vertices_count = 0; - for (const Vec2i& edge : contour_edges) { - init_data.add_vertex(m_vertices[edge(0)].v); - init_data.add_vertex(m_vertices[edge(1)].v); - vertices_count += 2; - init_data.add_line(vertices_count - 2, vertices_count - 1); - } - - if (!init_data.is_empty()) - m_paint_contour.init_from(std::move(init_data)); -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void TriangleSelectorGUI::render_paint_contour(const Transform3d& matrix) -#else -void TriangleSelectorGUI::render_paint_contour() -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - auto* curr_shader = wxGetApp().get_current_shader(); - if (curr_shader != nullptr) - curr_shader->stop_using(); - - auto* contour_shader = wxGetApp().get_shader("mm_contour"); - if (contour_shader != nullptr) { - contour_shader->start_using(); - - contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - contour_shader->set_uniform("view_model_matrix", camera.get_view_matrix() * matrix); - contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_paint_contour.render(); - contour_shader->stop_using(); - } - - if (curr_shader != nullptr) - curr_shader->start_using(); -} -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -} // namespace Slic3r::GUI +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoPainterBase.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" + +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/OpenGLManager.hpp" +#include "slic3r/Utils/UndoRedo.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/TriangleMesh.hpp" + +#include +#include + +namespace Slic3r::GUI { + +#if ENABLE_LEGACY_OPENGL_REMOVAL +std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; +#else +std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{ +} + +GLGizmoPainterBase::~GLGizmoPainterBase() +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (s_sphere != nullptr) + s_sphere.reset(); +#else + if (s_sphere != nullptr && s_sphere->has_VBOs()) + s_sphere->release_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLGizmoPainterBase::data_changed() +{ + if (m_state != On) + return; + + const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; + const Selection & selection = m_parent.get_selection(); + if (mo && selection.is_from_single_instance() + && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) + { + update_from_model_object(); + m_old_mo_id = mo->id(); + m_old_volumes_size = mo->volumes.size(); + m_schedule_update = false; + } +} + +GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const +{ + ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}}; + // Take care of the clipping plane. The normal of the clipping plane is + // saved with opposite sign than we need to pass to OpenGL (FIXME) + if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) { + const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); + for (size_t i = 0; i < 3; ++i) + clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]); + clp_data_out.clp_dataf[3] = float(clp->get_data()[3]); + } + + // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) + if (m_c->get_canvas()->get_use_clipping_planes()) { + const std::array &clps = m_c->get_canvas()->get_clipping_planes(); + clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])}; + } + + return clp_data_out; +} + +void GLGizmoPainterBase::render_triangles(const Selection& selection) const +{ + auto* shader = wxGetApp().get_shader("gouraud"); + if (! shader) + return; + shader->start_using(); + shader->set_uniform("slope.actived", false); + shader->set_uniform("print_volume.type", 0); + shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); + + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++mesh_id; + + const Transform3d trafo_matrix = + mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * + mv->get_matrix(); + + bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d matrix = camera.get_view_matrix() * trafo_matrix; + shader->set_uniform("view_model_matrix", matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo_matrix.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + // For printers with multiple extruders, it is necessary to pass trafo_matrix + // to the shader input variable print_box.volume_world_matrix before + // rendering the painted triangles. When this matrix is not set, the + // wrong transformation matrix is used for "Clipping of view". + shader->set_uniform("volume_world_matrix", trafo_matrix); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix); +#else + m_triangle_selectors[mesh_id]->render(m_imgui); + + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + } +} + +void GLGizmoPainterBase::render_cursor() +{ + // First check that the mouse pointer is on an object. + const ModelObject* mo = m_c->selection_info()->model_object(); + const Selection& selection = m_parent.get_selection(); + const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + const Camera& camera = wxGetApp().plater()->get_camera(); + + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part()) + trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); + } + // Raycast and return if there's no hit. + update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices); + if (m_rr.mesh_id == -1) + return; + + if (m_tool_type == ToolType::BRUSH) { + if (m_cursor_type == TriangleSelector::SPHERE) + render_cursor_sphere(trafo_matrices[m_rr.mesh_id]); + else if (m_cursor_type == TriangleSelector::CIRCLE) + render_cursor_circle(); + } +} + +void GLGizmoPainterBase::render_cursor_circle() +{ +#if !ENABLE_GL_SHADERS_ATTRIBUTES + const Camera &camera = wxGetApp().plater()->get_camera(); + const float zoom = float(camera.get_zoom()); + const float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + const Size cnv_size = m_parent.get_canvas_size(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const float cnv_width = float(cnv_size.get_width()); + const float cnv_height = float(cnv_size.get_height()); + if (cnv_width == 0.0f || cnv_height == 0.0f) + return; + + const float cnv_inv_width = 1.0f / cnv_width; + const float cnv_inv_height = 1.0f / cnv_height; + + const Vec2d center = m_parent.get_local_mouse_position(); + const float radius = m_cursor_radius * float(wxGetApp().plater()->get_camera().get_zoom()); +#else + const float cnv_half_width = 0.5f * float(cnv_size.get_width()); + const float cnv_half_height = 0.5f * float(cnv_size.get_height()); + if (cnv_half_width == 0.0f || cnv_half_height == 0.0f) + return; + const Vec2d mouse_pos(m_parent.get_local_mouse_position().x(), m_parent.get_local_mouse_position().y()); + Vec2d center(mouse_pos.x() - cnv_half_width, cnv_half_height - mouse_pos.y()); + center = center * inv_zoom; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + glsafe(::glLineWidth(1.5f)); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + static const std::array color = { 0.f, 1.f, 0.3f }; + glsafe(::glColor3fv(color.data())); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glDisable(GL_DEPTH_TEST)); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); + // ensure that the circle is renderered inside the frustrum + glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); + // ensure that the overlay fits the frustrum near z plane + const double gui_scale = camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + glsafe(::glPushAttrib(GL_ENABLE_BIT)); + glsafe(::glLineStipple(4, 0xAAAA)); + glsafe(::glEnable(GL_LINE_STIPPLE)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - radius) > EPSILON) { + m_old_cursor_radius = radius; +#else + if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - m_cursor_radius) > EPSILON) { + m_old_cursor_radius = m_cursor_radius; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_old_center = center; + m_circle.reset(); + + GLModel::Geometry init_data; + static const unsigned int StepsCount = 32; + static const float StepSize = 2.0f * float(PI) / float(StepsCount); + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; + init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f }; + init_data.reserve_vertices(StepsCount); + init_data.reserve_indices(StepsCount); + + // vertices + indices + for (unsigned int i = 0; i < StepsCount; ++i) { + const float angle = float(i) * StepSize; +#if ENABLE_GL_SHADERS_ATTRIBUTES + init_data.add_vertex(Vec2f(2.0f * ((center.x() + ::cos(angle) * radius) * cnv_inv_width - 0.5f), + -2.0f * ((center.y() + ::sin(angle) * radius) * cnv_inv_height - 0.5f))); +#else + init_data.add_vertex(Vec2f(center.x() + ::cos(angle) * m_cursor_radius, center.y() + ::sin(angle) * m_cursor_radius)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + init_data.add_index(i); + } + + m_circle.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = GUI::wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_circle.render(); + shader->stop_using(); + } +#else + ::glBegin(GL_LINE_LOOP); + for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) + ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glPopAttrib()); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glEnable(GL_DEPTH_TEST)); +} + + +void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const +{ + if (s_sphere == nullptr) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + s_sphere = std::make_shared(); + s_sphere->init_from(its_make_sphere(1.0, double(PI) / 12.0)); +#else + s_sphere = std::make_shared(); + s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0)); + s_sphere->finalize_geometry(true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_scaling_factor_matrix().inverse(); +#else + const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo.data())); + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + glsafe(::glTranslatef(m_rr.hit.x(), m_rr.hit.y(), m_rr.hit.z())); + glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data())); + glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + if (is_left_handed) + glFrontFace(GL_CW); + + ColorRGBA render_color = { 0.0f, 0.0f, 0.0f, 0.25f }; + if (m_button_down == Button::Left) + render_color = this->get_cursor_sphere_left_button_color(); + else if (m_button_down == Button::Right) + render_color = this->get_cursor_sphere_right_button_color(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->start_using(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * trafo * + Geometry::assemble_transform(m_rr.hit.cast()) * complete_scaling_matrix_inverse * + Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), m_cursor_radius * Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + assert(s_sphere != nullptr); + s_sphere->set_color(render_color); +#else + glsafe(::glColor4fv(render_color.data())); + + assert(s_sphere != nullptr); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + s_sphere->render(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + if (is_left_handed) + glFrontFace(GL_CCW); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + + +bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const +{ + if (m_c->object_clipper()->get_position() == 0.) + return false; + + auto sel_info = m_c->selection_info(); + Vec3d transformed_point = trafo * point; + transformed_point(2) += sel_info->get_sla_shift(); + return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); +} + +// Interpolate points between the previous and current mouse positions, which are then projected onto the object. +// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector +// with the same mesh_idx, but all items in std::vector always have the same mesh_idx. +std::vector> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector &trafo_matrices) const +{ + // List of mouse positions that will be used as seeds for painting. + std::vector mouse_positions{mouse_position}; + if (m_last_mouse_click != Vec2d::Zero()) { + // In case current mouse position is far from the last one, + // add several positions from between into the list, so there + // are no gaps in the painted region. + if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) { + const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1); + for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx) + mouse_positions.emplace_back(mouse_position + patch_idx * diff); + mouse_positions.emplace_back(m_last_mouse_click); + } + } + + const Camera &camera = wxGetApp().plater()->get_camera(); + std::vector mesh_hit_points; + mesh_hit_points.reserve(mouse_positions.size()); + + // In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't. + for (const Vec2d &mp : mouse_positions) { + update_raycast_cache(mp, camera, trafo_matrices); + mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet}); + if (m_rr.mesh_id == -1) + break; + } + + // Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx. + std::vector> mesh_hit_points_by_mesh; + for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) { + size_t next_mesh_hit_point = curr_mesh_hit_point + 1; + if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) { + mesh_hit_points_by_mesh.emplace_back(); + mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point)); + prev_mesh_hit_point = next_mesh_hit_point; + } + } + + auto on_same_facet = [](std::vector &hit_points) -> bool { + for (const ProjectedMousePosition &mesh_hit_point : hit_points) + if (mesh_hit_point.facet_idx != hit_points.front().facet_idx) + return false; + return true; + }; + + struct Plane + { + Vec3d origin; + Vec3d first_axis; + Vec3d second_axis; + }; + auto find_plane = [](std::vector &hit_points) -> std::optional { + assert(hit_points.size() >= 3); + for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) { + const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast(); + const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast(); + const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast(); + + const Vec3d first_vec = first_point - second_point; + const Vec3d second_vec = third_point - second_point; + + // If three points aren't collinear, then there exists only one plane going through all points. + if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) { + const Vec3d first_axis_vec_n = first_vec.normalized(); + // Make second_vec perpendicular to first_axis_vec_n using Gram–Schmidt orthogonalization process + const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized(); + return Plane{second_point, first_axis_vec_n, second_axis_vec_n}; + } + } + + return std::nullopt; + }; + + for(std::vector &hit_points : mesh_hit_points_by_mesh) { + assert(!hit_points.empty()); + if (hit_points.back().mesh_idx == -1) + break; + + if (hit_points.size() <= 2) + continue; + + if (on_same_facet(hit_points)) { + hit_points = {hit_points.front(), hit_points.back()}; + } else if (std::optional plane = find_plane(hit_points); plane) { + Polyline polyline; + polyline.points.reserve(hit_points.size()); + // Project hit_points into its plane to simplified them in the next step. + for (auto &hit_point : hit_points) { + const Vec3d &point = hit_point.mesh_hit.cast(); + const double x_cord = plane->first_axis.dot(point - plane->origin); + const double y_cord = plane->second_axis.dot(point - plane->origin); + polyline.points.emplace_back(scale_(x_cord), scale_(y_cord)); + } + + polyline.simplify(scale_(m_cursor_radius) / 10.); + + const int mesh_idx = hit_points.front().mesh_idx; + std::vector new_hit_points; + new_hit_points.reserve(polyline.points.size()); + // Project 2D simplified hit_points beck to 3D. + for (const Point &point : polyline.points) { + const double x_cord = unscale(point.x()); + const double y_cord = unscale(point.y()); + const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis; + const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast()); + new_hit_points.push_back({new_hit_point.cast(), mesh_idx, size_t(facet_idx)}); + } + + hit_points = new_hit_points; + } else { + hit_points = {hit_points.front(), hit_points.back()}; + } + } + + return mesh_hit_points_by_mesh; +} + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (action == SLAGizmoEventType::MouseWheelUp + || action == SLAGizmoEventType::MouseWheelDown) { + if (control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = action == SLAGizmoEventType::MouseWheelDown + ? std::max(0., pos - 0.01) + : std::min(1., pos + 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + else if (alt_down) { + if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) { + m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - this->get_cursor_radius_step(), this->get_cursor_radius_min()) + : std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max()); + m_parent.set_as_dirty(); + return true; + } else if (m_tool_type == ToolType::SMART_FILL) { + m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin) + : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); + m_parent.set_as_dirty(); + if (m_rr.mesh_id != -1) { + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); +#else + const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + 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; + } + return true; + } + + return false; + } + } + + if (action == SLAGizmoEventType::ResetClippingPlane) { + m_c->object_clipper()->set_position(-1., false); + return true; + } + + if (action == SLAGizmoEventType::LeftDown + || action == SLAGizmoEventType::RightDown + || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { + + if (m_triangle_selectors.empty()) + return false; + + EnforcerBlockerType new_state = EnforcerBlockerType::NONE; + if (! shift_down) { + if (action == SLAGizmoEventType::Dragging) + new_state = m_button_down == Button::Left ? this->get_left_button_state_type() : this->get_right_button_state_type(); + else + new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type(); + } + + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset(); +#else + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + std::vector trafo_matrices_not_translate; + for (const ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset()); +#else + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + } + + std::vector> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices); + m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved + + for (const std::vector &projected_mouse_positions : projected_mouse_positions_by_mesh) { + assert(!projected_mouse_positions.empty()); + const int mesh_idx = projected_mouse_positions.front().mesh_idx; + const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); + + // The mouse button click detection is enabled when there is a valid hit. + // Missing the object entirely + // shall not capture the mouse. + if (mesh_idx != -1) + if (m_button_down == Button::None) + m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); + + // In case we have no valid hit, we can return. The event will be stopped when + // dragging while painting (to prevent scene rotations and moving the object) + if (mesh_idx == -1) + return dragging_while_painting; + + const Transform3d &trafo_matrix = trafo_matrices[mesh_idx]; + const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx]; + + // Calculate direction from camera to the hit (in mesh coords): + Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); + + assert(mesh_idx < 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)) { + for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) { + assert(projected_mouse_position.mesh_idx == mesh_idx); + const Vec3f mesh_hit = projected_mouse_position.mesh_hit; + const int facet_idx = int(projected_mouse_position.facet_idx); + m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state); + if (m_tool_type == ToolType::SMART_FILL) + m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, 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[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true); + else if (m_tool_type == ToolType::BUCKET_FILL) + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true); + + m_seed_fill_last_mesh_id = -1; + } + } else if (m_tool_type == ToolType::BRUSH) { + assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE); + + if (projected_mouse_positions.size() == 1) { + const ProjectedMousePosition &first_position = projected_mouse_positions.front(); + std::unique_ptr cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit, + camera_pos, m_cursor_radius, + m_cursor_type, trafo_matrix, clp); + m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, + m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + } else { + for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) { + auto second_position_it = first_position_it + 1; + std::unique_ptr cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp); + m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + } + } + } + + m_triangle_selectors[mesh_idx]->request_update_render_data(); + m_last_mouse_click = mouse_position; + } + + return true; + } + + if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) { + if (m_triangle_selectors.empty()) + return false; + + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset(); +#else + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + std::vector trafo_matrices_not_translate; + for (const ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset()); +#else + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + } + + // Now "click" into all the prepared points and spill paint around them. + update_raycast_cache(mouse_position, camera, trafo_matrices); + + auto seed_fill_unselect_all = [this]() { + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + }; + + if (m_rr.mesh_id == -1) { + // Clean selected by seed fill for all triangles in all meshes when a mouse isn't pointing on any mesh. + seed_fill_unselect_all(); + m_seed_fill_last_mesh_id = -1; + + // In case we have no valid hit, we can return. + return false; + } + + // The mouse moved from one object's volume to another one. So it is needed to unselect all triangles selected by seed fill. + 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, 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), 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), 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; + } + + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) + && m_button_down != Button::None) { + // Take snapshot and update ModelVolume data. + wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name, UndoRedo::SnapshotType::GizmoAction); + update_model_object(); + + m_button_down = Button::None; + m_last_mouse_click = Vec2d::Zero(); + return true; + } + + return false; +} + +bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event) +{ + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + + if (mouse_event.Moving()) { + gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false); + return false; + } + + // when control is down we allow scene pan and rotation even when clicking + // over some object + bool control_down = mouse_event.CmdDown(); + bool grabber_contains_mouse = (get_hover_id() != -1); + + const Selection &selection = m_parent.get_selection(); + int selected_object_idx = selection.get_object_idx(); + if (mouse_event.LeftDown()) { + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + // the gizmo got the event and took some action, there is no need + // to do anything more + return true; + } else if (mouse_event.RightDown()){ + if (!control_down && selected_object_idx != -1 && + gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) + // event was taken care of + return true; + } else if (mouse_event.Dragging()) { + if (m_parent.get_move_volume_id() != -1) + // don't allow dragging objects with the Sla gizmo on + return true; + if (!control_down && gizmo_event(SLAGizmoEventType::Dragging, + mouse_pos, mouse_event.ShiftDown(), + mouse_event.AltDown(), false)) { + // the gizmo got the event and took some action, no need to do + // anything more here + m_parent.set_as_dirty(); + return true; + } + if(control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) + { + // CTRL has been pressed while already dragging -> stop current action + if (mouse_event.LeftIsDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + else if (mouse_event.RightIsDown()) + gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + return false; + } + } else if (mouse_event.LeftUp()) { + if (!m_parent.is_mouse_dragging()) { + // in case SLA/FDM gizmo is selected, we just pass the LeftUp + // event and stop processing - neither object moving or selecting + // is suppressed in that case + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); + return true; + } + } else if (mouse_event.RightUp()) { + if (!m_parent.is_mouse_dragging()) { + gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); + return true; + } + } + return false; +} + +void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position, + const Camera& camera, + const std::vector& trafo_matrices) const +{ + if (m_rr.mouse_position == mouse_position) { + // Same query as last time - the answer is already in the cache. + return; + } + + Vec3f normal = Vec3f::Zero(); + Vec3f hit = Vec3f::Zero(); + size_t facet = 0; + Vec3f closest_hit = Vec3f::Zero(); + double closest_hit_squared_distance = std::numeric_limits::max(); + size_t closest_facet = 0; + int closest_hit_mesh_id = -1; + + // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh + for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { + + if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( + mouse_position, + trafo_matrices[mesh_id], + camera, + hit, + normal, + m_c->object_clipper()->get_clipping_plane(), + &facet)) + { + // In case this hit is clipped, skip it. + if (is_mesh_point_clipped(hit.cast(), trafo_matrices[mesh_id])) + continue; + + // Is this hit the closest to the camera so far? + double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); + if (hit_squared_distance < closest_hit_squared_distance) { + closest_hit_squared_distance = hit_squared_distance; + closest_facet = facet; + closest_hit_mesh_id = mesh_id; + closest_hit = hit; + } + } + } + + m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet}; +} + +bool GLGizmoPainterBase::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF + || !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple) + return false; + + // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. + const Selection::IndicesList& list = selection.get_volume_idxs(); + return std::all_of(list.cbegin(), list.cend(), [&selection](unsigned int idx) { return !selection.get_volume(idx)->is_outside; }); +} + +bool GLGizmoPainterBase::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF + && wxGetApp().get_mode() != comSimple ); +} + + +CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const +{ + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::ObjectClipper)); +} + + +void GLGizmoPainterBase::on_set_state() +{ + if (m_state == m_old_state) + return; + + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + on_opening(); + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + // we are actually shutting down + on_shutdown(); + m_old_mo_id = -1; + //m_iva.release_geometry(); + m_triangle_selectors.clear(); + } + m_old_state = m_state; +} + + + +void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&) +{ + // We should update the gizmo from current ModelObject, but it is not + // possible at this point. That would require having updated selection and + // common gizmos data, which is not done at this point. Instead, save + // a flag to do the update in set_painter_gizmo_data, which will be called + // soon after. + 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}); +} + +ColorRGBA TriangleSelectorGUI::get_seed_fill_color(const ColorRGBA& base_color) +{ + return saturate(base_color, 0.75f); +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void TriangleSelectorGUI::render(ImGuiWrapper* imgui, const Transform3d& matrix) +#else +void TriangleSelectorGUI::render(ImGuiWrapper* imgui) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + static const ColorRGBA enforcers_color = { 0.47f, 0.47f, 1.0f, 1.0f }; + static const ColorRGBA blockers_color = { 1.0f, 0.44f, 0.44f, 1.0f }; + + if (m_update_render_data) { + update_render_data(); + m_update_render_data = false; + } + + auto* shader = wxGetApp().get_current_shader(); + if (! shader) + return; + + assert(shader->get_name() == "gouraud"); + + for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), + std::make_pair(&m_iva_blockers, blockers_color)}) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + iva.first->set_color(iva.second); + iva.first->render(); +#else + if (iva.first->has_VBOs()) { + shader->set_uniform("uniform_color", iva.second); + iva.first->render(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (auto& iva : m_iva_seed_fills) { + size_t color_idx = &iva - &m_iva_seed_fills.front(); + const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : + color_idx == 2 ? blockers_color : + GLVolume::NEUTRAL_COLOR); + iva.set_color(color); + iva.render(); + } +#else + for (auto& iva : m_iva_seed_fills) + if (iva.has_VBOs()) { + size_t color_idx = &iva - &m_iva_seed_fills.front(); + const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : + color_idx == 2 ? blockers_color : + GLVolume::NEUTRAL_COLOR); + shader->set_uniform("uniform_color", color); + iva.render(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_paint_contour(matrix); +#else + render_paint_contour(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#else + if (m_paint_contour.has_VBO()) { + ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); + shader->stop_using(); + + auto *contour_shader = wxGetApp().get_shader("mm_contour"); + contour_shader->start_using(); + contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); + m_paint_contour.render(); + contour_shader->stop_using(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + if (imgui) + render_debug(imgui); + else + assert(false); // If you want debug output, pass ptr to ImGuiWrapper. +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +} + +void TriangleSelectorGUI::update_render_data() +{ + int enf_cnt = 0; + int blc_cnt = 0; + std::vector seed_fill_cnt(m_iva_seed_fills.size(), 0); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) { + iva->reset(); + } + + for (auto& iva : m_iva_seed_fills) { + iva.reset(); + } + + GLModel::Geometry iva_enforcers_data; + iva_enforcers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + GLModel::Geometry iva_blockers_data; + iva_blockers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + std::array iva_seed_fills_data; + for (auto& data : iva_seed_fills_data) + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; +#else + for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) + iva->release_geometry(); + + for (auto &iva : m_iva_seed_fills) + iva.release_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // small value used to offset triangles along their normal to avoid z-fighting + static const float offset = 0.001f; + + for (const Triangle &tr : m_triangles) { + if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill())) + continue; + + int tr_state = int(tr.get_state()); +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data : + iva_blockers_data; +#else + GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : + m_iva_blockers; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : + blc_cnt; + const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v; + const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v; + const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v; + //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort + // or the current implementation may be more cache friendly. + const Vec3f n = (v1 - v0).cross(v2 - v1).normalized(); + // small value used to offset triangles along their normal to avoid z-fighting + const Vec3f offset_n = offset * n; +#if ENABLE_LEGACY_OPENGL_REMOVAL + iva.add_vertex(v0 + offset_n, n); + iva.add_vertex(v1 + offset_n, n); + iva.add_vertex(v2 + offset_n, n); + iva.add_triangle((unsigned int)cnt, (unsigned int)cnt + 1, (unsigned int)cnt + 2); +#else + iva.push_geometry(v0 + offset_n, n); + iva.push_geometry(v1 + offset_n, n); + iva.push_geometry(v2 + offset_n, n); + iva.push_triangle(cnt, cnt + 1, cnt + 2); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + cnt += 3; + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!iva_enforcers_data.is_empty()) + m_iva_enforcers.init_from(std::move(iva_enforcers_data)); + if (!iva_blockers_data.is_empty()) + m_iva_blockers.init_from(std::move(iva_blockers_data)); + for (size_t i = 0; i < m_iva_seed_fills.size(); ++i) { + if (!iva_seed_fills_data[i].is_empty()) + m_iva_seed_fills[i].init_from(std::move(iva_seed_fills_data[i])); + } + + update_paint_contour(); +#else + for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) + iva->finalize_geometry(true); + + for (auto &iva : m_iva_seed_fills) + iva.finalize_geometry(true); + + m_paint_contour.release_geometry(); + std::vector contour_edges = this->get_seed_fill_contour(); + m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); + for (const Vec2i &edge : contour_edges) { + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z()); + + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z()); + } + + m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0); + std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0); + m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); + + m_paint_contour.finalize_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +#if !ENABLE_LEGACY_OPENGL_REMOVAL +void GLPaintContour::render() const +{ + assert(this->m_contour_VBO_id != 0); + assert(this->m_contour_EBO_id != 0); + + glsafe(::glLineWidth(4.0f)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + + if (this->contour_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +void GLPaintContour::finalize_geometry() +{ + assert(this->m_contour_VBO_id == 0); + assert(this->m_contour_EBO_id == 0); + + if (!this->contour_vertices.empty()) { + glsafe(::glGenBuffers(1, &this->m_contour_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + this->contour_vertices.clear(); + } + + if (!this->contour_indices.empty()) { + glsafe(::glGenBuffers(1, &this->m_contour_EBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + this->contour_indices.clear(); + } +} + +void GLPaintContour::release_geometry() +{ + if (this->m_contour_VBO_id) { + glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id)); + this->m_contour_VBO_id = 0; + } + if (this->m_contour_EBO_id) { + glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id)); + this->m_contour_EBO_id = 0; + } + this->clear(); +} +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) +{ + imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + imgui->text("Edge limit (mm): "); + imgui->slider_float("", &edge_limit, 0.1f, 8.f); + set_edge_limit(edge_limit); + imgui->checkbox("Show split triangles: ", m_show_triangles); + imgui->checkbox("Show invalid triangles: ", m_show_invalid); + + int valid_triangles = m_triangles.size() - m_invalid_triangles; + imgui->text("Valid triangles: " + std::to_string(valid_triangles) + + "/" + std::to_string(m_triangles.size())); + imgui->text("Vertices: " + std::to_string(m_vertices.size())); + if (imgui->button("Force garbage collection")) + garbage_collect(); + + if (imgui->button("Serialize - deserialize")) { + auto map = serialize(); + deserialize(map); + } + + imgui->end(); + + if (! m_show_triangles) + return; + + enum vtype { + ORIGINAL = 0, + SPLIT, + INVALID + }; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (auto& va : m_varrays) + va.reset(); +#else + for (auto& va : m_varrays) + va.release_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + std::array cnts; + + ::glScalef(1.01f, 1.01f, 1.01f); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + std::array varrays_data; + for (auto& data : varrays_data) + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + for (int tr_id=0; tr_idadd_vertex(m_vertices[tr.verts_idxs[i]].v, Vec3f(0.0f, 0.0f, 1.0f)); + } + va->add_uint_triangle((unsigned int)*cnt, (unsigned int)*cnt + 1, (unsigned int)*cnt + 2); +#else + for (int i = 0; i < 3; ++i) + va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va->push_triangle(*cnt, + *cnt + 1, + *cnt + 2); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + *cnt += 3; + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (int i = 0; i < 3; ++i) { + if (!varrays_data[i].is_empty()) + m_varrays[i].init_from(std::move(varrays_data[i])); + } +#else +// for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) +// iva->finalize_geometry(true); +// +// for (auto& iva : m_iva_seed_fills) +// iva.finalize_geometry(true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + for (vtype i : {ORIGINAL, SPLIT, INVALID}) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLModel& va = m_varrays[i]; + switch (i) { + case ORIGINAL: va.set_color({ 0.0f, 0.0f, 1.0f, 1.0f }); break; + case SPLIT: va.set_color({ 1.0f, 0.0f, 0.0f, 1.0f }); break; + case INVALID: va.set_color({ 1.0f, 1.0f, 0.0f, 1.0f }); break; + } + va.render(); +#else + GLIndexedVertexArray& va = m_varrays[i]; + va.finalize_geometry(true); + if (va.has_VBOs()) { + switch (i) { + case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; + case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; + case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; + } + va.render(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void TriangleSelectorGUI::update_paint_contour() +{ + m_paint_contour.reset(); + + GLModel::Geometry init_data; + const std::vector contour_edges = this->get_seed_fill_contour(); + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * contour_edges.size()); + init_data.reserve_indices(2 * contour_edges.size()); +#if ENABLE_GL_SHADERS_ATTRIBUTES + init_data.color = ColorRGBA::WHITE(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +// + // vertices + indices + unsigned int vertices_count = 0; + for (const Vec2i& edge : contour_edges) { + init_data.add_vertex(m_vertices[edge(0)].v); + init_data.add_vertex(m_vertices[edge(1)].v); + vertices_count += 2; + init_data.add_line(vertices_count - 2, vertices_count - 1); + } + + if (!init_data.is_empty()) + m_paint_contour.init_from(std::move(init_data)); +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void TriangleSelectorGUI::render_paint_contour(const Transform3d& matrix) +#else +void TriangleSelectorGUI::render_paint_contour() +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + auto* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + auto* contour_shader = wxGetApp().get_shader("mm_contour"); + if (contour_shader != nullptr) { + contour_shader->start_using(); + + contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + contour_shader->set_uniform("view_model_matrix", camera.get_view_matrix() * matrix); + contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_paint_contour.render(); + contour_shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +} +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index c9947ee18..6579a1431 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -190,11 +190,7 @@ void GLGizmoRotate::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader != nullptr) { shader->start_using(); @@ -298,7 +294,12 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box = v.transformed_convex_hull_bounding_box( + v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); +#else m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_center = v.world_matrix() * m_bounding_box.center(); } else { @@ -308,8 +309,13 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) const GLVolume& v = *selection.get_volume(id); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center(); +#else m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } m_radius = Offset + m_bounding_box.radius(); @@ -322,11 +328,19 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_orient_matrix = Transform3d::Identity(); else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); +#else m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_orient_matrix = v.get_instance_transformation().get_rotation_matrix(); +#else m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } #endif // ENABLE_WORLD_COORDINATE @@ -656,11 +670,7 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick const double size = m_dragging ? double(m_grabbers.front().get_dragging_half_size(mean_size)) : double(m_grabbers.front().get_half_size(mean_size)); #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat_attr" : "gouraud_light_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -739,12 +749,12 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const { case X: { - ret = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.5 * PI, 0.0)) * Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, -0.5 * PI)); + ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); break; } case Y: { - ret = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, -0.5 * PI)) * Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, -0.5 * PI, 0.0)); + ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY()); break; } default: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 36e5b0217..05df5f193 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -219,14 +219,24 @@ void GLGizmoScale3D::on_render() } #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_scaling_factor_matrix()); +#else m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #endif // ENABLE_WORLD_COORDINATE // gets transform from first selected volume const GLVolume& v = *selection.get_volume(*idxs.begin()); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor(); + m_grabbers_transform = inst_trafo * Geometry::assemble_transform(m_bounding_box.center()); + m_center = inst_trafo * m_bounding_box.center(); +#else m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_instance_center = v.get_instance_offset(); } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { @@ -243,8 +253,14 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box.merge(v.transformed_convex_hull_bounding_box( + v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_matrix_no_offset())); + Geometry::Transformation trafo(v.get_instance_transformation().get_rotation_matrix()); +#else m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true))); Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); @@ -252,8 +268,14 @@ void GLGizmoScale3D::on_render() } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box.merge(v.transformed_convex_hull_bounding_box( + v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix())); + Geometry::Transformation trafo(v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix()); +#else m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); @@ -352,8 +374,15 @@ void GLGizmoScale3D::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = local_transform(selection); + for (int i = 0; i < 10; ++i) { + m_grabbers[i].matrix = base_matrix; + } +#else glsafe(::glPushMatrix()); transform_to_local(selection); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0); #else @@ -557,14 +586,23 @@ void GLGizmoScale3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = local_transform(m_parent.get_selection()); + for (int i = 0; i < 10; ++i) { + m_grabbers[i].matrix = base_matrix; + } +#else glsafe(::glPushMatrix()); transform_to_local(m_parent.get_selection()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_for_picking(m_bounding_box); +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #else render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); #endif // ENABLE_WORLD_COORDINATE -} + } #if ENABLE_LEGACY_OPENGL_REMOVAL void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color) @@ -773,6 +811,28 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const } #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES +Transform3d GLGizmoScale3D::local_transform(const Selection& selection) const +{ + Transform3d ret = Geometry::assemble_transform(m_center); + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); +#else + Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); +#else + orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + ret = ret * orient_matrix; + } + return ret; +} +#else void GLGizmoScale3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); @@ -784,6 +844,7 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const glsafe(::glMultMatrixd(orient_matrix.data())); } } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_WORLD_COORDINATE } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index e279e53e2..b8001d7a9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -107,7 +107,11 @@ private: double calc_ratio(const UpdateData& data) const; #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + Transform3d local_transform(const Selection& selection) const; +#else void transform_to_local(const Selection& selection) const; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_WORLD_COORDINATE }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 7c61673b4..f72ce3206 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1,1370 +1,1374 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoSlaSupports.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" -#include "slic3r/GUI/MainFrame.hpp" -#include "slic3r/Utils/UndoRedo.hpp" - -#include - -#include -#include -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/GUI_ObjectSettings.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/NotificationManager.hpp" -#include "slic3r/GUI/MsgDialog.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/SLAPrint.hpp" - - -namespace Slic3r { -namespace GUI { - -GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{} - -bool GLGizmoSlaSupports::on_init() -{ - m_shortcut_key = WXK_CONTROL_L; - - m_desc["head_diameter"] = _L("Head diameter") + ": "; - m_desc["lock_supports"] = _L("Lock supports under new islands"); - m_desc["remove_selected"] = _L("Remove selected points"); - m_desc["remove_all"] = _L("Remove all points"); - m_desc["apply_changes"] = _L("Apply changes"); - m_desc["discard_changes"] = _L("Discard changes"); - m_desc["minimal_distance"] = _L("Minimal points distance") + ": "; - m_desc["points_density"] = _L("Support points density") + ": "; - m_desc["auto_generate"] = _L("Auto-generate points"); - m_desc["manual_editing"] = _L("Manual editing"); - m_desc["clipping_of_view"] = _L("Clipping of view")+ ": "; - m_desc["reset_direction"] = _L("Reset direction"); - - m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24)); - m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.)); - m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.)); - - return true; -} - -void GLGizmoSlaSupports::data_changed() -{ - if (! m_c->selection_info()) - return; - - ModelObject* mo = m_c->selection_info()->model_object(); - - if (m_state == On && mo && mo->id() != m_old_mo_id) { - disable_editing_mode(); - reload_cache(); - m_old_mo_id = mo->id(); - m_c->instances_hider()->show_supports(true); - } - - // If we triggered autogeneration before, check backend and fetch results if they are there - if (mo) { - if (mo->sla_points_status == sla::PointsStatus::Generating) - get_data_from_backend(); - } -} - - - -void GLGizmoSlaSupports::on_render() -{ - if (!m_cone.is_initialized()) - m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); - if (!m_sphere.is_initialized()) - m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); - if (!m_cylinder.is_initialized()) - m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0)); - - ModelObject* mo = m_c->selection_info()->model_object(); - const Selection& selection = m_parent.get_selection(); - - // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off - if (m_state == On - && (mo != selection.get_model()->objects[selection.get_object_idx()] - || m_c->selection_info()->get_active_instance() != selection.get_instance_idx())) { - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); - return; - } - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - if (selection.is_from_single_instance()) - render_points(selection, false); - - m_selection_rectangle.render(m_parent); - m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); - - glsafe(::glDisable(GL_BLEND)); -} - - -void GLGizmoSlaSupports::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); - //glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} - -void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) -{ - const size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); - - const bool has_points = (cache_size != 0); - const bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh() - && ! m_c->selection_info()->model_object()->sla_drain_holes.empty()); - - if (! has_points && ! has_holes) - return; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); - ScopeGuard guard([shader]() { shader->stop_using(); }); -#else - GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); - if (shader != nullptr) - shader->start_using(); - ScopeGuard guard([shader]() { - if (shader != nullptr) - shader->stop_using(); - }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix()); - const Transform3d& instance_scaling_matrix_inverse = transformation.get_matrix(true, true, false, true).inverse(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * transformation.get_matrix(); - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d& view_matrix = camera.get_view_matrix(); - const Transform3d& projection_matrix = camera.get_projection_matrix(); - - shader->set_uniform("projection_matrix", projection_matrix); -#else - const Transform3d& instance_matrix = transformation.get_matrix(); - const float z_shift = m_c->selection_info()->get_sla_shift(); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, z_shift)); - glsafe(::glMultMatrixd(instance_matrix.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - ColorRGBA render_color; - for (size_t i = 0; i < cache_size; ++i) { - const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; - const bool point_selected = m_editing_mode ? m_editing_cache[i].selected : false; - - if (is_mesh_point_clipped(support_point.pos.cast())) - continue; - - // First decide about the color of the point. - if (picking) - render_color = picking_color_component(i); - else { - if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active - render_color = { 0.f, 1.f, 1.f, 1.f }; - else { // neigher hover nor picking - bool supports_new_island = m_lock_unique_islands && support_point.is_new_island; - if (m_editing_mode) { - if (point_selected) - render_color = { 1.f, 0.3f, 0.3f, 1.f}; - else - if (supports_new_island) - render_color = { 0.3f, 0.3f, 1.f, 1.f }; - else - render_color = { 0.7f, 0.7f, 0.7f, 1.f }; - } - else - render_color = { 0.5f, 0.5f, 0.5f, 1.f }; - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_cone.set_color(render_color); - m_sphere.set_color(render_color); - if (!picking) -#else - m_cone.set_color(-1, render_color); - m_sphere.set_color(-1, render_color); - if (shader && !picking) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - shader->set_uniform("emission_factor", 0.5f); - - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d support_matrix = Geometry::assemble_transform(support_point.pos.cast()) * instance_scaling_matrix_inverse; -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(support_point.pos.x(), support_point.pos.y(), support_point.pos.z())); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (vol->is_left_handed()) - glFrontFace(GL_CW); - - // Matrices set, we can render the point mark now. - // If in editing mode, we'll also render a cone pointing to the sphere. - if (m_editing_mode) { - // in case the normal is not yet cached, find and cache it - if (m_editing_cache[i].normal == Vec3f::Zero()) - m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); - - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); - const Eigen::AngleAxisd aa(q); - const double cone_radius = 0.25; // mm - const double cone_height = 0.75; -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * Transform3d(aa.toRotationMatrix()) * - Geometry::assemble_transform((cone_height + support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ(), - Vec3d(PI, 0.0, 0.0), Vec3d(cone_radius, cone_radius, cone_height)); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glPushMatrix()); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); - glsafe(::glTranslatef(0.f, 0.f, cone_height + support_point.head_front_radius * RenderPointScale)); - glsafe(::glRotated(180., 1., 0., 0.)); - glsafe(::glScaled(cone_radius, cone_radius, cone_height)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_cone.render(); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - - const double radius = (double)support_point.head_front_radius * RenderPointScale; -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * - Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), radius * Vec3d::Ones()); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glPushMatrix()); - glsafe(::glScaled(radius, radius, radius)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_sphere.render(); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - if (vol->is_left_handed()) - glFrontFace(GL_CCW); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - - // Now render the drain holes: - if (has_holes && ! picking) { - render_color = { 0.7f, 0.7f, 0.7f, 0.7f }; -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_cylinder.set_color(render_color); -#else - m_cylinder.set_color(-1, render_color); - if (shader != nullptr) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - shader->set_uniform("emission_factor", 0.5f); - for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { - if (is_mesh_point_clipped(drain_hole.pos.cast())) - continue; - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; -#else - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (vol->is_left_handed()) - glFrontFace(GL_CW); - - // Matrices set, we can render the point mark now. - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); - const Eigen::AngleAxisd aa(q); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * - Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); - glsafe(::glTranslated(0., 0., -drain_hole.height)); - glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_cylinder.render(); - - if (vol->is_left_handed()) - glFrontFace(GL_CCW); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - } - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - - - -bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const -{ - if (m_c->object_clipper()->get_position() == 0.) - return false; - - auto sel_info = m_c->selection_info(); - int active_inst = m_c->selection_info()->get_active_instance(); - const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; - const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); - - Vec3d transformed_point = trafo * point; - transformed_point(2) += sel_info->get_sla_shift(); - return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); -} - - - -// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal -// Return false if no intersection was found, true otherwise. -bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) -{ - if (! m_c->raycaster()->raycaster()) - return false; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - - double clp_dist = m_c->object_clipper()->get_position(); - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - - // The raycaster query - Vec3f hit; - Vec3f normal; - if (m_c->raycaster()->raycaster()->unproject_on_mesh( - mouse_pos, - trafo.get_matrix(), - camera, - hit, - normal, - clp_dist != 0. ? clp : nullptr)) - { - // Check whether the hit is in a hole - bool in_hole = false; - // In case the hollowed and drilled mesh is available, we can allow - // placing points in holes, because they should never end up - // on surface that's been drilled away. - if (! m_c->hollowed_mesh()->get_hollowed_mesh()) { - sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - for (const sla::DrainHole& hole : drain_holes) { - if (hole.is_inside(hit)) { - in_hole = true; - break; - } - } - } - if (! in_hole) { - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; - } - } - - return false; -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) -{ - ModelObject* mo = m_c->selection_info()->model_object(); - int active_inst = m_c->selection_info()->get_active_instance(); - - if (m_editing_mode) { - - // left down with shift - show the selection rectangle: - if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { - if (m_hover_id == -1) { - if (shift_down || alt_down) { - m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); - } - } - else { - if (m_editing_cache[m_hover_id].selected) - unselect_point(m_hover_id); - else { - if (!alt_down) - select_point(m_hover_id); - } - } - - return true; - } - - // left down without selection rectangle - place point on the mesh: - if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { - // If any point is in hover state, this should initiate its move - return control back to GLCanvas: - if (m_hover_id != -1) - return false; - - // If there is some selection, don't add new point and deselect everything instead. - if (m_selection_empty) { - std::pair pos_and_normal; - if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point")); - m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); - m_parent.set_as_dirty(); - m_wait_for_up_event = true; - } - else - return false; - } - else - select_point(NoPoints); - - return true; - } - - // left up with selection rectangle - select points inside the rectangle: - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { - // Is this a selection or deselection rectangle? - GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); - - // First collect positions of all the points in world coordinates. - Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - std::vector points; - for (unsigned int i=0; i()); - - // Now ask the rectangle which of the points are inside. - std::vector points_inside; - std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); - for (size_t idx : points_idxs) - points_inside.push_back(points[idx].cast()); - - // Only select/deselect points that are actually visible. We want to check not only - // the point itself, but also the center of base of its cone, so the points don't hide - // under every miniature irregularity on the model. Remember the actual number and - // append the cone bases. - size_t orig_pts_num = points_inside.size(); - for (size_t idx : points_idxs) - points_inside.emplace_back((trafo.get_matrix().cast() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast()); - - for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( - trafo, wxGetApp().plater()->get_camera(), points_inside, - m_c->object_clipper()->get_clipping_plane())) - { - if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to - idx -= orig_pts_num; - if (rectangle_status == GLSelectionRectangle::EState::Deselect) - unselect_point(points_idxs[idx]); - else - select_point(points_idxs[idx]); - } - return true; - } - - // left up with no selection rectangle - if (action == SLAGizmoEventType::LeftUp) { - if (m_wait_for_up_event) { - m_wait_for_up_event = false; - return true; - } - } - - // dragging the selection rectangle: - if (action == SLAGizmoEventType::Dragging) { - if (m_wait_for_up_event) - return true; // point has been placed and the button not released yet - // this prevents GLCanvas from starting scene rotation - - if (m_selection_rectangle.is_dragging()) { - m_selection_rectangle.dragging(mouse_position); - return true; - } - - return false; - } - - if (action == SLAGizmoEventType::Delete) { - // delete key pressed - delete_selected_points(); - return true; - } - - if (action == SLAGizmoEventType::ApplyChanges) { - editing_mode_apply_changes(); - return true; - } - - if (action == SLAGizmoEventType::DiscardChanges) { - ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, - [this](){ editing_mode_discard_changes(); }); - return true; - } - - if (action == SLAGizmoEventType::RightDown) { - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - delete_selected_points(); - return true; - } - return false; - } - - if (action == SLAGizmoEventType::SelectAll) { - select_point(AllPoints); - return true; - } - } - - if (!m_editing_mode) { - if (action == SLAGizmoEventType::AutomaticGeneration) { - auto_generate(); - return true; - } - - if (action == SLAGizmoEventType::ManualEditing) { - switch_to_editing_mode(); - return true; - } - } - - if (action == SLAGizmoEventType::MouseWheelUp && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelDown && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::max(0., pos - 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); - return true; - } - - return false; -} - -void GLGizmoSlaSupports::delete_selected_points(bool force) -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: delete_selected_points called out of editing mode!" << std::endl; - std::abort(); - } - - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point")); - - for (unsigned int idx=0; idx GLGizmoSlaSupports::get_config_options(const std::vector& keys) const -{ - std::vector out; - const ModelObject* mo = m_c->selection_info()->model_object(); - - if (! mo) - return out; - - const DynamicPrintConfig& object_cfg = mo->config.get(); - const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - std::unique_ptr default_cfg = nullptr; - - for (const std::string& key : keys) { - if (object_cfg.has(key)) - out.push_back(object_cfg.option(key)); - else - if (print_cfg.has(key)) - out.push_back(print_cfg.option(key)); - else { // we must get it from defaults - if (default_cfg == nullptr) - default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); - out.push_back(default_cfg->option(key)); - } - } - - return out; -} - - - -/* -void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& idxs) const -{ - if (aabb->is_leaf()) { // this is a facet - // corner.dot(normal) - offset - idxs.push_back(aabb->m_primitive); - } - else { // not a leaf - using CornerType = Eigen::AlignedBox::CornerType; - bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0)))); - for (unsigned int i=1; i<8; ++i) - if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) { - find_intersecting_facets(aabb->m_left, normal, offset, idxs); - find_intersecting_facets(aabb->m_right, normal, offset, idxs); - } - } -} - - - -void GLGizmoSlaSupports::make_line_segments() const -{ - TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh); - Vec3f normal(0.f, 1.f, 1.f); - double d = 0.; - - std::vector lines; - find_intersections(&m_AABB, normal, d, lines); - ExPolygons expolys; - tms.make_expolygons_simple(lines, &expolys); - - SVG svg("slice_loops.svg", get_extents(expolys)); - svg.draw(expolys); - //for (const IntersectionLine &l : lines[i]) - // svg.draw(l, "red", 0); - //svg.draw_outline(expolygons, "black", "blue", 0); - svg.Close(); -} -*/ - - -void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit) -{ - static float last_y = 0.0f; - static float last_h = 0.0f; - - ModelObject* mo = m_c->selection_info()->model_object(); - - if (! mo) - return; - - bool first_run = true; // This is a hack to redraw the button when all points are removed, - // so it is not delayed until the background process finishes. -RENDER_AGAIN: - //m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - //const ImVec2 window_size(m_imgui->scaled(18.f, 16.f)); - //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); - //ImGui::SetNextWindowSize(ImVec2(window_size)); - - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // adjust window position to avoid overlap the view toolbar - float win_h = ImGui::GetWindowHeight(); - y = std::min(y, bottom_limit - win_h); - ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if ((last_h != win_h) || (last_y != y)) - { - // ask canvas for another frame to render the window in the correct position - m_imgui->set_requires_extra_frame(); - if (last_h != win_h) - last_h = win_h; - if (last_y != y) - last_y = y; - } - - // 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("minimal_distance")).x, m_imgui->calc_text_size(m_desc.at("points_density")).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(1.5f); - const float diameter_slider_left = m_imgui->calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f); - const float minimal_slider_width = m_imgui->scaled(4.f); - const float buttons_width_approx = m_imgui->calc_text_size(m_desc.at("apply_changes")).x + m_imgui->calc_text_size(m_desc.at("discard_changes")).x + m_imgui->scaled(1.5f); - const float lock_supports_width_approx = m_imgui->calc_text_size(m_desc.at("lock_supports")).x + m_imgui->scaled(2.f); - - float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left); - window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx); - - bool force_refresh = false; - bool remove_selected = false; - bool remove_all = false; - - if (m_editing_mode) { - - float diameter_upper_cap = static_cast(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value; - if (m_new_point_head_diameter > diameter_upper_cap) - m_new_point_head_diameter = diameter_upper_cap; - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("head_diameter")); - ImGui::SameLine(diameter_slider_left); - ImGui::PushItemWidth(window_width - diameter_slider_left); - - // Following is a nasty way to: - // - save the initial value of the slider before one starts messing with it - // - 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("##head_diameter", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); - if (m_imgui->get_last_slider_status().clicked) { - if (m_old_point_head_diameter == 0.f) - m_old_point_head_diameter = initial_value; - } - if (m_imgui->get_last_slider_status().edited) { - for (auto& cache_entry : m_editing_cache) - if (cache_entry.selected) - cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; - } - if (m_imgui->get_last_slider_status().deactivated_after_edit) { - // momentarily restore the old value to take snapshot - for (auto& cache_entry : m_editing_cache) - if (cache_entry.selected) - cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; - float backup = m_new_point_head_diameter; - m_new_point_head_diameter = m_old_point_head_diameter; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change point head diameter")); - m_new_point_head_diameter = backup; - for (auto& cache_entry : m_editing_cache) - if (cache_entry.selected) - cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; - m_old_point_head_diameter = 0.f; - } - - bool changed = m_lock_unique_islands; - m_imgui->checkbox(m_desc.at("lock_supports"), m_lock_unique_islands); - force_refresh |= changed != m_lock_unique_islands; - - m_imgui->disabled_begin(m_selection_empty); - remove_selected = m_imgui->button(m_desc.at("remove_selected")); - m_imgui->disabled_end(); - - m_imgui->disabled_begin(m_editing_cache.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); - m_imgui->disabled_end(); - - m_imgui->text(" "); // vertical gap - - if (m_imgui->button(m_desc.at("apply_changes"))) { - editing_mode_apply_changes(); - force_refresh = true; - } - ImGui::SameLine(); - bool discard_changes = m_imgui->button(m_desc.at("discard_changes")); - if (discard_changes) { - editing_mode_discard_changes(); - force_refresh = true; - } - } - else { // not in editing mode: - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("minimal_distance")); - ImGui::SameLine(settings_sliders_left); - ImGui::PushItemWidth(window_width - settings_sliders_left); - - std::vector opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); - float density = static_cast(opts[0])->value; - float minimal_point_distance = static_cast(opts[1])->value; - - m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm"); - bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider - bool slider_edited = m_imgui->get_last_slider_status().edited; // someone is dragging the slider - bool slider_released = m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("points_density")); - ImGui::SameLine(settings_sliders_left); - - m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%"); - slider_clicked |= m_imgui->get_last_slider_status().clicked; - slider_edited |= m_imgui->get_last_slider_status().edited; - slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; - - if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo - m_minimal_point_distance_stash = minimal_point_distance; - m_density_stash = density; - } - if (slider_edited) { - mo->config.set("support_points_minimal_distance", minimal_point_distance); - mo->config.set("support_points_density_relative", (int)density); - } - if (slider_released) { - mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash); - mo->config.set("support_points_density_relative", (int)m_density_stash); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change")); - mo->config.set("support_points_minimal_distance", minimal_point_distance); - mo->config.set("support_points_density_relative", (int)density); - wxGetApp().obj_list()->update_and_show_object_settings_item(); - } - - bool generate = m_imgui->button(m_desc.at("auto_generate")); - - if (generate) - auto_generate(); - - ImGui::Separator(); - if (m_imgui->button(m_desc.at("manual_editing"))) - switch_to_editing_mode(); - - m_imgui->disabled_begin(m_normal_cache.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); - m_imgui->disabled_end(); - - // m_imgui->text(""); - // m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : - // (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) : - // (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) : - // (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS")))); - } - - - // Following is rendered in both editing and non-editing mode: - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - 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", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - - - if (m_imgui->button("?")) { - wxGetApp().CallAfter([]() { - SlaGizmoHelpDialog help_dlg; - help_dlg.ShowModal(); - }); - } - - m_imgui->end(); - - if (remove_selected || remove_all) { - force_refresh = false; - m_parent.set_as_dirty(); - bool was_in_editing = m_editing_mode; - if (! was_in_editing) - switch_to_editing_mode(); - if (remove_all) { - select_point(AllPoints); - delete_selected_points(true); // true - delete regardless of locked status - } - if (remove_selected) - delete_selected_points(false); // leave locked points - if (! was_in_editing) - editing_mode_apply_changes(); - - if (first_run) { - first_run = false; - goto RENDER_AGAIN; - } - } - - if (force_refresh) - m_parent.set_as_dirty(); -} - -bool GLGizmoSlaSupports::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) - return false; - - // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. - const Selection::IndicesList& list = selection.get_volume_idxs(); - for (const auto& idx : list) - if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) - return false; - - return true; -} - -bool GLGizmoSlaSupports::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); -} - -std::string GLGizmoSlaSupports::on_get_name() const -{ - return _u8L("SLA Support Points"); -} - -CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); -} - - - -void GLGizmoSlaSupports::ask_about_changes_call_after(std::function on_yes, std::function on_no) -{ - wxGetApp().CallAfter([on_yes, on_no]() { - // Following is called through CallAfter, because otherwise there was a problem - // on OSX with the wxMessageDialog being shown several times when clicked into. - MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually " - "edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL ); - int ret = dlg.ShowModal(); - if (ret == wxID_YES) - on_yes(); - else if (ret == wxID_NO) - on_no(); - }); -} - - -void GLGizmoSlaSupports::on_set_state() -{ - if (m_state == m_old_state) - return; - - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - // Set default head diameter from config. - const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; - } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable(); - if (will_ask) { - ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, - [this](){ editing_mode_discard_changes(); }); - // refuse to be turned off so the gizmo is active when the CallAfter is executed - m_state = m_old_state; - } - else { - // we are actually shutting down - disable_editing_mode(); // so it is not active next time the gizmo opens - m_old_mo_id = -1; - } - } - m_old_state = m_state; -} - - - -void GLGizmoSlaSupports::on_start_dragging() -{ - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - m_point_before_drag = m_editing_cache[m_hover_id]; - } - else - m_point_before_drag = CacheEntry(); -} - - -void GLGizmoSlaSupports::on_stop_dragging() -{ - if (m_hover_id != -1) { - CacheEntry backup = m_editing_cache[m_hover_id]; - - if (m_point_before_drag.support_point.pos != Vec3f::Zero() // some point was touched - && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected - { - m_editing_cache[m_hover_id] = m_point_before_drag; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point")); - m_editing_cache[m_hover_id] = backup; - } - } - m_point_before_drag = CacheEntry(); -} - -void GLGizmoSlaSupports::on_dragging(const UpdateData &data) -{ - assert(m_hover_id != -1); - if (!m_editing_mode) return; - if (m_editing_cache[m_hover_id].support_point.is_new_island && m_lock_unique_islands) - return; - - std::pair pos_and_normal; - if (!unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) - return; - - m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first; - m_editing_cache[m_hover_id].support_point.is_new_island = false; - m_editing_cache[m_hover_id].normal = pos_and_normal.second; -} - -void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) -{ - ar(m_new_point_head_diameter, - m_normal_cache, - m_editing_cache, - m_selection_empty - ); -} - - - -void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const -{ - ar(m_new_point_head_diameter, - m_normal_cache, - m_editing_cache, - m_selection_empty - ); -} - - - -void GLGizmoSlaSupports::select_point(int i) -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: select_point called when out of editing mode!" << std::endl; - std::abort(); - } - - if (i == AllPoints || i == NoPoints) { - for (auto& point_and_selection : m_editing_cache) - point_and_selection.selected = ( i == AllPoints ); - m_selection_empty = (i == NoPoints); - - if (i == AllPoints) - m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; - } - else { - m_editing_cache[i].selected = true; - m_selection_empty = false; - m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; - } -} - - -void GLGizmoSlaSupports::unselect_point(int i) -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: unselect_point called when out of editing mode!" << std::endl; - std::abort(); - } - - m_editing_cache[i].selected = false; - m_selection_empty = true; - for (const CacheEntry& ce : m_editing_cache) { - if (ce.selected) { - m_selection_empty = false; - break; - } - } -} - - - - -void GLGizmoSlaSupports::editing_mode_discard_changes() -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: editing_mode_discard_changes called when out of editing mode!" << std::endl; - std::abort(); - } - select_point(NoPoints); - disable_editing_mode(); -} - - - -void GLGizmoSlaSupports::editing_mode_apply_changes() -{ - // If there are no changes, don't touch the front-end. The data in the cache could have been - // taken from the backend and copying them to ModelObject would needlessly invalidate them. - disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken - - if (unsaved_changes()) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit")); - - m_normal_cache.clear(); - for (const CacheEntry& ce : m_editing_cache) - m_normal_cache.push_back(ce.support_point); - - ModelObject* mo = m_c->selection_info()->model_object(); - mo->sla_points_status = sla::PointsStatus::UserModified; - mo->sla_support_points.clear(); - mo->sla_support_points = m_normal_cache; - - reslice_SLA_supports(); - } -} - - - -void GLGizmoSlaSupports::reload_cache() -{ - const ModelObject* mo = m_c->selection_info()->model_object(); - m_normal_cache.clear(); - if (mo->sla_points_status == sla::PointsStatus::AutoGenerated || mo->sla_points_status == sla::PointsStatus::Generating) - get_data_from_backend(); - else - for (const sla::SupportPoint& point : mo->sla_support_points) - m_normal_cache.emplace_back(point); -} - - -bool GLGizmoSlaSupports::has_backend_supports() const -{ - const ModelObject* mo = m_c->selection_info()->model_object(); - if (! mo) - return false; - - // find SlaPrintObject with this ID - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == mo->id()) - return po->is_step_done(slaposSupportPoints); - } - return false; -} - -void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const -{ - wxGetApp().CallAfter([this, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_supports( - *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - -bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event){ - if (mouse_event.Moving()) return false; - if (use_grabbers(mouse_event)) return true; - - // wxCoord == int --> wx/types.h - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); - Vec2d mouse_pos = mouse_coord.cast(); - - static bool pending_right_up = false; - if (mouse_event.LeftDown()) { - bool grabber_contains_mouse = (get_hover_id() != -1); - bool control_down = mouse_event.CmdDown(); - if ((!control_down || grabber_contains_mouse) && - gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) - return true; - } else if (mouse_event.Dragging()) { - bool control_down = mouse_event.CmdDown(); - if (m_parent.get_move_volume_id() != -1) { - // don't allow dragging objects with the Sla gizmo on - return true; - } else if (!control_down && - gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { - // the gizmo got the event and took some action, no need to do - // anything more here - m_parent.set_as_dirty(); - return true; - } else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())){ - // CTRL has been pressed while already dragging -> stop current action - if (mouse_event.LeftIsDown()) - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); - else if (mouse_event.RightIsDown()) - pending_right_up = false; - } - } else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) { - // in case SLA/FDM gizmo is selected, we just pass the LeftUp event - // and stop processing - neither object moving or selecting is - // suppressed in that case - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); - return true; - }else if (mouse_event.RightDown()){ - if (m_parent.get_selection().get_object_idx() != -1 && - gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { - // we need to set the following right up as processed to avoid showing - // the context menu if the user release the mouse over the object - pending_right_up = true; - // event was taken care of by the SlaSupports gizmo - return true; - } - } else if (pending_right_up && mouse_event.RightUp()) { - pending_right_up = false; - return true; - } - return false; -} - -void GLGizmoSlaSupports::get_data_from_backend() -{ - if (! has_backend_supports()) - return; - ModelObject* mo = m_c->selection_info()->model_object(); - - // find the respective SLAPrintObject, we need a pointer to it - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == mo->id()) { - m_normal_cache.clear(); - const std::vector& points = po->get_support_points(); - auto mat = (po->trafo() * po->model_object()->volumes.front()->get_transformation().get_matrix()).inverse().cast(); - for (unsigned int i=0; isla_points_status = sla::PointsStatus::AutoGenerated; - break; - } - } - - // We don't copy the data into ModelObject, as this would stop the background processing. -} - - - -void GLGizmoSlaSupports::auto_generate() -{ - //wxMessageDialog dlg(GUI::wxGetApp().plater(), - MessageDialog dlg(GUI::wxGetApp().plater(), - _L("Autogeneration will erase all manually edited points.") + "\n\n" + - _L("Are you sure you want to do it?") + "\n", - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - - ModelObject* mo = m_c->selection_info()->model_object(); - - if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); - wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); - mo->sla_points_status = sla::PointsStatus::Generating; - } -} - - - -void GLGizmoSlaSupports::switch_to_editing_mode() -{ - wxGetApp().plater()->enter_gizmos_stack(); - m_editing_mode = true; - m_editing_cache.clear(); - for (const sla::SupportPoint& sp : m_normal_cache) - m_editing_cache.emplace_back(sp); - select_point(NoPoints); - - m_c->instances_hider()->show_supports(false); - m_parent.set_as_dirty(); -} - - -void GLGizmoSlaSupports::disable_editing_mode() -{ - if (m_editing_mode) { - m_editing_mode = false; - wxGetApp().plater()->leave_gizmos_stack(); - m_c->instances_hider()->show_supports(true); - m_parent.set_as_dirty(); - } - wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode); -} - - - -bool GLGizmoSlaSupports::unsaved_changes() const -{ - if (m_editing_cache.size() != m_normal_cache.size()) - return true; - - for (size_t i=0; iSetFont(font); - - auto vsizer = new wxBoxSizer(wxVERTICAL); - auto gridsizer = new wxFlexGridSizer(2, 5, 15); - auto hsizer = new wxBoxSizer(wxHORIZONTAL); - - hsizer->AddSpacer(20); - hsizer->Add(vsizer); - hsizer->AddSpacer(20); - - vsizer->AddSpacer(20); - vsizer->Add(note_text, 1, wxALIGN_CENTRE_HORIZONTAL); - vsizer->AddSpacer(20); - vsizer->Add(gridsizer); - vsizer->AddSpacer(20); - - std::vector> shortcuts; - shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point"))); - shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point"))); - shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point"))); - shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection"))); - shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection"))); - shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle"))); - shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle"))); - shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points"))); - shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points"))); - shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane"))); - shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane"))); - shortcuts.push_back(std::make_pair("Enter", _L("Apply changes"))); - shortcuts.push_back(std::make_pair("Esc", _L("Discard changes"))); - shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode"))); - shortcuts.push_back(std::make_pair("A", _L("Auto-generate points"))); - - for (const auto& pair : shortcuts) { - auto shortcut = new wxStaticText(this, wxID_ANY, pair.first); - auto desc = new wxStaticText(this, wxID_ANY, pair.second); - shortcut->SetFont(bold_font); - desc->SetFont(font); - gridsizer->Add(shortcut, -1, wxALIGN_CENTRE_VERTICAL); - gridsizer->Add(desc, -1, wxALIGN_CENTRE_VERTICAL); - } - - SetSizer(hsizer); - hsizer->SetSizeHints(this); -} - - - -} // namespace GUI -} // namespace Slic3r +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoSlaSupports.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" +#include "slic3r/GUI/MainFrame.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +#include + +#include +#include +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_ObjectSettings.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/NotificationManager.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/SLAPrint.hpp" + + +namespace Slic3r { +namespace GUI { + +GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{} + +bool GLGizmoSlaSupports::on_init() +{ + m_shortcut_key = WXK_CONTROL_L; + + m_desc["head_diameter"] = _L("Head diameter") + ": "; + m_desc["lock_supports"] = _L("Lock supports under new islands"); + m_desc["remove_selected"] = _L("Remove selected points"); + m_desc["remove_all"] = _L("Remove all points"); + m_desc["apply_changes"] = _L("Apply changes"); + m_desc["discard_changes"] = _L("Discard changes"); + m_desc["minimal_distance"] = _L("Minimal points distance") + ": "; + m_desc["points_density"] = _L("Support points density") + ": "; + m_desc["auto_generate"] = _L("Auto-generate points"); + m_desc["manual_editing"] = _L("Manual editing"); + m_desc["clipping_of_view"] = _L("Clipping of view")+ ": "; + m_desc["reset_direction"] = _L("Reset direction"); + + m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24)); + m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.)); + m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.)); + + return true; +} + +void GLGizmoSlaSupports::data_changed() +{ + if (! m_c->selection_info()) + return; + + ModelObject* mo = m_c->selection_info()->model_object(); + + if (m_state == On && mo && mo->id() != m_old_mo_id) { + disable_editing_mode(); + reload_cache(); + m_old_mo_id = mo->id(); + m_c->instances_hider()->show_supports(true); + } + + // If we triggered autogeneration before, check backend and fetch results if they are there + if (mo) { + if (mo->sla_points_status == sla::PointsStatus::Generating) + get_data_from_backend(); + } +} + + + +void GLGizmoSlaSupports::on_render() +{ + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); + if (!m_sphere.is_initialized()) + m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0)); + + ModelObject* mo = m_c->selection_info()->model_object(); + const Selection& selection = m_parent.get_selection(); + + // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off + if (m_state == On + && (mo != selection.get_model()->objects[selection.get_object_idx()] + || m_c->selection_info()->get_active_instance() != selection.get_instance_idx())) { + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); + return; + } + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + if (selection.is_from_single_instance()) + render_points(selection, false); + + m_selection_rectangle.render(m_parent); + m_c->object_clipper()->render_cut(); + m_c->supports_clipper()->render_cut(); + + glsafe(::glDisable(GL_BLEND)); +} + + +void GLGizmoSlaSupports::on_render_for_picking() +{ + const Selection& selection = m_parent.get_selection(); + //glsafe(::glEnable(GL_DEPTH_TEST)); + render_points(selection, true); +} + +void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) +{ + const size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); + + const bool has_points = (cache_size != 0); + const bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh() + && ! m_c->selection_info()->model_object()->sla_drain_holes.empty()); + + if (! has_points && ! has_holes) + return; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); +#else + GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) + shader->start_using(); + ScopeGuard guard([shader]() { + if (shader != nullptr) + shader->stop_using(); + }); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); +#else + const Transform3d& instance_scaling_matrix_inverse = transformation.get_matrix(true, true, false, true).inverse(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * transformation.get_matrix(); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d& projection_matrix = camera.get_projection_matrix(); + + shader->set_uniform("projection_matrix", projection_matrix); +#else + const Transform3d& instance_matrix = transformation.get_matrix(); + const float z_shift = m_c->selection_info()->get_sla_shift(); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0.0, 0.0, z_shift)); + glsafe(::glMultMatrixd(instance_matrix.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + ColorRGBA render_color; + for (size_t i = 0; i < cache_size; ++i) { + const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; + const bool point_selected = m_editing_mode ? m_editing_cache[i].selected : false; + + if (is_mesh_point_clipped(support_point.pos.cast())) + continue; + + // First decide about the color of the point. + if (picking) + render_color = picking_color_component(i); + else { + if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active + render_color = { 0.f, 1.f, 1.f, 1.f }; + else { // neigher hover nor picking + bool supports_new_island = m_lock_unique_islands && support_point.is_new_island; + if (m_editing_mode) { + if (point_selected) + render_color = { 1.f, 0.3f, 0.3f, 1.f}; + else + if (supports_new_island) + render_color = { 0.3f, 0.3f, 1.f, 1.f }; + else + render_color = { 0.7f, 0.7f, 0.7f, 1.f }; + } + else + render_color = { 0.5f, 0.5f, 0.5f, 1.f }; + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_cone.set_color(render_color); + m_sphere.set_color(render_color); + if (!picking) +#else + m_cone.set_color(-1, render_color); + m_sphere.set_color(-1, render_color); + if (shader && !picking) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + shader->set_uniform("emission_factor", 0.5f); + + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d support_matrix = Geometry::assemble_transform(support_point.pos.cast()) * instance_scaling_matrix_inverse; +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(support_point.pos.x(), support_point.pos.y(), support_point.pos.z())); + glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (vol->is_left_handed()) + glFrontFace(GL_CW); + + // Matrices set, we can render the point mark now. + // If in editing mode, we'll also render a cone pointing to the sphere. + if (m_editing_mode) { + // in case the normal is not yet cached, find and cache it + if (m_editing_cache[i].normal == Vec3f::Zero()) + m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); + + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); + const Eigen::AngleAxisd aa(q); + const double cone_radius = 0.25; // mm + const double cone_height = 0.75; +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform((cone_height + support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ(), + Vec3d(PI, 0.0, 0.0), Vec3d(cone_radius, cone_radius, cone_height)); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glPushMatrix()); + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); + glsafe(::glTranslatef(0.f, 0.f, cone_height + support_point.head_front_radius * RenderPointScale)); + glsafe(::glRotated(180., 1., 0., 0.)); + glsafe(::glScaled(cone_radius, cone_radius, cone_height)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_cone.render(); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + + const double radius = (double)support_point.head_front_radius * RenderPointScale; +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * + Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), radius * Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glPushMatrix()); + glsafe(::glScaled(radius, radius, radius)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_sphere.render(); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + if (vol->is_left_handed()) + glFrontFace(GL_CCW); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + + // Now render the drain holes: + if (has_holes && ! picking) { + render_color = { 0.7f, 0.7f, 0.7f, 0.7f }; +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_cylinder.set_color(render_color); +#else + m_cylinder.set_color(-1, render_color); + if (shader != nullptr) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + shader->set_uniform("emission_factor", 0.5f); + for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { + if (is_mesh_point_clipped(drain_hole.pos.cast())) + continue; + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; +#else + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); + glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (vol->is_left_handed()) + glFrontFace(GL_CW); + + // Matrices set, we can render the point mark now. + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); + const Eigen::AngleAxisd aa(q); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); + glsafe(::glTranslated(0., 0., -drain_hole.height)); + glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_cylinder.render(); + + if (vol->is_left_handed()) + glFrontFace(GL_CCW); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + } + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + + + +bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const +{ + if (m_c->object_clipper()->get_position() == 0.) + return false; + + auto sel_info = m_c->selection_info(); + int active_inst = m_c->selection_info()->get_active_instance(); + const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; + const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); + + Vec3d transformed_point = trafo * point; + transformed_point(2) += sel_info->get_sla_shift(); + return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); +} + + + +// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal +// Return false if no intersection was found, true otherwise. +bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) +{ + if (! m_c->raycaster()->raycaster()) + return false; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); + + double clp_dist = m_c->object_clipper()->get_position(); + const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); + + // The raycaster query + Vec3f hit; + Vec3f normal; + if (m_c->raycaster()->raycaster()->unproject_on_mesh( + mouse_pos, + trafo.get_matrix(), + camera, + hit, + normal, + clp_dist != 0. ? clp : nullptr)) + { + // Check whether the hit is in a hole + bool in_hole = false; + // In case the hollowed and drilled mesh is available, we can allow + // placing points in holes, because they should never end up + // on surface that's been drilled away. + if (! m_c->hollowed_mesh()->get_hollowed_mesh()) { + sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + for (const sla::DrainHole& hole : drain_holes) { + if (hole.is_inside(hit)) { + in_hole = true; + break; + } + } + } + if (! in_hole) { + // Return both the point and the facet normal. + pos_and_normal = std::make_pair(hit, normal); + return true; + } + } + + return false; +} + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + ModelObject* mo = m_c->selection_info()->model_object(); + int active_inst = m_c->selection_info()->get_active_instance(); + + if (m_editing_mode) { + + // left down with shift - show the selection rectangle: + if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { + if (m_hover_id == -1) { + if (shift_down || alt_down) { + m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); + } + } + else { + if (m_editing_cache[m_hover_id].selected) + unselect_point(m_hover_id); + else { + if (!alt_down) + select_point(m_hover_id); + } + } + + return true; + } + + // left down without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { + // If any point is in hover state, this should initiate its move - return control back to GLCanvas: + if (m_hover_id != -1) + return false; + + // If there is some selection, don't add new point and deselect everything instead. + if (m_selection_empty) { + std::pair pos_and_normal; + if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point")); + m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); + m_parent.set_as_dirty(); + m_wait_for_up_event = true; + } + else + return false; + } + else + select_point(NoPoints); + + return true; + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { + // Is this a selection or deselection rectangle? + GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); + + // First collect positions of all the points in world coordinates. + Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); + std::vector points; + for (unsigned int i=0; i()); + + // Now ask the rectangle which of the points are inside. + std::vector points_inside; + std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); + for (size_t idx : points_idxs) + points_inside.push_back(points[idx].cast()); + + // Only select/deselect points that are actually visible. We want to check not only + // the point itself, but also the center of base of its cone, so the points don't hide + // under every miniature irregularity on the model. Remember the actual number and + // append the cone bases. + size_t orig_pts_num = points_inside.size(); + for (size_t idx : points_idxs) + points_inside.emplace_back((trafo.get_matrix().cast() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast()); + + for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( + trafo, wxGetApp().plater()->get_camera(), points_inside, + m_c->object_clipper()->get_clipping_plane())) + { + if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to + idx -= orig_pts_num; + if (rectangle_status == GLSelectionRectangle::EState::Deselect) + unselect_point(points_idxs[idx]); + else + select_point(points_idxs[idx]); + } + return true; + } + + // left up with no selection rectangle + if (action == SLAGizmoEventType::LeftUp) { + if (m_wait_for_up_event) { + m_wait_for_up_event = false; + return true; + } + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging) { + if (m_wait_for_up_event) + return true; // point has been placed and the button not released yet + // this prevents GLCanvas from starting scene rotation + + if (m_selection_rectangle.is_dragging()) { + m_selection_rectangle.dragging(mouse_position); + return true; + } + + return false; + } + + if (action == SLAGizmoEventType::Delete) { + // delete key pressed + delete_selected_points(); + return true; + } + + if (action == SLAGizmoEventType::ApplyChanges) { + editing_mode_apply_changes(); + return true; + } + + if (action == SLAGizmoEventType::DiscardChanges) { + ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, + [this](){ editing_mode_discard_changes(); }); + return true; + } + + if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + delete_selected_points(); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::SelectAll) { + select_point(AllPoints); + return true; + } + } + + if (!m_editing_mode) { + if (action == SLAGizmoEventType::AutomaticGeneration) { + auto_generate(); + return true; + } + + if (action == SLAGizmoEventType::ManualEditing) { + switch_to_editing_mode(); + return true; + } + } + + if (action == SLAGizmoEventType::MouseWheelUp && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::min(1., pos + 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + + if (action == SLAGizmoEventType::MouseWheelDown && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::max(0., pos - 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + + if (action == SLAGizmoEventType::ResetClippingPlane) { + m_c->object_clipper()->set_position(-1., false); + return true; + } + + return false; +} + +void GLGizmoSlaSupports::delete_selected_points(bool force) +{ + if (! m_editing_mode) { + std::cout << "DEBUGGING: delete_selected_points called out of editing mode!" << std::endl; + std::abort(); + } + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point")); + + for (unsigned int idx=0; idx GLGizmoSlaSupports::get_config_options(const std::vector& keys) const +{ + std::vector out; + const ModelObject* mo = m_c->selection_info()->model_object(); + + if (! mo) + return out; + + const DynamicPrintConfig& object_cfg = mo->config.get(); + const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + std::unique_ptr default_cfg = nullptr; + + for (const std::string& key : keys) { + if (object_cfg.has(key)) + out.push_back(object_cfg.option(key)); + else + if (print_cfg.has(key)) + out.push_back(print_cfg.option(key)); + else { // we must get it from defaults + if (default_cfg == nullptr) + default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); + out.push_back(default_cfg->option(key)); + } + } + + return out; +} + + + +/* +void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& idxs) const +{ + if (aabb->is_leaf()) { // this is a facet + // corner.dot(normal) - offset + idxs.push_back(aabb->m_primitive); + } + else { // not a leaf + using CornerType = Eigen::AlignedBox::CornerType; + bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0)))); + for (unsigned int i=1; i<8; ++i) + if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) { + find_intersecting_facets(aabb->m_left, normal, offset, idxs); + find_intersecting_facets(aabb->m_right, normal, offset, idxs); + } + } +} + + + +void GLGizmoSlaSupports::make_line_segments() const +{ + TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh); + Vec3f normal(0.f, 1.f, 1.f); + double d = 0.; + + std::vector lines; + find_intersections(&m_AABB, normal, d, lines); + ExPolygons expolys; + tms.make_expolygons_simple(lines, &expolys); + + SVG svg("slice_loops.svg", get_extents(expolys)); + svg.draw(expolys); + //for (const IntersectionLine &l : lines[i]) + // svg.draw(l, "red", 0); + //svg.draw_outline(expolygons, "black", "blue", 0); + svg.Close(); +} +*/ + + +void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit) +{ + static float last_y = 0.0f; + static float last_h = 0.0f; + + ModelObject* mo = m_c->selection_info()->model_object(); + + if (! mo) + return; + + bool first_run = true; // This is a hack to redraw the button when all points are removed, + // so it is not delayed until the background process finishes. +RENDER_AGAIN: + //m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + //const ImVec2 window_size(m_imgui->scaled(18.f, 16.f)); + //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); + //ImGui::SetNextWindowSize(ImVec2(window_size)); + + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // adjust window position to avoid overlap the view toolbar + float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + if ((last_h != win_h) || (last_y != y)) + { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + // 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("minimal_distance")).x, m_imgui->calc_text_size(m_desc.at("points_density")).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(1.5f); + const float diameter_slider_left = m_imgui->calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f); + const float minimal_slider_width = m_imgui->scaled(4.f); + const float buttons_width_approx = m_imgui->calc_text_size(m_desc.at("apply_changes")).x + m_imgui->calc_text_size(m_desc.at("discard_changes")).x + m_imgui->scaled(1.5f); + const float lock_supports_width_approx = m_imgui->calc_text_size(m_desc.at("lock_supports")).x + m_imgui->scaled(2.f); + + float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left); + window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx); + + bool force_refresh = false; + bool remove_selected = false; + bool remove_all = false; + + if (m_editing_mode) { + + float diameter_upper_cap = static_cast(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value; + if (m_new_point_head_diameter > diameter_upper_cap) + m_new_point_head_diameter = diameter_upper_cap; + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("head_diameter")); + ImGui::SameLine(diameter_slider_left); + ImGui::PushItemWidth(window_width - diameter_slider_left); + + // Following is a nasty way to: + // - save the initial value of the slider before one starts messing with it + // - 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("##head_diameter", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); + if (m_imgui->get_last_slider_status().clicked) { + if (m_old_point_head_diameter == 0.f) + m_old_point_head_diameter = initial_value; + } + if (m_imgui->get_last_slider_status().edited) { + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; + } + if (m_imgui->get_last_slider_status().deactivated_after_edit) { + // momentarily restore the old value to take snapshot + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; + float backup = m_new_point_head_diameter; + m_new_point_head_diameter = m_old_point_head_diameter; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change point head diameter")); + m_new_point_head_diameter = backup; + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; + m_old_point_head_diameter = 0.f; + } + + bool changed = m_lock_unique_islands; + m_imgui->checkbox(m_desc.at("lock_supports"), m_lock_unique_islands); + force_refresh |= changed != m_lock_unique_islands; + + m_imgui->disabled_begin(m_selection_empty); + remove_selected = m_imgui->button(m_desc.at("remove_selected")); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(m_editing_cache.empty()); + remove_all = m_imgui->button(m_desc.at("remove_all")); + m_imgui->disabled_end(); + + m_imgui->text(" "); // vertical gap + + if (m_imgui->button(m_desc.at("apply_changes"))) { + editing_mode_apply_changes(); + force_refresh = true; + } + ImGui::SameLine(); + bool discard_changes = m_imgui->button(m_desc.at("discard_changes")); + if (discard_changes) { + editing_mode_discard_changes(); + force_refresh = true; + } + } + else { // not in editing mode: + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("minimal_distance")); + ImGui::SameLine(settings_sliders_left); + ImGui::PushItemWidth(window_width - settings_sliders_left); + + std::vector opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); + float density = static_cast(opts[0])->value; + float minimal_point_distance = static_cast(opts[1])->value; + + m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm"); + bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider + bool slider_edited = m_imgui->get_last_slider_status().edited; // someone is dragging the slider + bool slider_released = m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("points_density")); + ImGui::SameLine(settings_sliders_left); + + m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%"); + slider_clicked |= m_imgui->get_last_slider_status().clicked; + slider_edited |= m_imgui->get_last_slider_status().edited; + slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; + + if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo + m_minimal_point_distance_stash = minimal_point_distance; + m_density_stash = density; + } + if (slider_edited) { + mo->config.set("support_points_minimal_distance", minimal_point_distance); + mo->config.set("support_points_density_relative", (int)density); + } + if (slider_released) { + mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash); + mo->config.set("support_points_density_relative", (int)m_density_stash); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change")); + mo->config.set("support_points_minimal_distance", minimal_point_distance); + mo->config.set("support_points_density_relative", (int)density); + wxGetApp().obj_list()->update_and_show_object_settings_item(); + } + + bool generate = m_imgui->button(m_desc.at("auto_generate")); + + if (generate) + auto_generate(); + + ImGui::Separator(); + if (m_imgui->button(m_desc.at("manual_editing"))) + switch_to_editing_mode(); + + m_imgui->disabled_begin(m_normal_cache.empty()); + remove_all = m_imgui->button(m_desc.at("remove_all")); + m_imgui->disabled_end(); + + // m_imgui->text(""); + // m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : + // (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) : + // (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) : + // (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS")))); + } + + + // Following is rendered in both editing and non-editing mode: + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("clipping_of_view")); + } + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position(-1., false); + }); + } + } + + 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", &clp_dist, 0.f, 1.f, "%.2f")) + m_c->object_clipper()->set_position(clp_dist, true); + + + if (m_imgui->button("?")) { + wxGetApp().CallAfter([]() { + SlaGizmoHelpDialog help_dlg; + help_dlg.ShowModal(); + }); + } + + m_imgui->end(); + + if (remove_selected || remove_all) { + force_refresh = false; + m_parent.set_as_dirty(); + bool was_in_editing = m_editing_mode; + if (! was_in_editing) + switch_to_editing_mode(); + if (remove_all) { + select_point(AllPoints); + delete_selected_points(true); // true - delete regardless of locked status + } + if (remove_selected) + delete_selected_points(false); // leave locked points + if (! was_in_editing) + editing_mode_apply_changes(); + + if (first_run) { + first_run = false; + goto RENDER_AGAIN; + } + } + + if (force_refresh) + m_parent.set_as_dirty(); +} + +bool GLGizmoSlaSupports::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA + || !selection.is_from_single_instance()) + return false; + + // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. + const Selection::IndicesList& list = selection.get_volume_idxs(); + for (const auto& idx : list) + if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) + return false; + + return true; +} + +bool GLGizmoSlaSupports::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); +} + +std::string GLGizmoSlaSupports::on_get_name() const +{ + return _u8L("SLA Support Points"); +} + +CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const +{ + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::HollowedMesh) + | int(CommonGizmosDataID::ObjectClipper) + | int(CommonGizmosDataID::SupportsClipper)); +} + + + +void GLGizmoSlaSupports::ask_about_changes_call_after(std::function on_yes, std::function on_no) +{ + wxGetApp().CallAfter([on_yes, on_no]() { + // Following is called through CallAfter, because otherwise there was a problem + // on OSX with the wxMessageDialog being shown several times when clicked into. + MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually " + "edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL ); + int ret = dlg.ShowModal(); + if (ret == wxID_YES) + on_yes(); + else if (ret == wxID_NO) + on_no(); + }); +} + + +void GLGizmoSlaSupports::on_set_state() +{ + if (m_state == m_old_state) + return; + + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + // Set default head diameter from config. + const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable(); + if (will_ask) { + ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, + [this](){ editing_mode_discard_changes(); }); + // refuse to be turned off so the gizmo is active when the CallAfter is executed + m_state = m_old_state; + } + else { + // we are actually shutting down + disable_editing_mode(); // so it is not active next time the gizmo opens + m_old_mo_id = -1; + } + } + m_old_state = m_state; +} + + + +void GLGizmoSlaSupports::on_start_dragging() +{ + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + m_point_before_drag = m_editing_cache[m_hover_id]; + } + else + m_point_before_drag = CacheEntry(); +} + + +void GLGizmoSlaSupports::on_stop_dragging() +{ + if (m_hover_id != -1) { + CacheEntry backup = m_editing_cache[m_hover_id]; + + if (m_point_before_drag.support_point.pos != Vec3f::Zero() // some point was touched + && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected + { + m_editing_cache[m_hover_id] = m_point_before_drag; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point")); + m_editing_cache[m_hover_id] = backup; + } + } + m_point_before_drag = CacheEntry(); +} + +void GLGizmoSlaSupports::on_dragging(const UpdateData &data) +{ + assert(m_hover_id != -1); + if (!m_editing_mode) return; + if (m_editing_cache[m_hover_id].support_point.is_new_island && m_lock_unique_islands) + return; + + std::pair pos_and_normal; + if (!unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) + return; + + m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first; + m_editing_cache[m_hover_id].support_point.is_new_island = false; + m_editing_cache[m_hover_id].normal = pos_and_normal.second; +} + +void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) +{ + ar(m_new_point_head_diameter, + m_normal_cache, + m_editing_cache, + m_selection_empty + ); +} + + + +void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const +{ + ar(m_new_point_head_diameter, + m_normal_cache, + m_editing_cache, + m_selection_empty + ); +} + + + +void GLGizmoSlaSupports::select_point(int i) +{ + if (! m_editing_mode) { + std::cout << "DEBUGGING: select_point called when out of editing mode!" << std::endl; + std::abort(); + } + + if (i == AllPoints || i == NoPoints) { + for (auto& point_and_selection : m_editing_cache) + point_and_selection.selected = ( i == AllPoints ); + m_selection_empty = (i == NoPoints); + + if (i == AllPoints) + m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; + } + else { + m_editing_cache[i].selected = true; + m_selection_empty = false; + m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; + } +} + + +void GLGizmoSlaSupports::unselect_point(int i) +{ + if (! m_editing_mode) { + std::cout << "DEBUGGING: unselect_point called when out of editing mode!" << std::endl; + std::abort(); + } + + m_editing_cache[i].selected = false; + m_selection_empty = true; + for (const CacheEntry& ce : m_editing_cache) { + if (ce.selected) { + m_selection_empty = false; + break; + } + } +} + + + + +void GLGizmoSlaSupports::editing_mode_discard_changes() +{ + if (! m_editing_mode) { + std::cout << "DEBUGGING: editing_mode_discard_changes called when out of editing mode!" << std::endl; + std::abort(); + } + select_point(NoPoints); + disable_editing_mode(); +} + + + +void GLGizmoSlaSupports::editing_mode_apply_changes() +{ + // If there are no changes, don't touch the front-end. The data in the cache could have been + // taken from the backend and copying them to ModelObject would needlessly invalidate them. + disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken + + if (unsaved_changes()) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit")); + + m_normal_cache.clear(); + for (const CacheEntry& ce : m_editing_cache) + m_normal_cache.push_back(ce.support_point); + + ModelObject* mo = m_c->selection_info()->model_object(); + mo->sla_points_status = sla::PointsStatus::UserModified; + mo->sla_support_points.clear(); + mo->sla_support_points = m_normal_cache; + + reslice_SLA_supports(); + } +} + + + +void GLGizmoSlaSupports::reload_cache() +{ + const ModelObject* mo = m_c->selection_info()->model_object(); + m_normal_cache.clear(); + if (mo->sla_points_status == sla::PointsStatus::AutoGenerated || mo->sla_points_status == sla::PointsStatus::Generating) + get_data_from_backend(); + else + for (const sla::SupportPoint& point : mo->sla_support_points) + m_normal_cache.emplace_back(point); +} + + +bool GLGizmoSlaSupports::has_backend_supports() const +{ + const ModelObject* mo = m_c->selection_info()->model_object(); + if (! mo) + return false; + + // find SlaPrintObject with this ID + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == mo->id()) + return po->is_step_done(slaposSupportPoints); + } + return false; +} + +void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const +{ + wxGetApp().CallAfter([this, postpone_error_messages]() { + wxGetApp().plater()->reslice_SLA_supports( + *m_c->selection_info()->model_object(), postpone_error_messages); + }); +} + +bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event){ + if (mouse_event.Moving()) return false; + if (use_grabbers(mouse_event)) return true; + + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + + static bool pending_right_up = false; + if (mouse_event.LeftDown()) { + bool grabber_contains_mouse = (get_hover_id() != -1); + bool control_down = mouse_event.CmdDown(); + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + return true; + } else if (mouse_event.Dragging()) { + bool control_down = mouse_event.CmdDown(); + if (m_parent.get_move_volume_id() != -1) { + // don't allow dragging objects with the Sla gizmo on + return true; + } else if (!control_down && + gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { + // the gizmo got the event and took some action, no need to do + // anything more here + m_parent.set_as_dirty(); + return true; + } else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())){ + // CTRL has been pressed while already dragging -> stop current action + if (mouse_event.LeftIsDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + else if (mouse_event.RightIsDown()) + pending_right_up = false; + } + } else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) { + // in case SLA/FDM gizmo is selected, we just pass the LeftUp event + // and stop processing - neither object moving or selecting is + // suppressed in that case + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + return true; + }else if (mouse_event.RightDown()){ + if (m_parent.get_selection().get_object_idx() != -1 && + gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { + // we need to set the following right up as processed to avoid showing + // the context menu if the user release the mouse over the object + pending_right_up = true; + // event was taken care of by the SlaSupports gizmo + return true; + } + } else if (pending_right_up && mouse_event.RightUp()) { + pending_right_up = false; + return true; + } + return false; +} + +void GLGizmoSlaSupports::get_data_from_backend() +{ + if (! has_backend_supports()) + return; + ModelObject* mo = m_c->selection_info()->model_object(); + + // find the respective SLAPrintObject, we need a pointer to it + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == mo->id()) { + m_normal_cache.clear(); + const std::vector& points = po->get_support_points(); + auto mat = (po->trafo() * po->model_object()->volumes.front()->get_transformation().get_matrix()).inverse().cast(); + for (unsigned int i=0; isla_points_status = sla::PointsStatus::AutoGenerated; + break; + } + } + + // We don't copy the data into ModelObject, as this would stop the background processing. +} + + + +void GLGizmoSlaSupports::auto_generate() +{ + //wxMessageDialog dlg(GUI::wxGetApp().plater(), + MessageDialog dlg(GUI::wxGetApp().plater(), + _L("Autogeneration will erase all manually edited points.") + "\n\n" + + _L("Are you sure you want to do it?") + "\n", + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + ModelObject* mo = m_c->selection_info()->model_object(); + + if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); + wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); + mo->sla_points_status = sla::PointsStatus::Generating; + } +} + + + +void GLGizmoSlaSupports::switch_to_editing_mode() +{ + wxGetApp().plater()->enter_gizmos_stack(); + m_editing_mode = true; + m_editing_cache.clear(); + for (const sla::SupportPoint& sp : m_normal_cache) + m_editing_cache.emplace_back(sp); + select_point(NoPoints); + + m_c->instances_hider()->show_supports(false); + m_parent.set_as_dirty(); +} + + +void GLGizmoSlaSupports::disable_editing_mode() +{ + if (m_editing_mode) { + m_editing_mode = false; + wxGetApp().plater()->leave_gizmos_stack(); + m_c->instances_hider()->show_supports(true); + m_parent.set_as_dirty(); + } + wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode); +} + + + +bool GLGizmoSlaSupports::unsaved_changes() const +{ + if (m_editing_cache.size() != m_normal_cache.size()) + return true; + + for (size_t i=0; iSetFont(font); + + auto vsizer = new wxBoxSizer(wxVERTICAL); + auto gridsizer = new wxFlexGridSizer(2, 5, 15); + auto hsizer = new wxBoxSizer(wxHORIZONTAL); + + hsizer->AddSpacer(20); + hsizer->Add(vsizer); + hsizer->AddSpacer(20); + + vsizer->AddSpacer(20); + vsizer->Add(note_text, 1, wxALIGN_CENTRE_HORIZONTAL); + vsizer->AddSpacer(20); + vsizer->Add(gridsizer); + vsizer->AddSpacer(20); + + std::vector> shortcuts; + shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point"))); + shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point"))); + shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point"))); + shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection"))); + shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection"))); + shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle"))); + shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle"))); + shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points"))); + shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points"))); + shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane"))); + shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane"))); + shortcuts.push_back(std::make_pair("Enter", _L("Apply changes"))); + shortcuts.push_back(std::make_pair("Esc", _L("Discard changes"))); + shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode"))); + shortcuts.push_back(std::make_pair("A", _L("Auto-generate points"))); + + for (const auto& pair : shortcuts) { + auto shortcut = new wxStaticText(this, wxID_ANY, pair.first); + auto desc = new wxStaticText(this, wxID_ANY, pair.second); + shortcut->SetFont(bold_font); + desc->SetFont(font); + gridsizer->Add(shortcut, -1, wxALIGN_CENTRE_VERTICAL); + gridsizer->Add(desc, -1, wxALIGN_CENTRE_VERTICAL); + } + + SetSizer(hsizer); + hsizer->SetSizeHints(this); +} + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index bbdcdeb34..777a4d7ba 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -1,370 +1,378 @@ -#include "MeshUtils.hpp" - -#include "libslic3r/Tesselate.hpp" -#include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/TriangleMeshSlicer.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/Model.hpp" - -#if ENABLE_LEGACY_OPENGL_REMOVAL -#include "slic3r/GUI/GUI_App.hpp" -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#include "slic3r/GUI/Camera.hpp" -#if ENABLE_GL_SHADERS_ATTRIBUTES -#include "slic3r/GUI/Plater.hpp" -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#include - -#include - - -namespace Slic3r { -namespace GUI { - -void MeshClipper::set_plane(const ClippingPlane& plane) -{ - if (m_plane != plane) { - m_plane = plane; - m_triangles_valid = false; - } -} - - -void MeshClipper::set_limiting_plane(const ClippingPlane& plane) -{ - if (m_limiting_plane != plane) { - m_limiting_plane = plane; - m_triangles_valid = false; - } -} - - - -void MeshClipper::set_mesh(const TriangleMesh& mesh) -{ - if (m_mesh != &mesh) { - m_mesh = &mesh; - m_triangles_valid = false; - m_triangles2d.resize(0); - } -} - -void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) -{ - if (m_negative_mesh != &mesh) { - m_negative_mesh = &mesh; - m_triangles_valid = false; - m_triangles2d.resize(0); - } -} - - - -void MeshClipper::set_transformation(const Geometry::Transformation& trafo) -{ - if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { - m_trafo = trafo; - m_triangles_valid = false; - m_triangles2d.resize(0); - } -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void MeshClipper::render_cut(const ColorRGBA& color) -#else -void MeshClipper::render_cut() -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -{ - if (! m_triangles_valid) - recalculate_triangles(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (m_model.vertices_count() == 0 || m_model.indices_count() == 0) - return; - - GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); - if (curr_shader != nullptr) - curr_shader->stop_using(); - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_model.set_color(color); - m_model.render(); - shader->stop_using(); - } - - if (curr_shader != nullptr) - curr_shader->start_using(); -#else - if (m_vertex_array.has_VBOs()) - m_vertex_array.render(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - - - -void MeshClipper::recalculate_triangles() -{ - const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); - // Calculate clipping plane normal in mesh coordinates. - const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); - const Vec3d up = up_noscale.cast().cwiseProduct(m_trafo.get_scaling_factor()); - // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). - const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); - - // Now do the cutting - MeshSlicingParams slicing_params; - slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); - - ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params)); - - if (m_negative_mesh && !m_negative_mesh->empty()) { - const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params)); - expolys = diff_ex(expolys, neg_expolys); - } - - // Triangulate and rotate the cut into world coords: - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), up); - Transform3d tr = Transform3d::Identity(); - tr.rotate(q); - tr = m_trafo.get_matrix() * tr; - - if (m_limiting_plane != ClippingPlane::ClipsNothing()) - { - // Now remove whatever ended up below the limiting plane (e.g. sinking objects). - // First transform the limiting plane from world to mesh coords. - // Note that inverse of tr transforms the plane from world to horizontal. - const Vec3d normal_old = m_limiting_plane.get_normal().normalized(); - const Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized(); - - // normal_new should now be the plane normal in mesh coords. To find the offset, - // transform a point and set offset so it belongs to the transformed plane. - Vec3d pt = Vec3d::Zero(); - const double plane_offset = m_limiting_plane.get_data()[3]; - if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57 - pt.z() = - plane_offset / normal_old.z(); - else if (std::abs(normal_old.y()) > 0.5) - pt.y() = - plane_offset / normal_old.y(); - else - pt.x() = - plane_offset / normal_old.x(); - pt = tr.inverse() * pt; - const double offset = -(normal_new.dot(pt)); - - if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) { - // The cuts are parallel, show all or nothing. - if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh) - expolys.clear(); - } else { - // The cut is a horizontal plane defined by z=height_mesh. - // ax+by+e=0 is the line of intersection with the limiting plane. - // Normalized so a^2 + b^2 = 1. - const double len = std::hypot(normal_new.x(), normal_new.y()); - if (len == 0.) - return; - const double a = normal_new.x() / len; - const double b = normal_new.y() / len; - const double e = (normal_new.z() * height_mesh + offset) / len; - - // We need a half-plane to limit the cut. Get angle of the intersecting line. - double angle = (b != 0.0) ? std::atan(-a / b) : ((a < 0.0) ? -0.5 * M_PI : 0.5 * M_PI); - if (b > 0) // select correct half-plane - angle += M_PI; - - // We'll take a big rectangle above x-axis and rotate and translate - // it so it lies on our line. This will be the figure to subtract - // from the cut. The coordinates must not overflow after the transform, - // make the rectangle a bit smaller. - const coord_t size = (std::numeric_limits::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4; - Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})}; - ep.front().rotate(angle); - ep.front().translate(scale_(-e * a), scale_(-e * b)); - expolys = diff_ex(expolys, ep); - } - } - - m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.); - - tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_model.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - init_data.reserve_vertices(m_triangles2d.size()); - init_data.reserve_indices(m_triangles2d.size()); - - // vertices + indices - for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) { - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - const size_t idx = it - m_triangles2d.cbegin(); - init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); - } - - if (!init_data.is_empty()) - m_model.init_from(std::move(init_data)); -#else - m_vertex_array.release_geometry(); - for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { - m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); - m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); - m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); - const size_t idx = it - m_triangles2d.cbegin(); - m_vertex_array.push_triangle(idx, idx+1, idx+2); - } - m_vertex_array.finalize_geometry(true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - m_triangles_valid = true; -} - - -Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const -{ - return m_normals[facet_idx]; -} - -void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3d& point, Vec3d& direction) const -{ - Matrix4d modelview = camera.get_view_matrix().matrix(); - Matrix4d projection= camera.get_projection_matrix().matrix(); - Vec4i viewport(camera.get_viewport().data()); - - Vec3d pt1; - Vec3d pt2; - igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 0.), - modelview, projection, viewport, pt1); - igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 1.), - modelview, projection, viewport, pt2); - - Transform3d inv = trafo.inverse(); - pt1 = inv * pt1; - pt2 = inv * pt2; - - point = pt1; - direction = pt2-pt1; -} - - -bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, - size_t* facet_idx) const -{ - Vec3d point; - Vec3d direction; - line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); - - std::vector hits = m_emesh.query_ray_hits(point, direction); - - if (hits.empty()) - return false; // no intersection found - - unsigned i = 0; - - // Remove points that are obscured or cut by the clipping plane. - // Also, remove anything below the bed (sinking objects). - for (i=0; i= SINKING_Z_THRESHOLD && - (! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit))) - break; - } - - if (i==hits.size() || (hits.size()-i) % 2 != 0) { - // All hits are either clipped, or there is an odd number of unclipped - // hits - meaning the nearest must be from inside the mesh. - return false; - } - - // Now stuff the points in the provided vector and calculate normals if asked about them: - position = hits[i].position().cast(); - normal = hits[i].normal().cast(); - - if (facet_idx) - *facet_idx = hits[i].face(); - - return true; -} - - -std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, - const ClippingPlane* clipping_plane) const -{ - std::vector out; - - const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); - Vec3d direction_to_camera = -camera.get_dir_forward(); - Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval(); - direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor()); - const Transform3d inverse_trafo = trafo.get_matrix().inverse(); - - for (size_t i=0; iis_point_clipped(pt.cast())) - continue; - - bool is_obscured = false; - // Cast a ray in the direction of the camera and look for intersection with the mesh: - std::vector hits; - // Offset the start of the ray by EPSILON to account for numerical inaccuracies. - hits = m_emesh.query_ray_hits((inverse_trafo * pt.cast() + direction_to_camera_mesh * EPSILON), - direction_to_camera_mesh); - - if (! hits.empty()) { - // If the closest hit facet normal points in the same direction as the ray, - // we are looking through the mesh and should therefore discard the point: - if (hits.front().normal().dot(direction_to_camera_mesh.cast()) > 0) - is_obscured = true; - - // Eradicate all hits that the caller wants to ignore - for (unsigned j=0; jis_point_clipped(trafo.get_matrix() * hits[j].position())) { - hits.erase(hits.begin()+j); - --j; - } - } - - // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. - // Also, the threshold is in mesh coordinates, not in actual dimensions. - if (! hits.empty()) - is_obscured = true; - } - if (! is_obscured) - out.push_back(i); - } - return out; -} - - -Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const -{ - int idx = 0; - Vec3d closest_point; - m_emesh.squared_distance(point.cast(), idx, closest_point); - if (normal) - *normal = m_normals[idx]; - - return closest_point.cast(); -} - -int MeshRaycaster::get_closest_facet(const Vec3f &point) const -{ - int facet_idx = 0; - Vec3d closest_point; - m_emesh.squared_distance(point.cast(), facet_idx, closest_point); - return facet_idx; -} - -} // namespace GUI -} // namespace Slic3r +#include "MeshUtils.hpp" + +#include "libslic3r/Tesselate.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Model.hpp" + +#if ENABLE_LEGACY_OPENGL_REMOVAL +#include "slic3r/GUI/GUI_App.hpp" +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#include "slic3r/GUI/Camera.hpp" +#if ENABLE_GL_SHADERS_ATTRIBUTES +#include "slic3r/GUI/Plater.hpp" +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#include + +#include + + +namespace Slic3r { +namespace GUI { + +void MeshClipper::set_plane(const ClippingPlane& plane) +{ + if (m_plane != plane) { + m_plane = plane; + m_triangles_valid = false; + } +} + + +void MeshClipper::set_limiting_plane(const ClippingPlane& plane) +{ + if (m_limiting_plane != plane) { + m_limiting_plane = plane; + m_triangles_valid = false; + } +} + + + +void MeshClipper::set_mesh(const TriangleMesh& mesh) +{ + if (m_mesh != &mesh) { + m_mesh = &mesh; + m_triangles_valid = false; + m_triangles2d.resize(0); + } +} + +void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) +{ + if (m_negative_mesh != &mesh) { + m_negative_mesh = &mesh; + m_triangles_valid = false; + m_triangles2d.resize(0); + } +} + + + +void MeshClipper::set_transformation(const Geometry::Transformation& trafo) +{ + if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { + m_trafo = trafo; + m_triangles_valid = false; + m_triangles2d.resize(0); + } +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void MeshClipper::render_cut(const ColorRGBA& color) +#else +void MeshClipper::render_cut() +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +{ + if (! m_triangles_valid) + recalculate_triangles(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (m_model.vertices_count() == 0 || m_model.indices_count() == 0) + return; + + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_model.set_color(color); + m_model.render(); + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +#else + if (m_vertex_array.has_VBOs()) + m_vertex_array.render(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + + + +void MeshClipper::recalculate_triangles() +{ +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3f instance_matrix_no_translation_no_scaling = m_trafo.get_rotation_matrix().cast(); +#else + const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + // Calculate clipping plane normal in mesh coordinates. + const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); + const Vec3d up = up_noscale.cast().cwiseProduct(m_trafo.get_scaling_factor()); + // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). + const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); + + // Now do the cutting + MeshSlicingParams slicing_params; + slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); + + ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params)); + + if (m_negative_mesh && !m_negative_mesh->empty()) { + const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params)); + expolys = diff_ex(expolys, neg_expolys); + } + + // Triangulate and rotate the cut into world coords: + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), up); + Transform3d tr = Transform3d::Identity(); + tr.rotate(q); + tr = m_trafo.get_matrix() * tr; + + if (m_limiting_plane != ClippingPlane::ClipsNothing()) + { + // Now remove whatever ended up below the limiting plane (e.g. sinking objects). + // First transform the limiting plane from world to mesh coords. + // Note that inverse of tr transforms the plane from world to horizontal. + const Vec3d normal_old = m_limiting_plane.get_normal().normalized(); + const Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized(); + + // normal_new should now be the plane normal in mesh coords. To find the offset, + // transform a point and set offset so it belongs to the transformed plane. + Vec3d pt = Vec3d::Zero(); + const double plane_offset = m_limiting_plane.get_data()[3]; + if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57 + pt.z() = - plane_offset / normal_old.z(); + else if (std::abs(normal_old.y()) > 0.5) + pt.y() = - plane_offset / normal_old.y(); + else + pt.x() = - plane_offset / normal_old.x(); + pt = tr.inverse() * pt; + const double offset = -(normal_new.dot(pt)); + + if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) { + // The cuts are parallel, show all or nothing. + if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh) + expolys.clear(); + } else { + // The cut is a horizontal plane defined by z=height_mesh. + // ax+by+e=0 is the line of intersection with the limiting plane. + // Normalized so a^2 + b^2 = 1. + const double len = std::hypot(normal_new.x(), normal_new.y()); + if (len == 0.) + return; + const double a = normal_new.x() / len; + const double b = normal_new.y() / len; + const double e = (normal_new.z() * height_mesh + offset) / len; + + // We need a half-plane to limit the cut. Get angle of the intersecting line. + double angle = (b != 0.0) ? std::atan(-a / b) : ((a < 0.0) ? -0.5 * M_PI : 0.5 * M_PI); + if (b > 0) // select correct half-plane + angle += M_PI; + + // We'll take a big rectangle above x-axis and rotate and translate + // it so it lies on our line. This will be the figure to subtract + // from the cut. The coordinates must not overflow after the transform, + // make the rectangle a bit smaller. + const coord_t size = (std::numeric_limits::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4; + Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})}; + ep.front().rotate(angle); + ep.front().translate(scale_(-e * a), scale_(-e * b)); + expolys = diff_ex(expolys, ep); + } + } + + m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.); + + tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_model.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(m_triangles2d.size()); + init_data.reserve_indices(m_triangles2d.size()); + + // vertices + indices + for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) { + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + const size_t idx = it - m_triangles2d.cbegin(); + init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); + } + + if (!init_data.is_empty()) + m_model.init_from(std::move(init_data)); +#else + m_vertex_array.release_geometry(); + for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { + m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); + m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); + m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); + const size_t idx = it - m_triangles2d.cbegin(); + m_vertex_array.push_triangle(idx, idx+1, idx+2); + } + m_vertex_array.finalize_geometry(true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + m_triangles_valid = true; +} + + +Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const +{ + return m_normals[facet_idx]; +} + +void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + Vec3d& point, Vec3d& direction) const +{ + Matrix4d modelview = camera.get_view_matrix().matrix(); + Matrix4d projection= camera.get_projection_matrix().matrix(); + Vec4i viewport(camera.get_viewport().data()); + + Vec3d pt1; + Vec3d pt2; + igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 0.), + modelview, projection, viewport, pt1); + igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 1.), + modelview, projection, viewport, pt2); + + Transform3d inv = trafo.inverse(); + pt1 = inv * pt1; + pt2 = inv * pt2; + + point = pt1; + direction = pt2-pt1; +} + + +bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, + size_t* facet_idx) const +{ + Vec3d point; + Vec3d direction; + line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); + + std::vector hits = m_emesh.query_ray_hits(point, direction); + + if (hits.empty()) + return false; // no intersection found + + unsigned i = 0; + + // Remove points that are obscured or cut by the clipping plane. + // Also, remove anything below the bed (sinking objects). + for (i=0; i= SINKING_Z_THRESHOLD && + (! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit))) + break; + } + + if (i==hits.size() || (hits.size()-i) % 2 != 0) { + // All hits are either clipped, or there is an odd number of unclipped + // hits - meaning the nearest must be from inside the mesh. + return false; + } + + // Now stuff the points in the provided vector and calculate normals if asked about them: + position = hits[i].position().cast(); + normal = hits[i].normal().cast(); + + if (facet_idx) + *facet_idx = hits[i].face(); + + return true; +} + + +std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, + const ClippingPlane* clipping_plane) const +{ + std::vector out; + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_matrix_no_translation_no_scaling = trafo.get_rotation_matrix(); +#else + const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d direction_to_camera = -camera.get_dir_forward(); + Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval(); + direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor()); + const Transform3d inverse_trafo = trafo.get_matrix().inverse(); + + for (size_t i=0; iis_point_clipped(pt.cast())) + continue; + + bool is_obscured = false; + // Cast a ray in the direction of the camera and look for intersection with the mesh: + std::vector hits; + // Offset the start of the ray by EPSILON to account for numerical inaccuracies. + hits = m_emesh.query_ray_hits((inverse_trafo * pt.cast() + direction_to_camera_mesh * EPSILON), + direction_to_camera_mesh); + + if (! hits.empty()) { + // If the closest hit facet normal points in the same direction as the ray, + // we are looking through the mesh and should therefore discard the point: + if (hits.front().normal().dot(direction_to_camera_mesh.cast()) > 0) + is_obscured = true; + + // Eradicate all hits that the caller wants to ignore + for (unsigned j=0; jis_point_clipped(trafo.get_matrix() * hits[j].position())) { + hits.erase(hits.begin()+j); + --j; + } + } + + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. + // Also, the threshold is in mesh coordinates, not in actual dimensions. + if (! hits.empty()) + is_obscured = true; + } + if (! is_obscured) + out.push_back(i); + } + return out; +} + + +Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const +{ + int idx = 0; + Vec3d closest_point; + m_emesh.squared_distance(point.cast(), idx, closest_point); + if (normal) + *normal = m_normals[idx]; + + return closest_point.cast(); +} + +int MeshRaycaster::get_closest_facet(const Vec3f &point) const +{ + int facet_idx = 0; + Vec3d closest_point; + m_emesh.squared_distance(point.cast(), facet_idx, closest_point); + return facet_idx; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cd9f221b4..0c1985733 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3492,7 +3492,11 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); new_volume->set_transformation(old_volume->get_transformation()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); +#else new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) new_volume->convert_from_imperial_units(); @@ -3847,10 +3851,16 @@ void Plater::priv::reload_from_disk() new_volume->config.apply(old_volume->config); new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) * + old_volume->get_transformation().get_matrix_no_offset() * old_volume->source.transform.get_matrix_no_offset()); + new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); +#else new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) * old_volume->get_transformation().get_matrix(true) * old_volume->source.transform.get_matrix(true)); new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES new_volume->source.object_idx = old_volume->source.object_idx; new_volume->source.volume_idx = old_volume->source.volume_idx; assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 3bb2db1ac..45d80a145 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -723,7 +723,11 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const const GLVolume& volume = *(*m_volumes)[i]; if (volume.is_modifier) continue; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix(); +#else Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.translation().z() += volume.get_sla_shift_z(); (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); } @@ -1486,8 +1490,14 @@ void Selection::render(float scale_factor) } else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { const GLVolume& v = *get_volume(*get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + box = v.transformed_convex_hull_bounding_box( + v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); + trafo = v.get_instance_transformation().get_matrix_no_scaling_factor() * v.get_volume_transformation().get_matrix_no_scaling_factor(); +#else box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const Selection::IndicesList& ids = get_volume_idxs(); @@ -1495,8 +1505,13 @@ void Selection::render(float scale_factor) const GLVolume& v = *get_volume(id); box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); + trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor(); +#else box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } render_bounding_box(box, trafo, ColorRGB::WHITE()); @@ -1516,11 +1531,7 @@ void Selection::render_center(bool gizmo_is_dragging) return; #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -1565,11 +1576,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) return; #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat_attr" : "gouraud_light_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -1622,7 +1629,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES Transform3d orient_matrix = Transform3d::Identity(); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); +#else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); #else @@ -1667,13 +1678,21 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES if (wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume* v = (*m_volumes)[*m_list.begin()]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = v->get_instance_transformation().get_rotation_matrix() * v->get_volume_transformation().get_rotation_matrix(); +#else orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation(); #endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } else { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); +#else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); } @@ -1700,7 +1719,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) else { #if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES if (requires_local_axes()) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); +#else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else glsafe(::glTranslated(center.x(), center.y(), center.z())); if (requires_local_axes()) { @@ -2357,11 +2380,7 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con glsafe(::glLineWidth(2.0f * m_scale_factor)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -3061,8 +3080,13 @@ void Selection::paste_volumes_from_clipboard() { ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix_no_offset(); + Transform3d dst_matrix = dst_instance->get_transformation().get_matrix_no_offset(); +#else Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); // used to keep relative position of multivolume selections when pasting from another object From 2f6f73e10f07a5aa16b15efbd3196e45dc756ad1 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 2 May 2022 12:21:48 +0200 Subject: [PATCH 073/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::translate(const Vec3d& displacement, ECoordinatesType type) to use matrix multiplication Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 2 +- src/libslic3r/Geometry.hpp | 4 +- src/libslic3r/Model.hpp | 2 +- src/slic3r/GUI/3DScene.hpp | 6 ++- src/slic3r/GUI/Selection.cpp | 81 ++++++++++++++++++++++++++++++++++-- 5 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index fba1b2378..44e68a9d8 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -712,7 +712,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation // No need to run the non-linear least squares fitting for uniform scaling. // Just set the inverse. #if ENABLE_TRANSFORMATIONS_BY_MATRICES - out = instance_transformation.get_matrix_no_offset().inverse(); + out.set_matrix(instance_transformation.get_matrix_no_offset().inverse()); #else out.set_from_transform(instance_transformation.get_matrix(true).inverse()); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index ffe96cd3a..d0548f765 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -398,8 +398,6 @@ public: #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_TRANSFORMATIONS_BY_MATRICES - Transformation& operator = (const Transform3d& transform) { m_matrix = transform; return *this; } - Vec3d get_offset() const { return m_matrix.translation(); } double get_offset(Axis axis) const { return get_offset()[axis]; } @@ -481,6 +479,8 @@ public: const Transform3d& get_matrix() const { return m_matrix; } Transform3d get_matrix_no_offset() const; Transform3d get_matrix_no_scaling_factor() const; + + void set_matrix(const Transform3d& transform) { m_matrix = transform; } #else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index f2844abcb..016689a46 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -692,7 +692,7 @@ public: const Geometry::Transformation& get_transformation() const { return m_transformation; } void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } #if ENABLE_TRANSFORMATIONS_BY_MATRICES - void set_transformation(const Transform3d& trafo) { m_transformation = trafo; } + void set_transformation(const Transform3d& trafo) { m_transformation.set_matrix(trafo); } #else void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 263d8af08..9fb5a6965 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -432,8 +432,9 @@ public: const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } - #if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_instance_transformation(const Transform3d& transform) { m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } + Vec3d get_instance_offset() const { return m_instance_transformation.get_offset(); } #else const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } @@ -471,8 +472,9 @@ public: const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } - #if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_volume_transformation(const Transform3d& transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } + Vec3d get_volume_offset() const { return m_volume_transformation.get_offset(); } #else const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 45d80a145..1f0e77e16 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -763,6 +763,81 @@ void Selection::setup_cache() set_caches(); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::translate(const Vec3d& displacement, ECoordinatesType type) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + const VolumeCache& volume_data = m_cache.volumes_data[i]; + if (m_mode == Instance && !v.is_wipe_tower) { + switch (type) + { + case ECoordinatesType::World: + { + if (is_from_fully_selected_instance(i)) + v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix()); + else + assert(false); + + break; + } + case ECoordinatesType::Local: + { + if (is_from_fully_selected_instance(i)) { + const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement; + v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix()); + } + else + assert(false); + + break; + } + default: { assert(false); break; } + } + } + else { + switch (type) + { + case ECoordinatesType::World: + { + const Transform3d inst_matrix_no_offset = volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix(); + const Vec3d inst_displacement = inst_matrix_no_offset.inverse() * displacement; + v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); + break; + } + case ECoordinatesType::Instance: + { + const Vec3d inst_displacement = volume_data.get_instance_scale_matrix().inverse() * displacement; + v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); + break; + } + case ECoordinatesType::Local: + { + const Vec3d inst_displacement = volume_data.get_instance_scale_matrix().inverse() * + volume_data.get_volume_rotation_matrix() * displacement; + v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); + break; + } + default: { assert(false); break; } + } + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances(SyncRotationType::NONE); + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + ensure_not_below_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} +#else #if ENABLE_WORLD_COORDINATE void Selection::translate(const Vec3d& displacement, ECoordinatesType type) #else @@ -835,6 +910,7 @@ void Selection::translate(const Vec3d& displacement, bool local) set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Rotate an object around one of the axes. Only one rotation component is expected to be changing. void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) @@ -1491,9 +1567,8 @@ void Selection::render(float scale_factor) else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { const GLVolume& v = *get_volume(*get_volume_idxs().begin()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - box = v.transformed_convex_hull_bounding_box( - v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); - trafo = v.get_instance_transformation().get_matrix_no_scaling_factor() * v.get_volume_transformation().get_matrix_no_scaling_factor(); + box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_scaling_factor_matrix()); + trafo = v.get_instance_transformation().get_matrix() * v.get_volume_transformation().get_matrix_no_scaling_factor(); #else box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); From 689b274fd3b47fb2365848b54960a182961a8ce4 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Fri, 3 Jun 2022 14:57:07 +0200 Subject: [PATCH 074/102] updated bundle version to 0.1.5 --- resources/profiles/Creality.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index d57a885ba..ce39cd6d6 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -5,7 +5,7 @@ name = Creality # 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 = 0.1.4 +config_version = 0.1.5 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% From 0b617e2cf6b7fb5aa3f4c8e0098f0dfa509bb8b6 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Fri, 3 Jun 2022 15:00:48 +0200 Subject: [PATCH 075/102] Fixed min_slic3r_version --- resources/profiles/INAT.idx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/profiles/INAT.idx b/resources/profiles/INAT.idx index 6087bae62..0446399a1 100644 --- a/resources/profiles/INAT.idx +++ b/resources/profiles/INAT.idx @@ -1,5 +1,6 @@ -min_slic3r_version = 2.4.1 +min_slic3r_version = 2.5.0-alpha0 0.0.4 Improve Proton X profiles, Add Proton XE-750 printer +min_slic3r_version = 2.4.1 0.0.3 Set default filament profile. 0.0.2 Improved start gcode, changed filename format 0.0.1 Initial version From 55be16d158ce72c860700e26ae9bd19b03e0af95 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 2 May 2022 12:46:13 +0200 Subject: [PATCH 076/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) to use matrix multiplication and removed unused method void Selection::translate(unsigned int object_idx, const Vec3d& displacement) Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 10 ++++++++++ src/slic3r/GUI/Selection.hpp | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 1f0e77e16..7bc3ce91a 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1292,6 +1292,7 @@ void Selection::mirror(Axis axis) set_bounding_boxes_dirty(); } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::translate(unsigned int object_idx, const Vec3d& displacement) { if (!m_valid) @@ -1340,6 +1341,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) this->set_bounding_boxes_dirty(); } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) { @@ -1349,7 +1351,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + v.set_instance_transformation(Geometry::assemble_transform(displacement) * v.get_instance_transformation().get_matrix()); +#else v.set_instance_offset(v.get_instance_offset() + displacement); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } std::set done; // prevent processing volumes twice @@ -1382,7 +1388,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx) continue; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + v.set_instance_transformation(Geometry::assemble_transform(displacement) * v.get_instance_transformation().get_matrix()); +#else v.set_instance_offset(v.get_instance_offset() + displacement); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES done.insert(j); } } diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 95a73f8f7..012fb0e4d 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -363,7 +363,9 @@ public: void scale_to_fit_print_volume(const BuildVolume& volume); void mirror(Axis axis); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(unsigned int object_idx, const Vec3d& displacement); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED From 9f503b95e8494d31e4c05fb0c8b48167650bdd8b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 4 May 2022 10:28:59 +0200 Subject: [PATCH 077/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) to use matrix multiplication Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 12 ++ src/libslic3r/Geometry.hpp | 21 ++- src/slic3r/GUI/GLCanvas3D.cpp | 33 +++-- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 12 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 63 ++++++--- src/slic3r/GUI/Selection.cpp | 163 ++++++++++++++++++------ src/slic3r/GUI/Selection.hpp | 29 +++-- 7 files changed, 256 insertions(+), 77 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 44e68a9d8..031e489f7 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -314,6 +314,18 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, } #if ENABLE_TRANSFORMATIONS_BY_MATRICES +void assemble_transform(Transform3d& transform, const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror) +{ + transform = translation * rotation * scale * mirror; +} + +Transform3d assemble_transform(const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror) +{ + Transform3d transform; + assemble_transform(transform, translation, rotation, scale, mirror); + return transform; +} + void rotation_transform(Transform3d& transform, const Vec3d& rotation) { transform = Transform3d::Identity(); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index d0548f765..c55867023 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -323,7 +323,8 @@ bool arrange( // 4) rotate Y // 5) rotate Z // 6) translate -void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); +void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), + const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); // Returns the transform obtained by assembling the given transformations in the following order: // 1) mirror @@ -332,9 +333,21 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d // 4) rotate Y // 5) rotate Z // 6) translate -Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); +Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), + const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES +// Sets the given transform by multiplying the given transformations in the following order: +// T = translation * rotation * scale * mirror +void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d::Identity(), + const Transform3d& rotation = Transform3d::Identity(), const Transform3d& scale = Transform3d::Identity(), + const Transform3d& mirror = Transform3d::Identity()); + +// Returns the transform obtained by multiplying the given transformations in the following order: +// T = translation * rotation * scale * mirror +Transform3d assemble_transform(const Transform3d& translation = Transform3d::Identity(), const Transform3d& rotation = Transform3d::Identity(), + const Transform3d& scale = Transform3d::Identity(), const Transform3d& mirror = Transform3d::Identity()); + // Sets the given transform by assembling the given rotations in the following order: // 1) rotate X // 2) rotate Y @@ -391,10 +404,10 @@ class Transformation public: #if ENABLE_TRANSFORMATIONS_BY_MATRICES Transformation() = default; - explicit Transformation(const Transform3d & transform) : m_matrix(transform) {} + explicit Transformation(const Transform3d& transform) : m_matrix(transform) {} #else Transformation(); - explicit Transformation(const Transform3d & transform); + explicit Transformation(const Transform3d& transform); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index adab374f5..f3ba04c50 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3825,9 +3825,17 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES else if (selection_mode == Selection::Volume) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES object_moved = true; model_object->invalidate_bounding_box(); @@ -3907,8 +3915,8 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) int object_idx = v->object_idx(); if (object_idx == 1000) { // the wipe tower #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - Vec3d offset = v->get_volume_offset(); - post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); + const Vec3d offset = v->get_volume_offset(); + post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset.x(), offset.y(), v->get_volume_rotation().z()))); } #if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL int object_idx = v->object_idx(); @@ -3916,8 +3924,8 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) continue; - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); + const int instance_idx = v->instance_idx(); + const int volume_idx = v->volume_idx(); done.insert(std::pair(object_idx, instance_idx)); @@ -3925,12 +3933,20 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (selection_mode == Selection::Volume) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } model_object->invalidate_bounding_box(); } @@ -7347,13 +7363,11 @@ bool GLCanvas3D::_is_any_volume_outside() const void GLCanvas3D::_update_selection_from_hover() { bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); - bool selection_changed = false; - if (m_hover_volume_idxs.empty()) { - if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) { - selection_changed = ! m_selection.is_empty(); + if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) m_selection.remove_all(); - } + + return; } GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); @@ -7366,6 +7380,7 @@ void GLCanvas3D::_update_selection_from_hover() } } + bool selection_changed = false; #if ENABLE_NEW_RECTANGLE_SELECTION if (!m_rectangle_selection.is_empty()) { #endif // ENABLE_NEW_RECTANGLE_SELECTION diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 52dd207e9..42fe796a4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -54,9 +54,7 @@ bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) { } void GLGizmoMove3D::data_changed() { - const Selection &selection = m_parent.get_selection(); - bool is_wipe_tower = selection.is_wipe_tower(); - m_grabbers[2].enabled = !is_wipe_tower; + m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower(); } bool GLGizmoMove3D::on_init() @@ -100,11 +98,19 @@ void GLGizmoMove3D::on_start_dragging() m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; +#else m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; +#else m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } m_starting_box_center = m_center; m_starting_box_bottom_center = m_center; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 6579a1431..51d089873 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,7 +287,15 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + ECoordinatesType coordinates_type; + if (selection.is_wipe_tower()) + coordinates_type = ECoordinatesType::Local; + else + coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); +#else const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (coordinates_type == ECoordinatesType::World) { m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); @@ -326,7 +334,11 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) if (coordinates_type == ECoordinatesType::World) m_orient_matrix = Transform3d::Identity(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + else if (coordinates_type == ECoordinatesType::Local && (selection.is_wipe_tower() || selection.is_single_volume_or_modifier())) { +#else else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); @@ -749,12 +761,20 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const { case X: { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + ret = Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()); +#else ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } case Y: { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + ret = Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY()); +#else ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } default: @@ -868,13 +888,21 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) // Apply new temporary rotations #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; - switch (wxGetApp().obj_manipul()->get_coordinates_type()) - { - default: - case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } - case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; } - case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + if (m_parent.get_selection().is_wipe_tower()) + transformation_type = TransformationType::Instance_Relative_Joint; + else { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + switch (wxGetApp().obj_manipul()->get_coordinates_type()) + { + default: + case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } + case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; } + case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } + } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else TransformationType transformation_type(TransformationType::World_Relative_Joint); #endif // ENABLE_WORLD_COORDINATE @@ -885,22 +913,27 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) } void GLGizmoRotate3D::data_changed() { - const Selection &selection = m_parent.get_selection(); - bool is_wipe_tower = selection.is_wipe_tower(); - if (is_wipe_tower) { - DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; - float wipe_tower_rotation_angle = - dynamic_cast( - config.option("wipe_tower_rotation_angle")) - ->value; + if (m_parent.get_selection().is_wipe_tower()) { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES + const DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + const float wipe_tower_rotation_angle = + dynamic_cast( + config.option("wipe_tower_rotation_angle"))->value; set_rotation(Vec3d(0., 0., (M_PI / 180.) * wipe_tower_rotation_angle)); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_gizmos[0].disable_grabber(); m_gizmos[1].disable_grabber(); - } else { + } + else { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES set_rotation(Vec3d::Zero()); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_gizmos[0].enable_grabber(); m_gizmos[1].enable_grabber(); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + set_rotation(Vec3d::Zero()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } bool GLGizmoRotate3D::on_init() diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 7bc3ce91a..c96e8efc7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -35,29 +35,25 @@ static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5 namespace Slic3r { namespace GUI { -Selection::VolumeCache::TransformCache::TransformCache() - : position(Vec3d::Zero()) - , rotation(Vec3d::Zero()) - , scaling_factor(Vec3d::Ones()) - , mirror(Vec3d::Ones()) - , rotation_matrix(Transform3d::Identity()) - , scale_matrix(Transform3d::Identity()) - , mirror_matrix(Transform3d::Identity()) - , full_matrix(Transform3d::Identity()) -{ -} - -Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) - : position(transform.get_offset()) - , rotation(transform.get_rotation()) - , scaling_factor(transform.get_scaling_factor()) - , mirror(transform.get_mirror()) - , full_matrix(transform.get_matrix()) -{ - rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); - scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); -} + Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) + : position(transform.get_offset()) + , rotation(transform.get_rotation()) + , scaling_factor(transform.get_scaling_factor()) + , mirror(transform.get_mirror()) + , full_matrix(transform.get_matrix()) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + , transform(transform) + , rotation_matrix(transform.get_rotation_matrix()) + , scale_matrix(transform.get_scaling_factor_matrix()) + , mirror_matrix(transform.get_mirror_matrix()) +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES + rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES + } Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) : m_volume(volume_transform) @@ -772,27 +768,19 @@ void Selection::translate(const Vec3d& displacement, ECoordinatesType type) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; - if (m_mode == Instance && !v.is_wipe_tower) { + if (m_mode == Instance && !is_wipe_tower()) { + assert(is_from_fully_selected_instance(i)); switch (type) { case ECoordinatesType::World: { - if (is_from_fully_selected_instance(i)) - v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix()); - else - assert(false); - + v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix()); break; } case ECoordinatesType::Local: { - if (is_from_fully_selected_instance(i)) { - const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement; - v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix()); - } - else - assert(false); - + const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement; + v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix()); break; } default: { assert(false); break; } @@ -913,6 +901,88 @@ void Selection::translate(const Vec3d& displacement, bool local) #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Rotate an object around one of the axes. Only one rotation component is expected to be changing. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) +{ + if (!m_valid) + return; + + assert(transformation_type.relative() || (transformation_type.absolute() && transformation_type.local())); + + const Transform3d rotation_matrix = Geometry::rotation_transform(rotation); + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + const VolumeCache& volume_data = m_cache.volumes_data[i]; + if (m_mode == Instance && !is_wipe_tower()) { + assert(is_from_fully_selected_instance(i)); + const Geometry::Transformation& old_trafo = volume_data.get_instance_transform(); + Transform3d new_rotation_matrix = Transform3d::Identity(); + if (transformation_type.absolute()) + new_rotation_matrix = rotation_matrix; + else { + if (transformation_type.world()) + new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix(); + else if (transformation_type.local()) + new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix; + else + assert(false); + } + + const Vec3d new_offset = transformation_type.independent() ? old_trafo.get_offset() : + m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() * + (old_trafo.get_offset() - m_cache.dragging_center); + v.set_instance_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix, + old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix())); + } + else { + const Geometry::Transformation& old_trafo = volume_data.get_volume_transform(); + Transform3d new_rotation_matrix = Transform3d::Identity(); + + if (transformation_type.absolute()) + new_rotation_matrix = rotation_matrix; + else { + if (transformation_type.world()) { + const Transform3d inst_rotation_matrix = volume_data.get_instance_transform().get_rotation_matrix(); + new_rotation_matrix = inst_rotation_matrix.inverse() * rotation_matrix * inst_rotation_matrix * old_trafo.get_rotation_matrix(); + } + else if (transformation_type.instance()) + new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix(); + else if (transformation_type.local()) + new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix; + else + assert(false); + } + + const Vec3d new_offset = !is_wipe_tower() ? old_trafo.get_offset() : + m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() * + (old_trafo.get_offset() - m_cache.dragging_center); + v.set_volume_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix, + old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix())); + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) { + int rot_axis_max = 0; + rotation.cwiseAbs().maxCoeff(&rot_axis_max); + SyncRotationType synch; + if (transformation_type.world() && rot_axis_max == 2) + synch = SyncRotationType::NONE; + else if (transformation_type.local()) + synch = SyncRotationType::FULL; + else + synch = SyncRotationType::GENERAL; + synchronize_unselected_instances(synch); + } + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} +#else void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) { if (!m_valid) @@ -1073,6 +1143,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::flattening_rotate(const Vec3d& normal) { @@ -2975,7 +3046,13 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) continue; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation(); + const Vec3d inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); + assert(is_rotation_xy_synchronized(inst_rotation_i, inst_rotation_j)); +#else assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES switch (sync_rotation_type) { case SyncRotationType::NONE: { // z only rotation -> synch instance z @@ -2987,17 +3064,29 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ } case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const double z_diff = Geometry::rotation_diff_z(inst_rotation_i, inst_rotation_j); +#else const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; } #if ENABLE_WORLD_COORDINATE case SyncRotationType::FULL: { // generic rotation -> update instance z with the delta of the rotation. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, inst_rotation_j)); +#else const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation())); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& axis = angle_axis.axis(); const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ? - angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, inst_rotation_j); +#else + angle_axis.angle()* axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 012fb0e4d..47a40877c 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -121,16 +121,19 @@ private: private: struct TransformCache { - Vec3d position; - Vec3d rotation; - Vec3d scaling_factor; - Vec3d mirror; - Transform3d rotation_matrix; - Transform3d scale_matrix; - Transform3d mirror_matrix; - Transform3d full_matrix; + Vec3d position{ Vec3d::Zero() }; + Vec3d rotation{ Vec3d::Zero() }; + Vec3d scaling_factor{ Vec3d::Ones() }; + Vec3d mirror{ Vec3d::Ones() }; + Transform3d rotation_matrix{ Transform3d::Identity() }; + Transform3d scale_matrix{ Transform3d::Identity() }; + Transform3d mirror_matrix{ Transform3d::Identity() }; + Transform3d full_matrix{ Transform3d::Identity() }; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Geometry::Transformation transform; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - TransformCache(); + TransformCache() = default; explicit TransformCache(const Geometry::Transformation& transform); }; @@ -142,13 +145,18 @@ private: VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); const Vec3d& get_volume_position() const { return m_volume.position; } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& get_volume_rotation() const { return m_volume.rotation; } const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; } const Vec3d& get_volume_mirror() const { return m_volume.mirror; } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; } const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; } const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; } const Transform3d& get_volume_full_matrix() const { return m_volume.full_matrix; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& get_volume_transform() const { return m_volume.transform; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& get_instance_position() const { return m_instance.position; } const Vec3d& get_instance_rotation() const { return m_instance.rotation; } @@ -158,6 +166,9 @@ private: const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; } const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& get_instance_transform() const { return m_instance.transform; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES }; public: From 0e3490620e2b835b7382d23b6d0022205e9293f4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 4 May 2022 13:31:19 +0200 Subject: [PATCH 078/102] Added method const GLVolume* Selection::get_first_volume() const to simplify client code Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectList.cpp | 6 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 40 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 832 ++++++------- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 12 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 23 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 21 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 52 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 1134 +++++++++--------- src/slic3r/GUI/Plater.cpp | 6 +- src/slic3r/GUI/Selection.cpp | 49 +- src/slic3r/GUI/Selection.hpp | 1 + 13 files changed, 1094 insertions(+), 1092 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 216c2f472..ecf015b6d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1530,7 +1530,7 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo const BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx); // First (any) GLVolume of the selected instance. They all share the same instance matrix. - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); const Geometry::Transformation inst_transform = v->get_instance_transformation(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d inv_inst_transform = inst_transform.get_matrix_no_offset().inverse(); @@ -1654,7 +1654,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode ModelVolume *new_volume = model_object.add_volume(std::move(mesh), type); // First (any) GLVolume of the selected instance. They all share the same instance matrix. - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); @@ -3282,7 +3282,7 @@ void ObjectList::update_selections() #else else if (selection.is_single_volume() || selection.is_any_modifier()) { #endif // ENABLE_WORLD_COORDINATE - const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); + const auto gl_vol = selection.get_first_volume(); if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) return; } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c73f5eb4a..ddb878a7d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -287,7 +287,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : #else if (selection.is_single_volume() || selection.is_single_modifier()) { #endif // ENABLE_WORLD_COORDINATE - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + GLVolume* volume = const_cast(selection.get_first_volume()); volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); } else if (selection.is_single_full_instance()) { @@ -351,7 +351,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : #if ENABLE_WORLD_COORDINATE if (selection.is_single_volume_or_modifier()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); const double min_z = get_volume_min_z(*volume); if (!is_world_coordinates()) { #if ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -371,7 +371,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : } #else if (selection.is_single_volume() || selection.is_single_modifier()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (get_volume_min_z(*volume) * Vec3d::UnitZ()); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); @@ -384,7 +384,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : #if ENABLE_WORLD_COORDINATE const double min_z = selection.get_scaled_instance_bounding_box().min.z(); if (!is_world_coordinates()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); #else @@ -432,7 +432,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : #else if (selection.is_single_volume() || selection.is_single_modifier()) #endif // ENABLE_WORLD_COORDINATE - const_cast(selection.get_volume(*selection.get_volume_idxs().begin()))->set_volume_rotation(Vec3d::Zero()); + const_cast(selection.get_first_volume())->set_volume_rotation(Vec3d::Zero()); else if (selection.is_single_full_instance()) { for (unsigned int idx : selection.get_volume_idxs()) { const_cast(selection.get_volume(idx))->set_instance_rotation(Vec3d::Zero()); @@ -611,7 +611,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) ObjectList* obj_list = wxGetApp().obj_list(); if (selection.is_single_full_instance()) { // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); #if !ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); #endif // !ENABLE_WORLD_COORDINATE @@ -674,7 +674,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE // the selection contains a single volume - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { const Geometry::Transformation trafo(volume->world_matrix()); @@ -872,18 +872,18 @@ void ObjectManipulation::update_reset_buttons_visibility() if ((m_coordinates_type == ECoordinatesType::World && selection.is_single_full_instance()) || (m_coordinates_type == ECoordinatesType::Instance && selection.is_single_volume_or_modifier())) { const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : - get_volume_min_z(*selection.get_volume(*selection.get_volume_idxs().begin())); + get_volume_min_z(*selection.get_first_volume()); show_drop_to_bed = std::abs(min_z) > EPSILON; } if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); #else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Vec3d rotation; Vec3d scale; double min_z = 0.0; @@ -947,7 +947,7 @@ void ObjectManipulation::update_mirror_buttons_visibility() #else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Vec3d mirror; if (selection.is_single_full_instance()) @@ -1141,7 +1141,7 @@ void ObjectManipulation::change_size_value(int axis, double value) #else if (selection.is_single_volume() || selection.is_single_modifier()) { #endif // ENABLE_WORLD_COORDINATE - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); const Vec3d local_size = size.cwiseQuotient(v->get_instance_scaling_factor()); const Vec3d local_ref_size = v->bounding_box().size().cwiseProduct(v->get_volume_scaling_factor()); const Vec3d local_change = local_size.cwiseQuotient(local_ref_size); @@ -1156,7 +1156,7 @@ void ObjectManipulation::change_size_value(int axis, double value) ref_size = m_world_coordinates ? #endif // ENABLE_WORLD_COORDINATE selection.get_unscaled_instance_bounding_box().size() : - wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); + wxGetApp().model().objects[selection.get_first_volume()->object_idx()]->raw_mesh_bounding_box().size(); #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED this->do_size(axis, size.cwiseQuotient(ref_size)); @@ -1196,10 +1196,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const if (!uniform_scale && is_world_coordinates()) { if (selection.is_single_full_instance()) - scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); + scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); } } @@ -1236,10 +1236,10 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const if (!uniform_scale && is_world_coordinates()) { if (selection.is_single_full_instance()) - scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); + scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); } } @@ -1309,7 +1309,7 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) #endif // ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); // Is the angle close to a multiple of 90 degrees? if (!Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Cannot apply scaling in the world coordinate system. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9a87d5a45..dfad3817c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1,416 +1,416 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoCut.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" - -#include - -#include -#include -#include -#include - -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/GUI_ObjectManipulation.hpp" -#include "libslic3r/AppConfig.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/TriangleMeshSlicer.hpp" - -namespace Slic3r { -namespace GUI { - -const double GLGizmoCut::Offset = 10.0; -const double GLGizmoCut::Margin = 20.0; -static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); - -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{} - -std::string GLGizmoCut::get_tooltip() const -{ - double cut_z = m_cut_z; - if (wxGetApp().app_config->get("use_inches") == "1") - cut_z *= ObjectManipulation::mm_to_in; - - return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : ""; -} - -bool GLGizmoCut::on_mouse(const wxMouseEvent &mouse_event) -{ - return use_grabbers(mouse_event); -} - -bool GLGizmoCut::on_init() -{ - m_grabbers.emplace_back(); - m_shortcut_key = WXK_CONTROL_C; - return true; -} - -std::string GLGizmoCut::on_get_name() const -{ - return _u8L("Cut"); -} - -void GLGizmoCut::on_set_state() -{ - // Reset m_cut_z on gizmo activation - if (m_state == On) - m_cut_z = bounding_box().center().z(); -} - -bool GLGizmoCut::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - return selection.is_single_full_instance() && !selection.is_wipe_tower(); -} - -void GLGizmoCut::on_start_dragging() -{ - if (m_hover_id == -1) - return; - - const BoundingBoxf3 box = bounding_box(); - m_max_z = box.max.z(); - m_start_z = m_cut_z; - m_drag_pos = m_grabbers[m_hover_id].center; - m_drag_center = box.center(); - m_drag_center.z() = m_cut_z; -} - -void GLGizmoCut::on_dragging(const UpdateData &data) -{ - assert(m_hover_id != -1); - set_cut_z(m_start_z + calc_projection(data.mouse_ray)); -} - -void GLGizmoCut::on_render() -{ - const BoundingBoxf3 box = bounding_box(); - Vec3d plane_center = box.center(); - plane_center.z() = m_cut_z; - m_max_z = box.max.z(); - set_cut_z(m_cut_z); - - update_contours(); - - const float min_x = box.min.x() - Margin; - const float max_x = box.max.x() + Margin; - const float min_y = box.min.y() - Margin; - const float max_y = box.max.y() + Margin; - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); - const Vec3d diff = plane_center - m_old_center; - // Z changed when move with cut plane - // X and Y changed when move with cutted object - bool is_changed = std::abs(diff.x()) > EPSILON || - std::abs(diff.y()) > EPSILON || - std::abs(diff.z()) > EPSILON; - m_old_center = plane_center; - - if (!m_plane.is_initialized() || is_changed) { - m_plane.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z())); - init_data.add_vertex(Vec3f(max_x, min_y, plane_center.z())); - init_data.add_vertex(Vec3f(max_x, max_y, plane_center.z())); - init_data.add_vertex(Vec3f(min_x, max_y, plane_center.z())); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_plane.init_from(std::move(init_data)); - } - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_plane.render(); -#else - // Draw the cutting plane - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, plane_center.z()); - ::glVertex3f(max_x, min_y, plane_center.z()); - ::glVertex3f(max_x, max_y, plane_center.z()); - ::glVertex3f(min_x, max_y, plane_center.z()); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); - - // Draw the grabber and the connecting line - m_grabbers[0].center = plane_center; - m_grabbers[0].center.z() = plane_center.z() + Offset; - - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - - glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_grabber_connection.is_initialized() || is_changed) { - m_grabber_connection.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = ColorRGBA::YELLOW(); - init_data.reserve_vertices(2); - init_data.reserve_indices(2); - - // vertices - init_data.add_vertex((Vec3f)plane_center.cast()); - init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); - - // indices - init_data.add_line(0, 1); - - m_grabber_connection.init_from(std::move(init_data)); - } - - m_grabber_connection.render(); - - shader->stop_using(); - } - - shader = wxGetApp().get_shader("gouraud_light"); -#else - glsafe(::glColor3f(1.0, 1.0, 0.0)); - ::glBegin(GL_LINES); - ::glVertex3dv(plane_center.data()); - ::glVertex3dv(m_grabbers[0].center.data()); - glsafe(::glEnd()); - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (shader != nullptr) { - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); - - m_grabbers[0].color = GRABBER_COLOR; - m_grabbers[0].render(m_hover_id == 0, float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); - - shader->stop_using(); - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()* Geometry::assemble_transform(m_cut_contours.shift)); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glLineWidth(2.0f)); - m_cut_contours.contours.render(); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -void GLGizmoCut::on_render_for_picking() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); - render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); -} - -void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) -{ - static float last_y = 0.0f; - static float last_h = 0.0f; - - m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - - // adjust window position to avoid overlap the view toolbar - const float win_h = ImGui::GetWindowHeight(); - y = std::min(y, bottom_limit - win_h); - ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if (last_h != win_h || last_y != y) { - // ask canvas for another frame to render the window in the correct position - m_imgui->set_requires_extra_frame(); - if (last_h != win_h) - last_h = win_h; - if (last_y != y) - last_y = y; - } - - ImGui::AlignTextToFramePadding(); - m_imgui->text("Z"); - ImGui::SameLine(); - ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f); - - double cut_z = m_cut_z; - if (imperial_units) - cut_z *= ObjectManipulation::mm_to_in; - ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); - - ImGui::SameLine(); - m_imgui->text(imperial_units ? _L("in") : _L("mm")); - - m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0); - - ImGui::Separator(); - - m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); - m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); - m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); - - ImGui::Separator(); - - m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z); - const bool cut_clicked = m_imgui->button(_L("Perform cut")); - m_imgui->disabled_end(); - - m_imgui->end(); - - if (cut_clicked && (m_keep_upper || m_keep_lower)) - perform_cut(m_parent.get_selection()); -} - -void GLGizmoCut::set_cut_z(double cut_z) -{ - // Clamp the plane to the object's bounding box - m_cut_z = std::clamp(cut_z, 0.0, m_max_z); -} - -void GLGizmoCut::perform_cut(const Selection& selection) -{ - const int instance_idx = selection.get_instance_idx(); - const int object_idx = selection.get_object_idx(); - - wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); - - // m_cut_z is the distance from the bed. Subtract possible SLA elevation. - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z(); - - if (0.0 < object_cut_z && object_cut_z < m_max_z) - wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, - only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | - only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | - only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); - else { - // the object is SLA-elevated and the plane is under it. - } -} - -double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const -{ - double projection = 0.0; - - const Vec3d starting_vec = m_drag_pos - m_drag_center; - const double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) { - const Vec3d mouse_dir = mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - const Vec3d inters_vec = inters - m_drag_pos; - - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec.normalized()); - } - return projection; -} - -BoundingBoxf3 GLGizmoCut::bounding_box() const -{ - BoundingBoxf3 ret; - const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - return selection.get_bounding_box(); - - for (unsigned int i : idxs) { - const GLVolume* volume = selection.get_volume(i); - if (!volume->is_modifier) - ret.merge(volume->transformed_convex_hull_bounding_box()); - } - return ret; -} - -void GLGizmoCut::update_contours() -{ - const Selection& selection = m_parent.get_selection(); - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box(); - - const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()]; - const int instance_idx = selection.get_instance_idx(); - std::vector volumes_idxs = std::vector(model_object->volumes.size()); - for (size_t i = 0; i < model_object->volumes.size(); ++i) { - volumes_idxs[i] = model_object->volumes[i]->id(); - } - - if (0.0 < m_cut_z && m_cut_z < m_max_z) { - if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || - m_cut_contours.instance_idx != instance_idx || m_cut_contours.volumes_idxs != volumes_idxs) { - m_cut_contours.cut_z = m_cut_z; - - if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs) - m_cut_contours.mesh = model_object->raw_mesh(); - - m_cut_contours.position = box.center(); - m_cut_contours.shift = Vec3d::Zero(); - m_cut_contours.object_id = model_object->id(); - m_cut_contours.instance_idx = instance_idx; - m_cut_contours.volumes_idxs = volumes_idxs; - m_cut_contours.contours.reset(); - - MeshSlicingParams slicing_params; - slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix(); - slicing_params.trafo.pretranslate(Vec3d(0., 0., first_glvolume->get_sla_shift_z())); - - const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); - if (!polys.empty()) { - m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_cut_contours.contours.set_color(ColorRGBA::WHITE()); -#else - m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - else if (box.center() != m_cut_contours.position) { - m_cut_contours.shift = box.center() - m_cut_contours.position; - } - } - else - m_cut_contours.contours.reset(); -} - -} // namespace GUI -} // namespace Slic3r +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoCut.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" + +#include + +#include +#include +#include +#include + +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" + +namespace Slic3r { +namespace GUI { + +const double GLGizmoCut::Offset = 10.0; +const double GLGizmoCut::Margin = 20.0; +static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); + +GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{} + +std::string GLGizmoCut::get_tooltip() const +{ + double cut_z = m_cut_z; + if (wxGetApp().app_config->get("use_inches") == "1") + cut_z *= ObjectManipulation::mm_to_in; + + return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : ""; +} + +bool GLGizmoCut::on_mouse(const wxMouseEvent &mouse_event) +{ + return use_grabbers(mouse_event); +} + +bool GLGizmoCut::on_init() +{ + m_grabbers.emplace_back(); + m_shortcut_key = WXK_CONTROL_C; + return true; +} + +std::string GLGizmoCut::on_get_name() const +{ + return _u8L("Cut"); +} + +void GLGizmoCut::on_set_state() +{ + // Reset m_cut_z on gizmo activation + if (m_state == On) + m_cut_z = bounding_box().center().z(); +} + +bool GLGizmoCut::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + return selection.is_single_full_instance() && !selection.is_wipe_tower(); +} + +void GLGizmoCut::on_start_dragging() +{ + if (m_hover_id == -1) + return; + + const BoundingBoxf3 box = bounding_box(); + m_max_z = box.max.z(); + m_start_z = m_cut_z; + m_drag_pos = m_grabbers[m_hover_id].center; + m_drag_center = box.center(); + m_drag_center.z() = m_cut_z; +} + +void GLGizmoCut::on_dragging(const UpdateData &data) +{ + assert(m_hover_id != -1); + set_cut_z(m_start_z + calc_projection(data.mouse_ray)); +} + +void GLGizmoCut::on_render() +{ + const BoundingBoxf3 box = bounding_box(); + Vec3d plane_center = box.center(); + plane_center.z() = m_cut_z; + m_max_z = box.max.z(); + set_cut_z(m_cut_z); + + update_contours(); + + const float min_x = box.min.x() - Margin; + const float max_x = box.max.x() + Margin; + const float min_y = box.min.y() - Margin; + const float max_y = box.max.y() + Margin; + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + const Vec3d diff = plane_center - m_old_center; + // Z changed when move with cut plane + // X and Y changed when move with cutted object + bool is_changed = std::abs(diff.x()) > EPSILON || + std::abs(diff.y()) > EPSILON || + std::abs(diff.z()) > EPSILON; + m_old_center = plane_center; + + if (!m_plane.is_initialized() || is_changed) { + m_plane.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z())); + init_data.add_vertex(Vec3f(max_x, min_y, plane_center.z())); + init_data.add_vertex(Vec3f(max_x, max_y, plane_center.z())); + init_data.add_vertex(Vec3f(min_x, max_y, plane_center.z())); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_plane.init_from(std::move(init_data)); + } + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_plane.render(); +#else + // Draw the cutting plane + ::glBegin(GL_QUADS); + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, plane_center.z()); + ::glVertex3f(max_x, min_y, plane_center.z()); + ::glVertex3f(max_x, max_y, plane_center.z()); + ::glVertex3f(min_x, max_y, plane_center.z()); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); + + // Draw the grabber and the connecting line + m_grabbers[0].center = plane_center; + m_grabbers[0].center.z() = plane_center.z() + Offset; + + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + + glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!m_grabber_connection.is_initialized() || is_changed) { + m_grabber_connection.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::YELLOW(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex((Vec3f)plane_center.cast()); + init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); + + // indices + init_data.add_line(0, 1); + + m_grabber_connection.init_from(std::move(init_data)); + } + + m_grabber_connection.render(); + + shader->stop_using(); + } + + shader = wxGetApp().get_shader("gouraud_light"); +#else + glsafe(::glColor3f(1.0, 1.0, 0.0)); + ::glBegin(GL_LINES); + ::glVertex3dv(plane_center.data()); + ::glVertex3dv(m_grabbers[0].center.data()); + glsafe(::glEnd()); + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + + m_grabbers[0].color = GRABBER_COLOR; + m_grabbers[0].render(m_hover_id == 0, float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); + + shader->stop_using(); + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()* Geometry::assemble_transform(m_cut_contours.shift)); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glLineWidth(2.0f)); + m_cut_contours.contours.render(); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +void GLGizmoCut::on_render_for_picking() +{ + glsafe(::glDisable(GL_DEPTH_TEST)); + render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); +} + +void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) +{ + static float last_y = 0.0f; + static float last_h = 0.0f; + + m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + + // adjust window position to avoid overlap the view toolbar + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + ImGui::AlignTextToFramePadding(); + m_imgui->text("Z"); + ImGui::SameLine(); + ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f); + + double cut_z = m_cut_z; + if (imperial_units) + cut_z *= ObjectManipulation::mm_to_in; + ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); + + ImGui::SameLine(); + m_imgui->text(imperial_units ? _L("in") : _L("mm")); + + m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0); + + ImGui::Separator(); + + m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); + m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); + m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); + + ImGui::Separator(); + + m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z); + const bool cut_clicked = m_imgui->button(_L("Perform cut")); + m_imgui->disabled_end(); + + m_imgui->end(); + + if (cut_clicked && (m_keep_upper || m_keep_lower)) + perform_cut(m_parent.get_selection()); +} + +void GLGizmoCut::set_cut_z(double cut_z) +{ + // Clamp the plane to the object's bounding box + m_cut_z = std::clamp(cut_z, 0.0, m_max_z); +} + +void GLGizmoCut::perform_cut(const Selection& selection) +{ + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); + + wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); + + // m_cut_z is the distance from the bed. Subtract possible SLA elevation. + const GLVolume* first_glvolume = selection.get_first_volume(); + const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z(); + + if (0.0 < object_cut_z && object_cut_z < m_max_z) + wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, + only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | + only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); + else { + // the object is SLA-elevated and the plane is under it. + } +} + +double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const +{ + double projection = 0.0; + + const Vec3d starting_vec = m_drag_pos - m_drag_center; + const double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) { + const Vec3d mouse_dir = mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + const Vec3d inters_vec = inters - m_drag_pos; + + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec.normalized()); + } + return projection; +} + +BoundingBoxf3 GLGizmoCut::bounding_box() const +{ + BoundingBoxf3 ret; + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + return selection.get_bounding_box(); + + for (unsigned int i : idxs) { + const GLVolume* volume = selection.get_volume(i); + if (!volume->is_modifier) + ret.merge(volume->transformed_convex_hull_bounding_box()); + } + return ret; +} + +void GLGizmoCut::update_contours() +{ + const Selection& selection = m_parent.get_selection(); + const GLVolume* first_glvolume = selection.get_first_volume(); + const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box(); + + const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()]; + const int instance_idx = selection.get_instance_idx(); + std::vector volumes_idxs = std::vector(model_object->volumes.size()); + for (size_t i = 0; i < model_object->volumes.size(); ++i) { + volumes_idxs[i] = model_object->volumes[i]->id(); + } + + if (0.0 < m_cut_z && m_cut_z < m_max_z) { + if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || + m_cut_contours.instance_idx != instance_idx || m_cut_contours.volumes_idxs != volumes_idxs) { + m_cut_contours.cut_z = m_cut_z; + + if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs) + m_cut_contours.mesh = model_object->raw_mesh(); + + m_cut_contours.position = box.center(); + m_cut_contours.shift = Vec3d::Zero(); + m_cut_contours.object_id = model_object->id(); + m_cut_contours.instance_idx = instance_idx; + m_cut_contours.volumes_idxs = volumes_idxs; + m_cut_contours.contours.reset(); + + MeshSlicingParams slicing_params; + slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix(); + slicing_params.trafo.pretranslate(Vec3d(0., 0., first_glvolume->get_sla_shift_z())); + + const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); + if (!polys.empty()) { + m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_cut_contours.contours.set_color(ColorRGBA::WHITE()); +#else + m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + else if (box.center() != m_cut_contours.position) { + m_cut_contours.shift = box.center() - m_cut_contours.position; + } + } + else + m_cut_contours.contours.reset(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index b882570fc..9bac7f293 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -118,17 +118,17 @@ void GLGizmoFlatten::on_render() glsafe(::glEnable(GL_BLEND)); if (selection.is_single_full_instance()) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + const Transform3d& m = selection.get_first_volume()->get_instance_transformation().get_matrix(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + Geometry::assemble_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * m; shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #else glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); + glsafe(::glTranslatef(0.f, 0.f, selection.get_first_volume()->get_sla_shift_z())); glsafe(::glMultMatrixd(m.data())); #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (this->is_plane_update_necessary()) @@ -172,17 +172,17 @@ void GLGizmoFlatten::on_render_for_picking() glsafe(::glDisable(GL_BLEND)); if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + const Transform3d& m = selection.get_first_volume()->get_instance_transformation().get_matrix(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + Geometry::assemble_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * m; shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #else glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); + glsafe(::glTranslatef(0.f, 0.f, selection.get_first_volume()->get_sla_shift_z())); glsafe(::glMultMatrixd(m.data())); #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (this->is_plane_update_necessary()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index a60da3591..15030a0fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -94,7 +94,7 @@ void GLGizmoHollow::on_render_for_picking() { const Selection& selection = m_parent.get_selection(); //#if ENABLE_RENDER_PICKING_PASS -// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); +// m_z_shift = selection.get_first_volume()->get_sla_shift_z(); //#endif glsafe(::glEnable(GL_DEPTH_TEST)); @@ -117,7 +117,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); #endif // ENABLE_LEGACY_OPENGL_REMOVAL - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* vol = selection.get_first_volume(); Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation(); #if ENABLE_GL_SHADERS_ATTRIBUTES @@ -242,7 +242,7 @@ bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pairget_camera(); const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 42fe796a4..afb0b9140 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -97,7 +97,7 @@ void GLGizmoMove3D::on_start_dragging() if (coordinates_type == ECoordinatesType::World) m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; #else @@ -105,7 +105,7 @@ void GLGizmoMove3D::on_start_dragging() #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; #else @@ -315,7 +315,7 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -359,7 +359,7 @@ void GLGizmoMove3D::on_render() #endif // ENABLE_GL_SHADERS_ATTRIBUTES #else render_grabber_extension((Axis)m_hover_id, box, false); -#endif // ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_GIZMO_GRABBER_REFACTOR } @@ -508,7 +508,7 @@ Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const { Transform3d ret = Geometry::assemble_transform(m_center); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); #else @@ -530,7 +530,7 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); @@ -548,7 +548,7 @@ void GLGizmoMove3D::calc_selection_box_and_center() m_center = m_bounding_box.center(); } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box = v.transformed_convex_hull_bounding_box( v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); @@ -565,11 +565,12 @@ void GLGizmoMove3D::calc_selection_box_and_center() m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } #if ENABLE_TRANSFORMATIONS_BY_MATRICES - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center(); + const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation(); + m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix()); + m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center(); #else - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); + m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); + m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 51d089873..a89173460 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -301,7 +301,7 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_center = m_bounding_box.center(); } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box = v.transformed_convex_hull_bounding_box( v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); @@ -318,11 +318,12 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } #if ENABLE_TRANSFORMATIONS_BY_MATRICES - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center(); + const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation(); + m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix()); + m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center(); #else - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); + m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); + m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } @@ -339,7 +340,7 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) #else else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); #else @@ -347,7 +348,7 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix(); #else @@ -789,7 +790,7 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const return Geometry::assemble_transform(m_center) * m_orient_matrix * ret; #else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) - ret = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true) * ret; + ret = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true) * ret; return Geometry::assemble_transform(m_center) * ret; #endif // ENABLE_WORLD_COORDINATE @@ -803,7 +804,7 @@ void GLGizmoRotate::transform_to_local(const Selection& selection) const glsafe(::glMultMatrixd(m_orient_matrix.data())); #else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) { - const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + const Transform3d orient_matrix = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } #endif // ENABLE_WORLD_COORDINATE @@ -864,7 +865,7 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons m = m * m_orient_matrix.inverse(); #else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) - m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse(); + m = m * selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true).inverse(); #endif // ENABLE_WORLD_COORDINATE m.translate(-m_center); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 05df5f193..675ed0b3f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -45,13 +45,13 @@ std::string GLGizmoScale3D::get_tooltip() const Vec3d scale = 100.0 * Vec3d::Ones(); if (selection.is_single_full_instance()) - scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor(); + scale = 100.0 * selection.get_first_volume()->get_instance_scaling_factor(); #if ENABLE_WORLD_COORDINATE else if (selection.is_single_volume_or_modifier()) #else else if (selection.is_single_modifier() || selection.is_single_volume()) #endif // ENABLE_WORLD_COORDINATE - scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor(); + scale = 100.0 * selection.get_first_volume()->get_volume_scaling_factor(); if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) return "X: " + format(scale.x(), 4) + "%"; @@ -99,16 +99,14 @@ void GLGizmoScale3D::data_changed() if (enable_scale_xyz) { // all volumes in the selection belongs to the same instance, any of // them contains the needed data, so we take the first - const GLVolume *volume = selection.get_volume(*selection.get_volume_idxs().begin()); - if (selection.is_single_full_instance()) { + const GLVolume* volume = selection.get_first_volume(); + if (selection.is_single_full_instance()) set_scale(volume->get_instance_scaling_factor()); - } else if (selection.is_single_volume() || - selection.is_single_modifier()) { + else if (selection.is_single_volume() || selection.is_single_modifier()) set_scale(volume->get_volume_scaling_factor()); - } - } else { - set_scale(Vec3d::Ones()); } + else + set_scale(Vec3d::Ones()); } bool GLGizmoScale3D::on_init() @@ -220,14 +218,14 @@ void GLGizmoScale3D::on_render() #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_scaling_factor_matrix()); + m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix()); #else - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); + m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #endif // ENABLE_WORLD_COORDINATE // gets transform from first selected volume - const GLVolume& v = *selection.get_volume(*idxs.begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor(); @@ -235,7 +233,7 @@ void GLGizmoScale3D::on_render() m_center = inst_trafo * m_bounding_box.center(); #else m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); - m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); + m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_instance_center = v.get_instance_offset(); } @@ -251,7 +249,7 @@ void GLGizmoScale3D::on_render() } else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box.merge(v.transformed_convex_hull_bounding_box( @@ -267,7 +265,7 @@ void GLGizmoScale3D::on_render() m_instance_center = m_center; } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box.merge(v.transformed_convex_hull_bounding_box( v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix())); @@ -285,7 +283,7 @@ void GLGizmoScale3D::on_render() m_bounding_box = selection.get_bounding_box(); m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center()); m_center = m_bounding_box.center(); - m_instance_center = selection.is_single_full_instance() ? selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_offset() : m_center; + m_instance_center = selection.is_single_full_instance() ? selection.get_first_volume()->get_instance_offset() : m_center; } #else m_bounding_box = v.bounding_box(); @@ -668,13 +666,13 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { if (selection.is_single_full_instance()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()); curr_scale = (m * curr_scale).cwiseAbs(); starting_scale = (m * starting_scale).cwiseAbs(); } else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()); const Transform3d m = mi * mv; curr_scale = (m * curr_scale).cwiseAbs(); starting_scale = (m * starting_scale).cwiseAbs(); @@ -685,10 +683,10 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (coordinates_type == ECoordinatesType::World) { if (selection.is_single_full_instance()) - m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); + m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); m_scale = (mv * mi * curr_scale).cwiseAbs(); } else @@ -714,7 +712,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #if ENABLE_WORLD_COORDINATE Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -763,7 +761,7 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -816,7 +814,7 @@ Transform3d GLGizmoScale3D::local_transform(const Selection& selection) const { Transform3d ret = Geometry::assemble_transform(m_center); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); #else @@ -838,9 +836,9 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { - Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + Transform3d orient_matrix = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true); if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) - orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); + orient_matrix = orient_matrix * selection.get_first_volume()->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index f72ce3206..25b918e4b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -146,7 +146,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) }); #endif // ENABLE_LEGACY_OPENGL_REMOVAL - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* vol = selection.get_first_volume(); Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); @@ -361,7 +361,7 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pairget_camera(); const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index f1156f937..a77c1dd30 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -1,567 +1,567 @@ -#include "GLGizmosCommon.hpp" - -#include - -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "libslic3r/SLAPrint.hpp" -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Plater.hpp" - -#include "libslic3r/PresetBundle.hpp" - -#include - -namespace Slic3r { -namespace GUI { - -using namespace CommonGizmosDataObjects; - -CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas) - : m_canvas(canvas) -{ - using c = CommonGizmosDataID; - m_data[c::SelectionInfo].reset( new SelectionInfo(this)); - m_data[c::InstancesHider].reset( new InstancesHider(this)); - m_data[c::HollowedMesh].reset( new HollowedMesh(this)); - m_data[c::Raycaster].reset( new Raycaster(this)); - m_data[c::ObjectClipper].reset( new ObjectClipper(this)); - m_data[c::SupportsClipper].reset( new SupportsClipper(this)); - -} - -void CommonGizmosDataPool::update(CommonGizmosDataID required) -{ - assert(check_dependencies(required)); - for (auto& [id, data] : m_data) { - if (int(required) & int(CommonGizmosDataID(id))) - data->update(); - else - if (data->is_valid()) - data->release(); - - } -} - - -SelectionInfo* CommonGizmosDataPool::selection_info() const -{ - SelectionInfo* sel_info = dynamic_cast(m_data.at(CommonGizmosDataID::SelectionInfo).get()); - assert(sel_info); - return sel_info->is_valid() ? sel_info : nullptr; -} - - -InstancesHider* CommonGizmosDataPool::instances_hider() const -{ - InstancesHider* inst_hider = dynamic_cast(m_data.at(CommonGizmosDataID::InstancesHider).get()); - assert(inst_hider); - return inst_hider->is_valid() ? inst_hider : nullptr; -} - -HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const -{ - HollowedMesh* hol_mesh = dynamic_cast(m_data.at(CommonGizmosDataID::HollowedMesh).get()); - assert(hol_mesh); - return hol_mesh->is_valid() ? hol_mesh : nullptr; -} - -Raycaster* CommonGizmosDataPool::raycaster() const -{ - Raycaster* rc = dynamic_cast(m_data.at(CommonGizmosDataID::Raycaster).get()); - assert(rc); - return rc->is_valid() ? rc : nullptr; -} - -ObjectClipper* CommonGizmosDataPool::object_clipper() const -{ - ObjectClipper* oc = dynamic_cast(m_data.at(CommonGizmosDataID::ObjectClipper).get()); - // ObjectClipper is used from outside the gizmos to report current clipping plane. - // This function can be called when oc is nullptr. - return (oc && oc->is_valid()) ? oc : nullptr; -} - -SupportsClipper* CommonGizmosDataPool::supports_clipper() const -{ - SupportsClipper* sc = dynamic_cast(m_data.at(CommonGizmosDataID::SupportsClipper).get()); - assert(sc); - return sc->is_valid() ? sc : nullptr; -} - -#ifndef NDEBUG -// Check the required resources one by one and return true if all -// dependencies are met. -bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const -{ - // This should iterate over currently required data. Each of them should - // be asked about its dependencies and it must check that all dependencies - // are also in required and before the current one. - for (auto& [id, data] : m_data) { - // in case we don't use this, the deps are irrelevant - if (! (int(required) & int(CommonGizmosDataID(id)))) - continue; - - - CommonGizmosDataID deps = data->get_dependencies(); - assert(int(deps) == (int(deps) & int(required))); - } - - - return true; -} -#endif // NDEBUG - - - - -void SelectionInfo::on_update() -{ - const Selection& selection = get_pool()->get_canvas()->get_selection(); - if (selection.is_single_full_instance()) { - m_model_object = selection.get_model()->objects[selection.get_object_idx()]; - m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); - } - else - m_model_object = nullptr; -} - -void SelectionInfo::on_release() -{ - m_model_object = nullptr; -} - -int SelectionInfo::get_active_instance() const -{ - const Selection& selection = get_pool()->get_canvas()->get_selection(); - return selection.get_instance_idx(); -} - - - - - -void InstancesHider::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - int active_inst = get_pool()->selection_info()->get_active_instance(); - GLCanvas3D* canvas = get_pool()->get_canvas(); - - if (mo && active_inst != -1) { - canvas->toggle_model_objects_visibility(false); - canvas->toggle_model_objects_visibility(true, mo, active_inst); - canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst); - canvas->set_use_clipping_planes(true); - // Some objects may be sinking, do not show whatever is below the bed. - canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits::max())); - - - std::vector meshes; - for (const ModelVolume* mv : mo->volumes) - meshes.push_back(&mv->mesh()); - - if (meshes != m_old_meshes) { - m_clippers.clear(); - for (const TriangleMesh* mesh : meshes) { - m_clippers.emplace_back(new MeshClipper); - m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - m_clippers.back()->set_mesh(*mesh); - } - m_old_meshes = meshes; - } - } - else - canvas->toggle_model_objects_visibility(true); -} - -void InstancesHider::on_release() -{ - get_pool()->get_canvas()->toggle_model_objects_visibility(true); - get_pool()->get_canvas()->set_use_clipping_planes(false); - m_old_meshes.clear(); - m_clippers.clear(); -} - -void InstancesHider::show_supports(bool show) { - if (m_show_supports != show) { - m_show_supports = show; - on_update(); - } -} - -void InstancesHider::render_cut() const -{ - const SelectionInfo* sel_info = get_pool()->selection_info(); - const ModelObject* mo = sel_info->model_object(); - Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); - - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_transformation(trafo); - const ObjectClipper* obj_clipper = get_pool()->object_clipper(); - if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane() - && obj_clipper->get_position() != 0.) { - ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane(); - clp.set_normal(-clp.get_normal()); - clipper->set_limiting_plane(clp); - } - else - clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#if !ENABLE_LEGACY_OPENGL_REMOVAL - if (mv->is_model_part()) - glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); - else { - const ColorRGBA color = color_from_model_volume(*mv); - glsafe(::glColor4fv(color.data())); - } -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glPushAttrib(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_DEPTH_TEST)); -#if ENABLE_LEGACY_OPENGL_REMOVAL - clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); -#else - clipper->render_cut(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glPopAttrib()); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - ++clipper_id; - } -} - - - -void HollowedMesh::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; - if (! mo || ! is_sla) - return; - - const GLCanvas3D* canvas = get_pool()->get_canvas(); - const PrintObjects& print_objects = canvas->sla_print()->objects(); - const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) - ? print_objects[m_print_object_idx] - : nullptr; - - // Find the respective SLAPrintObject. - if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { - m_print_objects_count = print_objects.size(); - m_print_object_idx = -1; - for (const SLAPrintObject* po : print_objects) { - ++m_print_object_idx; - if (po->model_object()->id() == mo->id()) { - print_object = po; - break; - } - } - } - - // If there is a valid SLAPrintObject, check state of Hollowing step. - if (print_object) { - if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { - size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; - if (timestamp > m_old_hollowing_timestamp) { - const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); - if (! backend_mesh.empty()) { - m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); - Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse(); - m_hollowed_mesh_transformed->transform(trafo_inv); - m_drainholes = print_object->model_object()->sla_drain_holes; - m_old_hollowing_timestamp = timestamp; - - indexed_triangle_set interior = print_object->hollowed_interior_mesh(); - its_flip_triangles(interior); - m_hollowed_interior_transformed = std::make_unique(std::move(interior)); - m_hollowed_interior_transformed->transform(trafo_inv); - } - else { - m_hollowed_mesh_transformed.reset(nullptr); - } - } - } - else - m_hollowed_mesh_transformed.reset(nullptr); - } -} - - -void HollowedMesh::on_release() -{ - m_hollowed_mesh_transformed.reset(); - m_old_hollowing_timestamp = 0; - m_print_object_idx = -1; -} - - -const TriangleMesh* HollowedMesh::get_hollowed_mesh() const -{ - return m_hollowed_mesh_transformed.get(); -} - -const TriangleMesh* HollowedMesh::get_hollowed_interior() const -{ - return m_hollowed_interior_transformed.get(); -} - - - - -void Raycaster::on_update() -{ - wxBusyCursor wait; - const ModelObject* mo = get_pool()->selection_info()->model_object(); - - if (! mo) - return; - - std::vector meshes; - const std::vector& mvs = mo->volumes; - if (mvs.size() == 1) { - assert(mvs.front()->is_model_part()); - const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh(); - if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh()) - meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh()); - } - if (meshes.empty()) { - for (const ModelVolume* mv : mvs) { - if (mv->is_model_part()) - meshes.push_back(&mv->mesh()); - } - } - - if (meshes != m_old_meshes) { - m_raycasters.clear(); - for (const TriangleMesh* mesh : meshes) - m_raycasters.emplace_back(new MeshRaycaster(*mesh)); - m_old_meshes = meshes; - } -} - -void Raycaster::on_release() -{ - m_raycasters.clear(); - m_old_meshes.clear(); -} - -std::vector Raycaster::raycasters() const -{ - std::vector mrcs; - for (const auto& raycaster_unique_ptr : m_raycasters) - mrcs.push_back(raycaster_unique_ptr.get()); - return mrcs; -} - - - - - -void ObjectClipper::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - if (! mo) - return; - - // which mesh should be cut? - std::vector meshes; - bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh(); - if (has_hollowed) - meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh()); - - if (meshes.empty()) - for (const ModelVolume* mv : mo->volumes) - meshes.push_back(&mv->mesh()); - - if (meshes != m_old_meshes) { - m_clippers.clear(); - for (const TriangleMesh* mesh : meshes) { - m_clippers.emplace_back(new MeshClipper); - m_clippers.back()->set_mesh(*mesh); - } - m_old_meshes = meshes; - - if (has_hollowed) - m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); - - m_active_inst_bb_radius = - mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); - } -} - - -void ObjectClipper::on_release() -{ - m_clippers.clear(); - m_old_meshes.clear(); - m_clp.reset(); - m_clp_ratio = 0.; - -} - -void ObjectClipper::render_cut() const -{ - if (m_clp_ratio == 0.) - return; - const SelectionInfo* sel_info = get_pool()->selection_info(); - const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); - - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - const Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_plane(*m_clp); - clipper->set_transformation(trafo); - clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_LEGACY_OPENGL_REMOVAL - clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); -#else - glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); - clipper->render_cut(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - ++clipper_id; - } -} - - -void ObjectClipper::set_position(double pos, bool keep_normal) -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - int active_inst = get_pool()->selection_info()->get_active_instance(); - double z_shift = get_pool()->selection_info()->get_sla_shift(); - - Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward(); - const Vec3d& center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift); - float dist = normal.dot(center); - - if (pos < 0.) - pos = m_clp_ratio; - - m_clp_ratio = pos; - m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2*m_active_inst_bb_radius))); - get_pool()->get_canvas()->set_as_dirty(); -} - - - -void SupportsClipper::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; - if (! mo || ! is_sla) - return; - - const GLCanvas3D* canvas = get_pool()->get_canvas(); - const PrintObjects& print_objects = canvas->sla_print()->objects(); - const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) - ? print_objects[m_print_object_idx] - : nullptr; - - // Find the respective SLAPrintObject. - if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { - m_print_objects_count = print_objects.size(); - m_print_object_idx = -1; - for (const SLAPrintObject* po : print_objects) { - ++m_print_object_idx; - if (po->model_object()->id() == mo->id()) { - print_object = po; - break; - } - } - } - - if (print_object - && print_object->is_step_done(slaposSupportTree) - && ! print_object->support_mesh().empty()) - { - // If the supports are already calculated, save the timestamp of the respective step - // so we can later tell they were recalculated. - size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp; - if (! m_clipper || timestamp != m_old_timestamp) { - // The timestamp has changed. - m_clipper.reset(new MeshClipper); - // The mesh should already have the shared vertices calculated. - m_clipper->set_mesh(print_object->support_mesh()); - m_old_timestamp = timestamp; - } - } - else - // The supports are not valid. We better dump the cached data. - m_clipper.reset(); -} - - -void SupportsClipper::on_release() -{ - m_clipper.reset(); - m_old_timestamp = 0; - m_print_object_idx = -1; -} - -void SupportsClipper::render_cut() const -{ - const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper(); - if (ocl->get_position() == 0. - || ! get_pool()->instances_hider()->are_supports_shown() - || ! m_clipper) - return; - - const SelectionInfo* sel_info = get_pool()->selection_info(); - const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); - //Geometry::Transformation vol_trafo = mo->volumes.front()->get_transformation(); - Geometry::Transformation trafo = inst_trafo;// * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - - // Get transformation of supports - Geometry::Transformation supports_trafo = trafo; - supports_trafo.set_scaling_factor(Vec3d::Ones()); - supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift())); - supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2))); - // I don't know why, but following seems to be correct. - supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2), - 1, - 1.)); - - m_clipper->set_plane(*ocl->get_clipping_plane()); - m_clipper->set_transformation(supports_trafo); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f }); -#else - glsafe(::glColor3f(1.0f, 0.f, 0.37f)); - m_clipper->render_cut(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - - -} // namespace GUI -} // namespace Slic3r +#include "GLGizmosCommon.hpp" + +#include + +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" + +#include "libslic3r/PresetBundle.hpp" + +#include + +namespace Slic3r { +namespace GUI { + +using namespace CommonGizmosDataObjects; + +CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas) + : m_canvas(canvas) +{ + using c = CommonGizmosDataID; + m_data[c::SelectionInfo].reset( new SelectionInfo(this)); + m_data[c::InstancesHider].reset( new InstancesHider(this)); + m_data[c::HollowedMesh].reset( new HollowedMesh(this)); + m_data[c::Raycaster].reset( new Raycaster(this)); + m_data[c::ObjectClipper].reset( new ObjectClipper(this)); + m_data[c::SupportsClipper].reset( new SupportsClipper(this)); + +} + +void CommonGizmosDataPool::update(CommonGizmosDataID required) +{ + assert(check_dependencies(required)); + for (auto& [id, data] : m_data) { + if (int(required) & int(CommonGizmosDataID(id))) + data->update(); + else + if (data->is_valid()) + data->release(); + + } +} + + +SelectionInfo* CommonGizmosDataPool::selection_info() const +{ + SelectionInfo* sel_info = dynamic_cast(m_data.at(CommonGizmosDataID::SelectionInfo).get()); + assert(sel_info); + return sel_info->is_valid() ? sel_info : nullptr; +} + + +InstancesHider* CommonGizmosDataPool::instances_hider() const +{ + InstancesHider* inst_hider = dynamic_cast(m_data.at(CommonGizmosDataID::InstancesHider).get()); + assert(inst_hider); + return inst_hider->is_valid() ? inst_hider : nullptr; +} + +HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const +{ + HollowedMesh* hol_mesh = dynamic_cast(m_data.at(CommonGizmosDataID::HollowedMesh).get()); + assert(hol_mesh); + return hol_mesh->is_valid() ? hol_mesh : nullptr; +} + +Raycaster* CommonGizmosDataPool::raycaster() const +{ + Raycaster* rc = dynamic_cast(m_data.at(CommonGizmosDataID::Raycaster).get()); + assert(rc); + return rc->is_valid() ? rc : nullptr; +} + +ObjectClipper* CommonGizmosDataPool::object_clipper() const +{ + ObjectClipper* oc = dynamic_cast(m_data.at(CommonGizmosDataID::ObjectClipper).get()); + // ObjectClipper is used from outside the gizmos to report current clipping plane. + // This function can be called when oc is nullptr. + return (oc && oc->is_valid()) ? oc : nullptr; +} + +SupportsClipper* CommonGizmosDataPool::supports_clipper() const +{ + SupportsClipper* sc = dynamic_cast(m_data.at(CommonGizmosDataID::SupportsClipper).get()); + assert(sc); + return sc->is_valid() ? sc : nullptr; +} + +#ifndef NDEBUG +// Check the required resources one by one and return true if all +// dependencies are met. +bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const +{ + // This should iterate over currently required data. Each of them should + // be asked about its dependencies and it must check that all dependencies + // are also in required and before the current one. + for (auto& [id, data] : m_data) { + // in case we don't use this, the deps are irrelevant + if (! (int(required) & int(CommonGizmosDataID(id)))) + continue; + + + CommonGizmosDataID deps = data->get_dependencies(); + assert(int(deps) == (int(deps) & int(required))); + } + + + return true; +} +#endif // NDEBUG + + + + +void SelectionInfo::on_update() +{ + const Selection& selection = get_pool()->get_canvas()->get_selection(); + if (selection.is_single_full_instance()) { + m_model_object = selection.get_model()->objects[selection.get_object_idx()]; + m_z_shift = selection.get_first_volume()->get_sla_shift_z(); + } + else + m_model_object = nullptr; +} + +void SelectionInfo::on_release() +{ + m_model_object = nullptr; +} + +int SelectionInfo::get_active_instance() const +{ + const Selection& selection = get_pool()->get_canvas()->get_selection(); + return selection.get_instance_idx(); +} + + + + + +void InstancesHider::on_update() +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + int active_inst = get_pool()->selection_info()->get_active_instance(); + GLCanvas3D* canvas = get_pool()->get_canvas(); + + if (mo && active_inst != -1) { + canvas->toggle_model_objects_visibility(false); + canvas->toggle_model_objects_visibility(true, mo, active_inst); + canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst); + canvas->set_use_clipping_planes(true); + // Some objects may be sinking, do not show whatever is below the bed. + canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits::max())); + + + std::vector meshes; + for (const ModelVolume* mv : mo->volumes) + meshes.push_back(&mv->mesh()); + + if (meshes != m_old_meshes) { + m_clippers.clear(); + for (const TriangleMesh* mesh : meshes) { + m_clippers.emplace_back(new MeshClipper); + m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + m_clippers.back()->set_mesh(*mesh); + } + m_old_meshes = meshes; + } + } + else + canvas->toggle_model_objects_visibility(true); +} + +void InstancesHider::on_release() +{ + get_pool()->get_canvas()->toggle_model_objects_visibility(true); + get_pool()->get_canvas()->set_use_clipping_planes(false); + m_old_meshes.clear(); + m_clippers.clear(); +} + +void InstancesHider::show_supports(bool show) { + if (m_show_supports != show) { + m_show_supports = show; + on_update(); + } +} + +void InstancesHider::render_cut() const +{ + const SelectionInfo* sel_info = get_pool()->selection_info(); + const ModelObject* mo = sel_info->model_object(); + Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); + + size_t clipper_id = 0; + for (const ModelVolume* mv : mo->volumes) { + Geometry::Transformation vol_trafo = mv->get_transformation(); + Geometry::Transformation trafo = inst_trafo * vol_trafo; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + + auto& clipper = m_clippers[clipper_id]; + clipper->set_transformation(trafo); + const ObjectClipper* obj_clipper = get_pool()->object_clipper(); + if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane() + && obj_clipper->get_position() != 0.) { + ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane(); + clp.set_normal(-clp.get_normal()); + clipper->set_limiting_plane(clp); + } + else + clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#if !ENABLE_LEGACY_OPENGL_REMOVAL + if (mv->is_model_part()) + glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); + else { + const ColorRGBA color = color_from_model_volume(*mv); + glsafe(::glColor4fv(color.data())); + } +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPushAttrib(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_LEGACY_OPENGL_REMOVAL + clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); +#else + clipper->render_cut(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPopAttrib()); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + ++clipper_id; + } +} + + + +void HollowedMesh::on_update() +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; + if (! mo || ! is_sla) + return; + + const GLCanvas3D* canvas = get_pool()->get_canvas(); + const PrintObjects& print_objects = canvas->sla_print()->objects(); + const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) + ? print_objects[m_print_object_idx] + : nullptr; + + // Find the respective SLAPrintObject. + if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { + m_print_objects_count = print_objects.size(); + m_print_object_idx = -1; + for (const SLAPrintObject* po : print_objects) { + ++m_print_object_idx; + if (po->model_object()->id() == mo->id()) { + print_object = po; + break; + } + } + } + + // If there is a valid SLAPrintObject, check state of Hollowing step. + if (print_object) { + if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { + size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; + if (timestamp > m_old_hollowing_timestamp) { + const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); + if (! backend_mesh.empty()) { + m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); + Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse(); + m_hollowed_mesh_transformed->transform(trafo_inv); + m_drainholes = print_object->model_object()->sla_drain_holes; + m_old_hollowing_timestamp = timestamp; + + indexed_triangle_set interior = print_object->hollowed_interior_mesh(); + its_flip_triangles(interior); + m_hollowed_interior_transformed = std::make_unique(std::move(interior)); + m_hollowed_interior_transformed->transform(trafo_inv); + } + else { + m_hollowed_mesh_transformed.reset(nullptr); + } + } + } + else + m_hollowed_mesh_transformed.reset(nullptr); + } +} + + +void HollowedMesh::on_release() +{ + m_hollowed_mesh_transformed.reset(); + m_old_hollowing_timestamp = 0; + m_print_object_idx = -1; +} + + +const TriangleMesh* HollowedMesh::get_hollowed_mesh() const +{ + return m_hollowed_mesh_transformed.get(); +} + +const TriangleMesh* HollowedMesh::get_hollowed_interior() const +{ + return m_hollowed_interior_transformed.get(); +} + + + + +void Raycaster::on_update() +{ + wxBusyCursor wait; + const ModelObject* mo = get_pool()->selection_info()->model_object(); + + if (! mo) + return; + + std::vector meshes; + const std::vector& mvs = mo->volumes; + if (mvs.size() == 1) { + assert(mvs.front()->is_model_part()); + const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh(); + if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh()) + meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh()); + } + if (meshes.empty()) { + for (const ModelVolume* mv : mvs) { + if (mv->is_model_part()) + meshes.push_back(&mv->mesh()); + } + } + + if (meshes != m_old_meshes) { + m_raycasters.clear(); + for (const TriangleMesh* mesh : meshes) + m_raycasters.emplace_back(new MeshRaycaster(*mesh)); + m_old_meshes = meshes; + } +} + +void Raycaster::on_release() +{ + m_raycasters.clear(); + m_old_meshes.clear(); +} + +std::vector Raycaster::raycasters() const +{ + std::vector mrcs; + for (const auto& raycaster_unique_ptr : m_raycasters) + mrcs.push_back(raycaster_unique_ptr.get()); + return mrcs; +} + + + + + +void ObjectClipper::on_update() +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + if (! mo) + return; + + // which mesh should be cut? + std::vector meshes; + bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh(); + if (has_hollowed) + meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh()); + + if (meshes.empty()) + for (const ModelVolume* mv : mo->volumes) + meshes.push_back(&mv->mesh()); + + if (meshes != m_old_meshes) { + m_clippers.clear(); + for (const TriangleMesh* mesh : meshes) { + m_clippers.emplace_back(new MeshClipper); + m_clippers.back()->set_mesh(*mesh); + } + m_old_meshes = meshes; + + if (has_hollowed) + m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); + + m_active_inst_bb_radius = + mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); + } +} + + +void ObjectClipper::on_release() +{ + m_clippers.clear(); + m_old_meshes.clear(); + m_clp.reset(); + m_clp_ratio = 0.; + +} + +void ObjectClipper::render_cut() const +{ + if (m_clp_ratio == 0.) + return; + const SelectionInfo* sel_info = get_pool()->selection_info(); + const ModelObject* mo = sel_info->model_object(); + const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); + + size_t clipper_id = 0; + for (const ModelVolume* mv : mo->volumes) { + const Geometry::Transformation vol_trafo = mv->get_transformation(); + Geometry::Transformation trafo = inst_trafo * vol_trafo; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + + auto& clipper = m_clippers[clipper_id]; + clipper->set_plane(*m_clp); + clipper->set_transformation(trafo); + clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_LEGACY_OPENGL_REMOVAL + clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); +#else + glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); + clipper->render_cut(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + ++clipper_id; + } +} + + +void ObjectClipper::set_position(double pos, bool keep_normal) +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + int active_inst = get_pool()->selection_info()->get_active_instance(); + double z_shift = get_pool()->selection_info()->get_sla_shift(); + + Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward(); + const Vec3d& center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift); + float dist = normal.dot(center); + + if (pos < 0.) + pos = m_clp_ratio; + + m_clp_ratio = pos; + m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2*m_active_inst_bb_radius))); + get_pool()->get_canvas()->set_as_dirty(); +} + + + +void SupportsClipper::on_update() +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; + if (! mo || ! is_sla) + return; + + const GLCanvas3D* canvas = get_pool()->get_canvas(); + const PrintObjects& print_objects = canvas->sla_print()->objects(); + const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) + ? print_objects[m_print_object_idx] + : nullptr; + + // Find the respective SLAPrintObject. + if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { + m_print_objects_count = print_objects.size(); + m_print_object_idx = -1; + for (const SLAPrintObject* po : print_objects) { + ++m_print_object_idx; + if (po->model_object()->id() == mo->id()) { + print_object = po; + break; + } + } + } + + if (print_object + && print_object->is_step_done(slaposSupportTree) + && ! print_object->support_mesh().empty()) + { + // If the supports are already calculated, save the timestamp of the respective step + // so we can later tell they were recalculated. + size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp; + if (! m_clipper || timestamp != m_old_timestamp) { + // The timestamp has changed. + m_clipper.reset(new MeshClipper); + // The mesh should already have the shared vertices calculated. + m_clipper->set_mesh(print_object->support_mesh()); + m_old_timestamp = timestamp; + } + } + else + // The supports are not valid. We better dump the cached data. + m_clipper.reset(); +} + + +void SupportsClipper::on_release() +{ + m_clipper.reset(); + m_old_timestamp = 0; + m_print_object_idx = -1; +} + +void SupportsClipper::render_cut() const +{ + const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper(); + if (ocl->get_position() == 0. + || ! get_pool()->instances_hider()->are_supports_shown() + || ! m_clipper) + return; + + const SelectionInfo* sel_info = get_pool()->selection_info(); + const ModelObject* mo = sel_info->model_object(); + const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); + //Geometry::Transformation vol_trafo = mo->volumes.front()->get_transformation(); + Geometry::Transformation trafo = inst_trafo;// * vol_trafo; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + + + // Get transformation of supports + Geometry::Transformation supports_trafo = trafo; + supports_trafo.set_scaling_factor(Vec3d::Ones()); + supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift())); + supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2))); + // I don't know why, but following seems to be correct. + supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2), + 1, + 1.)); + + m_clipper->set_plane(*ocl->get_clipping_plane()); + m_clipper->set_transformation(supports_trafo); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f }); +#else + glsafe(::glColor3f(1.0f, 0.f, 0.37f)); + m_clipper->render_cut(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0c1985733..e50676ee1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2930,7 +2930,7 @@ int Plater::priv::get_selected_volume_idx() const if ((0 > idx) || (idx > 1000)) #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL return-1; - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); if (model.objects[idx]->volumes.size() > 1) return v->volume_idx(); return -1; @@ -3533,7 +3533,7 @@ void Plater::priv::replace_with_stl() if (selection.is_wipe_tower() || get_selection().get_volume_idxs().size() != 1) return; - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); int object_idx = v->object_idx(); int volume_idx = v->volume_idx(); @@ -6052,7 +6052,7 @@ void Plater::export_stl_obj(bool extended, bool selection_only) if (selection.get_mode() == Selection::Instance) mesh = mesh_to_export(*model_object, (selection.is_single_full_object() && model_object->instances.size() > 1) ? -1 : selection.get_instance_idx()); else { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); mesh = model_object->volumes[volume->volume_idx()]->mesh(); mesh.transform(volume->get_volume_transformation().get_matrix(), true); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c96e8efc7..18b3cfaa7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -35,25 +35,25 @@ static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5 namespace Slic3r { namespace GUI { - Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) - : position(transform.get_offset()) - , rotation(transform.get_rotation()) - , scaling_factor(transform.get_scaling_factor()) - , mirror(transform.get_mirror()) - , full_matrix(transform.get_matrix()) +Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) + : position(transform.get_offset()) + , rotation(transform.get_rotation()) + , scaling_factor(transform.get_scaling_factor()) + , mirror(transform.get_mirror()) + , full_matrix(transform.get_matrix()) #if ENABLE_TRANSFORMATIONS_BY_MATRICES - , transform(transform) - , rotation_matrix(transform.get_rotation_matrix()) - , scale_matrix(transform.get_scaling_factor_matrix()) - , mirror_matrix(transform.get_mirror_matrix()) + , transform(transform) + , rotation_matrix(transform.get_rotation_matrix()) + , scale_matrix(transform.get_scaling_factor_matrix()) + , mirror_matrix(transform.get_mirror_matrix()) #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - { +{ #if !ENABLE_TRANSFORMATIONS_BY_MATRICES - rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); - scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); + rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - } +} Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) : m_volume(volume_transform) @@ -612,14 +612,14 @@ bool Selection::requires_uniform_scale() const ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (is_single_volume_or_modifier()) { if (coord_type == ECoordinatesType::World) { - if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation())) { + if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_first_volume()->world_matrix()).get_rotation())) { if (reason != nullptr) *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_World; return true; } } else if (coord_type == ECoordinatesType::Instance) { - if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_volume_rotation())) { + if (!Geometry::is_rotation_ninety_degrees(get_first_volume()->get_volume_rotation())) { if (reason != nullptr) *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; return true; @@ -629,7 +629,7 @@ bool Selection::requires_uniform_scale() const } else if (is_single_full_instance()) { if (coord_type == ECoordinatesType::World) { - if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation())) { + if (!Geometry::is_rotation_ninety_degrees(get_first_volume()->get_instance_rotation())) { if (reason != nullptr) *reason = EUniformScaleRequiredReason::InstanceNotAxisAligned_World; return true; @@ -1478,7 +1478,7 @@ int Selection::bake_transform_if_needed() const (is_single_volume_or_modifier() && !wxGetApp().obj_manipul()->is_local_coordinates())) { // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one - const GLVolume& volume = *get_volume(*get_volume_idxs().begin()); + const GLVolume& volume = *get_first_volume(); bool needs_baking = false; if (is_single_full_instance()) { // Is the instance angle close to a multiple of 90 degrees? @@ -1646,13 +1646,14 @@ void Selection::render(float scale_factor) trafo = Transform3d::Identity(); } else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { - const GLVolume& v = *get_volume(*get_volume_idxs().begin()); + const GLVolume& v = *get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_scaling_factor_matrix()); - trafo = v.get_instance_transformation().get_matrix() * v.get_volume_transformation().get_matrix_no_scaling_factor(); + const Geometry::Transformation inst_trafo = get_first_volume()->get_instance_transformation(); + box = box.transformed(inst_trafo.get_scaling_factor_matrix()); + trafo = inst_trafo.get_matrix_no_scaling_factor(); #else - box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); - trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); + box = box.transformed(get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); + trafo = get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 47a40877c..c9f0eb7c6 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -351,6 +351,7 @@ public: const IndicesList& get_volume_idxs() const { return m_list; } const GLVolume* get_volume(unsigned int volume_idx) const; + const GLVolume* get_first_volume() const { return get_volume(*m_list.begin()); } const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; } From f9f7e6e75995165cf8485cab506ada6522233d5b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 5 May 2022 08:14:59 +0200 Subject: [PATCH 079/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::flattening_rotate(const Vec3d& normal) to use matrix multiplication Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 18b3cfaa7..e2a5b4a9d 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1158,13 +1158,21 @@ void Selection::flattening_rotate(const Vec3d& normal) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; // Normal transformed from the object coordinate space to the world coordinate space. - const auto &voldata = m_cache.volumes_data[i]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& old_inst_trafo = v.get_instance_transformation(); + const Vec3d tnormal = old_inst_trafo.get_matrix().matrix().block(0, 0, 3, 3).inverse().transpose() * normal; + // Additional rotation to align tnormal with the down vector in the world coordinate space. + const Transform3d rotation_matrix = Transform3d(Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ())); + v.set_instance_transformation(old_inst_trafo.get_offset_matrix() * rotation_matrix * old_inst_trafo.get_matrix_no_offset()); +#else + const auto& voldata = m_cache.volumes_data[i]; Vec3d tnormal = (Geometry::assemble_transform( - Vec3d::Zero(), voldata.get_instance_rotation(), + Vec3d::Zero(), voldata.get_instance_rotation(), voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized(); // Additional rotation to align tnormal with the down vector in the world coordinate space. - auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, - Vec3d::UnitZ()); + auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ()); v.set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix())); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } #if !DISABLE_INSTANCES_SYNCH @@ -1648,12 +1656,11 @@ void Selection::render(float scale_factor) else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { const GLVolume& v = *get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Geometry::Transformation inst_trafo = get_first_volume()->get_instance_transformation(); - box = box.transformed(inst_trafo.get_scaling_factor_matrix()); - trafo = inst_trafo.get_matrix_no_scaling_factor(); + box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_scaling_factor_matrix()); + trafo = v.get_instance_transformation().get_matrix() * v.get_volume_transformation().get_matrix_no_scaling_factor(); #else - box = box.transformed(get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); - trafo = get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false); + box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); + trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { @@ -1663,11 +1670,12 @@ void Selection::render(float scale_factor) box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } #if ENABLE_TRANSFORMATIONS_BY_MATRICES - box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); - trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor(); + const Geometry::Transformation inst_trafo = get_first_volume()->get_instance_transformation(); + box = box.transformed(inst_trafo.get_scaling_factor_matrix()); + trafo = inst_trafo.get_matrix_no_scaling_factor(); #else - box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); - trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false); + box = box.transformed(get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); + trafo = get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } From c8d93e69beab268681329a8132814b97e4957faf Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 5 May 2022 08:48:31 +0200 Subject: [PATCH 080/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::synchronize_unselected_volumes() to use the new matrix only implementation of Geometry::Transformation Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index e2a5b4a9d..b94c2eb38 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -3131,10 +3131,14 @@ void Selection::synchronize_unselected_volumes() #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL const int volume_idx = volume->volume_idx(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& trafo = volume->get_volume_transformation(); +#else const Vec3d& offset = volume->get_volume_offset(); const Vec3d& rotation = volume->get_volume_rotation(); const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); const Vec3d& mirror = volume->get_volume_mirror(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Process unselected volumes. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -3145,10 +3149,14 @@ void Selection::synchronize_unselected_volumes() if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) continue; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + v->set_volume_transformation(trafo); +#else v->set_volume_offset(offset); v->set_volume_rotation(rotation); v->set_volume_scaling_factor(scaling_factor); v->set_volume_mirror(mirror); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } } From 63c9ce23df0ffd6d8d4d1c6fa1dc0a185f554c6e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 5 May 2022 09:56:23 +0200 Subject: [PATCH 081/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void synchronize_unselected_instances(SyncRotationType sync_rotation_type) to use the new matrix only implementation of Geometry::Transformation Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 71 ++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b94c2eb38..a3bf2bf7b 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -3026,22 +3026,30 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (done.size() == m_volumes->size()) break; - const GLVolume* volume = (*m_volumes)[i]; + const GLVolume* volume_i = (*m_volumes)[i]; #if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (volume->is_wipe_tower) + if (volume_i->is_wipe_tower) continue; - const int object_idx = volume->object_idx(); + const int object_idx = volume_i->object_idx(); #else - const int object_idx = volume->object_idx(); + const int object_idx = volume_i->object_idx(); if (object_idx >= 1000) continue; #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - const int instance_idx = volume->instance_idx(); - const Vec3d& rotation = volume->get_instance_rotation(); - const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); - const Vec3d& mirror = volume->get_instance_mirror(); + const int instance_idx = volume_i->instance_idx(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& curr_inst_trafo_i = volume_i->get_instance_transformation(); + const Vec3d curr_inst_rotation_i = curr_inst_trafo_i.get_rotation(); + const Vec3d& curr_inst_scaling_factor_i = curr_inst_trafo_i.get_scaling_factor(); + const Vec3d& curr_inst_mirror_i = curr_inst_trafo_i.get_mirror(); + const Vec3d old_inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation(); +#else + const Vec3d& rotation = volume_i->get_instance_rotation(); + const Vec3d& scaling_factor = volume_i->get_instance_scaling_factor(); + const Vec3d& mirror = volume_i->get_instance_mirror(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Process unselected instances. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -3051,14 +3059,17 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (done.find(j) != done.end()) continue; - GLVolume* v = (*m_volumes)[j]; - if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) + GLVolume* volume_j = (*m_volumes)[j]; + if (volume_j->object_idx() != object_idx || volume_j->instance_idx() == instance_idx) continue; #if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Vec3d inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation(); - const Vec3d inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); - assert(is_rotation_xy_synchronized(inst_rotation_i, inst_rotation_j)); + const Vec3d old_inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); + assert(is_rotation_xy_synchronized(old_inst_rotation_i, old_inst_rotation_j)); + const Geometry::Transformation& curr_inst_trafo_j = volume_j->get_instance_transformation(); + const Vec3d curr_inst_rotation_j = curr_inst_trafo_j.get_rotation(); + Vec3d new_inst_offset_j = curr_inst_trafo_j.get_offset(); + Vec3d new_inst_rotation_j = curr_inst_rotation_j; #else assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -3066,45 +3077,59 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ case SyncRotationType::NONE: { // z only rotation -> synch instance z // The X,Y rotations should be synchronized from start to end of the rotation. - assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + assert(is_rotation_xy_synchronized(curr_inst_rotation_i, curr_inst_rotation_j)); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) - v->set_instance_offset(Z, volume->get_instance_offset().z()); + new_inst_offset_j.z() = curr_inst_trafo_i.get_offset().z(); +#else + assert(is_rotation_xy_synchronized(rotation, volume_j->get_instance_rotation())); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + volume_j->set_instance_offset(Z, volume_i->get_instance_offset().z()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. #if ENABLE_TRANSFORMATIONS_BY_MATRICES - const double z_diff = Geometry::rotation_diff_z(inst_rotation_i, inst_rotation_j); + const double z_diff = Geometry::rotation_diff_z(old_inst_rotation_i, old_inst_rotation_j); + new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); #else const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; } #if ENABLE_WORLD_COORDINATE case SyncRotationType::FULL: { // generic rotation -> update instance z with the delta of the rotation. #if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, inst_rotation_j)); + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(curr_inst_rotation_i, old_inst_rotation_j)); #else const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation())); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& axis = angle_axis.axis(); const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ? #if ENABLE_TRANSFORMATIONS_BY_MATRICES - angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, inst_rotation_j); + angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(curr_inst_rotation_i, old_inst_rotation_j); + + new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); #else angle_axis.angle()* axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); + volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } #endif // ENABLE_WORLD_COORDINATE } - v->set_instance_scaling_factor(scaling_factor); - v->set_instance_mirror(mirror); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + volume_j->set_instance_transformation(Geometry::assemble_transform(new_inst_offset_j, new_inst_rotation_j, + curr_inst_scaling_factor_i, curr_inst_mirror_i)); +#else + volume_j->set_instance_scaling_factor(scaling_factor); + volume_j->set_instance_mirror(mirror); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES done.insert(j); } From b5d366d385a8f0f9e44f08470ee1910051fcf0fa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 5 May 2022 10:12:16 +0200 Subject: [PATCH 082/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Allow skew in matrices Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 30 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ddb878a7d..9b892eb53 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1289,6 +1289,7 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (!use_uniform_scale) { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES int res = selection.bake_transform_if_needed(); if (res == -1) { // Enforce uniform scaling. @@ -1296,6 +1297,7 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) return; } else if (res == 0) +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Recalculate cached values at this panel, refresh the screen. this->UpdateAndShow(true); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 675ed0b3f..061361e40 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -75,14 +75,36 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) { if (mouse_event.Dragging()) { if (m_dragging) { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + int res = 1; + if (m_scale.x() != m_scale.y() || m_scale.x() != m_scale.z()) + res = m_parent.get_selection().bake_transform_if_needed(); + + if (res != 1) { + do_stop_dragging(true); + return true; + } + else { +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Apply new temporary scale factors TransformationType transformation_type(TransformationType::Local_Absolute_Joint); if (mouse_event.AltDown()) transformation_type.set_independent(); - Selection &selection = m_parent.get_selection(); - selection.scale(get_scale(), transformation_type); - if (mouse_event.CmdDown()) selection.translate(m_offset, ECoordinatesType::Local); - } + Selection &selection = m_parent.get_selection(); + selection.scale(m_scale, transformation_type); +#if ENABLE_WORLD_COORDINATE + if (mouse_event.CmdDown()) selection.translate(m_offset, wxGetApp().obj_manipul()->get_coordinates_type()); +#else + if (mouse_event.CmdDown()) selection.translate(m_offset, true); +#endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + } +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES + } } return use_grabbers(mouse_event); } From 88ce6ccdef5f680709ea8b676688784a7af287dd Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 11 May 2022 10:54:42 +0200 Subject: [PATCH 083/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::scale(const Vec3d& scale, TransformationType transformation_type) to use matrix multiplication Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 32 +++- src/libslic3r/Geometry.hpp | 12 +- src/slic3r/GUI/GLCanvas3D.cpp | 31 +++- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 40 ++++ src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 35 +++- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 5 + src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 12 ++ src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 154 ++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 4 + src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/Selection.cpp | 217 ++++++++++++++-------- src/slic3r/GUI/Selection.hpp | 14 ++ 14 files changed, 464 insertions(+), 98 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 031e489f7..61217f4a1 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -313,7 +313,6 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, return transform; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES void assemble_transform(Transform3d& transform, const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror) { transform = translation * rotation * scale * mirror; @@ -326,6 +325,19 @@ Transform3d assemble_transform(const Transform3d& translation, const Transform3d return transform; } +void translation_transform(Transform3d& transform, const Vec3d& translation) +{ + transform = Transform3d::Identity(); + transform.translate(translation); +} + +Transform3d translation_transform(const Vec3d& translation) +{ + Transform3d transform; + translation_transform(transform, translation); + return transform; +} + void rotation_transform(Transform3d& transform, const Vec3d& rotation) { transform = Transform3d::Identity(); @@ -351,7 +363,6 @@ Transform3d scale_transform(const Vec3d& scale) scale_transform(transform, scale); return transform; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix) { @@ -433,6 +444,14 @@ static std::pair extract_rotation_scale(const Transfor return { Transform3d(rotation), Transform3d(scale) }; } +static bool contains_skew(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return !scale.isDiagonal(); +} + Vec3d Transformation::get_rotation() const { return extract_euler_angles(extract_rotation(m_matrix)); @@ -623,7 +642,12 @@ void Transformation::set_mirror(Axis axis, double mirror) #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +bool Transformation::has_skew() const +{ + return contains_skew(m_matrix); +} +#else void Transformation::set_from_transform(const Transform3d& transform) { // offset @@ -661,7 +685,7 @@ void Transformation::set_from_transform(const Transform3d& transform) // if (!m_matrix.isApprox(transform)) // std::cout << "something went wrong in extracting data from matrix" << std::endl; } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Transformation::reset() { diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index c55867023..bbca3a5e3 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -336,7 +336,6 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES // Sets the given transform by multiplying the given transformations in the following order: // T = translation * rotation * scale * mirror void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d::Identity(), @@ -348,6 +347,12 @@ void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d assemble_transform(const Transform3d& translation = Transform3d::Identity(), const Transform3d& rotation = Transform3d::Identity(), const Transform3d& scale = Transform3d::Identity(), const Transform3d& mirror = Transform3d::Identity()); +// Sets the given transform by assembling the given translation +void translation_transform(Transform3d& transform, const Vec3d& translation); + +// Returns the transform obtained by assembling the given translation +Transform3d translation_transform(const Vec3d& translation); + // Sets the given transform by assembling the given rotations in the following order: // 1) rotate X // 2) rotate Y @@ -365,7 +370,6 @@ void scale_transform(Transform3d& transform, const Vec3d& scale); // Returns the transform obtained by assembling the given scale factors Transform3d scale_transform(const Vec3d& scale); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Returns the euler angles extracted from the given rotation matrix // Warning -> The matrix should not contain any scale or shear !!! @@ -478,6 +482,10 @@ public: void set_mirror(const Vec3d& mirror); void set_mirror(Axis axis, double mirror); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool has_skew() const; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + #if !ENABLE_TRANSFORMATIONS_BY_MATRICES void set_from_transform(const Transform3d& transform); #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f3ba04c50..8a2599866 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2914,7 +2914,13 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) else displacement = multiplier * direction; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + m_selection.translate(displacement, trafo_type); +#else m_selection.translate(displacement); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_dirty = true; } ); @@ -3579,7 +3585,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type); +#else m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) update_sequential_clearance(); wxGetApp().obj_manipul()->set_dirty(); @@ -3996,12 +4008,12 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) Selection::EMode selection_mode = m_selection.get_mode(); for (const GLVolume* v : m_volumes.volumes) { - int object_idx = v->object_idx(); + const int object_idx = v->object_idx(); if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) continue; - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); + const int instance_idx = v->instance_idx(); + const int volume_idx = v->volume_idx(); done.insert(std::pair(object_idx, instance_idx)); @@ -4009,13 +4021,22 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (selection_mode == Selection::Volume) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } model_object->invalidate_bounding_box(); } @@ -4024,10 +4045,10 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) // Fixes sinking/flying instances for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; - double shift_z = m->get_instance_min_z(i.second); + const double shift_z = m->get_instance_min_z(i.second); // leave sinking instances as sinking if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { - Vec3d shift(0.0, 0.0, -shift_z); + const Vec3d shift(0.0, 0.0, -shift_z); m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 9b892eb53..871c90c51 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -461,9 +461,16 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_reset_scale_button->SetToolTip(_L("Reset scale")); m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale")); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool old_uniform = m_uniform_scale; + m_uniform_scale = true; + change_scale_value(0, 100.0); + m_uniform_scale = old_uniform; +#else change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES }); editors_grid_sizer->Add(m_reset_scale_button); @@ -616,6 +623,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_instance_offset(); #endif // !ENABLE_WORLD_COORDINATE +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. #if ENABLE_WORLD_COORDINATE if (is_world_coordinates() && !m_uniform_scale && @@ -627,6 +635,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_uniform_scale = true; m_lock_bnt->SetLock(true); } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { @@ -790,6 +799,7 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE Selection::EUniformScaleRequiredReason reason; if (selection.requires_uniform_scale(&reason)) { @@ -829,10 +839,13 @@ void ObjectManipulation::update_if_dirty() #endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else { +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_lock_bnt->SetLock(m_uniform_scale); m_lock_bnt->SetToolTip(wxEmptyString); m_lock_bnt->enable(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_WORLD_COORDINATE { @@ -1048,7 +1061,19 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.setup_cache(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + switch (get_coordinates_type()) + { + case ECoordinatesType::Instance: { trafo_type.set_instance(); break; } + case ECoordinatesType::Local: { trafo_type.set_local(); break; } + default: { break; } + } + selection.translate(position - m_cache.position, trafo_type); +#else selection.translate(position - m_cache.position, get_coordinates_type()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else selection.translate(position - m_cache.position, selection.requires_local_axes()); #endif // ENABLE_WORLD_COORDINATE @@ -1191,6 +1216,9 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const transformation_type.set_local(); #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; +#else bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; @@ -1203,6 +1231,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { @@ -1231,6 +1260,12 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); + if (!is_local_coordinates()) + transformation_type.set_relative(); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; +#else bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; @@ -1243,6 +1278,7 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES selection.setup_cache(); selection.scale(scaling_factor, transformation_type); @@ -1303,6 +1339,10 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) } m_uniform_scale = use_uniform_scale; + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + set_dirty(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else #if ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance() && is_world_coordinates() && !use_uniform_scale) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 1e0ff6c9e..bee57ec6e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -333,7 +333,11 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { wxGetApp().obj_manipul()->set_dirty(); m_parent.set_as_dirty(); return true; - } else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) { + } + else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) { +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + do_stop_dragging(is_leaving); +#else for (auto &grabber : m_grabbers) grabber.dragging = false; m_dragging = false; @@ -356,12 +360,41 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); // updates camera target constraints m_parent.refresh_camera_scene_box(); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED return true; } } return false; } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup) +{ + for (auto& grabber : m_grabbers) grabber.dragging = false; + m_dragging = false; + + // NOTE: This should be part of GLCanvas3D + // Reset hover_id when leave window + if (perform_mouse_cleanup) m_parent.mouse_up_cleanup(); + + on_stop_dragging(); + + // There is prediction that after draggign, data are changed + // Data are updated twice also by canvas3D::reload_scene. + // Should be fixed. + m_parent.get_gizmos_manager().update_data(); + + wxGetApp().obj_manipul()->set_dirty(); + + // Let the plater know that the dragging finished, so a delayed + // refresh of the scene with the background processing data should + // be performed. + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + // updates camera target constraints + m_parent.refresh_camera_scene_box(); +} +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + std::string GLGizmoBase::format(float value, unsigned int decimals) const { return Slic3r::string_printf("%.*f", decimals, value); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index f61654183..fff699479 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -227,6 +227,11 @@ protected: /// Keep information about mouse click /// same as on_mouse bool use_grabbers(const wxMouseEvent &mouse_event); + +#if ENABLE_WORLD_COORDINATE + void do_stop_dragging(bool perform_mouse_cleanup); +#endif // ENABLE_WORLD_COORDINATE + private: // Flag for dirty visible state of Gizmo // When True then need new rendering diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 15030a0fa..8ef23d538 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -126,7 +126,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) #else const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); + const Transform3d instance_matrix = Geometry::translation_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d& view_matrix = camera.get_view_matrix(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index afb0b9140..19422e167 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -141,7 +141,19 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) Selection &selection = m_parent.get_selection(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + switch (wxGetApp().obj_manipul()->get_coordinates_type()) + { + case ECoordinatesType::Instance: { trafo_type.set_instance(); break; } + case ECoordinatesType::Local: { trafo_type.set_local(); break; } + default: { break; } + } + selection.translate(m_displacement, trafo_type); +#else selection.translate(m_displacement, wxGetApp().obj_manipul()->get_coordinates_type()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else selection.translate(m_displacement); #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index a89173460..b72c6761f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -787,7 +787,7 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const } #if ENABLE_WORLD_COORDINATE - return Geometry::assemble_transform(m_center) * m_orient_matrix * ret; + return Geometry::translation_transform(m_center) * m_orient_matrix * ret; #else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) ret = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true) * ret; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 061361e40..af5b3adb2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -41,6 +41,9 @@ GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filen std::string GLGizmoScale3D::get_tooltip() const { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d scale = 100.0 * m_scale; +#else const Selection& selection = m_parent.get_selection(); Vec3d scale = 100.0 * Vec3d::Ones(); @@ -52,6 +55,7 @@ std::string GLGizmoScale3D::get_tooltip() const else if (selection.is_single_modifier() || selection.is_single_volume()) #endif // ENABLE_WORLD_COORDINATE scale = 100.0 * selection.get_first_volume()->get_volume_scaling_factor(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) return "X: " + format(scale.x(), 4) + "%"; @@ -89,32 +93,65 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Apply new temporary scale factors - TransformationType transformation_type(TransformationType::Local_Absolute_Joint); - if (mouse_event.AltDown()) transformation_type.set_independent(); +#if ENABLE_WORLD_COORDINATE + TransformationType transformation_type; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + if (wxGetApp().obj_manipul()->is_local_coordinates()) + transformation_type.set_local(); + else if (wxGetApp().obj_manipul()->is_instance_coordinates()) + transformation_type.set_instance(); - Selection &selection = m_parent.get_selection(); + transformation_type.set_relative(); +#else + if (!wxGetApp().obj_manipul()->is_world_coordinates()) + transformation_type.set_local(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#else + TransformationType transformation_type(TransformationType::Local_Absolute_Joint); +#endif // ENABLE_WORLD_COORDINATE + if (mouse_event.AltDown()) transformation_type.set_independent(); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_parent.get_selection().scale_and_translate(m_scale, m_offset, transformation_type); +#else + Selection& selection = m_parent.get_selection(); selection.scale(m_scale, transformation_type); #if ENABLE_WORLD_COORDINATE if (mouse_event.CmdDown()) selection.translate(m_offset, wxGetApp().obj_manipul()->get_coordinates_type()); #else if (mouse_event.CmdDown()) selection.translate(m_offset, true); #endif // ENABLE_WORLD_COORDINATE +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED } #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - } + } } return use_grabbers(mouse_event); } void GLGizmoScale3D::data_changed() { - const Selection &selection = m_parent.get_selection(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES + const Selection &selection = m_parent.get_selection(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED + bool enable_scale_xyz = !selection.requires_uniform_scale(); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#else bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); +#endif // ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + set_scale(Vec3d::Ones()); +#else +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { +#else for (unsigned int i = 0; i < 6; ++i) m_grabbers[i].enabled = enable_scale_xyz; @@ -129,6 +166,8 @@ void GLGizmoScale3D::data_changed() } else set_scale(Vec3d::Ones()); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } bool GLGizmoScale3D::on_init() @@ -251,7 +290,7 @@ void GLGizmoScale3D::on_render() #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor(); - m_grabbers_transform = inst_trafo * Geometry::assemble_transform(m_bounding_box.center()); + m_grabbers_transform = inst_trafo * Geometry::translation_transform(m_bounding_box.center()); m_center = inst_trafo * m_bounding_box.center(); #else m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); @@ -677,6 +716,58 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int } #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) { + Vec3d curr_scale = m_scale; + Vec3d starting_scale = m_starting.scale; + const Selection& selection = m_parent.get_selection(); + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + + curr_scale(axis) = starting_scale(axis) * ratio; + m_scale = curr_scale; + + if (m_starting.ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { + double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); + + if (m_hover_id == 2 * axis) + local_offset *= -1.0; + + Vec3d center_offset = m_starting.instance_center - m_starting.center; // world coordinates (== Vec3d::Zero() for single volume selection) + if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local) + // from world coordinates to instance coordinates + center_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix().inverse() * center_offset; + + local_offset += (ratio - 1.0) * center_offset(axis); + + switch (axis) + { + case X: { m_offset = local_offset * Vec3d::UnitX(); break; } + case Y: { m_offset = local_offset * Vec3d::UnitY(); break; } + case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } + default: { m_offset = Vec3d::Zero(); break; } + } + + if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local) + // from instance coordinates to world coordinates + m_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix() * m_offset; + + if (selection.is_single_volume_or_modifier()) { + if (coordinates_type == ECoordinatesType::Instance) + m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset; + else if (coordinates_type == ECoordinatesType::Local) { + m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * + selection.get_first_volume()->get_volume_transformation().get_rotation_matrix() * m_offset; + } + } + } + else + m_offset = Vec3d::Zero(); + } +} +#else void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { #if ENABLE_WORLD_COORDINATE @@ -721,6 +812,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; #endif // ENABLE_WORLD_COORDINATE + if (m_starting.ctrl_down) { #if ENABLE_WORLD_COORDINATE double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); @@ -734,7 +826,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #if ENABLE_WORLD_COORDINATE Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d m = Geometry::rotation_transform(selection.get_first_volume()->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -764,12 +856,57 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) m_offset = Vec3d::Zero(); } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void GLGizmoScale3D::do_scale_uniform(const UpdateData & data) +{ + const double ratio = calc_ratio(data); + if (ratio > 0.0) { + m_scale = m_starting.scale * ratio; + + const Selection& selection = m_parent.get_selection(); + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (m_starting.ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { + m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); + + if (m_hover_id == 6 || m_hover_id == 9) + m_offset.x() *= -1.0; + if (m_hover_id == 6 || m_hover_id == 7) + m_offset.y() *= -1.0; + + Vec3d center_offset = m_starting.instance_center - m_starting.center; // world coordinates (== Vec3d::Zero() for single volume selection) + + if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local) + // from world coordinates to instance coordinates + center_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix().inverse() * center_offset; + + m_offset += (ratio - 1.0) * center_offset; + + if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local) + // from instance coordinates to world coordinates + m_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix() * m_offset; + + if (selection.is_single_volume_or_modifier()) { + if (coordinates_type == ECoordinatesType::Instance) + m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset; + else if (coordinates_type == ECoordinatesType::Local) { + m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * + selection.get_first_volume()->get_volume_transformation().get_rotation_matrix() * m_offset; + } + } + } + else + m_offset = Vec3d::Zero(); + } +} +#else void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) { const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale = m_starting.scale * ratio; + #if ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); @@ -783,7 +920,7 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d m = Geometry::rotation_transform(selection.get_first_volume()->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -794,6 +931,7 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset = Vec3d::Zero(); } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index b8001d7a9..754b588b4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -67,7 +67,11 @@ public: void set_snap_step(double step) { m_snap_step = step; } const Vec3d& get_scale() const { return m_scale; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; m_offset = Vec3d::Zero(); } +#else void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED const Vec3d& get_starting_scale() const { return m_starting.scale; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e50676ee1..c7ddf8f78 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3852,7 +3852,7 @@ void Plater::priv::reload_from_disk() new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) * + new_volume->set_transformation(Geometry::translation_transform(old_volume->source.transform.get_offset()) * old_volume->get_transformation().get_matrix_no_offset() * old_volume->source.transform.get_matrix_no_offset()); new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); #else diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a3bf2bf7b..33ecd2412 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -599,6 +599,7 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE bool Selection::requires_uniform_scale(EUniformScaleRequiredReason* reason) const #else @@ -667,6 +668,7 @@ bool Selection::requires_uniform_scale() const return true; #endif // ENABLE_WORLD_COORDINATE } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES int Selection::get_object_idx() const { @@ -760,58 +762,29 @@ void Selection::setup_cache() } #if ENABLE_TRANSFORMATIONS_BY_MATRICES -void Selection::translate(const Vec3d& displacement, ECoordinatesType type) +void Selection::translate(const Vec3d& displacement, TransformationType transformation_type) { if (!m_valid) return; + assert(transformation_type.relative()); + for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; if (m_mode == Instance && !is_wipe_tower()) { assert(is_from_fully_selected_instance(i)); - switch (type) - { - case ECoordinatesType::World: - { - v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix()); - break; - } - case ECoordinatesType::Local: - { - const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement; - v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix()); - break; - } - default: { assert(false); break; } - } - } - else { - switch (type) - { - case ECoordinatesType::World: - { - const Transform3d inst_matrix_no_offset = volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix(); - const Vec3d inst_displacement = inst_matrix_no_offset.inverse() * displacement; - v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); - break; - } - case ECoordinatesType::Instance: - { - const Vec3d inst_displacement = volume_data.get_instance_scale_matrix().inverse() * displacement; - v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); - break; - } - case ECoordinatesType::Local: - { - const Vec3d inst_displacement = volume_data.get_instance_scale_matrix().inverse() * - volume_data.get_volume_rotation_matrix() * displacement; - v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); - break; - } - default: { assert(false); break; } + if (transformation_type.world()) + v.set_instance_transformation(Geometry::translation_transform(displacement) * volume_data.get_instance_full_matrix()); + else if (transformation_type.local()) { + const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement; + v.set_instance_transformation(Geometry::translation_transform(world_displacement) * volume_data.get_instance_full_matrix()); } + else + assert(false); } + else + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(displacement)); } #if !DISABLE_INSTANCES_SYNCH @@ -914,51 +887,35 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); if (m_mode == Instance && !is_wipe_tower()) { assert(is_from_fully_selected_instance(i)); - const Geometry::Transformation& old_trafo = volume_data.get_instance_transform(); Transform3d new_rotation_matrix = Transform3d::Identity(); if (transformation_type.absolute()) new_rotation_matrix = rotation_matrix; else { if (transformation_type.world()) - new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix(); + new_rotation_matrix = rotation_matrix * inst_trafo.get_rotation_matrix(); else if (transformation_type.local()) - new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix; + new_rotation_matrix = inst_trafo.get_rotation_matrix() * rotation_matrix; else assert(false); } - const Vec3d new_offset = transformation_type.independent() ? old_trafo.get_offset() : - m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() * - (old_trafo.get_offset() - m_cache.dragging_center); - v.set_instance_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix, - old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix())); + const Vec3d new_offset = transformation_type.independent() ? inst_trafo.get_offset() : + m_cache.dragging_center + new_rotation_matrix * inst_trafo.get_rotation_matrix().inverse() * + (inst_trafo.get_offset() - m_cache.dragging_center); + v.set_instance_transformation(Geometry::assemble_transform(Geometry::translation_transform(new_offset), new_rotation_matrix, + inst_trafo.get_scaling_factor_matrix(), inst_trafo.get_mirror_matrix())); } else { - const Geometry::Transformation& old_trafo = volume_data.get_volume_transform(); - Transform3d new_rotation_matrix = Transform3d::Identity(); - - if (transformation_type.absolute()) - new_rotation_matrix = rotation_matrix; - else { - if (transformation_type.world()) { - const Transform3d inst_rotation_matrix = volume_data.get_instance_transform().get_rotation_matrix(); - new_rotation_matrix = inst_rotation_matrix.inverse() * rotation_matrix * inst_rotation_matrix * old_trafo.get_rotation_matrix(); - } - else if (transformation_type.instance()) - new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix(); - else if (transformation_type.local()) - new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix; - else - assert(false); + if (transformation_type.absolute()) { + const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); + v.set_volume_transformation(Geometry::assemble_transform(volume_trafo.get_offset_matrix(), Geometry::rotation_transform(rotation), + volume_trafo.get_scaling_factor_matrix(), volume_trafo.get_mirror_matrix())); } - - const Vec3d new_offset = !is_wipe_tower() ? old_trafo.get_offset() : - m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() * - (old_trafo.get_offset() - m_cache.dragging_center); - v.set_volume_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix, - old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix())); + else + transform_volume_relative(v, volume_data, transformation_type, Geometry::rotation_transform(rotation)); } } @@ -1185,6 +1142,12 @@ void Selection::flattening_rotate(const Vec3d& normal) this->set_bounding_boxes_dirty(); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::scale(const Vec3d& scale, TransformationType transformation_type) +{ + scale_and_translate(scale, Vec3d::Zero(), transformation_type); +} +#else void Selection::scale(const Vec3d& scale, TransformationType transformation_type) { if (!m_valid) @@ -1259,6 +1222,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::scale_to_fit_print_volume(const BuildVolume& volume) { @@ -1281,7 +1245,13 @@ void Selection::scale_to_fit_print_volume(const BuildVolume& volume) // center selection on print bed setup_cache(); offset.z() = -get_bounding_box().min.z(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + translate(offset, trafo_type); +#else translate(offset); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); @@ -1371,7 +1341,78 @@ void Selection::mirror(Axis axis) set_bounding_boxes_dirty(); } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type) +{ + if (!m_valid) + return; + + std::cout << "Selection::scale_and_translate: " << to_string(scale) << " - " << to_string(translation) << "\n"; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); + if (m_mode == Instance) { + assert(is_from_fully_selected_instance(i)); + if (transformation_type.absolute()) { + assert(transformation_type.local()); + assert(transformation_type.joint()); + v.set_instance_transformation(Geometry::assemble_transform(inst_trafo.get_offset_matrix(), inst_trafo.get_rotation_matrix(), + Geometry::scale_transform(scale), inst_trafo.get_mirror_matrix())); + } + else { + if (transformation_type.world()) { + const Transform3d scale_matrix = Geometry::scale_transform(scale); + const Transform3d offset_matrix = (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) ? + // non-constrained scaling - add offset to scale around selection center + Geometry::translation_transform(m_cache.dragging_center + scale_matrix * (inst_trafo.get_offset() - m_cache.dragging_center)) : + // constrained scaling - add offset to keep constraint + Geometry::translation_transform(translation) * inst_trafo.get_offset_matrix(); + v.set_instance_transformation(offset_matrix * scale_matrix * inst_trafo.get_matrix_no_offset()); + } + else if (transformation_type.local()) { + const Transform3d scale_matrix = Geometry::scale_transform(scale); + Vec3d offset; + if (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) { + // non-constrained scaling - add offset to scale around selection center + offset = inst_trafo.get_matrix_no_offset().inverse() * (inst_trafo.get_offset() - m_cache.dragging_center); + offset = inst_trafo.get_matrix_no_offset() * (scale_matrix * offset - offset); + } + else + // constrained scaling - add offset to keep constraint + offset = translation; + + v.set_instance_transformation(Geometry::translation_transform(offset) * inst_trafo.get_matrix() * scale_matrix); + } + else + assert(false); + } + } + else { + if (transformation_type.absolute()) { + assert(transformation_type.local()); + const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); + v.set_volume_transformation(Geometry::assemble_transform(volume_trafo.get_offset_matrix(), volume_trafo.get_rotation_matrix(), + Geometry::scale_transform(scale), volume_trafo.get_mirror_matrix())); + } + else + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(scale)); + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances(SyncRotationType::NONE); + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + ensure_on_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} +#else void Selection::translate(unsigned int object_idx, const Vec3d& displacement) { if (!m_valid) @@ -1420,7 +1461,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) this->set_bounding_boxes_dirty(); } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) { @@ -1431,7 +1472,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co GLVolume& v = *(*m_volumes)[i]; if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) #if ENABLE_TRANSFORMATIONS_BY_MATRICES - v.set_instance_transformation(Geometry::assemble_transform(displacement) * v.get_instance_transformation().get_matrix()); + v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix()); #else v.set_instance_offset(v.get_instance_offset() + displacement); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -1468,7 +1509,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co continue; #if ENABLE_TRANSFORMATIONS_BY_MATRICES - v.set_instance_transformation(Geometry::assemble_transform(displacement) * v.get_instance_transformation().get_matrix()); + v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix()); #else v.set_instance_offset(v.get_instance_offset() + displacement); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -2770,7 +2811,11 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLS void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) #endif // ENABLE_GL_SHADERS_ATTRIBUTES { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const bool uniform_scale = wxGetApp().obj_manipul()->get_uniform_scaling(); +#else const bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_GL_SHADERS_ATTRIBUTES auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& matrix) { @@ -3380,5 +3425,27 @@ void Selection::paste_objects_from_clipboard() #endif /* _DEBUG */ } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, + const Transform3d& transform) +{ + const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); + const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); + if (transformation_type.world()) { + const Transform3d inst_matrix_no_offset = inst_trafo.get_matrix_no_offset(); + const Transform3d new_volume_matrix = inst_matrix_no_offset.inverse() * transform * inst_matrix_no_offset; + volume.set_volume_transformation(volume_trafo.get_offset_matrix() * new_volume_matrix * volume_trafo.get_matrix_no_offset()); + } + else if (transformation_type.instance()) + volume.set_volume_transformation(volume_trafo.get_offset_matrix() * transform * volume_trafo.get_matrix_no_offset()); + else if (transformation_type.local()) { + const Geometry::Transformation trafo(transform); + volume.set_volume_transformation(trafo.get_offset_matrix() * volume_trafo.get_matrix() * trafo.get_matrix_no_offset()); + } + else + assert(false); +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c9f0eb7c6..bd9c9216c 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -336,7 +336,9 @@ public: VolumeNotAxisAligned_Instance, MultipleSelection, }; +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES bool requires_uniform_scale(EUniformScaleRequiredReason* reason = nullptr) const; +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #else bool requires_uniform_scale() const; #endif // ENABLE_WORLD_COORDINATE @@ -365,7 +367,11 @@ public: void setup_cache(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void translate(const Vec3d& displacement, TransformationType transformation_type); +#else void translate(const Vec3d& displacement, ECoordinatesType type = ECoordinatesType::World); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else void translate(const Vec3d& displacement, bool local = false); #endif // ENABLE_WORLD_COORDINATE @@ -374,6 +380,9 @@ public: void scale(const Vec3d& scale, TransformationType transformation_type); void scale_to_fit_print_volume(const BuildVolume& volume); void mirror(Axis axis); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(unsigned int object_idx, const Vec3d& displacement); @@ -468,6 +477,11 @@ private: void paste_volumes_from_clipboard(); void paste_objects_from_clipboard(); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, + const Transform3d& transform); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES }; } // namespace GUI From eeb81b1ef8d3e34d4b8cd755f2bb9bddde714a54 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 11 May 2022 11:54:01 +0200 Subject: [PATCH 084/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Let reset buttons in object manipulator to be always visible when needed, no matter what is the current selected reference system --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 871c90c51..141c34c9b 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -882,15 +882,21 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { +#else if ((m_coordinates_type == ECoordinatesType::World && selection.is_single_full_instance()) || (m_coordinates_type == ECoordinatesType::Instance && selection.is_single_volume_or_modifier())) { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : get_volume_min_z(*selection.get_first_volume()); show_drop_to_bed = std::abs(min_z) > EPSILON; +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES } if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume* volume = selection.get_first_volume(); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); From 19712749c3eedaed1fc25d10e1ec6f66571c3607 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 12 May 2022 09:41:01 +0200 Subject: [PATCH 085/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Added reset button to remove skew, when detected, in object manipulator panel Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 24 +++++++ src/libslic3r/Geometry.hpp | 1 + src/slic3r/GUI/GLCanvas3D.cpp | 50 ++++++++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 6 ++ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 83 +++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectManipulation.hpp | 13 +++- src/slic3r/GUI/Plater.cpp | 3 + src/slic3r/GUI/Selection.cpp | 42 +++++++++++- src/slic3r/GUI/Selection.hpp | 1 + 9 files changed, 218 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 61217f4a1..a611e10c0 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -702,6 +702,30 @@ void Transformation::reset() } #if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Transformation::reset_skew() +{ + Matrix3d rotation; + Matrix3d scale; + m_matrix.computeRotationScaling(&rotation, &scale); + + const double average_scale = std::cbrt(scale(0, 0) * scale(1, 1) * scale(2, 2)); + + scale(0, 0) = average_scale; + scale(1, 1) = average_scale; + scale(2, 2) = average_scale; + + scale(0, 1) = 0.0; + scale(0, 2) = 0.0; + scale(1, 0) = 0.0; + scale(1, 2) = 0.0; + scale(2, 0) = 0.0; + scale(2, 1) = 0.0; + + const Vec3d offset = get_offset(); + m_matrix = rotation * scale; + m_matrix.translation() = offset; +} + Transform3d Transformation::get_matrix_no_offset() const { Transformation copy(*this); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index bbca3a5e3..0d804c52c 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -496,6 +496,7 @@ public: void reset_rotation() { set_rotation(Vec3d::Zero()); } void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); } void reset_mirror() { set_mirror(Vec3d::Ones()); } + void reset_skew(); const Transform3d& get_matrix() const { return m_matrix; } Transform3d get_matrix_no_offset() const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8a2599866..45ef3c002 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1101,6 +1101,9 @@ wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +wxDEFINE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); @@ -4098,9 +4101,17 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES else if (selection_mode == Selection::Volume) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES model_object->invalidate_bounding_box(); } @@ -4124,6 +4135,45 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) m_dirty = true; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); + + for (unsigned int id : idxs) { + const GLVolume* v = m_volumes.volumes[id]; + int object_idx = v->object_idx(); + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); + model_object->invalidate_bounding_box(); + } + } + + post_event(SimpleEvent(EVT_GLCANVAS_RESET_SKEW)); + + m_dirty = true; +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + void GLCanvas3D::update_gizmos_on_off_state() { set_as_dirty(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 1876500e2..2daa10321 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -156,6 +156,9 @@ wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +wxDECLARE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); @@ -807,6 +810,9 @@ public: void do_rotate(const std::string& snapshot_type); void do_scale(const std::string& snapshot_type); void do_mirror(const std::string& snapshot_type); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void do_reset_skew(const std::string& snapshot_type); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void update_gizmos_on_off_state(); void reset_all_gizmos() { m_gizmos.reset_all_states(); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 141c34c9b..c82e06d7d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -481,6 +481,25 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_skew_label = new wxStaticText(parent, wxID_ANY, _L("Skew")); + m_main_grid_sizer->Add(m_skew_label, 1, wxEXPAND); + + m_reset_skew_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); + m_reset_skew_button->SetToolTip(_L("Reset skew")); + m_reset_skew_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { + selection.setup_cache(); + selection.reset_skew(); + canvas->do_reset_skew(L("Reset skew")); + UpdateAndShow(true); + } + }); + m_main_grid_sizer->Add(m_reset_skew_button); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + m_check_inch = new wxCheckBox(parent, wxID_ANY, _L("Inches")); m_check_inch->SetFont(wxGetApp().normal_font()); @@ -880,6 +899,9 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_rotation = false; bool show_scale = false; bool show_drop_to_bed = false; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool show_skew = false; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -898,8 +920,14 @@ void ObjectManipulation::update_reset_buttons_visibility() if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume* volume = selection.get_first_volume(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d rotation = Transform3d::Identity(); + Transform3d scale = Transform3d::Identity(); + Geometry::Transformation skew; +#else Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* volume = selection.get_first_volume(); @@ -909,27 +937,72 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance()) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& trafo = volume->get_instance_transformation(); + rotation = trafo.get_rotation_matrix(); + scale = trafo.get_scaling_factor_matrix(); + if (trafo.has_skew()) + // the instance transform contains skew + skew = trafo; + else { + // the world transform contains skew + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int id : idxs) { + const Geometry::Transformation world_trafo(selection.get_volume(id)->world_matrix()); + if (world_trafo.has_skew()) { + skew = world_trafo; + break; + } + } + } +#else rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_WORLD_COORDINATE min_z = selection.get_scaled_instance_bounding_box().min.z(); #endif // !ENABLE_WORLD_COORDINATE } else { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& trafo = volume->get_volume_transformation(); + rotation = trafo.get_rotation_matrix(); + scale = trafo.get_scaling_factor_matrix(); + if (trafo.has_skew()) + // the volume transform contains skew + skew = trafo; + else { + // the world transform contains skew + const Geometry::Transformation world_trafo(volume->world_matrix()); + if (world_trafo.has_skew()) + skew = world_trafo; + } +#else rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_WORLD_COORDINATE min_z = get_volume_min_z(*volume); #endif // !ENABLE_WORLD_COORDINATE } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + show_rotation = !rotation.isApprox(Transform3d::Identity()); + show_scale = !scale.isApprox(Transform3d::Identity()); + show_skew = skew.has_skew(); +#else show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_WORLD_COORDINATE show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; #endif // !ENABLE_WORLD_COORDINATE } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed, show_skew] { +#else wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // There is a case (under OSX), when this function is called after the Manipulation panel is hidden // So, let check if Manipulation panel is still shown for this moment if (!this->IsShown()) @@ -937,6 +1010,10 @@ void ObjectManipulation::update_reset_buttons_visibility() m_reset_rotation_button->Show(show_rotation); m_reset_scale_button->Show(show_scale); m_drop_to_bed_button->Show(show_drop_to_bed); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_reset_skew_button->Show(show_skew); + m_skew_label->Show(show_skew); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time Sidebar& panel = wxGetApp().sidebar(); @@ -1423,6 +1500,9 @@ void ObjectManipulation::msw_rescale() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_reset_skew_button->msw_rescale(); +#endif /// ENABLE_TRANSFORMATIONS_BY_MATRICES m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); @@ -1462,6 +1542,9 @@ void ObjectManipulation::sys_color_changed() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_reset_skew_button->msw_rescale(); +#endif /// ENABLE_TRANSFORMATIONS_BY_MATRICES m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 289485dad..696f75128 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -120,9 +120,12 @@ private: wxStaticText* m_empty_str = nullptr; // Non-owning pointers to the reset buttons, so we can hide and show them. - ScalableButton* m_reset_scale_button = nullptr; - ScalableButton* m_reset_rotation_button = nullptr; - ScalableButton* m_drop_to_bed_button = nullptr; + ScalableButton* m_reset_scale_button{ nullptr }; + ScalableButton* m_reset_rotation_button{ nullptr }; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + ScalableButton* m_reset_skew_button{ nullptr }; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + ScalableButton* m_drop_to_bed_button{ nullptr }; wxCheckBox* m_check_inch {nullptr}; @@ -176,6 +179,10 @@ private: wxFlexGridSizer* m_main_grid_sizer; wxFlexGridSizer* m_labels_grid_sizer; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + wxStaticText* m_skew_label{ nullptr }; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + // sizers, used for msw_rescale wxBoxSizer* m_word_local_combo_sizer; std::vector m_rescalable_sizers; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c7ddf8f78..c9cc7fc22 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2085,6 +2085,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + view3D_canvas->Bind(EVT_GLCANVAS_RESET_SKEW, [this](SimpleEvent&) { update(); }); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event& evt) { this->sidebar->enable_buttons(evt.data); }); view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 33ecd2412..346d0fe9b 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1347,8 +1347,6 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation if (!m_valid) return; - std::cout << "Selection::scale_and_translate: " << to_string(scale) << " - " << to_string(translation) << "\n"; - for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; @@ -1412,6 +1410,46 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } + +void Selection::reset_skew() +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); + const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); + if (m_mode == Instance && inst_trafo.has_skew()) { + Geometry::Transformation trafo = inst_trafo; + trafo.reset_skew(); + v.set_instance_transformation(trafo); + } + else if (m_mode == Volume && vol_trafo.has_skew()) { + Geometry::Transformation trafo = vol_trafo; + trafo.reset_skew(); + v.set_volume_transformation(trafo); + } + else { + const Geometry::Transformation world_trafo = inst_trafo * vol_trafo; + if (world_trafo.has_skew()) { + if (m_mode == Instance) { + // TODO + int a = 0; + } + else { + // TODO + int a = 0; + } + } + } + } + + ensure_on_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} #else void Selection::translate(unsigned int object_idx, const Vec3d& displacement) { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index bd9c9216c..0be33b1e8 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -382,6 +382,7 @@ public: void mirror(Axis axis); #if ENABLE_TRANSFORMATIONS_BY_MATRICES void scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type); + void reset_skew(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_TRANSFORMATIONS_BY_MATRICES From fd45d0eeed99df3f9c5f0aa50e55db2d667cbaad Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 12 May 2022 11:26:44 +0200 Subject: [PATCH 086/102] Tech ENABLE_WORLD_COORDINATE_SHOW_AXES - Fixed bed axes visualization --- src/slic3r/GUI/CoordAxes.cpp | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp index 75038e23c..88f4b6c86 100644 --- a/src/slic3r/GUI/CoordAxes.cpp +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -47,25 +47,14 @@ void CoordAxes::render(float emission_factor) m_arrow.init_from(stilized_arrow(16, m_tip_radius, m_tip_length, m_stem_radius, m_stem_length)); GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light_attr"); -#else - bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; - if (shader_differs) { - if (curr_shader != nullptr) - curr_shader->stop_using(); - shader->start_using(); - } + if (curr_shader != nullptr) + curr_shader->stop_using(); + + shader->start_using(); shader->set_uniform("emission_factor", emission_factor); // x axis @@ -104,11 +93,9 @@ void CoordAxes::render(float emission_factor) render_axis(Geometry::assemble_transform(m_origin).cast()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (shader_differs) { - shader->stop_using(); - if (curr_shader != nullptr) - curr_shader->start_using(); - } + shader->stop_using(); + if (curr_shader != nullptr) + curr_shader->start_using(); } } // GUI From 3fcfd04921a4eed5fca2b7e16d46eb92fd0de26e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 12 May 2022 12:07:27 +0200 Subject: [PATCH 087/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Fixed translation of volumes in local coordinate system Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 346d0fe9b..b6d9cfbd4 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -783,8 +783,11 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor else assert(false); } - else - transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(displacement)); + else { + const Vec3d offset = transformation_type.local() ? + volume_data.get_volume_transform().get_rotation_matrix() * displacement : displacement; + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(offset)); + } } #if !DISABLE_INSTANCES_SYNCH From 243985173e70c189ad9a86eefaaea0757d9749cb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 12 May 2022 14:33:41 +0200 Subject: [PATCH 088/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Allow for relative rotations only when using the object manipulator panel --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c82e06d7d..8af365457 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -677,9 +677,16 @@ void ObjectManipulation::update_settings_value(const Selection& selection) else { #if ENABLE_WORLD_COORDINATE m_new_move_label_string = L("Translate"); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_rotate_label_string = L("Rotate"); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_position = Vec3d::Zero(); #endif // ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_rotation = Vec3d::Zero(); +#else m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); m_new_scale = volume->get_instance_scaling_factor() * 100.0; } @@ -728,8 +735,15 @@ void ObjectManipulation::update_settings_value(const Selection& selection) } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_rotate_label_string = L("Rotate"); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_position = Vec3d::Zero(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_rotation = Vec3d::Zero(); +#else m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()); } @@ -1178,19 +1192,27 @@ void ObjectManipulation::change_rotation_value(int axis, double value) GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - TransformationType transformation_type(TransformationType::World_Relative_Joint); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType transformation_type; + transformation_type.set_relative(); +#else + TransformationType transformation_type(TransformationType::World_Relative_Joint); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (selection.is_single_full_instance()) transformation_type.set_independent(); if (is_local_coordinates()) { transformation_type.set_local(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES transformation_type.set_absolute(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES } if (is_instance_coordinates()) transformation_type.set_instance(); #else + TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance() || selection.requires_local_axes()) transformation_type.set_independent(); if (selection.is_single_full_instance() && ! m_world_coordinates) { From 9efeb0b9e594b250987f2f2ff589cdc75b898d18 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 09:27:16 +0200 Subject: [PATCH 089/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Fixed missing update of object manipulator panel after selecting an object in the 3D scene --- src/slic3r/GUI/GLCanvas3D.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2daa10321..9cf6501fc 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -742,7 +742,11 @@ public: void update_volumes_colors_by_extruder(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool is_dragging() const { return m_gizmos.is_dragging() || (m_moving && !m_mouse.scene_position.isApprox(m_mouse.drag.start_position_3D)); } +#else bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void render(); // printable_only == false -> render also non printable volumes as grayed From 9062a74c5c29c9f124d172838ade3cd6088b035d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 09:46:09 +0200 Subject: [PATCH 090/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Fixed scale reset --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8af365457..6b11b46bf 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -460,13 +460,24 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); m_reset_scale_button->SetToolTip(_L("Reset scale")); m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale")); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - bool old_uniform = m_uniform_scale; - m_uniform_scale = true; - change_scale_value(0, 100.0); - m_uniform_scale = old_uniform; + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + if (selection.is_single_volume_or_modifier()) + const_cast(selection.get_first_volume())->set_volume_scaling_factor(Vec3d::Ones()); + else if (selection.is_single_full_instance()) { + for (unsigned int idx : selection.get_volume_idxs()) { + const_cast(selection.get_volume(idx))->set_instance_scaling_factor(Vec3d::Ones()); + } + } + else + return; + + canvas->do_scale(L("Reset scale")); + + UpdateAndShow(true); #else + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale")); change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); From 882a5ffec5ced2cddcf1078ed304b160562e4cfa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 12:07:33 +0200 Subject: [PATCH 091/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Fixed uniform scale using object manipulator panel Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 34 +++++++++++++++++------ src/slic3r/GUI/Selection.cpp | 2 -- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6b11b46bf..0317d0d72 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -468,7 +468,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else if (selection.is_single_full_instance()) { for (unsigned int idx : selection.get_volume_idxs()) { const_cast(selection.get_volume(idx))->set_instance_scaling_factor(Vec3d::Ones()); - } + } } else return; @@ -674,16 +674,18 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (m_world_coordinates) { #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_scale_label_string = L("Scale"); #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = Vec3d(100.0, 100.0, 100.0); #else - m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else { #if ENABLE_WORLD_COORDINATE @@ -733,16 +735,22 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = offset; m_new_rotate_label_string = L("Rotate"); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_scale_label_string = L("Scale"); #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0; #else m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); @@ -762,17 +770,23 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotate_label_string = L("Rotate"); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_scale_label_string = L("Scale"); #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0; #else m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; @@ -1325,8 +1339,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES if (!is_local_coordinates()) transformation_type.set_relative(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #else if (!is_world_coordinates()) transformation_type.set_local(); @@ -1376,8 +1392,10 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES if (!is_local_coordinates()) transformation_type.set_relative(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b6d9cfbd4..a3161e786 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1357,7 +1357,6 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation if (m_mode == Instance) { assert(is_from_fully_selected_instance(i)); if (transformation_type.absolute()) { - assert(transformation_type.local()); assert(transformation_type.joint()); v.set_instance_transformation(Geometry::assemble_transform(inst_trafo.get_offset_matrix(), inst_trafo.get_rotation_matrix(), Geometry::scale_transform(scale), inst_trafo.get_mirror_matrix())); @@ -1392,7 +1391,6 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation } else { if (transformation_type.absolute()) { - assert(transformation_type.local()); const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); v.set_volume_transformation(Geometry::assemble_transform(volume_trafo.get_offset_matrix(), volume_trafo.get_rotation_matrix(), Geometry::scale_transform(scale), volume_trafo.get_mirror_matrix())); From 7c86cf84a39cd868a5b20f28d61d424c2e2cd431 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 12:27:37 +0200 Subject: [PATCH 092/102] Tech ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX merged into tech ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 4 +--- src/slic3r/GUI/Selection.cpp | 28 ++++++++++++++-------------- src/slic3r/GUI/Selection.hpp | 4 ++-- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index c44b914b4..a9de30fb2 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -72,9 +72,7 @@ // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them -#define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) -// Enable rendering the selection bounding box in the current reference system -#define ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX (1 && ENABLE_WORLD_COORDINATE) +#define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (0 && ENABLE_WORLD_COORDINATE) // Enable showing the axes of the current reference system when sidebar hints are active #define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) // Enable alternate implementation of manipulating scale for instances and volumes diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a3161e786..1ce1f899c 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1725,7 +1725,7 @@ void Selection::render(float scale_factor) m_scale_factor = scale_factor; // render cumulative bounding box of selected volumes #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE BoundingBoxf3 box; Transform3d trafo; const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); @@ -1762,7 +1762,7 @@ void Selection::render(float scale_factor) render_bounding_box(box, trafo, ColorRGB::WHITE()); #else render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE #else render_selected_volumes(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -2477,11 +2477,11 @@ void Selection::render_synchronized_volumes() float color[3] = { 1.0f, 1.0f, 0.0f }; #endif // !ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); BoundingBoxf3 box; Transform3d trafo; -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE for (unsigned int i : m_list) { const GLVolume& volume = *(*m_volumes)[i]; @@ -2496,7 +2496,7 @@ void Selection::render_synchronized_volumes() continue; #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE if (coordinates_type == ECoordinatesType::World) { box = v.transformed_convex_hull_bounding_box(); trafo = Transform3d::Identity(); @@ -2512,7 +2512,7 @@ void Selection::render_synchronized_volumes() render_bounding_box(box, trafo, ColorRGB::YELLOW()); #else render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW()); -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE #else render_bounding_box(v.transformed_convex_hull_bounding_box(), color); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -2521,11 +2521,11 @@ void Selection::render_synchronized_volumes() } #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE void Selection::render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color) #else void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color) -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE { #else void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const @@ -2629,32 +2629,32 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con if (shader == nullptr) return; -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo.data())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo); #else shader->set_uniform("view_model_matrix", camera.get_view_matrix()); -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES m_box.set_color(to_rgba(color)); m_box.render(); shader->stop_using(); -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE #else ::glBegin(GL_LINES); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 0be33b1e8..6f42f622c 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -436,11 +436,11 @@ private: void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } void render_synchronized_volumes(); #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE void render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color); #else void render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color); -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE #else void render_selected_volumes() const; void render_bounding_box(const BoundingBoxf3& box, float* color) const; From ebb9a4aadbba4bf42ad85b6593bd8d4da45568f2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 14:27:18 +0200 Subject: [PATCH 093/102] Tech ENABLE_WORLD_COORDINATE_SHOW_AXES merged into tech ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/3DBed.cpp | 12 ++--- src/slic3r/GUI/3DBed.hpp | 12 ++--- src/slic3r/GUI/CoordAxes.cpp | 4 +- src/slic3r/GUI/CoordAxes.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 10 +++- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 29 ++++++++++-- src/slic3r/GUI/Selection.cpp | 65 +++++++++----------------- src/slic3r/GUI/Selection.hpp | 8 +--- 9 files changed, 74 insertions(+), 72 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a9de30fb2..e8f9fc965 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,8 +73,6 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (0 && ENABLE_WORLD_COORDINATE) -// Enable showing the axes of the current reference system when sidebar hints are active -#define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) // Enable alternate implementation of manipulating scale for instances and volumes #define ENABLE_WORLD_COORDINATE_SCALE_REVISITED (1 && ENABLE_WORLD_COORDINATE) // Enable implementation of Geometry::Transformation using matrices only diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 7b34d59bf..503c35a9b 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -102,7 +102,7 @@ const float* GeometryBuffer::get_vertices_data() const } #endif // !ENABLE_LEGACY_OPENGL_REMOVAL -#if !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_WORLD_COORDINATE const float Bed3D::Axes::DefaultStemRadius = 0.5f; const float Bed3D::Axes::DefaultStemLength = 25.0f; const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius; @@ -180,7 +180,7 @@ void Bed3D::Axes::render() glsafe(::glDisable(GL_DEPTH_TEST)); } -#endif // !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_WORLD_COORDINATE bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { @@ -343,11 +343,11 @@ BoundingBoxf3 Bed3D::calc_extended_bounding_box() const out.max.z() = 0.0; // extend to contain axes out.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE out.merge(out.min + Vec3d(-m_axes.get_tip_radius(), -m_axes.get_tip_radius(), out.max.z())); #else out.merge(out.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, out.max.z())); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE // extend to contain model, if any BoundingBoxf3 model_bb = m_model.get_bounding_box(); if (model_bb.defined) { @@ -545,7 +545,7 @@ std::tuple Bed3D::detect_type(const Point void Bed3D::render_axes() { if (m_build_volume.valid()) -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES m_axes.render(Transform3d::Identity(), 0.25f); #else @@ -553,7 +553,7 @@ void Bed3D::render_axes() #endif // ENABLE_GL_SHADERS_ATTRIBUTES #else m_axes.render(); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 61f01f021..3c48a3901 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,11 +3,11 @@ #include "GLTexture.hpp" #include "3DScene.hpp" -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE #include "CoordAxes.hpp" #else #include "GLModel.hpp" -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #include "libslic3r/BuildVolume.hpp" #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -48,7 +48,7 @@ public: class Bed3D { -#if !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_WORLD_COORDINATE class Axes { public: @@ -72,7 +72,7 @@ class Bed3D float get_total_length() const { return m_stem_length + DefaultTipLength; } void render(); }; -#endif // !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_WORLD_COORDINATE public: enum class Type : unsigned char @@ -113,11 +113,11 @@ private: #if !ENABLE_LEGACY_OPENGL_REMOVAL unsigned int m_vbo_id{ 0 }; #endif // !ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE CoordAxes m_axes; #else Axes m_axes; -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE float m_scale_factor{ 1.0f }; diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp index 88f4b6c86..edd1d4f03 100644 --- a/src/slic3r/GUI/CoordAxes.cpp +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -10,7 +10,7 @@ #include -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE namespace Slic3r { namespace GUI { @@ -101,4 +101,4 @@ void CoordAxes::render(float emission_factor) } // GUI } // Slic3r -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/CoordAxes.hpp b/src/slic3r/GUI/CoordAxes.hpp index d2e38e184..9e49d1391 100644 --- a/src/slic3r/GUI/CoordAxes.hpp +++ b/src/slic3r/GUI/CoordAxes.hpp @@ -1,7 +1,7 @@ #ifndef slic3r_CoordAxes_hpp_ #define slic3r_CoordAxes_hpp_ -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE #include "GLModel.hpp" namespace Slic3r { @@ -59,6 +59,6 @@ public: } // GUI } // Slic3r -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #endif // slic3r_CoordAxes_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 19422e167..04ccfb4c8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -267,7 +267,11 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_WORLD_COORDINATE shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); +#else + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); +#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -327,7 +331,11 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); +#if ENABLE_WORLD_COORDINATE + shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix); +#else + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); +#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index af5b3adb2..58095375f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -357,9 +357,10 @@ void GLGizmoScale3D::on_render() else m_bounding_box = selection.get_bounding_box(); - Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); - Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); - Vec3d offset_z = offsets_transform * (Offset * Vec3d::UnitZ()); + const Vec3d& center = m_bounding_box.center(); + const Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); + const Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); + const Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); #endif // ENABLE_WORLD_COORDINATE @@ -397,8 +398,6 @@ void GLGizmoScale3D::on_render() m_grabbers[9].color = (use_constrain && m_hover_id == 7) ? CONSTRAINED_COLOR : m_highlight_color; #else // x axis - const Vec3d center = m_bounding_box.center(); - m_grabbers[0].center = m_transform * Vec3d(m_bounding_box.min.x(), center.y(), center.z()) - offset_x; m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; m_grabbers[1].center = m_transform * Vec3d(m_bounding_box.max.x(), center.y(), center.z()) + offset_x; @@ -457,7 +456,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_WORLD_COORDINATE + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); +#else shader->set_uniform("view_model_matrix", camera.get_view_matrix()); +#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (m_grabbers[0].enabled && m_grabbers[1].enabled) @@ -504,7 +507,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_WORLD_COORDINATE + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); +#else shader->set_uniform("view_model_matrix", camera.get_view_matrix()); +#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_connection(0, 1, m_grabbers[0].color); @@ -537,7 +544,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_WORLD_COORDINATE + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); +#else shader->set_uniform("view_model_matrix", camera.get_view_matrix()); +#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_connection(2, 3, m_grabbers[2].color); @@ -570,7 +581,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_WORLD_COORDINATE + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); +#else shader->set_uniform("view_model_matrix", camera.get_view_matrix()); +#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_connection(4, 5, m_grabbers[4].color); @@ -603,7 +618,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_WORLD_COORDINATE + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); +#else shader->set_uniform("view_model_matrix", camera.get_view_matrix()); +#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_connection(6, 7, m_drag_color); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 1ce1f899c..0212ed305 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -119,12 +119,12 @@ Selection::Selection() , m_scale_factor(1.0f) { this->set_bounding_boxes_dirty(); -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE m_axes.set_stem_radius(0.15f); m_axes.set_stem_length(3.0f); m_axes.set_tip_radius(0.45f); m_axes.set_tip_length(1.5f); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE } @@ -1844,46 +1844,39 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE Transform3d orient_matrix = Transform3d::Identity(); #else glsafe(::glPushMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE const Vec3d center = get_bounding_box().center(); Vec3d axes_center = center; -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { #if ENABLE_GL_SHADERS_ATTRIBUTES shader->set_uniform("emission_factor", 0.05f); #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE const Vec3d& center = get_bounding_box().center(); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { #else if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { #endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES - Transform3d orient_matrix = Transform3d::Identity(); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES #if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); #else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); -#else - glsafe(::glMultMatrixd(orient_matrix.data())); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #else if (!boost::starts_with(sidebar_field, "position")) { #if !ENABLE_GL_SHADERS_ATTRIBUTES @@ -1913,14 +1906,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else else if (is_single_volume() || is_single_modifier()) { #endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE if (!wxGetApp().obj_manipul()->is_world_coordinates()) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES - Transform3d orient_matrix = Transform3d::Identity(); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES if (wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume* v = (*m_volumes)[*m_list.begin()]; #if ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -1928,9 +1918,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation(); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } else { #if ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -1938,15 +1926,8 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); } -#else - } -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glMultMatrixd(orient_matrix.data())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } #else #if ENABLE_GL_SHADERS_ATTRIBUTES @@ -1962,7 +1943,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #endif // ENABLE_WORLD_COORDINATE } else { -#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE if (requires_local_axes()) #if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); @@ -1975,16 +1956,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } -#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES - } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE } + } #if ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); #endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { shader->set_uniform("emission_factor", 0.1f); #if !ENABLE_GL_SHADERS_ATTRIBUTES @@ -1993,7 +1974,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) glsafe(::glMultMatrixd(orient_matrix.data())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES } -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES if (boost::starts_with(sidebar_field, "position")) @@ -2004,12 +1985,12 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field, *shader); -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { if (!wxGetApp().obj_manipul()->is_world_coordinates()) m_axes.render(Geometry::assemble_transform(axes_center) * orient_matrix, 0.25f); } -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #else if (boost::starts_with(sidebar_field, "position")) render_sidebar_position_hints(sidebar_field); @@ -2020,7 +2001,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field); -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { glsafe(::glPopMatrix()); glsafe(::glPushMatrix()); @@ -2030,14 +2011,14 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) m_axes.render(0.25f); glsafe(::glPopMatrix()); } -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 6f42f622c..b62db2e6e 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -4,13 +4,9 @@ #include "libslic3r/Geometry.hpp" #if ENABLE_WORLD_COORDINATE #include "GUI_Geometry.hpp" -#if ENABLE_WORLD_COORDINATE_SHOW_AXES #include "CoordAxes.hpp" #else #include "GLModel.hpp" -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES -#else -#include "GLModel.hpp" #endif // ENABLE_WORLD_COORDINATE #include @@ -238,9 +234,9 @@ private: GLModel m_vbo_sphere; #endif // ENABLE_RENDER_SELECTION_CENTER -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE CoordAxes m_axes; -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE GLModel m_arrow; GLModel m_curved_arrow; #if ENABLE_LEGACY_OPENGL_REMOVAL From e4fb142afcc8db7f2fa63084d5b107d62e570c2e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 15:32:06 +0200 Subject: [PATCH 094/102] Tech ENABLE_WORLD_COORDINATE_SCALE_REVISITED merged into tech ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Model.cpp | 4 +- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/GUI_ObjectManipulation.cpp | 91 ++----- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 306 +++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 21 +- src/slic3r/GUI/Selection.cpp | 8 +- src/slic3r/GUI/Selection.hpp | 4 +- src/slic3r/GUI/wxExtensions.cpp | 4 +- 10 files changed, 202 insertions(+), 252 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 7fb7ed9f1..f3f31ed4f 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1425,11 +1425,11 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) assert(instance_idx < this->instances.size()); const Geometry::Transformation reference_trafo = this->instances[instance_idx]->get_transformation(); -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if !ENABLE_WORLD_COORDINATE if (Geometry::is_rotation_ninety_degrees(reference_trafo.get_rotation())) // nothing to do, scaling in the world coordinate space is possible in the representation of Geometry::Transformation. return; -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_WORLD_COORDINATE bool left_handed = reference_trafo.is_left_handed(); bool has_mirrorring = ! reference_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.)); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index e8f9fc965..250a45f3f 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,8 +73,6 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (0 && ENABLE_WORLD_COORDINATE) -// Enable alternate implementation of manipulating scale for instances and volumes -#define ENABLE_WORLD_COORDINATE_SCALE_REVISITED (1 && ENABLE_WORLD_COORDINATE) // Enable implementation of Geometry::Transformation using matrices only #define ENABLE_TRANSFORMATIONS_BY_MATRICES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 0317d0d72..6fbc55511 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -675,17 +675,17 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale = Vec3d(100.0, 100.0, 100.0); +#if ENABLE_WORLD_COORDINATE && !ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_scale = Vec3d(100.0, 100.0, 100.0); #else m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else { #if ENABLE_WORLD_COORDINATE @@ -736,21 +736,15 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = offset; m_new_rotate_label_string = L("Rotate"); #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#else #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0; #else - m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; + m_new_scale = Vec3d(100.0, 100.0, 100.0); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); @@ -771,22 +765,18 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_volume_offset(); m_new_rotate_label_string = L("Rotate"); #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#else #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0; #else - m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; + m_new_scale = Vec3d(100.0, 100.0, 100.0); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; @@ -861,40 +851,14 @@ void ObjectManipulation::update_if_dirty() #if ENABLE_WORLD_COORDINATE Selection::EUniformScaleRequiredReason reason; if (selection.requires_uniform_scale(&reason)) { -#else - if (selection.requires_uniform_scale()) { -#endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - m_lock_bnt->SetLock(true); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED -#if ENABLE_WORLD_COORDINATE - wxString tooltip; - if (selection.is_single_volume_or_modifier()) { -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) - tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the instance local axes"); - else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_World) - tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the printer axes"); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - } - else if (selection.is_single_full_instance()) { -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - if (reason == Selection::EUniformScaleRequiredReason::InstanceNotAxisAligned_World) - tooltip = _L("You cannot use non-uniform scaling mode for instances non aligned with the printer axes"); - else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) - tooltip = _L("You cannot use non-uniform scaling mode for instances containing non locally axis-aligned parts"); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - } - else - tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); - + wxString tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); m_lock_bnt->SetToolTip(tooltip); #else + if (selection.requires_uniform_scale()) { + m_lock_bnt->SetLock(true); m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); -#endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_lock_bnt->disable(); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE } else { #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -1313,11 +1277,11 @@ void ObjectManipulation::change_size_value(int axis, double value) selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_first_volume()->object_idx()]->raw_mesh_bounding_box().size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE this->do_size(axis, size.cwiseQuotient(ref_size)); #else this->do_scale(axis, size.cwiseQuotient(ref_size)); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; @@ -1333,24 +1297,17 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (is_local_coordinates()) transformation_type.set_local(); else if (is_instance_coordinates()) transformation_type.set_instance(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - if (!is_local_coordinates()) - transformation_type.set_relative(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES -#else - if (!is_world_coordinates()) - transformation_type.set_local(); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED - #if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; #else + if (!is_local_coordinates()) + transformation_type.set_relative(); + bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; @@ -1381,7 +1338,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE void ObjectManipulation::do_size(int axis, const Vec3d& scale) const { Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); @@ -1418,7 +1375,7 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const selection.scale(scaling_factor, transformation_type); wxGetApp().plater()->canvas3D()->do_scale(L("Set Size")); } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value) { @@ -1457,7 +1414,7 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) { const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE if (!use_uniform_scale) { #if !ENABLE_TRANSFORMATIONS_BY_MATRICES int res = selection.bake_transform_if_needed(); @@ -1477,12 +1434,8 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) #if ENABLE_TRANSFORMATIONS_BY_MATRICES set_dirty(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#else -#if ENABLE_WORLD_COORDINATE - if (selection.is_single_full_instance() && is_world_coordinates() && !use_uniform_scale) { #else if (selection.is_single_full_instance() && m_world_coordinates && !use_uniform_scale) { -#endif // ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_first_volume(); @@ -1513,7 +1466,7 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) } m_uniform_scale = use_uniform_scale; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 696f75128..e25e05f60 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -263,11 +263,9 @@ private: void change_scale_value(int axis, double value); void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED - void do_size(int axis, const Vec3d& scale) const; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED - #if ENABLE_WORLD_COORDINATE + void do_size(int axis, const Vec3d& scale) const; + void set_coordinates_type(const wxString& type_string); #endif // ENABLE_WORLD_COORDINATE }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index bee57ec6e..0810ddc97 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -335,7 +335,7 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { return true; } else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) { -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE do_stop_dragging(is_leaving); #else for (auto &grabber : m_grabbers) grabber.dragging = false; @@ -360,14 +360,14 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); // updates camera target constraints m_parent.refresh_camera_scene_box(); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE return true; } } return false; } -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup) { for (auto& grabber : m_grabbers) grabber.dragging = false; @@ -393,7 +393,7 @@ void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup) // updates camera target constraints m_parent.refresh_camera_scene_box(); } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE std::string GLGizmoBase::format(float value, unsigned int decimals) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index fff699479..95763f004 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -89,156 +89,156 @@ protected: static GLModel s_cone; #else GLModel m_cube; -#endif // ENABLE_GIZMO_GRABBER_REFACTOR - }; - -public: - enum EState - { - Off, - On, - Num_States - }; - - struct UpdateData - { - const Linef3& mouse_ray; - const Point& mouse_pos; - - UpdateData(const Linef3& mouse_ray, const Point& mouse_pos) - : mouse_ray(mouse_ray), mouse_pos(mouse_pos) - {} - }; - -protected: - GLCanvas3D& m_parent; - int m_group_id; // TODO: remove only for rotate - EState m_state; - int m_shortcut_key; - std::string m_icon_filename; - unsigned int m_sprite_id; - int m_hover_id; - bool m_dragging; - mutable std::vector m_grabbers; - ImGuiWrapper* m_imgui; - bool m_first_input_window_render; - CommonGizmosDataPool* m_c; -public: - GLGizmoBase(GLCanvas3D& parent, - const std::string& icon_filename, - unsigned int sprite_id); - virtual ~GLGizmoBase() = default; - - bool init() { return on_init(); } - - void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); } - void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); } - - std::string get_name(bool include_shortcut = true) const; - - EState get_state() const { return m_state; } - void set_state(EState state) { m_state = state; on_set_state(); } - - int get_shortcut_key() const { return m_shortcut_key; } - - const std::string& get_icon_filename() const { return m_icon_filename; } - - bool is_activable() const { return on_is_activable(); } - bool is_selectable() const { return on_is_selectable(); } - CommonGizmosDataID get_requirements() const { return on_get_requirements(); } - virtual bool wants_enter_leave_snapshots() const { return false; } - virtual std::string get_gizmo_entering_text() const { assert(false); return ""; } - virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; } - virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); } - void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } - - unsigned int get_sprite_id() const { return m_sprite_id; } - - int get_hover_id() const { return m_hover_id; } - void set_hover_id(int id); - - bool is_dragging() const { return m_dragging; } - - // returns True when Gizmo changed its state - bool update_items_state(); - - void render() { on_render(); } - void render_for_picking() { on_render_for_picking(); } - void render_input_window(float x, float y, float bottom_limit); - - /// - /// Mouse tooltip text - /// - /// Text to be visible in mouse tooltip - virtual std::string get_tooltip() const { return ""; } - - /// - /// Is called when data (Selection) is changed - /// - virtual void data_changed(){}; - - /// - /// Implement when want to process mouse events in gizmo - /// Click, Right click, move, drag, ... - /// - /// Keep information about mouse click - /// Return True when use the information and don't want to propagate it otherwise False. - virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; } -protected: - virtual bool on_init() = 0; - virtual void on_load(cereal::BinaryInputArchive& ar) {} - virtual void on_save(cereal::BinaryOutputArchive& ar) const {} - virtual std::string on_get_name() const = 0; - virtual void on_set_state() {} - virtual void on_set_hover_id() {} - virtual bool on_is_activable() const { return true; } - virtual bool on_is_selectable() const { return true; } - virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); } - virtual void on_enable_grabber(unsigned int id) {} - virtual void on_disable_grabber(unsigned int id) {} - - // called inside use_grabbers - virtual void on_start_dragging() {} - virtual void on_stop_dragging() {} - virtual void on_dragging(const UpdateData& data) {} - - virtual void on_render() = 0; - virtual void on_render_for_picking() = 0; - virtual void on_render_input_window(float x, float y, float bottom_limit) {} - - // Returns the picking color for the given id, based on the BASE_ID constant - // No check is made for clashing with other picking color (i.e. GLVolumes) - ColorRGBA picking_color_component(unsigned int id) const; - - void render_grabbers(const BoundingBoxf3& box) const; - void render_grabbers(float size) const; - void render_grabbers_for_picking(const BoundingBoxf3& box) const; - - std::string format(float value, unsigned int decimals) const; - - // Mark gizmo as dirty to Re-Render when idle() - void set_dirty(); - - /// - /// function which - /// Set up m_dragging and call functions - /// on_start_dragging / on_dragging / on_stop_dragging - /// - /// Keep information about mouse click - /// same as on_mouse - bool use_grabbers(const wxMouseEvent &mouse_event); - -#if ENABLE_WORLD_COORDINATE - void do_stop_dragging(bool perform_mouse_cleanup); -#endif // ENABLE_WORLD_COORDINATE - -private: - // Flag for dirty visible state of Gizmo - // When True then need new rendering - bool m_dirty; -}; - -} // namespace GUI -} // namespace Slic3r - -#endif // slic3r_GLGizmoBase_hpp_ +#endif // ENABLE_GIZMO_GRABBER_REFACTOR + }; + +public: + enum EState + { + Off, + On, + Num_States + }; + + struct UpdateData + { + const Linef3& mouse_ray; + const Point& mouse_pos; + + UpdateData(const Linef3& mouse_ray, const Point& mouse_pos) + : mouse_ray(mouse_ray), mouse_pos(mouse_pos) + {} + }; + +protected: + GLCanvas3D& m_parent; + int m_group_id; // TODO: remove only for rotate + EState m_state; + int m_shortcut_key; + std::string m_icon_filename; + unsigned int m_sprite_id; + int m_hover_id; + bool m_dragging; + mutable std::vector m_grabbers; + ImGuiWrapper* m_imgui; + bool m_first_input_window_render; + CommonGizmosDataPool* m_c; +public: + GLGizmoBase(GLCanvas3D& parent, + const std::string& icon_filename, + unsigned int sprite_id); + virtual ~GLGizmoBase() = default; + + bool init() { return on_init(); } + + void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); } + void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); } + + std::string get_name(bool include_shortcut = true) const; + + EState get_state() const { return m_state; } + void set_state(EState state) { m_state = state; on_set_state(); } + + int get_shortcut_key() const { return m_shortcut_key; } + + const std::string& get_icon_filename() const { return m_icon_filename; } + + bool is_activable() const { return on_is_activable(); } + bool is_selectable() const { return on_is_selectable(); } + CommonGizmosDataID get_requirements() const { return on_get_requirements(); } + virtual bool wants_enter_leave_snapshots() const { return false; } + virtual std::string get_gizmo_entering_text() const { assert(false); return ""; } + virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; } + virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); } + void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } + + unsigned int get_sprite_id() const { return m_sprite_id; } + + int get_hover_id() const { return m_hover_id; } + void set_hover_id(int id); + + bool is_dragging() const { return m_dragging; } + + // returns True when Gizmo changed its state + bool update_items_state(); + + void render() { on_render(); } + void render_for_picking() { on_render_for_picking(); } + void render_input_window(float x, float y, float bottom_limit); + + /// + /// Mouse tooltip text + /// + /// Text to be visible in mouse tooltip + virtual std::string get_tooltip() const { return ""; } + + /// + /// Is called when data (Selection) is changed + /// + virtual void data_changed(){}; + + /// + /// Implement when want to process mouse events in gizmo + /// Click, Right click, move, drag, ... + /// + /// Keep information about mouse click + /// Return True when use the information and don't want to propagate it otherwise False. + virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; } +protected: + virtual bool on_init() = 0; + virtual void on_load(cereal::BinaryInputArchive& ar) {} + virtual void on_save(cereal::BinaryOutputArchive& ar) const {} + virtual std::string on_get_name() const = 0; + virtual void on_set_state() {} + virtual void on_set_hover_id() {} + virtual bool on_is_activable() const { return true; } + virtual bool on_is_selectable() const { return true; } + virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); } + virtual void on_enable_grabber(unsigned int id) {} + virtual void on_disable_grabber(unsigned int id) {} + + // called inside use_grabbers + virtual void on_start_dragging() {} + virtual void on_stop_dragging() {} + virtual void on_dragging(const UpdateData& data) {} + + virtual void on_render() = 0; + virtual void on_render_for_picking() = 0; + virtual void on_render_input_window(float x, float y, float bottom_limit) {} + + // Returns the picking color for the given id, based on the BASE_ID constant + // No check is made for clashing with other picking color (i.e. GLVolumes) + ColorRGBA picking_color_component(unsigned int id) const; + + void render_grabbers(const BoundingBoxf3& box) const; + void render_grabbers(float size) const; + void render_grabbers_for_picking(const BoundingBoxf3& box) const; + + std::string format(float value, unsigned int decimals) const; + + // Mark gizmo as dirty to Re-Render when idle() + void set_dirty(); + + /// + /// function which + /// Set up m_dragging and call functions + /// on_start_dragging / on_dragging / on_stop_dragging + /// + /// Keep information about mouse click + /// same as on_mouse + bool use_grabbers(const wxMouseEvent &mouse_event); + +#if ENABLE_WORLD_COORDINATE + void do_stop_dragging(bool perform_mouse_cleanup); +#endif // ENABLE_WORLD_COORDINATE + +private: + // Flag for dirty visible state of Gizmo + // When True then need new rendering + bool m_dirty; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoBase_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 58095375f..1d87d772a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -80,7 +80,7 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) if (mouse_event.Dragging()) { if (m_dragging) { #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE int res = 1; if (m_scale.x() != m_scale.y() || m_scale.x() != m_scale.z()) res = m_parent.get_selection().bake_transform_if_needed(); @@ -90,7 +90,7 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) return true; } else { -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Apply new temporary scale factors #if ENABLE_WORLD_COORDINATE @@ -123,9 +123,9 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) #endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED - } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE + } +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES } } @@ -138,9 +138,6 @@ void GLGizmoScale3D::data_changed() const Selection &selection = m_parent.get_selection(); #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - bool enable_scale_xyz = !selection.requires_uniform_scale(); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED #else bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || @@ -149,24 +146,28 @@ void GLGizmoScale3D::data_changed() #if ENABLE_TRANSFORMATIONS_BY_MATRICES set_scale(Vec3d::Ones()); #else -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { #else for (unsigned int i = 0; i < 6; ++i) m_grabbers[i].enabled = enable_scale_xyz; if (enable_scale_xyz) { +#endif // ENABLE_WORLD_COORDINATE // all volumes in the selection belongs to the same instance, any of // them contains the needed data, so we take the first const GLVolume* volume = selection.get_first_volume(); if (selection.is_single_full_instance()) set_scale(volume->get_instance_scaling_factor()); +#if ENABLE_WORLD_COORDINATE + else if (selection.is_single_volume_or_modifier()) +#else else if (selection.is_single_volume() || selection.is_single_modifier()) +#endif // ENABLE_WORLD_COORDINATE set_scale(volume->get_volume_scaling_factor()); } else set_scale(Vec3d::Ones()); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 0212ed305..c377fda16 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -10,9 +10,9 @@ #include "Camera.hpp" #include "Plater.hpp" -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE #include "MsgDialog.hpp" -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE #include "Gizmos/GLGizmoBase.hpp" @@ -1559,7 +1559,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co this->set_bounding_boxes_dirty(); } -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE int Selection::bake_transform_if_needed() const { if ((is_single_full_instance() && wxGetApp().obj_manipul()->is_world_coordinates()) || @@ -1610,7 +1610,7 @@ int Selection::bake_transform_if_needed() const return 1; } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE void Selection::erase() { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index b62db2e6e..c919927ea 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -386,13 +386,13 @@ public: #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE // returns: // -1 if the user refused to proceed with baking when asked // 0 if the baking was performed // 1 if no baking was needed int bake_transform_if_needed() const; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE void erase(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index bacdb1f9c..876398510 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -581,12 +581,12 @@ void LockButton::OnButton(wxCommandEvent& event) if (m_disabled) return; -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE SetLock(!m_is_pushed); #else m_is_pushed = !m_is_pushed; update_button_bitmaps(); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE event.Skip(); } From 2602c6bf92f13ebb02a96c3ba0c15252b12c4d82 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 18 May 2022 09:11:20 +0200 Subject: [PATCH 095/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Implemented reset skew button for the case when volume world matrix contains skew while volume and instance matrices do not Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c377fda16..60bc8346e 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1422,7 +1422,13 @@ void Selection::reset_skew() const VolumeCache& volume_data = m_cache.volumes_data[i]; const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); - if (m_mode == Instance && inst_trafo.has_skew()) { + const Geometry::Transformation world_trafo = inst_trafo * vol_trafo; + if (!inst_trafo.has_skew() && !vol_trafo.has_skew() && world_trafo.has_skew()) { + Geometry::Transformation mod_world_trafo = Geometry::Transformation(world_trafo.get_matrix_no_offset()); + mod_world_trafo.reset_skew(); + v.set_volume_transformation(vol_trafo.get_offset_matrix() * inst_trafo.get_matrix_no_offset().inverse() * mod_world_trafo.get_matrix()); + } + else if (m_mode == Instance && inst_trafo.has_skew()) { Geometry::Transformation trafo = inst_trafo; trafo.reset_skew(); v.set_instance_transformation(trafo); @@ -1432,19 +1438,6 @@ void Selection::reset_skew() trafo.reset_skew(); v.set_volume_transformation(trafo); } - else { - const Geometry::Transformation world_trafo = inst_trafo * vol_trafo; - if (world_trafo.has_skew()) { - if (m_mode == Instance) { - // TODO - int a = 0; - } - else { - // TODO - int a = 0; - } - } - } } ensure_on_bed(); From e3d648c8025edfd0465ae543c58a21846f04e685 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 18 May 2022 10:58:27 +0200 Subject: [PATCH 096/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reoworked calculation of volume matrix for newly added modifiers and parts Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 6 ++---- src/libslic3r/Geometry.hpp | 2 ++ src/slic3r/GUI/GUI_ObjectList.cpp | 14 +++++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index a611e10c0..6e593af94 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -764,6 +764,7 @@ Transformation Transformation::operator * (const Transformation& other) const return Transformation(get_matrix() * other.get_matrix()); } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox) { Transformation out; @@ -771,11 +772,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation if (instance_transformation.is_scaling_uniform()) { // No need to run the non-linear least squares fitting for uniform scaling. // Just set the inverse. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - out.set_matrix(instance_transformation.get_matrix_no_offset().inverse()); -#else out.set_from_transform(instance_transformation.get_matrix(true).inverse()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) { // Anisotropic scaling, rotation by multiples of ninety degrees. @@ -823,6 +820,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation return out; } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // For parsing a transformation matrix from 3MF / AMF. Transform3d transform3d_from_string(const std::string& transform_str) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 0d804c52c..725ced361 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -509,10 +509,12 @@ public: Transformation operator * (const Transformation& other) const; +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity // as possible in least squares norm in regard to the 8 corners of bbox. // Bounding box is expected to be centered around zero in all axes. static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES private: friend class cereal::access; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index ecf015b6d..73946786d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1584,9 +1584,15 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo new_volume->source.mesh_offset = model.objects.front()->volumes.front()->source.mesh_offset; if (from_galery) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); + // Transform the new modifier to be aligned with the print bed. + const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); +#else // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(inst_transform, mesh_bb)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Set the modifier position. // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. const Vec3d offset = Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - instance_offset; @@ -1655,9 +1661,15 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_first_volume(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); // Transform the new modifier to be aligned with the print bed. - const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); + const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); +#else + // Transform the new modifier to be aligned with the print bed. + const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Set the modifier position. auto offset = (type_name == "Slab") ? // Slab: Lift to print bed From 3b3edb5a9743c9597ecb894fe74bb5bd9c2852f0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 18 May 2022 12:40:38 +0200 Subject: [PATCH 097/102] Fixed build and warnings on Linux and Mac Fixed conflicts during rebase with master --- src/slic3r/GUI/GLCanvas3D.cpp | 1 - src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 ++ src/slic3r/GUI/Selection.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 45ef3c002..959611bea 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4146,7 +4146,6 @@ void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) std::set> done; // keeps track of modified instances - Selection::EMode selection_mode = m_selection.get_mode(); const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); for (unsigned int id : idxs) { diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6fbc55511..74cb64e9f 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1413,7 +1413,9 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE if (!use_uniform_scale) { #if !ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 60bc8346e..4a60cbe63 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -785,7 +785,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor } else { const Vec3d offset = transformation_type.local() ? - volume_data.get_volume_transform().get_rotation_matrix() * displacement : displacement; + (Vec3d)(volume_data.get_volume_transform().get_rotation_matrix() * displacement) : displacement; transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(offset)); } } From b76f9fc2eef4776d1945727f0c860e26f6d0102e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 19 May 2022 10:01:54 +0200 Subject: [PATCH 098/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Scaling using object manipulator fields Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 +- src/slic3r/GUI/Selection.cpp | 147 ++++++++++++++++------ src/slic3r/GUI/Selection.hpp | 46 ++++++- 4 files changed, 159 insertions(+), 43 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 73946786d..a8c9819a6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1585,8 +1585,8 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo if (from_galery) { #if ENABLE_TRANSFORMATIONS_BY_MATRICES - new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); // Transform the new modifier to be aligned with the print bed. + new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); #else // Transform the new modifier to be aligned with the print bed. @@ -1662,8 +1662,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); // Transform the new modifier to be aligned with the print bed. + new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); #else // Transform the new modifier to be aligned with the print bed. diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 74cb64e9f..ff8fe940c 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1303,7 +1303,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const transformation_type.set_instance(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; + if (!selection.is_single_full_instance() && !selection.is_single_volume_or_modifier()) + transformation_type.set_relative(); + + const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; #else if (!is_local_coordinates()) transformation_type.set_relative(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 4a60cbe63..be6abeee3 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -713,6 +713,8 @@ const BoundingBoxf3& Selection::get_bounding_box() const const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const { + assert(is_single_full_instance()); + if (!m_unscaled_instance_bounding_box.has_value()) { std::optional* bbox = const_cast*>(&m_unscaled_instance_bounding_box); *bbox = BoundingBoxf3(); @@ -736,6 +738,8 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const { + assert(is_single_full_instance()); + if (!m_scaled_instance_bounding_box.has_value()) { std::optional* bbox = const_cast*>(&m_scaled_instance_bounding_box); *bbox = BoundingBoxf3(); @@ -753,6 +757,65 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const return *m_scaled_instance_bounding_box; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +const BoundingBoxf3& Selection::get_full_unscaled_instance_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_unscaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_full_unscaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_unscaled_instance_bounding_box; +} + +const BoundingBoxf3& Selection::get_full_scaled_instance_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_scaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_full_scaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_instance_transformation().get_matrix() * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_scaled_instance_bounding_box; +} + +const BoundingBoxf3& Selection::get_full_unscaled_instance_local_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_unscaled_instance_local_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_full_unscaled_instance_local_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_unscaled_instance_local_bounding_box; +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + void Selection::setup_cache() { if (!m_valid) @@ -1350,54 +1413,66 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation if (!m_valid) return; + Vec3d relative_scale = scale; + for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); - if (m_mode == Instance) { - assert(is_from_fully_selected_instance(i)); - if (transformation_type.absolute()) { - assert(transformation_type.joint()); - v.set_instance_transformation(Geometry::assemble_transform(inst_trafo.get_offset_matrix(), inst_trafo.get_rotation_matrix(), - Geometry::scale_transform(scale), inst_trafo.get_mirror_matrix())); + + if (transformation_type.absolute()) { + // convert from absolute scaling to relative scaling + BoundingBoxf3 original_box; + if (m_mode == Instance) { + assert(is_from_fully_selected_instance(i)); + if (transformation_type.world()) + original_box = get_full_unscaled_instance_bounding_box(); + else + original_box = get_full_unscaled_instance_local_bounding_box(); } else { - if (transformation_type.world()) { - const Transform3d scale_matrix = Geometry::scale_transform(scale); - const Transform3d offset_matrix = (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) ? - // non-constrained scaling - add offset to scale around selection center - Geometry::translation_transform(m_cache.dragging_center + scale_matrix * (inst_trafo.get_offset() - m_cache.dragging_center)) : - // constrained scaling - add offset to keep constraint - Geometry::translation_transform(translation) * inst_trafo.get_offset_matrix(); - v.set_instance_transformation(offset_matrix * scale_matrix * inst_trafo.get_matrix_no_offset()); - } - else if (transformation_type.local()) { - const Transform3d scale_matrix = Geometry::scale_transform(scale); - Vec3d offset; - if (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) { - // non-constrained scaling - add offset to scale around selection center - offset = inst_trafo.get_matrix_no_offset().inverse() * (inst_trafo.get_offset() - m_cache.dragging_center); - offset = inst_trafo.get_matrix_no_offset() * (scale_matrix * offset - offset); - } - else - // constrained scaling - add offset to keep constraint - offset = translation; + if (transformation_type.world()) + original_box = v.transformed_convex_hull_bounding_box((volume_data.get_instance_transform() * + volume_data.get_volume_transform()).get_matrix_no_scaling_factor()); + else if (transformation_type.instance()) + original_box = v.transformed_convex_hull_bounding_box(volume_data.get_volume_transform().get_matrix_no_scaling_factor()); + else + original_box = v.bounding_box(); + } - v.set_instance_transformation(Geometry::translation_transform(offset) * inst_trafo.get_matrix() * scale_matrix); + relative_scale = original_box.size().cwiseProduct(scale).cwiseQuotient(m_box.get_bounding_box().size()); + } + + if (m_mode == Instance) { + assert(is_from_fully_selected_instance(i)); + if (transformation_type.world()) { + const Transform3d scale_matrix = Geometry::scale_transform(relative_scale); + const Transform3d offset_matrix = (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) ? + // non-constrained scaling - add offset to scale around selection center + Geometry::translation_transform(m_cache.dragging_center + scale_matrix * (inst_trafo.get_offset() - m_cache.dragging_center)) : + // constrained scaling - add offset to keep constraint + Geometry::translation_transform(translation) * inst_trafo.get_offset_matrix(); + v.set_instance_transformation(offset_matrix * scale_matrix * inst_trafo.get_matrix_no_offset()); + } + else if (transformation_type.local()) { + const Transform3d scale_matrix = Geometry::scale_transform(relative_scale); + Vec3d offset; + if (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) { + // non-constrained scaling - add offset to scale around selection center + offset = inst_trafo.get_matrix_no_offset().inverse() * (inst_trafo.get_offset() - m_cache.dragging_center); + offset = inst_trafo.get_matrix_no_offset() * (scale_matrix * offset - offset); } else - assert(false); - } - } - else { - if (transformation_type.absolute()) { - const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); - v.set_volume_transformation(Geometry::assemble_transform(volume_trafo.get_offset_matrix(), volume_trafo.get_rotation_matrix(), - Geometry::scale_transform(scale), volume_trafo.get_mirror_matrix())); + // constrained scaling - add offset to keep constraint + offset = translation; + + v.set_instance_transformation(Geometry::translation_transform(offset) * inst_trafo.get_matrix() * scale_matrix); } else - transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(scale)); + assert(false); } + else + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(relative_scale)); } #if !DISABLE_INSTANCES_SYNCH diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c919927ea..a20e952f3 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -225,10 +225,24 @@ private: Cache m_cache; Clipboard m_clipboard; std::optional m_bounding_box; - // Bounding box of a selection, with no instance scaling applied. This bounding box - // is useful for absolute scaling of tilted objects in world coordinate space. + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // This bounding box is useful for absolute scaling of tilted objects in world coordinate space. + // Modifiers are NOT taken in account std::optional m_unscaled_instance_bounding_box; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are NOT taken in account std::optional m_scaled_instance_bounding_box; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // Modifiers are taken in account + std::optional m_full_unscaled_instance_bounding_box; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are taken in account + std::optional m_full_scaled_instance_bounding_box; + // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. + // Modifiers are taken in account + std::optional m_full_unscaled_instance_local_bounding_box; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_RENDER_SELECTION_CENTER GLModel m_vbo_sphere; @@ -355,10 +369,25 @@ public: unsigned int volumes_count() const { return (unsigned int)m_list.size(); } const BoundingBoxf3& get_bounding_box() const; - // Bounding box of a selection, with no instance scaling applied. This bounding box - // is useful for absolute scaling of tilted objects in world coordinate space. + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // This bounding box is useful for absolute scaling of tilted objects in world coordinate space. + // Modifiers are NOT taken in account const BoundingBoxf3& get_unscaled_instance_bounding_box() const; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are NOT taken in account const BoundingBoxf3& get_scaled_instance_bounding_box() const; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // Modifiers are taken in account + const BoundingBoxf3& get_full_unscaled_instance_bounding_box() const; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are taken in account + const BoundingBoxf3& get_full_scaled_instance_bounding_box() const; + + // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. + // Modifiers are taken in account + const BoundingBoxf3& get_full_unscaled_instance_local_bounding_box() const; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void setup_cache(); @@ -429,7 +458,16 @@ private: void do_remove_volume(unsigned int volume_idx); void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_bounding_boxes_dirty() { + m_bounding_box.reset(); + m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); + m_full_unscaled_instance_bounding_box.reset(); m_full_scaled_instance_bounding_box.reset(); + m_full_unscaled_instance_local_bounding_box.reset();; + } +#else void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void render_synchronized_volumes(); #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_WORLD_COORDINATE From 4846b504a2977dc31fe8db3855356cc2cc621466 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 19 May 2022 12:19:18 +0200 Subject: [PATCH 099/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Improved detection and removal of skew in matrices Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 30 ++++---------- src/slic3r/GUI/Selection.cpp | 50 +++++++++++++++-------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ff8fe940c..c6ca3f746 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -944,18 +944,12 @@ void ObjectManipulation::update_reset_buttons_visibility() const Geometry::Transformation& trafo = volume->get_instance_transformation(); rotation = trafo.get_rotation_matrix(); scale = trafo.get_scaling_factor_matrix(); - if (trafo.has_skew()) - // the instance transform contains skew - skew = trafo; - else { - // the world transform contains skew - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int id : idxs) { - const Geometry::Transformation world_trafo(selection.get_volume(id)->world_matrix()); - if (world_trafo.has_skew()) { - skew = world_trafo; - break; - } + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int id : idxs) { + const Geometry::Transformation world_trafo(selection.get_volume(id)->world_matrix()); + if (world_trafo.has_skew()) { + skew = world_trafo; + break; } } #else @@ -971,15 +965,9 @@ void ObjectManipulation::update_reset_buttons_visibility() const Geometry::Transformation& trafo = volume->get_volume_transformation(); rotation = trafo.get_rotation_matrix(); scale = trafo.get_scaling_factor_matrix(); - if (trafo.has_skew()) - // the volume transform contains skew - skew = trafo; - else { - // the world transform contains skew - const Geometry::Transformation world_trafo(volume->world_matrix()); - if (world_trafo.has_skew()) - skew = world_trafo; - } + const Geometry::Transformation world_trafo(volume->world_matrix()); + if (world_trafo.has_skew()) + skew = world_trafo; #else rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index be6abeee3..02edd9c31 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1495,23 +1495,41 @@ void Selection::reset_skew() for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); - const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); - const Geometry::Transformation world_trafo = inst_trafo * vol_trafo; - if (!inst_trafo.has_skew() && !vol_trafo.has_skew() && world_trafo.has_skew()) { - Geometry::Transformation mod_world_trafo = Geometry::Transformation(world_trafo.get_matrix_no_offset()); - mod_world_trafo.reset_skew(); - v.set_volume_transformation(vol_trafo.get_offset_matrix() * inst_trafo.get_matrix_no_offset().inverse() * mod_world_trafo.get_matrix()); + Geometry::Transformation inst_trafo = volume_data.get_instance_transform(); + Geometry::Transformation vol_trafo = volume_data.get_volume_transform(); + Geometry::Transformation world_trafo = inst_trafo * vol_trafo; + if (world_trafo.has_skew()) { + if (!inst_trafo.has_skew() && !vol_trafo.has_skew()) { + // = [I][V] + world_trafo.reset_offset(); + world_trafo.reset_skew(); + v.set_volume_transformation(vol_trafo.get_offset_matrix() * inst_trafo.get_matrix_no_offset().inverse() * world_trafo.get_matrix()); + } + else { + // = + // = [V] + // = [I] + if (inst_trafo.has_skew()) { + inst_trafo.reset_skew(); + v.set_instance_transformation(inst_trafo); + } + if (vol_trafo.has_skew()) { + vol_trafo.reset_skew(); + v.set_volume_transformation(vol_trafo); + } + } } - else if (m_mode == Instance && inst_trafo.has_skew()) { - Geometry::Transformation trafo = inst_trafo; - trafo.reset_skew(); - v.set_instance_transformation(trafo); - } - else if (m_mode == Volume && vol_trafo.has_skew()) { - Geometry::Transformation trafo = vol_trafo; - trafo.reset_skew(); - v.set_volume_transformation(trafo); + else { + // [W] = [I][V] + // [W] = + if (inst_trafo.has_skew()) { + inst_trafo.reset_skew(); + v.set_instance_transformation(inst_trafo); + } + if (vol_trafo.has_skew()) { + vol_trafo.reset_skew(); + v.set_volume_transformation(vol_trafo); + } } } From f591535d2050aa306a18bb85d2d533cfdb39e5a3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 1 Jun 2022 10:13:36 +0200 Subject: [PATCH 100/102] Removed tech ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET --- src/libslic3r/Technologies.hpp | 2 -- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 ---- 2 files changed, 6 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 250a45f3f..4f0a04df1 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -71,8 +71,6 @@ #define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) -// Enable showing world coordinates of volumes' offset relative to the instance containing them -#define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (0 && ENABLE_WORLD_COORDINATE) // Enable implementation of Geometry::Transformation using matrices only #define ENABLE_TRANSFORMATIONS_BY_MATRICES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c6ca3f746..8f5445f54 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -727,11 +727,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (is_world_coordinates()) { const Geometry::Transformation trafo(volume->world_matrix()); -#if ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET - const Vec3d offset = trafo.get_offset() - volume->get_instance_offset(); -#else const Vec3d& offset = trafo.get_offset(); -#endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET m_new_position = offset; m_new_rotate_label_string = L("Rotate"); From 00878fb330dcdf2034ac7f7b7e05c9d98ac3ad91 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 1 Jun 2022 14:14:46 +0200 Subject: [PATCH 101/102] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES merged into ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 59 ++-- src/libslic3r/Geometry.hpp | 62 ++-- src/libslic3r/Model.cpp | 50 ++-- src/libslic3r/Model.hpp | 43 ++- src/libslic3r/Point.hpp | 4 +- src/libslic3r/PrintApply.cpp | 8 +- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/3DScene.hpp | 28 +- src/slic3r/GUI/GLCanvas3D.cpp | 48 ++-- src/slic3r/GUI/GLCanvas3D.hpp | 12 +- src/slic3r/GUI/GUI_ObjectList.cpp | 16 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 212 +++----------- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 29 -- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 24 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 56 +--- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 203 ++----------- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 +- src/slic3r/GUI/MeshUtils.cpp | 8 +- src/slic3r/GUI/Plater.cpp | 12 +- src/slic3r/GUI/Selection.cpp | 286 ++++--------------- src/slic3r/GUI/Selection.hpp | 47 ++- 26 files changed, 340 insertions(+), 897 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 6e593af94..115d21693 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -414,7 +414,7 @@ Vec3d extract_euler_angles(const Transform3d& transform) return extract_euler_angles(m); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transform3d Transformation::get_offset_matrix() const { return assemble_transform(get_offset()); @@ -499,11 +499,11 @@ void Transformation::set_offset(Axis axis, double offset) m_dirty = true; } } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Transformation::set_rotation(const Vec3d& rotation) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Vec3d offset = get_offset(); m_matrix = rotation_transform(rotation) * extract_scale(m_matrix); m_matrix.translation() = offset; @@ -511,7 +511,7 @@ void Transformation::set_rotation(const Vec3d& rotation) set_rotation(X, rotation.x()); set_rotation(Y, rotation.y()); set_rotation(Z, rotation.z()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } void Transformation::set_rotation(Axis axis, double rotation) @@ -520,7 +520,7 @@ void Transformation::set_rotation(Axis axis, double rotation) if (is_approx(std::abs(rotation), 2.0 * double(PI))) rotation = 0.0; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE auto [curr_rotation, scale] = extract_rotation_scale(m_matrix); Vec3d angles = extract_euler_angles(curr_rotation); angles[axis] = rotation; @@ -533,10 +533,10 @@ void Transformation::set_rotation(Axis axis, double rotation) m_rotation(axis) = rotation; m_dirty = true; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d Transformation::get_scaling_factor() const { const Transform3d scale = extract_scale(m_matrix); @@ -547,11 +547,11 @@ Transform3d Transformation::get_scaling_factor_matrix() const { return extract_scale(m_matrix); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Transformation::set_scaling_factor(const Vec3d& scaling_factor) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE assert(scaling_factor.x() > 0.0 && scaling_factor.y() > 0.0 && scaling_factor.z() > 0.0); const Vec3d offset = get_offset(); @@ -561,12 +561,12 @@ void Transformation::set_scaling_factor(const Vec3d& scaling_factor) set_scaling_factor(X, scaling_factor.x()); set_scaling_factor(Y, scaling_factor.y()); set_scaling_factor(Z, scaling_factor.z()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } void Transformation::set_scaling_factor(Axis axis, double scaling_factor) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE assert(scaling_factor > 0.0); auto [rotation, scale] = extract_rotation_scale(m_matrix); scale(axis, axis) = scaling_factor; @@ -579,10 +579,10 @@ void Transformation::set_scaling_factor(Axis axis, double scaling_factor) m_scaling_factor(axis) = std::abs(scaling_factor); m_dirty = true; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d Transformation::get_mirror() const { const Transform3d scale = extract_scale(m_matrix); @@ -594,11 +594,11 @@ Transform3d Transformation::get_mirror_matrix() const const Vec3d scale = get_scaling_factor(); return scale_transform({ scale.x() / std::abs(scale.x()), scale.y() / std::abs(scale.y()), scale.z() / std::abs(scale.z()) }); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Transformation::set_mirror(const Vec3d& mirror) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d copy(mirror); const Vec3d abs_mirror = copy.cwiseAbs(); for (int i = 0; i < 3; ++i) { @@ -619,7 +619,7 @@ void Transformation::set_mirror(const Vec3d& mirror) set_mirror(X, mirror.x()); set_mirror(Y, mirror.y()); set_mirror(Z, mirror.z()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } void Transformation::set_mirror(Axis axis, double mirror) @@ -630,7 +630,7 @@ void Transformation::set_mirror(Axis axis, double mirror) else if (abs_mirror != 1.0) mirror /= abs_mirror; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const double curr_scale = get_scaling_factor(axis); const double sign = curr_scale * mirror; set_scaling_factor(axis, sign < 0.0 ? std::abs(curr_scale) * mirror : curr_scale); @@ -639,10 +639,10 @@ void Transformation::set_mirror(Axis axis, double mirror) m_mirror(axis) = mirror; m_dirty = true; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE bool Transformation::has_skew() const { return contains_skew(m_matrix); @@ -685,23 +685,23 @@ void Transformation::set_from_transform(const Transform3d& transform) // if (!m_matrix.isApprox(transform)) // std::cout << "something went wrong in extracting data from matrix" << std::endl; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Transformation::reset() { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); m_rotation = Vec3d::Zero(); m_scaling_factor = Vec3d::Ones(); m_mirror = Vec3d::Ones(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE m_matrix = Transform3d::Identity(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE m_dirty = false; -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Transformation::reset_skew() { Matrix3d rotation; @@ -757,14 +757,14 @@ const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rot return m_matrix; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Transformation Transformation::operator * (const Transformation& other) const { return Transformation(get_matrix() * other.get_matrix()); } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox) { Transformation out; @@ -810,8 +810,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation out.set_scaling_factor(Vec3d(std::abs(scale.x()), std::abs(scale.y()), std::abs(scale.z()))); out.set_mirror(Vec3d(scale.x() > 0 ? 1. : -1, scale.y() > 0 ? 1. : -1, scale.z() > 0 ? 1. : -1)); } - else - { + else { // General anisotropic scaling, general rotation. // Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world. // Scale it to get the required size. @@ -820,7 +819,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation return out; } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE // For parsing a transformation matrix from 3MF / AMF. Transform3d transform3d_from_string(const std::string& transform_str) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 725ced361..6ff52016e 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -381,7 +381,7 @@ Vec3d extract_euler_angles(const Transform3d& transform); class Transformation { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transform3d m_matrix{ Transform3d::Identity() }; #else struct Flags @@ -403,47 +403,43 @@ class Transformation mutable Transform3d m_matrix{ Transform3d::Identity() }; mutable Flags m_flags; mutable bool m_dirty{ false }; -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE public: -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transformation() = default; - explicit Transformation(const Transform3d& transform) : m_matrix(transform) {} -#else - Transformation(); - explicit Transformation(const Transform3d& transform); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + explicit Transformation(const Transform3d & transform) : m_matrix(transform) {} -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d get_offset() const { return m_matrix.translation(); } double get_offset(Axis axis) const { return get_offset()[axis]; } Transform3d get_offset_matrix() const; - void set_offset(const Vec3d& offset) { m_matrix.translation() = offset; } + void set_offset(const Vec3d & offset) { m_matrix.translation() = offset; } void set_offset(Axis axis, double offset) { m_matrix.translation()[axis] = offset; } -#else - const Vec3d& get_offset() const { return m_offset; } - double get_offset(Axis axis) const { return m_offset(axis); } - void set_offset(const Vec3d& offset); - void set_offset(Axis axis, double offset); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d get_rotation() const; double get_rotation(Axis axis) const { return get_rotation()[axis]; } Transform3d get_rotation_matrix() const; #else + Transformation(); + explicit Transformation(const Transform3d & transform); + + const Vec3d& get_offset() const { return m_offset; } + double get_offset(Axis axis) const { return m_offset(axis); } + + void set_offset(const Vec3d & offset); + void set_offset(Axis axis, double offset); + const Vec3d& get_rotation() const { return m_rotation; } double get_rotation(Axis axis) const { return m_rotation(axis); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_scaling_factor() const; double get_scaling_factor(Axis axis) const { return get_scaling_factor()[axis]; } @@ -456,12 +452,12 @@ public: #else const Vec3d& get_scaling_factor() const { return m_scaling_factor; } double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void set_scaling_factor(const Vec3d& scaling_factor); void set_scaling_factor(Axis axis, double scaling_factor); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_mirror() const; double get_mirror(Axis axis) const { return get_mirror()[axis]; } @@ -477,21 +473,19 @@ public: const Vec3d& get_mirror() const { return m_mirror; } double get_mirror(Axis axis) const { return m_mirror(axis); } bool is_left_handed() const { return m_mirror.x() * m_mirror.y() * m_mirror.z() < 0.; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void set_mirror(const Vec3d& mirror); void set_mirror(Axis axis, double mirror); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE bool has_skew() const; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#else void set_from_transform(const Transform3d& transform); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void reset(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void reset_offset() { set_offset(Vec3d::Zero()); } void reset_rotation() { set_rotation(Vec3d::Zero()); } void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); } @@ -505,20 +499,20 @@ public: void set_matrix(const Transform3d& transform) { m_matrix = transform; } #else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Transformation operator * (const Transformation& other) const; -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity // as possible in least squares norm in regard to the 8 corners of bbox. // Bounding box is expected to be centered around zero in all axes. static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE private: friend class cereal::access; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE template void serialize(Archive& ar) { ar(m_matrix); } explicit Transformation(int) {} template static void load_and_construct(Archive& ar, cereal::construct& construct) @@ -536,7 +530,7 @@ private: construct(1); ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE }; // For parsing a transformation matrix from 3MF / AMF. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index f3f31ed4f..1f8083aca 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -945,11 +945,11 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const if (this->instances.empty()) throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances"); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d inst_matrix = this->instances.front()->get_transformation().get_matrix_no_offset(); #else const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (const ModelVolume *v : this->volumes) if (v->is_model_part()) m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); @@ -961,14 +961,14 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_translate) const { BoundingBoxf3 bb; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Transform3d inst_matrix = dont_translate ? +#if ENABLE_WORLD_COORDINATE + const Transform3d inst_matrix = dont_translate ? this->instances[instance_idx]->get_transformation().get_matrix_no_offset() : this->instances[instance_idx]->get_transformation().get_matrix(); #else const Transform3d& inst_matrix = this->instances[instance_idx]->get_transformation().get_matrix(dont_translate); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (ModelVolume *v : this->volumes) { if (v->is_model_part()) bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); @@ -1379,11 +1379,11 @@ void ModelObject::split(ModelObjectPtrs* new_objects) ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh)); for (ModelInstance* model_instance : new_object->instances) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d shift = model_instance->get_transformation().get_matrix_no_offset() * new_vol->get_offset(); #else Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE model_instance->set_offset(model_instance->get_offset() + shift); } @@ -1447,7 +1447,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) // Adjust the meshes. // Transformation to be applied to the meshes. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Geometry::Transformation reference_trafo_mod = reference_trafo; reference_trafo_mod.reset_offset(); if (uniform_scaling) @@ -1457,7 +1457,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0); #else Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); for (ModelVolume *model_volume : this->volumes) { const Geometry::Transformation volume_trafo = model_volume->get_transformation(); @@ -1467,7 +1467,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().z()) < EPSILON; double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.; // Transform the mesh. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Geometry::Transformation volume_trafo_mod = volume_trafo; volume_trafo_mod.reset_offset(); if (volume_uniform_scaling) @@ -1477,7 +1477,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) Eigen::Matrix3d volume_trafo_3x3 = volume_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0); #else Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Following method creates a new shared_ptr model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); // Reset the rotation, scaling and mirroring. @@ -1524,11 +1524,11 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const double min_z = DBL_MAX; const ModelInstance* inst = instances[instance_idx]; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Transform3d& mi = inst->get_matrix_no_offset(); +#if ENABLE_WORLD_COORDINATE + const Transform3d mi = inst->get_matrix_no_offset(); #else const Transform3d& mi = inst->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1549,11 +1549,11 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const double max_z = -DBL_MAX; const ModelInstance* inst = instances[instance_idx]; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Transform3d& mi = inst->get_matrix_no_offset(); +#if ENABLE_WORLD_COORDINATE + const Transform3d mi = inst->get_matrix_no_offset(); #else const Transform3d& mi = inst->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1979,22 +1979,22 @@ void ModelVolume::convert_from_meters() void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix()); #else mesh->transform(get_matrix(dont_translate)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const { // Rotate around mesh origin. TriangleMesh copy(mesh); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE copy.transform(get_transformation().get_rotation_matrix()); #else copy.transform(get_matrix(true, false, true, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 bbox = copy.bounding_box(); if (!empty(bbox)) { @@ -2019,20 +2019,20 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mes BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE return bbox.transformed(dont_translate ? get_matrix_no_offset() : get_matrix()); #else return bbox.transformed(get_matrix(dont_translate)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE return dont_translate ? get_matrix_no_offset() * v : get_matrix() * v; #else return get_matrix(dont_translate) * v; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } void ModelInstance::transform_polygon(Polygon* polygon) const diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 016689a46..828358419 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -691,27 +691,26 @@ public: const Geometry::Transformation& get_transformation() const { return m_transformation; } void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_transformation(const Transform3d& trafo) { m_transformation.set_matrix(trafo); } -#else - void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d get_offset() const { return m_transformation.get_offset(); } #else + void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } + const Vec3d& get_offset() const { return m_transformation.get_offset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE + double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_rotation() const { return m_transformation.get_rotation(); } #else const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } @@ -723,11 +722,11 @@ public: void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_mirror() const { return m_transformation.get_mirror(); } #else const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } bool is_left_handed() const { return m_transformation.is_left_handed(); } @@ -736,12 +735,12 @@ public: void convert_from_imperial_units(); void convert_from_meters(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d& get_matrix() const { return m_transformation.get_matrix(); } Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); } #else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void set_new_unique_id() { ObjectBase::set_new_unique_id(); @@ -944,41 +943,41 @@ public: const Geometry::Transformation& get_transformation() const { return m_transformation; } void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_offset() const { return m_transformation.get_offset(); } #else const Vec3d& get_offset() const { return m_transformation.get_offset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_rotation() const { return m_transformation.get_rotation(); } #else const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } #else const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_mirror() const { return m_transformation.get_mirror(); } #else const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } bool is_left_handed() const { return m_transformation.is_left_handed(); } @@ -996,12 +995,12 @@ public: // To be called on an external polygon. It does not translate the polygon, only rotates and scales. void transform_polygon(Polygon* polygon) const; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d& get_matrix() const { return m_transformation.get_matrix(); } Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); } #else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 4fc76800c..ec071673b 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -545,10 +545,10 @@ namespace cereal { template void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE template void load(Archive& archive, Slic3r::Transform3d& m) { archive.loadBinary((char*)m.data(), sizeof(double) * 16); } template void save(Archive& archive, const Slic3r::Transform3d& m) { archive.saveBinary((char*)m.data(), sizeof(double) * 16); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } // To be able to use Vec<> and Mat<> in range based for loops: diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index b88ae9e50..4c085728c 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -790,13 +790,13 @@ void update_volume_bboxes( if (it != volumes_old.end() && it->volume_id == model_volume->id()) layer_range.volumes.emplace_back(*it); } else -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE layer_range.volumes.push_back({ model_volume->id(), transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), offset) }); #else layer_range.volumes.push_back({ model_volume->id(), transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) }); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } } else { std::vector> volumes_old; @@ -828,11 +828,11 @@ void update_volume_bboxes( layer_range.volumes.emplace_back(*it); } } else { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), ranges, bboxes, offset); #else transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) if (auto &bbox = bboxes[&layer_range - layer_ranges.data()]; bbox.second) layer_range.volumes.push_back({ model_volume->id(), bbox.first }); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 4f0a04df1..302c62879 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -71,8 +71,6 @@ #define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) -// Enable implementation of Geometry::Transformation using matrices only -#define ENABLE_TRANSFORMATIONS_BY_MATRICES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 9fb5a6965..4ef095223 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -432,23 +432,23 @@ public: const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_instance_transformation(const Transform3d& transform) { m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } Vec3d get_instance_offset() const { return m_instance_transformation.get_offset(); } #else const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); } void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_instance_rotation() const { return m_instance_transformation.get_rotation(); } #else const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); } void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } @@ -460,11 +460,11 @@ public: void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_instance_mirror() const { return m_instance_transformation.get_mirror(); } #else const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); } void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } @@ -472,43 +472,43 @@ public: const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_volume_transformation(const Transform3d& transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } Vec3d get_volume_offset() const { return m_volume_transformation.get_offset(); } #else const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); } void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_volume_rotation() const { return m_volume_transformation.get_rotation(); } #else const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); } void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } #else const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); } void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_volume_mirror() const { return m_volume_transformation.get_mirror(); } #else const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); } void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 959611bea..026734711 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1101,9 +1101,9 @@ wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE wxDEFINE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); @@ -2917,13 +2917,13 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) else displacement = multiplier * direction; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE TransformationType trafo_type; trafo_type.set_relative(); m_selection.translate(displacement, trafo_type); #else m_selection.translate(displacement); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE m_dirty = true; } ); @@ -3588,13 +3588,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE TransformationType trafo_type; trafo_type.set_relative(); m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type); #else m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) update_sequential_clearance(); wxGetApp().obj_manipul()->set_dirty(); @@ -3840,17 +3840,17 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); #else model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE else if (selection_mode == Selection::Volume) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); #else model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE object_moved = true; model_object->invalidate_bounding_box(); @@ -3948,20 +3948,20 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); #else model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } else if (selection_mode == Selection::Volume) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); #else model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } model_object->invalidate_bounding_box(); } @@ -4024,22 +4024,22 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); #else model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } else if (selection_mode == Selection::Volume) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); #else model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } model_object->invalidate_bounding_box(); } @@ -4101,17 +4101,17 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); #else model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE else if (selection_mode == Selection::Volume) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); #else model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE model_object->invalidate_bounding_box(); } @@ -4135,7 +4135,7 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) m_dirty = true; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) { if (m_model == nullptr) @@ -4171,7 +4171,7 @@ void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) m_dirty = true; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void GLCanvas3D::update_gizmos_on_off_state() { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9cf6501fc..18ad42ae1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -156,9 +156,9 @@ wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE wxDECLARE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); @@ -742,11 +742,11 @@ public: void update_volumes_colors_by_extruder(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE bool is_dragging() const { return m_gizmos.is_dragging() || (m_moving && !m_mouse.scene_position.isApprox(m_mouse.drag.start_position_3D)); } #else bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void render(); // printable_only == false -> render also non printable volumes as grayed @@ -814,9 +814,9 @@ public: void do_rotate(const std::string& snapshot_type); void do_scale(const std::string& snapshot_type); void do_mirror(const std::string& snapshot_type); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void do_reset_skew(const std::string& snapshot_type); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void update_gizmos_on_off_state(); void reset_all_gizmos() { m_gizmos.reset_all_states(); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a8c9819a6..bfedd8e1e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1532,11 +1532,11 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_first_volume(); const Geometry::Transformation inst_transform = v->get_instance_transformation(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d inv_inst_transform = inst_transform.get_matrix_no_offset().inverse(); #else const Transform3d inv_inst_transform = inst_transform.get_matrix(true).inverse(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const Vec3d instance_offset = v->get_instance_offset(); for (size_t i = 0; i < input_files.size(); ++i) { @@ -1584,7 +1584,7 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo new_volume->source.mesh_offset = model.objects.front()->volumes.front()->source.mesh_offset; if (from_galery) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE // Transform the new modifier to be aligned with the print bed. new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); @@ -1592,7 +1592,7 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(inst_transform, mesh_bb)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Set the modifier position. // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. const Vec3d offset = Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - instance_offset; @@ -1661,7 +1661,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE // Transform the new modifier to be aligned with the print bed. new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); @@ -1669,18 +1669,18 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Set the modifier position. auto offset = (type_name == "Slab") ? // Slab: Lift to print bed Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) : // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - v->get_instance_offset(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE new_volume->set_offset(v->get_instance_transformation().get_matrix_no_offset().inverse() * offset); #else new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const wxString name = _L("Generic") + "-" + _(type_name); new_volume->name = into_u8(name); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8f5445f54..24ae01389 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -354,11 +354,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const GLVolume* volume = selection.get_first_volume(); const double min_z = get_volume_min_z(*volume); if (!is_world_coordinates()) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); -#else - const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); @@ -385,11 +381,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const double min_z = selection.get_scaled_instance_bounding_box().min.z(); if (!is_world_coordinates()) { const GLVolume* volume = selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); -#else - const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); @@ -460,7 +452,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); m_reset_scale_button->SetToolTip(_L("Reset scale")); m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); if (selection.is_single_volume_or_modifier()) @@ -481,8 +473,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - }); +#endif // ENABLE_WORLD_COORDINATE + }); editors_grid_sizer->Add(m_reset_scale_button); for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) @@ -492,7 +484,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE m_skew_label = new wxStaticText(parent, wxID_ANY, _L("Skew")); m_main_grid_sizer->Add(m_skew_label, 1, wxEXPAND); @@ -509,7 +501,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : } }); m_main_grid_sizer->Add(m_reset_skew_button); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE m_check_inch = new wxCheckBox(parent, wxID_ANY, _L("Inches")); m_check_inch->SetFont(wxGetApp().normal_font()); @@ -651,21 +643,15 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const GLVolume* volume = selection.get_first_volume(); #if !ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); -#endif // !ENABLE_WORLD_COORDINATE -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. -#if ENABLE_WORLD_COORDINATE - if (is_world_coordinates() && !m_uniform_scale && -#else if (m_world_coordinates && ! m_uniform_scale && -#endif // ENABLE_WORLD_COORDINATE ! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling. m_uniform_scale = true; m_lock_bnt->SetLock(true); } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { @@ -674,32 +660,19 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (m_world_coordinates) { #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); -#if ENABLE_WORLD_COORDINATE && !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#else m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else { #if ENABLE_WORLD_COORDINATE m_new_move_label_string = L("Translate"); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotate_label_string = L("Rotate"); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_position = Vec3d::Zero(); -#endif // ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); #else m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); m_new_scale = volume->get_instance_scaling_factor() * 100.0; } @@ -731,28 +704,15 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = offset; m_new_rotate_label_string = L("Rotate"); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale_label_string = L("Scale"); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0; -#else - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotate_label_string = L("Rotate"); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_position = Vec3d::Zero(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); -#else - m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()); } @@ -760,19 +720,10 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotate_label_string = L("Rotate"); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0; -#else - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; @@ -843,29 +794,20 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - Selection::EUniformScaleRequiredReason reason; - if (selection.requires_uniform_scale(&reason)) { - wxString tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); - m_lock_bnt->SetToolTip(tooltip); -#else +#if !ENABLE_WORLD_COORDINATE if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); m_lock_bnt->disable(); -#endif // ENABLE_WORLD_COORDINATE } else { -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE m_lock_bnt->SetLock(m_uniform_scale); m_lock_bnt->SetToolTip(wxEmptyString); m_lock_bnt->enable(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - #if !ENABLE_WORLD_COORDINATE + } + { int new_selection = m_world_coordinates ? 0 : 1; if (m_word_local_combo->GetSelection() != new_selection) @@ -898,35 +840,18 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_rotation = false; bool show_scale = false; bool show_drop_to_bed = false; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - bool show_skew = false; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool show_skew = false; + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { -#else - if ((m_coordinates_type == ECoordinatesType::World && selection.is_single_full_instance()) || - (m_coordinates_type == ECoordinatesType::Instance && selection.is_single_volume_or_modifier())) { -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : get_volume_min_z(*selection.get_first_volume()); show_drop_to_bed = std::abs(min_z) > EPSILON; -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - } - - if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume* volume = selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d rotation = Transform3d::Identity(); Transform3d scale = Transform3d::Identity(); Geometry::Transformation skew; -#else - Vec3d rotation = Vec3d::Zero(); - Vec3d scale = Vec3d::Ones(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* volume = selection.get_first_volume(); @@ -936,7 +861,7 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance()) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& trafo = volume->get_instance_transformation(); rotation = trafo.get_rotation_matrix(); scale = trafo.get_scaling_factor_matrix(); @@ -951,13 +876,11 @@ void ObjectManipulation::update_reset_buttons_visibility() #else rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if !ENABLE_WORLD_COORDINATE min_z = selection.get_scaled_instance_bounding_box().min.z(); -#endif // !ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE } else { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& trafo = volume->get_volume_transformation(); rotation = trafo.get_rotation_matrix(); scale = trafo.get_scaling_factor_matrix(); @@ -967,29 +890,25 @@ void ObjectManipulation::update_reset_buttons_visibility() #else rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if !ENABLE_WORLD_COORDINATE min_z = get_volume_min_z(*volume); -#endif // !ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE show_rotation = !rotation.isApprox(Transform3d::Identity()); show_scale = !scale.isApprox(Transform3d::Identity()); show_skew = skew.has_skew(); #else show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if !ENABLE_WORLD_COORDINATE show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; -#endif // !ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed, show_skew] { #else wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // There is a case (under OSX), when this function is called after the Manipulation panel is hidden // So, let check if Manipulation panel is still shown for this moment if (!this->IsShown()) @@ -997,10 +916,10 @@ void ObjectManipulation::update_reset_buttons_visibility() m_reset_rotation_button->Show(show_rotation); m_reset_scale_button->Show(show_scale); m_drop_to_bed_button->Show(show_drop_to_bed); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE m_reset_skew_button->Show(show_skew); m_skew_label->Show(show_skew); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time Sidebar& panel = wxGetApp().sidebar(); @@ -1131,7 +1050,6 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.setup_cache(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES TransformationType trafo_type; trafo_type.set_relative(); switch (get_coordinates_type()) @@ -1141,9 +1059,6 @@ void ObjectManipulation::change_position_value(int axis, double value) default: { break; } } selection.translate(position - m_cache.position, trafo_type); -#else - selection.translate(position - m_cache.position, get_coordinates_type()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else selection.translate(position - m_cache.position, selection.requires_local_axes()); #endif // ENABLE_WORLD_COORDINATE @@ -1166,21 +1081,13 @@ void ObjectManipulation::change_rotation_value(int axis, double value) Selection& selection = canvas->get_selection(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES TransformationType transformation_type; transformation_type.set_relative(); -#else - TransformationType transformation_type(TransformationType::World_Relative_Joint); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (selection.is_single_full_instance()) transformation_type.set_independent(); - if (is_local_coordinates()) { + if (is_local_coordinates()) transformation_type.set_local(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - transformation_type.set_absolute(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - } if (is_instance_coordinates()) transformation_type.set_instance(); @@ -1286,28 +1193,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES if (!selection.is_single_full_instance() && !selection.is_single_volume_or_modifier()) transformation_type.set_relative(); const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; -#else - if (!is_local_coordinates()) - transformation_type.set_relative(); - - bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); - Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; - - if (!uniform_scale && is_world_coordinates()) { - if (selection.is_single_full_instance()) - scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); - else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); - scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); - } - } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { @@ -1336,28 +1225,7 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - if (!is_local_coordinates()) - transformation_type.set_relative(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; -#else - bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); - Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; - - if (!uniform_scale && is_world_coordinates()) { - if (selection.is_single_full_instance()) - scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); - else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); - scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); - } - } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - + const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; selection.setup_cache(); selection.scale(scaling_factor, transformation_type); wxGetApp().plater()->canvas3D()->do_scale(L("Set Size")); @@ -1400,30 +1268,16 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE - if (!use_uniform_scale) { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - int res = selection.bake_transform_if_needed(); - if (res == -1) { - // Enforce uniform scaling. - m_lock_bnt->SetLock(true); - return; - } - else if (res == 0) -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - // Recalculate cached values at this panel, refresh the screen. - this->UpdateAndShow(true); - } + if (!use_uniform_scale) + // Recalculate cached values at this panel, refresh the screen. + this->UpdateAndShow(true); m_uniform_scale = use_uniform_scale; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES set_dirty(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); if (selection.is_single_full_instance() && m_world_coordinates && !use_uniform_scale) { // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one @@ -1493,9 +1347,9 @@ void ObjectManipulation::msw_rescale() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE m_reset_skew_button->msw_rescale(); -#endif /// ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif /// ENABLE_WORLD_COORDINATE m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); @@ -1535,9 +1389,9 @@ void ObjectManipulation::sys_color_changed() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE m_reset_skew_button->msw_rescale(); -#endif /// ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index e25e05f60..cfa43b43a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -122,9 +122,9 @@ private: // Non-owning pointers to the reset buttons, so we can hide and show them. ScalableButton* m_reset_scale_button{ nullptr }; ScalableButton* m_reset_rotation_button{ nullptr }; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE ScalableButton* m_reset_skew_button{ nullptr }; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE ScalableButton* m_drop_to_bed_button{ nullptr }; wxCheckBox* m_check_inch {nullptr}; @@ -179,9 +179,9 @@ private: wxFlexGridSizer* m_main_grid_sizer; wxFlexGridSizer* m_labels_grid_sizer; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE wxStaticText* m_skew_label{ nullptr }; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // sizers, used for msw_rescale wxBoxSizer* m_word_local_combo_sizer; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index a52c85d67..49e97ee1f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -341,11 +341,11 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) ++mesh_id; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d trafo_matrix = mi->get_matrix_no_offset() * mv->get_matrix_no_offset(); #else const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 9bac7f293..f854edb81 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -228,11 +228,11 @@ void GLGizmoFlatten::update_planes() } ch = ch.convex_hull_3d(); m_planes.clear(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d inst_matrix = mo->instances.front()->get_matrix_no_offset(); #else const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Following constants are used for discarding too small polygons. const float minimal_area = 5.f; // in square mm (world coordinates) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 8ef23d538..7272a5ef7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -121,11 +121,11 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation(); #if ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_scaling_factor_matrix().inverse(); #else const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const Transform3d instance_matrix = Geometry::translation_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 04ccfb4c8..79aed06b9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -98,19 +98,11 @@ void GLGizmoMove3D::on_start_dragging() m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; -#else - m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; -#else - m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } m_starting_box_center = m_center; m_starting_box_bottom_center = m_center; @@ -141,7 +133,6 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) Selection &selection = m_parent.get_selection(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES TransformationType trafo_type; trafo_type.set_relative(); switch (wxGetApp().obj_manipul()->get_coordinates_type()) @@ -151,9 +142,6 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) default: { break; } } selection.translate(m_displacement, trafo_type); -#else - selection.translate(m_displacement, wxGetApp().obj_manipul()->get_coordinates_type()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else selection.translate(m_displacement); #endif // ENABLE_WORLD_COORDINATE @@ -529,17 +517,9 @@ Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const Transform3d ret = Geometry::assemble_transform(m_center); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); -#else - Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); -#else - orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES ret = ret * orient_matrix; } return ret; @@ -569,12 +549,8 @@ void GLGizmoMove3D::calc_selection_box_and_center() } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box = v.transformed_convex_hull_bounding_box( v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); -#else - m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_center = v.world_matrix() * m_bounding_box.center(); } else { @@ -584,14 +560,9 @@ void GLGizmoMove3D::calc_selection_box_and_center() const GLVolume& v = *selection.get_volume(id); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation(); m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix()); m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center(); -#else - m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); - m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index c7731f1ff..4e03abb33 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -291,11 +291,11 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const return; #endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_scaling_factor_matrix().inverse(); #else const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); #if !ENABLE_GL_SHADERS_ATTRIBUTES @@ -512,11 +512,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Selection &selection = m_parent.get_selection(); const ModelObject *mo = m_c->selection_info()->model_object(); const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); #else const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE 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); @@ -555,11 +555,11 @@ 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 instance_trafo = mi->get_transformation().get_matrix(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset(); #else const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Precalculate transformations of individual meshes. std::vector trafo_matrices; @@ -567,11 +567,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous for (const ModelVolume *mv : mo->volumes) if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset()); #else trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } std::vector> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices); @@ -653,11 +653,11 @@ 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 instance_trafo = mi->get_transformation().get_matrix(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset(); #else const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Precalculate transformations of individual meshes. std::vector trafo_matrices; @@ -665,11 +665,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous for (const ModelVolume *mv : mo->volumes) if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset()); #else trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } // Now "click" into all the prepared points and spill paint around them. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b72c6761f..3a4f77837 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,27 +287,19 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES ECoordinatesType coordinates_type; if (selection.is_wipe_tower()) coordinates_type = ECoordinatesType::Local; else coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); -#else - const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (coordinates_type == ECoordinatesType::World) { m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box = v.transformed_convex_hull_bounding_box( v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); -#else - m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_center = v.world_matrix() * m_bounding_box.center(); } else { @@ -317,14 +309,9 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) const GLVolume& v = *selection.get_volume(id); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation(); m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix()); m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center(); -#else - m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); - m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } m_radius = Offset + m_bounding_box.radius(); @@ -335,25 +322,13 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) if (coordinates_type == ECoordinatesType::World) m_orient_matrix = Transform3d::Identity(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES else if (coordinates_type == ECoordinatesType::Local && (selection.is_wipe_tower() || selection.is_single_volume_or_modifier())) { -#else - else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); -#else - m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix(); -#else - m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } #endif // ENABLE_WORLD_COORDINATE @@ -762,20 +737,20 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const { case X: { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE ret = Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()); #else ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE break; } case Y: { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE ret = Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY()); #else ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE break; } default: @@ -889,25 +864,22 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) // Apply new temporary rotations #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES if (m_parent.get_selection().is_wipe_tower()) transformation_type = TransformationType::Instance_Relative_Joint; else { -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES switch (wxGetApp().obj_manipul()->get_coordinates_type()) { default: - case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } + case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; } - case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } + case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else TransformationType transformation_type(TransformationType::World_Relative_Joint); #endif // ENABLE_WORLD_COORDINATE - if (mouse_event.AltDown()) transformation_type.set_independent(); + if (mouse_event.AltDown()) + transformation_type.set_independent(); m_parent.get_selection().rotate(get_rotation(), transformation_type); } return use_grabbers(mouse_event); @@ -915,26 +887,26 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) void GLGizmoRotate3D::data_changed() { if (m_parent.get_selection().is_wipe_tower()) { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE const DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; const float wipe_tower_rotation_angle = dynamic_cast( config.option("wipe_tower_rotation_angle"))->value; set_rotation(Vec3d(0., 0., (M_PI / 180.) * wipe_tower_rotation_angle)); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE m_gizmos[0].disable_grabber(); m_gizmos[1].disable_grabber(); } else { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE set_rotation(Vec3d::Zero()); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE m_gizmos[0].enable_grabber(); m_gizmos[1].enable_grabber(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE set_rotation(Vec3d::Zero()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } bool GLGizmoRotate3D::on_init() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 1d87d772a..f287a69f7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -41,7 +41,7 @@ GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filen std::string GLGizmoScale3D::get_tooltip() const { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Vec3d scale = 100.0 * m_scale; #else const Selection& selection = m_parent.get_selection(); @@ -49,13 +49,9 @@ std::string GLGizmoScale3D::get_tooltip() const Vec3d scale = 100.0 * Vec3d::Ones(); if (selection.is_single_full_instance()) scale = 100.0 * selection.get_first_volume()->get_instance_scaling_factor(); -#if ENABLE_WORLD_COORDINATE - else if (selection.is_single_volume_or_modifier()) -#else else if (selection.is_single_modifier() || selection.is_single_volume()) -#endif // ENABLE_WORLD_COORDINATE scale = 100.0 * selection.get_first_volume()->get_volume_scaling_factor(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) return "X: " + format(scale.x(), 4) + "%"; @@ -79,54 +75,29 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) { if (mouse_event.Dragging()) { if (m_dragging) { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - int res = 1; - if (m_scale.x() != m_scale.y() || m_scale.x() != m_scale.z()) - res = m_parent.get_selection().bake_transform_if_needed(); - - if (res != 1) { - do_stop_dragging(true); - return true; - } - else { -#endif // ENABLE_WORLD_COORDINATE -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Apply new temporary scale factors #if ENABLE_WORLD_COORDINATE - TransformationType transformation_type; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - if (wxGetApp().obj_manipul()->is_local_coordinates()) - transformation_type.set_local(); - else if (wxGetApp().obj_manipul()->is_instance_coordinates()) - transformation_type.set_instance(); + TransformationType transformation_type; + if (wxGetApp().obj_manipul()->is_local_coordinates()) + transformation_type.set_local(); + else if (wxGetApp().obj_manipul()->is_instance_coordinates()) + transformation_type.set_instance(); - transformation_type.set_relative(); + transformation_type.set_relative(); #else - if (!wxGetApp().obj_manipul()->is_world_coordinates()) - transformation_type.set_local(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#else - TransformationType transformation_type(TransformationType::Local_Absolute_Joint); + TransformationType transformation_type(TransformationType::Local_Absolute_Joint); #endif // ENABLE_WORLD_COORDINATE - if (mouse_event.AltDown()) transformation_type.set_independent(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - m_parent.get_selection().scale_and_translate(m_scale, m_offset, transformation_type); -#else - Selection& selection = m_parent.get_selection(); - selection.scale(m_scale, transformation_type); + if (mouse_event.AltDown()) + transformation_type.set_independent(); + #if ENABLE_WORLD_COORDINATE - if (mouse_event.CmdDown()) selection.translate(m_offset, wxGetApp().obj_manipul()->get_coordinates_type()); + m_parent.get_selection().scale_and_translate(m_scale, m_offset, transformation_type); #else - if (mouse_event.CmdDown()) selection.translate(m_offset, true); + Selection& selection = m_parent.get_selection(); + selection.scale(m_scale, transformation_type); + if (mouse_event.CmdDown()) selection.translate(m_offset, true); #endif // ENABLE_WORLD_COORDINATE -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - } -#endif // ENABLE_WORLD_COORDINATE -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES } } return use_grabbers(mouse_event); @@ -134,41 +105,29 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) void GLGizmoScale3D::data_changed() { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - const Selection &selection = m_parent.get_selection(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE -#else - bool enable_scale_xyz = selection.is_single_full_instance() || - selection.is_single_volume() || - selection.is_single_modifier(); -#endif // ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES set_scale(Vec3d::Ones()); #else -#if ENABLE_WORLD_COORDINATE - if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { -#else + const Selection& selection = m_parent.get_selection(); + bool enable_scale_xyz = selection.is_single_full_instance() || + selection.is_single_volume() || + selection.is_single_modifier(); + for (unsigned int i = 0; i < 6; ++i) m_grabbers[i].enabled = enable_scale_xyz; if (enable_scale_xyz) { -#endif // ENABLE_WORLD_COORDINATE // all volumes in the selection belongs to the same instance, any of // them contains the needed data, so we take the first const GLVolume* volume = selection.get_first_volume(); if (selection.is_single_full_instance()) set_scale(volume->get_instance_scaling_factor()); -#if ENABLE_WORLD_COORDINATE - else if (selection.is_single_volume_or_modifier()) -#else else if (selection.is_single_volume() || selection.is_single_modifier()) -#endif // ENABLE_WORLD_COORDINATE set_scale(volume->get_volume_scaling_factor()); } else set_scale(Vec3d::Ones()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } bool GLGizmoScale3D::on_init() @@ -279,24 +238,15 @@ void GLGizmoScale3D::on_render() } #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix()); -#else - m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #endif // ENABLE_WORLD_COORDINATE // gets transform from first selected volume const GLVolume& v = *selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor(); m_grabbers_transform = inst_trafo * Geometry::translation_transform(m_bounding_box.center()); m_center = inst_trafo * m_bounding_box.center(); -#else - m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); - m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_instance_center = v.get_instance_offset(); } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { @@ -313,14 +263,9 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE const GLVolume& v = *selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box.merge(v.transformed_convex_hull_bounding_box( v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_matrix_no_offset())); Geometry::Transformation trafo(v.get_instance_transformation().get_rotation_matrix()); -#else - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true))); - Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); @@ -328,14 +273,9 @@ void GLGizmoScale3D::on_render() } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box.merge(v.transformed_convex_hull_bounding_box( v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix())); Geometry::Transformation trafo(v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix()); -#else - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); - Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); @@ -736,7 +676,7 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int } #endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { double ratio = calc_ratio(data); @@ -790,76 +730,16 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #else void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { -#if ENABLE_WORLD_COORDINATE - double ratio = calc_ratio(data); - if (ratio > 0.0) { - Vec3d curr_scale = m_scale; - Vec3d starting_scale = m_starting.scale; - const Selection& selection = m_parent.get_selection(); - const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); - if (coordinates_type == ECoordinatesType::World) { - if (selection.is_single_full_instance()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()); - curr_scale = (m * curr_scale).cwiseAbs(); - starting_scale = (m * starting_scale).cwiseAbs(); - } - else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()); - const Transform3d m = mi * mv; - curr_scale = (m * curr_scale).cwiseAbs(); - starting_scale = (m * starting_scale).cwiseAbs(); - } - } - - curr_scale(axis) = starting_scale(axis) * ratio; - - if (coordinates_type == ECoordinatesType::World) { - if (selection.is_single_full_instance()) - m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); - else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); - m_scale = (mv * mi * curr_scale).cwiseAbs(); - } - else - m_scale = curr_scale; - } - else - m_scale = curr_scale; -#else const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; -#endif // ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { -#if ENABLE_WORLD_COORDINATE - double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); -#else double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); -#endif // ENABLE_WORLD_COORDINATE if (m_hover_id == 2 * axis) local_offset *= -1.0; -#if ENABLE_WORLD_COORDINATE - Vec3d center_offset = m_starting.instance_center - m_starting.center; - if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { - const Transform3d m = Geometry::rotation_transform(selection.get_first_volume()->get_instance_rotation()).inverse(); - center_offset = m * center_offset; - } - - local_offset += (ratio - 1.0) * center_offset(axis); - - switch (axis) - { - case X: { m_offset = local_offset * Vec3d::UnitX(); break; } - case Y: { m_offset = local_offset * Vec3d::UnitY(); break; } - case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } - default: { m_offset = Vec3d::Zero(); break; } - } -#else Vec3d local_offset_vec; switch (axis) { @@ -870,15 +750,14 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) } m_offset = m_offsets_transform * local_offset_vec; -#endif // ENABLE_WORLD_COORDINATE } else m_offset = Vec3d::Zero(); } } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void GLGizmoScale3D::do_scale_uniform(const UpdateData & data) { const double ratio = calc_ratio(data); @@ -926,32 +805,10 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale = m_starting.scale * ratio; - -#if ENABLE_WORLD_COORDINATE - if (m_starting.ctrl_down) { - m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); - - if (m_hover_id == 6 || m_hover_id == 9) - m_offset.x() *= -1.0; - if (m_hover_id == 6 || m_hover_id == 7) - m_offset.y() *= -1.0; - - const Selection& selection = m_parent.get_selection(); - Vec3d center_offset = m_starting.instance_center - m_starting.center; - - if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { - const Transform3d m = Geometry::rotation_transform(selection.get_first_volume()->get_instance_rotation()).inverse(); - center_offset = m * center_offset; - } - - m_offset += (ratio - 1.0) * center_offset; - } - else -#endif // ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); } } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { @@ -995,17 +852,9 @@ Transform3d GLGizmoScale3D::local_transform(const Selection& selection) const Transform3d ret = Geometry::assemble_transform(m_center); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); -#else - Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); -#else - orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES ret = ret * orient_matrix; } return ret; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 754b588b4..b2a9b7a23 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -67,11 +67,11 @@ public: void set_snap_step(double step) { m_snap_step = step; } const Vec3d& get_scale() const { return m_scale; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; m_offset = Vec3d::Zero(); } #else void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED const Vec3d& get_starting_scale() const { return m_starting.scale; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 25b918e4b..987fd325f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -148,11 +148,11 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const GLVolume* vol = selection.get_first_volume(); Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); #else const Transform3d& instance_scaling_matrix_inverse = transformation.get_matrix(true, true, false, true).inverse(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * transformation.get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 777a4d7ba..7782b8d75 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -112,11 +112,11 @@ void MeshClipper::render_cut() void MeshClipper::recalculate_triangles() { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3f instance_matrix_no_translation_no_scaling = m_trafo.get_rotation_matrix().cast(); #else const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Calculate clipping plane normal in mesh coordinates. const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); const Vec3d up = up_noscale.cast().cwiseProduct(m_trafo.get_scaling_factor()); @@ -307,11 +307,11 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo { std::vector out; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_matrix_no_translation_no_scaling = trafo.get_rotation_matrix(); #else const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Vec3d direction_to_camera = -camera.get_dir_forward(); Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval(); direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor()); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c9cc7fc22..d23a3dd13 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2085,9 +2085,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE view3D_canvas->Bind(EVT_GLCANVAS_RESET_SKEW, [this](SimpleEvent&) { update(); }); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event& evt) { this->sidebar->enable_buttons(evt.data); }); view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); @@ -3495,11 +3495,11 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); new_volume->set_transformation(old_volume->get_transformation()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); #else new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) new_volume->convert_from_imperial_units(); @@ -3854,7 +3854,7 @@ void Plater::priv::reload_from_disk() new_volume->config.apply(old_volume->config); new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE new_volume->set_transformation(Geometry::translation_transform(old_volume->source.transform.get_offset()) * old_volume->get_transformation().get_matrix_no_offset() * old_volume->source.transform.get_matrix_no_offset()); new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); @@ -3863,7 +3863,7 @@ void Plater::priv::reload_from_disk() old_volume->get_transformation().get_matrix(true) * old_volume->source.transform.get_matrix(true)); new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE new_volume->source.object_idx = old_volume->source.object_idx; new_volume->source.volume_idx = old_volume->source.volume_idx; assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 02edd9c31..61ffd4b19 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -41,18 +41,18 @@ Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transform , scaling_factor(transform.get_scaling_factor()) , mirror(transform.get_mirror()) , full_matrix(transform.get_matrix()) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE , transform(transform) , rotation_matrix(transform.get_rotation_matrix()) , scale_matrix(transform.get_scaling_factor_matrix()) , mirror_matrix(transform.get_mirror_matrix()) -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); - scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); +#endif // !ENABLE_WORLD_COORDINATE } Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) @@ -599,76 +599,15 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE -bool Selection::requires_uniform_scale(EUniformScaleRequiredReason* reason) const -#else +#if !ENABLE_WORLD_COORDINATE bool Selection::requires_uniform_scale() const -#endif // ENABLE_WORLD_COORDINATE { -#if ENABLE_WORLD_COORDINATE - if (is_empty()) - return false; - - ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); - if (is_single_volume_or_modifier()) { - if (coord_type == ECoordinatesType::World) { - if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_first_volume()->world_matrix()).get_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_World; - return true; - } - } - else if (coord_type == ECoordinatesType::Instance) { - if (!Geometry::is_rotation_ninety_degrees(get_first_volume()->get_volume_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; - return true; - } - } - return false; - } - else if (is_single_full_instance()) { - if (coord_type == ECoordinatesType::World) { - if (!Geometry::is_rotation_ninety_degrees(get_first_volume()->get_instance_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::InstanceNotAxisAligned_World; - return true; - } - else { - for (unsigned int i : m_list) { - if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; - return true; - } - } - } - } - else { - for (unsigned int i : m_list) { - if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; - return true; - } - } - } - return false; - } - - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::MultipleSelection; - - return true; -#else if (is_single_full_instance() || is_single_modifier() || is_single_volume()) return false; return true; -#endif // ENABLE_WORLD_COORDINATE } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE int Selection::get_object_idx() const { @@ -723,11 +662,11 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const const GLVolume& volume = *(*m_volumes)[i]; if (volume.is_modifier) continue; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix(); #else Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE trafo.translation().z() += volume.get_sla_shift_z(); (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); } @@ -757,7 +696,7 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const return *m_scaled_instance_bounding_box; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const BoundingBoxf3& Selection::get_full_unscaled_instance_bounding_box() const { assert(is_single_full_instance()); @@ -814,7 +753,7 @@ const BoundingBoxf3& Selection::get_full_unscaled_instance_local_bounding_box() } return *m_full_unscaled_instance_local_bounding_box; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Selection::setup_cache() { @@ -824,7 +763,7 @@ void Selection::setup_cache() set_caches(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::translate(const Vec3d& displacement, TransformationType transformation_type) { if (!m_valid) @@ -865,11 +804,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } #else -#if ENABLE_WORLD_COORDINATE -void Selection::translate(const Vec3d& displacement, ECoordinatesType type) -#else void Selection::translate(const Vec3d& displacement, bool local) -#endif // ENABLE_WORLD_COORDINATE { if (!m_valid) return; @@ -879,45 +814,16 @@ void Selection::translate(const Vec3d& displacement, bool local) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (m_mode == Volume || v.is_wipe_tower) { -#if ENABLE_WORLD_COORDINATE - if (type == ECoordinatesType::Instance) -#else if (local) -#endif // ENABLE_WORLD_COORDINATE v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); -#if ENABLE_WORLD_COORDINATE - else if (type == ECoordinatesType::Local) { - const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d local_displacement = volume_data.get_volume_rotation_matrix() * displacement; - v.set_volume_offset(volume_data.get_volume_position() + local_displacement); - } -#endif // ENABLE_WORLD_COORDINATE else { -#if ENABLE_WORLD_COORDINATE - const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d local_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix() * volume_data.get_instance_mirror_matrix()).inverse() * displacement; - v.set_volume_offset(volume_data.get_volume_position() + local_displacement); -#else const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); -#endif // ENABLE_WORLD_COORDINATE } } else if (m_mode == Instance) { -#if ENABLE_WORLD_COORDINATE - if (is_from_fully_selected_instance(i)) { - if (type == ECoordinatesType::Local) { - const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement; - v.set_instance_offset(volume_data.get_instance_position() + world_displacement); - } - else - v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); - } -#else if (is_from_fully_selected_instance(i)) v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); -#endif // ENABLE_WORLD_COORDINATE else { const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); @@ -937,10 +843,10 @@ void Selection::translate(const Vec3d& displacement, bool local) set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Rotate an object around one of the axes. Only one rotation component is expected to be changing. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) { if (!m_valid) @@ -1052,18 +958,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } else { // extracts rotations from the composed transformation -#if ENABLE_WORLD_COORDINATE - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - const Vec3d new_rotation = transformation_type.world() ? - Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()) : - transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m); - const Vec3d relative_instance_offset = m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center; - if (rot_axis_max == 2 && transformation_type.joint() && !relative_instance_offset.isApprox(Vec3d::Zero())) { - // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. - const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); - volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * relative_instance_offset); - } -#else const Vec3d new_rotation = transformation_type.world() ? Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); @@ -1072,9 +966,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); } -#endif // ENABLE_WORLD_COORDINATE - else if (!(m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center).isApprox(Vec3d::Zero())) - volume.set_instance_offset(m_cache.dragging_center + Geometry::assemble_transform(Vec3d::Zero(), new_rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); volume.set_instance_rotation(new_rotation); object_instance_first[volume.object_idx()] = i; @@ -1085,25 +976,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ GLVolume &v = *(*m_volumes)[i]; if (is_single_full_instance()) rotate_instance(v, i); -#if ENABLE_WORLD_COORDINATE - else if (is_single_volume_or_modifier()) { - if (transformation_type.local()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - const Vec3d new_rotation = transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m); - v.set_volume_rotation(new_rotation); - } - else if (transformation_type.instance()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - v.set_volume_rotation(Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix())); - } - else { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - m = m * m_cache.volumes_data[i].get_instance_rotation_matrix(); - m = m * m_cache.volumes_data[i].get_volume_rotation_matrix(); - m = m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * m; - v.set_volume_rotation(Geometry::extract_euler_angles(m)); - } -#else else if (is_single_volume() || is_single_modifier()) { if (transformation_type.independent()) v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); @@ -1112,7 +984,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); v.set_volume_rotation(new_rotation); } -#endif // ENABLE_WORLD_COORDINATE } else { if (m_mode == Instance) @@ -1133,21 +1004,8 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } #if !DISABLE_INSTANCES_SYNCH -#if ENABLE_WORLD_COORDINATE - if (m_mode == Instance) { - SyncRotationType synch; - if (transformation_type.world() && rot_axis_max == 2) - synch = SyncRotationType::NONE; - else if (transformation_type.local()) - synch = SyncRotationType::FULL; - else - synch = SyncRotationType::GENERAL; - synchronize_unselected_instances(synch); - } -#else if (m_mode == Instance) synchronize_unselected_instances((rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL); -#endif // ENABLE_WORLD_COORDINATE else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1166,7 +1024,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Selection::flattening_rotate(const Vec3d& normal) { @@ -1181,7 +1039,7 @@ void Selection::flattening_rotate(const Vec3d& normal) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; // Normal transformed from the object coordinate space to the world coordinate space. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& old_inst_trafo = v.get_instance_transformation(); const Vec3d tnormal = old_inst_trafo.get_matrix().matrix().block(0, 0, 3, 3).inverse().transpose() * normal; // Additional rotation to align tnormal with the down vector in the world coordinate space. @@ -1195,7 +1053,7 @@ void Selection::flattening_rotate(const Vec3d& normal) // Additional rotation to align tnormal with the down vector in the world coordinate space. auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ()); v.set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix())); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } #if !DISABLE_INSTANCES_SYNCH @@ -1208,7 +1066,7 @@ void Selection::flattening_rotate(const Vec3d& normal) this->set_bounding_boxes_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::scale(const Vec3d& scale, TransformationType transformation_type) { scale_and_translate(scale, Vec3d::Zero(), transformation_type); @@ -1233,9 +1091,6 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type v.set_instance_scaling_factor(new_scale); } else { -#if ENABLE_WORLD_COORDINATE - v.set_instance_scaling_factor(scale); -#else if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { // Non-uniform scaling. Transform the scaling factors into the local coordinate system. // This is only possible, if the instance rotation is mulitples of ninety degrees. @@ -1244,14 +1099,9 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type } else v.set_instance_scaling_factor(scale); -#endif // ENABLE_WORLD_COORDINATE } } -#if ENABLE_WORLD_COORDINATE - else if (is_single_volume_or_modifier()) -#else else if (is_single_volume() || is_single_modifier()) -#endif // ENABLE_WORLD_COORDINATE v.set_volume_scaling_factor(scale); else { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); @@ -1288,7 +1138,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Selection::scale_to_fit_print_volume(const BuildVolume& volume) { @@ -1311,13 +1161,13 @@ void Selection::scale_to_fit_print_volume(const BuildVolume& volume) // center selection on print bed setup_cache(); offset.z() = -get_bounding_box().min.z(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE TransformationType trafo_type; trafo_type.set_relative(); translate(offset, trafo_type); #else translate(offset); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); @@ -1407,7 +1257,7 @@ void Selection::mirror(Axis axis) set_bounding_boxes_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type) { if (!m_valid) @@ -1586,7 +1436,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) this->set_bounding_boxes_dirty(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) { @@ -1596,11 +1446,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix()); #else v.set_instance_offset(v.get_instance_offset() + displacement); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } std::set done; // prevent processing volumes twice @@ -1633,11 +1483,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx) continue; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix()); #else v.set_instance_offset(v.get_instance_offset() + displacement); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE done.insert(j); } } @@ -1821,13 +1671,8 @@ void Selection::render(float scale_factor) } else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { const GLVolume& v = *get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_scaling_factor_matrix()); trafo = v.get_instance_transformation().get_matrix() * v.get_volume_transformation().get_matrix_no_scaling_factor(); -#else - box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); - trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const Selection::IndicesList& ids = get_volume_idxs(); @@ -1835,14 +1680,9 @@ void Selection::render(float scale_factor) const GLVolume& v = *get_volume(id); box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Geometry::Transformation inst_trafo = get_first_volume()->get_instance_transformation(); box = box.transformed(inst_trafo.get_scaling_factor_matrix()); trafo = inst_trafo.get_matrix_no_scaling_factor(); -#else - box = box.transformed(get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); - trafo = get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } render_bounding_box(box, trafo, ColorRGB::WHITE()); @@ -1929,12 +1769,10 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE Transform3d orient_matrix = Transform3d::Identity(); #else glsafe(::glPushMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #if ENABLE_WORLD_COORDINATE const Vec3d center = get_bounding_box().center(); @@ -1957,11 +1795,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) glsafe(::glTranslated(center.x(), center.y(), center.z())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); -#else - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); #else if (!boost::starts_with(sidebar_field, "position")) { @@ -1999,19 +1833,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) if (!wxGetApp().obj_manipul()->is_world_coordinates()) { if (wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume* v = (*m_volumes)[*m_list.begin()]; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = v->get_instance_transformation().get_rotation_matrix() * v->get_volume_transformation().get_rotation_matrix(); -#else - orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation(); } else { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); -#else - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); } } @@ -2031,11 +1857,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) else { #if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE if (requires_local_axes()) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); #else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #else glsafe(::glTranslated(center.x(), center.y(), center.z())); if (requires_local_axes()) { @@ -2917,11 +2743,11 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLS void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) #endif // ENABLE_GL_SHADERS_ATTRIBUTES { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const bool uniform_scale = wxGetApp().obj_manipul()->get_uniform_scaling(); #else const bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& matrix) { @@ -3190,7 +3016,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL const int instance_idx = volume_i->instance_idx(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& curr_inst_trafo_i = volume_i->get_instance_transformation(); const Vec3d curr_inst_rotation_i = curr_inst_trafo_i.get_rotation(); const Vec3d& curr_inst_scaling_factor_i = curr_inst_trafo_i.get_scaling_factor(); @@ -3200,7 +3026,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ const Vec3d& rotation = volume_i->get_instance_rotation(); const Vec3d& scaling_factor = volume_i->get_instance_scaling_factor(); const Vec3d& mirror = volume_i->get_instance_mirror(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Process unselected instances. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -3214,7 +3040,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (volume_j->object_idx() != object_idx || volume_j->instance_idx() == instance_idx) continue; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Vec3d old_inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); assert(is_rotation_xy_synchronized(old_inst_rotation_i, old_inst_rotation_j)); const Geometry::Transformation& curr_inst_trafo_j = volume_j->get_instance_transformation(); @@ -3223,12 +3049,12 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ Vec3d new_inst_rotation_j = curr_inst_rotation_j; #else assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE switch (sync_rotation_type) { case SyncRotationType::NONE: { // z only rotation -> synch instance z // The X,Y rotations should be synchronized from start to end of the rotation. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE assert(is_rotation_xy_synchronized(curr_inst_rotation_i, curr_inst_rotation_j)); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) new_inst_offset_j.z() = curr_inst_trafo_i.get_offset().z(); @@ -3236,51 +3062,41 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ assert(is_rotation_xy_synchronized(rotation, volume_j->get_instance_rotation())); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) volume_j->set_instance_offset(Z, volume_i->get_instance_offset().z()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE break; } case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const double z_diff = Geometry::rotation_diff_z(old_inst_rotation_i, old_inst_rotation_j); new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); #else const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE break; } #if ENABLE_WORLD_COORDINATE case SyncRotationType::FULL: { // generic rotation -> update instance z with the delta of the rotation. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(curr_inst_rotation_i, old_inst_rotation_j)); -#else - const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation())); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& axis = angle_axis.axis(); const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ? -#if ENABLE_TRANSFORMATIONS_BY_MATRICES angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(curr_inst_rotation_i, old_inst_rotation_j); new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); -#else - angle_axis.angle()* axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); - - volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } #endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE volume_j->set_instance_transformation(Geometry::assemble_transform(new_inst_offset_j, new_inst_rotation_j, curr_inst_scaling_factor_i, curr_inst_mirror_i)); #else volume_j->set_instance_scaling_factor(scaling_factor); volume_j->set_instance_mirror(mirror); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE done.insert(j); } @@ -3307,14 +3123,14 @@ void Selection::synchronize_unselected_volumes() #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL const int volume_idx = volume->volume_idx(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& trafo = volume->get_volume_transformation(); #else const Vec3d& offset = volume->get_volume_offset(); const Vec3d& rotation = volume->get_volume_rotation(); const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); const Vec3d& mirror = volume->get_volume_mirror(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Process unselected volumes. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -3325,14 +3141,14 @@ void Selection::synchronize_unselected_volumes() if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) continue; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE v->set_volume_transformation(trafo); #else v->set_volume_offset(offset); v->set_volume_rotation(rotation); v->set_volume_scaling_factor(scaling_factor); v->set_volume_mirror(mirror); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } } } @@ -3447,13 +3263,13 @@ void Selection::paste_volumes_from_clipboard() { ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix_no_offset(); Transform3d dst_matrix = dst_instance->get_transformation().get_matrix_no_offset(); #else Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); // used to keep relative position of multivolume selections when pasting from another object @@ -3531,7 +3347,7 @@ void Selection::paste_objects_from_clipboard() #endif /* _DEBUG */ } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, const Transform3d& transform) { @@ -3551,7 +3367,7 @@ void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& v else assert(false); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index a20e952f3..0e6922c63 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -125,9 +125,9 @@ private: Transform3d scale_matrix{ Transform3d::Identity() }; Transform3d mirror_matrix{ Transform3d::Identity() }; Transform3d full_matrix{ Transform3d::Identity() }; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Geometry::Transformation transform; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE TransformCache() = default; explicit TransformCache(const Geometry::Transformation& transform); @@ -141,18 +141,18 @@ private: VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); const Vec3d& get_volume_position() const { return m_volume.position; } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE const Vec3d& get_volume_rotation() const { return m_volume.rotation; } const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; } const Vec3d& get_volume_mirror() const { return m_volume.mirror; } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; } const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; } const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; } const Transform3d& get_volume_full_matrix() const { return m_volume.full_matrix; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& get_volume_transform() const { return m_volume.transform; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const Vec3d& get_instance_position() const { return m_instance.position; } const Vec3d& get_instance_rotation() const { return m_instance.rotation; } @@ -162,9 +162,9 @@ private: const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; } const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& get_instance_transform() const { return m_instance.transform; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE }; public: @@ -232,7 +232,7 @@ private: // Bounding box of a single full instance selection, in world coordinates. // Modifiers are NOT taken in account std::optional m_scaled_instance_bounding_box; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. // Modifiers are taken in account std::optional m_full_unscaled_instance_bounding_box; @@ -242,7 +242,7 @@ private: // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. // Modifiers are taken in account std::optional m_full_unscaled_instance_local_bounding_box; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_RENDER_SELECTION_CENTER GLModel m_vbo_sphere; @@ -346,9 +346,6 @@ public: VolumeNotAxisAligned_Instance, MultipleSelection, }; -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - bool requires_uniform_scale(EUniformScaleRequiredReason* reason = nullptr) const; -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #else bool requires_uniform_scale() const; #endif // ENABLE_WORLD_COORDINATE @@ -376,7 +373,7 @@ public: // Bounding box of a single full instance selection, in world coordinates. // Modifiers are NOT taken in account const BoundingBoxf3& get_scaled_instance_bounding_box() const; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. // Modifiers are taken in account const BoundingBoxf3& get_full_unscaled_instance_bounding_box() const; @@ -387,16 +384,12 @@ public: // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. // Modifiers are taken in account const BoundingBoxf3& get_full_unscaled_instance_local_bounding_box() const; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void setup_cache(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(const Vec3d& displacement, TransformationType transformation_type); -#else - void translate(const Vec3d& displacement, ECoordinatesType type = ECoordinatesType::World); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else void translate(const Vec3d& displacement, bool local = false); #endif // ENABLE_WORLD_COORDINATE @@ -405,14 +398,12 @@ public: void scale(const Vec3d& scale, TransformationType transformation_type); void scale_to_fit_print_volume(const BuildVolume& volume); void mirror(Axis axis); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type); void reset_skew(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#else void translate(unsigned int object_idx, const Vec3d& displacement); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); #if ENABLE_WORLD_COORDINATE @@ -458,7 +449,7 @@ private: void do_remove_volume(unsigned int volume_idx); void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); @@ -467,7 +458,7 @@ private: } #else void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void render_synchronized_volumes(); #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_WORLD_COORDINATE @@ -513,10 +504,10 @@ private: void paste_volumes_from_clipboard(); void paste_objects_from_clipboard(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, const Transform3d& transform); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE }; } // namespace GUI From c99e93c3578f0ccb92a9de2a546a56a88662129e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 6 Jun 2022 14:55:38 +0200 Subject: [PATCH 102/102] Fixed differences after rebase with master --- src/libslic3r/Geometry.hpp | 8 ++-- src/slic3r/GUI/GLCanvas3D.cpp | 11 +++--- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 23 +++++++----- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 46 +++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 8 +--- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 20 ++++++---- src/slic3r/GUI/Selection.cpp | 35 ++++++++--------- 9 files changed, 80 insertions(+), 77 deletions(-) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 6ff52016e..aa09a0d3e 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -408,14 +408,14 @@ class Transformation public: #if ENABLE_WORLD_COORDINATE Transformation() = default; - explicit Transformation(const Transform3d & transform) : m_matrix(transform) {} + explicit Transformation(const Transform3d& transform) : m_matrix(transform) {} Vec3d get_offset() const { return m_matrix.translation(); } double get_offset(Axis axis) const { return get_offset()[axis]; } Transform3d get_offset_matrix() const; - void set_offset(const Vec3d & offset) { m_matrix.translation() = offset; } + void set_offset(const Vec3d& offset) { m_matrix.translation() = offset; } void set_offset(Axis axis, double offset) { m_matrix.translation()[axis] = offset; } Vec3d get_rotation() const; @@ -424,12 +424,12 @@ public: Transform3d get_rotation_matrix() const; #else Transformation(); - explicit Transformation(const Transform3d & transform); + explicit Transformation(const Transform3d& transform); const Vec3d& get_offset() const { return m_offset; } double get_offset(Axis axis) const { return m_offset(axis); } - void set_offset(const Vec3d & offset); + void set_offset(const Vec3d& offset); void set_offset(Axis axis, double offset); const Vec3d& get_rotation() const { return m_rotation; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 026734711..c44ba66a0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7433,11 +7433,13 @@ bool GLCanvas3D::_is_any_volume_outside() const void GLCanvas3D::_update_selection_from_hover() { bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); - if (m_hover_volume_idxs.empty()) { - if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) - m_selection.remove_all(); + bool selection_changed = false; - return; + if (m_hover_volume_idxs.empty()) { + if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) { + selection_changed = ! m_selection.is_empty(); + m_selection.remove_all(); + } } GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); @@ -7450,7 +7452,6 @@ void GLCanvas3D::_update_selection_from_hover() } } - bool selection_changed = false; #if ENABLE_NEW_RECTANGLE_SELECTION if (!m_rectangle_selection.is_empty()) { #endif // ENABLE_NEW_RECTANGLE_SELECTION diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 85f3c4785..dc5618cc4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -57,16 +57,15 @@ public: /// Detect reduction of move for wipetover on selection change /// void data_changed() override; - protected: - virtual bool on_init() override; - virtual std::string on_get_name() const override; - virtual bool on_is_activable() const override; - virtual void on_start_dragging() override; - virtual void on_stop_dragging() override; - virtual void on_dragging(const UpdateData& data) override; - virtual void on_render() override; - virtual void on_render_for_picking() override; + bool on_init() override; + std::string on_get_name() const override; + bool on_is_activable() const override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_dragging(const UpdateData& data) override; + void on_render() override; + void on_render_for_picking() override; private: double calc_projection(const UpdateData& data) const; @@ -79,10 +78,16 @@ private: void calc_selection_box_and_center(); #endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES + void render_grabber_extension(Axis axis, const Transform3d& base_matrix, const BoundingBoxf3& box, bool picking); +#else void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); +#endif // ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES #endif // !ENABLE_GIZMO_GRABBER_REFACTOR }; + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 3a4f77837..e877fa9f3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -393,7 +393,7 @@ void GLGizmoRotate::render_circle() const #else ::glBegin(GL_LINE_LOOP); for (unsigned int i = 0; i < ScaleStepsCount; ++i) { - const float angle = (float)i * ScaleStepRad; + const float angle = float(i) * ScaleStepRad; const float x = ::cos(angle) * m_radius; const float y = ::sin(angle) * m_radius; const float z = 0.0f; @@ -595,7 +595,7 @@ void GLGizmoRotate::render_angle() const #else ::glBegin(GL_LINE_STRIP); for (unsigned int i = 0; i <= AngleResolution; ++i) { - const float angle = (float)i * step_angle; + const float angle = float(i) * step_angle; const float x = ::cos(angle) * ex_radius; const float y = ::sin(angle) * ex_radius; const float z = 0.0f; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 5fc24ed90..9b0417aaf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -137,7 +137,7 @@ public: GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } - void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } + void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation.x()); m_gizmos[Y].set_angle(rotation.y()); m_gizmos[Z].set_angle(rotation.z()); } std::string get_tooltip() const override { std::string tooltip = m_gizmos[X].get_tooltip(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index f287a69f7..98c9ffeef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -11,7 +11,7 @@ #include -#include +#include namespace Slic3r { namespace GUI { @@ -166,26 +166,25 @@ bool GLGizmoScale3D::on_is_activable() const void GLGizmoScale3D::on_start_dragging() { - if (m_hover_id != -1) { - m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); + assert(m_hover_id != -1); + m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); #if ENABLE_WORLD_COORDINATE - m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; - m_starting.box = m_bounding_box; - m_starting.center = m_center; - m_starting.instance_center = m_instance_center; + m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; + m_starting.box = m_bounding_box; + m_starting.center = m_center; + m_starting.instance_center = m_instance_center; #else - m_starting.drag_position = m_grabbers[m_hover_id].center; - m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_bounding_box : m_parent.get_selection().get_bounding_box(); + m_starting.drag_position = m_grabbers[m_hover_id].center; + m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_bounding_box : m_parent.get_selection().get_bounding_box(); - const Vec3d center = m_starting.box.center(); - m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); - m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); - m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); - m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); - m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); + const Vec3d& center = m_starting.box.center(); + m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); + m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); + m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); + m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); #endif // ENABLE_WORLD_COORDINATE - } } void GLGizmoScale3D::on_stop_dragging() { @@ -230,11 +229,7 @@ void GLGizmoScale3D::on_render() const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int idx : idxs) { const GLVolume& v = *selection.get_volume(idx); -#if ENABLE_WORLD_COORDINATE m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); -#else - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); -#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE @@ -303,7 +298,7 @@ void GLGizmoScale3D::on_render() const Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); const Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); - bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); + const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); #endif // ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE @@ -361,6 +356,7 @@ void GLGizmoScale3D::on_render() m_grabbers[7].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.min.y(), center.z()) + offset_x - offset_y; m_grabbers[8].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.max.y(), center.z()) + offset_x + offset_y; m_grabbers[9].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.max.y(), center.z()) - offset_x + offset_y; + for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } @@ -383,7 +379,7 @@ void GLGizmoScale3D::on_render() transform_to_local(selection); #endif // ENABLE_GL_SHADERS_ATTRIBUTES - float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0); + const float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0); #else const BoundingBoxf3& selection_box = selection.get_bounding_box(); const float grabber_mean_size = (float)((selection_box.size().x() + selection_box.size().y() + selection_box.size().z()) / 3.0); @@ -597,7 +593,9 @@ void GLGizmoScale3D::on_render() } #if ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_WORLD_COORDINATE } @@ -621,7 +619,7 @@ void GLGizmoScale3D::on_render_for_picking() #else render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); #endif // ENABLE_WORLD_COORDINATE - } +} #if ENABLE_LEGACY_OPENGL_REMOVAL void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index b2a9b7a23..fb4ff09b6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -3,7 +3,9 @@ #include "GLGizmoBase.hpp" +#if !ENABLE_WORLD_COORDINATE #include "libslic3r/BoundingBox.hpp" +#endif // !ENABLE_WORLD_COORDINATE namespace Slic3r { namespace GUI { @@ -73,12 +75,6 @@ public: void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } #endif // ENABLE_WORLD_COORDINATE -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED - const Vec3d& get_starting_scale() const { return m_starting.scale; } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED - - const Vec3d& get_offset() const { return m_offset; } - std::string get_tooltip() const override; /// diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 21b55ea69..39a1cba8e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -408,7 +408,8 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) { // at this moment is enebled to process mouse move under gizmo // tools bar e.g. Do not interupt dragging. return false; - } else if (mc.exist_tooltip) { + } + else if (mc.exist_tooltip) { // first move out of gizmo tool bar - unselect tooltip mc.exist_tooltip = false; update_hover_state(Undefined); @@ -423,10 +424,12 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) { mc.left = true; open_gizmo(gizmo); return true; - } else if (mouse_event.RightDown()) { + } + else if (mouse_event.RightDown()) { mc.right = true; return true; - } else if (mouse_event.MiddleDown()) { + } + else if (mouse_event.MiddleDown()) { mc.middle = true; return true; } @@ -441,14 +444,17 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) { update_hover_state(Undefined); } // draging start on toolbar so no propagation into scene - return true; - } else if (mc.left && mouse_event.LeftUp()) { + return true; + } + else if (mc.left && mouse_event.LeftUp()) { mc.left = false; return true; - } else if (mc.right && mouse_event.RightUp()) { + } + else if (mc.right && mouse_event.RightUp()) { mc.right = false; return true; - } else if (mc.middle && mouse_event.MiddleUp()) { + } + else if (mc.middle && mouse_event.MiddleUp()) { mc.middle = false; return true; } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 61ffd4b19..271435c99 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -9,7 +9,6 @@ #include "GUI_ObjectList.hpp" #include "Camera.hpp" #include "Plater.hpp" - #if ENABLE_WORLD_COORDINATE #include "MsgDialog.hpp" #endif // ENABLE_WORLD_COORDINATE @@ -786,7 +785,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor assert(false); } else { - const Vec3d offset = transformation_type.local() ? + const Vec3d offset = transformation_type.local() ? (Vec3d)(volume_data.get_volume_transform().get_rotation_matrix() * displacement) : displacement; transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(offset)); } @@ -966,7 +965,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); } - volume.set_instance_rotation(new_rotation); object_instance_first[volume.object_idx()] = i; } @@ -1048,7 +1046,7 @@ void Selection::flattening_rotate(const Vec3d& normal) #else const auto& voldata = m_cache.volumes_data[i]; Vec3d tnormal = (Geometry::assemble_transform( - Vec3d::Zero(), voldata.get_instance_rotation(), + Vec3d::Zero(), voldata.get_instance_rotation(), voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized(); // Additional rotation to align tnormal with the down vector in the world coordinate space. auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ()); @@ -1897,6 +1895,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field, *shader); + #if ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { if (!wxGetApp().obj_manipul()->is_world_coordinates()) @@ -2014,8 +2013,7 @@ std::vector Selection::get_volume_idxs_from_object(unsigned int ob { std::vector idxs; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { if ((*m_volumes)[i]->object_idx() == (int)object_idx) idxs.push_back(i); } @@ -2027,10 +2025,9 @@ std::vector Selection::get_volume_idxs_from_instance(unsigned int { std::vector idxs; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { const GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx)) + if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) idxs.push_back(i); } @@ -2044,9 +2041,8 @@ std::vector Selection::get_volume_idxs_from_volume(unsigned int ob for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { const GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->volume_idx() == (int)volume_idx)) - { - if (((int)instance_idx != -1) && (v->instance_idx() == (int)instance_idx)) + if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) { + if ((int)instance_idx != -1 && v->instance_idx() == (int)instance_idx) idxs.push_back(i); } } @@ -2058,8 +2054,7 @@ std::vector Selection::get_missing_volume_idxs_from(const std::vec { std::vector idxs; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { std::vector::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i); if (it == volume_idxs.end()) idxs.push_back(i); @@ -2105,7 +2100,8 @@ void Selection::update_type() if (!m_valid) m_type = Invalid; - else { + else + { if (m_list.empty()) m_type = Empty; else if (m_list.size() == 1) { @@ -2203,8 +2199,7 @@ void Selection::update_type() int object_idx = get_object_idx(); int instance_idx = get_instance_idx(); - for (GLVolume* v : *m_volumes) - { + for (GLVolume* v : *m_volumes) { v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; } @@ -2393,7 +2388,7 @@ void Selection::render_synchronized_volumes() if (coordinates_type == ECoordinatesType::World) { box = v.transformed_convex_hull_bounding_box(); trafo = Transform3d::Identity(); - } + } else if (coordinates_type == ECoordinatesType::Local) { box = v.bounding_box(); trafo = v.world_matrix(); @@ -2437,6 +2432,7 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con #if ENABLE_LEGACY_OPENGL_REMOVAL const BoundingBoxf3& curr_box = m_box.get_bounding_box(); + if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) { m_box.reset(); @@ -3046,10 +3042,11 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ const Geometry::Transformation& curr_inst_trafo_j = volume_j->get_instance_transformation(); const Vec3d curr_inst_rotation_j = curr_inst_trafo_j.get_rotation(); Vec3d new_inst_offset_j = curr_inst_trafo_j.get_offset(); - Vec3d new_inst_rotation_j = curr_inst_rotation_j; + Vec3d new_inst_rotation_j = curr_inst_rotation_j; #else assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); #endif // ENABLE_WORLD_COORDINATE + switch (sync_rotation_type) { case SyncRotationType::NONE: { // z only rotation -> synch instance z