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 84ec0f37a..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% @@ -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,16 @@ 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 +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 @@ -66,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 @@ -75,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 @@ -84,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 @@ -93,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 @@ -102,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 @@ -111,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 @@ -120,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 @@ -129,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 @@ -138,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 @@ -147,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 @@ -156,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 @@ -165,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 @@ -174,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 @@ -183,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 @@ -192,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 @@ -201,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 @@ -210,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 @@ -219,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 @@ -228,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 @@ -237,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 @@ -246,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 @@ -255,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 @@ -264,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 @@ -273,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 @@ -282,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 @@ -291,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 @@ -300,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 @@ -309,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 @@ -318,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 @@ -327,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. @@ -842,6 +851,17 @@ 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 + # Common printer preset [printer:*common*] printer_technology = FFF @@ -989,6 +1009,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 000000000..d11396b05 Binary files /dev/null and b/resources/profiles/Creality/ENDER3S1PRO_thumbnail.png differ diff --git a/resources/profiles/INAT.idx b/resources/profiles/INAT.idx index a756b34b5..0446399a1 100644 --- a/resources/profiles/INAT.idx +++ b/resources/profiles/INAT.idx @@ -1,4 +1,6 @@ -min_slic3r_version = 2.3.1-beta +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 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 000000000..114247ade Binary files /dev/null and b/resources/profiles/INAT/PROTON_XE750_thumbnail.png differ diff --git a/src/libslic3r/AStar.hpp b/src/libslic3r/AStar.hpp index 052d0e814..61c82157b 100644 --- a/src/libslic3r/AStar.hpp +++ b/src/libslic3r/AStar.hpp @@ -1,11 +1,12 @@ #ifndef ASTAR_HPP #define ASTAR_HPP +#include // std::isinf() is here +#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 +35,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); @@ -50,131 +53,132 @@ template struct TracerTraits_ template using TracerNodeT = typename TracerTraits_>::Node; -namespace detail { -// Helper functions dispatching calls through the TracerTraits_ interface +constexpr size_t Unassigned = size_t(-1); -template using TracerTraits = TracerTraits_>; - -template -void foreach_reachable(const T &tracer, const TracerNodeT &from, Fn &&fn) +template +struct QNode // Queue node. Keeps track of scores g, and h { - TracerTraits::foreach_reachable(tracer, from, fn); -} + 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 -template -float trace_distance(const T &tracer, const TracerNodeT &a, const TracerNodeT &b) -{ - return TracerTraits::distance(tracer, a, b); -} + float g, h; + float f() const { return g + h; } -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 + 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; + using QNode = QNode; + using TracerTraits = TracerTraits_>; - using Node = TracerNodeT; - enum class QueueType { Open, Closed, None }; - - 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 = TracerTraits::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 = TracerTraits::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)); + + TracerTraits::foreach_reachable(tracer, q.node, [&](const Node &succ_nd) { + if (goal_id != Unassigned) + return true; + + 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}; - 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 == decltype(qopen)::invalid_id()) + // 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 (q->parent != Unassigned) { + assert(!std::isinf(q->g)); // Uninitialized nodes are NOT allowed + + *out = q->node; + ++out; + q = &cached_nodes[q->parent]; + } + } + + return goal_id != Unassigned; } }} // namespace Slic3r::astar 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) 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 e226fbbab..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: @@ -125,7 +120,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); 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; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index aef83f21f..58951ffa5 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 { this->observe(false); }; + + 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{false}; +}; + // 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/Geometry.cpp b/src/libslic3r/Geometry.cpp index 6eae83277..115d21693 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -313,9 +313,60 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, return transform; } +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 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(); + 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; +} + 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) { @@ -363,6 +414,54 @@ Vec3d extract_euler_angles(const Transform3d& transform) return extract_euler_angles(m); } +#if ENABLE_WORLD_COORDINATE +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) }; +} + +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)); +} + +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 +499,19 @@ void Transformation::set_offset(Axis axis, double offset) m_dirty = true; } } +#endif // ENABLE_WORLD_COORDINATE void Transformation::set_rotation(const Vec3d& rotation) { +#if ENABLE_WORLD_COORDINATE + 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_WORLD_COORDINATE } void Transformation::set_rotation(Axis axis, double rotation) @@ -414,32 +520,106 @@ void Transformation::set_rotation(Axis axis, double rotation) if (is_approx(std::abs(rotation), 2.0 * double(PI))) rotation = 0.0; +#if ENABLE_WORLD_COORDINATE + 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_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE +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_WORLD_COORDINATE + void Transformation::set_scaling_factor(const Vec3d& scaling_factor) { +#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(); + 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_WORLD_COORDINATE } void Transformation::set_scaling_factor(Axis axis, double scaling_factor) { +#if ENABLE_WORLD_COORDINATE + 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_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE +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_WORLD_COORDINATE + void Transformation::set_mirror(const Vec3d& mirror) { +#if ENABLE_WORLD_COORDINATE + 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_WORLD_COORDINATE } void Transformation::set_mirror(Axis axis, double mirror) @@ -450,12 +630,24 @@ void Transformation::set_mirror(Axis axis, double mirror) else if (abs_mirror != 1.0) mirror /= abs_mirror; +#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); +#else if (m_mirror(axis) != mirror) { m_mirror(axis) = mirror; m_dirty = true; } +#endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE +bool Transformation::has_skew() const +{ + return contains_skew(m_matrix); +} +#else void Transformation::set_from_transform(const Transform3d& transform) { // offset @@ -493,17 +685,62 @@ 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_WORLD_COORDINATE void Transformation::reset() { +#if !ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); m_rotation = Vec3d::Zero(); m_scaling_factor = Vec3d::Ones(); m_mirror = Vec3d::Ones(); +#endif // !ENABLE_WORLD_COORDINATE m_matrix = Transform3d::Identity(); +#if !ENABLE_WORLD_COORDINATE m_dirty = false; +#endif // !ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE +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); + 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,12 +757,14 @@ const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rot return m_matrix; } +#endif // ENABLE_WORLD_COORDINATE Transformation Transformation::operator * (const Transformation& other) const { return Transformation(get_matrix() * other.get_matrix()); } +#if !ENABLE_WORLD_COORDINATE Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox) { Transformation out; @@ -571,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. @@ -581,6 +819,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation return out; } +#endif // !ENABLE_WORLD_COORDINATE // For parsing a transformation matrix from 3MF / AMF. Transform3d transform3d_from_string(const std::string& transform_str) @@ -619,7 +858,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/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 2ca4ef884..aa09a0d3e 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,7 +333,43 @@ 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()); + +// 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 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 +// 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); // Returns the euler angles extracted from the given rotation matrix // Warning -> The matrix should not contain any scale or shear !!! @@ -344,6 +381,9 @@ Vec3d extract_euler_angles(const Transform3d& transform); class Transformation { +#if ENABLE_WORLD_COORDINATE + Transform3d m_matrix{ Transform3d::Identity() }; +#else struct Flags { bool dont_translate{ true }; @@ -363,8 +403,26 @@ class Transformation mutable Transform3d m_matrix{ Transform3d::Identity() }; mutable Flags m_flags; mutable bool m_dirty{ false }; +#endif // ENABLE_WORLD_COORDINATE public: +#if ENABLE_WORLD_COORDINATE + Transformation() = default; + 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(Axis axis, double offset) { m_matrix.translation()[axis] = offset; } + + 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); @@ -376,47 +434,103 @@ public: const Vec3d& get_rotation() const { return m_rotation; } double get_rotation(Axis axis) const { return m_rotation(axis); } +#endif // ENABLE_WORLD_COORDINATE void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); +#if ENABLE_WORLD_COORDINATE + 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_WORLD_COORDINATE void set_scaling_factor(const Vec3d& scaling_factor); void set_scaling_factor(Axis axis, double scaling_factor); + +#if ENABLE_WORLD_COORDINATE + 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_WORLD_COORDINATE void set_mirror(const Vec3d& mirror); void set_mirror(Axis axis, double mirror); +#if ENABLE_WORLD_COORDINATE + bool has_skew() const; +#else void set_from_transform(const Transform3d& transform); +#endif // ENABLE_WORLD_COORDINATE void reset(); +#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()); } + void reset_mirror() { set_mirror(Vec3d::Ones()); } + void reset_skew(); + 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_WORLD_COORDINATE Transformation operator * (const Transformation& other) const; +#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_WORLD_COORDINATE 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_WORLD_COORDINATE + 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_WORLD_COORDINATE }; // For parsing a transformation matrix from 3MF / AMF. 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/Model.cpp b/src/libslic3r/Model.cpp index 3376cc888..1f8083aca 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_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_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())); @@ -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_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); - for (ModelVolume *v : this->volumes) - { +#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())); } @@ -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_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_WORLD_COORDINATE model_instance->set_offset(model_instance->get_offset() + shift); } @@ -1412,9 +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 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 bool left_handed = reference_trafo.is_left_handed(); bool has_mirrorring = ! reference_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.)); @@ -1432,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_WORLD_COORDINATE + 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_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(); bool volume_left_handed = volume_trafo.is_left_handed(); @@ -1442,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_WORLD_COORDINATE + 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_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. @@ -1489,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_WORLD_COORDINATE + const Transform3d mi = inst->get_matrix_no_offset(); +#else const Transform3d& mi = inst->get_matrix(true); +#endif // ENABLE_WORLD_COORDINATE for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1510,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_WORLD_COORDINATE + const Transform3d mi = inst->get_matrix_no_offset(); +#else const Transform3d& mi = inst->get_matrix(true); +#endif // ENABLE_WORLD_COORDINATE for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1936,14 +1979,22 @@ void ModelVolume::convert_from_meters() void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { +#if ENABLE_WORLD_COORDINATE + mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix()); +#else mesh->transform(get_matrix(dont_translate)); +#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_WORLD_COORDINATE + copy.transform(get_transformation().get_rotation_matrix()); +#else copy.transform(get_matrix(true, false, true, true)); +#endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 bbox = copy.bounding_box(); if (!empty(bbox)) { @@ -1968,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_WORLD_COORDINATE + return bbox.transformed(dont_translate ? get_matrix_no_offset() : get_matrix()); +#else return bbox.transformed(get_matrix(dont_translate)); +#endif // ENABLE_WORLD_COORDINATE } Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const { +#if ENABLE_WORLD_COORDINATE + return dont_translate ? get_matrix_no_offset() * v : get_matrix() * v; +#else return get_matrix(dont_translate) * v; +#endif // ENABLE_WORLD_COORDINATE } void ModelInstance::transform_polygon(Polygon* polygon) const diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0c95d98c0..828358419 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1,1212 +1,1253 @@ -#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_WORLD_COORDINATE + void set_transformation(const Transform3d& trafo) { m_transformation.set_matrix(trafo); } + + 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_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_WORLD_COORDINATE + Vec3d get_rotation() const { return m_transformation.get_rotation(); } +#else + const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } +#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); } + + 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_WORLD_COORDINATE + Vec3d get_mirror() const { return m_transformation.get_mirror(); } +#else + const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } +#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(); } + + 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_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_WORLD_COORDINATE + + 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_WORLD_COORDINATE + Vec3d get_offset() const { return m_transformation.get_offset(); } +#else + const Vec3d& get_offset() const { return m_transformation.get_offset(); } +#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_WORLD_COORDINATE + Vec3d get_rotation() const { return m_transformation.get_rotation(); } +#else + const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } +#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_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_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_WORLD_COORDINATE + Vec3d get_mirror() const { return m_transformation.get_mirror(); } +#else + const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } +#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(); } + + 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_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_WORLD_COORDINATE + + 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 993718fa8..2cc568e3c 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -547,6 +547,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_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_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 34e3129d9..4c085728c 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_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_WORLD_COORDINATE + } + } 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_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_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 }); + } + } + } + + 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 fe50a3416..71d679f98 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 @@ -67,6 +67,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/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(); } }); } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 78e73ba9a..29b8b7e73 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 @@ -129,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 97eb3bedb..a9545b3e6 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 const float Bed3D::Axes::DefaultStemRadius = 0.5f; const float Bed3D::Axes::DefaultStemLength = 25.0f; const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius; @@ -171,6 +172,7 @@ void Bed3D::Axes::render() glsafe(::glDisable(GL_DEPTH_TEST)); } +#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) { @@ -333,7 +335,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 + 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 // extend to contain model, if any BoundingBoxf3 model_bb = m_model.get_bounding_box(); if (model_bb.defined) { @@ -531,7 +537,15 @@ std::tuple Bed3D::detect_type(const Point void Bed3D::render_axes() { if (m_build_volume.valid()) +#if ENABLE_WORLD_COORDINATE +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_axes.render(Transform3d::Identity(), 0.25f); +#else + m_axes.render(0.25f); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#else m_axes.render(); +#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_LEGACY_OPENGL_REMOVAL diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 9cde0779a..8eb28c9f6 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 +#include "CoordAxes.hpp" +#else #include "GLModel.hpp" +#endif // ENABLE_WORLD_COORDINATE #include "libslic3r/BuildVolume.hpp" #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -44,6 +48,7 @@ public: class Bed3D { +#if !ENABLE_WORLD_COORDINATE 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 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 + CoordAxes m_axes; +#else Axes m_axes; +#endif // ENABLE_WORLD_COORDINATE float m_scale_factor{ 1.0f }; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 3f6b1361d..237d19405 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -432,14 +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_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_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_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_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(); } @@ -451,7 +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_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_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(); } @@ -459,26 +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_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_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_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_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_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_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_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_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/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp new file mode 100644 index 000000000..b1c3ba9e0 --- /dev/null +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -0,0 +1,95 @@ +#include "libslic3r/libslic3r.h" + +#include "CoordAxes.hpp" +#include "GUI_App.hpp" +#include "3DScene.hpp" +#if ENABLE_LEGACY_OPENGL_REMOVAL +#include "Plater.hpp" +#include "Camera.hpp" +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#include + +#if ENABLE_WORLD_COORDINATE + +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; + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void CoordAxes::render(const Transform3d& trafo, float emission_factor) +#else +void CoordAxes::render(float emission_factor) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + 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_LEGACY_OPENGL_REMOVAL + }; + + 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(); + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + 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()); + render_axis(*shader, trafo * Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 })); +#else + m_arrow.set_color(-1, ColorRGBA::X()); + render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // y axis +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(ColorRGBA::Y()); + render_axis(*shader, trafo * Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 })); +#else + m_arrow.set_color(-1, ColorRGBA::Y()); + render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // z axis +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(ColorRGBA::Z()); + render_axis(*shader, trafo * Geometry::assemble_transform(m_origin)); +#else + m_arrow.set_color(-1, ColorRGBA::Z()); + render_axis(Geometry::assemble_transform(m_origin).cast()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + shader->stop_using(); + if (curr_shader != nullptr) + curr_shader->start_using(); +} + +} // GUI +} // Slic3r + +#endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/CoordAxes.hpp b/src/slic3r/GUI/CoordAxes.hpp new file mode 100644 index 000000000..d2e830a65 --- /dev/null +++ b/src/slic3r/GUI/CoordAxes.hpp @@ -0,0 +1,64 @@ +#ifndef slic3r_CoordAxes_hpp_ +#define slic3r_CoordAxes_hpp_ + +#if ENABLE_WORLD_COORDINATE +#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; } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + void render(const Transform3d& trafo, float emission_factor = 0.0f); +#else + void render(float emission_factor = 0.0f); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +}; + +} // GUI +} // Slic3r + +#endif // ENABLE_WORLD_COORDINATE + +#endif // slic3r_CoordAxes_hpp_ diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fd1783e3f..26df38936 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1075,6 +1075,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_WORLD_COORDINATE +wxDEFINE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); +#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); @@ -2888,7 +2891,13 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) else displacement = multiplier * direction; +#if ENABLE_WORLD_COORDINATE + TransformationType trafo_type; + trafo_type.set_relative(); + m_selection.translate(displacement, trafo_type); +#else m_selection.translate(displacement); +#endif // ENABLE_WORLD_COORDINATE m_dirty = true; } ); @@ -3553,7 +3562,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } +#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_WORLD_COORDINATE if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) update_sequential_clearance(); wxGetApp().obj_manipul()->set_dirty(); @@ -3799,9 +3814,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_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_WORLD_COORDINATE else if (selection_mode == Selection::Volume) +#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_WORLD_COORDINATE object_moved = true; model_object->invalidate_bounding_box(); @@ -3881,8 +3904,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(); @@ -3890,8 +3913,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)); @@ -3899,12 +3922,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_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_WORLD_COORDINATE } else if (selection_mode == Selection::Volume) { +#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_WORLD_COORDINATE } model_object->invalidate_bounding_box(); } @@ -3954,12 +3985,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)); @@ -3967,13 +3998,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_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_WORLD_COORDINATE } else if (selection_mode == Selection::Volume) { +#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_WORLD_COORDINATE } model_object->invalidate_bounding_box(); } @@ -3982,10 +4022,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); } @@ -4035,9 +4075,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_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_WORLD_COORDINATE else if (selection_mode == Selection::Volume) +#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_WORLD_COORDINATE model_object->invalidate_bounding_box(); } @@ -4061,6 +4109,44 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) m_dirty = true; } +#if ENABLE_WORLD_COORDINATE +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 + + 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_WORLD_COORDINATE + 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 fdad69161..3a6a35e47 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_WORLD_COORDINATE +wxDECLARE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); +#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); @@ -732,7 +735,11 @@ public: void update_volumes_colors_by_extruder(); +#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_WORLD_COORDINATE void render(); // printable_only == false -> render also non printable volumes as grayed @@ -800,6 +807,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_WORLD_COORDINATE + void do_reset_skew(const std::string& snapshot_type); +#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_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..0d6cf7f4b --- /dev/null +++ b/src/slic3r/GUI/GUI_Geometry.hpp @@ -0,0 +1,81 @@ +#ifndef slic3r_GUI_Geometry_hpp_ +#define slic3r_GUI_Geometry_hpp_ + +namespace Slic3r { +namespace GUI { + +#if ENABLE_WORLD_COORDINATE +enum class ECoordinatesType : unsigned char +{ + World, + Instance, + Local +}; + +class TransformationType +{ +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 = 2, + // Absolute transformations, allowed in local coordinate system only. + Absolute = 0, + // Relative transformations, allowed in both local and world coordinate system. + 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 = 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, + 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(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(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); } + 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_WORLD_COORDINATE + +} // namespace Slic3r +} // namespace GUI + +#endif // slic3r_GUI_Geometry_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4370a2f64..bfedd8e1e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1530,9 +1530,13 @@ 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_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_WORLD_COORDINATE const Vec3d instance_offset = v->get_instance_offset(); for (size_t i = 0; i < input_files.size(); ++i) { @@ -1580,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_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(); +#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_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; @@ -1650,17 +1660,27 @@ 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(); +#if ENABLE_WORLD_COORDINATE // Transform the new modifier to be aligned with the print bed. - const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); + 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. + 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_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_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_WORLD_COORDINATE const wxString name = _L("Generic") + "-" + _(type_name); new_volume->name = into_u8(name); @@ -2545,7 +2565,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); @@ -3263,8 +3289,12 @@ 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()) { - const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); +#endif // ENABLE_WORLD_COORDINATE + 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 6ab87150b..24ae01389 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_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)); + temp->Select((int)ECoordinatesType::World); +#else temp->Append(_L("World coordinates")); temp->Append(_L("Local coordinates")); temp->SetSelection(0); temp->SetValue(temp->GetString(0)); +#endif // ENABLE_WORLD_COORDINATE 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_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_WORLD_COORDINATE 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,8 +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_WORLD_COORDINATE + this->set_coordinates_type(evt.GetString()); +#else this->set_world_coordinates(evt.GetSelection() != 1); - }), 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 @@ -264,8 +282,12 @@ 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()) { - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); +#endif // ENABLE_WORLD_COORDINATE + 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()) { @@ -278,7 +300,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")); @@ -327,28 +349,60 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - if (selection.is_single_volume() || selection.is_single_modifier()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_volume_or_modifier()) { + const GLVolume* volume = selection.get_first_volume(); + const double min_z = get_volume_min_z(*volume); + if (!is_world_coordinates()) { + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().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 + if (selection.is_single_volume() || selection.is_single_modifier()) { + 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")); 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 (!is_world_coordinates()) { + const GLVolume* volume = selection.get_first_volume(); + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().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); @@ -365,21 +419,22 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - if (selection.is_single_volume() || selection.is_single_modifier()) { - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); - volume->set_volume_rotation(Vec3d::Zero()); - } +#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_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()) { - 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 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")); @@ -397,11 +452,29 @@ 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_WORLD_COORDINATE + 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.); - }); +#endif // ENABLE_WORLD_COORDINATE + }); editors_grid_sizer->Add(m_reset_scale_button); for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) @@ -411,6 +484,25 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND); +#if ENABLE_WORLD_COORDINATE + 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_WORLD_COORDINATE + m_check_inch = new wxCheckBox(parent, wxID_ANY, _L("Inches")); m_check_inch->SetFont(wxGetApp().normal_font()); @@ -444,8 +536,27 @@ 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_or_modifier()); + 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->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->GetString(m_word_local_combo->GetSelection())); + } +#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); } } @@ -489,8 +600,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; @@ -522,33 +632,49 @@ 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_WORLD_COORDINATE if (wxGetApp().get_mode() == comSimple) m_world_coordinates = true; +#endif // !ENABLE_WORLD_COORDINATE 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(); // 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 (m_world_coordinates && ! m_uniform_scale && ! 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_WORLD_COORDINATE +#if ENABLE_WORLD_COORDINATE + if (is_world_coordinates()) { + m_new_position = volume->get_instance_offset(); +#else if (m_world_coordinates) { - 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.; - } +#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.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 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()); - m_new_scale = volume->get_instance_scaling_factor() * 100.; +#if ENABLE_WORLD_COORDINATE + m_new_move_label_string = L("Translate"); + m_new_rotate_label_string = L("Rotate"); + 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_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; } m_new_enabled = true; @@ -557,19 +683,52 @@ 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"); 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()); + const GLVolume* volume = selection.get_first_volume(); +#if ENABLE_WORLD_COORDINATE + if (is_world_coordinates()) { + const Geometry::Transformation trafo(volume->world_matrix()); + + const Vec3d& offset = trafo.get_offset(); + + m_new_position = offset; + 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.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 if (is_local_coordinates()) { + m_new_move_label_string = L("Translate"); + m_new_rotate_label_string = L("Rotate"); + 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()); + } + 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_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.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 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_WORLD_COORDINATE m_new_enabled = true; } else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { @@ -635,22 +794,26 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } +#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(); } else { +#endif // !ENABLE_WORLD_COORDINATE m_lock_bnt->SetLock(m_uniform_scale); m_lock_bnt->SetToolTip(wxEmptyString); m_lock_bnt->enable(); +#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_WORLD_COORDINATE if (m_new_enabled) m_og->enable(); @@ -677,29 +840,75 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_rotation = false; bool show_scale = false; bool show_drop_to_bed = false; +#if ENABLE_WORLD_COORDINATE + bool show_skew = false; + if (selection.is_single_full_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_first_volume()); + + show_drop_to_bed = std::abs(min_z) > EPSILON; + const GLVolume* volume = selection.get_first_volume(); + Transform3d rotation = Transform3d::Identity(); + Transform3d scale = Transform3d::Identity(); + Geometry::Transformation skew; +#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.; + double min_z = 0.0; +#endif // ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance()) { +#if ENABLE_WORLD_COORDINATE + const Geometry::Transformation& trafo = volume->get_instance_transformation(); + rotation = trafo.get_rotation_matrix(); + scale = trafo.get_scaling_factor_matrix(); + 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(); - min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z(); + min_z = selection.get_scaled_instance_bounding_box().min.z(); +#endif // ENABLE_WORLD_COORDINATE } else { +#if ENABLE_WORLD_COORDINATE + const Geometry::Transformation& trafo = volume->get_volume_transformation(); + rotation = trafo.get_rotation_matrix(); + scale = trafo.get_scaling_factor_matrix(); + 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(); min_z = get_volume_min_z(*volume); +#endif // ENABLE_WORLD_COORDINATE } +#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()); show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; +#endif // ENABLE_WORLD_COORDINATE } +#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_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()) @@ -707,6 +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_WORLD_COORDINATE + m_reset_skew_button->Show(show_skew); + m_skew_label->Show(show_skew); +#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(); @@ -726,9 +939,17 @@ void ObjectManipulation::update_mirror_buttons_visibility() Selection& selection = canvas->get_selection(); std::array new_states = {mbHidden, mbHidden, mbHidden}; +#if ENABLE_WORLD_COORDINATE + if (is_local_coordinates()) { +#else if (!m_world_coordinates) { +#endif // ENABLE_WORLD_COORDINATE +#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()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#endif // ENABLE_WORLD_COORDINATE + const GLVolume* volume = selection.get_first_volume(); Vec3d mirror; if (selection.is_single_full_instance()) @@ -792,6 +1013,19 @@ void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning m_fix_throught_netfab_bitmap->SetToolTip(tooltip); } +#if ENABLE_WORLD_COORDINATE +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); return _L("Unknown"); } + } +} +#endif // ENABLE_WORLD_COORDINATE + void ObjectManipulation::reset_settings_value() { m_new_position = Vec3d::Zero(); @@ -815,7 +1049,19 @@ 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 + 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, selection.requires_local_axes()); +#endif // ENABLE_WORLD_COORDINATE canvas->do_move(L("Set Position")); m_cache.position = position; @@ -834,6 +1080,18 @@ void ObjectManipulation::change_rotation_value(int axis, double value) GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); +#if ENABLE_WORLD_COORDINATE + TransformationType transformation_type; + transformation_type.set_relative(); + if (selection.is_single_full_instance()) + transformation_type.set_independent(); + + if (is_local_coordinates()) + transformation_type.set_local(); + + 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(); @@ -842,6 +1100,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( @@ -887,8 +1146,12 @@ 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()) { - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); +#endif // ENABLE_WORLD_COORDINATE + 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); @@ -897,11 +1160,19 @@ 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_WORLD_COORDINATE + ref_size = is_world_coordinates() ? +#else + 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 + this->do_size(axis, size.cwiseQuotient(ref_size)); +#else this->do_scale(axis, size.cwiseQuotient(ref_size)); +#endif // ENABLE_WORLD_COORDINATE m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; @@ -911,8 +1182,22 @@ 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 (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + + 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 TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { transformation_type.set_absolute(); @@ -922,12 +1207,31 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const 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); wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } +#if ENABLE_WORLD_COORDINATE +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(); + + 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")); +} +#endif // ENABLE_WORLD_COORDINATE + void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value) { if (!m_cache.is_valid()) @@ -962,17 +1266,26 @@ 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 (selection.is_single_full_instance() && m_world_coordinates && !new_value) { +#if ENABLE_WORLD_COORDINATE + if (!use_uniform_scale) + // Recalculate cached values at this panel, refresh the screen. + this->UpdateAndShow(true); + + m_uniform_scale = use_uniform_scale; + + set_dirty(); +#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 - 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())) { + 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" @@ -980,7 +1293,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); @@ -994,9 +1307,29 @@ 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 } +#if ENABLE_WORLD_COORDINATE +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(); +} +#endif // ENABLE_WORLD_COORDINATE + void ObjectManipulation::msw_rescale() { const int em = wxGetApp().em_unit(); @@ -1014,6 +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_WORLD_COORDINATE + m_reset_skew_button->msw_rescale(); +#endif /// ENABLE_WORLD_COORDINATE m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); @@ -1053,6 +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_WORLD_COORDINATE + m_reset_skew_button->msw_rescale(); +#endif // ENABLE_WORLD_COORDINATE m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); @@ -1060,6 +1399,19 @@ void ObjectManipulation::sys_color_changed() m_mirror_buttons[id].first->msw_rescale(); } +#if ENABLE_WORLD_COORDINATE +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_WORLD_COORDINATE + static const char axes[] = { 'x', 'y', 'z' }; ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, @@ -1101,8 +1453,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 diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index a15c72fb8..cfa43b43a 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_WORLD_COORDINATE +#include "GUI_Geometry.hpp" +#endif // ENABLE_WORLD_COORDINATE #include "libslic3r/Point.hpp" #include @@ -57,6 +60,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(); }; @@ -113,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_WORLD_COORDINATE + ScalableButton* m_reset_skew_button{ nullptr }; +#endif // ENABLE_WORLD_COORDINATE + ScalableButton* m_drop_to_bed_button{ nullptr }; wxCheckBox* m_check_inch {nullptr}; @@ -144,22 +154,35 @@ private: Vec3d m_new_size; bool m_new_enabled {true}; bool m_uniform_scale {true}; +#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_WORLD_COORDINATE LockButton* m_lock_bnt{ nullptr }; choice_ctrl* m_word_local_combo { nullptr }; 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; +#if ENABLE_WORLD_COORDINATE + wxStaticText* m_skew_label{ nullptr }; +#endif // ENABLE_WORLD_COORDINATE + // sizers, used for msw_rescale wxBoxSizer* m_word_local_combo_sizer; std::vector m_rescalable_sizers; @@ -180,11 +203,19 @@ 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); + 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) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); } bool get_world_coordinates() const { return m_world_coordinates; } +#endif // ENABLE_WORLD_COORDINATE void reset_cache() { m_cache.reset(); } #ifndef __APPLE__ @@ -200,11 +231,23 @@ 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 + +#if ENABLE_WORLD_COORDINATE + static wxString coordinate_type_str(ECoordinatesType type); +#endif // ENABLE_WORLD_COORDINATE + private: void reset_settings_value(); void update_settings_value(const Selection& selection); @@ -220,6 +263,11 @@ 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 + 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/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()) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 57ebd7880..1b6eb8233 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -331,7 +331,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 + do_stop_dragging(is_leaving); +#else for (auto &grabber : m_grabbers) grabber.dragging = false; m_dragging = false; @@ -354,12 +358,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 return true; } } return false; } +#if ENABLE_WORLD_COORDINATE +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 + 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 a3c0eb0bb..0ca0afca8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -89,151 +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); -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/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 4ad4a2daa..a83fcde2c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -122,8 +122,8 @@ void GLGizmoCut::on_render() // 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; + std::abs(diff.y()) > EPSILON || + std::abs(diff.z()) > EPSILON; #endif // !ENABLE_GL_CORE_PROFILE m_old_center = plane_center; @@ -132,7 +132,7 @@ void GLGizmoCut::on_render() 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.color = { 0.8f, 0.8f, 0.8f, 0.5f }; init_data.reserve_vertices(4); init_data.reserve_indices(6); @@ -344,7 +344,7 @@ void GLGizmoCut::perform_cut(const Selection& selection) 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 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) @@ -397,7 +397,7 @@ BoundingBoxf3 GLGizmoCut::bounding_box() const void GLGizmoCut::update_contours() { const Selection& selection = m_parent.get_selection(); - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); + 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()]; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 66b6dcf60..49e97ee1f 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_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_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(); + + 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 026fa11ea..fea23034b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -116,17 +116,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_LEGACY_OPENGL_REMOVAL 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_LEGACY_OPENGL_REMOVAL if (this->is_plane_update_necessary()) @@ -170,17 +170,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_LEGACY_OPENGL_REMOVAL 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_LEGACY_OPENGL_REMOVAL if (this->is_plane_update_necessary()) @@ -226,7 +226,11 @@ void GLGizmoFlatten::update_planes() } ch = ch.convex_hull_3d(); m_planes.clear(); +#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_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 2451cd7de..3485075ec 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,12 +117,16 @@ 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_LEGACY_OPENGL_REMOVAL +#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(); - const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); +#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(); const Transform3d& view_matrix = camera.get_view_matrix(); @@ -235,7 +239,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 c16f93a8a..0fd6fb5e8 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_LEGACY_OPENGL_REMOVAL #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -21,18 +24,29 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filenam std::string GLGizmoMove3D::get_tooltip() const { +#if ENABLE_WORLD_COORDINATE + if (m_hover_id == 0) + return "X: " + format(m_displacement.x(), 2); + else if (m_hover_id == 1) + return "Y: " + format(m_displacement.y(), 2); + else if (m_hover_id == 2) + return "Z: " + format(m_displacement.z(), 2); + else + return ""; +#else 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 ""; +#endif // ENABLE_WORLD_COORDINATE } bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) { @@ -40,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() @@ -79,11 +91,29 @@ void GLGizmoMove3D::on_start_dragging() 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_first_volume(); + 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 { + const GLVolume& v = *selection.get_first_volume(); + m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * 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(2) = box.min(2); + m_starting_box_bottom_center.z() = box.min.z(); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_stop_dragging() @@ -102,7 +132,19 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) m_displacement.z() = calc_projection(data); Selection &selection = m_parent.get_selection(); +#if ENABLE_WORLD_COORDINATE + 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); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_render() @@ -112,11 +154,39 @@ 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)); +#if ENABLE_WORLD_COORDINATE +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPushMatrix()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + calc_selection_box_and_center(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + 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_LEGACY_OPENGL_REMOVAL + + const Vec3d zero = Vec3d::Zero(); + 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 }; + 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 + const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); const Vec3d& center = box.center(); @@ -131,6 +201,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 #if ENABLE_GL_CORE_PROFILE if (!OpenGLManager::get_gl_info().is_core_profile()) @@ -138,10 +209,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; @@ -151,7 +231,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 @@ -175,7 +259,11 @@ void GLGizmoMove3D::on_render() if (shader != nullptr) { shader->start_using(); 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()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -193,7 +281,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()); } @@ -206,6 +298,19 @@ 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) +#if ENABLE_LEGACY_OPENGL_REMOVAL + render_grabber_extension((Axis)i, base_matrix, m_bounding_box, false); +#else + render_grabber_extension((Axis)i, m_bounding_box, false); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#else render_grabbers(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR for (unsigned int i = 0; i < 3; ++i) { @@ -213,6 +318,7 @@ void GLGizmoMove3D::on_render() render_grabber_extension((Axis)i, box, false); } #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#endif // ENABLE_WORLD_COORDINATE } else { // draw axis @@ -226,7 +332,11 @@ void GLGizmoMove3D::on_render() shader->start_using(); 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()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -243,7 +353,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()); @@ -253,20 +367,65 @@ 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); +#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 +#if ENABLE_LEGACY_OPENGL_REMOVAL + 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_LEGACY_OPENGL_REMOVAL +#else render_grabber_extension((Axis)m_hover_id, box, false); +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_GIZMO_GRABBER_REFACTOR } + +#if ENABLE_WORLD_COORDINATE +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPopMatrix()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_WORLD_COORDINATE +#if ENABLE_LEGACY_OPENGL_REMOVAL + 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_LEGACY_OPENGL_REMOVAL + render_grabbers_for_picking(m_bounding_box); +#if ENABLE_LEGACY_OPENGL_REMOVAL +#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_LEGACY_OPENGL_REMOVAL +#else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); render_grabbers_for_picking(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR @@ -274,23 +433,24 @@ void GLGizmoMove3D::on_render_for_picking() render_grabber_extension(Y, box, true); render_grabber_extension(Z, box, true); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#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(); + 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) // 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()); @@ -303,9 +463,14 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const } #if !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE && ENABLE_LEGACY_OPENGL_REMOVAL +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_LEGACY_OPENGL_REMOVAL { - 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 @@ -361,5 +526,62 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box } #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE +#if ENABLE_LEGACY_OPENGL_REMOVAL +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(); + Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) + orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); + 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())); + + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + 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); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +} +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +void GLGizmoMove3D::calc_selection_box_and_center() +{ + const Selection& selection = m_parent.get_selection(); + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + 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(); + 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()); + m_center = v.world_matrix() * 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_volume_transformation().get_matrix())); + } + 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(); + } +} +#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..1fac329ff 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -7,11 +7,19 @@ namespace Slic3r { namespace GUI { +#if ENABLE_WORLD_COORDINATE +class Selection; +#endif // ENABLE_WORLD_COORDINATE + 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() }; @@ -49,7 +57,6 @@ public: /// Detect reduction of move for wipetover on selection change /// void data_changed() override; - protected: bool on_init() override; std::string on_get_name() const override; @@ -62,11 +69,25 @@ protected: private: double calc_projection(const UpdateData& data) const; +#if ENABLE_WORLD_COORDINATE +#if ENABLE_LEGACY_OPENGL_REMOVAL + Transform3d local_transform(const Selection& selection) const; +#else + void transform_to_local(const Selection& selection) const; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + void calc_selection_box_and_center(); +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE && ENABLE_LEGACY_OPENGL_REMOVAL + 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_LEGACY_OPENGL_REMOVAL #endif // !ENABLE_GIZMO_GRABBER_REFACTOR }; + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index d89bb590d..2a24961de 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -324,7 +324,11 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const return; #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#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_WORLD_COORDINATE const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); #if !ENABLE_LEGACY_OPENGL_REMOVAL @@ -539,7 +543,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_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_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); @@ -578,7 +586,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_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_WORLD_COORDINATE // Precalculate transformations of individual meshes. std::vector trafo_matrices; @@ -586,7 +598,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_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_WORLD_COORDINATE } std::vector> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices); @@ -668,7 +684,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_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_WORLD_COORDINATE // Precalculate transformations of individual meshes. std::vector trafo_matrices; @@ -676,7 +696,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_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_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 9caf6cf8b..d938097b8 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) @@ -151,15 +158,21 @@ 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 + 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); @@ -234,10 +247,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_LEGACY_OPENGL_REMOVAL glsafe(::glPopMatrix()); @@ -257,17 +277,73 @@ void GLGizmoRotate::on_render_for_picking() transform_to_local(selection); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#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_LEGACY_OPENGL_REMOVAL glsafe(::glPopMatrix()); #endif // !ENABLE_LEGACY_OPENGL_REMOVAL } +#if ENABLE_WORLD_COORDINATE +void GLGizmoRotate::init_data_from_selection(const Selection& selection) +{ + ECoordinatesType coordinates_type; + if (selection.is_wipe_tower()) + coordinates_type = ECoordinatesType::Local; + else + coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + 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(); + 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()); + m_center = v.world_matrix() * 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_volume_transformation().get_matrix())); + } + 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(); + } + + 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; + m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; + + if (coordinates_type == ECoordinatesType::World) + m_orient_matrix = Transform3d::Identity(); + else if (coordinates_type == ECoordinatesType::Local && (selection.is_wipe_tower() || selection.is_single_volume_or_modifier())) { + const GLVolume& v = *selection.get_first_volume(); + m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); + } + else { + const GLVolume& v = *selection.get_first_volume(); + m_orient_matrix = v.get_instance_transformation().get_rotation_matrix(); + } +} +#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) @@ -328,10 +404,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()); @@ -530,10 +606,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()); @@ -670,12 +746,20 @@ 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)); +#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_WORLD_COORDINATE 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)); +#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_WORLD_COORDINATE break; } default: @@ -686,20 +770,28 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const } } +#if ENABLE_WORLD_COORDINATE + 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_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 } #else 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); + 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 switch (m_axis) { @@ -753,8 +845,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(); + m = m * selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true).inverse(); +#endif // ENABLE_WORLD_COORDINATE m.translate(-m_center); @@ -775,31 +871,51 @@ 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 (mouse_event.AltDown()) transformation_type.set_independent(); +#if ENABLE_WORLD_COORDINATE + TransformationType transformation_type; + if (m_parent.get_selection().is_wipe_tower()) + transformation_type = TransformationType::Instance_Relative_Joint; + else { + 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); } return use_grabbers(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_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_WORLD_COORDINATE m_gizmos[0].disable_grabber(); m_gizmos[1].disable_grabber(); - } else { + } + else { +#if !ENABLE_WORLD_COORDINATE set_rotation(Vec3d::Zero()); +#endif // !ENABLE_WORLD_COORDINATE m_gizmos[0].enable_grabber(); m_gizmos[1].enable_grabber(); } +#if ENABLE_WORLD_COORDINATE + set_rotation(Vec3d::Zero()); +#endif // ENABLE_WORLD_COORDINATE } bool GLGizmoRotate3D::on_init() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index d1689834c..2f66e3433 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -34,6 +34,10 @@ 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 + BoundingBoxf3 m_bounding_box; + Transform3d m_orient_matrix{ Transform3d::Identity() }; +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR GLModel m_cone; @@ -119,6 +123,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 @@ -129,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 8c06743fc..cb526c296 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -2,19 +2,22 @@ #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_LEGACY_OPENGL_REMOVAL #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_LEGACY_OPENGL_REMOVAL #include -#include +#include 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) @@ -38,16 +41,17 @@ GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filen std::string GLGizmoScale3D::get_tooltip() const { +#if ENABLE_WORLD_COORDINATE + const Vec3d scale = 100.0 * m_scale; +#else 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_first_volume()->get_instance_scaling_factor(); + else if (selection.is_single_modifier() || selection.is_single_volume()) + scale = 100.0 * selection.get_first_volume()->get_volume_scaling_factor(); +#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) + "%"; @@ -72,12 +76,28 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) if (mouse_event.Dragging()) { if (m_dragging) { // 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 (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(); - selection.scale(get_scale(), transformation_type); + transformation_type.set_relative(); +#else + TransformationType transformation_type(TransformationType::Local_Absolute_Joint); +#endif // ENABLE_WORLD_COORDINATE + + if (mouse_event.AltDown()) + transformation_type.set_independent(); + +#if ENABLE_WORLD_COORDINATE + 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.CmdDown()) selection.translate(m_offset, true); +#endif // ENABLE_WORLD_COORDINATE } } return use_grabbers(mouse_event); @@ -85,26 +105,29 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) void GLGizmoScale3D::data_changed() { - const Selection &selection = m_parent.get_selection(); - bool enable_scale_xyz = selection.is_single_full_instance() || - selection.is_single_volume() || - selection.is_single_modifier(); +#if ENABLE_WORLD_COORDINATE + set_scale(Vec3d::Ones()); +#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) { // 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()); +#endif // ENABLE_WORLD_COORDINATE } bool GLGizmoScale3D::on_init() @@ -113,15 +136,17 @@ bool GLGizmoScale3D::on_init() m_grabbers.push_back(Grabber()); } +#if !ENABLE_WORLD_COORDINATE 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; +#endif // !ENABLE_WORLD_COORDINATE m_shortcut_key = WXK_CONTROL_S; @@ -142,9 +167,15 @@ 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 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; +#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(); const Vec3d& center = m_starting.box.center(); m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); @@ -153,6 +184,7 @@ void GLGizmoScale3D::on_start_dragging() 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() { @@ -175,78 +207,156 @@ 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)); - m_box.reset(); + m_bounding_box.reset(); +#if ENABLE_WORLD_COORDINATE + m_grabbers_transform = Transform3d::Identity(); + m_center = Vec3d::Zero(); + m_instance_center = Vec3d::Zero(); + if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { +#else 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 (single_instance) { + 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* 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_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } +#if ENABLE_WORLD_COORDINATE + m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_scaling_factor_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(); + const GLVolume& v = *selection.get_first_volume(); +#if ENABLE_WORLD_COORDINATE + 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(); + 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(); + // 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; } - else if (single_volume) { - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); - m_box = v->bounding_box(); - m_transform = v->world_matrix(); + else if (selection.is_single_modifier() || selection.is_single_volume()) { +#endif // ENABLE_WORLD_COORDINATE + const GLVolume& v = *selection.get_first_volume(); +#if ENABLE_WORLD_COORDINATE + 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()); + 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 if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { + const GLVolume& v = *selection.get_first_volume(); + 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()); + 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_first_volume()->get_instance_offset() : m_center; + } +#else + 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()); + 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(); + m_bounding_box = selection.get_bounding_box(); - const Vec3d& center = m_box.center(); + 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); const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); +#endif // ENABLE_WORLD_COORDINATE +#if ENABLE_WORLD_COORDINATE // x axis - m_grabbers[0].center = m_transform * Vec3d(m_box.min.x(), center.y(), center.z()) - offset_x; + 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_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]; + 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]; + + // y axis + 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]; + + // z axis + 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]; + + // uniform + 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 + // x axis + 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_box.max.x(), center.y(), center.z()) + offset_x; + 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_box.min.y(), center.z()) - offset_y; + 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_box.max.y(), center.z()) + offset_y; + 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_box.min.z()) - offset_z; + 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_box.max.z()) + offset_z; + 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_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_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; } @@ -255,15 +365,29 @@ void GLGizmoScale3D::on_render() for (int i = 0; i < 10; ++i) { m_grabbers[i].angles = angles; } +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_CORE_PROFILE if (!OpenGLManager::get_gl_info().is_core_profile()) #endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); - const BoundingBoxf3& selection_box = selection.get_bounding_box(); +#if ENABLE_WORLD_COORDINATE +#if ENABLE_LEGACY_OPENGL_REMOVAL + 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_LEGACY_OPENGL_REMOVAL + 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); +#endif // ENABLE_WORLD_COORDINATE if (m_hover_id == -1) { #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -276,7 +400,11 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); 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()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -331,7 +459,11 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); 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()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -347,7 +479,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 @@ -372,7 +504,11 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); 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()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -388,7 +524,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 @@ -413,7 +549,11 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); 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()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -429,7 +569,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 @@ -454,7 +594,11 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); 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()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -491,12 +635,34 @@ void GLGizmoScale3D::on_render() shader->stop_using(); } } + +#if ENABLE_WORLD_COORDINATE +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPopMatrix()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoScale3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_WORLD_COORDINATE +#if ENABLE_LEGACY_OPENGL_REMOVAL + 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_LEGACY_OPENGL_REMOVAL + render_grabbers_for_picking(m_bounding_box); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPopMatrix()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#else render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); +#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -552,13 +718,67 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int } #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE 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) +{ + const double ratio = calc_ratio(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); + if (m_hover_id == 2 * axis) local_offset *= -1.0; @@ -577,36 +797,86 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) m_offset = Vec3d::Zero(); } } +#endif // ENABLE_WORLD_COORDINATE +#if ENABLE_WORLD_COORDINATE +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) { - 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(); } } +#endif // ENABLE_WORLD_COORDINATE 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(); +#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(); - Vec3d starting_vec = m_starting.drag_position - pivot; - 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) // 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; } @@ -617,5 +887,34 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const return ratio; } +#if ENABLE_WORLD_COORDINATE +#if ENABLE_LEGACY_OPENGL_REMOVAL +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(); + Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) + orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); + 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())); + + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + 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_first_volume()->get_volume_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +} +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#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 f4efe052a..d6bef4475 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -3,31 +3,46 @@ #include "GLGizmoBase.hpp" +#if !ENABLE_WORLD_COORDINATE #include "libslic3r/BoundingBox.hpp" - +#endif // !ENABLE_WORLD_COORDINATE namespace Slic3r { namespace GUI { +#if ENABLE_WORLD_COORDINATE +class Selection; +#endif // ENABLE_WORLD_COORDINATE + 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() }; +#if ENABLE_WORLD_COORDINATE + Vec3d center{ Vec3d::Zero() }; + Vec3d instance_center{ Vec3d::Zero() }; +#endif // ENABLE_WORLD_COORDINATE 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(); } } +#if !ENABLE_WORLD_COORDINATE + std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; +#endif // !ENABLE_WORLD_COORDINATE }; - mutable BoundingBoxf3 m_box; - mutable 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) - mutable Transform3d m_offsets_transform; + Transform3d m_offsets_transform; +#endif // ENABLE_WORLD_COORDINATE Vec3d m_scale{ Vec3d::Ones() }; Vec3d m_offset{ Vec3d::Zero() }; double m_snap_step{ 0.05 }; @@ -54,7 +69,11 @@ public: void set_snap_step(double step) { m_snap_step = step; } const Vec3d& get_scale() const { return m_scale; } +#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_WORLD_COORDINATE std::string get_tooltip() const override; @@ -87,6 +106,13 @@ private: void do_scale_uniform(const UpdateData& data); double calc_ratio(const UpdateData& data) const; +#if ENABLE_WORLD_COORDINATE +#if ENABLE_LEGACY_OPENGL_REMOVAL + Transform3d local_transform(const Selection& selection) const; +#else + void transform_to_local(const Selection& selection) const; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_WORLD_COORDINATE }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index c782b02e0..fd003bba1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -146,9 +146,13 @@ 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_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_WORLD_COORDINATE #if ENABLE_LEGACY_OPENGL_REMOVAL 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(); @@ -360,7 +364,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 35d0c3d39..9e2822dac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -119,7 +119,7 @@ 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(); + m_z_shift = selection.get_first_volume()->get_sla_shift_z(); } else m_model_object = nullptr; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 749ad5f03..eddcfd3a5 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/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 9f66d7c68..083bfad97 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/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp index 58bd1ec32..37b18b3b8 100644 --- a/src/slic3r/GUI/Jobs/PlaterWorker.hpp +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -38,22 +38,18 @@ 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(); + wxWakeUpIdle(); } 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: diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index f9a437883..2e73434a3 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -108,7 +108,11 @@ void MeshClipper::render_cut() void MeshClipper::recalculate_triangles() { +#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_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()); @@ -299,7 +303,11 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo { std::vector out; +#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_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 30c30d2ec..d23a3dd13 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_WORLD_COORDINATE +#include "GUI_Geometry.hpp" +#endif // ENABLE_WORLD_COORDINATE #include "GUI_Factories.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -1514,6 +1517,11 @@ void Sidebar::update_mode() wxWindowUpdateLocker noUpdates(this); +#if ENABLE_WORLD_COORDINATE + if (m_mode == comSimple) + p->object_manipulation->set_coordinates_type(ECoordinatesType::World); +#endif // ENABLE_WORLD_COORDINATE + p->object_list->get_sizer()->Show(m_mode > comSimple); p->object_list->unselect_objects(); @@ -2077,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_WORLD_COORDINATE + view3D_canvas->Bind(EVT_GLCANVAS_RESET_SKEW, [this](SimpleEvent&) { update(); }); +#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); @@ -2922,7 +2933,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; @@ -3484,7 +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_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_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(); @@ -3521,7 +3536,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(); @@ -3839,10 +3854,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_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)); +#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_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); @@ -4448,8 +4469,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(); } } @@ -6030,7 +6055,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 f0e06ecb5..0b2a13040 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -7,9 +7,14 @@ #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 +#include "MsgDialog.hpp" +#endif // ENABLE_WORLD_COORDINATE + +#include "Gizmos/GLGizmoBase.hpp" + #include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/LocalesUtils.hpp" @@ -29,28 +34,24 @@ 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()) +#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_WORLD_COORDINATE { +#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); + 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) @@ -117,6 +118,12 @@ Selection::Selection() , m_scale_factor(1.0f) { this->set_bounding_boxes_dirty(); +#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 } @@ -591,6 +598,7 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } +#if !ENABLE_WORLD_COORDINATE bool Selection::requires_uniform_scale() const { if (is_single_full_instance() || is_single_modifier() || is_single_volume()) @@ -598,6 +606,7 @@ bool Selection::requires_uniform_scale() const return true; } +#endif // !ENABLE_WORLD_COORDINATE int Selection::get_object_idx() const { @@ -642,6 +651,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(); @@ -650,7 +661,11 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const const GLVolume& volume = *(*m_volumes)[i]; if (volume.is_modifier) continue; +#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_WORLD_COORDINATE trafo.translation().z() += volume.get_sla_shift_z(); (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); } @@ -661,6 +676,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(); @@ -669,7 +686,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)); } @@ -678,6 +695,65 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const return *m_scaled_instance_bounding_box; } +#if ENABLE_WORLD_COORDINATE +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_WORLD_COORDINATE + void Selection::setup_cache() { if (!m_valid) @@ -686,6 +762,47 @@ void Selection::setup_cache() set_caches(); } +#if ENABLE_WORLD_COORDINATE +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)); + 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 { + 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)); + } + } + +#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 void Selection::translate(const Vec3d& displacement, bool local) { if (!m_valid) @@ -716,7 +833,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 @@ -725,8 +842,75 @@ void Selection::translate(const Vec3d& displacement, bool local) set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } +#endif // ENABLE_WORLD_COORDINATE // Rotate an object around one of the axes. Only one rotation component is expected to be changing. +#if ENABLE_WORLD_COORDINATE +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]; + const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); + if (m_mode == Instance && !is_wipe_tower()) { + assert(is_from_fully_selected_instance(i)); + 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 * inst_trafo.get_rotation_matrix(); + else if (transformation_type.local()) + new_rotation_matrix = inst_trafo.get_rotation_matrix() * rotation_matrix; + else + assert(false); + } + + 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 { + 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())); + } + else + transform_volume_relative(v, volume_data, transformation_type, Geometry::rotation_transform(rotation)); + } + } + +#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) @@ -769,11 +953,11 @@ 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 - Vec3d new_rotation = transformation_type.world() ? + 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()) { @@ -781,9 +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)); } - 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; } @@ -802,14 +983,13 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ v.set_volume_rotation(new_rotation); } } - else - { + else { if (m_mode == Instance) 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); @@ -821,12 +1001,12 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } } - #if !DISABLE_INSTANCES_SYNCH +#if !DISABLE_INSTANCES_SYNCH 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); 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 @@ -834,7 +1014,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::UnitZ()) * center_local; volume.set_volume_rotation(rotation); volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); } @@ -842,6 +1022,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } +#endif // ENABLE_WORLD_COORDINATE void Selection::flattening_rotate(const Vec3d& normal) { @@ -856,25 +1037,39 @@ 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_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. + 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(), 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_WORLD_COORDINATE } #if !DISABLE_INSTANCES_SYNCH // 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(); } +#if ENABLE_WORLD_COORDINATE +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) @@ -884,10 +1079,10 @@ 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)); @@ -907,22 +1102,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); @@ -932,7 +1127,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 @@ -941,6 +1136,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } +#endif // ENABLE_WORLD_COORDINATE void Selection::scale_to_fit_print_volume(const BuildVolume& volume) { @@ -963,7 +1159,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_WORLD_COORDINATE + TransformationType trafo_type; + trafo_type.set_relative(); + translate(offset, trafo_type); +#else translate(offset); +#endif // ENABLE_WORLD_COORDINATE wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); @@ -1045,7 +1247,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 @@ -1053,6 +1255,137 @@ void Selection::mirror(Axis axis) set_bounding_boxes_dirty(); } +#if ENABLE_WORLD_COORDINATE +void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type) +{ + 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 (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()) + 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(); + } + + 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 + // 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 + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(relative_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(); +} + +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]; + 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 { + // [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); + } + } + } + + 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) @@ -1101,6 +1434,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) this->set_bounding_boxes_dirty(); } +#endif // ENABLE_WORLD_COORDINATE void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) { @@ -1110,7 +1444,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_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_WORLD_COORDINATE } std::set done; // prevent processing volumes twice @@ -1143,7 +1481,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_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_WORLD_COORDINATE done.insert(j); } } @@ -1151,6 +1493,59 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co this->set_bounding_boxes_dirty(); } +#if ENABLE_WORLD_COORDINATE +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())) { + // 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_first_volume(); + 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; + + 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 + void Selection::erase() { if (!m_valid) @@ -1264,7 +1659,34 @@ 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_WORLD_COORDINATE + 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_first_volume(); + 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 { + 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())); + } + 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(); + } + + render_bounding_box(box, trafo, ColorRGB::WHITE()); +#else render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); +#endif // ENABLE_WORLD_COORDINATE #else render_selected_volumes(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -1345,16 +1767,30 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) glsafe(::glPushMatrix()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE + const Vec3d center = get_bounding_box().center(); + Vec3d axes_center = center; +#endif // ENABLE_WORLD_COORDINATE + if (!boost::starts_with(sidebar_field, "layer")) { #if ENABLE_LEGACY_OPENGL_REMOVAL shader->set_uniform("emission_factor", 0.05f); -#else - const Vec3d& center = get_bounding_box().center(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if !ENABLE_LEGACY_OPENGL_REMOVAL&& !ENABLE_WORLD_COORDINATE + const Vec3d& center = get_bounding_box().center(); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL&& !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()) { -#if !ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_LEGACY_OPENGL_REMOVAL&& !ENABLE_WORLD_COORDINATE glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL&& !ENABLE_WORLD_COORDINATE +#if ENABLE_WORLD_COORDINATE + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); + axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); +#else if (!boost::starts_with(sidebar_field, "position")) { #if !ENABLE_LEGACY_OPENGL_REMOVAL Transform3d orient_matrix = Transform3d::Identity(); @@ -1376,8 +1812,29 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) glsafe(::glMultMatrixd(orient_matrix.data())); #endif // !ENABLE_LEGACY_OPENGL_REMOVAL } +#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_LEGACY_OPENGL_REMOVAL&& !ENABLE_WORLD_COORDINATE + glsafe(::glTranslated(center.x(), center.y(), center.z())); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL&& !ENABLE_WORLD_COORDINATE +#if ENABLE_WORLD_COORDINATE + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + if (wxGetApp().obj_manipul()->is_local_coordinates()) { + const GLVolume* v = (*m_volumes)[*m_list.begin()]; + orient_matrix = v->get_instance_transformation().get_rotation_matrix() * v->get_volume_transformation().get_rotation_matrix(); + axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation(); + } + else { + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); + axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); + } + } +#else #if ENABLE_LEGACY_OPENGL_REMOVAL orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #else @@ -1390,25 +1847,43 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if !ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glMultMatrixd(orient_matrix.data())); #endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_WORLD_COORDINATE } else { -#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_LEGACY_OPENGL_REMOVAL|| ENABLE_WORLD_COORDINATE if (requires_local_axes()) +#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_WORLD_COORDINATE #else glsafe(::glTranslated(center.x(), center.y(), center.z())); if (requires_local_axes()) { 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_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_LEGACY_OPENGL_REMOVAL|| 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 + if (!boost::starts_with(sidebar_field, "layer")) { + shader->set_uniform("emission_factor", 0.1f); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPushMatrix()); + glsafe(::glTranslated(center.x(), center.y(), center.z())); + glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + } +#endif // ENABLE_WORLD_COORDINATE + +#if ENABLE_LEGACY_OPENGL_REMOVAL if (boost::starts_with(sidebar_field, "position")) render_sidebar_position_hints(sidebar_field, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "rotation")) @@ -1417,6 +1892,13 @@ 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()) + m_axes.render(Geometry::assemble_transform(axes_center) * orient_matrix, 0.25f); + } +#endif // ENABLE_WORLD_COORDINATE #else if (boost::starts_with(sidebar_field, "position")) render_sidebar_position_hints(sidebar_field); @@ -1427,8 +1909,26 @@ 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 + 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 +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPopMatrix()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_WORLD_COORDINATE + +#if !ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) #endif // ENABLE_LEGACY_OPENGL_REMOVAL shader->stop_using(); @@ -1510,8 +2010,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); } @@ -1523,10 +2022,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); } @@ -1540,9 +2038,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); } } @@ -1554,8 +2051,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); @@ -1568,8 +2064,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); } @@ -1587,8 +2082,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(); @@ -1607,23 +2101,19 @@ void Selection::update_type() { 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; @@ -1634,15 +2124,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) { @@ -1657,25 +2145,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; } @@ -1688,25 +2171,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; @@ -1717,8 +2196,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; } @@ -1884,6 +2362,12 @@ void Selection::render_synchronized_volumes() float color[3] = { 1.0f, 1.0f, 0.0f }; #endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + BoundingBoxf3 box; + Transform3d trafo; +#endif // ENABLE_WORLD_COORDINATE + for (unsigned int i : m_list) { const GLVolume& volume = *(*m_volumes)[i]; int object_idx = volume.object_idx(); @@ -1897,7 +2381,23 @@ void Selection::render_synchronized_volumes() continue; #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE + 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_WORLD_COORDINATE #else render_bounding_box(v.transformed_convex_hull_bounding_box(), color); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -1906,7 +2406,11 @@ void Selection::render_synchronized_volumes() } #if ENABLE_LEGACY_OPENGL_REMOVAL +#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_WORLD_COORDINATE { #else void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const @@ -1925,6 +2429,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(); @@ -2016,9 +2521,20 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con if (shader == nullptr) return; +#if ENABLE_WORLD_COORDINATE +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo.data())); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_WORLD_COORDINATE + shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); +#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_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -2029,6 +2545,12 @@ 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_WORLD_COORDINATE +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPopMatrix()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_WORLD_COORDINATE #else ::glBegin(GL_LINES); @@ -2189,7 +2711,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_LEGACY_OPENGL_REMOVAL { +#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_WORLD_COORDINATE #if ENABLE_LEGACY_OPENGL_REMOVAL auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& view_matrix, const Transform3d& model_matrix) { @@ -2438,22 +2964,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_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(); + 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_WORLD_COORDINATE // Process unselected instances. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -2463,29 +2997,68 @@ 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_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(); + 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_WORLD_COORDINATE + 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())); +#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) - 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_WORLD_COORDINATE break; } - case SYNC_ROTATION_GENERAL: + case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. +#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()); - 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_WORLD_COORDINATE 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(curr_inst_rotation_i, old_inst_rotation_j)); + 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(curr_inst_rotation_i, old_inst_rotation_j); + + new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); + break; + } +#endif // ENABLE_WORLD_COORDINATE + } - v->set_instance_scaling_factor(scaling_factor); - v->set_instance_mirror(mirror); +#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_WORLD_COORDINATE done.insert(j); } @@ -2512,10 +3085,14 @@ void Selection::synchronize_unselected_volumes() #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL const int volume_idx = volume->volume_idx(); +#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_WORLD_COORDINATE // Process unselected volumes. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -2526,10 +3103,14 @@ void Selection::synchronize_unselected_volumes() if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) continue; +#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_WORLD_COORDINATE } } } @@ -2644,8 +3225,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_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_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 @@ -2723,5 +3309,27 @@ void Selection::paste_objects_from_clipboard() #endif /* _DEBUG */ } +#if ENABLE_WORLD_COORDINATE +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_WORLD_COORDINATE + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index e77ca8715..cebea29e0 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -2,7 +2,12 @@ #define slic3r_GUI_Selection_hpp_ #include "libslic3r/Geometry.hpp" +#if ENABLE_WORLD_COORDINATE +#include "GUI_Geometry.hpp" +#include "CoordAxes.hpp" +#else #include "GLModel.hpp" +#endif // ENABLE_WORLD_COORDINATE #include #include @@ -24,6 +29,7 @@ using ModelObjectPtrs = std::vector; namespace GUI { +#if !ENABLE_WORLD_COORDINATE class TransformationType { public: @@ -76,6 +82,7 @@ private: Enum m_value; }; +#endif // !ENABLE_WORLD_COORDINATE class Selection { @@ -110,16 +117,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_WORLD_COORDINATE + Geometry::Transformation transform; +#endif // ENABLE_WORLD_COORDINATE - TransformCache(); + TransformCache() = default; explicit TransformCache(const Geometry::Transformation& transform); }; @@ -131,13 +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_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_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_WORLD_COORDINATE + const Geometry::Transformation& get_volume_transform() const { return m_volume.transform; } +#endif // ENABLE_WORLD_COORDINATE const Vec3d& get_instance_position() const { return m_instance.position; } const Vec3d& get_instance_rotation() const { return m_instance.rotation; } @@ -147,6 +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_WORLD_COORDINATE + const Geometry::Transformation& get_instance_transform() const { return m_instance.transform; } +#endif // ENABLE_WORLD_COORDINATE }; public: @@ -207,15 +225,32 @@ 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_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; + // 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_WORLD_COORDINATE #if ENABLE_RENDER_SELECTION_CENTER GLModel m_vbo_sphere; #endif // ENABLE_RENDER_SELECTION_CENTER +#if ENABLE_WORLD_COORDINATE + CoordAxes m_axes; +#endif // ENABLE_WORLD_COORDINATE GLModel m_arrow; GLModel m_curved_arrow; #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -290,6 +325,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 @@ -299,7 +337,18 @@ public: // returns true if the selection contains all and only the given indices bool matches(const std::vector& volume_idxs) const; +#if ENABLE_WORLD_COORDINATE + enum class EUniformScaleRequiredReason : unsigned char + { + NotRequired, + InstanceNotAxisAligned_World, + VolumeNotAxisAligned_World, + VolumeNotAxisAligned_Instance, + MultipleSelection, + }; +#else bool requires_uniform_scale() const; +#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; @@ -311,28 +360,60 @@ 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; } 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_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; + // 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_WORLD_COORDINATE void setup_cache(); +#if ENABLE_WORLD_COORDINATE + void translate(const Vec3d& displacement, TransformationType transformation_type); +#else void translate(const Vec3d& displacement, bool local = false); +#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); void scale_to_fit_print_volume(const BuildVolume& volume); void mirror(Axis axis); - +#if ENABLE_WORLD_COORDINATE + void scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type); + void reset_skew(); +#else void translate(unsigned int object_idx, const Vec3d& displacement); +#endif // ENABLE_WORLD_COORDINATE void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); +#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 + void erase(); void render(float scale_factor = 1.0); @@ -368,10 +449,25 @@ 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_WORLD_COORDINATE + 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_WORLD_COORDINATE void render_synchronized_volumes(); #if ENABLE_LEGACY_OPENGL_REMOVAL +#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_WORLD_COORDINATE + void render_selected_volumes() const; + void render_bounding_box(const BoundingBoxf3& box, float* color) const; void render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix); void render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix); void render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix); @@ -386,11 +482,15 @@ private: #endif // ENABLE_LEGACY_OPENGL_REMOVAL 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(); @@ -402,6 +502,11 @@ private: void paste_volumes_from_clipboard(); void paste_objects_from_clipboard(); + +#if ENABLE_WORLD_COORDINATE + void transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, + const Transform3d& transform); +#endif // ENABLE_WORLD_COORDINATE }; } // namespace GUI diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index dc177ade3..876398510 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 + SetLock(!m_is_pushed); +#else m_is_pushed = !m_is_pushed; update_button_bitmaps(); +#endif // ENABLE_WORLD_COORDINATE event.Skip(); } diff --git a/tests/libslic3r/test_astar.cpp b/tests/libslic3r/test_astar.cpp index f673ad9fe..867f5be47 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); + } } 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 ()