diff options
62 files changed, 2297 insertions, 1303 deletions
diff --git a/resources/localization/fr/Slic3r.po b/resources/localization/fr/Slic3r.po index 89374fb6e..007aff0b0 100644 --- a/resources/localization/fr/Slic3r.po +++ b/resources/localization/fr/Slic3r.po @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: Slic3r\n" -"POT-Creation-Date: 2021-09-06 00:00\n" -"PO-Revision-Date: 2021-09-06 00:00\n" +"POT-Creation-Date: 2021-11-06 00:00\n" +"PO-Revision-Date: 2021-11-06 00:00\n" "Last-Translator: 5axes, supermerill\n" "Language-Team: fr\n" "MIME-Version: 1.0\n" @@ -550,11 +550,13 @@ msgstr "" msgid "" "Add a M106 S255 (max speed for fan) for this amount of seconds before going " "down to the desired speed to kick-start the cooling fan.\n" +"This value is used for a 0->100% speedup, it will go down if the delta is " +"lower.\n" "Set to 0 to deactivate." msgstr "" -"Ajoutez un M106 S255 (vitesse maximale du ventilateur) pendant ce nombre de secondes avant de passer " -"à la vitesse désirée pour démarrer le ventilateur de refroidissement.\n" -"Réglez à 0 pour désactiver." +"Ajoutez un M106 S255 (vitesse maximale du ventilateur) pendant ce nombre de secondes avant de passer à la vitesse désirée pour démarrer le ventilateur de refroidissement." +"\nCette valeur est utilisée lors du passage de 0 à 100% du ventilateur, cela durera moins longtemps si le delta est plus faible." +"\nRéglez à 0 pour désactiver." msgid "Add a pad underneath the supported model" msgstr "Ajouter une base sous le modèle supporté" @@ -711,8 +713,10 @@ msgstr "Ajouter une forme" msgid "" "Add solid infill near sloping surfaces to guarantee the vertical shell " -"thickness (top+bottom solid layers)." -msgstr "Ajouter un remplissage plein à proximité des surfaces inclinées pour garantir une épaisseur de coque verticale (couches pleines du dessus+du dessous)." +"thickness (top+bottom solid layers).\n" +"!! solid_over_perimeters may erase these surfaces !! So you should " +"deactivate it if you want to use this." +msgstr "Ajouter un remplissage plein à proximité des surfaces inclinées pour garantir une épaisseur de coque verticale (couches pleines supérieures+inférieures).\n!! solid_over_perimeters peut supprimer ces surfaces !! Vous ne devriez pas utiliser les deux options en même temps." msgid "Add support blocker" msgstr "Ajouter un bloqueur de support" @@ -856,11 +860,9 @@ msgstr "" "expression rationnelle regexp (entrez '[^a-zA-Z]' pour utiliser uniquement les caractères ascii)." msgid "" -"All gaps between the alst periemter and the infill which are thinner than a " -"perimeter will be filled by gapfill." -msgstr "" -"Tous les espaces entre le dernier périmère et le remplissage qui sont plus minces qu'un " -"périmètre seront remplis par du remplissage." +"All gaps, between the last perimeter and the infill, which are thinner than " +"a perimeter will be filled by gapfill." +msgstr "Tous les espaces entre le dernier périmère et le remplissage qui sont plus minces qu'un périmètre seront remplis par du remplissage." msgid "All gizmos: Rotate - left mouse button; Pan - right mouse button" msgstr "" @@ -1029,6 +1031,9 @@ msgstr "Clé API" msgid "API Key / Password" msgstr "Clé API / Mot de Passe" +msgid "Appearance" +msgstr "Apparence" + msgid "Application preferences" msgstr "Préférences de l'application" @@ -1150,6 +1155,9 @@ msgstr "Associe les fichiers .gcode à %1%" msgid "Associate .stl files to %1%" msgstr "Associe les fichiers .stl à %1%" +msgid "at %1%%% over all bridges" +msgstr "à %1%%% sur tous les ponts" + msgid "at %1%%% over bridges" msgstr "à %1%%% au-dessus des ponts" @@ -1797,6 +1805,12 @@ msgstr "Couleur d'impression" msgid "Colorprint height" msgstr "Hauteur de l'impression couleur" +msgid "Colors" +msgstr "Couleurs" + +msgid "Colour Change G-code" +msgstr "G-code de changement de couleur" + msgid "Combine infill every" msgstr "Combiner le remplissage toutes les" @@ -1991,6 +2005,9 @@ msgstr "" "Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik et " "de nombreux autres." +msgid "Controls" +msgstr "Contrôles" + msgid "" "Controls the bridge type between two neighboring pillars. Can be zig-zag, " "cross (double zig-zag) or dynamic which will automatically switch between " @@ -2333,6 +2350,9 @@ msgstr "Couleur par défaut" msgid "default color" msgstr "couleur par défaut" +msgid "Default distance between objects" +msgstr "Distance par défaut entre les objets" + msgid "" "Default distance used for the auto-arrange feature of the plater.\n" "Set to 0 to use the last value instead." @@ -2388,9 +2408,6 @@ msgstr "Profil par défaut du matériau SLA" msgid "default SLA print profile" msgstr "profil d'impression SLA par défaut" -msgid "Default speed" -msgstr "Vitesse par défaut" - msgid "default value" msgstr "Valeur par défaut" @@ -2596,9 +2613,6 @@ msgid "" "center." msgstr "Diamètre du plateau d'impression. Il est supposé que l'origine (0,0) est située au centre." -msgid "Did you forgot to put a '%' in the " -msgstr "As-tu oublié de mettre un '%' dans le " - msgid "Dimension:" msgstr "Dimension:" @@ -2648,6 +2662,9 @@ msgstr "Miroir d'affichage" msgid "Display orientation" msgstr "Orientation de l'affichage" +msgid "Display setting icons" +msgstr "Afficher les icônes des réglages" + msgid "Display the Print Host Upload Queue window" msgstr "Afficher la fenêtre de la File d'Attente de Téléchargement de l'Hôte d'Impression" @@ -2663,9 +2680,6 @@ msgstr "Distance" msgid "Distance between ironing lines" msgstr "Distance entre les lignes de lissage" -msgid "Distance between objects" -msgstr "Distance entre les objets" - msgid "" "Distance between skirt and object(s). Set this to zero to attach the skirt " "to the object(s) and get a brim for better adhesion." @@ -2879,17 +2893,10 @@ msgid "" "print the object without elevation." msgstr "L'Élévation est trop basse pour cet objet. utilisez la fonction \"Socle autour de l'objet\" pour imprimer l'objet sans élévation." -msgid "else from object" -msgstr "sinon à partir de l'objet" - msgid "" -"Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute " -"intervals into the G-code to let the firmware show accurate remaining time. " -"As of now only the Prusa i3 MK3 firmware recognizes M73. Also the i3 MK3 " -"firmware supports M73 Qxx Sxx for the silent mode." -msgstr "" -"Émet M73 P[pourcentage imprimé] R[temps restant en minutes] à 1 minute d'intervalle dans le G-Code afin que le firmware puisse indiquer précisément le temps restant." -" Jusqu'à présent seul le firmware Prusa i3 MK3 reconnait M73. Par ailleurs le firmware i3 MK3 supporte M73 Qxx Sxx pour le mode silencieux." +"Emit something at 1 minute intervals into the G-code to let the firmware " +"show accurate remaining time." +msgstr "Emmetre quelquechose à une minute d'intervalle dan le G-code pour permettre au Firmware d'afficher précisément le temps d'impression restant" msgid "Empty layers detected, the output would not be printable." msgstr "Couches vides détectées, la sortie ne serait pas imprimable." @@ -3539,6 +3546,9 @@ msgstr "Périmètres supplémentaires au-dessus des surplombs" msgid "Extra skirt lines on the first layer." msgstr "Lignes de bordure supplémentaires sur la première couche." +msgid "Extra unretraction" +msgstr "Non-retrait supplémentaire" + msgid "Extra Wipe for external perimeters" msgstr "Essuyage Extra pour les périmètres extérieurs" @@ -4188,8 +4198,8 @@ msgstr "Vitesse de remplissage des lacunes" msgid "Gapfill after last perimeter" msgstr "Remplissage après le dernier périmètre" -msgid "Gcode" -msgstr "G-Code" +msgid "Gcode done" +msgstr "G-Code fait" msgid "GCode Pre&view Tab" msgstr "Onglet Pré&Visualisation G-Code" @@ -4889,43 +4899,24 @@ msgid "If enabled, you can change size of toolbar icons manually." msgstr "Si activé, vous pouvez changer la taille des icônes de la barre d'outils manuellement." msgid "" -"If expressed as absolute value in mm/s, this speed will be applied to all " -"the print moves but infill of the first layer, it can be overwritten by the " -"'default' (default depends of the type of the path) speed if it's lower than " -"that. If expressed as a percentage it will scale the current speed.\n" -"Set it at 100% to remove any first layer speed modification (with " -"first_layer_infill_speed and first_layer_speed_min)." +"If expressed as absolute value in mm/s, this speed will be applied as a " +"maximum for all infill print moves of the first layer.\n" +"If expressed as a percentage it will scale the current infill speed.\n" +"Set it at 100% to remove any infill first layer speed modification." msgstr "" -"Si elle est exprimée en valeur absolue en mm/s, cette vitesse sera appliquée à tous les " -"les mouvements d'impression sauf le remplissage de la première couche, elle peut être écrasée par la vitesse " -"'par défaut' (la valeur par défaut dépend du type de trajectoire) si elle est inférieure à celle-ci. " -"Si elle est exprimée en pourcentage, elle sera mise à l'échelle de la vitesse actuelle.\n" -"Définissez-la à 100% pour supprimer toute modification de la vitesse de la première couche (avec " -"first_layer_infill_speed et first_layer_speed_min)." +"Si elle est exprimée en valeur absolue en mm/s, cette vitesse sera appliquée aux remplissages de la première couche.\n" +"Si elle est exprimée en pourcentage, elle sera utilisé en tant que ratio de la vitesse qui aurait normalement utilisé pour le remplissage.\n" +"Utiliser une valeur de 100% supprimera toute modification de la vitesse d'impression des remplissage de la première couche." msgid "" -"If expressed as absolute value in mm/s, this speed will be applied to all " -"the print moves, it can be overwritten by the 'default' (default depends of " -"the type of the path) speed if it's higher than that. If expressed as a " -"percentage it will scale the current speed.\n" -"Set zero to disable." +"If expressed as absolute value in mm/s, this speed will be applied as a " +"maximum to all the print moves (but infill) of the first layer.\n" +"If expressed as a percentage it will scale the current speed.\n" +"Set it at 100% to remove any first layer speed modification (but for infill)." msgstr "" -"Si elle est exprimée en valeur absolue en mm/s, cette vitesse sera appliquée à tous " -"les mouvements d'impression, elle peut être écrasée par la vitesse 'par défaut' (la valeur par défaut dépend du " -"le type de trajectoire) si elle est supérieure à celle-ci. Si elle est exprimée en " -"pourcentage, elle sera mise à l'échelle de la vitesse actuelle.\n" -"Définir à zéro pour désactiver." - -msgid "" -"If expressed as absolute value in mm/s, this speed will be applied to infill " -"moves of the first layer, it can be overwritten by the 'default' (solid " -"infill or infill if not bottom) speed if it's lower than that. If expressed " -"as a percentage (for example: 40%) it will scale the current infill speed." -msgstr "" -"Si elle est exprimée en valeur absolue en mm/s, cette vitesse sera appliquée aux déplacements du remplissage " -"de la première couche, elle peut être écrasée par la vitesse 'par défaut' (remplissage plein " -"ou remplissage si pas de fond) si elle est inférieure à cette vitesse.\n" -"Si elle est exprimée en pourcentage (par exemple : 40%), elle mettra à l'échelle la vitesse de remplissage actuelle." +"Si elle est exprimée en valeur absolue en mm/s, cette vitesse sera appliquée aux extrusions de la première couche (sauf les remplissages).\n" +"Si elle est exprimée en pourcentage, elle sera utilisé en tant que ratio de la vitesse qui aurait normalement utilisé.\n" +"Utiliser une valeur de 100% supprimera toute modification de la vitesse d'impression de la première couche (sauf les remplissages)." msgid "" "If it point to a valid freecad instance (the bin directory or the python " @@ -5067,17 +5058,19 @@ msgid "" "If your firmware stops while printing, it may have its gcode queue full. Set " "this parameter to merge extrusions into bigger ones to reduce the number of " "gcode commands the printer has to process each second.\n" +"On 8bit controlers, a value of 150 is typical.\n" "Note that reducing your printing speed (at least for the external " "extrusions) will reduce the number of time this will triggger and so " "increase quality.\n" "Set zero to disable." msgstr "" "Si votre firmware s'arrête pendant l'impression, il se peut que sa file d'attente de gcode soit pleine. Définissez " -"ce paramètre pour fusionner les extrusions en de plus grandes afin de réduire le nombre de " -"commandes de gcode que l'imprimante doit traiter chaque seconde.\n" -"Notez que la réduction de la vitesse d'impression (au moins pour les extrusions externes)" -" réduira le nombre de fois où cela se déclenchera et donc augmentera la qualité." -"Mettez à zéro pour désactiver." +"ce paramètre pour fusionner les extrusions en de plus grandes afin de réduire le nombre de commandes de gcode que " +"l'imprimante doit traiter chaque seconde.\n" +"Notez que la réduction de la vitesse d'impression (au moins pour les extrusions externes) réduira le nombre de fois " +"où cela se déclenchera et donc augmentera la qualité.\n" +"Une valeur de 150 est typique pour un contrôleur 8 bit.\n" +"Mettre zéro pour désactiver." msgid "Ignore non-existent config files" msgstr "Ignorer les fichiers de configuration non-existants" @@ -5130,6 +5123,12 @@ msgstr "Échec de l'import du fichier 3mf réparé" msgid "Import profile only" msgstr "Importer le profil uniquement" +msgid "Import Prusa Config" +msgstr "Importer une Configuration de Prusa" + +msgid "Import Prusa Config Bundle" +msgstr "Importer un Lot de Configuration de Prusa" + msgid "Import SL1 / SL1S archive" msgstr "Importation des archives SL1 / SL1S" @@ -5323,6 +5322,9 @@ msgstr "Création d'interface entre coques" msgid "Interior Brim width" msgstr "Largeur de bordure intérieure" +msgid "Internal" +msgstr "Interne" + msgid "Internal bridge infill" msgstr "Remplissage des ponts intérieurs" @@ -5341,14 +5343,16 @@ msgstr "Remplissage interne" msgid "Internal perimeter" msgstr "Périmètre interne" +msgid "Internal perimeters speed" +msgstr "Vitesse des périmètres internes" + msgid "" "Internal perimeters will go around sharp corners by turning around instead " "of making the same sharp corner. This can help when there are visible holes " -"in sharp corners on perimeters" -msgstr "" -"Les périmètres internes contourneront les angles aigus en créant un rayon au lieu " -"de faire un angle aigu. Cela peut aider quand il y a des trous visibles " -"dans les coins aigus des périmètres" +"in sharp corners on perimeters. It also help to print the letters on the " +"benchy stern.\n" +"Can incur some more processing time, and corners are a bit less sharp." +msgstr "Les périmètres contourneront les coins en tournant autour au lieu de répliquer la gaométrie de l'object. Cela peut aider à supprimer les petits trous entre les périmètres que l'on peut voir aux coins. Cela peut aussi aider à imprimer les lettres au derrière du Benchy." msgid "Introduction" msgstr "Introduction" @@ -5399,8 +5403,8 @@ msgstr "Angle de lissage. S'il est négatif, il utilisera l'angle de remplissage msgid "Ironing flow distribution" msgstr "Distribution du débit de lissage" -msgid "Ironing infill tuning" -msgstr "Réglage de remplissage pour le lissage" +msgid "Ironing infill pattern tuning" +msgstr "Réglage de remplissage lissé" msgid "Ironing pattern calibration" msgstr "Calibration du débit de lissage" @@ -5600,6 +5604,9 @@ msgstr "Couches et périmètres" msgid "Layout Options" msgstr "Options de disposition" +msgid "Layout with the tab bar" +msgstr "Interface avec la barre d'onglets" + msgid "Leaving Paint-on supports" msgstr "Laisser les Supports peints" @@ -5621,6 +5628,9 @@ msgstr "Bouton gauche de souris :" msgid "Left View" msgstr "Vue Gauche" +msgid "Legacy layout" +msgstr "Interface historique" + msgid "Legend" msgstr "Légende" @@ -5771,6 +5781,9 @@ msgstr "Charger le fichier de configuration" msgid "Load Config from ini/amf/3mf/gcode and merge" msgstr "Charger une configuration à partir d'un ini/amf/3mf/gcode et fusionner" +msgid "Load configuration file exported from PrusaSlicer" +msgstr "Charge un fichier de configuration exporté de PrusaSlicer" + msgid "Load configuration from project file" msgstr "Charger la configuration depuis le fichier du projet" @@ -5796,6 +5809,9 @@ msgstr "Charger une Pièce" msgid "Load presets from a bundle" msgstr "Charger les préréglages à partir d'un lot" +msgid "Load presets from a PrusaSlicer bundle" +msgstr "Charge les différentes configurations de réglages issues d'un fichier de Lot de Configuration de PrusaSlicer" + msgid "Load Project" msgstr "Charger le Projet" @@ -5877,6 +5893,21 @@ msgstr "le plus bas Y" msgid "lowest Z" msgstr "le plus bas Z" +msgid "M117" +msgstr "M117" + +msgid "M73" +msgstr "M73" + +msgid "" +"M73: Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute " +"intervals into the G-code to let the firmware show accurate remaining time. " +"As of now only the Prusa i3 MK3 firmware recognizes M73. Also the i3 MK3 " +"firmware supports M73 Qxx Sxx for the silent mode.\n" +"M117: Send a command to display a message to the printer, this is 'Time " +"Left .h..m..s'." +msgstr "M73: Émet M73 P[pourcentage imprimé] R[temps restant en minutes] à 1 minute d'intervalle dans le G-Code afin que le firmware puisse indiquer précisément le temps restant. Jusqu'à présent seul le firmware Prusa i3 MK3 reconnait M73. Par ailleurs le firmware i3 MK3 supporte M73 Qxx Sxx pour le mode silencieux.\nM117: Emet une comande pour afficher un message à l'imprimante, du type 'Time Left .h..m..s'." + msgid "Machine limits" msgstr "Limites de la machine" @@ -5979,15 +6010,18 @@ msgstr "Vitesse maximale du ventilateur" msgid "Max layer height" msgstr "Hauteur de couche maximum" +msgid "Max layer height can't be greater than nozzle diameter" +msgstr "La hauteur maximale de la couche ne doit pas être supérieure au diamètre de la buse." + msgid "Max length" msgstr "Longueur maximale" +msgid "Max line overlap" +msgstr "Chevauchement maximal des lignes" + msgid "Max merge distance" msgstr "Distance maximum de fusion" -msgid "Max perimeters layer for solid infill" -msgstr "Couche de périmètre maximale pour le remplissage plein" - msgid "Max pillar linking distance" msgstr "Distance maximum de jonction de pilier" @@ -6231,6 +6265,9 @@ msgid "Message for pause print on current layer (%1% mm)." msgstr "" "Message pour mettre en pause l'impression sur la couche en cours (%1% mm)." +msgid "Method" +msgstr "Méthode" + msgid "Mill" msgstr "Fraisage" @@ -6276,9 +6313,15 @@ msgstr "Vitesse minimale de la première couche" msgid "Min gap-fill surface" msgstr "Surface minimale de remplissage des espaces" +msgid "Min height for travel" +msgstr "Hauteur min pour les déplacements" + msgid "Min layer height" msgstr "Hauteur de couche minimum" +msgid "Min layer height can't be greater than Max layer height" +msgstr "La hauteur minimale de la couche ne peut pas être supérieure à la hauteur maximale de la couche." + msgid "Min length" msgstr "Longueur minimale" @@ -6357,9 +6400,17 @@ msgstr "Vitesses minimums" msgid "Minimum initial exposure time" msgstr "Temps d'exposition initiale minimum" +msgid "Minimum retraction" +msgstr "Retrait minimum" + msgid "Minimum shell thickness" msgstr "Épaisseur de coque minimale" +msgid "" +"Minimum speed when printing the first layer.\n" +"Set zero to disable." +msgstr "Vitesse minimum lorsque l'on imprime la première couche.\nMettre 0 pour désactiver." + msgid "Minimum thickness of a top / bottom shell" msgstr "Épaisseur minimale d'une coque supérieure / inférieure" @@ -6625,12 +6676,19 @@ msgstr "Déplacer un point de support" msgid "" "Move the fan start in the past by at least this delay (in seconds, you can " "use decimals). It assumes infinite acceleration for this time estimation, " -"and will only take into account G1 and G0 moves. Use 0 to deactivate." -msgstr "" -"Anticipez le démarrage du ventilateur d'au moins ce délai (en secondes, vous pouvez " -"utiliser des décimales). Il suppose une accélération infinie pour cette estimation de temps " +"and will only take into account G1 and G0 moves.\n" +"It won't move fan comands from custom gcodes (they act as a sort of " +"'barrier').\n" +"It won't move fan comands into the start gcode if the 'only custom start " +"gcode' is activated.\n" +"Use 0 to deactivate." +msgstr "" +"Déplacez le démarrage du ventilateur dans le passé d'au moins ce délai (en secondes, vous pouvez " +"utiliser des décimales). Il suppose une accélération infinie pour cette estimation de temps, " "et ne prendra en compte que les déplacements G1 et G0.\n" -"Utilisez 0 pour désactiver." +"Il ne déplacera pas les commandes mises dans un champ de G-code personalisé (elle agissenta lors en tant que 'barrière').\n" +"Les commndes ne seront pas déplacé dans le G-code de démarrage si 'Uniquement sur G-Code de démarrage personnalisé' est sélectionné.\n" +"Mettre 0 pour désactiver." msgid "Movement" msgstr "Mouvement" @@ -6769,6 +6827,12 @@ msgstr "Aucun fichier précédemment découpé." msgid "NO RAMMING AT ALL" msgstr "PAS D'EXPULSION DU TOUT" +msgid "No solid infill over" +msgstr "Pas de remplissage plein si plus de" + +msgid "No solid infill over perimeters" +msgstr "Pas de remplissage plein sur les périmètres" + msgid "No sparse layers (EXPERIMENTAL)" msgstr "Sans couches dispersées (EXPERIMENTAL)" @@ -7016,9 +7080,6 @@ msgstr "" "Ancienne interface PrusaSlicer : toutes les fenêtres sont dans l'application, les paramètres sont sur l'onglet du haut " "et le choix du rendu des sections en bas de la vue 3D." -msgid "Old PrusaSlicer layout" -msgstr "Ancienne interface PrusaSlicer" - msgid "Old Value" msgstr "Ancienne Valeur" @@ -7028,6 +7089,9 @@ msgstr "Ancienne valeur" msgid "On" msgstr "Sur" +msgid "On first layer" +msgstr "Pour la première couche" + msgid "On odd layers" msgstr "Sur les couches impaires" @@ -7044,6 +7108,16 @@ msgstr "" msgid "On overhangs" msgstr "Sur les surplombs" +msgid "" +"On some OS like MacOS or some Linux, tooltips can't stay on for a long time. " +"This setting replaces native tooltips with custom dialogs to improve " +"readability (only for settings).\n" +"Note that for the number controls, you need to hover the arrows to get the " +"custom tooltip. Also, it keeps the focus but will give it back when it " +"closes. It won't show up if you are editing the field." +msgstr "Dans certains systèmes d'exploitation, comme MacOs ou certains Linux, les info-bulles ne peuvent rester ouvert bien longtemps. Ce réglage permet de remplacer les info-bulles natives par des fenêtres internes pour elur permettre d'être plus facilement visible (ne fonctionne que pour les rêglages).\n" +"Note: Pour les champs numérique avec flèches, vous devez survoler l'une des deux flèches. Le focus est conservé par la fenêtre, vous ne pouvez donc pas taper au clavier en même temps. Elle sera fermé et el focus rendu lorsque vous retirerez votre pointeur du champ. Il ne s'affichera pas si vous avez cliqué dans le champ pour l'éditer." + msgid "On surfaces" msgstr "Sur surfaces" @@ -7054,6 +7128,9 @@ msgstr "" "Dans ce système, %s utilise des certificats HTTPS issus du système Magasin " "de Certificats ou Trousseau." +msgid "On top surfaces" +msgstr "Sur les surfaces supérieures" + msgid "On/Off one layer mode of the vertical slider" msgstr "On/Off mode couche unique de la barre de défilement verticale" @@ -7134,6 +7211,12 @@ msgstr "Décaler Z seulement en-dessous de" msgid "Only one peri - other algo" msgstr "Un seul perimetre - autre algo" +msgid "Only one perimeter" +msgstr "Un seul périmètre" + +msgid "Only one perimeter on First layer" +msgstr "Un seul périmètre sur la première couche" + msgid "Only one perimeter on Top surfaces" msgstr "Un seul périmètre sur le dessus" @@ -7759,6 +7842,9 @@ msgstr "Imprimer un cube d'étalonnage, pour divers objectifs d'étalonnage." msgid "Print all brim at startup" msgstr "Imprimer toutes les bordures au démarrage" +msgid "Print at the end" +msgstr "Mettre à la fin" + msgid "" "Print contour perimeters from the outermost one to the innermost one instead " "of the default inverse order." @@ -7798,6 +7884,9 @@ msgstr "Mode d'impression" msgid "Print pauses" msgstr "Pauses d'impression" +msgid "Print remaining times" +msgstr "Ecrire le temps restant" + msgid "Print settings" msgstr "Réglages de l'impression" @@ -7815,6 +7904,12 @@ msgid "" msgstr "" "La vitesse d'impression sera réduite pour que pas moins de %1%s soient dépensés sur cette couche" +msgid "" +"Print the thumbnail code at the end of the gcode file instead of the front.\n" +"Be careful! Most firmwares expect it at the front, so be sure that your " +"firmware support it." +msgstr "Mettre le code des vignettes à la fin du fichier au lieu du début.\n Soyez prudent! La plupart des Firmwares l'attendent au début, soyez sûr que votre imprimante le supporte bien." + msgid "Print z" msgstr "Imprimer z" @@ -8120,9 +8215,6 @@ msgstr "Réduction du temps d'impression" msgid "Refresh Printers" msgstr "Actualiser les imprimantes" -msgid "Regular layout with the tab bar" -msgstr "Interface standard avec la barre d'onglets" - msgid "Related printer preset name" msgstr "Nom du préréglage d'imprimante associé" @@ -8591,6 +8683,9 @@ msgstr "Jointures" msgid "Seam angle cost" msgstr "Coût de l'angle de jointure" +msgid "Seam gap" +msgstr "Espace de jointure" + msgid "Seam painting" msgstr "Peinture de jointure" @@ -8749,8 +8844,14 @@ msgstr "" msgid "Select the STL file to repair:" msgstr "Sélectionner le fichier STL à réparer :" -msgid "Select this option to enforce z-lift on the first layer." -msgstr "Sélectionnez cette option pour appliquer le Décalage en Z à la première couche." +msgid "" +"Select this option to enforce z-lift on the first layer.\n" +"If this is enabled and the lift value is 0 or deactivated, then every first " +"move before each object will be lifted by the first layer height." +msgstr "" +"Sélectionnez cette option pour imposer le z-lift sur la première couche.\n" +"Si cette option est activée et que la valeur d'élévation est 0 ou désactivée, alors chaque premier " +"mouvement avant chaque objet sera soulevé de la hauteur de la première couche." msgid "Select this option to not use/enforce the z-lift on a top surface." msgstr "Sélectionnez cette option pour ne pas utiliser/appliquer le Décalage en Z sur une surface du dessus." @@ -9141,9 +9242,6 @@ msgstr "" "Réglages dans une fenêtre non modal : les paramètres sont affichés dans leur propre fenêtre. Vous devez " "cliquer sur les icones engrenages des paramètres pour afficher la fenêtre des paramètres." -msgid "Shall I add the '%'?" -msgstr "Dois-je ajouter le '%' ?" - msgid "Shall I adjust those settings for supports?" msgstr "Dois-je ajuster ces paramètres pour les supports ?" @@ -9353,13 +9451,8 @@ msgstr "Taille pour le G-Code" msgid "Size in X and Y of the rectangular plate." msgstr "Taille en X et Y du plateau rectangulaire." -msgid "" -"Size of the tab icons, in pixels. Set to 0 to remove icons.\n" -"You have to restart the application before any change will be taken into " -"account." -msgstr "" -"Taille des icônes de l'onglet, en pixels. Mettez la valeur 0 pour supprimer les icônes.\n" -"Vous devez redémarrer l'application avant que tout changement soit pris en compte." +msgid "Size of the tab icons, in pixels. Set to 0 to remove icons." +msgstr "Taille des icônes des onglets, en pixels. La valeur 0 permet de supprimer les icônes." msgid "" "Skinnydip performs a secondary dip into the meltzone to burn off fine " @@ -9466,6 +9559,11 @@ msgstr "" "le mot de passe dans l'URL en respectant le format suivant : " "https://username:password@your-octopi-address/" +msgid "" +"Slic3r contains sizable contributions from Prusa Research. Original work by " +"Alessandro Ranellucci and the RepRap community." +msgstr "Slic3r contient une important contribution de Prusa Research. Code orignalement crée par Alessandro Ranellucci and the RepRap community." + msgid "Slic3r logo designed by Corey Daniels." msgstr "Logo Slic3r conçu par Corey Daniels." @@ -9846,9 +9944,6 @@ msgstr "" msgid "Speed:" msgstr "Vitesse:" -msgid "Speedup" -msgstr "Accélération" - msgid "Speedup time" msgstr "Temps d'accélération" @@ -10010,13 +10105,6 @@ msgstr "" "Démonté avec succès. Le périphérique %s(% s) peut maintenant être retiré en " "toute sécurité de l'ordinateur." -msgid "" -"SuperSlicer is a skinned version of Slic3r, based on PrusaSlicer by Prusa " -"and the original Slic3r by Alessandro Ranellucci & the RepRap community." -msgstr "" -"SuperSlicer est une version modifiée de Slic3r, basée sur PrusaSlicer de Prusa " -"et sur la version Slic3r originale d'Alessandro Ranellucci et de la communauté RepRap." - msgid "support" msgstr "support" @@ -10124,6 +10212,9 @@ msgstr "supports et socle" msgid "Supports remaining times" msgstr "Temps d'impression restant" +msgid "Supports remaining times method" +msgstr "Type de 'Temps d'impression restant'" + msgid "Supports stealth mode" msgstr "Supporte le mode silencieux" @@ -10265,6 +10356,9 @@ msgstr "Température °C" msgid "Temperatures" msgstr "Températures" +msgid "Template Custom G-code" +msgstr "Patron de G-Code personnalisé" + msgid "Test" msgstr "Test" @@ -10658,6 +10752,11 @@ msgstr "" "impression séquentielle.\n" "Ce code ne sera pas traité au cours de la génération du G-Code." +msgid "" +"The settings have a lock and dot to show how they are modified. You can hide " +"them by uncheking this option." +msgstr "Les réglages ont un cadenas et des points (bouton pour remttre al valeur apr défaut, type de réglage). Vous pouvez les cacher en déactivant cette option." + msgid "The size of the object can be specified in inches" msgstr "La taille de l'objet peut être spécifiée en pouces" @@ -10958,18 +11057,14 @@ msgstr "" msgid "" "This custom code is inserted at every extrusion type change.Note that you " "can use placeholder variables for all Slic3r settings as well as " -"[extrusion_role], [layer_num] and [layer_z] that can take these string " -"values: { Perimeter, ExternalPerimeter, OverhangPerimeter, InternalInfill, " -"SolidInfill, TopSolidInfill, BridgeInfill, GapFill, Skirt, SupportMaterial, " +"[last_extrusion_role], [extrusion_role], [layer_num] and [layer_z]. The " +"'extrusion_role' strings can take these string values: { Perimeter, " +"ExternalPerimeter, OverhangPerimeter, InternalInfill, SolidInfill, " +"TopSolidInfill, BridgeInfill, GapFill, Skirt, SupportMaterial, " "SupportMaterialInterface, WipeTower, Mixed }. Mixed is only used when the " "role of the extrusion is not unique, not exactly inside another category or " "not known." -msgstr "" -"Ce code personnalisé est inséré à chaque changement de type d'extrusion." -" Notez que vous pouvez utiliser des variables de substitution pour tous les paramètres Slic3r," -" ainsi que pour [layer_num], [layer_z] et [extrusion_role] pouvant prendre ces valeurs de chaîne:" -" { Perimeter, ExternalPerimeter, OverhangPerimeter, InternalInfill, SolidInfill, TopSolidInfill, BridgeInfill, GapFill, Skirt, SupportMaterial, SupportMaterialInterface, WipeTower, Mixed }." -" Mixed n'est utilisé que lorsque le rôle de l'extrusion n'est pas unique, pas uniquement dans une catégorie ou n'est pas connu." +msgstr "Ce code personnalisé est inséré à chaque changement de type d'extrusion. Notez que vous pouvez utiliser des variables de substitution pour tous les paramètres Slic3r, ainsi que pour [last_extrusion_role], [extrusion_role], [layer_num] et [layer_z]. Les 'extrusion_role' pouvant prendre ces valeurs de chaîne: { Perimeter, ExternalPerimeter, OverhangPerimeter, InternalInfill, SolidInfill, TopSolidInfill, BridgeInfill, GapFill, Skirt, SupportMaterial, SupportMaterialInterface, WipeTower, Mixed }. Mixed n'est utilisé que lorsque le rôle de l'extrusion n'est pas unique, pas uniquement dans une catégorie ou n'est pas connu" msgid "" "This custom code is inserted at every layer change, right after the Z move " @@ -11087,16 +11182,11 @@ msgstr "" msgid "" "This fan speed is enforced during all infill bridges. It won't slow down the " "fan if it's currently running at a higher speed.\n" -"Set to 1 to disable the fan.\n" -"Set to -1 to disable this override (will take the value of Bridges fan " +"Set to 1 to follow default speed.\n" +"Set to -1 to disable this override (internal bridges will use Bridges fan " "speed).\n" "Can only be overriden by disable_fan_first_layers." -msgstr "" -" Cette vitesse de ventilation est appliquée pendant tous les remplissage des ponts. Cela ne ralentira pas le " -"le ventilateur s'il fonctionne déjà à une vitesse plus élevée." -"Mettez la valeur 1 pour désactiver le ventilateur.\n" -"Mettez la valeur -1 pour désactiver cette valeur prioritaire (elle prendra la valeur du ventilateur des ponts).\n" -"Ne peut être remplacée que par disable_fan_first_layers." +msgstr "Cette vitesse de ventilation est appliquée pendant tous les ponts de remplissage. Cela ne ralentira pas le le ventilateur s'il fonctionne déjà à une vitesse plus élevée. Mettre 1 pour utiliser la vitesse par défaut.\nMettre -1 pour désactiver ce réglage (Les ponts de remplissage auront le ventilateur allant à la même vitesse que pour les ponts).\nNe peut être remplacée que par disable_fan_first_layers." msgid "" "This fan speed is enforced during all top fills.\n" @@ -11107,20 +11197,14 @@ msgstr "" "Cette vitesse de ventilation est appliquée pendant tous les remplissages du dessus.\n" " Réglez sur 1 pour désactiver le ventilateur.\n" "Mettez la valeur -1 pour utiliser la vitesse normale du ventilateur sur les couches du dessus.\n" -"Ne peut être remplacée que par disable_fan_first_layers." +"Ne peut étre passée outre que par disable_fan_first_layers." msgid "" "This fan speed is enforced during bridges and overhangs. It won't slow down " "the fan if it's currently running at a higher speed.\n" -"Set to 1 to disable the fan.\n" "Set to -1 to disable this override.\n" "Can only be overriden by disable_fan_first_layers." -msgstr "" -" Cette vitesse de ventilation est appliquée pendant les ponts et les surplombs. Elle ne ralentira pas " -"le ventilateur s'il fonctionne déjà à une vitesse plus élevée." -"Mettez la valeur 1 pour désactiver le ventilateur.\n" -"Mettez la valeur -1 pour désactiver cette valeur prioritaire." -"Ne peut être remplacée que par disable_fan_first_layers." +msgstr "Cette vitesse de ventilation est appliquée pendant les ponts et les surplombs. Elle ne ralentira pas le ventilateur s'il fonctionne déjà à une vitesse plus élevée.\nMettez la valeur -1 pour désactiver ce réglage. Ne peut être passé outre que par disable_fan_first_layers." msgid "" "This feature allows you to combine infill and speed up your print by " @@ -11241,6 +11325,9 @@ msgstr "" msgid "This is a system preset." msgstr "Ceci est un préréglage système." +msgid "This is only used in Slic3r interface as a visual help." +msgstr "Ceci est uniquement utilisé dans l'interface de Slic3r comme indication visuelle." + msgid "This is only used in the Slic3r interface as a visual help." msgstr "Ceci est uniquement utilisé dans l'interface de Slic3r comme indication visuelle." @@ -11307,20 +11394,17 @@ msgid "" "This is the highest printable layer height for this extruder, used to cap " "the variable layer height and support layer height. Maximum recommended " "layer height is 75% of the extrusion width to achieve reasonable inter-layer " -"adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." -msgstr "" -"Ceci est la hauteur de couche imprimable maximum pour cet extrudeuse," -" utilisée pour plafonner la hauteur de couche variable et la hauteur de couche des supports." -" La hauteur de couche maximum recommandée est 75% de la largeur d'extrusion afin d'obtenir une adhésion inter-couches correcte." -" Si réglée sur 0, la hauteur de couche est limitée à 75% du diamètre de la buse." +"adhesion. \n" +"Can be a % of the nozzle diameter.\n" +"If set to 0, layer height is limited to 75% of the nozzle diameter." +msgstr "Ceci est la hauteur de couche imprimable maximum pour cet extrudeuse, utilisée pour plafonner la hauteur de couche variable et la hauteur de couche des supports. Il n'est aps recommandé d'avoir une hauteur de couche supérieur à 75% du diamètre de la buse afin d'obtenir une adhésion inter-couches correcte.\nPeut être un % du diamètre de la buse.\nSi réglée sur 0, la hauteur de couche est fixée à 75% du diamètre de la buse." msgid "" "This is the lowest printable layer height for this extruder and limits the " "resolution for variable layer height. Typical values are between 0.05 mm and " -"0.1 mm." -msgstr "" -"Cette valeur est la hauteur de couche imprimable minimum pour cet extrudeuse et elle limite la résolution pour la hauteur de couche variable." -" Les valeurs type se situent entre 0.05 mm et 0.1 mm." +"0.1 mm.\n" +"Can be a % of the nozzle diameter." +msgstr "Cette valeur est la hauteur de couche imprimable minimum pour cet extrudeuse et elle limite la résolution pour la hauteur de couche variable. Les valeurs type se situent entre 0.05 mm et 0.1 mm.\nPeut être un % du diamètre de la buse." msgid "" "This is the percentage of the flow that is used for the second ironing pass. " @@ -11647,6 +11731,12 @@ msgstr "" "dépend de l'objet)." msgid "" +"This setting will ensure that all 'overlap' are not higher than this value. " +"This is useful for filaments that are too viscous, as the line can't flow " +"under the previous one." +msgstr "Ce paramètre garantit que tous les 'chevauchements' ne sont pas supérieurs à cette valeur. Ceci est utile pour les filaments trop visqueux, car une ligne n'arrive pas à passer sous la précédente pour remplir les vides." + +msgid "" "This start procedure is inserted at the beginning, after any printer start " "gcode (and after any toolchange to this filament in case of multi-material " "printers). This is used to override settings for a specific filament. If " @@ -11776,6 +11866,9 @@ msgstr "Seuil :" msgid "Thumbnail color" msgstr "Couleur des vignettes" +msgid "Thumbnail options" +msgstr "Options pour vignettes" + msgid "Thumbnails" msgstr "Vignettes" @@ -11833,6 +11926,14 @@ msgstr "" msgid "to" msgstr "à" +msgid "" +"To avoid visible seam, the extrusion can be stoppped a bit before the end of " +"the loop.\n" +"Can be a mm or a % of the current extruder diameter." +msgstr "" +"Pour éviter une couture visible, l'extrusion peut être arrêtée un peu avant la fin de la boucle.\n" +"Peut être un mm ou un % du diamètre actuel de la buse." + msgid "To do that please specify a new name for the preset." msgstr "Pour faire cela veuillez spécifier un nouveau nom pour le préréglage." @@ -12198,6 +12299,9 @@ msgstr "Utiliser un autre extrudeuse" msgid "Use custom size for toolbar icons" msgstr "Utiliser une taille personnalisée pour les icônes de la barre d'outils" +msgid "Use custom tooltip" +msgstr "Utiliser les info-bulles alternatives" + msgid "Use environment map" msgstr "Utiliser la carte d'environnement" @@ -12220,6 +12324,11 @@ msgid "Use only as safeguards" msgstr "Utiliser uniquement pour sauvegarde" msgid "" +"Use only one perimeter on first layer, to give more space to the top infill " +"pattern." +msgstr "Utilisez un seul périmètre sur la première couche, pour donner plus d'espace au remplissage supérieur." + +msgid "" "Use only one perimeter on flat top surface, to give more space to the top " "infill pattern." msgstr "" @@ -12477,6 +12586,15 @@ msgstr "" "l'enregistrement ?" msgid "" +"When an extruder travels to an object (from the start position or from an " +"object to another), the nozzle height is guaranteed to be at least at this " +"value.\n" +"It's made to ensure the nozzle won't hit clips or things you have on your " +"bed. But be careful to not put a clip in the 'convex shape' of an object.\n" +"Set to 0 to disable." +msgstr "Quand un extrudeur se déplace vers un objet (de la position de départ ou d'un autre), la buse est garantie d'être au moins à cette hauteur.\nCeci permet de s'assurer que si le lit d'impression possède des clips ou d'autres objets fixés dessus, la buse ne les percutera pas. Faites tout de même attention qu'il n'y ai rien dans l'enveloppe convexe des objets." + +msgid "" "When an object is sliced, it will switch your view from the curent view to " "the preview (and then gcode-preview) automatically, depending on the option " "choosen." @@ -12556,10 +12674,14 @@ msgstr "Lorsque vous imprimez avec de très basses hauteurs de couche, vous pouv msgid "" "When retraction is triggered before changing tool, filament is pulled back " "by the specified amount (the length is measured on raw filament, before it " -"enters the extruder)." +"enters the extruder).\n" +"Note: This value will be unretracted when this extruder will load the next " +"time." msgstr "" -"Lorsque la rétraction est déclenchée avant un changement de filament, le filament est retiré de la longueur indiquée" -" (la longueur est mesurée sur le filament brut, avant qu'il entre dans l'extrudeuse)." +"Lorsque la rétraction est déclenchée avant le changement de filament, il est tiré en arrière " +"de la quantité spécifiée (la longueur est mesurée sur le filament brut, avant qu'il " +"n'entre dans l'extrudeuse).\n" +"Note : Cette valeur sera rétractée lors du prochain chargement de cette extrudeuse." msgid "" "When retraction is triggered, filament is pulled back by the specified " @@ -12616,10 +12738,12 @@ msgstr "" msgid "" "When the retraction is compensated after changing tool, the extruder will " -"push this additional amount of filament." +"push this additional amount of filament (but not on the first extruder after " +"start, as it should already be loaded)." msgstr "" -"Lorsque la rétraction est compensée après un changement de filament," -" l'extrudeuse extrudera cette quantité de filament en plus." +"Lorsque la rétraction est compensée après le changement de filament, l'extrudeuse " +"pousse cette quantité supplémentaire de filament (mais pas sur la première extrudeuse après " +"démarrage, car elle devrait déjà être chargée)." msgid "" "When the retraction is compensated after the travel move, the extruder will " @@ -12659,25 +12783,17 @@ msgid "" "When you have a medium/hight number of top/bottom solid layers, and a low/" "medium of perimeters, then it have to put some solid infill inside the part " "to have enough solid layers.\n" -"By setting this to somethign higher than 0, you can remove this 'inside " +"By setting this to something higher than 0, you can remove this 'inside " "filling'. This number allow to keep some if there is a low number of " "perimeter over the void.\n" "If this setting is equal or higher than the top/bottom solid layer count, it " "won't evict anything.\n" "If this setting is set to 1, it will evict all solid fill are are only over " "perimeters.\n" -"Set zero to disable." -msgstr "" -"Lorsque vous avez un nombre moyen/haut de couches pleines supérieures/inférieures, et un nombre faible/" -"moyen de périmètres, alors il faut mettre du remplissage plein à l'intérieur de la pièce " -"pour avoir assez de couches solides." -"En fixant cette valeur à un niveau supérieur à 0, vous pouvez supprimer ce 'remplissage intérieur'. " -"Ce nombre permet d'en garder si le nombre de 'périmètre au-dessus du vide' est faible.\n" -"Si ce paramètre est égal ou supérieur au nombre de couches pleines supérieures et inférieures, il " -"n'enlévera rien.\n" -"Si ce paramètre est réglé sur 1, il enlévera toutes les couches pleines qui sont seulement au-dessus " -"des périmètres." -"Mettez à zéro pour le désactiver." +"Set zero to disable.\n" +"!! ensure_vertical_shell_thickness may be erased by this setting !! You may " +"want to deactivate at least one of the two." +msgstr "Lorsque vous avez un nombre moyen/haut de couches pleines supérieures/inférieures, et un nombre faible/moyen de périmètres, alors il faut mettre du remplissage plein à l'intérieur de la pièce pour avoir assez de couches solides.En fixant cette valeur à un niveau supérieur à 0, vous pouvez supprimer ce 'remplissage intérieur'. Ce nombre permet d'en garder si le nombre de 'périmètre au-dessus du vide' est faible.\nSi ce paramètre est égal ou supérieur au nombre de couches pleines supérieures et inférieures, il n'enlévera rien.\nSi ce paramètre est réglé sur 1, il enlévera toutes les couches pleines qui sont seulement au-dessus des périmètres.Mettez à zéro pour le désactiver.\n!! ensure_vertical_shell_thickness peut voir so effet supprimé !! Vous ne devriez pas utiliser les deux options en même temps." msgid "WHITE BULLET" msgstr "PUCE BLANCHE" @@ -12971,17 +13087,10 @@ msgstr "" "\"Téléchargement vers l'hôte d'impression\"" msgid "" -"You have to restart the application before any change will be taken into " -"account." -msgstr "" -"Vous devez relancer l'application avant que tout changement soit pris en " -"compte." - -msgid "" -"You have unsaved Changed, do you want to save your project or to remove all " +"You have unsaved changes, do you want to save your project or to remove all " "settings and objects?" msgstr "" -"Vous avez des modifications non sauvegardées, voulez-vous sauvegarder votre projet ou supprimer toutes les modifications ? " +"Vous avez des modifications non sauvegardées, voulez-vous sauvegarder votre projet ou supprimer toutes les modifications " "des paramètres et des objets ?" msgid "You may need to update your graphics card driver." diff --git a/resources/profiles b/resources/profiles -Subproject b2309c4a532bd2bbe26c5d9f1bff8bb2bee707f +Subproject 38cf62ab55e88865c21d8d8b15f8fd1d0b5139a diff --git a/resources/ui_layout/Readme.md b/resources/ui_layout/Readme.md index 27a097fe0..77c4bea1c 100644 --- a/resources/ui_layout/Readme.md +++ b/resources/ui_layout/Readme.md @@ -47,13 +47,15 @@ each parameter is separated by ':' * full_width: to tell to create a field that span the full width. * sidetext$STR: the suffix at the right of the widget (like 'mm'). * sidetext_width$INT: the suffix label length (override the group one). -1 for auto. + * max_literal$INT[%]: if the user enter a value higher than that and it's a 'float or percent' field, then emit a pop-up to question if he doesn't forgot a '%'. If negative, it check if the value isn't lower than the absolute max_literal, instead of greater. If there is a '%' after the value, then it's multiplied by the biggest nozzle diameter. * simple|advanced|expert: add one of these to modify the mode in which this setting appear. * width$INT: change the width of the field. Shouod work on most type of settings. - * height$INT: change the height of the field. Don't works with every type of setting. + * height$INT: change the height of the field. Don't works with every type of setting (mostly multilne text). Set to -1 to 'disable'. * precision$INT: number of digit after the dot displayed. * url$STR: the url to call when clicking on it. * id $INT: for setting only a single value of a setting array. * idx: for setting only a single value of a setting array, with the index of the page (for extruder ui page) +* height:INT: change the default height of settings. Don't works with every type of setting (mostly multilne text). Set to 0 or -1 to disable. * recommended_thin_wall_thickness_description: create a text widget to explain recommended thin wall thickness (only in a fff print tab). * parent_preset_description: create a text widget to explain parent preset. * cooling_description: create a text widget to explain cooling (only in a filament tab). diff --git a/resources/ui_layout/print.ui b/resources/ui_layout/print.ui index 469ff345f..94f54222d 100644 --- a/resources/ui_layout/print.ui +++ b/resources/ui_layout/print.ui @@ -178,7 +178,7 @@ group:Skirt setting:skirts line:Distance setting:skirt_distance - setting:label$from brim:sidetext$else from object:skirt_distance_from_brim + setting:label$from brim:skirt_distance_from_brim end_line setting:skirt_height setting:draft_shield diff --git a/resources/ui_layout/printer_fff.ui b/resources/ui_layout/printer_fff.ui index 5ccc4da39..90acf9b84 100644 --- a/resources/ui_layout/printer_fff.ui +++ b/resources/ui_layout/printer_fff.ui @@ -5,6 +5,7 @@ group:Size and coordinates setting:max_print_height setting:z_offset setting:z_step + setting:lift_min group:extruders_count_event:milling_count_event:Capabilities extruders_count setting:single_extruder_multi_material @@ -28,8 +29,8 @@ group:silent_mode_event:Firmware end_line setting:gcode_filename_illegal_char group:Cooling fan - line:Speedup - setting:label$Speedup time:fan_speedup_time + line:Speedup time + setting:label$:fan_speedup_time setting:label$Only for overhangs:fan_speedup_overhangs end_line setting:label$Kickstart duration:fan_kickstart @@ -43,7 +44,10 @@ group:Thumbnails setting:label$:sidetext_width$1:thumbnails_custom_color setting:label$:label_width$1:thumbnails_color end_line - setting:thumbnails_with_bed + line:Thumbnail options + setting:thumbnails_with_bed + setting:thumbnails_end_file + end_line group:Advanced setting:use_relative_e_distances setting:use_firmware_retraction @@ -55,25 +59,25 @@ group: setting:start_gcode_manual height:15 group:no_title:Start G-code - setting:full_width:start_gcode + setting:full_width:height$21:start_gcode group:no_title:End G-code setting:full_width:end_gcode group:no_title:Before layer change G-code - setting:full_width:before_layer_gcode + setting:full_width:height$9:before_layer_gcode group:no_title:After layer change G-code - setting:full_width:layer_gcode + setting:full_width:height$9:layer_gcode group:no_title:Tool change G-code - setting:full_width:toolchange_gcode + setting:full_width:height$9:toolchange_gcode group:no_title:Between objects G-code (for sequential printing) - setting:full_width:between_objects_gcode + setting:full_width:height$9:between_objects_gcode group:no_title:Between extrusion role change G-code - setting:full_width:feature_gcode + setting:full_width:height$9:feature_gcode group:no_title:Colour Change G-code - setting:full_width:color_change_gcode + setting:full_width:height$5:color_change_gcode group:no_title:Pause Print G-code - setting:full_width:pause_print_gcode + setting:full_width:height$5:pause_print_gcode group:no_title:Template Custom G-code - setting:full_width:template_custom_gcode + setting:full_width:height$5:template_custom_gcode height:0 page:Notes:note.png diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index b03b73964..9be3b5eae 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -169,6 +169,9 @@ void AppConfig::set_defaults() if (get("custom_toolbar_size").empty()) set("custom_toolbar_size", "100"); + if (get("setting_icon").empty()) + set("setting_icon", "1"); + if (get("auto_toolbar_size").empty()) set("auto_toolbar_size", "100"); @@ -197,11 +200,11 @@ void AppConfig::set_defaults() if (get("use_rich_tooltip").empty()) set("use_rich_tooltip", -#ifndef WIN32 - "1" -#else +//#if __APPLE__ + //"1" +//#else "0" -#endif +//#endif ); } #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index 700fba07b..7b33a6dd5 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -34,8 +34,8 @@ BridgeDetector::BridgeDetector( void BridgeDetector::initialize() { - // 5 degrees stepping - this->resolution = PI/(36.0*5); + // 2 degrees stepping + this->resolution = PI/(90); // output angle not known this->angle = -1.; @@ -80,10 +80,7 @@ bool BridgeDetector::detect_angle(double bridge_direction_override) std::vector<BridgeDirection> candidates; if (bridge_direction_override == 0.) { - std::vector<double> angles = bridge_direction_candidates(); - candidates.reserve(angles.size()); - for (size_t i = 0; i < angles.size(); ++ i) - candidates.emplace_back(BridgeDirection(angles[i])); + candidates = bridge_direction_candidates(); } else candidates.emplace_back(BridgeDirection(bridge_direction_override)); @@ -100,68 +97,178 @@ bool BridgeDetector::detect_angle(double bridge_direction_override) for (size_t i_angle = 0; i_angle < candidates.size(); ++ i_angle) { const double angle = candidates[i_angle].angle; - Lines lines; { // Get an oriented bounding box around _anchor_regions. BoundingBox bbox = get_extents_rotated(this->_anchor_regions, - angle); // Cover the region with line segments. - lines.reserve((bbox.max(1) - bbox.min(1) + this->spacing) / this->spacing); + lines.reserve((bbox.max.y() - bbox.min.y() + this->spacing - SCALED_EPSILON) / this->spacing); double s = sin(angle); double c = cos(angle); - //FIXME Vojtech: The lines shall be spaced half the line width from the edge, but then - // some of the test cases fail. Need to adjust the test cases then? -// for (coord_t y = bbox.min(1) + this->spacing / 2; y <= bbox.max(1); y += this->spacing) - for (coord_t y = bbox.min(1); y <= bbox.max(1); y += this->spacing) + // As The lines be spaced half the line width from the edge + // FIXME: some of the test cases may fail. Need to adjust the test cases + for (coord_t y = bbox.min.y() + this->spacing / 2; y <= bbox.max.y(); y += this->spacing) + //for (coord_t y = bbox.min.y(); y <= bbox.max.y(); y += this->spacing) //this is the old version lines.push_back(Line( - Point((coord_t)round(c * bbox.min(0) - s * y), (coord_t)round(c * y + s * bbox.min(0))), - Point((coord_t)round(c * bbox.max(0) - s * y), (coord_t)round(c * y + s * bbox.max(0))))); + Point((coord_t)round(c * bbox.min.x() - s * y), (coord_t)round(c * y + s * bbox.min.x())), + Point((coord_t)round(c * bbox.max.x() - s * y), (coord_t)round(c * y + s * bbox.max.x())))); } - double total_length = 0; - uint32_t nbLines = 0; - double max_length = 0; + //compute stat on line with anchors, and their lengths. + BridgeDirection& c = candidates[i_angle]; + std::vector<coordf_t> dist_anchored; { Lines clipped_lines = intersection_ln(lines, clip_area); for (size_t i = 0; i < clipped_lines.size(); ++i) { const Line &line = clipped_lines[i]; + bool good_line = false; if (expolygons_contain(this->_anchor_regions, line.a) && expolygons_contain(this->_anchor_regions, line.b)) { + //check that it isn't always inside + Lines lines = intersection_ln(line, to_polygons(this->_anchor_regions)); + good_line = lines.size() > 1; + } + if(good_line) { // This line could be anchored. - double len = line.length(); - total_length += len; - max_length = std::max(max_length, len); - nbLines++; + coordf_t len = line.length(); + //store stats + c.total_length_anchored += len; + c.max_length_anchored = std::max(c.max_length_anchored, len); + c.nb_lines_anchored++; + dist_anchored.push_back(len); + } else { + // this line could NOT be anchored. + coordf_t len = line.length(); + c.total_length_free += len; + c.max_length_free = std::max(c.max_length_free, len); + c.nb_lines_free++; } } } - if (total_length == 0. || nbLines == 0) + if (c.total_length_anchored == 0. || c.nb_lines_anchored == 0) { continue; + } else { + have_coverage = true; + // compute median + if (!dist_anchored.empty()) { + std::sort(dist_anchored.begin(), dist_anchored.end()); + c.median_length_anchor = dist_anchored[dist_anchored.size() / 2]; + } + + + // size is 20% + } + } + + // if no direction produced coverage, then there's no bridge direction ? + if (!have_coverage) { + //try again to choose the least worse + // use only poly contour angles + if (bridge_direction_override == 0.) { + candidates = bridge_direction_candidates(true); + } else + candidates.emplace_back(BridgeDirection(bridge_direction_override)); + for (size_t i_angle = 0; i_angle < candidates.size(); ++i_angle) + { + const double angle = candidates[i_angle].angle; + //use the whole polygon + Lines lines; + { + // Get an oriented bounding box around _anchor_regions. + BoundingBox bbox = get_extents_rotated(clip_area, -angle); + // Cover the region with line segments. + lines.reserve((bbox.max.y() - bbox.min.y() + this->spacing - SCALED_EPSILON) / this->spacing); + double s = sin(angle); + double c = cos(angle); + // The lines be spaced half the line width from the edge + for (coord_t y = bbox.min.y() + this->spacing / 2; y <= bbox.max.y(); y += this->spacing) + lines.push_back(Line( + Point((coord_t)round(c * bbox.min.x() - s * y), (coord_t)round(c * y + s * bbox.min.x())), + Point((coord_t)round(c * bbox.max.x() - s * y), (coord_t)round(c * y + s * bbox.max.x())))); + } + //compute stat on line with anchors, and their lengths. + BridgeDirection& c = candidates[i_angle]; + std::vector<coordf_t> dist_anchored; + { + Lines clipped_lines = intersection_ln(lines, clip_area); + for (size_t i = 0; i < clipped_lines.size(); ++i) { + const Line& line = clipped_lines[i]; + if (expolygons_contain(this->_anchor_regions, line.a) || expolygons_contain(this->_anchor_regions, line.b)) { + // This line has one anchor + coordf_t len = line.length(); + //store stats + c.total_length_anchored += len; + c.max_length_anchored = std::max(c.max_length_anchored, len); + c.nb_lines_anchored++; + dist_anchored.push_back(len); + } else { + // this line could NOT be anchored. + coordf_t len = line.length(); + c.total_length_free += len; + c.max_length_free = std::max(c.max_length_free, len); + c.nb_lines_free++; + } + } + } + if (c.total_length_anchored == 0. || c.nb_lines_anchored == 0) { + continue; + } else { + have_coverage = true; + // compute median + if (!dist_anchored.empty()) { + std::sort(dist_anchored.begin(), dist_anchored.end()); + c.median_length_anchor = dist_anchored[dist_anchored.size() / 2]; + } - have_coverage = true; - // Sum length of bridged lines. - candidates[i_angle].coverage = total_length; - /* The following produces more correct results in some cases and more broken in others. - TODO: investigate, as it looks more reliable than line clipping. */ - // $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0; - // max length of bridged lines - candidates[i_angle].max_length = max_length; - candidates[i_angle].mean_length = total_length / nbLines; + + // size is 20% + } + } } // if no direction produced coverage, then there's no bridge direction - if (! have_coverage) + if (!have_coverage) return false; - - // sort directions by coverage - most coverage first - std::sort(candidates.begin(), candidates.end()); + + //compute global stat (max & min median & max length) + std::vector<coordf_t> all_median_length; + std::vector<coordf_t> all_max_length; + for (BridgeDirection &c : candidates) { + all_median_length.push_back(c.median_length_anchor); + all_max_length.push_back(c.max_length_anchored); + } + std::sort(all_median_length.begin(), all_median_length.end()); + std::sort(all_max_length.begin(), all_max_length.end()); + coordf_t median_max_length = all_max_length[all_max_length.size() / 2]; + coordf_t min_max_length = all_max_length.front(); + coordf_t max_max_length = all_max_length.back(); + coordf_t median_median_length = all_median_length[all_median_length.size() / 2]; + coordf_t min_median_length = all_median_length.front(); + coordf_t max_median_length = all_median_length.back(); + + //compute individual score + for (BridgeDirection& c : candidates) { + c.coverage = 0; + //ratio_anchored is 70% of the score + double ratio_anchored = c.total_length_anchored / (c.total_length_anchored + c.total_length_free); + c.coverage = 70 * ratio_anchored; + //median is 15% (and need to invert it) + double ratio_median = 1 - double(c.median_length_anchor - min_median_length) / (double)std::max(1., max_median_length - min_median_length); + c.coverage += 15 * ratio_median; + //max is 15 % (and need to invert it) + double ratio_max = 1 - double(c.max_length_anchored - min_max_length) / (double)std::max(1., max_max_length - min_max_length); + c.coverage += 15 * ratio_max; + //bonus for perimeter dir + if (c.along_perimeter) + c.coverage += 5; + + } // if any other direction is within extrusion width of coverage, prefer it if shorter // shorter = shorter max length, or if in espilon (10) range, the shorter mean length. // TODO: There are two options here - within width of the angle with most coverage, or within width of the currently perferred? size_t i_best = 0; - for (size_t i = 1; i < candidates.size() && candidates[i_best].coverage - candidates[i].coverage < this->spacing; ++ i) - if (candidates[i].max_length < candidates[i_best].max_length || - (candidates[i].max_length < candidates[i_best].max_length - 10 && candidates[i].mean_length < candidates[i_best].mean_length)) + for (size_t i = 1; i < candidates.size(); ++ i) + if (candidates[i].coverage > candidates[i_best].coverage) i_best = i; this->angle = candidates[i_best].angle; @@ -171,42 +278,43 @@ bool BridgeDetector::detect_angle(double bridge_direction_override) #ifdef SLIC3R_DEBUG printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle)); #endif - + return true; } -std::vector<double> BridgeDetector::bridge_direction_candidates() const +std::vector<BridgeDetector::BridgeDirection> BridgeDetector::bridge_direction_candidates(bool only_from_polygon) const { + std::vector<BridgeDirection> angles; // we test angles according to configured resolution - std::vector<double> angles; - for (int i = 0; i <= PI/this->resolution; ++i) - angles.push_back(i * this->resolution); + if (!only_from_polygon) + for (int i = 0; i <= PI/this->resolution; ++i) + angles.emplace_back(i * this->resolution); // we also test angles of each bridge contour { Lines lines = to_lines(this->expolygons); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) - angles.push_back(line->direction()); + angles.emplace_back(line->direction(), true); } /* we also test angles of each open supporting edge (this finds the optimal angle for C-shaped supports) */ for (const Polyline &edge : this->_edges) if (edge.first_point() != edge.last_point()) - angles.push_back(Line(edge.first_point(), edge.last_point()).direction()); + angles.emplace_back(Line(edge.first_point(), edge.last_point()).direction()); // remove duplicates - double min_resolution = PI/180.0; // 1 degree + double min_resolution = PI/(4*180.0); // /180 = 1 degree std::sort(angles.begin(), angles.end()); for (size_t i = 1; i < angles.size(); ++i) { - if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) { + if (Slic3r::Geometry::directions_parallel(angles[i].angle, angles[i-1].angle, min_resolution)) { angles.erase(angles.begin() + i); --i; } } /* compare first value with last one and remove the greatest one (PI) in case they are parallel (PI, 0) */ - if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution)) + if (Slic3r::Geometry::directions_parallel(angles.front().angle, angles.back().angle, min_resolution)) angles.pop_back(); return angles; diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index 74f926e0b..9a81aa06f 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -43,7 +43,7 @@ private: void initialize(); struct BridgeDirection { - BridgeDirection(double a = -1.) : angle(a), coverage(0.), max_length(0.) {} + BridgeDirection(double a = -1., bool along_perimeter = false) : angle(a), coverage(0.), along_perimeter(along_perimeter){} // the best direction is the one causing most lines to be bridged (thus most coverage) bool operator<(const BridgeDirection &other) const { // Initial sort by coverage only - comparator must obey strict weak ordering @@ -51,12 +51,19 @@ private: }; double angle; double coverage; - double max_length; - double mean_length; + + bool along_perimeter; + coordf_t total_length_anchored = 0; + coordf_t median_length_anchor = 0; + coordf_t max_length_anchored = 0; + uint32_t nb_lines_anchored = 0; + coordf_t total_length_free = 0; + coordf_t max_length_free = 0; + uint32_t nb_lines_free = 0; }; public: // Get possible briging direction candidates. - std::vector<double> bridge_direction_candidates() const; + std::vector<BridgeDirection> bridge_direction_candidates(bool only_from_polygon = false) const; // Open lines representing the supporting edges. Polylines _edges; diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index b7d9a142a..cc91ce050 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -598,16 +598,18 @@ ClipperLib::PolyTree _clipper_do_pl(const ClipperLib::ClipType clipType, const P // read input ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); + //scale to have some more precision to do some Y-bugfix + scaleClipperPolygons(input_subject); + scaleClipperPolygons(input_clip); //perform y safing : if a line is on the same Y, clipper may not pick the good point. - std::set<coord_t> bad_y; - for (ClipperLib::Paths* input : {&input_subject, &input_clip} ) + //note: if not enough, next time, add some of the X coordinate (modulo it so it's contained in the scaling part) + for (ClipperLib::Paths* input : { &input_subject, &input_clip }) for (ClipperLib::Path& path : *input) { coord_t lasty = 0; for (ClipperLib::IntPoint& pt : path) { if (lasty == pt.Y) { - pt.Y+=5; //min is 3 on a certain exemple. Using 5 to have some security - bad_y.insert(pt.Y); + pt.Y += 50;// well below CLIPPER_OFFSET_POWER_OF_2 } lasty = pt.Y; } @@ -629,19 +631,31 @@ ClipperLib::PolyTree _clipper_do_pl(const ClipperLib::ClipType clipType, const P clipper.Execute(clipType, retval, fillType, fillType); //restore good y - if (!bad_y.empty()) { - std::vector<ClipperLib::PolyNode*> to_check; - to_check.push_back(&retval); - while (!to_check.empty()) { - ClipperLib::PolyNode* node = to_check.back(); - to_check.pop_back(); - for (ClipperLib::IntPoint& pt : node->Contour) { - if (bad_y.find(pt.Y) != bad_y.end()) { - pt.Y-=5; - } + std::vector<ClipperLib::PolyNode*> to_check; + to_check.push_back(&retval); + while (!to_check.empty()) { + ClipperLib::PolyNode* node = to_check.back(); + to_check.pop_back(); + for (ClipperLib::IntPoint& pit : node->Contour) { + pit.X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; + pit.Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; + pit.X >>= CLIPPER_OFFSET_POWER_OF_2; + pit.Y >>= CLIPPER_OFFSET_POWER_OF_2; + } + //note: moving in Y may create 0-length segment, so it needs an extra post-processing step to remove these duplicate points. + for (size_t idx = 1; idx < node->Contour.size(); ++idx) { + ClipperLib::IntPoint& pit = node->Contour[idx]; + ClipperLib::IntPoint& previous = node->Contour[idx - 1]; + // unscaling remove too small differences. The equality is enough. + if (pit.X == previous.X && pit.Y == previous.Y) { + node->Contour.erase(node->Contour.begin() + idx); + --idx; } - to_check.insert(to_check.end(), node->Childs.begin(), node->Childs.end()); } + //be sure you don't save 1-point paths + if (node->Contour.size() == 1) + node->Contour.clear(); + to_check.insert(to_check.end(), node->Childs.begin(), node->Childs.end()); } return retval; diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 21d3a835f..208a041d2 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -1,4 +1,5 @@ #include "Config.hpp" +#include "Preset.hpp" #include "format.hpp" #include "Utils.hpp" #include <assert.h> @@ -673,6 +674,12 @@ double ConfigBase::get_computed_value(const t_config_option_key &opt_key, int ex std::stringstream ss; ss << "You can't define an option that need " << opt_key << " without defining it!"; throw std::runtime_error(ss.str()); } + // Get option definition. + const ConfigDef* def = this->def(); + if (def == nullptr) + throw NoDefinitionException(opt_key); + const ConfigOptionDef* opt_def = def->get(opt_key); + assert(opt_def != nullptr); if (!raw_opt->is_vector()) { if (raw_opt->type() == coFloat) @@ -681,26 +688,13 @@ double ConfigBase::get_computed_value(const t_config_option_key &opt_key, int ex return static_cast<const ConfigOptionInt*>(raw_opt)->value; if (raw_opt->type() == coBool) return static_cast<const ConfigOptionBool*>(raw_opt)->value ? 1 : 0; - const ConfigOptionDef* opt_def = nullptr; const ConfigOptionPercent* cast_opt = nullptr; if (raw_opt->type() == coFloatOrPercent) { if (!static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->percent) return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->value; - // Get option definition. - const ConfigDef* def = this->def(); - if (def == nullptr) - throw NoDefinitionException(opt_key); - opt_def = def->get(opt_key); cast_opt = static_cast<const ConfigOptionFloatOrPercent*>(raw_opt); - assert(opt_def != nullptr); } if (raw_opt->type() == coPercent) { - // Get option definition. - const ConfigDef* def = this->def(); - if (def == nullptr) - throw NoDefinitionException(opt_key); - opt_def = def->get(opt_key); - assert(opt_def != nullptr); cast_opt = static_cast<const ConfigOptionPercent*>(raw_opt); } if (opt_def != nullptr) { @@ -719,7 +713,9 @@ double ConfigBase::get_computed_value(const t_config_option_key &opt_key, int ex } else { // check if it's an extruder_id array const ConfigOptionVectorBase* vector_opt = static_cast<const ConfigOptionVectorBase*>(raw_opt); + int idx = -1; if (vector_opt->is_extruder_size()) { + idx = extruder_id; if (extruder_id < 0) { const ConfigOption* opt_extruder_id = nullptr; if ((opt_extruder_id = this->option("extruder")) == nullptr) @@ -729,8 +725,15 @@ double ConfigBase::get_computed_value(const t_config_option_key &opt_key, int ex throw ConfigurationError(ss.str()); } extruder_id = opt_extruder_id->getInt(); + idx = extruder_id; } - + } else { + t_config_option_keys machine_limits = Preset::machine_limits_options(); + if (std::find(machine_limits.begin(), machine_limits.end(), opt_key) != machine_limits.end()) { + idx = 0; + } + } + if (idx >= 0) { if (raw_opt->type() == coFloats || raw_opt->type() == coInts || raw_opt->type() == coBools) return vector_opt->getFloat(extruder_id); if (raw_opt->type() == coFloatsOrPercents) { @@ -738,10 +741,6 @@ double ConfigBase::get_computed_value(const t_config_option_key &opt_key, int ex if (!opt_fl_per->values[extruder_id].percent) return opt_fl_per->values[extruder_id].value; - const ConfigDef* def = this->def(); - if (def == nullptr) - throw NoDefinitionException(opt_key); - const ConfigOptionDef* opt_def = def->get(opt_key); if (opt_def->ratio_over.empty()) return opt_fl_per->get_abs_value(extruder_id, 1); if (opt_def->ratio_over != "depends") @@ -751,10 +750,6 @@ double ConfigBase::get_computed_value(const t_config_option_key &opt_key, int ex } if (raw_opt->type() == coPercents) { const ConfigOptionPercents* opt_per = static_cast<const ConfigOptionPercents*>(raw_opt); - const ConfigDef* def = this->def(); - if (def == nullptr) - throw NoDefinitionException(opt_key); - const ConfigOptionDef* opt_def = def->get(opt_key); if (opt_def->ratio_over.empty()) return opt_per->get_abs_value(extruder_id, 1); if (opt_def->ratio_over != "depends") @@ -762,7 +757,7 @@ double ConfigBase::get_computed_value(const t_config_option_key &opt_key, int ex std::stringstream ss; ss << "ConfigBase::get_abs_value(): " << opt_key << " has no valid ratio_over to compute of"; throw ConfigurationError(ss.str()); } - } + } } std::stringstream ss; ss << "ConfigBase::get_abs_value(): "<< opt_key<<" has not a valid option type for get_abs_value()"; throw ConfigurationError(ss.str()); diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 254c02204..d20514e60 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -302,13 +302,13 @@ public: // if true, this option doesn't need to be saved, it's a computed value from an other configOption. // uint32_t because macos crash if it's a bool. and it doesn't change the size of the object because of alignment. uint32_t flags; - enum FlagsConfigOption : uint8_t { + enum FlagsConfigOption : uint32_t { FCO_PHONY = 1, FCO_EXTRUDER_ARRAY = 1 << 1, }; ConfigOption() : flags(uint32_t(0)) {} - ConfigOption(bool phony) : flags(uint32_t(FlagsConfigOption::FCO_PHONY)) {} + ConfigOption(bool phony) : flags(phony ? uint32_t(FlagsConfigOption::FCO_PHONY) : uint32_t(0)) {} virtual ~ConfigOption() {} @@ -1108,9 +1108,12 @@ private: struct FloatOrPercent { +public: double value; bool percent; + double get_abs_value(double ratio) const { return percent ? value * ratio : value; } + private: friend class cereal::access; template<class Archive> void serialize(Archive & ar) {ar(this->value); ar(this->percent); } @@ -1798,7 +1801,7 @@ public: // With which printer technology is this configuration valid? PrinterTechnology printer_technology = ptUnknown; // Category of a configuration field, from the GUI perspective. - OptionCategory category = OptionCategory::none; + OptionCategory category = OptionCategory::none; // A tooltip text shown in the GUI. std::string tooltip; // Text right from the input field, usually a unit of measurement. @@ -1825,21 +1828,23 @@ public: // Height of a multiline GUI text box. int height = -1; // Optional width of an input field. - int width = -1; + int width = -1; // Optional label width of the label (if in a line). - int label_width = -1; + int label_width = -1; // Optional label alignement to the left instead of the right bool aligned_label_left = false; // Optional label width of the sidetext (if in a line). - int sidetext_width = -1; + int sidetext_width = -1; // <min, max> limit of a numeric input. // If not set, the <min, max> is set to <INT_MIN, INT_MAX> // By setting min=0, only nonnegative input is allowed. - double min = INT_MIN; - double max = INT_MAX; + double min = INT_MIN; + double max = INT_MAX; + // To check if it's not a typo and a % is missing + FloatOrPercent max_literal = FloatOrPercent{ 0., false }; // max precision after the dot, only for display - int precision = 6; - ConfigOptionMode mode = comSimple; + int precision = 6; + ConfigOptionMode mode = comSimple; // Legacy names for this configuration option. // Used when parsing legacy configuration file. std::vector<t_config_option_key> aliases; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 60c34db7c..9f0030fa3 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -492,6 +492,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: for (ExPolygon &expoly : surface_fill.expolygons) { //set overlap polygons + f->no_overlap_expolygons.clear(); if (surface_fill.params.config->perimeters > 0) { f->overlap = surface_fill.params.config->infill_overlap.get_abs_value((perimeter_spacing + (f->get_spacing())) / 2); if (f->overlap != 0) { diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 998d91170..08bcc9d9f 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -150,14 +150,14 @@ std::pair<float, Point> Fill::_infill_direction(const Surface *surface) const double Fill::compute_unscaled_volume_to_fill(const Surface* surface, const FillParams& params) const { double polyline_volume = 0; - for (auto poly = this->no_overlap_expolygons.begin(); poly != this->no_overlap_expolygons.end(); ++poly) { - polyline_volume += params.flow.height * unscaled(unscaled(poly->area())); + for (const ExPolygon& poly : this->no_overlap_expolygons) { + polyline_volume += params.flow.height * unscaled(unscaled(poly.area())); double perimeter_gap_usage = params.config->perimeter_overlap.get_abs_value(1); // add external "perimeter gap" - double perimeter_round_gap = unscaled(poly->contour.length()) * params.flow.height * (1 - 0.25 * PI) * 0.5; + double perimeter_round_gap = unscaled(poly.contour.length()) * params.flow.height * (1 - 0.25 * PI) * 0.5; // add holes "perimeter gaps" double holes_gaps = 0; - for (auto hole = poly->holes.begin(); hole != poly->holes.end(); ++hole) { + for (auto hole = poly.holes.begin(); hole != poly.holes.end(); ++hole) { holes_gaps += unscaled(hole->length()) * params.flow.height * (1 - 0.25 * PI) * 0.5; } polyline_volume += (perimeter_round_gap + holes_gaps) * perimeter_gap_usage; diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index ab150c48c..bfac2d55b 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -36,7 +36,8 @@ public: struct FillParams { - bool full_infill() const { return density > 0.9999f && density < 1.0001f; } + // Allways consider bridge as full infill, whatever the density is. + bool full_infill() const { return flow.bridge || (density > 0.9999f && density < 1.0001f); } // Don't connect the fill lines around the inner perimeter. bool dont_connect() const { return connection == InfillConnection::icNotConnected; } diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 69615f0c4..3e557956a 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -3236,7 +3236,7 @@ std::vector<SegmentedIntersectionLine> FillScatteredRectilinear::_vert_lines_for void FillRectilinearSawtooth::fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) const { const coord_t scaled_nozzle_diam = scale_(params.flow.nozzle_diameter); - const coord_t clearance = scaled_nozzle_diam * 2; + const coord_t clearance = scaled_nozzle_diam * 2.5; const coord_t tooth_spacing_min = scaled_nozzle_diam; const coord_t tooth_spacing_max = scaled_nozzle_diam * 3; const coord_t tooth_zhop = scaled_nozzle_diam; @@ -3251,13 +3251,13 @@ FillRectilinearSawtooth::fill_surface_extrusion(const Surface *surface, const Fi ExtrusionRole good_role = getRoleFromSurfaceType(params, surface); - for (Polyline poly : polylines_out) { + for (const Polyline &poly : polylines_out) { if (!poly.is_valid()) continue; ExtrusionMultiPath3D *extrusions = new ExtrusionMultiPath3D(); extrusions->paths.push_back(ExtrusionPath3D(good_role, params.flow.mm3_per_mm() * params.flow_mult, params.flow.width * params.flow_mult, params.flow.height)); ExtrusionPath3D *current_extrusion = &(extrusions->paths.back()); - Points &pts = poly.points; + const Points &pts = poly.points; coord_t next_zhop = tooth_spacing_min + (coord_t)abs((rand() / (float)RAND_MAX) * (tooth_spacing_max - tooth_spacing_min)); size_t idx = 1; @@ -3270,21 +3270,26 @@ FillRectilinearSawtooth::fill_surface_extrusion(const Surface *surface, const Fi //do not use the "return" line nor the tangent ones. while (idx < poly.size() && maxLength > tooth_spacing_min && (next_zhop >= line_length || line_length < clearance || (std::abs(std::abs((int)(this->angle * 180 / PI) % 180) - 90) > 45 ? pts[idx].y() < pts[idx - 1].y() : pts[idx].x() < pts[idx - 1].x()))) { - if (line_length < clearance || pts[idx].x() < pts[idx - 1].x()) { - + if (line_length < clearance + || (std::abs(std::abs((int)(this->angle * 180 / PI) % 180) - 90) > 45 ? pts[idx].y() < pts[idx - 1].y() : pts[idx].x() < pts[idx - 1].x())) { + // not becasue of next_zhop too big, so don't reduce it. } else { next_zhop -= line_length; } + //update maxlength maxLength -= line_length; + //add the point current_extrusion->push_back(pts[idx], 0); + //new point & recompute length to the new point last = pts[idx]; idx++; if (idx < poly.size()) line_length = (coord_t)last.distance_to(pts[idx]); } - if (idx < poly.size() && maxLength > clearance) { + if (idx < poly.size() && maxLength > clearance /*&& line_length > scaled_nozzle_diam * 2.5*/) { //do z-hop //keep some room for the mouv - if (next_zhop > line_length - scaled_nozzle_diam * 2) next_zhop -= line_length - scaled_nozzle_diam * 2.5; + if (next_zhop > line_length - scaled_nozzle_diam * 2) + next_zhop = line_length - scaled_nozzle_diam * 2.5; last = last.interpolate(next_zhop / (double)line_length, pts[idx]); //Create point at pos if (last != pts[idx - 1]) { @@ -3297,11 +3302,12 @@ FillRectilinearSawtooth::fill_surface_extrusion(const Surface *surface, const Fi current_extrusion->push_back(last, 0); current_extrusion->push_back(last, tooth_zhop); - //add new extrusion that move a bit to let the palce for the nozzle tip + //add new extrusion that move a bit to let the place for the nozzle tip extrusions->paths.push_back(ExtrusionPath3D(good_role, 0, params.flow.nozzle_diameter / 10, params.flow.nozzle_diameter / 10)); current_extrusion = &(extrusions->paths.back()); + //add first point current_extrusion->push_back(last, tooth_zhop); - //add next point + //add next point at scaled_nozzle_diam distance line_length = (coord_t)last.distance_to(pts[idx]); last = last.interpolate(scaled_nozzle_diam / (double)line_length, pts[idx]); current_extrusion->push_back(last, tooth_zhop); @@ -3310,7 +3316,7 @@ FillRectilinearSawtooth::fill_surface_extrusion(const Surface *surface, const Fi extrusions->paths.push_back(ExtrusionPath3D(good_role, params.flow.mm3_per_mm() / std::sqrt(2), float(params.flow.width / std::sqrt(2)), params.flow.height)); current_extrusion = &(extrusions->paths.back()); current_extrusion->push_back(last, tooth_zhop); - //add next point + //add next point at scaled_nozzle_diam distance line_length = (coord_t)last.distance_to(pts[idx]); last = last.interpolate(scaled_nozzle_diam / (double)line_length, pts[idx]); current_extrusion->push_back(last, 0); @@ -3318,6 +3324,7 @@ FillRectilinearSawtooth::fill_surface_extrusion(const Surface *surface, const Fi // now go back to normal flow extrusions->paths.push_back(ExtrusionPath3D(good_role, params.flow.mm3_per_mm() * params.flow_mult, params.flow.width * params.flow_mult, params.flow.height)); current_extrusion = &(extrusions->paths.back()); + //add first point current_extrusion->push_back(last, 0); line_length = (coord_t)last.distance_to(pts[idx]); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 492bbe558..0e18211f4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -57,49 +57,59 @@ namespace Slic3r { #define _(s) Slic3r::I18N::translate(s) // Only add a newline in case the current G-code does not end with a newline. - static inline void check_add_eol(std::string& gcode) - { - if (!gcode.empty() && gcode.back() != '\n') - gcode += '\n'; - } +static inline void check_add_eol(std::string& gcode) +{ + if (!gcode.empty() && gcode.back() != '\n') + gcode += '\n'; +} - // Return true if tch_prefix is found in custom_gcode - static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) +// Return true if tch_prefix is found in custom_gcode +static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) +{ +bool ok = false; +size_t from_pos = 0; +size_t pos = 0; +while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) { + if (pos + 1 == custom_gcode.size()) + break; + from_pos = pos + 1; + // only whitespace is allowed before the command + while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') { + if (!std::isspace(custom_gcode[pos])) + goto NEXT; + } { - bool ok = false; - size_t from_pos = 0; - size_t pos = 0; - while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) { - if (pos + 1 == custom_gcode.size()) - break; - from_pos = pos + 1; - // only whitespace is allowed before the command - while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') { - if (!std::isspace(custom_gcode[pos])) - goto NEXT; - } - { - // we should also check that the extruder changes to what was expected - std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos)); - unsigned num = 0; - if (ss >> num) - ok = (num == next_extruder); - } - NEXT:; - } - return ok; + // we should also check that the extruder changes to what was expected + std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos)); + unsigned num = 0; + if (ss >> num) + ok = (num == next_extruder); } - - double get_default_acceleration(PrintConfig& config) { - double max = 0; - max = config.machine_max_acceleration_extruding.values.front(); - // on 2.3, check for enable/disable if(config.machine_limits_usage) - if (config.machine_limits_usage <= MachineLimitsUsage::Limits) - return std::min(config.default_acceleration.get_abs_value(max), max); - else - return config.default_acceleration.get_abs_value(max); + NEXT:; } + return ok; +} + +double get_default_acceleration(PrintConfig& config) { + double max = 0; + max = config.machine_max_acceleration_extruding.get_at(0); + // on 2.3, check for enable/disable if(config.machine_limits_usage) + if (config.machine_limits_usage <= MachineLimitsUsage::Limits) + return std::min(config.get_computed_value("default_acceleration"), max); + else + return config.get_computed_value("default_acceleration"); +} + +double get_travel_acceleration(PrintConfig& config) { + double max = 0; + max = config.machine_max_acceleration_travel.get_at(0); + // on 2.3, check for enable/disable if(config.machine_limits_usage) + if (config.machine_limits_usage <= MachineLimitsUsage::Limits) + return std::min(config.get_computed_value("travel_acceleration"), max); + else + return config.get_computed_value("travel_acceleration"); +} std::string OozePrevention::pre_toolchange(GCode& gcodegen) { @@ -118,8 +128,7 @@ std::string OozePrevention::pre_toolchange(GCode& gcodegen) /* We don't call gcodegen.travel_to() because we don't need retraction (it was already triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates of the destination point must not be transformed by origin nor current extruder offset. */ - gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), - "move to standby position"); + gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), 0.0, "move to standby position"); } if (gcodegen.config().standby_temperature_delta.value != 0 && gcodegen.writer().tool_is_extruder() && this->_get_temp(gcodegen) > 0) { @@ -157,7 +166,7 @@ std::string Wipe::wipe(GCode& gcodegen, bool toolchange) /* Reduce feedrate a bit; travel speed is often too high to move on existing material. Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ - double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8; + double wipe_speed = gcodegen.writer().config.get_computed_value("travel_speed") * 0.8; if(gcodegen.writer().tool_is_extruder() && gcodegen.writer().config.wipe_speed.get_at(gcodegen.writer().tool()->id()) > 0) wipe_speed = gcodegen.writer().config.wipe_speed.get_at(gcodegen.writer().tool()->id()); @@ -219,16 +228,23 @@ std::string Wipe::wipe(GCode& gcodegen, bool toolchange) } //if first layer, ask for a bigger lift for travel to object, to be on the safe side -static inline void set_extra_lift(const Layer& layer, const Print& print, GCodeWriter & writer, int extruder_id) { +static inline void set_extra_lift(const float previous_print_z, const int layer_id, const PrintConfig& print_config, GCodeWriter & writer, int extruder_id) { //if first layer, ask for a bigger lift for travel to object, to be on the safe side - if (layer.id() == 0 && - (print.config().retract_lift.get_at(extruder_id) != 0 || print.config().retract_lift_first_layer.get_at(extruder_id))) { - //get biggest first layer height and set extra lift for first travel, to be safe. - double extra_lift_value = 0; - for (const PrintObject* obj : print.objects()) - extra_lift_value = std::max(extra_lift_value, print.get_object_first_layer_height(*obj)); - writer.set_extra_lift(extra_lift_value * 2); + double extra_lift_value = 0; + if (print_config.lift_min.value > 0) { + double retract_lift = 0; + //get the current lift (imo, should be given by the writer... i'm duplicating stuff here) + if(//(previous_print_z == 0 && print_config.retract_lift_above.get_at(writer.tool()->id()) == 0) || + print_config.retract_lift_above.get_at(writer.tool()->id()) <= previous_print_z + EPSILON + || (layer_id == 0 && print_config.retract_lift_first_layer.get_at(writer.tool()->id()))) + retract_lift = writer.tool()->retract_lift(); + // see if it's positive + if (previous_print_z + extra_lift_value + retract_lift < print_config.lift_min.value) { + extra_lift_value = print_config.lift_min.value - previous_print_z - retract_lift; + } } + if(extra_lift_value > 0) + writer.set_extra_lift(extra_lift_value); } static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt) @@ -518,8 +534,10 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec gap_over_supports = std::max(0., gap_over_supports); // Not a soluble support, double support_layer_height_min = 1000000.; - for (auto lh : object.print()->config().min_layer_height.values) - support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh)); + const ConfigOptionFloatsOrPercents& min_layer_height = object.print()->config().min_layer_height; + const ConfigOptionFloats& nozzle_diameter = object.print()->config().nozzle_diameter; + for(int extr_id = 0; extr_id < min_layer_height.values.size(); ++extr_id) + support_layer_height_min = std::min(support_layer_height_min, std::max(nozzle_diameter.values[extr_id]/40, min_layer_height.get_abs_value(extr_id, nozzle_diameter.values[extr_id]))); gap_over_supports += support_layer_height_min; } @@ -1169,12 +1187,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); - const ConfigOptionBool *thumbnails_with_bed = print.full_print_config().option<ConfigOptionBool>("thumbnails_with_bed"); - DoExport::export_thumbnails_to_file(thumbnail_cb, - print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, - thumbnails_with_bed==nullptr? false:thumbnails_with_bed->value, - [this, file](const char* sz) { this->_write(file, sz); }, - [&print]() { print.throw_if_canceled(); }); + //print thumbnails at the start unless requested at the end. + const ConfigOptionBool* thumbnails_with_bed = print.full_print_config().option<ConfigOptionBool>("thumbnails_with_bed"); + const ConfigOptionBool* thumbnails_end_file = print.full_print_config().option<ConfigOptionBool>("thumbnails_end_file"); + if(!thumbnails_end_file || !thumbnails_end_file->value) + DoExport::export_thumbnails_to_file(thumbnail_cb, + print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, + thumbnails_with_bed==nullptr? false:thumbnails_with_bed->value, + [this, file](const char* sz) { this->_write(file, sz); }, + [&print]() { print.throw_if_canceled(); }); // Write notes (content of the Print Settings tab -> Notes) { @@ -1393,6 +1414,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Write the custom start G-code _writeln(file, start_gcode); + m_last_pos_defined = false; + + //flush FanMover buffer to avoid modifying the start gcode if it's manual. + if (this->config().start_gcode_manual && this->m_fan_mover.get() != nullptr) { + std::string to_write = this->m_fan_mover.get()->process_gcode("", true); + const char* gcode_to_write = to_write.c_str(); + // writes string to file + fwrite(gcode_to_write, 1, ::strlen(gcode_to_write), file); + } // Process filament-specific gcode. /* if (has_wipe_tower) { @@ -1502,8 +1532,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // This happens before Z goes down to layer 0 again, so that no collision happens hopefully. m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_avoid_crossing_perimeters.use_external_mp_once(); + set_extra_lift(m_last_layer_z, 0, print.config(), m_writer, initial_extruder_id); _write(file, this->retract()); std::string gcode; + //go to origin of the next object (it's 0,0 because we shifted the origin to it) Polyline polyline = this->travel_to(gcode, Point(0, 0), erNone); this->write_travel_to(gcode, polyline, "move to origin position for next object"); _write(file, gcode); @@ -1519,6 +1551,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false); this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false); _writeln(file, between_objects_gcode); + } else { + set_extra_lift(0, 0, print.config(), m_writer, initial_extruder_id); } //reinit the seam placer on the new object m_seam_placer.init(print); @@ -1543,6 +1577,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu m_second_layer_things_done = false; prev_object = &object; } + set_extra_lift(m_last_layer_z, prev_object->layers().back()->id(), print.config(), m_writer, initial_extruder_id /* osef, it's only for the lift_min */); } else { // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. @@ -1634,7 +1669,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config)); } else { for (const std::string& end_gcode : print.config().end_filament_gcode.values) { - int extruder_id = (uint16_t)(&end_gcode - &print.config().end_filament_gcode.values.front()); + int extruder_id = (uint16_t)(&end_gcode - &print.config().end_filament_gcode.get_at(0)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); config.set_key_value("previous_extruder", new ConfigOptionInt(extruder_id)); config.set_key_value("next_extruder", new ConfigOptionInt(0)); @@ -1676,6 +1711,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write(file, full_config, true); } print.throw_if_canceled(); + + //print thumbnails at the end instead of the start if requested + if (thumbnails_end_file && thumbnails_end_file->value) + DoExport::export_thumbnails_to_file(thumbnail_cb, + print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, + thumbnails_with_bed == nullptr ? false : thumbnails_with_bed->value, + [this, file](const char* sz) { this->_write(file, sz); }, + [&print]() { print.throw_if_canceled(); }); + print.throw_if_canceled(); } std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, uint16_t current_extruder_id, DynamicConfig *config_override) @@ -1697,7 +1741,7 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std func_add_colour("thumbnails_color_int", config().thumbnails_color); std::string gcode = m_placeholder_parser.process(templ, current_extruder_id, config_override, &m_placeholder_parser_context); - if (!gcode.empty() && m_config.gcode_comments) { + if (!gcode.empty() && (m_config.gcode_comments || m_config.fan_speedup_time.value != 0 || m_config.fan_kickstart.value != 0 )) { gcode = "; custom gcode: " + name + "\n" + gcode; check_add_eol(gcode); gcode += "; custom gcode end: "+ name + "\n"; @@ -1791,60 +1835,60 @@ void GCode::print_machine_envelope(FILE *file, Print &print) if (print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) { if (std::set<uint8_t>{gcfMarlin, gcfLerdge, gcfRepetier, gcfRepRap, gcfSprinter}.count(print.config().gcode_flavor.value) > 0) _write_format(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", - int(print.config().machine_max_acceleration_x.values.front() + 0.5), - int(print.config().machine_max_acceleration_y.values.front() + 0.5), - int(print.config().machine_max_acceleration_z.values.front() + 0.5), - int(print.config().machine_max_acceleration_e.values.front() + 0.5)); + int(print.config().machine_max_acceleration_x.get_at(0) + 0.5), + int(print.config().machine_max_acceleration_y.get_at(0) + 0.5), + int(print.config().machine_max_acceleration_z.get_at(0) + 0.5), + int(print.config().machine_max_acceleration_e.get_at(0) + 0.5)); if (std::set<uint8_t>{gcfRepetier}.count(print.config().gcode_flavor.value) > 0) _write_format(file, "M202 X%d Y%d ; sets maximum travel acceleration\n", - int(print.config().machine_max_acceleration_travel.values.front() + 0.5), - int(print.config().machine_max_acceleration_travel.values.front() + 0.5)); + int(print.config().machine_max_acceleration_travel.get_at(0) + 0.5), + int(print.config().machine_max_acceleration_travel.get_at(0) + 0.5)); if (std::set<uint8_t>{gcfMarlin, gcfLerdge, gcfRepetier, gcfSmoothie, gcfSprinter}.count(print.config().gcode_flavor.value) > 0) _write_format(file, (print.config().gcode_flavor.value == gcfMarlin || print.config().gcode_flavor.value == gcfLerdge || print.config().gcode_flavor.value == gcfSmoothie) ? "M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec\n" : "M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/min\n", - int(print.config().machine_max_feedrate_x.values.front() + 0.5), - int(print.config().machine_max_feedrate_y.values.front() + 0.5), - int(print.config().machine_max_feedrate_z.values.front() + 0.5), - int(print.config().machine_max_feedrate_e.values.front() + 0.5)); + int(print.config().machine_max_feedrate_x.get_at(0) + 0.5), + int(print.config().machine_max_feedrate_y.get_at(0) + 0.5), + int(print.config().machine_max_feedrate_z.get_at(0) + 0.5), + int(print.config().machine_max_feedrate_e.get_at(0) + 0.5)); if (print.config().gcode_flavor.value == gcfRepRap) { _write_format(file, "M203 X%d Y%d Z%d E%d I%d; sets maximum feedrates, mm/min\n", - int(print.config().machine_max_feedrate_x.values.front() + 0.5), - int(print.config().machine_max_feedrate_y.values.front() + 0.5), - int(print.config().machine_max_feedrate_z.values.front() + 0.5), - int(print.config().machine_max_feedrate_e.values.front() + 0.5), - int(print.config().machine_min_extruding_rate.values.front() + 0.5)); + int(print.config().machine_max_feedrate_x.get_at(0) + 0.5), + int(print.config().machine_max_feedrate_y.get_at(0) + 0.5), + int(print.config().machine_max_feedrate_z.get_at(0) + 0.5), + int(print.config().machine_max_feedrate_e.get_at(0) + 0.5), + int(print.config().machine_min_extruding_rate.get_at(0) + 0.5)); } if (std::set<uint8_t>{gcfMarlin, gcfLerdge}.count(print.config().gcode_flavor.value) > 0) _write_format(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", - int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), - int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), - int(print.config().machine_max_acceleration_travel.values.front() + 0.5)); + int(print.config().machine_max_acceleration_extruding.get_at(0) + 0.5), + int(print.config().machine_max_acceleration_retracting.get_at(0) + 0.5), + int(print.config().machine_max_acceleration_travel.get_at(0) + 0.5)); if (std::set<uint8_t>{gcfRepRap, gcfKlipper, gcfSprinter}.count(print.config().gcode_flavor.value) > 0) _write_format(file, "M204 P%d T%d ; sets acceleration (P, T), mm/sec^2\n", - int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), - int(print.config().machine_max_acceleration_travel.values.front() + 0.5)); + int(print.config().machine_max_acceleration_extruding.get_at(0) + 0.5), + int(print.config().machine_max_acceleration_travel.get_at(0) + 0.5)); if (std::set<uint8_t>{gcfRepRap}.count(print.config().gcode_flavor.value) > 0) _write_format(file, "M566 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/min\n", - print.config().machine_max_jerk_x.values.front() * 60, - print.config().machine_max_jerk_y.values.front() * 60, - print.config().machine_max_jerk_z.values.front() * 60, - print.config().machine_max_jerk_e.values.front() * 60); + print.config().machine_max_jerk_x.get_at(0) * 60, + print.config().machine_max_jerk_y.get_at(0) * 60, + print.config().machine_max_jerk_z.get_at(0) * 60, + print.config().machine_max_jerk_e.get_at(0) * 60); if (std::set<uint8_t>{gcfMarlin, gcfLerdge, gcfRepetier}.count(print.config().gcode_flavor.value) > 0) _write_format(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", - print.config().machine_max_jerk_x.values.front(), - print.config().machine_max_jerk_y.values.front(), - print.config().machine_max_jerk_z.values.front(), - print.config().machine_max_jerk_e.values.front()); + print.config().machine_max_jerk_x.get_at(0), + print.config().machine_max_jerk_y.get_at(0), + print.config().machine_max_jerk_z.get_at(0), + print.config().machine_max_jerk_e.get_at(0)); if (std::set<uint8_t>{gcfSmoothie}.count(print.config().gcode_flavor.value) > 0) _write_format(file, "M205 X%.2lf Z%.2lf ; sets the jerk limits, mm/sec\n", - std::min(print.config().machine_max_jerk_x.values.front(), - print.config().machine_max_jerk_y.values.front()), - print.config().machine_max_jerk_z.values.front()); + std::min(print.config().machine_max_jerk_x.get_at(0), + print.config().machine_max_jerk_y.get_at(0)), + print.config().machine_max_jerk_z.get_at(0)); if (std::set<uint8_t>{gcfMarlin, gcfLerdge, gcfRepetier}.count(print.config().gcode_flavor.value) > 0) _write_format(file, "M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n", - int(print.config().machine_min_extruding_rate.values.front() + 0.5), - int(print.config().machine_min_travel_rate.values.front() + 0.5)); + int(print.config().machine_min_extruding_rate.get_at(0) + 0.5), + int(print.config().machine_min_travel_rate.get_at(0) + 0.5)); } } @@ -2242,7 +2286,22 @@ void GCode::process_layer( print.config().before_layer_gcode.value, m_writer.tool()->id(), &config) + "\n"; } - gcode += this->change_layer(print_z); // this will increase m_layer_index + // print z move to next layer UNLESS + // if it's going to the first layer, then we may want to dealy the move in these condition: + // there is no "after layer change gcode" and it's the first move from the unknown + if (print.config().layer_gcode.value.empty() && !m_last_pos_defined && m_config.start_gcode_manual && ( + // there is a lift (on the first llyer, so the first move will bring us to the required height + (m_writer.tool()->retract_lift() > 0 && (m_config.retract_lift_above.get_at(m_writer.tool()->id()) == 0 || BOOL_EXTRUDER_CONFIG(retract_lift_first_layer))) + || // or lift_min is higher than the first layer height. + m_config.lift_min.value > layer.print_z + )) + m_delayed_layer_change = this->change_layer(print_z); //HACK for superslicer#1775 + else { + //extra lift on layer change if multiple objects + if(single_object_instance_idx == size_t(-1) && (support_layer != nullptr || layers.size() > 1)) + set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, first_extruder_id); + gcode += this->change_layer(print_z); // this will increase m_layer_index + } m_layer = &layer; if (! print.config().layer_gcode.value.empty()) { DynamicConfig config; @@ -2457,9 +2516,12 @@ void GCode::process_layer( m_last_processor_extrusion_role = erWipeTower; //if first layer, ask for a bigger lift for travel to object, to be on the safe side - set_extra_lift(layer, print, m_writer, extruder_id); + if(single_object_instance_idx == size_t(-1) && m_layer_index == 0) + set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id); if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { + // before going to and from a global skirt, please ensure you are a a safe height + set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id); const std::pair<size_t, size_t> loops = loops_it->second; this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); @@ -2483,27 +2545,30 @@ void GCode::process_layer( // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). if (first_layer && loops.first == 0) m_avoid_crossing_perimeters.disable_once(); + // before going to and from a global skirt, please ensure you are a a safe height + set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id); } // Extrude brim with the extruder of the 1st region. if (! m_brim_done) { - //if first layer, ask for a bigger lift for travel to object, to be on the safe side - set_extra_lift(layer, print, m_writer, extruder_id); - this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); - gcode += this->extrude_entity(print.brim(), "Brim", m_config.support_material_speed.value); + for (const ExtrusionEntity* brim_entity : print.brim().entities) { + //if first layer, ask for a bigger lift for travel to each brim, to be on the safe side + set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id); + gcode += this->extrude_entity(*brim_entity, "Brim", m_config.support_material_speed.value); + } m_brim_done = true; m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. m_avoid_crossing_perimeters.disable_once(); + //to go to the object-only skirt or brim, or to the object (May be overriden here but I don't care) + set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id); } //extrude object-only skirt //TODO: use it also for wiping like the other one (as they are exlusiev) if (single_object_instance_idx != size_t(-1) && !layers.front().object()->skirt().empty() && extruder_id == layer_tools.extruders.front()) { - //if first layer, ask for a bigger lift for travel to object, to be on the safe side - set_extra_lift(layer, print, m_writer, extruder_id); const PrintObject *print_object = layers.front().object(); this->set_origin(unscale(print_object->instances()[single_object_instance_idx].shift)); @@ -2519,8 +2584,6 @@ void GCode::process_layer( //extrude object-only brim if (single_object_instance_idx != size_t(-1) && !layers.front().object()->brim().empty() && extruder_id == layer_tools.extruders.front()) { - //if first layer, ask for a bigger lift for travel to object, to be on the safe side - set_extra_lift(layer, print, m_writer, extruder_id); const PrintObject *print_object = layers.front().object(); this->set_origin(unscale(print_object->instances()[single_object_instance_idx].shift)); @@ -2545,7 +2608,7 @@ void GCode::process_layer( for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { if (is_anything_overridden && print_wipe_extrusions == 0) gcode+="; PURGING FINISHED\n"; - + bool first_object = true; for (InstanceToPrint &instance_to_print : instances_to_print) { m_config.apply(instance_to_print.print_object.config(), true); m_layer = layers[instance_to_print.layer_id].layer(); @@ -2571,8 +2634,11 @@ void GCode::process_layer( m_gcode_label_objects_start += std::string("M486 S") + std::to_string(instance_plater_id) + "\n"; } } - //if first layer, ask for a bigger lift for travel to object, to be on the safe side - set_extra_lift(layer, print, m_writer, extruder_id); + // ask for a bigger lift for travel to object when moving to another object + if (single_object_instance_idx == size_t(-1) && !first_object) + set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id); + else + first_object = false; // When starting a new object, use the external motion planner for the first travel move. const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset); @@ -2903,9 +2969,9 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s } //all in unscaled coordinates (hence why it's coordf_t and not coord_t) - const coordf_t min_height = EXTRUDER_CONFIG_WITH_DEFAULT(min_layer_height, this->m_layer->height); + const coordf_t min_height = m_config.min_layer_height.get_abs_value(m_writer.tool()->id(), m_config.nozzle_diameter.get_at(m_writer.tool()->id())); const coordf_t bot_init_z = - this->m_layer->height; - //const coordf_t bot_last_z = bot_init_z + this->m_layer->height - EXTRUDER_CONFIG(min_layer_height); + //const coordf_t bot_last_z = bot_init_z + this->m_layer->height - m_config.min_layer_height.get_abs_value(m_writer.tool()->id(), m_config.nozzle_diameter.get_at(m_writer.tool()->id())); const coordf_t init_z = bot_init_z + min_height; //const coordf_t last_z = bot_init_z + this->m_layer->height; @@ -3090,7 +3156,7 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s inward_point.rotate(angle, paths.front().polyline.points.front()); // generate the travel move - gcode += m_writer.travel_to_xy(this->point_to_gcode(inward_point), "move inwards before travel"); + gcode += m_writer.travel_to_xy(this->point_to_gcode(inward_point), 0.0, "move inwards before travel"); } return gcode; @@ -3145,7 +3211,7 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s //but not for the first layer && this->m_layer->id() > 0 //exclude if min_layer_height * 2 > layer_height (increase from 2 to 3 because it's working but uses in-between) - && this->m_layer->height >= EXTRUDER_CONFIG_WITH_DEFAULT(min_layer_height, 0) * 2 - EPSILON + && this->m_layer->height >= m_config.min_layer_height.get_abs_value(m_writer.tool()->id(), m_config.nozzle_diameter.get_at(m_writer.tool()->id())) * 2 - EPSILON ) { return extrude_loop_vase(original_loop, description, speed, lower_layer_edge_grid); } @@ -3269,7 +3335,7 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s for (Point& pt : path.polyline.points) { prev_point = current_point; current_point = pt; - gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), config().gcode_comments ? "; extra wipe" : ""); + gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), 0.0, config().gcode_comments ? "; extra wipe" : ""); } } } @@ -3304,7 +3370,7 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s Point pt = ((nd * nd >= l2) ? next_pos : (current_pos + vec_dist * (nd / sqrt(l2)))).cast<coord_t>(); pt.rotate(angle, current_point); // generate the travel move - gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel"); + gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), 0.0, "move inwards before travel"); } return gcode; @@ -3840,7 +3906,7 @@ double_t GCode::_compute_speed_mm_per_sec(const ExtrusionPath& path, double spee if (first_layer_speed > 0) speed = std::min(first_layer_speed, speed); } - double first_layer_min_speed = m_config.first_layer_min_speed.get_abs_value(base_speed); + double first_layer_min_speed = m_config.first_layer_min_speed.value; speed = std::max(first_layer_min_speed, speed); } // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) @@ -3866,23 +3932,24 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string // adjust acceleration, inside the travel to set the deceleration (unless it's deactivated) double acceleration = get_default_acceleration(m_config); - double travel_acceleration = m_writer.get_acceleration(); + double max_acceleration = std::numeric_limits<double>::max(); + // on 2.3, check for enable/disable if(config.machine_limits_usage) + if (m_config.machine_limits_usage <= MachineLimitsUsage::Limits) + max_acceleration = m_config.machine_max_acceleration_extruding.get_at(0); + double travel_acceleration = get_travel_acceleration(m_config); if(acceleration > 0){ if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) { - acceleration = m_config.first_layer_acceleration.get_abs_value(acceleration); + acceleration = std::min(max_acceleration, m_config.first_layer_acceleration.get_abs_value(acceleration)); } else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) { - acceleration = m_config.perimeter_acceleration.get_abs_value(acceleration); + acceleration = std::min(max_acceleration, m_config.perimeter_acceleration.get_abs_value(acceleration)); } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role()) && path.role() != erOverhangPerimeter) { - acceleration = m_config.bridge_acceleration.get_abs_value(acceleration); + acceleration = std::min(max_acceleration, m_config.bridge_acceleration.get_abs_value(acceleration)); } else if (m_config.infill_acceleration.value > 0 && is_infill(path.role())) { - acceleration = m_config.infill_acceleration.get_abs_value(acceleration); + acceleration = std::min(max_acceleration, m_config.infill_acceleration.get_abs_value(acceleration)); } - if (m_config.travel_acceleration.value > 0) - travel_acceleration = m_config.travel_acceleration.get_abs_value(acceleration); } - - if (travel_acceleration == acceleration) { + if (travel_acceleration <= acceleration || travel_acceleration == 0 || acceleration == 0) { m_writer.set_acceleration((uint32_t)floor(acceleration + 0.5)); // go to first point of extrusion path (stop at midpoint to let us set the decel speed) if (!m_last_pos_defined || m_last_pos != path.first_point()) { @@ -3894,7 +3961,10 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string if (!m_last_pos_defined || m_last_pos != path.first_point()) { Polyline poly_start = this->travel_to(gcode, path.first_point(), path.role()); coordf_t length = poly_start.length(); - if (length > SCALED_EPSILON) { + // if length is enough, it's not the hack for first move, and the travel accel is different than the normal accel + // then cut the travel in two to change the accel in-between + // TODO: compute the real point where it should be cut, considering an infinite max speed. + if (length > std::max(coordf_t(SCALED_EPSILON), scale_d(m_config.min_length)) && m_last_pos_defined) { Polyline poly_end; coordf_t min_length = scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.5)) * 20; if (poly_start.size() > 2 && length > min_length * 3) { @@ -3933,7 +4003,18 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string _add_object_change_labels(gcode); // compensate retraction - gcode += this->unretract(); + if (m_delayed_layer_change.empty()) { + gcode += m_writer.unlift();//this->unretract(); + } else { + //check if an unlift happens + std::string unlift = m_writer.unlift(); + if (unlift.empty()) { + unlift = m_delayed_layer_change; + } + m_delayed_layer_change.clear(); + gcode += unlift; + } + gcode += m_writer.unretract(); speed = _compute_speed_mm_per_sec(path, speed); double F = speed * 60; // convert mm/sec to mm/min @@ -4054,19 +4135,20 @@ void GCode::_add_object_change_labels(std::string& gcode) { // This method accepts &point in print coordinates. Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole role) { - /* Define the travel move as a line between current position and the taget point. + /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by this->origin in order to get G-code coordinates. */ Polyline travel { this->last_pos(), point }; // check whether a straight travel move would need retraction - bool needs_retraction = this->needs_retraction(travel, role); + bool will_cross_perimeter = this->can_cross_perimeter(travel); + bool needs_retraction = will_cross_perimeter && this->needs_retraction(travel, role); // check whether wipe could be disabled without causing visible stringing bool could_be_wipe_disabled = false; - // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a + // if a retraction would be needed (with a low min_dist threshold), try to use avoid_crossing_perimeters to plan a // multi-hop travel path inside the configuration space - if (needs_retraction + if (will_cross_perimeter && this->needs_retraction(travel, role, scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4)) * 3) && m_config.avoid_crossing_perimeters && ! m_avoid_crossing_perimeters.disabled_once() && m_avoid_crossing_perimeters.is_init() @@ -4094,10 +4176,10 @@ Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole append(retract_travel.points, travel.points); travel = std::move(retract_travel); } - } else + } else { // Reset the wipe path when traveling, so one would not wipe along an old path. m_wipe.reset_path(); - + } //if needed, write the gcode_label_objects_end then gcode_label_objects_start _add_object_change_labels(gcode); @@ -4107,16 +4189,82 @@ Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole void GCode::write_travel_to(std::string &gcode, const Polyline& travel, std::string comment) { - // use G1 because we rely on paths being straight (G0 may make round paths) - if (travel.size() >= 2) { + if (travel.size() > 4) { + //ensure that you won't overload the firmware. + // travel are strait lines, but with avoid_crossing_perimeters, there can be many points. Reduce speed instead of deleting points, as it's already optimised as much as possible, even if it can be a bit more => TODO?) + // we are using a window of 10 moves. + const double max_gcode_per_second = this->config().max_gcode_per_second.value; + coordf_t dist_next_10_moves = 0; + size_t idx_10 = 1; + size_t idx_print = 1; + const double max_speed = m_config.get_computed_value("travel_speed"); + double current_speed = max_speed; + for (; idx_10 < travel.size() && idx_10 < 11; ++idx_10) { + dist_next_10_moves += travel.points[idx_10 - 1].distance_to(travel.points[idx_10]); + } + + while (idx_10 < travel.size()) { + //compute speed + double time_for10_moves = unscaled(dist_next_10_moves) / current_speed; + double min_time = 10 / max_gcode_per_second; + double ratio_speed = time_for10_moves / min_time; + double possible_speed = current_speed * ratio_speed; + + //write + if (possible_speed < max_speed) { + if(ratio_speed < 0.95 | ratio_speed > 1.05) + current_speed = possible_speed; + } else if (current_speed < max_speed) { + current_speed = max_speed; + } + gcode += m_writer.travel_to_xy( + this->point_to_gcode(travel.points[idx_print]), + current_speed>2 ? double(uint32_t(current_speed * 60)) : current_speed * 60, + comment); + + //update for next move + dist_next_10_moves -= travel.points[idx_print - 1].distance_to(travel.points[idx_print]); + dist_next_10_moves += travel.points[idx_10 - 1].distance_to(travel.points[idx_10]); + idx_10++; + idx_print++; + } + + if (travel.size() < 10) { + //compute a global speed + double time_for10_moves = unscaled(dist_next_10_moves) / current_speed; + double min_time = travel.size() / max_gcode_per_second; + double ratio_speed = time_for10_moves / min_time; + current_speed *= ratio_speed; + current_speed = std::min(max_speed, current_speed); + } else { + //compute speed for the end + double time_for10_moves = unscaled(dist_next_10_moves) / current_speed; + double min_time = 10 / max_gcode_per_second; + double ratio_speed = time_for10_moves / min_time; + current_speed *= ratio_speed; + current_speed = std::min(max_speed, current_speed); + } + + //finish writing moves at current speed + for (; idx_print < travel.size(); ++idx_print) + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[idx_print]), + current_speed > 2 ? double(uint32_t(current_speed * 60)) : current_speed * 60, + comment); + this->set_last_pos(travel.points.back()); + } else if (travel.size() >= 2) { for (size_t i = 1; i < travel.size(); ++i) - gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); + // use G1 because we rely on paths being straight (G0 may make round paths) + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), 0.0, comment); this->set_last_pos(travel.points.back()); } } -bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) + +bool GCode::needs_retraction(const Polyline& travel, ExtrusionRole role /*=erNone*/, coordf_t max_min_dist /*=0*/) { - if (travel.length() < scale_(EXTRUDER_CONFIG_WITH_DEFAULT(retract_before_travel, 0))) { + coordf_t min_dist = scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(retract_before_travel, 0)); + if (max_min_dist > 0) + min_dist = std::min(max_min_dist, min_dist); + if (travel.length() < min_dist) { // skip retraction if the move is shorter than the configured threshold return false; } @@ -4128,15 +4276,43 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) // skip retraction if this is a travel move inside a support material island //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material // at the end of the extrusion path! - return false; + return false; } - if (m_config.only_retract_when_crossing_perimeters && m_layer != nullptr && - m_config.fill_density.value > 0 && m_layer->any_internal_region_slice_contains(travel)) + return true; +} + +bool GCode::can_cross_perimeter(const Polyline& travel) +{ + if(m_layer != nullptr) + if ( (m_config.only_retract_when_crossing_perimeters && m_config.fill_density.value > 0) || m_config.avoid_crossing_perimeters) + { + //test && m_layer->any_internal_region_slice_contains(travel) // Skip retraction if travel is contained in an internal slice *and* // internal infill is enabled (so that stringing is entirely not visible). - //FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. - return false; + //note: any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. + //bool inside = false; + //BoundingBox bbtravel(travel.points); + //for (const BoundingBox &bbox : m_layer->lslices_bboxes) { + // inside = bbox.overlap(bbtravel); + // if(inside) break; + //} + ////have to do a bit more work to be sure + //if (inside) { + //contained inside at least one bb + //construct m_layer_slices_offseted if needed + if (m_layer_slices_offseted.layer != m_layer) { + m_layer_slices_offseted.layer = m_layer; + m_layer_slices_offseted.diameter = scale_t(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4)); + m_layer_slices_offseted.slices = offset_ex(m_layer->lslices, - m_layer_slices_offseted.diameter * 1.5); + } + // test if a expoly contains the entire travel + for (const ExPolygon &poly : m_layer_slices_offseted.slices) + if (poly.contains(travel)) { + return false; + } + //} + } // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply return true; @@ -4164,10 +4340,12 @@ std::string GCode::retract(bool toolchange) methods even if we performed wipe, since this will ensure the entire retraction length is honored in case wipe path was too short. */ gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); - bool need_lift = !m_writer.tool_is_extruder() || toolchange || (BOOL_EXTRUDER_CONFIG(retract_lift_first_layer) && m_config.print_retract_lift.value != 0 && this->m_layer_index == 0); - bool last_fill_extusion_role_top_infill = (this->m_last_extrusion_role == ExtrusionRole::erTopSolidInfill); + bool need_lift = !m_writer.tool_is_extruder() || toolchange + || (BOOL_EXTRUDER_CONFIG(retract_lift_first_layer) && m_config.print_retract_lift.value != 0 && this->m_layer_index == 0) + || this->m_writer.get_extra_lift() > 0; + bool last_fill_extusion_role_top_infill = (this->m_last_extrusion_role == ExtrusionRole::erTopSolidInfill || this->m_last_extrusion_role == ExtrusionRole::erIroning); if(this->m_last_extrusion_role == ExtrusionRole::erGapFill) - last_fill_extusion_role_top_infill = (this->m_last_notgapfill_extrusion_role == ExtrusionRole::erTopSolidInfill); + last_fill_extusion_role_top_infill = (this->m_last_notgapfill_extrusion_role == ExtrusionRole::erTopSolidInfill || this->m_last_notgapfill_extrusion_role == ExtrusionRole::erIroning); if (!need_lift && m_config.print_retract_lift.value != 0) { if (EXTRUDER_CONFIG_WITH_DEFAULT(retract_lift_top, "") == "Not on top") need_lift = !last_fill_extusion_role_top_infill; @@ -4177,12 +4355,7 @@ std::string GCode::retract(bool toolchange) need_lift = true; } if (need_lift) - if (m_writer.tool()->retract_length() > 0 - || m_config.use_firmware_retraction - || (!m_writer.tool_is_extruder() && m_writer.tool()->retract_lift() != 0) - || (BOOL_EXTRUDER_CONFIG(retract_lift_first_layer) && this->m_layer_index == 0) - ) - gcode += m_writer.lift(); + gcode += m_writer.lift(this->m_layer_index); return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index a5bfb881e..8c2bc4242 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -318,7 +318,8 @@ private: Polyline travel_to(std::string& gcode, const Point &point, ExtrusionRole role); void write_travel_to(std::string& gcode, const Polyline& travel, std::string comment); - bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); + bool can_cross_perimeter(const Polyline& travel); + bool needs_retraction(const Polyline& travel, ExtrusionRole role = erNone, coordf_t max_min_dist = 0); std::string retract(bool toolchange = false); std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } std::string set_extruder(uint16_t extruder_id, double print_z, bool no_toolchange = false); @@ -349,6 +350,8 @@ private: // Markers for the Pressure Equalizer to recognize the extrusion type. // The Pressure Equalizer removes the markers from the final G-code. bool m_enable_extrusion_role_markers; + // HACK to avoid multiple Z move. + std::string m_delayed_layer_change; // Keeps track of the last extrusion role passed to the processor ExtrusionRole m_last_processor_extrusion_role; // How many times will change_layer() be called? @@ -359,6 +362,11 @@ private: // Current layer processed. Insequential printing mode, only a single copy will be printed. // In non-sequential mode, all its copies will be printed. const Layer* m_layer; + // For crossing perimeter retraction detection (contain the layer & nozzle widdth used to construct it) + // !!!! not thread-safe !!!! if threaded per layer, please store it in the thread. + struct SliceOffsetted { + ExPolygons slices; const Layer* layer; coord_t diameter; + } m_layer_slices_offseted{ {},nullptr, 0}; double m_volumetric_speed; // Support for the extrusion role markers. Which marker is active? ExtrusionRole m_last_extrusion_role; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 0a3a3eabb..1d40a62bd 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -268,14 +268,97 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B return simplified_path; } +bool is_in_internal_boundary(const AvoidCrossingPerimeters::Boundary& boundary, const Point& pt) { + for (const std::pair<ExPolygon, ExPolygons>& bound : boundary.boundary_growth) + for (const ExPolygon& poly : bound.second) + if (poly.contains(pt)) + return true; + return false; +} + +bool find_point_on_boundary(Point& pt_to_move, const AvoidCrossingPerimeters::Boundary& boundary, coord_t max_dist) { + + if (!is_in_internal_boundary(boundary, pt_to_move)) { + //get nearest point + EdgeGrid::Grid::ClosestPointResult pt_closest = boundary.grid.closest_point(pt_to_move, max_dist); + Point contour_pt; + if (pt_closest.contour_idx == size_t(-1)) { + //manual search on edges + bool found = false; + for (const std::pair<ExPolygon, ExPolygons>& bound : boundary.boundary_growth) + if (bound.first.contains(pt_to_move)) { + coordf_t dist2 = std::numeric_limits<coordf_t>::max(); + for (const Polygon& poly : to_polygons(bound.second)) { + Point test_point = *poly.closest_point(pt_to_move); + coordf_t dist2_test = test_point.distance_to_square(pt_to_move); + if (dist2_test < dist2) { + dist2 = dist2_test; + contour_pt = test_point; + found = true; + } + } + if(std::sqrt(dist2) > max_dist) + for (const Polygon& poly : to_polygons(bound.second)) { + Point test_point = pt_to_move.projection_onto(poly); + coordf_t dist2_test = test_point.distance_to_square(pt_to_move); + if (dist2_test < dist2) { + dist2 = dist2_test; + contour_pt = test_point; + found = true; + } + } + break; + } + if (!found) { + return false; + } + } else { + const Slic3r::Points& pts = *boundary.grid.contours()[pt_closest.contour_idx]; + contour_pt = pts[pt_closest.start_point_idx].interpolate(pt_closest.t, pts[(pt_closest.start_point_idx + 1 == pts.size()) ? 0 : pt_closest.start_point_idx + 1]); + } + //push it a bit to be sure it's inside + Line l{ pt_to_move, contour_pt }; + l.extend_end(SCALED_EPSILON); + pt_to_move = l.b; + } + return true; +} + // Called by avoid_perimeters() and by simplify_travel_heuristics(). static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary, - const Point &start, - const Point &end, + const Point &real_start, + const Point &real_end, + coord_t extrusion_spacing, std::vector<TravelPoint> &result_out) { const Polygons &boundaries = boundary.boundaries; const EdgeGrid::Grid &edge_grid = boundary.grid; + + Point start = real_start; + Point end = real_end; + + //ensure that start & end are inside + if(extrusion_spacing > 0) + if (!find_point_on_boundary(start, boundary, (extrusion_spacing * 3)/2) || !find_point_on_boundary(end, boundary, (extrusion_spacing * 3) / 2)) { + BOOST_LOG_TRIVIAL(info) << "Fail to find a point in the contour for avoid_perimeter."; + //{ + // static int isazfn = 0; + // std::stringstream stri; + // stri << "avoid_peri_initbad_" << isazfn++ << ".svg"; + // SVG svg(stri.str()); + // for (auto elt : boundary.boundary_growth) + // svg.draw((elt.first), "grey"); + // for (auto elt : boundary.boundary_growth) + // svg.draw((elt.second), "pink"); + // //svg.draw((boundary.boundaries), "pink"); + // svg.draw((Line{ start,end }), "red"); + // svg.draw((Line{ real_start,real_end }), "green"); + // svg.Close(); + //} + result_out = { {start,-1}, {end,-1} }; + return 0; + } + // Find all intersections between boundaries and the line segment, sort them along the line segment. std::vector<Intersection> intersections; { @@ -366,8 +449,11 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - + if(start != real_start) + result_out.push_back({ real_start, -1 }); append(result_out, std::move(result)); + if (end != real_end) + result_out.push_back({ real_end, -1 }); return intersections.size(); } @@ -375,11 +461,12 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, const Point &end, + coord_t spacing, Polyline &result_out) { // Travel line is completely or partially inside the bounding box. std::vector<TravelPoint> path; - size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path); + size_t num_intersections = avoid_perimeters_inner(boundary, start, end, spacing, path); result_out = to_polyline(path); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT @@ -796,7 +883,7 @@ static ExPolygons get_boundary(const Layer &layer) append(boundary, inner_offset(support_layer->support_islands.expolygons, perimeter_offset)); #endif auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON); - if (layer_below) + if (layer_below) //why? append(boundary, inner_offset(layer_below->lslices, perimeter_offset)); // After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons boundary = union_ex(boundary); @@ -913,14 +1000,41 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & const ExPolygons &lslices = gcodegen.layer()->lslices; const std::vector<BoundingBox> &lslices_bboxes = gcodegen.layer()->lslices_bboxes; bool is_support_layer = dynamic_cast<const SupportLayer *>(gcodegen.layer()) != nullptr; - if (!use_external && (is_support_layer || (!lslices.empty() && !any_expolygon_contains(lslices, lslices_bboxes, m_grid_lslice, travel)))) { + const float perimeter_spacing = get_perimeter_spacing(*gcodegen.layer()); + if (!use_external && (is_support_layer || !lslices.empty() + /*|| (!lslices.empty() && !any_expolygon_contains(lslices, lslices_bboxes, m_grid_lslice, travel)) already done by the caller */ + )) { // Initialize m_internal only when it is necessary. - if (m_internal.boundaries.empty()) - init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer()))); + if (m_internal.boundaries.empty()) { + std::vector<std::pair<ExPolygon, ExPolygons>> boundary_growth; + //create better slice (on second perimeter instead of the first) + ExPolygons expoly_boundary; + //as we are going to reduce, do it expoli per expoli + for (const ExPolygon& origin : lslices) { + ExPolygons second_peri = offset_ex(origin, -perimeter_spacing * 1.5f); + //there is a collapse! try to add missing parts + if (second_peri.size() > 1) { + // get the bits that are collapsed + ExPolygons missing_parts = diff_ex({ origin }, offset_ex(second_peri, perimeter_spacing * 1.51f), true); + //have to grow a bit to be able to fit inside the reduced thing + // then intersect to be sure it don't stick out of the initial poly + missing_parts = intersection_ex({ origin }, offset_ex(missing_parts, perimeter_spacing * 1.1f)); + // offset to second peri (-first) where possible, then union and reduce to the first. + second_peri = offset_ex(union_ex(missing_parts, offset_ex(origin, -perimeter_spacing * 0.9f)), -perimeter_spacing * .6f); + } else if (second_peri.size() == 0) { + // try again with the first perimeter (should be 0.5, but even with overlapping peri, it's almost never a 50% overlap, so it's better that way) + second_peri = offset_ex(origin, -perimeter_spacing * .6f); + } + append(expoly_boundary, second_peri); + boundary_growth.push_back({ origin, second_peri }); + } + init_boundary(&m_internal, to_polygons(expoly_boundary)); + m_internal.boundary_growth = boundary_growth; + } // Trim the travel line by the bounding box. if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { - travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl); + travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), perimeter_spacing, result_pl); result_pl.points.front() = start; result_pl.points.back() = end; } @@ -931,7 +1045,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & // Trim the travel line by the bounding box. if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { - travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl); + travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), 0, result_pl); result_pl.points.front() = start; result_pl.points.back() = end; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index 307527af2..90cf3281e 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -42,11 +42,14 @@ public: std::vector<std::vector<float>> boundaries_params; // Used for detection of intersection between line and any polygon from boundaries EdgeGrid::Grid grid; + //used to move the point inside the boundary + std::vector<std::pair<ExPolygon, ExPolygons>> boundary_growth; void clear() { boundaries.clear(); boundaries_params.clear(); + boundary_growth.clear(); } }; diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 239dc8b5d..a146210df 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -797,9 +797,8 @@ std::string CoolingBuffer::apply_layer_cooldown( // Is the fan speed ramp enabled? int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); - // When ramping up fan speed from disable_fan_first_layers to full_fan_speed_layer, force disable_fan_first_layers above zero, - // so there will be a zero fan speed at least at the 1st layer. - disable_fan_first_layers = std::max(disable_fan_first_layers, 1); + // When ramping up fan speed from disable_fan_first_layers to full_fan_speed_layer, if disable_fan_first_layers is zero, + // the not-fan layer is a hypothetical -1 layer. if (int(layer_id) >= disable_fan_first_layers && int(layer_id) + 1 < full_fan_speed_layer) { // Ramp up the fan speed from disable_fan_first_layers to full_fan_speed_layer. float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers); diff --git a/src/libslic3r/GCode/FanMover.cpp b/src/libslic3r/GCode/FanMover.cpp index d4d50526a..ca6db9ecf 100644 --- a/src/libslic3r/GCode/FanMover.cpp +++ b/src/libslic3r/GCode/FanMover.cpp @@ -27,8 +27,9 @@ const std::string& FanMover::process_gcode(const std::string& gcode, bool flush) m_buffer_time_size = 0; for (auto& data : m_buffer) m_buffer_time_size += data.time; - m_parser.parse_buffer(gcode, - [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { /*m_process_output += line.raw() + "\n";*/ this->_process_gcode_line(reader, line); }); + if(!gcode.empty()) + m_parser.parse_buffer(gcode, + [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { /*m_process_output += line.raw() + "\n";*/ this->_process_gcode_line(reader, line); }); if (flush) { while (!m_buffer.empty()) { @@ -95,7 +96,6 @@ int16_t get_fan_speed(const std::string &line, GCodeFlavor flavor) { } void FanMover::_put_in_middle_G1(std::list<BufferData>::iterator item_to_split, float nb_sec_since_itemtosplit_start, BufferData &&line_to_write) { - //std::cout << "_put_in_middle_G1\n"; assert(item_to_split != m_buffer.end()); if (nb_sec_since_itemtosplit_start > item_to_split->time * 0.9) { // doesn't really need to be split, print it after @@ -152,7 +152,6 @@ void FanMover::_put_in_middle_G1(std::list<BufferData>::iterator item_to_split, } void FanMover::_print_in_middle_G1(BufferData& line_to_split, float nb_sec, const std::string &line_to_write) { - //std::cout << "_print_in_middle_G1\n"; if (nb_sec < line_to_split.time * 0.1) { // doesn't really need to be split, print it after m_process_output += line_to_split.raw + "\n"; @@ -215,6 +214,7 @@ void FanMover::_remove_slow_fan(int16_t min_speed, float past_sec) { void FanMover::_process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line) { // processes 'normal' gcode lines + bool need_flush = false; std::string cmd(line.cmd()); double time = 0; int16_t fan_speed = -1; @@ -245,7 +245,7 @@ void FanMover::_process_gcode_line(GCodeReader& reader, const GCodeReader::GCode if (!m_is_custom_gcode) { // if slow down => put in the queue. if not => if (m_back_buffer_fan_speed < fan_speed) { - if (nb_seconds_delay > 0 && (!only_overhangs || current_role != ExtrusionRole::erOverhangPerimeter)) { + if (nb_seconds_delay > 0 && (!only_overhangs || current_role == ExtrusionRole::erOverhangPerimeter)) { //don't put this command in the queue time = -1; // this M106 need to go in the past @@ -339,6 +339,9 @@ void FanMover::_process_gcode_line(GCodeReader& reader, const GCodeReader::GCode } //update back buffer fan speed m_back_buffer_fan_speed = fan_speed; + } else { + // have to flush the buffer to avoid erasing a fan command. + need_flush = true; } } break; @@ -352,8 +355,12 @@ void FanMover::_process_gcode_line(GCodeReader& reader, const GCodeReader::GCode std::string extrusion_string = line.raw().substr(6, line.raw().size() - 6); current_role = ExtrusionEntity::string_to_role(extrusion_string); } - if (line.raw().size() > 16 && line.raw().rfind("; custom gcode", 0) == 0) { - m_is_custom_gcode = line.raw().rfind("; custom gcode end", 0) != 0; + if (line.raw().size() > 16) { + if (line.raw().rfind("; custom gcode", 0) != std::string::npos) + if (line.raw().rfind("; custom gcode end", 0) != std::string::npos) + m_is_custom_gcode = false; + else + m_is_custom_gcode = true; } } } @@ -412,7 +419,7 @@ void FanMover::_process_gcode_line(GCodeReader& reader, const GCodeReader::GCode // puts the line back into the gcode //if buffer too big, flush it. if (time >= 0) { - while (!m_buffer.empty() && m_buffer_time_size - m_buffer.front().time > nb_seconds_delay - EPSILON) { + while (!m_buffer.empty() && (need_flush || m_buffer_time_size - m_buffer.front().time > nb_seconds_delay - EPSILON) ){ BufferData& frontdata = m_buffer.front(); if (frontdata.fan_speed < 0 || frontdata.fan_speed != m_front_buffer_fan_speed || frontdata.is_kickstart) { if (frontdata.is_kickstart && frontdata.fan_speed < m_front_buffer_fan_speed) { diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 21792aa78..d7f82fd78 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -349,7 +349,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // Test for a raft, insert additional wipe tower layer to fill in the raft separation gap. double max_layer_height = std::numeric_limits<double>::max(); for (size_t i = 0; i < config.nozzle_diameter.values.size(); ++ i) { - double mlh = config.max_layer_height.values[i]; + double mlh = config.max_layer_height.get_abs_value(i, config.nozzle_diameter.values[i]); if (mlh == 0.) mlh = 0.75 * config.nozzle_diameter.values[i]; max_layer_height = std::min(max_layer_height, mlh); diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 2ed093492..ede145d8d 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -52,9 +52,6 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) this->config.apply(print_config, true); m_extrusion_axis = this->config.get_extrusion_axis(); m_single_extruder_multi_material = print_config.single_extruder_multi_material.value; - m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin || print_config.gcode_flavor.value == gcfLerdge || print_config.gcode_flavor.value == gcfKlipper) - && print_config.machine_limits_usage.value <= MachineLimitsUsage::Limits ? - print_config.machine_max_acceleration_extruding.values.front() : 0); } void GCodeWriter::apply_print_region_config(const PrintRegionConfig& print_region_config) @@ -301,10 +298,6 @@ std::string GCodeWriter::set_fan(const uint8_t speed, bool dont_save, uint16_t d void GCodeWriter::set_acceleration(uint32_t acceleration) { - // Clamp the acceleration to the allowed maximum. - if (m_max_acceleration > 0 && acceleration > m_max_acceleration) - acceleration = m_max_acceleration; - if (acceleration == 0 || acceleration == m_current_acceleration) return; @@ -453,23 +446,27 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s return gcode.str(); } -std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment) +std::string GCodeWriter::travel_to_xy(const Vec2d &point, double F, const std::string &comment) { std::ostringstream gcode; gcode << write_acceleration(); + double speed = this->config.travel_speed.value * 60.0; + if ((F > 0) & (F < speed)) + speed = F; + m_pos.x() = point.x(); m_pos.y() = point.y(); gcode << "G1 X" << XYZ_NUM(point.x()) << " Y" << XYZ_NUM(point.y()) - << " F" << F_NUM(this->config.travel_speed.value * 60.0); + << " F" << F_NUM(speed); COMMENT(comment); gcode << "\n"; return gcode.str(); } -std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment) +std::string GCodeWriter::travel_to_xyz(const Vec3d &point, double F, const std::string &comment) { /* If target Z is lower than current Z but higher than nominal Z we don't perform the Z move but we only move in the XY plane and @@ -482,7 +479,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co // and a retract could be skipped (https://github.com/prusa3d/PrusaSlicer/issues/2154 if (std::abs(m_lifted) < EPSILON) m_lifted = 0.; - return this->travel_to_xy(to_2d(point)); + return this->travel_to_xy(to_2d(point), F, comment); } /* In all the other cases, we perform an actual XYZ move and cancel @@ -490,6 +487,10 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co m_lifted = 0; m_pos = point; + double speed = this->config.travel_speed.value * 60.0; + if ((F > 0) & (F < speed)) + speed = F; + std::ostringstream gcode; gcode << write_acceleration(); gcode << "G1 X" << XYZ_NUM(point.x()) @@ -498,7 +499,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co gcode << " Z" << PRECISION(point.z(), 6); else gcode << " Z" << XYZ_NUM(point.z()); - gcode << " F" << F_NUM(this->config.travel_speed.value * 60.0); + gcode << " F" << F_NUM(speed); COMMENT(comment); gcode << "\n"; @@ -510,14 +511,14 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment) /* If target Z is lower than current Z but higher than nominal Z we don't perform the move but we only adjust the nominal Z by reducing the lift amount that will be used for unlift. */ - if (!this->will_move_z(z)) { + // note that if we move but it's lower and we are lifted, we can wait a bit for unlifting, to avoid possible dance on layer change. + if (!this->will_move_z(z) || z < m_pos.z() && m_lifted > EPSILON) { double nominal_z = m_pos.z() - m_lifted; m_lifted -= (z - nominal_z); if (std::abs(m_lifted) < EPSILON) m_lifted = 0.; return ""; } - /* In all the other cases, we perform an actual Z move and cancel the lift. */ m_lifted = 0; @@ -548,7 +549,7 @@ bool GCodeWriter::will_move_z(double z) const we don't perform an actual Z move. */ if (m_lifted > 0) { double nominal_z = m_pos.z() - m_lifted; - if (z >= nominal_z && z <= m_pos.z()) + if (z >= nominal_z + EPSILON && z <= m_pos.z() - EPSILON) return false; } return true; @@ -698,15 +699,19 @@ std::string GCodeWriter::unretract() /* If this method is called more than once before calling unlift(), it will not perform subsequent lifts, even if Z was raised manually (i.e. with travel_to_z()) and thus _lifted was reduced. */ -std::string GCodeWriter::lift() +std::string GCodeWriter::lift(int layer_id) { // check whether the above/below conditions are met double target_lift = 0; if(this->tool_is_extruder()){ - //these two should be in the Tool class methods.... - double above = this->config.retract_lift_above.get_at(m_tool->id()); - double below = this->config.retract_lift_below.get_at(m_tool->id()); - if (m_pos.z() >= above && (below == 0 || m_pos.z() <= below)) + bool can_lift = this->config.retract_lift_first_layer.get_at(m_tool->id()) && layer_id == 0; + if (!can_lift) { + //these two should be in the Tool class methods.... + double above = this->config.retract_lift_above.get_at(m_tool->id()); + double below = this->config.retract_lift_below.get_at(m_tool->id()); + can_lift = (m_pos.z() >= above - EPSILON && (below == 0 || m_pos.z() <= below + EPSILON)); + } + if(can_lift) target_lift = m_tool->retract_lift(); } else { target_lift = m_tool->retract_lift(); @@ -717,17 +722,19 @@ std::string GCodeWriter::lift() target_lift = config_region->print_retract_lift.value; } - if (this->extra_lift > 0) { - target_lift += this->extra_lift; - this->extra_lift = 0; + // one-time extra lift (often for dangerous travels) + if (this->m_extra_lift > 0) { + target_lift += this->m_extra_lift; + this->m_extra_lift = 0; } // compare against epsilon because travel_to_z() does math on it // and subtracting layer_height from retract_lift might not give // exactly zero - if (std::abs(m_lifted) < EPSILON && target_lift > 0) { + if (std::abs(m_lifted) < target_lift - EPSILON && target_lift > 0) { + std::string str = this->_travel_to_z(m_pos.z() + target_lift - m_lifted, "lift Z"); m_lifted = target_lift; - return this->_travel_to_z(m_pos.z() + target_lift, "lift Z"); + return str; } return ""; } diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 72264aac3..f74eec403 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -21,7 +21,7 @@ public: GCodeWriter() : multiple_extruders(false), m_extrusion_axis("E"), m_tool(nullptr), m_single_extruder_multi_material(false), - m_last_acceleration(0), m_current_acceleration(0), m_max_acceleration(0), m_last_fan_speed(0), + m_last_acceleration(0), m_current_acceleration(0), m_last_fan_speed(0), m_last_bed_temperature(0), m_last_bed_temperature_reached(true), m_lifted(0) {} @@ -64,8 +64,8 @@ public: std::string toolchange_prefix() const; std::string toolchange(uint16_t tool_id); std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const; - std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()); - std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string()); + std::string travel_to_xy(const Vec2d &point, double F = 0.0, const std::string &comment = std::string()); + std::string travel_to_xyz(const Vec3d &point, double F = 0.0, const std::string &comment = std::string()); std::string travel_to_z(double z, const std::string &comment = std::string()); bool will_move_z(double z) const; std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string()); @@ -73,11 +73,12 @@ public: std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); std::string unretract(); - std::string lift(); + void set_extra_lift(double extra_zlift) { this->m_extra_lift = extra_zlift; } + double get_extra_lift() { return this->m_extra_lift; } + std::string lift(int layer_id); std::string unlift(); Vec3d get_position() const { return m_pos; } - void set_extra_lift(double extra_zlift) { this->extra_lift = extra_zlift; } private: // Extruders are sorted by their ID, so that binary search is possible. std::vector<Extruder> m_extruders; @@ -87,23 +88,21 @@ private: Tool* m_tool; uint32_t m_last_acceleration; uint32_t m_current_acceleration; - // Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware. - // If set to zero, the limit is not in action. - uint32_t m_max_acceleration; uint8_t m_last_fan_speed; uint8_t m_last_fan_speed_with_offset; int16_t m_last_temperature; int16_t m_last_temperature_with_offset; int16_t m_last_bed_temperature; bool m_last_bed_temperature_reached; + // if positive, it's set, and the next lift wil have this extra lift + double m_extra_lift = 0; + // current lift, to remove from m_pos to have the current height. double m_lifted; Vec3d m_pos = Vec3d::Zero(); std::string _travel_to_z(double z, const std::string &comment); std::string _retract(double length, double restart_extra, double restart_extra_toolchange, const std::string &comment); - // if positive, it's set, and the next lift wil have this extra lift - double extra_lift = 0; }; } /* namespace Slic3r */ diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index ed0c70071..c11ae3100 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -143,14 +143,6 @@ public: void restore_untyped_slices(); // Slices merged into islands, to be used by the elephant foot compensation to trim the individual surfaces with the shrunk merged slices. ExPolygons merged(float offset) const; - template <class T> bool any_internal_region_slice_contains(const T &item) const { - for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_internal_contains(item)) return true; - return false; - } - template <class T> bool any_bottom_region_slice_contains(const T &item) const { - for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_bottom_contains(item)) return true; - return false; - } void make_perimeters(); void make_milling_post_process(); void make_fills() { this->make_fills(nullptr, nullptr); }; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 3a5bcfd5b..36051dbfd 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -241,105 +241,140 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly break; } // Grown by bridged_infill_margin. - // also, remove all bridge area that are thinner than a single line. - Polygons polys = offset2(to_polygons(bridges[i].expolygon), - (-this->flow(frInfill).scaled_width() / 2), - (this->flow(frInfill).scaled_width() / 2) + float(margin_bridged), - EXTERNAL_SURFACES_OFFSET_PARAMETERS); + Polygons polys; if (idx_island == -1) { BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; } else { + // also, remove all bridge area that are thinner than a single line. + ExPolygons expoly_collapsed = offset2_ex(bridges[i].expolygon, + (-this->flow(frInfill).scaled_width() / 2), + (this->flow(frInfill).scaled_width() / 2) + SCALED_EPSILON, + EXTERNAL_SURFACES_OFFSET_PARAMETERS); + //check if there is something cut + ExPolygons cut = diff_ex({ bridges[i].expolygon }, expoly_collapsed, true); + double area_cut = 0; for (ExPolygon& c : cut) area_cut += c.area(); + if (area_cut > (this->flow(frInfill).scaled_width() * this->flow(frInfill).scaled_width())) { + //if area not negligible, we will consider it. + // compute the bridge area, if any. + ExPolygons ex_polys = offset_ex(expoly_collapsed, float(margin_bridged), EXTERNAL_SURFACES_OFFSET_PARAMETERS); + polys = to_polygons(ex_polys); + //add the cut section as solid infill + Surface srf_bottom = bridges[i]; + srf_bottom.surface_type = stPosBottom | stDensSolid; + // clip it to the infill area and remove the bridge part. + surfaces_append( + bottom, + diff_ex( + intersection_ex( + ExPolygons{ fill_boundaries_ex[idx_island] }, + offset_ex(cut, double(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS) + ), + ex_polys), + srf_bottom); + } else { + //negligible, don't offset2 + polys = offset(to_polygons(bridges[i].expolygon), float(margin_bridged), EXTERNAL_SURFACES_OFFSET_PARAMETERS); + } // Found an island, to which this bridge region belongs. Trim it, polys = intersection(polys, to_polygons(fill_boundaries_ex[idx_island])); } - bridge_bboxes.push_back(get_extents(polys)); - bridges_grown.push_back(std::move(polys)); - } - } - - // 2) Group the bridge surfaces by overlaps. - std::vector<size_t> bridge_group(bridges.size(), (size_t)-1); - size_t n_groups = 0; - for (size_t i = 0; i < bridges.size(); ++ i) { - // A grup id for this bridge. - size_t group_id = (bridge_group[i] == size_t(-1)) ? (n_groups ++) : bridge_group[i]; - bridge_group[i] = group_id; - // For all possibly overlaping bridges: - for (size_t j = i + 1; j < bridges.size(); ++ j) { - if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) - continue; - if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) - continue; - // The two bridge regions intersect. Give them the same group id. - if (bridge_group[j] != size_t(-1)) { - // The j'th bridge has been merged with some other bridge before. - size_t group_id_new = bridge_group[j]; - for (size_t k = 0; k < j; ++ k) - if (bridge_group[k] == group_id) - bridge_group[k] = group_id_new; - group_id = group_id_new; + //keep bridges & bridge_bboxes & bridges_grown the SAME SIZE + if (!polys.empty()) { + bridge_bboxes.push_back(get_extents(polys)); + bridges_grown.push_back(std::move(polys)); + } else { + bridges.erase(bridges.begin() + i); + --i; } - bridge_group[j] = group_id; } } - - // 3) Merge the groups with the same group id, detect bridges. + if (bridges.empty()) { - BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z << ", bridge groups: " << n_groups; - for (size_t group_id = 0; group_id < n_groups; ++ group_id) { - size_t n_bridges_merged = 0; - size_t idx_last = (size_t)-1; - for (size_t i = 0; i < bridges.size(); ++ i) { - if (bridge_group[i] == group_id) { - ++ n_bridges_merged; - idx_last = i; + fill_boundaries = union_(fill_boundaries, true); + } else { + // 2) Group the bridge surfaces by overlaps. + std::vector<size_t> bridge_group(bridges.size(), (size_t)-1); + size_t n_groups = 0; + for (size_t i = 0; i < bridges.size(); ++ i) { + // A grup id for this bridge. + size_t group_id = (bridge_group[i] == size_t(-1)) ? (n_groups ++) : bridge_group[i]; + bridge_group[i] = group_id; + // For all possibly overlaping bridges: + for (size_t j = i + 1; j < bridges.size(); ++ j) { + if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) + continue; + if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) + continue; + // The two bridge regions intersect. Give them the same group id. + if (bridge_group[j] != size_t(-1)) { + // The j'th bridge has been merged with some other bridge before. + size_t group_id_new = bridge_group[j]; + for (size_t k = 0; k < j; ++ k) + if (bridge_group[k] == group_id) + bridge_group[k] = group_id_new; + group_id = group_id_new; } + bridge_group[j] = group_id; } - if (n_bridges_merged == 0) - // This group has no regions assigned as these were moved into another group. - continue; - // Collect the initial ungrown regions and the grown polygons. - ExPolygons initial; - Polygons grown; - for (size_t i = 0; i < bridges.size(); ++ i) { - if (bridge_group[i] != group_id) + } + + // 3) Merge the groups with the same group id, detect bridges. + { + BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z << ", bridge groups: " << n_groups; + for (size_t group_id = 0; group_id < n_groups; ++ group_id) { + size_t n_bridges_merged = 0; + size_t idx_last = (size_t)-1; + for (size_t i = 0; i < bridges.size(); ++ i) { + if (bridge_group[i] == group_id) { + ++ n_bridges_merged; + idx_last = i; + } + } + if (n_bridges_merged == 0) + // This group has no regions assigned as these were moved into another group. continue; - initial.push_back(std::move(bridges[i].expolygon)); - polygons_append(grown, bridges_grown[i]); - } - // detect bridge direction before merging grown surfaces otherwise adjacent bridges - // would get merged into a single one while they need different directions - // also, supply the original expolygon instead of the grown one, because in case - // of very thin (but still working) anchors, the grown expolygon would go beyond them - BridgeDetector bd( - initial, - lower_layer->lslices, - this->flow(frInfill, true).scaled_width() - ); - #ifdef SLIC3R_DEBUG - printf("Processing bridge at layer %zu:\n", this->layer()->id()); - #endif - double custom_angle = Geometry::deg2rad(this->region()->config().bridge_angle.value); - if (custom_angle > 0) { - // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in - // using a bridging flow, therefore it makes sense to respect the custom bridging direction. - bridges[idx_last].bridge_angle = custom_angle; - }else if (bd.detect_angle(custom_angle)) { - bridges[idx_last].bridge_angle = bd.angle; - if (this->layer()->object()->config().support_material) { - //polygons_append(this->bridged, intersection(bd.coverage(), to_polygons(initial))); - append(this->unsupported_bridge_edges, bd.unsupported_edges()); + // Collect the initial ungrown regions and the grown polygons. + ExPolygons initial; + Polygons grown; + for (size_t i = 0; i < bridges.size(); ++ i) { + if (bridge_group[i] != group_id) + continue; + initial.push_back(std::move(bridges[i].expolygon)); + polygons_append(grown, bridges_grown[i]); } - } else { - bridges[idx_last].bridge_angle = 0; + // detect bridge direction before merging grown surfaces otherwise adjacent bridges + // would get merged into a single one while they need different directions + // also, supply the original expolygon instead of the grown one, because in case + // of very thin (but still working) anchors, the grown expolygon would go beyond them + BridgeDetector bd( + initial, + lower_layer->lslices, + this->flow(frInfill, true).scaled_width() + ); + #ifdef SLIC3R_DEBUG + printf("Processing bridge at layer %zu:\n", this->layer()->id()); + #endif + double custom_angle = Geometry::deg2rad(this->region()->config().bridge_angle.value); + if (custom_angle > 0) { + // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in + // using a bridging flow, therefore it makes sense to respect the custom bridging direction. + bridges[idx_last].bridge_angle = custom_angle; + }else if (bd.detect_angle(custom_angle)) { + bridges[idx_last].bridge_angle = bd.angle; + if (this->layer()->object()->config().support_material) { + //polygons_append(this->bridged, intersection(bd.coverage(), to_polygons(initial))); + append(this->unsupported_bridge_edges, bd.unsupported_edges()); + } + } else { + bridges[idx_last].bridge_angle = 0; + } + // without safety offset, artifacts are generated (GH #2494) + surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]); } - // without safety offset, artifacts are generated (GH #2494) - surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]); - } - fill_boundaries = std::move(to_polygons(fill_boundaries_ex)); - BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; - } + fill_boundaries = std::move(to_polygons(fill_boundaries_ex)); + BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; + } #if 0 { @@ -347,6 +382,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun ++), true); } #endif + } } Surfaces new_surfaces; @@ -423,7 +459,7 @@ void LayerRegion::prepare_fill_surfaces() if (! spiral_vase && this->region()->config().top_solid_layers == 0) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) if (surface->has_pos_top()) - surface->surface_type = (this->layer()->object()->config().infill_only_where_needed) ? + surface->surface_type = (this->layer()->object()->config().infill_only_where_needed && !this->region()->config().infill_dense.value) ? stPosInternal | stDensVoid : stPosInternal | stDensSparse; } if (this->region()->config().bottom_solid_layers == 0) { diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 762425cd5..de98c3e92 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -123,7 +123,9 @@ BoundingBox get_extents(const Lines &lines) } return bbox; -}Point Line::point_at(double distance) const { +} + +Point Line::point_at(double distance) const { Point point; double len = this->length(); point = this->a; diff --git a/src/libslic3r/MedialAxis.cpp b/src/libslic3r/MedialAxis.cpp index 2bfd81b25..1ed1320f7 100644 --- a/src/libslic3r/MedialAxis.cpp +++ b/src/libslic3r/MedialAxis.cpp @@ -1727,12 +1727,13 @@ MedialAxis::build(ThickPolylines &polylines_out) tp.width[i] = this->max_width; } } - // voronoi bugfix: when we have a wheel, it creates a polyline at the center, completly out of the polygon. - if (tp.endpoints.first && tp.endpoints.second && !this->expolygon.contains(tp.first_point()) && !this->expolygon.contains(tp.last_point())) { - //delete this out-of-bounds polyline - pp.erase(pp.begin() + tp_idx); - --tp_idx; - } + // voronoi bugfix: when we have a wheel, it creates a polyline at the center, completly out of the polygon. #651 + // note: can't reproduce in the new verison. This may have been fixed by another way. + //if (tp.endpoints.first && tp.endpoints.second && !this->expolygon.contains(tp.first_point()) && !this->expolygon.contains(tp.last_point()) && pp.size() > 1) { + // //delete this out-of-bounds polyline + // pp.erase(pp.begin() + tp_idx); + // --tp_idx; + //} } //std::cout << "polyline_from_voronoi\n"; //{ diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 6c1658b62..610cce98f 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -43,15 +43,15 @@ void PerimeterGenerator::process() // other perimeters this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); - coord_t perimeter_width = this->perimeter_flow.scaled_width(); + const coord_t perimeter_width = this->perimeter_flow.scaled_width(); //spacing between internal perimeters - coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); + const coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); // external perimeters this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); - coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); + const coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); //spacing between two external perimeter (where you don't have the space to add other loops) - coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); + const coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); //spacing between external perimeter and the second coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow); @@ -59,19 +59,21 @@ void PerimeterGenerator::process() this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); //gap fill - coord_t gap_fill_spacing = this->config->gap_fill_overlap.get_abs_value(this->perimeter_flow.scaled_spacing()) - + this->perimeter_flow.scaled_width() * (100 - this->config->gap_fill_overlap.value) / 100.; + const coord_t gap_fill_spacing_external = this->config->gap_fill_overlap.get_abs_value(this->ext_perimeter_flow.scaled_spacing()) + + this->ext_perimeter_flow.scaled_width() * (1 - this->config->gap_fill_overlap.get_abs_value(1.)); + const coord_t gap_fill_spacing = this->config->gap_fill_overlap.get_abs_value(this->perimeter_flow.scaled_spacing()) + + this->perimeter_flow.scaled_width() * (1 - this->config->gap_fill_overlap.get_abs_value(1.)); // solid infill - coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); + const coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); //infill / perimeter coord_t infill_peri_overlap = (coord_t)scale_(this->config->get_abs_value("infill_overlap", unscale<coordf_t>(perimeter_spacing + solid_infill_spacing) / 2)); // infill gap to add vs perimeter (useful if using perimeter bonding) coord_t infill_gap = 0; - bool round_peri = this->config->perimeter_round_corners.value; - coord_t min_round_spacing = perimeter_width / 10; + const bool round_peri = this->config->perimeter_round_corners.value; + const coord_t min_round_spacing = perimeter_width / 10; // nozzle diameter const double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1); @@ -95,8 +97,8 @@ void PerimeterGenerator::process() // which is the spacing between external and internal, which is not correct // and would make the collapsing (thus the details resolution) dependent on // internal flow which is unrelated. <- i don't undertand, so revert to ext_perimeter_spacing2 - coord_t min_spacing = (coord_t)( perimeter_spacing * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) ); - coord_t ext_min_spacing = (coord_t)( ext_perimeter_spacing2 * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) ); + const coord_t min_spacing = (coord_t)( perimeter_spacing * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) ); + const coord_t ext_min_spacing = (coord_t)( ext_perimeter_spacing2 * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) ); // prepare grown lower layer slices for overhang detection if (this->lower_slices != NULL && (this->config->overhangs_width.value > 0 || this->config->overhangs_width_speed.value > 0)) { @@ -135,12 +137,12 @@ void PerimeterGenerator::process() } } - if (overhangs_width_speed > 0) { + if (overhangs_width_speed > 0 && this->config->overhangs_width_speed.value < this->config->overhangs_width.value) { this->_lower_slices_bridge_speed_small = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_speed_90 - (coordf_t)(ext_perimeter_width / 2)); this->_lower_slices_bridge_speed_big = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_speed_110 - (coordf_t)(ext_perimeter_width / 2)); } if (overhangs_width_flow > 0) { - if (overhangs_width_speed_110 == overhangs_width_flow_90) + if (overhangs_width_speed_110 == overhangs_width_flow_90 && this->config->overhangs_width_speed.value < this->config->overhangs_width.value) this->_lower_slices_bridge_flow_small = this->_lower_slices_bridge_speed_big; else this->_lower_slices_bridge_flow_small = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_flow_90 - (coordf_t)(ext_perimeter_width / 2)); @@ -363,22 +365,22 @@ void PerimeterGenerator::process() surface_idx = 0; const int extra_odd_perimeter = (config->extra_perimeters_odd_layers && layer->id() % 2 == 1 ? 1:0); - for (const Surface &surface : all_surfaces) { + for (const Surface& surface : all_surfaces) { // detect how many perimeters must be generated for this island int loop_number = this->config->perimeters + surface.extra_perimeters - 1 + extra_odd_perimeter; // 0-indexed loops surface_idx++; - if ((layer->id() == 0 && this->config->only_one_perimeter_first_layer) || (this->config->only_one_perimeter_top && loop_number > 0 && this->upper_slices == NULL)){ + if ((layer->id() == 0 && this->config->only_one_perimeter_first_layer) || (this->config->only_one_perimeter_top && loop_number > 0 && this->upper_slices == NULL)) { loop_number = 0; } - + ExPolygons gaps; //this var store infill surface removed from last to not add any more perimeters to it. ExPolygons top_fills; ExPolygons fill_clip; // simplification already done at slicing //ExPolygons last = union_ex(surface.expolygon.simplify_p(SCALED_RESOLUTION)); - ExPolygons last = union_ex(surface.expolygon); + ExPolygons last = union_ex(surface.expolygon); double last_area = -1; if (loop_number >= 0) { @@ -397,11 +399,11 @@ void PerimeterGenerator::process() // Add perimeters on overhangs : initialization ExPolygons overhangs_unsupported; - if ( (this->config->extra_perimeters_overhangs || (this->config->overhangs_reverse && this->layer->id() % 2 == 1)) - && !last.empty() && this->lower_slices != NULL && !this->lower_slices->empty()) { + if ((this->config->extra_perimeters_overhangs || (this->config->overhangs_reverse && this->layer->id() % 2 == 1)) + && !last.empty() && this->lower_slices != NULL && !this->lower_slices->empty()) { //remove holes from lower layer, we only ant that for overhangs, not bridges! ExPolygons lower_without_holes; - for (const ExPolygon &exp : *this->lower_slices) + for (const ExPolygon& exp : *this->lower_slices) lower_without_holes.emplace_back(to_expolygon(exp.contour)); overhangs_unsupported = offset2_ex(diff_ex(last, lower_without_holes, true), -SCALED_RESOLUTION, SCALED_RESOLUTION); if (!overhangs_unsupported.empty()) { @@ -421,7 +423,7 @@ void PerimeterGenerator::process() if (!bridgeable.empty()) { //simplify to avoid most of artefacts from printing lines. ExPolygons bridgeable_simplified; - for (const ExPolygon &poly : bridgeable) { + for (const ExPolygon& poly : bridgeable) { poly.simplify(perimeter_spacing / 2, &bridgeable_simplified); } @@ -447,16 +449,17 @@ void PerimeterGenerator::process() } // In case no perimeters are to be generated, loop_number will equal to -1. - std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops - std::vector<PerimeterGeneratorLoops> holes(loop_number+1); // depth => loops + std::vector<PerimeterGeneratorLoops> contours(loop_number + 1); // depth => loops + std::vector<PerimeterGeneratorLoops> holes(loop_number + 1); // depth => loops ThickPolylines thin_walls; + ExPolygons no_last_gapfill; // we loop one time more than needed in order to find gaps after the last perimeter was applied - for (int i = 0;; ++ i) { // outer loop is 0 + for (int i = 0;; ++i) { // outer loop is 0 // We can add more perimeters if there are uncovered overhangs // improvement for future: find a way to add perimeters only where it's needed. bool has_overhang = false; - if ( this->config->extra_perimeters_overhangs && !last.empty() && !overhangs_unsupported.empty()) { + if (this->config->extra_perimeters_overhangs && !last.empty() && !overhangs_unsupported.empty()) { overhangs_unsupported = intersection_ex(overhangs_unsupported, last, true); if (overhangs_unsupported.size() > 0) { //please don't stop adding perimeter yet. @@ -489,9 +492,9 @@ void PerimeterGenerator::process() (round_peri ? min_round_spacing : 3)); // look for thin walls - if (this->config->thin_walls) { + if (this->config->thin_walls) { // detect edge case where a curve can be split in multiple small chunks. - std::vector<float> divs = { 2.1f, 1.9f, 2.2f, 1.75f, 1.5f}; //don't go too far, it's not possible to print thin wall after that + std::vector<float> divs = { 2.1f, 1.9f, 2.2f, 1.75f, 1.5f }; //don't go too far, it's not possible to print thin wall after that size_t idx_div = 0; while (next_onion.size() > last.size() && idx_div < divs.size()) { float div = divs[idx_div]; @@ -509,7 +512,7 @@ void PerimeterGenerator::process() // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) coord_t min_width = (coord_t)scale_(this->config->thin_walls_min_width.get_abs_value(this->ext_perimeter_flow.nozzle_diameter)); - + ExPolygons no_thin_zone = offset_ex(next_onion, double(ext_perimeter_width / 2), jtSquare); // medial axis requires non-overlapping geometry ExPolygons thin_zones = diff_ex(last, no_thin_zone, true); @@ -517,7 +520,7 @@ void PerimeterGenerator::process() //a very little bit of overlap can be created here with other thin polygons, but it's more useful than worisome. ExPolygons half_thins = offset_ex(thin_zones, double(-min_width / 2)); //simplify them - for (ExPolygon &half_thin : half_thins) { + for (ExPolygon& half_thin : half_thins) { half_thin.remove_point_too_near((coord_t)SCALED_RESOLUTION); } //we push the bits removed and put them into what we will use as our anchor @@ -526,7 +529,7 @@ void PerimeterGenerator::process() } ExPolygons thins; // compute a bit of overlap to anchor thin walls inside the print. - for (ExPolygon &half_thin : half_thins) { + for (ExPolygon& half_thin : half_thins) { //growing back the polygon ExPolygons thin = offset_ex(half_thin, double(min_width / 2)); assert(thin.size() <= 1); @@ -535,15 +538,15 @@ void PerimeterGenerator::process() ExPolygons anchor = intersection_ex(offset_ex(half_thin, double(min_width / 2) + (float)(thin_walls_overlap), jtSquare), no_thin_zone, true); ExPolygons bounds = union_ex(thin, anchor, true); - for (ExPolygon &bound : bounds) { + for (ExPolygon& bound : bounds) { if (!intersection_ex(thin[0], bound).empty()) { //be sure it's not too small to extrude reliably thin[0].remove_point_too_near((coord_t)SCALED_RESOLUTION); - if (thin[0].area() > min_width*(ext_perimeter_width + ext_perimeter_spacing)) { + if (thin[0].area() > min_width * (ext_perimeter_width + ext_perimeter_spacing)) { thins.push_back(thin[0]); bound.remove_point_too_near((coord_t)SCALED_RESOLUTION); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop (*1.2 because of circles approx. and enlrgment from 'div') - Slic3r::MedialAxis ma{ thin[0], (coord_t)((ext_perimeter_width + ext_perimeter_spacing)*1.2), + Slic3r::MedialAxis ma{ thin[0], (coord_t)((ext_perimeter_width + ext_perimeter_spacing) * 1.2), min_width, coord_t(this->layer->height) }; ma.use_bounds(bound) .use_min_real_width((coord_t)scale_(this->ext_perimeter_flow.nozzle_diameter)) @@ -565,7 +568,7 @@ void PerimeterGenerator::process() (round_peri ? min_round_spacing : 3))); else next_onion = union_ex(next_onion, offset2_ex(diff_ex(last, thins, true), - -(float)((ext_perimeter_width / 2) + (ext_min_spacing / 4)), + -(float)((ext_perimeter_width / 2) + (ext_min_spacing / 4)), (float)(ext_min_spacing / 4), (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), (round_peri ? min_round_spacing : 3))); @@ -607,7 +610,7 @@ void PerimeterGenerator::process() new_area += expoly.area(); } - std::vector<float> divs { 1.8f, 1.6f }; //don't over-extrude, so don't use divider >2 + std::vector<float> divs{ 1.8f, 1.6f }; //don't over-extrude, so don't use divider >2 size_t idx_div = 0; while ((next_onion.size() > no_thin_onion.size() || (new_area != 0 && last_area > new_area * 100)) && idx_div < divs.size()) { float div = divs[idx_div]; @@ -616,7 +619,7 @@ void PerimeterGenerator::process() last, -(float)(good_spacing + (min_spacing / div) - 1), +(float)((min_spacing / div) - 1)); - if (next_onion.size() > next_onion_secondTry.size() * 1.2 && next_onion.size() > next_onion_secondTry.size() + 2) { + if (next_onion.size() > next_onion_secondTry.size() * 1.2 && next_onion.size() > next_onion_secondTry.size() + 2) { // don't get it if it creates too many next_onion = next_onion_secondTry; } else if (next_onion.size() > next_onion_secondTry.size() || last_area > new_area * 100) { @@ -636,22 +639,30 @@ void PerimeterGenerator::process() } else { // If "overlapping_perimeters" is enabled, this paths will be entered, which // leads to overflows, as in prusa3d/Slic3r GH #32 - next_onion = offset_ex(last, double( - good_spacing), + next_onion = offset_ex(last, double(-good_spacing), (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), (round_peri ? min_round_spacing : 3)); } // look for gaps - if (this->config->gap_fill + if (this->config->gap_fill //check if we are going to have an other perimeter - && (i <= loop_number || has_overhang || next_onion.empty() || (this->config->gap_fill_last.value && i == loop_number+1))) + && (i <= loop_number || has_overhang || next_onion.empty() || (this->config->gap_fill_last.value && i == loop_number + 1))) { // not using safety offset here would "detect" very narrow gaps // (but still long enough to escape the area threshold) that gap fill // won't be able to fill but we'd still remove from infill area - append(gaps, diff_ex( - offset(last, -0.5f * gap_fill_spacing), - offset(next_onion, 0.5f * good_spacing + 10, - (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), - (round_peri ? min_round_spacing : 3)))); // safety offset + no_last_gapfill = offset_ex(next_onion, 0.5f * good_spacing + 10, + (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), + (round_peri ? min_round_spacing : 3)); + if (i == 1) { + append(gaps, diff_ex( + offset_ex(last, -0.5f * gap_fill_spacing_external), + no_last_gapfill)); // safety offset + } else { + append(gaps, diff_ex( + offset_ex(last, -0.5f * gap_fill_spacing), + no_last_gapfill)); // safety offset + } + } } if (next_onion.empty()) { @@ -671,19 +682,19 @@ void PerimeterGenerator::process() } } - for (const ExPolygon &expolygon : next_onion) { + for (const ExPolygon& expolygon : next_onion) { //TODO: add width here to allow variable width (if we want to extrude a sightly bigger perimeter, see thin wall) contours[i].emplace_back(expolygon.contour, i, true, has_steep_overhang); - if (! expolygon.holes.empty()) { + if (!expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); - for (const Polygon &hole : expolygon.holes) + for (const Polygon& hole : expolygon.holes) holes[i].emplace_back(hole, i, false, has_steep_overhang); } } last = std::move(next_onion); - + //store surface for top infill if only_one_perimeter_top - if(i==0 && (config->only_one_perimeter_top && this->upper_slices != NULL)){ + if (i == 0 && (config->only_one_perimeter_top && this->upper_slices != NULL)) { if (this->config->only_one_perimeter_top_other_algo) { //split the polygons with top/not_top //get the offset from solid surface anchor @@ -850,14 +861,14 @@ void PerimeterGenerator::process() // nest loops: holes first for (int d = 0; d <= loop_number; ++d) { - PerimeterGeneratorLoops &holes_d = holes[d]; + PerimeterGeneratorLoops& holes_d = holes[d]; // loop through all holes having depth == d for (int i = 0; i < (int)holes_d.size(); ++i) { - const PerimeterGeneratorLoop &loop = holes_d[i]; + const PerimeterGeneratorLoop& loop = holes_d[i]; // find the hole loop that contains this one, if any - for (int t = d+1; t <= loop_number; ++t) { + for (int t = d + 1; t <= loop_number; ++t) { for (int j = 0; j < (int)holes[t].size(); ++j) { - PerimeterGeneratorLoop &candidate_parent = holes[t][j]; + PerimeterGeneratorLoop& candidate_parent = holes[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); holes_d.erase(holes_d.begin() + i); @@ -869,7 +880,7 @@ void PerimeterGenerator::process() // if no hole contains this hole, find the contour loop that contains it for (int t = loop_number; t >= 0; --t) { for (int j = 0; j < (int)contours[t].size(); ++j) { - PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + PerimeterGeneratorLoop& candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); holes_d.erase(holes_d.begin() + i); @@ -878,19 +889,19 @@ void PerimeterGenerator::process() } } } - NEXT_LOOP: ; + NEXT_LOOP:; } } // nest contour loops for (int d = loop_number; d >= 1; --d) { - PerimeterGeneratorLoops &contours_d = contours[d]; + PerimeterGeneratorLoops& contours_d = contours[d]; // loop through all contours having depth == d for (int i = 0; i < (int)contours_d.size(); ++i) { - const PerimeterGeneratorLoop &loop = contours_d[i]; + const PerimeterGeneratorLoop& loop = contours_d[i]; // find the contour loop that contains it - for (int t = d - 1; t >= 0; -- t) { - for (size_t j = 0; j < contours[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + for (int t = d - 1; t >= 0; --t) { + for (size_t j = 0; j < contours[t].size(); ++j) { + PerimeterGeneratorLoop& candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); contours_d.erase(contours_d.begin() + i); @@ -899,7 +910,7 @@ void PerimeterGenerator::process() } } } - NEXT_CONTOUR: ; + NEXT_CONTOUR:; } } // at this point, all loops should be in contours[0] (= contours.front() ) @@ -907,7 +918,7 @@ void PerimeterGenerator::process() ExtrusionEntityCollection entities; if (config->perimeter_loop.value) { //onlyone_perimter = >fusion all perimeterLoops - for (PerimeterGeneratorLoop &loop : contours.front()) { + for (PerimeterGeneratorLoop& loop : contours.front()) { ExtrusionLoop extr_loop = this->_traverse_and_join_loops(loop, get_all_Childs(loop), loop.polygon.points.front()); //ExtrusionLoop extr_loop = this->_traverse_and_join_loops_old(loop, loop.polygon.points.front(), true); extr_loop.paths.back().polyline.points.push_back(extr_loop.paths.front().polyline.points.front()); @@ -917,7 +928,7 @@ void PerimeterGenerator::process() // append thin walls if (!thin_walls.empty()) { ExtrusionEntityCollection tw = thin_variable_width - (thin_walls, erThinWall, this->ext_perimeter_flow); + (thin_walls, erThinWall, this->ext_perimeter_flow); entities.append(tw.entities); thin_walls.clear(); @@ -932,7 +943,7 @@ void PerimeterGenerator::process() } } - + // if brim will be printed, reverse the order of perimeters so that // we continue inwards after having finished the brim // TODO: add test for perimeter order @@ -988,11 +999,13 @@ void PerimeterGenerator::process() // append perimeters for this slice as a collection if (!entities.empty()) { //move it, to avoid to clone evrything and then delete it - this->loops->entities.emplace_back( new ExtrusionEntityCollection(std::move(entities))); + this->loops->entities.emplace_back(new ExtrusionEntityCollection(std::move(entities))); } } // for each loop of an island // fill gaps + ExPolygons gaps_ex; + ExPolygons gap_srf; if (!gaps.empty()) { // collapse double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); @@ -1001,11 +1014,10 @@ void PerimeterGenerator::process() double max = 2.2 * perimeter_spacing; //remove areas that are too big (shouldn't occur...) ExPolygons too_big = offset2_ex(gaps, double(-max / 2), double(+max / 2)); - ExPolygons gaps_ex_to_test = too_big.empty()? gaps : diff_ex(gaps,too_big,true); - ExPolygons gaps_ex; - const double minarea = scale_(scale_(this->config->gap_fill_min_area.get_abs_value(unscaled((double)perimeter_width)*unscaled((double)perimeter_width)))); + ExPolygons gaps_ex_to_test = too_big.empty() ? gaps : diff_ex(gaps, too_big, true); + const double minarea = scale_(scale_(this->config->gap_fill_min_area.get_abs_value(unscaled((double)perimeter_width) * unscaled((double)perimeter_width)))); // check each gapfill area to see if it's printable. - for (const ExPolygon &expoly : gaps_ex_to_test) { + for (const ExPolygon& expoly : gaps_ex_to_test) { //remove too small gaps that are too hard to fill. //ie one that are smaller than an extrusion with width of min and a length of max. if (expoly.area() > minarea) { @@ -1026,13 +1038,13 @@ void PerimeterGenerator::process() } } //maybe some areas are a just bit too thin, try with just a little more offset to remove them. - ExPolygons expoly_after_shrink_test2 = offset_ex(ExPolygons{ expoly }, double(-min *0.8)); + ExPolygons expoly_after_shrink_test2 = offset_ex(ExPolygons{ expoly }, double(-min * 0.8)); for (int i = 0; i < expoly_after_shrink_test2.size(); i++) { if (expoly_after_shrink_test2[i].area() < (SCALED_EPSILON * SCALED_EPSILON * 4)) { expoly_after_shrink_test2.erase(expoly_after_shrink_test2.begin() + i); i--; - }else{ + } else { ExPolygons wider = offset_ex(ExPolygons{ expoly_after_shrink_test2[i] }, min * 0.5); if (wider.empty() || wider[0].area() < minarea) { expoly_after_shrink_test2.erase(expoly_after_shrink_test2.begin() + i); @@ -1041,7 +1053,7 @@ void PerimeterGenerator::process() } } //it's better if there are significantly less extrusions - if (expoly_after_shrink_test.size()/1.42 > expoly_after_shrink_test2.size()) { + if (expoly_after_shrink_test.size() / 1.42 > expoly_after_shrink_test2.size()) { expoly_after_shrink_test2 = offset_ex(expoly_after_shrink_test2, double(min * 0.8)); //insert with move instead of copy std::move(expoly_after_shrink_test2.begin(), expoly_after_shrink_test2.end(), std::back_inserter(gaps_ex)); @@ -1057,12 +1069,12 @@ void PerimeterGenerator::process() } // create lines from the area ThickPolylines polylines; - for (const ExPolygon &ex : gaps_ex) { - MedialAxis{ ex, coord_t(max*1.1), coord_t(min), coord_t(this->layer->height) }.build(polylines); + for (const ExPolygon& ex : gaps_ex) { + MedialAxis{ ex, coord_t(max * 1.1), coord_t(min), coord_t(this->layer->height) }.build(polylines); } // create extrusion from lines if (!polylines.empty()) { - ExtrusionEntityCollection gap_fill = thin_variable_width(polylines, + ExtrusionEntityCollection gap_fill = thin_variable_width(polylines, erGapFill, this->solid_infill_flow); this->gap_fill->append(gap_fill.entities); /* Make sure we don't infill narrow parts that are already gap-filled @@ -1071,9 +1083,11 @@ void PerimeterGenerator::process() are not subtracted from fill surfaces (they might be too short gaps that medial axis skips but infill might join with other infill regions and use zigzag). */ - //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, - // therefore it may cover the area, but no the volume. - last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f)); + // get clean surface of gap + gap_srf = union_ex(offset(gap_fill.polygons_covered_by_width(float(SCALED_EPSILON) / 10), float(SCALED_EPSILON / 2))); + // intersection to ignore the bits of gapfill tha may be over infill, as it's epsilon and there may be some voids here anyway. + gap_srf = intersection_ex(gap_srf, gaps_ex); + // the diff(last, gap) will be done after, as we have to keep the last un-gapped to avoid unneeded gap/infill offset } } //TODO: if a gapfill extrusion is a loop and with width always >= perimeter width then change the type to perimeter and put it at the right place in the loops vector. @@ -1082,36 +1096,59 @@ void PerimeterGenerator::process() // we offset by half the perimeter spacing (to get to the actual infill boundary) // and then we offset back and forth by half the infill spacing to only consider the // non-collapsing regions - coord_t inset = + coord_t inset = (loop_number < 0) ? 0 : (loop_number == 0) ? // one loop - ext_perimeter_spacing / 2 : - // two or more loops? - perimeter_spacing / 2; + ext_perimeter_spacing / 2 : + // two or more loops? + perimeter_spacing / 2; // only apply infill overlap if we actually have one perimeter if (inset == 0) { infill_peri_overlap = 0; } + + //remove gapfill from last + ExPolygons last_no_gaps = (gap_srf.empty()) ? last : diff_ex(last, gap_srf); + // simplify infill contours according to resolution Polygons not_filled_p; - for (ExPolygon &ex : last) + for (ExPolygon& ex : last_no_gaps) ex.simplify_p(SCALED_RESOLUTION, ¬_filled_p); ExPolygons not_filled_exp = union_ex(not_filled_p); // collapse too narrow infill areas - coord_t min_perimeter_infill_spacing = (coord_t)( solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE) ); - // append infill areas to fill_surfaces - //auto it_surf = this->fill_surfaces->surfaces.end(); - ExPolygons infill_exp = offset2_ex(not_filled_exp, - double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - infill_gap), - double(min_perimeter_infill_spacing / 2)); + coord_t min_perimeter_infill_spacing = (coord_t)(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + ExPolygons infill_exp; + //special branch if gap : don't inset away from gaps! + if (gap_srf.empty()) + infill_exp = offset2_ex(not_filled_exp, + double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - infill_gap), + double(min_perimeter_infill_spacing / 2)); + else { + //store the infill_exp but not offseted, it will be used as a clip to remove the gapfill portion + const ExPolygons infill_exp_no_gap = offset2_ex(not_filled_exp, + double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - infill_gap), + double(inset + min_perimeter_infill_spacing / 2 - infill_peri_overlap + infill_gap)); + //redo the same as not_filled_exp but with last instead of last_no_gaps + not_filled_p.clear(); + for (ExPolygon& ex : last) + ex.simplify_p(SCALED_RESOLUTION, ¬_filled_p); + not_filled_exp = union_ex(not_filled_p); + infill_exp = offset2_ex(not_filled_exp, + double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - infill_gap), + double(min_perimeter_infill_spacing / 2)); + // intersect(growth(last-gap) , last), so you have the (last - small gap) but without voids betweeng gap & last + infill_exp = intersection_ex(infill_exp, infill_exp_no_gap); + } + //if any top_fills, grow them by ext_perimeter_spacing/2 to have the real un-anchored fill ExPolygons top_infill_exp = intersection_ex(fill_clip, offset_ex(top_fills, double(ext_perimeter_spacing / 2))); if (!top_fills.empty()) { infill_exp = union_ex(infill_exp, offset_ex(top_infill_exp, double(infill_peri_overlap))); } + // append infill areas to fill_surfaces this->fill_surfaces->append(infill_exp, stPosInternal | stDensSparse); - + if (infill_peri_overlap != 0) { ExPolygons polyWithoutOverlap; if (min_perimeter_infill_spacing / 2 > infill_peri_overlap) @@ -1127,18 +1164,19 @@ void PerimeterGenerator::process() polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp); } this->fill_no_overlap.insert(this->fill_no_overlap.end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end()); - /*{ - std::stringstream stri; - stri << this->layer->id() << "_2_end_makeperimeter_" << this->layer->id() << ".svg"; - SVG svg(stri.str()); - svg.draw(to_polylines(infill_exp), "blue"); - svg.draw(to_polylines(fill_no_overlap), "cyan"); - svg.draw(to_polylines(not_filled_exp), "green"); - svg.draw(to_polylines(last), "yellow"); - svg.draw(to_polylines(offset_ex(fill_clip, ext_perimeter_spacing / 2)), "yellow"); - svg.draw(to_polylines(top_infill_exp), "orange"); - svg.Close(); - }*/ + /*{ + static int isaqsdsdfsdfqzfn = 0; + std::stringstream stri; + stri << this->layer->id() << "_2_end_makeperimeter_" << isaqsdsdfsdfqzfn++ << ".svg"; + SVG svg(stri.str()); + svg.draw(to_polylines(infill_exp), "blue"); + svg.draw(to_polylines(fill_no_overlap), "cyan"); + svg.draw(to_polylines(not_filled_exp), "green"); + svg.draw(to_polylines(last_no_gaps), "yellow"); + svg.draw(to_polylines(offset_ex(fill_clip, ext_perimeter_spacing / 2)), "brown"); + svg.draw(to_polylines(top_infill_exp), "orange"); + svg.Close(); + }*/ } } // for each island } @@ -1171,7 +1209,7 @@ ExtrusionPaths PerimeterGenerator::create_overhangs(const Polyline& loop_polygon Polylines big_flow; Polylines* previous = &ok_polylines; - if (this->config->overhangs_width_speed.value > 0) { + if (this->config->overhangs_width_speed.value > 0 && this->config->overhangs_width_speed.value < this->config->overhangs_width.value) { if (!this->_lower_slices_bridge_speed_small.empty()) { small_speed = diff_pl(*previous, this->_lower_slices_bridge_speed_small); if (!small_speed.empty()) { diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 1d12c0b05..8ca131f34 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -798,6 +798,8 @@ namespace client opt_key_str.resize(opt_key_str.size() - 1); opt = ctx->resolve_symbol(opt_key_str); } + if (opt == nullptr) + ctx->throw_exception("Variable does not exist", opt_key); if (! opt->is_vector()) ctx->throw_exception("Trying to index a scalar variable", opt_key); const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index b311d722c..a133254fc 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -735,6 +735,7 @@ const std::vector<std::string>& Preset::printer_options() "gcode_precision_e", "use_relative_e_distances", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", + "lift_min", "min_length", "max_gcode_per_second", //FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset. @@ -757,6 +758,7 @@ const std::vector<std::string>& Preset::printer_options() "thumbnails", "thumbnails_color", "thumbnails_custom_color", + "thumbnails_end_file", "thumbnails_with_bed", "wipe_advanced", "wipe_advanced_nozzle_melted_volume", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a76893662..d4b3f7336 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1595,9 +1595,9 @@ std::pair<PrintBase::PrintValidationError, std::string> Print::validate() const PrintRegion::collect_object_printing_extruders(config(), object->config(), region->config(), object_extruders); double layer_height = object->config().layer_height.value; for (uint16_t extruder_id : object_extruders) { - double min_layer_height = config().min_layer_height.values[extruder_id]; - double max_layer_height = config().max_layer_height.values[extruder_id]; - double nozzle_diameter = config().nozzle_diameter.values[extruder_id]; + double nozzle_diameter = config().nozzle_diameter.get_at(extruder_id); + double min_layer_height = config().min_layer_height.get_abs_value(extruder_id, nozzle_diameter); + double max_layer_height = config().max_layer_height.get_abs_value(extruder_id, nozzle_diameter); if (max_layer_height < EPSILON) max_layer_height = nozzle_diameter * 0.75; if (min_layer_height > max_layer_height) return { PrintBase::PrintValidationError::pveWrongSettings, L("Min layer height can't be greater than Max layer height") }; if (max_layer_height > nozzle_diameter) return { PrintBase::PrintValidationError::pveWrongSettings, L("Max layer height can't be greater than nozzle diameter") }; @@ -1797,11 +1797,12 @@ void Print::process() { name_tbb_thread_pool_threads(); bool something_done = !is_step_done_unguarded(psBrim); - BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); for (PrintObject *obj : m_objects) obj->make_perimeters(); this->set_status(70, L("Infilling layers")); + //note: as object seems to be sliced independantly, it's maybe possible to add a tbb parallel_loop with simple partitioner on infill, + // as prepare_infill has some function not // for (PrintObject *obj : m_objects) obj->infill(); for (PrintObject *obj : m_objects) @@ -1986,7 +1987,6 @@ void Print::_make_skirt(const PrintObjectPtrs &objects, ExtrusionEntityCollectio std::min(size_t(m_config.skirt_height.value), object->layer_count()); skirt_height_z = std::max(skirt_height_z, object->m_layers[skirt_layers-1]->print_z); } - // Collect points from all layers contained in skirt height. Points points; for (const PrintObject *object : objects) { @@ -2006,12 +2006,26 @@ void Print::_make_skirt(const PrintObjectPtrs &objects, ExtrusionEntityCollectio for (const ExtrusionEntity *extrusion_entity : layer->support_fills.entities) { Polylines poly; extrusion_entity->collect_polylines(poly); - for (const Polyline &polyline : poly) + for (const Polyline& polyline : poly) append(object_points, polyline.points); } } - // Include the brim. + // if brim, it superseed object & support for first layer if (config().skirt_distance_from_brim) { + // get first layer support + if (!object->support_layers().empty() && object->support_layers().front()->print_z == object->m_layers[0]->print_z) { + Points support_points; + for (const ExtrusionEntity* extrusion_entity : object->support_layers().front()->support_fills.entities) { + Polylines poly; + extrusion_entity->collect_polylines(poly); + for (const Polyline& polyline : poly) + append(support_points, polyline.points); + } + Polygon hull_support = Slic3r::Geometry::convex_hull(support_points); + for (const Polygon& poly : offset(hull_support, scale_(object->config().brim_width))) + append(object_points, poly.points); + } + // get object for (const ExPolygon& expoly : object->m_layers[0]->lslices) for (const Polygon& poly : offset(expoly.contour, scale_(object->config().brim_width))) append(object_points, poly.points); @@ -2414,6 +2428,7 @@ void Print::_make_brim_ears(const Flow &flow, const PrintObjectPtrs &objects, Ex ExPolygons unbrimmable_with_support = unbrimmable; for (PrintObject *object : objects) { ExPolygons object_islands; + ExPolygons support_island; for (const ExPolygon &expoly : object->m_layers.front()->lslices) if (brim_config.brim_inside_holes || brim_config.brim_width_interior > 0) object_islands.push_back(brim_offset==0?expoly:offset_ex(expoly, brim_offset)[0]); @@ -2422,19 +2437,21 @@ void Print::_make_brim_ears(const Flow &flow, const PrintObjectPtrs &objects, Ex if (!object->support_layers().empty()) { Polygons polys = object->support_layers().front()->support_fills.polygons_covered_by_spacing(flow.spacing_ratio, float(SCALED_EPSILON)); - for (Polygon poly : polys) { - //don't put ears over supports unless it's 100% fill - if (object->config().support_material_solid_first_layer) { - object_islands.push_back(brim_offset == 0 ? ExPolygon{ poly } : offset_ex(poly, brim_offset)[0]); - } else { - unbrimmable_with_support.push_back(ExPolygon{ poly }); + //put ears over supports unless it's 100% fill + if (object->config().support_material_solid_first_layer) { + for (Polygon poly : polys) { + object_islands.push_back(brim_offset == 0 ? ExPolygon{ poly } : offset_ex(poly, brim_offset)[0]); } + } else { + // offset2 to avoid bits of brim inside the raft + append(support_island, offset2_ex(polys, flow.scaled_width() * 2, -flow.scaled_width() * 2)); } } islands.reserve(islands.size() + object_islands.size() * object->m_instances.size()); coord_t ear_detection_length = scale_t(object->config().brim_ears_detection_length.value); - for (const PrintInstance ©_pt : object->m_instances) - for (const ExPolygon &poly : object_islands) { + // duplicate & translate for each instance + for (const PrintInstance& copy_pt : object->m_instances) { + for (const ExPolygon& poly : object_islands) { islands.push_back(poly); islands.back().translate(copy_pt.shift.x(), copy_pt.shift.y()); Polygon decimated_polygon = poly.contour; @@ -2449,11 +2466,17 @@ void Print::_make_brim_ears(const Flow &flow, const PrintObjectPtrs &objects, Ex decimated_polygon.points = points; } } - for (const Point &p : decimated_polygon.convex_points(brim_config.brim_ears_max_angle.value * PI / 180.0)) { + for (const Point& p : decimated_polygon.convex_points(brim_config.brim_ears_max_angle.value* PI / 180.0)) { pt_ears.push_back(p); pt_ears.back() += (copy_pt.shift); } } + // also for support-fobidden area + for (const ExPolygon& poly : support_island) { + unbrimmable_with_support.push_back(poly); + unbrimmable_with_support.back().translate(copy_pt.shift.x(), copy_pt.shift.y()); + } + } } islands = union_ex(islands, true); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 35a5e7a14..46d77e229 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -97,6 +97,13 @@ void PrintConfigDef::init_common_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("thumbnails_end_file", coBool); + def->label = L("Print at the end"); + def->tooltip = L("Print the thumbnail code at the end of the gcode file instead of the front." + "\nBe careful! Most firmwares expect it at the front, so be sure that your firmware support it."); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("thumbnails_with_bed", coBool); def->label = L("Bed on thumbnail"); def->tooltip = L("Show the bed texture on the thumbnail picture."); @@ -313,6 +320,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s² or %"); def->ratio_over = "default_acceleration"; def->min = 0; + def->max_literal = { -220, false }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); @@ -332,7 +340,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Bridges fan speed"); def->category = OptionCategory::cooling; def->tooltip = L("This fan speed is enforced during bridges and overhangs. It won't slow down the fan if it's currently running at a higher speed." - "\nSet to 1 to disable the fan." "\nSet to -1 to disable this override." "\nCan only be overriden by disable_fan_first_layers."); def->sidetext = L("%"); @@ -346,8 +353,8 @@ void PrintConfigDef::init_fff_params() def->label = L("Infill bridges fan speed"); def->category = OptionCategory::cooling; def->tooltip = L("This fan speed is enforced during all infill bridges. It won't slow down the fan if it's currently running at a higher speed." - "\nSet to 1 to disable the fan." - "\nSet to -1 to disable this override (will take the value of Bridges fan speed)." + "\nSet to 1 to follow default speed." + "\nSet to -1 to disable this override (internal bridges will use Bridges fan speed)." "\nCan only be overriden by disable_fan_first_layers."); def->sidetext = L("%"); def->min = -1; @@ -657,6 +664,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s² or %"); def->ratio_over = "machine_max_acceleration_X"; def->min = 0; + def->max_literal = { -200, false }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); @@ -684,7 +692,7 @@ void PrintConfigDef::init_fff_params() def->max = 1000; def->mode = comExpert; def->is_vector_extruder = true; - def->set_default_value(new ConfigOptionInts { 3 }); + def->set_default_value(new ConfigOptionInts { 1 }); def = this->add("dont_support_bridges", coBool); def->label = L("Don't support bridges"); @@ -708,7 +716,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->aliases = { "multiply_distance" }; def->min = 0; - def->set_default_value(new ConfigOptionFloat(0)); + def->set_default_value(new ConfigOptionFloat(6)); def = this->add("end_gcode", coString); def->label = L("End G-code"); @@ -740,7 +748,8 @@ void PrintConfigDef::init_fff_params() def->label = L("Ensure vertical shell thickness"); def->category = OptionCategory::perimeter; def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness " - "(top+bottom solid layers)."); + "(top+bottom solid layers)." + "\n!! solid_over_perimeters may erase these surfaces !! So you should deactivate it if you want to use this."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); @@ -848,6 +857,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/%"); def->ratio_over = "perimeter_extrusion_width"; def->min = 0; + def->max_literal = { 50, true }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(150, true)); @@ -859,6 +869,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/%"); def->ratio_over = "external_perimeter_extrusion_width"; def->min = 0; + def->max_literal = { 50, true }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(200, true)); @@ -874,10 +885,11 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + def->set_default_value(new ConfigOptionFloatOrPercent(105, true, false)); def = this->add("external_perimeter_extrusion_spacing", coFloatOrPercent); def->label = L("External perimeters"); @@ -890,6 +902,7 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; @@ -1154,7 +1167,7 @@ void PrintConfigDef::init_fff_params() def = this->add("extruder_colour", coStrings); def->label = L("Extruder Color"); def->category = OptionCategory::extruders; - def->tooltip = L("This is only used in the Slic3r interface as a visual help."); + def->tooltip = L("This is only used in Slic3r interface as a visual help."); def->gui_type = "color"; // Empty string means no color assigned yet. def->mode = comAdvanced; @@ -1239,10 +1252,11 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + def->set_default_value(new ConfigOptionFloatOrPercent(0, false, true)); def = this->add("extrusion_spacing", coFloatOrPercent); def->label = L("Default extrusion spacing"); @@ -1253,10 +1267,11 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false, true)); + def->set_default_value(new ConfigOptionFloatOrPercent(100, true, false)); def = this->add("fan_always_on", coBools); def->label = L("Keep fan always on"); @@ -1265,7 +1280,7 @@ void PrintConfigDef::init_fff_params() " Useful for PLA, harmful for ABS."); def->mode = comSimple; def->is_vector_extruder = true; - def->set_default_value(new ConfigOptionBools{ false }); + def->set_default_value(new ConfigOptionBools{ true }); def = this->add("fan_below_layer_time", coInts); def->label = L("Enable fan if layer print time is below"); @@ -1576,8 +1591,8 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_max_overlap", coPercents); def->label = L("Max line overlap"); - def->tooltip = L("This settign will ensure that all overlap are no hgher than this value." - " This is useful for filament that are too viscous, as the line can't flow under the previous one."); + def->tooltip = L("This setting will ensure that all 'overlap' are not higher than this value." + " This is useful for filaments that are too viscous, as the line can't flow under the previous one."); def->sidetext = L("%"); def->ratio_over = ""; def->min = 0; @@ -1835,6 +1850,7 @@ void PrintConfigDef::init_fff_params() " A value too low will make your extruder eat the filament."); def->ratio_over = "top_infill_extrusion_width"; def->min = 0; + def->max_literal = { 1, true }; def->mode = comExpert; def->sidetext = L("mm/%"); def->set_default_value(new ConfigOptionFloatOrPercent(50, true)); @@ -1862,6 +1878,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s² or %"); def->ratio_over = "default_acceleration"; def->min = 0; + def->max_literal = { -200, false }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -1891,10 +1908,11 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(140, true)); + def->set_default_value(new ConfigOptionFloatOrPercent(140, true, false)); def = this->add("first_layer_extrusion_spacing", coFloatOrPercent); def->label = L("First layer"); @@ -1906,6 +1924,7 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; @@ -1921,6 +1940,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm or %"); def->ratio_over = "nozzle_diameter"; def->min = 0; + def->max_literal = { 20, false }; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(75, true)); @@ -1928,11 +1948,9 @@ void PrintConfigDef::init_fff_params() def->label = L("Max"); def->full_label = L("Default first layer speed"); def->category = OptionCategory::speed; - def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied to all the print moves " - "but infill of the first layer, it can be overwritten by the 'default' (default depends of the type of the path) " - "speed if it's lower than that. If expressed as a percentage " - "it will scale the current speed." - "\nSet it at 100% to remove any first layer speed modification (with first_layer_infill_speed and first_layer_speed_min)."); + def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied as a maximum to all the print moves (but infill) of the first layer." + "\nIf expressed as a percentage it will scale the current speed." + "\nSet it at 100% to remove any first layer speed modification (but for infill)."); def->sidetext = L("mm/s or %"); def->ratio_over = "depends"; def->min = 0; @@ -1943,29 +1961,25 @@ void PrintConfigDef::init_fff_params() def->label = L("Infill"); def->full_label = L("Infill first layer speed"); def->category = OptionCategory::speed; - def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied to infill moves " - "of the first layer, it can be overwritten by the 'default' (solid infill or infill if not bottom) " - "speed if it's lower than that. If expressed as a percentage " - "(for example: 40%) it will scale the current infill speed."); + def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied as a maximum for all infill print moves of the first layer." + "\nIf expressed as a percentage it will scale the current infill speed." + "\nSet it at 100% to remove any infill first layer speed modification."); def->sidetext = L("mm/s or %"); def->ratio_over = "depends"; def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(30, false)); - def = this->add("first_layer_min_speed", coFloatOrPercent); + def = this->add("first_layer_min_speed", coFloat); def->label = L("Min"); def->full_label = L("Min first layer speed"); def->category = OptionCategory::speed; - def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied to all the print moves" - ", it can be overwritten by the 'default' (default depends of the type of the path) speed if it's higher than that." - " If expressed as a percentage it will scale the current speed." + def->tooltip = L("Minimum speed when printing the first layer." "\nSet zero to disable."); - def->sidetext = L("mm/s or %"); - def->ratio_over = "depends"; + def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + def->set_default_value(new ConfigOptionFloat(0)); def = this->add("first_layer_temperature", coInts); def->label = L("First layer"); @@ -1989,7 +2003,7 @@ void PrintConfigDef::init_fff_params() def->max = 1000; def->mode = comExpert; def->is_vector_extruder = true; - def->set_default_value(new ConfigOptionInts { 0 }); + def->set_default_value(new ConfigOptionInts { 4 }); def = this->add("gap_fill", coBool); def->label = L("Gap fill"); @@ -2006,7 +2020,7 @@ void PrintConfigDef::init_fff_params() def->category = OptionCategory::perimeter; def->tooltip = L("All gaps, between the last perimeter and the infill, which are thinner than a perimeter will be filled by gapfill."); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool{true }); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("gap_fill_min_area", coFloatOrPercent); def->label = L("Min surface"); @@ -2059,7 +2073,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("All characters that are written here will be replaced by '_' when writing the gcode file name." "\nIf the first charater is '[' or '(', then this field will be considered as a regexp (enter '[^a-zA-Z0-9]' to only use ascii char)."); def->mode = comExpert; - def->set_default_value(new ConfigOptionString("")); + def->set_default_value(new ConfigOptionString("[<>:\"/\\\\|?*]")); def = this->add("gcode_flavor", coEnum); def->label = L("G-code flavor"); @@ -2095,7 +2109,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back("Lerdge"); def->enum_labels.push_back(L("No extrusion")); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfSprinter)); + def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfMarlin)); def = this->add("gcode_filename_illegal_char", coString); def->label = L("Illegal characters"); @@ -2149,6 +2163,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s² or %"); def->ratio_over = "default_acceleration"; def->min = 0; + def->max_literal = { -200, false }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); @@ -2201,6 +2216,7 @@ void PrintConfigDef::init_fff_params() def->ratio_over = def_infill_anchor_min->ratio_over; def->gui_type = def_infill_anchor_min->gui_type; def->enum_values = def_infill_anchor_min->enum_values; + def->max_literal = def_infill_anchor_min->max_literal; def->enum_labels.push_back(L("0 (Simple connect)")); def->enum_labels.push_back("1 mm"); def->enum_labels.push_back("2 mm"); @@ -2331,10 +2347,11 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + def->set_default_value(new ConfigOptionFloatOrPercent(0, false, true)); def = this->add("infill_extrusion_spacing", coFloatOrPercent); def->label = L("Infill"); @@ -2346,10 +2363,11 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false, true)); + def->set_default_value(new ConfigOptionFloatOrPercent(100, true, false)); def = this->add("infill_first", coBool); def->label = L("Infill before perimeters"); @@ -2375,6 +2393,8 @@ void PrintConfigDef::init_fff_params() "as percentage (example: 15%) it is calculated over perimeter extrusion width."); def->sidetext = L("mm or %"); def->ratio_over = "perimeter_extrusion_width"; + def->min = 0; + def->max_literal = { 1, true }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(25, true)); @@ -2512,7 +2532,7 @@ void PrintConfigDef::init_fff_params() def = this->add("remaining_times", coBool); def->label = L("Supports remaining times"); def->category = OptionCategory::firmware; - def->tooltip = L("Emit somethign at 1 minute intervals into the G-code to let the firmware show accurate remaining time."); + def->tooltip = L("Emit something at 1 minute intervals into the G-code to let the firmware show accurate remaining time."); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); @@ -2538,13 +2558,16 @@ void PrintConfigDef::init_fff_params() def->category = OptionCategory::firmware; def->tooltip = L("The firmware supports stealth mode"); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("fan_speedup_time", coFloat); def->label = L("Fan startup delay"); def->category = OptionCategory::firmware; def->tooltip = L("Move the fan start in the past by at least this delay (in seconds, you can use decimals)." - " It assumes infinite acceleration for this time estimation, and will only take into account G1 and G0 moves. Use 0 to deactivate."); + " It assumes infinite acceleration for this time estimation, and will only take into account G1 and G0 moves." + "\nIt won't move fan comands from custom gcodes (they act as a sort of 'barrier')." + "\nIt won't move fan comands into the start gcode if the 'only custom start gcode' is activated." + "\nUse 0 to deactivate."); def->sidetext = L("s"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0)); @@ -2560,12 +2583,25 @@ void PrintConfigDef::init_fff_params() def->label = L("Fan KickStart time"); def->category = OptionCategory::firmware; def->tooltip = L("Add a M106 S255 (max speed for fan) for this amount of seconds before going down to the desired speed to kick-start the cooling fan." + "\nThis value is used for a 0->100% speedup, it will go down if the delta is lower." "\nSet to 0 to deactivate."); def->sidetext = L("s"); def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("lift_min", coFloat); + def->label = L("Min height for travel"); + def->category = OptionCategory::extruders; + def->tooltip = L("When an extruder travels to an object (from the start position or from an object to another), the nozzle height is guaranteed to be at least at this value." + "\nIt's made to ensure the nozzle won't hit clips or things you have on your bed. But be careful to not put a clip in the 'convex shape' of an object." + "\nSet to 0 to disable."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->is_vector_extruder = true; + def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("machine_limits_usage", coEnum); def->label = L("How to apply limits"); def->full_label = L("Purpose of Machine Limits"); @@ -2713,6 +2749,7 @@ void PrintConfigDef::init_fff_params() def->category = OptionCategory::speed; def->tooltip = L("If your firmware stops while printing, it may have its gcode queue full." " Set this parameter to merge extrusions into bigger ones to reduce the number of gcode commands the printer has to process each second." + "\nOn 8bit controlers, a value of 150 is typical." "\nNote that reducing your printing speed (at least for the external extrusions) will reduce the number of time this will triggger and so increase quality." "\nSet zero to disable."); def->min = 0; @@ -2731,19 +2768,22 @@ void PrintConfigDef::init_fff_params() def->is_vector_extruder = true; def->set_default_value(new ConfigOptionInts { 100 }); - def = this->add("max_layer_height", coFloats); + def = this->add("max_layer_height", coFloatsOrPercents); def->label = L("Max"); def->full_label = L("Max layer height"); def->category = OptionCategory::general; def->tooltip = L("This is the highest printable layer height for this extruder, used to cap " "the variable layer height and support layer height. Maximum recommended layer height " "is 75% of the extrusion width to achieve reasonable inter-layer adhesion. " - "If set to 0, layer height is limited to 75% of the nozzle diameter."); - def->sidetext = L("mm"); + "\nCan be a % of the nozzle diameter." + "\nIf set to 0, layer height is limited to 75% of the nozzle diameter."); + def->sidetext = L("mm or %"); + def->ratio_over = "nozzle_diameter"; def->min = 0; + def->max_literal = { 10, false }; def->mode = comSimple; def->is_vector_extruder = true; - def->set_default_value(new ConfigOptionFloats { 0. }); + def->set_default_value(new ConfigOptionFloatsOrPercents{ FloatOrPercent{ 75, true} }); def = this->add("max_print_speed", coFloat); def->label = L("Max print speed"); @@ -2823,17 +2863,20 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); - def = this->add("min_layer_height", coFloats); + def = this->add("min_layer_height", coFloatsOrPercents); def->label = L("Min"); def->full_label = L("Min layer height"); def->category = OptionCategory::extruders; def->tooltip = L("This is the lowest printable layer height for this extruder and limits " - "the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm."); - def->sidetext = L("mm"); + "the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm." + "\nCan be a % of the nozzle diameter."); + def->sidetext = L("mm or %"); + def->ratio_over = "nozzle_diameter"; def->min = 0; + def->max_literal = { 5, false }; def->mode = comSimple; def->is_vector_extruder = true; - def->set_default_value(new ConfigOptionFloats { 0.07 }); + def->set_default_value(new ConfigOptionFloatsOrPercents{ FloatOrPercent{ 5, true} }); def = this->add("min_length", coFloat); def->label = L("Minimum extrusion length"); @@ -2856,6 +2899,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm or %"); def->ratio_over = "perimeter_extrusion_width"; def->min = 0; + def->max_literal = { 15, false }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(200, true)); @@ -3006,6 +3050,7 @@ void PrintConfigDef::init_fff_params() " Set to 0 to deactivate."); def->ratio_over = "nozzle_diameter"; def->min = 0; + def->max_literal = { 10, true }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(75, true)); @@ -3025,6 +3070,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Number of mm the overhang need to be for the reversal to be considered useful. Can be a % of the perimeter width."); def->ratio_over = "perimeter_extrusion_width"; def->min = 0; + def->max_literal = { 20, false }; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(250, true)); @@ -3080,6 +3126,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s² or %"); def->ratio_over = "default_acceleration"; def->min = 0; + def->max_literal = { -200, false }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); @@ -3088,7 +3135,8 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Round corners for perimeters"); def->category = OptionCategory::perimeter; def->tooltip = L("Internal perimeters will go around sharp corners by turning around instead of making the same sharp corner." - " This can help when there are visible holes in sharp corners on perimeters"); + " This can help when there are visible holes in sharp corners on perimeters. It also help to print the letters on the benchy stern." + "\nCan incur some more processing time, and corners are a bit less sharp."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); @@ -3114,10 +3162,11 @@ void PrintConfigDef::init_fff_params() def->aliases = { "perimeters_extrusion_width" }; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + def->set_default_value(new ConfigOptionFloatOrPercent(0, false, true)); def = this->add("perimeter_extrusion_spacing", coFloatOrPercent); def->label = L("Perimeters"); @@ -3129,10 +3178,11 @@ void PrintConfigDef::init_fff_params() def->aliases = { "perimeters_extrusion_width" }; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false, true)); + def->set_default_value(new ConfigOptionFloatOrPercent(100, true, false)); def = this->add("perimeter_speed", coFloat); def->label = L("Internal"); @@ -3430,7 +3480,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("%"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionPercent(100)); + def->set_default_value(new ConfigOptionPercent(80)); def = this->add("seam_travel_cost", coPercent); def->label = L("Travel cost"); @@ -3440,7 +3490,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("%"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionPercent(100)); + def->set_default_value(new ConfigOptionPercent(20)); def = this->add("seam_gap", coFloatsOrPercents); def->label = L("Seam gap"); @@ -3449,6 +3499,7 @@ void PrintConfigDef::init_fff_params() "\nCan be a mm or a % of the current extruder diameter."); def->sidetext = L("mm or %"); def->min = 0; + def->max_literal = { 5, false }; def->mode = comExpert; def->is_vector_extruder = true; def->set_default_value(new ConfigOptionFloatsOrPercents{ FloatOrPercent{15,true} }); @@ -3522,6 +3573,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm or %"); def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -3572,6 +3624,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm or %"); def->ratio_over = "nozzle_diameter"; def->min = 0; + def->max_literal = { 100, false }; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(6, false)); @@ -3586,6 +3639,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm or %"); def->ratio_over = "nozzle_diameter"; def->min = 0; + def->max_literal = { 500, false }; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(20, false)); @@ -3686,10 +3740,11 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + def->set_default_value(new ConfigOptionFloatOrPercent(0, false, true)); def = this->add("solid_infill_extrusion_spacing", coFloatOrPercent); def->label = L("Solid spacing"); @@ -3701,10 +3756,11 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false, true)); + def->set_default_value(new ConfigOptionFloatOrPercent(100, true, false)); def = this->add("solid_infill_speed", coFloatOrPercent); def->label = L("Solid"); @@ -3871,11 +3927,12 @@ void PrintConfigDef::init_fff_params() def->category = OptionCategory::perimeter; def->tooltip = L("When you have a medium/hight number of top/bottom solid layers, and a low/medium of perimeters," " then it have to put some solid infill inside the part to have enough solid layers." - "\nBy setting this to somethign higher than 0, you can remove this 'inside filling'." + "\nBy setting this to something higher than 0, you can remove this 'inside filling'." " This number allow to keep some if there is a low number of perimeter over the void." "\nIf this setting is equal or higher than the top/bottom solid layer count, it won't evict anything." "\nIf this setting is set to 1, it will evict all solid fill are are only over perimeters." - "\nSet zero to disable."); + "\nSet zero to disable." + "\n!! ensure_vertical_shell_thickness may be erased by this setting !! You may want to deactivate at least one of the two."); def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(2)); @@ -3902,6 +3959,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm or %"); def->ratio_over = "external_perimeter_extrusion_width"; def->min = 0; + def->max_literal = { 10, false}; def->mode = comAdvanced; // Default is half the external perimeter width. def->set_default_value(new ConfigOptionFloatOrPercent(50, true)); @@ -3953,6 +4011,7 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "top_infill_extrusion_width"; def->sidetext = L("mm"); def->min = 0; + def->max_literal = { 20, true }; def->mode = comAdvanced; def->aliases = { "support_material_contact_distance" }; def->set_default_value(new ConfigOptionFloatOrPercent(0.2, false)); @@ -3966,6 +4025,7 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "top_infill_extrusion_width"; def->sidetext = L("mm"); def->min = 0; + def->max_literal = { 20, true }; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0.2,false)); @@ -3989,7 +4049,7 @@ void PrintConfigDef::init_fff_params() "(1+, 0 to use the current extruder to minimize tool changes)."); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); def = this->add("support_material_extrusion_width", coFloatOrPercent); def->label = L("Support material"); @@ -4002,6 +4062,7 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -4020,7 +4081,7 @@ void PrintConfigDef::init_fff_params() "(1+, 0 to use the current extruder to minimize tool changes). This affects raft too."); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); def = this->add("support_material_interface_layers", coInt); def->label = L("Interface layers"); @@ -4203,6 +4264,7 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->mode = comExpert; def->min = 0; + def->max_literal = { 20, true }; def->set_default_value(new ConfigOptionFloatOrPercent(33, true)); def = this->add("thin_walls_overlap", coFloatOrPercent); @@ -4213,6 +4275,7 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "external_perimeter_extrusion_width"; def->mode = comExpert; def->min = 0; + def->max_literal = { 10, true }; def->set_default_value(new ConfigOptionFloatOrPercent(50, true)); def = this->add("thin_walls_merge", coBool); @@ -4289,10 +4352,11 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + def->set_default_value(new ConfigOptionFloatOrPercent(105, true, false)); def = this->add("top_infill_extrusion_spacing", coFloatOrPercent); def->label = L("Top solid spacing"); @@ -4303,6 +4367,7 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "nozzle_diameter"; def->min = 0; def->max = 1000; + def->max_literal = { 10, true }; def->precision = 6; def->can_phony = true; def->mode = comAdvanced; @@ -4354,6 +4419,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s² or %"); def->ratio_over = "default_acceleration"; def->min = 0; + def->max_literal = { -200, false }; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(1500, false)); @@ -4513,6 +4579,8 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Width of the brim for the wipe tower. Can be in mm or in % of the (assumed) only one nozzle diameter."); def->ratio_over = "nozzle_diameter"; def->mode = comAdvanced; + def->min = 0; + def->max_literal = { 100, true }; def->set_default_value(new ConfigOptionFloatOrPercent(150,true)); def = this->add("wipe_tower_x", coFloat); @@ -4645,6 +4713,7 @@ void PrintConfigDef::init_fff_params() " This setting allows you some leway to broaden the detection." "\nIn mm or in % of the radius."); def->sidetext = L("mm or %"); + def->max_literal = { 10, false}; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0.01, false)); @@ -4870,6 +4939,7 @@ void PrintConfigDef::init_milling_params() " You can set a number of mm or a percentage of the calculated optimal extra width (from flow calculation)."); def->sidetext = L("mm or %"); def->ratio_over = "computed_on_the_fly"; + def->max_literal = { 20, false }; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(150, true)); @@ -4879,6 +4949,7 @@ void PrintConfigDef::init_milling_params() def->tooltip = L("This setting restricts the post-process milling to a certain height, to avoid milling the bed. It can be a mm or a % of the first layer height (so it can depend on the object)."); def->sidetext = L("mm or %"); def->ratio_over = "first_layer_height"; + def->max_literal = { 10, false }; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(200, true)); @@ -5673,6 +5744,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va value = "5"; } } + if ("first_layer_min_speed" == opt_key && value.back() == '%') + value = value.substr(0, value.length() - 1); //no percent. // Ignore the following obsolete configuration keys: static std::set<std::string> ignore = { @@ -5730,6 +5803,10 @@ std::map<std::string,std::string> PrintConfigDef::from_prusa(t_config_option_key if(value == "0") output["infill_connection"] = "notconnected"; } + if ("first_layer_speed" == opt_key) { + output["first_layer_min_speed"] = value; + output["first_layer_infill_speed"] = value; + } return output; } @@ -5851,6 +5928,7 @@ std::unordered_set<std::string> prusa_export_to_remove_keys = { "infill_dense_algo", "infill_dense", "infill_extrusion_spacing", +"lift_min", "machine_max_acceleration_travel", "max_speed_reduction", "milling_after_z", @@ -5914,6 +5992,7 @@ std::unordered_set<std::string> prusa_export_to_remove_keys = { "thin_walls_speed", "thumbnails_color", "thumbnails_custom_color", +"thumbnails_end_file", "thumbnails_with_bed", "thumbnails_with_support", "time_estimation_compensation", @@ -6113,7 +6192,8 @@ double PrintConfig::min_object_distance(const ConfigBase *config, double ref_hei skirts += config->option("skirt_brim")->getInt(); if (skirts > 0 && config->option("skirt_height")->getInt() >= 1 && !config->option("complete_objects_one_skirt")->getBool()) { float overlap_ratio = 1; - if (config->option<ConfigOptionPercents>("filament_max_overlap")) overlap_ratio = config->get_computed_value("filament_max_overlap"); + //can't know the extruder, so we settle on the worst: 100% + //if (config->option<ConfigOptionPercents>("filament_max_overlap")) overlap_ratio = config->get_computed_value("filament_max_overlap"); if (ref_height == 0) { skirt_dist = config->option("skirt_distance")->getFloat(); Flow skirt_flow = Flow::new_from_config_width( diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 592b576ab..0672cdced 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1334,12 +1334,13 @@ public: ConfigOptionPercent first_layer_flow_ratio; ConfigOptionFloatOrPercent first_layer_speed; ConfigOptionFloatOrPercent first_layer_infill_speed; - ConfigOptionFloatOrPercent first_layer_min_speed; + ConfigOptionFloat first_layer_min_speed; ConfigOptionInts first_layer_temperature; ConfigOptionInts full_fan_speed_layer; ConfigOptionFloatOrPercent infill_acceleration; + ConfigOptionFloat lift_min; ConfigOptionInts max_fan_speed; - ConfigOptionFloats max_layer_height; + ConfigOptionFloatsOrPercents max_layer_height; ConfigOptionFloat max_print_height; ConfigOptionPercents max_speed_reduction; ConfigOptionFloats milling_diameter; @@ -1348,7 +1349,7 @@ public: //ConfigOptionPoints milling_offset; //ConfigOptionFloats milling_z_offset; ConfigOptionInts min_fan_speed; - ConfigOptionFloats min_layer_height; + ConfigOptionFloatsOrPercents min_layer_height; ConfigOptionFloats min_print_speed; ConfigOptionFloat min_skirt_length; ConfigOptionString notes; @@ -1379,6 +1380,7 @@ public: ConfigOptionPoints thumbnails; ConfigOptionString thumbnails_color; ConfigOptionBool thumbnails_custom_color; + ConfigOptionBool thumbnails_end_file; ConfigOptionBool thumbnails_with_bed; ConfigOptionPercent time_estimation_compensation; ConfigOptionInts top_fan_speed; @@ -1441,6 +1443,7 @@ protected: OPT_PTR(first_layer_temperature); OPT_PTR(full_fan_speed_layer); OPT_PTR(infill_acceleration); + OPT_PTR(lift_min); OPT_PTR(max_fan_speed); OPT_PTR(max_layer_height); OPT_PTR(max_print_height); @@ -1482,6 +1485,7 @@ protected: OPT_PTR(thumbnails); OPT_PTR(thumbnails_color); OPT_PTR(thumbnails_custom_color); + OPT_PTR(thumbnails_end_file); OPT_PTR(thumbnails_with_bed); OPT_PTR(time_estimation_compensation); OPT_PTR(top_fan_speed); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 25c33cd84..cbfa7d5ef 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -489,6 +489,8 @@ namespace Slic3r { // and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces // to close these surfaces reliably. //FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters? + //note: only if not "ensure vertical shell" + //TODO merill: as "ensure_vertical_shell_thickness" is innefective, this should be simplified / streamlined / deleted? this->discover_horizontal_shells(); m_print->throw_if_canceled(); @@ -1041,6 +1043,7 @@ namespace Slic3r { if ((best_point - polygon_reduced.contour.points[pos_check]).norm() < scale_(0.01)) ++pos_check; else polygon_reduced.contour.points.erase(polygon_reduced.contour.points.begin() + pos_check); } + polygon_reduced.holes.clear(); return polygon_reduced; } @@ -1051,7 +1054,7 @@ namespace Slic3r { //fix uncoverable area ExPolygons polygons_to_cover = intersection_ex(bad_polygon_to_cover, growing_area); if (polygons_to_cover.size() != 1) - return { bad_polygon_to_cover }; + return { growing_area }; const ExPolygon polygon_to_cover = polygons_to_cover.front(); //grow the polygon_to_check enough to cover polygon_to_cover @@ -1152,160 +1155,177 @@ namespace Slic3r { const float COEFF_SPLIT = 1.5; for (const PrintRegion* region : this->m_print->regions()) { - LayerRegion* previousOne = NULL; //count how many surface there are on each one if (region->config().infill_dense.getBool() && region->config().fill_density < 40) { - for (size_t idx_layer = this->layers().size() - 1; idx_layer < this->layers().size(); --idx_layer) { - LayerRegion* layerm = NULL; + std::vector<LayerRegion*> layeridx2lregion; + std::vector<Surfaces> new_surfaces; //surface store, as you can't modify them when working in // + // store the LayerRegion on which we are working + layeridx2lregion.resize(this->layers().size(), nullptr); + new_surfaces.resize(this->layers().size(), Surfaces{}); + for (size_t idx_layer = 0; idx_layer < this->layers().size(); ++idx_layer) { + LayerRegion* layerm = nullptr; for (LayerRegion* lregion : this->layers()[idx_layer]->regions()) { if (lregion->region() == region) { layerm = lregion; break; } } - if (layerm == NULL) { - previousOne = NULL; - continue; - } - if (previousOne == NULL) { - previousOne = layerm; - continue; - } - Surfaces surfs_to_add; - for (Surface& surface : layerm->fill_surfaces.surfaces) { - surface.maxNbSolidLayersOnTop = -1; - if (!surface.has_fill_solid()) { - Surfaces surf_to_add; - ExPolygons dense_polys; - std::vector<uint16_t> dense_priority; - ExPolygons surfs_with_overlap = { surface.expolygon }; - ////create a surface with overlap to allow the dense thing to bond to the infill - coord_t scaled_width = layerm->flow(frInfill, true).scaled_width(); - coord_t overlap = scaled_width / 4; - for (const ExPolygon& surf_with_overlap : surfs_with_overlap) { - ExPolygons sparse_polys = { surf_with_overlap }; - //find the surface which intersect with the smallest maxNb possible - for (Surface& upp : previousOne->fill_surfaces.surfaces) { - if (upp.has_fill_solid()) { - // i'm using intersection_ex because the result different than - // upp.expolygon.overlaps(surf.expolygon) or surf.expolygon.overlaps(upp.expolygon) - //and a little offset2 to remove the almost supported area - ExPolygons intersect = - offset2_ex( - intersection_ex(sparse_polys, { upp.expolygon }, true) - , (float)-layerm->flow(frInfill).scaled_width(), (float)layerm->flow(frInfill).scaled_width()); - if (!intersect.empty()) { - double area_intersect = 0; - // calculate area to decide if area is small enough for autofill - if (layerm->region()->config().infill_dense_algo.value == dfaAutoNotFull || layerm->region()->config().infill_dense_algo.value == dfaAutoOrEnlarged) - for (ExPolygon poly_inter : intersect) - area_intersect += poly_inter.area(); - - double surf_with_overlap_area = surf_with_overlap.area(); - if (layerm->region()->config().infill_dense_algo.value == dfaEnlarged - || (layerm->region()->config().infill_dense_algo.value == dfaAutoOrEnlarged && surf_with_overlap_area <= area_intersect * COEFF_SPLIT)) { - //expand the area a bit - intersect = offset_ex(intersect, double(scale_(layerm->region()->config().external_infill_margin.get_abs_value( - region->config().perimeters == 0 ? 0 : (layerm->flow(frExternalPerimeter).width + layerm->flow(frPerimeter).spacing() * (region->config().perimeters - 1)))))); - } else if (layerm->region()->config().infill_dense_algo.value == dfaAutoNotFull - || layerm->region()->config().infill_dense_algo.value == dfaAutomatic) { - - //like intersect.empty() but more resilient - if (layerm->region()->config().infill_dense_algo.value == dfaAutomatic - || surf_with_overlap_area > area_intersect * COEFF_SPLIT) { - ExPolygons cover_intersect; - - // it will be a dense infill, split the surface if needed - //ExPolygons cover_intersect; - for (ExPolygon& expoly_tocover : intersect) { - ExPolygons temp = dense_fill_fit_to_size( - expoly_tocover, - surf_with_overlap, - 4 * layerm->flow(frInfill).scaled_width(), - 0.01f); - cover_intersect.insert(cover_intersect.end(), temp.begin(), temp.end()); - } - intersect = cover_intersect; - } else { - intersect.clear(); - } - } + if (layerm != nullptr) + layeridx2lregion[idx_layer] = layerm; + } + // run in parallel, it's a costly thing. + tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()-1), + [this, &layeridx2lregion, &new_surfaces, region, COEFF_SPLIT](const tbb::blocked_range<size_t>& range) { + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + // we our LayerRegion and the one on top + LayerRegion* layerm = layeridx2lregion[idx_layer]; + const LayerRegion* previousOne = nullptr; + previousOne = layeridx2lregion[idx_layer + 1]; + if (layerm == nullptr || previousOne == nullptr) { + continue; + } + Surfaces &surfs_to_add = new_surfaces[idx_layer]; + // check all surfaces to cover + for (Surface& surface : layerm->fill_surfaces.surfaces) { + surface.maxNbSolidLayersOnTop = -1; + if (!surface.has_fill_solid()) { + Surfaces surf_to_add; + ExPolygons dense_polys; + std::vector<uint16_t> dense_priority; + const ExPolygons surfs_with_overlap = { surface.expolygon }; + ////create a surface with overlap to allow the dense thing to bond to the infill + coord_t scaled_width = layerm->flow(frInfill, true).scaled_width(); + coord_t overlap = scaled_width / 4; + for (const ExPolygon& surf_with_overlap : surfs_with_overlap) { + ExPolygons sparse_polys = { surf_with_overlap }; + //find the surface which intersect with the smallest maxNb possible + for (const Surface& upp : previousOne->fill_surfaces.surfaces) { + if (upp.has_fill_solid()) { + // i'm using intersection_ex because the result different than + // upp.expolygon.overlaps(surf.expolygon) or surf.expolygon.overlaps(upp.expolygon) + //and a little offset2 to remove the almost supported area + ExPolygons intersect = + offset2_ex( + intersection_ex(sparse_polys, { upp.expolygon }, true) + , (float)-layerm->flow(frInfill).scaled_width(), (float)layerm->flow(frInfill).scaled_width()); if (!intersect.empty()) { - - ExPolygons sparse_surfaces = diff_ex(sparse_polys, intersect, true); - ExPolygons dense_surfaces = diff_ex(sparse_polys, sparse_surfaces, true); - for (ExPolygon& poly : intersect) { - uint16_t priority = 1; - ExPolygons dense = { poly }; - for (size_t idx_dense = 0; idx_dense < dense_polys.size(); idx_dense++) { - ExPolygons dense_test = diff_ex(dense, { dense_polys[idx_dense] }, true); - if (dense_test != dense) { - priority = std::max(priority, uint16_t(dense_priority[idx_dense] + 1)); + double area_intersect = 0; + // calculate area to decide if area is small enough for autofill + if (layerm->region()->config().infill_dense_algo.value == dfaAutoNotFull || layerm->region()->config().infill_dense_algo.value == dfaAutoOrEnlarged) + for (ExPolygon poly_inter : intersect) + area_intersect += poly_inter.area(); + + double surf_with_overlap_area = surf_with_overlap.area(); + if (layerm->region()->config().infill_dense_algo.value == dfaEnlarged + || (layerm->region()->config().infill_dense_algo.value == dfaAutoOrEnlarged && surf_with_overlap_area <= area_intersect * COEFF_SPLIT)) { + //expand the area a bit + intersect = offset_ex(intersect, (scaled(layerm->region()->config().external_infill_margin.get_abs_value( + region->config().perimeters == 0 ? 0 : (layerm->flow(frExternalPerimeter).width + layerm->flow(frPerimeter).spacing() * (region->config().perimeters - 1)))))); + } else if (layerm->region()->config().infill_dense_algo.value == dfaAutoNotFull + || layerm->region()->config().infill_dense_algo.value == dfaAutomatic) { + + //like intersect.empty() but more resilient + if (layerm->region()->config().infill_dense_algo.value == dfaAutomatic + || surf_with_overlap_area > area_intersect * COEFF_SPLIT) { + ExPolygons cover_intersect; + + // it will be a dense infill, split the surface if needed + //ExPolygons cover_intersect; + for (ExPolygon& expoly_tocover : intersect) { + ExPolygons temp = dense_fill_fit_to_size( + expoly_tocover, + surf_with_overlap, + 4 * layerm->flow(frInfill).scaled_width(), + 0.01f); + cover_intersect.insert(cover_intersect.end(), temp.begin(), temp.end()); } - dense = dense_test; + intersect = cover_intersect; + } else { + intersect.clear(); } - dense_polys.insert(dense_polys.end(), dense.begin(), dense.end()); - for (int i = 0; i < dense.size(); i++) - dense_priority.push_back(priority); } - //assign (copy) - sparse_polys = std::move(sparse_surfaces); + if (!intersect.empty()) { + + ExPolygons sparse_surfaces = diff_ex(sparse_polys, intersect, true); + ExPolygons dense_surfaces = diff_ex(sparse_polys, sparse_surfaces, true); + for (ExPolygon& poly : intersect) { + uint16_t priority = 1; + ExPolygons dense = { poly }; + for (size_t idx_dense = 0; idx_dense < dense_polys.size(); idx_dense++) { + ExPolygons dense_test = diff_ex(dense, { dense_polys[idx_dense] }, true); + if (dense_test != dense) { + priority = std::max(priority, uint16_t(dense_priority[idx_dense] + 1)); + } + dense = dense_test; + } + dense_polys.insert(dense_polys.end(), dense.begin(), dense.end()); + for (int i = 0; i < dense.size(); i++) + dense_priority.push_back(priority); + } + //assign (copy) + sparse_polys = std::move(sparse_surfaces); + } } } + //check if we are full-dense + if (sparse_polys.empty()) break; } - //check if we are full-dense - if (sparse_polys.empty()) break; - } - //check if we need to split the surface - if (!dense_polys.empty()) { - double area_dense = 0; - for (ExPolygon poly_inter : dense_polys) area_dense += poly_inter.area(); - double area_sparse = 0; - for (ExPolygon poly_inter : sparse_polys) area_sparse += poly_inter.area(); - // if almost no empty space, simplify by filling everything (else) - if (area_sparse > area_dense * 0.1) { - //split - //dense_polys = union_ex(dense_polys); - for (int idx_dense = 0; idx_dense < dense_polys.size(); idx_dense++) { - ExPolygon dense_poly = dense_polys[idx_dense]; - //remove overlap with perimeter - ExPolygons offseted_dense_polys = intersection_ex({ dense_poly }, layerm->fill_no_overlap_expolygons); - //add overlap with everything - offseted_dense_polys = offset_ex(offseted_dense_polys, overlap); - for (ExPolygon offseted_dense_poly : offseted_dense_polys) { - Surface dense_surf(surface, offseted_dense_poly); - dense_surf.maxNbSolidLayersOnTop = 1; - dense_surf.priority = dense_priority[idx_dense]; - surf_to_add.push_back(dense_surf); + //check if we need to split the surface + if (!dense_polys.empty()) { + double area_dense = 0; + for (ExPolygon poly_inter : dense_polys) area_dense += poly_inter.area(); + double area_sparse = 0; + for (ExPolygon poly_inter : sparse_polys) area_sparse += poly_inter.area(); + // if almost no empty space, simplify by filling everything (else) + if (area_sparse > area_dense * 0.1) { + //split + //dense_polys = union_ex(dense_polys); + for (int idx_dense = 0; idx_dense < dense_polys.size(); idx_dense++) { + ExPolygon dense_poly = dense_polys[idx_dense]; + //remove overlap with perimeter + ExPolygons offseted_dense_polys = intersection_ex({ dense_poly }, layerm->fill_no_overlap_expolygons); + //add overlap with everything + offseted_dense_polys = offset_ex(offseted_dense_polys, overlap); + for (ExPolygon offseted_dense_poly : offseted_dense_polys) { + Surface dense_surf(surface, offseted_dense_poly); + dense_surf.maxNbSolidLayersOnTop = 1; + dense_surf.priority = dense_priority[idx_dense]; + surf_to_add.push_back(dense_surf); + } } + sparse_polys = union_ex(sparse_polys); + for (ExPolygon sparse_poly : sparse_polys) { + Surface sparse_surf(surface, sparse_poly); + surf_to_add.push_back(sparse_surf); + } + //layerm->fill_surfaces.surfaces.erase(it_surf); + } else { + surface.maxNbSolidLayersOnTop = 1; + surf_to_add.clear(); + surf_to_add.push_back(surface); + break; } - sparse_polys = union_ex(sparse_polys); - for (ExPolygon sparse_poly : sparse_polys) { - Surface sparse_surf(surface, sparse_poly); - surf_to_add.push_back(sparse_surf); - } - //layerm->fill_surfaces.surfaces.erase(it_surf); } else { - surface.maxNbSolidLayersOnTop = 1; surf_to_add.clear(); - surf_to_add.push_back(surface); + surf_to_add.emplace_back(std::move(surface)); + // mitigation: if not possible, don't try the others. break; } - } else { - surf_to_add.clear(); - surf_to_add.emplace_back(std::move(surface)); - // mitigation: if not possible, don't try the others. - break; } - } - // break go here - surfs_to_add.insert(surfs_to_add.begin(), surf_to_add.begin(), surf_to_add.end()); - } else surfs_to_add.emplace_back(std::move(surface)); + // break go here + surfs_to_add.insert(surfs_to_add.begin(), surf_to_add.begin(), surf_to_add.end()); + } else surfs_to_add.emplace_back(std::move(surface)); + } + //layerm->fill_surfaces.surfaces = std::move(surfs_to_add); } - layerm->fill_surfaces.surfaces = std::move(surfs_to_add); - previousOne = layerm; + }); + // now set the new surfaces + for (size_t idx_layer = 0; idx_layer < this->layers().size() - 1; ++idx_layer) { + LayerRegion* lr = layeridx2lregion[idx_layer]; + if(lr != nullptr && layeridx2lregion[idx_layer + 1] != nullptr) + lr->fill_surfaces.surfaces = new_surfaces[idx_layer]; } } } @@ -1745,6 +1765,8 @@ namespace Slic3r { //solid_over_perimeters value, to remove solid fill where there's only perimeters on multiple layers const int nb_perimeter_layers_for_solid_fill = region.config().solid_over_perimeters.value; + const int min_layer_no_solid = region.config().bottom_solid_layers.value - 1; + const int min_z_no_solid = region.config().bottom_solid_min_thickness; if (!top_bottom_surfaces_all_regions) { // This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness @@ -1752,7 +1774,8 @@ namespace Slic3r { BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : cache top / bottom"; tbb::parallel_for( tbb::blocked_range<size_t>(0, num_layers, grain_size), - [this, idx_region, &cache_top_botom_regions, nb_perimeter_layers_for_solid_fill](const tbb::blocked_range<size_t>& range) { + [this, idx_region, &cache_top_botom_regions, nb_perimeter_layers_for_solid_fill, min_layer_no_solid, min_z_no_solid] + (const tbb::blocked_range<size_t>& range) { for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { m_print->throw_if_canceled(); Layer& layer = *m_layers[idx_layer]; @@ -1762,7 +1785,8 @@ namespace Slic3r { auto& cache = cache_top_botom_regions[idx_layer]; cache.top_surfaces = offset_ex(to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing); append(cache.top_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing)); - if (nb_perimeter_layers_for_solid_fill != 0) { + if (nb_perimeter_layers_for_solid_fill != 0 && (idx_layer > min_layer_no_solid || layer.print_z < min_z_no_solid)) { + //it needs to be activated and we don't check the firs layers, where everything have to be solid. cache.top_fill_surfaces = offset_ex(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing); cache.top_perimeter_surfaces = to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)); } @@ -1770,7 +1794,7 @@ namespace Slic3r { const SurfaceType surfaces_bottom[2] = { stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge }; cache.bottom_surfaces = offset_ex(to_expolygons(layerm.slices().filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing); append(cache.bottom_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); - if (nb_perimeter_layers_for_solid_fill != 0) { + if (nb_perimeter_layers_for_solid_fill != 0 && (idx_layer > min_layer_no_solid || layer.print_z < min_z_no_solid)) { cache.bottom_fill_surfaces = offset_ex(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing); cache.bottom_perimeter_surfaces = to_expolygons(layerm.slices().filter_by_types(surfaces_bottom, 2)); } @@ -1788,7 +1812,7 @@ namespace Slic3r { BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : ensure vertical wall thickness"; tbb::parallel_for( tbb::blocked_range<size_t>(0, num_layers, grain_size), - [this, idx_region, &cache_top_botom_regions, nb_perimeter_layers_for_solid_fill] + [this, idx_region, &cache_top_botom_regions, nb_perimeter_layers_for_solid_fill, min_layer_no_solid, min_z_no_solid] (const tbb::blocked_range<size_t>& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { @@ -1861,7 +1885,7 @@ namespace Slic3r { // than running the union_ all at once. shell = union_ex(shell, false); } - if (nb_perimeter_layers_for_solid_fill != 0) { + if (nb_perimeter_layers_for_solid_fill != 0 && (idx_layer > min_layer_no_solid || print_z < min_z_no_solid)) { if (!cache.top_fill_surfaces.empty()) { expolygons_append(fill_shell, cache.top_fill_surfaces); fill_shell = union_ex(fill_shell, false); @@ -1890,7 +1914,7 @@ namespace Slic3r { // than running the union_ all at once. shell = union_ex(shell, false); } - if (nb_perimeter_layers_for_solid_fill != 0) { + if (nb_perimeter_layers_for_solid_fill != 0 && (idx_layer > min_layer_no_solid || layer->print_z < min_z_no_solid)) { if (!cache.bottom_fill_surfaces.empty()) { expolygons_append(fill_shell, cache.bottom_fill_surfaces); fill_shell = union_ex(fill_shell, false); @@ -1969,7 +1993,7 @@ namespace Slic3r { shell = union_ex(shell); ExPolygons toadd; //check if a polygon is only over perimeter, in this case evict it (depends from nb_perimeter_layers_for_solid_fill value) - if (nb_perimeter_layers_for_solid_fill != 0) { + if (nb_perimeter_layers_for_solid_fill != 0 && (idx_layer > min_layer_no_solid || layer->print_z < min_z_no_solid)) { for (int i = 0; i < shell.size(); i++) { if (nb_perimeter_layers_for_solid_fill < 2 || intersection_ex({ shell[i] }, max_perimeter_shell, false).empty()) { ExPolygons expoly = intersection_ex({ shell[i] }, fill_shell); @@ -2120,6 +2144,7 @@ namespace Slic3r { // iterate through lower layers spanned by bridge_flow double bottom_z = layer->print_z - bridge_flow.height; + //TODO take into account sparse ratio! double protrude_by = bridge_flow.height - layer->height; for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { const Layer* lower_layer = m_layers[i]; @@ -2134,22 +2159,44 @@ namespace Slic3r { // intersect such lower internal surfaces with the candidate solid surfaces to_bridge_pp = intersection(to_bridge_pp, lower_internal); } + if (to_bridge_pp.empty()) continue; + //put to_bridge_pp into to_bridge // there's no point in bridging too thin/short regions //FIXME Vojtech: The offset2 function is not a geometric offset, // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. + { + to_bridge.clear(); + //choose betweent two offset the one that split the less the surface. float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width); + for (Polygon& poly_to_check_for_thin : to_bridge_pp) { + ExPolygons collapsed = offset2_ex({ poly_to_check_for_thin }, -min_width, +min_width * 1.25f); + ExPolygons bridge = intersection_ex(collapsed, { ExPolygon{ poly_to_check_for_thin } }); + ExPolygons not_bridge = diff_ex({ ExPolygon{ poly_to_check_for_thin } }, collapsed); + int try1_count = bridge.size() + not_bridge.size(); + if (try1_count > 1) { + if (layer->id() == 15) + std::cout << "lol\n"; + min_width = float(bridge_flow.scaled_width()) * 1.5f; + collapsed = offset2_ex({ poly_to_check_for_thin }, -min_width, +min_width * 1.5f); + ExPolygons bridge2 = intersection_ex(collapsed, { ExPolygon{ poly_to_check_for_thin } }); + not_bridge = diff_ex({ ExPolygon{ poly_to_check_for_thin } }, collapsed); + int try2_count = bridge2.size() + not_bridge.size(); + if(try2_count < try1_count) + to_bridge.insert(to_bridge.begin(), bridge2.begin(), bridge2.end()); + else + to_bridge.insert(to_bridge.begin(), bridge.begin(), bridge.end()); + } else if (!bridge.empty()) + to_bridge.push_back(bridge.front()); + } } + if (to_bridge.empty()) continue; - if (to_bridge_pp.empty()) continue; - - // convert into ExPolygons - to_bridge = union_ex(to_bridge_pp); + // union + to_bridge = union_ex(to_bridge); } - #ifdef SLIC3R_DEBUG printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); #endif @@ -3424,7 +3471,7 @@ static void fix_mesh_connectivity(TriangleMesh &mesh) upper_internal = intersection(overhangs, lower_layer_internal_surfaces); // Apply new internal infill to regions. for (LayerRegion* layerm : lower_layer->m_regions) { - if (layerm->region()->config().fill_density.value == 0) + if (layerm->region()->config().fill_density.value == 0 || layerm->region()->config().infill_dense.value) continue; SurfaceType internal_surface_types[] = { stPosInternal | stDensSparse, stPosInternal | stDensVoid }; Polygons internal; @@ -3507,11 +3554,13 @@ static void fix_mesh_connectivity(TriangleMesh &mesh) // Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking. for (int n = ((type & stPosTop) == stPosTop) ? int(i) - 1 : int(i) + 1; + ((type & stPosTop) == stPosTop) ? (n >= 0 && (int(i) - n < num_solid_layers || print_z - m_layers[n]->print_z < region_config.top_solid_min_thickness.value - EPSILON)) : (n < int(m_layers.size()) && (n - int(i) < num_solid_layers || m_layers[n]->bottom_z() - bottom_z < region_config.bottom_solid_min_thickness.value - EPSILON)); + ((type & stPosTop) == stPosTop) ? --n : ++n) { // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; @@ -3565,7 +3614,6 @@ static void fix_mesh_connectivity(TriangleMesh &mesh) offset2_ex(new_internal_solid, -margin, +margin, jtMiter, 5), true); // Trim the regularized region by the original region. - if (!too_narrow.empty()) if (!too_narrow.empty()) { solid = new_internal_solid = diff_ex(new_internal_solid, too_narrow); } @@ -3665,7 +3713,8 @@ static void fix_mesh_connectivity(TriangleMesh &mesh) // Work on each region separately. for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) { const PrintRegion* region = this->print()->regions()[region_id]; - const size_t every = region->config().infill_every_layers.value; + // can't have void if using infill_dense + const size_t every = region->config().infill_dense.value ? 1 : region->config().infill_every_layers.value; if (every < 2 || region->config().fill_density == 0.) continue; // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 66c7b2e7f..a24bdf8aa 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -856,6 +856,7 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt "thumbnails", "thumbnails_color", "thumbnails_custom_color", + "thumbnails_end_file", "thumbnails_with_bed", "thumbnails_with_support" }; diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 6423f750a..b11a68b86 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -39,36 +39,40 @@ inline bool test_z_step(const coordf_t val, const coordf_t z_step) { } // Minimum layer height for the variable layer height algorithm. -inline coordf_t min_layer_height_from_nozzle(const PrintConfig &print_config, int idx_nozzle) +// idx_nozzle began at 0 +inline coordf_t min_layer_height_from_nozzle(const PrintConfig &print_config, uint16_t idx_nozzle) { - coordf_t min_layer_height = print_config.min_layer_height.get_at(idx_nozzle - 1); + coordf_t min_layer_height = print_config.min_layer_height.get_abs_value(idx_nozzle, print_config.nozzle_diameter.get_at(idx_nozzle)); return check_z_step( (min_layer_height == 0.) ? (MIN_LAYER_HEIGHT_DEFAULT) : std::max(MIN_LAYER_HEIGHT, min_layer_height), print_config.z_step); } // Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, // it should not be smaller than the minimum layer height. -inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, int idx_nozzle) +// idx_nozzle began at 0 +inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, uint16_t idx_nozzle) { coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle); - coordf_t max_layer_height = print_config.max_layer_height.get_at(idx_nozzle - 1); - coordf_t nozzle_dmr = print_config.nozzle_diameter.get_at(idx_nozzle - 1); + coordf_t nozzle_dmr = print_config.nozzle_diameter.get_at(idx_nozzle); + coordf_t max_layer_height = print_config.max_layer_height.get_abs_value(idx_nozzle, nozzle_dmr); return check_z_step(std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height), print_config.z_step); } // Minimum layer height for the variable layer height algorithm. -coordf_t Slicing::min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) +// idx_nozzle began at 0 +coordf_t Slicing::min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, uint16_t idx_nozzle) { - coordf_t min_layer_height = print_config.opt_float("min_layer_height", idx_nozzle - 1); + coordf_t min_layer_height = print_config.get_computed_value("min_layer_height", idx_nozzle); return check_z_step((min_layer_height == 0.) ? (MIN_LAYER_HEIGHT_DEFAULT) : std::max(MIN_LAYER_HEIGHT, min_layer_height), print_config.opt_float("z_step")); } // Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, // it should not be smaller than the minimum layer height. -coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) +// idx_nozzle began at 0 +coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, uint16_t idx_nozzle) { coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle); - coordf_t max_layer_height = print_config.opt_float("max_layer_height", idx_nozzle - 1); - coordf_t nozzle_dmr = print_config.opt_float("nozzle_diameter", idx_nozzle - 1); + coordf_t max_layer_height = print_config.get_computed_value("max_layer_height", idx_nozzle); + coordf_t nozzle_dmr = print_config.opt_float("nozzle_diameter", idx_nozzle); return check_z_step(std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height), print_config.opt_float("z_step")); } @@ -124,18 +128,26 @@ SlicingParameters SlicingParameters::create_from_config( if (params.object_print_z_max < object_height) params.object_print_z_max += params.z_step; // Miniumum/maximum of the minimum layer height over all extruders. - params.min_layer_height = MIN_LAYER_HEIGHT; + params.min_layer_height = 0; params.max_layer_height = std::numeric_limits<double>::max(); + params.max_suport_layer_height = 0; params.exact_last_layer_height = object_config.exact_last_layer_height.value; if (object_config.support_material.value || params.base_raft_layers > 0) { // Has some form of support. Add the support layers to the minimum / maximum layer height limits. - params.min_layer_height = std::max( - min_layer_height_from_nozzle(print_config, object_config.support_material_extruder), - min_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder)); - params.max_layer_height = std::min( - max_layer_height_from_nozzle(print_config, object_config.support_material_extruder), - max_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder)); - params.max_suport_layer_height = params.max_layer_height; + if (object_config.support_material_extruder > 0) + params.min_layer_height = std::max(params.min_layer_height, + min_layer_height_from_nozzle(print_config, object_config.support_material_extruder - 1)); + if (object_config.support_material_interface_extruder > 0) + params.min_layer_height = std::max(params.min_layer_height, + min_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder - 1)); + if (object_config.support_material_extruder > 0) + params.max_layer_height = std::min(params.max_layer_height, + max_layer_height_from_nozzle(print_config, object_config.support_material_extruder - 1)); + if (object_config.support_material_interface_extruder > 0) + params.max_layer_height = std::min(params.max_layer_height, + max_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder - 1)); + if (params.max_layer_height < std::numeric_limits<double>::max()) + params.max_suport_layer_height = params.max_layer_height; } if (object_extruders.empty()) { params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, 0)); @@ -151,6 +163,7 @@ SlicingParameters SlicingParameters::create_from_config( //apply z_step to min/max params.min_layer_height = check_z_step(params.min_layer_height, params.z_step); params.max_layer_height = check_z_step(params.max_layer_height, params.z_step); + if (params.max_suport_layer_height == 0) params.max_suport_layer_height = params.max_layer_height; params.max_suport_layer_height = check_z_step(params.max_suport_layer_height, params.z_step); if (! soluble_interface) { diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 4d18e342f..13a1145f0 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -188,13 +188,13 @@ extern int generate_layer_height_texture( void *data, int rows, int cols, bool level_of_detail_2nd_level); namespace Slicing { - // Minimum layer height for the variable layer height algorithm. Nozzle index is 1 based. - coordf_t min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle); + // Minimum layer height for the variable layer height algorithm. Nozzle index is 0 based. + coordf_t min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, uint16_t idx_nozzle); // Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, // it should not be smaller than the minimum layer height. - // Nozzle index is 1 based. - coordf_t max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle); + // Nozzle index is 0 based. + coordf_t max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, uint16_t idx_nozzle); } // namespace Slicing } // namespace Slic3r diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index c8e8d2f2a..71f734cd3 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -152,8 +152,10 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object { // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. m_support_layer_height_min = 1000000.; - for (auto lh : m_print_config->min_layer_height.values) - m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, lh)); + const ConfigOptionFloatsOrPercents& min_layer_height = m_print_config->min_layer_height; + const ConfigOptionFloats& nozzle_diameter = m_print_config->nozzle_diameter; + for (int extr_id = 0; extr_id < min_layer_height.values.size(); ++extr_id) + m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, min_layer_height.get_abs_value(extr_id, nozzle_diameter.get_at(extr_id)))); if (m_object_config->support_material_interface_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. @@ -3228,6 +3230,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( m_support_material_interface_flow.spacing_ratio, layer_ex.layer->bridging); Fill *filler = i == 2 ? filler_intermediate_interface.get() : filler_interface.get(); + filler->layer_id = support_layer_id; + filler->z = support_layer.print_z; float density = interface_density; coordf_t spacing; //if first layer and solid first layer : draw concentric with 100% density diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index cd775764b..12677be72 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -19,14 +19,6 @@ public: operator ExPolygons() const; void simplify(double tolerance); void group(std::vector<SurfacesPtr> *retval); - template <class T> bool any_internal_contains(const T &item) const { - for (const Surface &surface : this->surfaces) if (surface.has_pos_internal() && surface.expolygon.contains(item)) return true; - return false; - } - template <class T> bool any_bottom_contains(const T &item) const { - for (const Surface &surface : this->surfaces) if (surface.has_pos_bottom() && surface.expolygon.contains(item)) return true; - return false; - } SurfacesConstPtr filter_by_type(const SurfaceType type) const; SurfacesConstPtr filter_by_type_flag(const SurfaceType allowed, const SurfaceType not_allowed = stNone) const; SurfacesConstPtr filter_by_types(const SurfaceType *types, int ntypes) const; diff --git a/src/slic3r/GUI/CalibrationBedDialog.cpp b/src/slic3r/GUI/CalibrationBedDialog.cpp index 9d3a2efe8..cd82d354c 100644 --- a/src/slic3r/GUI/CalibrationBedDialog.cpp +++ b/src/slic3r/GUI/CalibrationBedDialog.cpp @@ -121,6 +121,8 @@ void CalibrationBedDialog::create_geometry(wxCommandEvent& event_args) { model.objects[objs_idx[i]]->config.set_key_value("gap_fill", new ConfigOptionBool(false)); model.objects[objs_idx[i]]->config.set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent(140, true)); model.objects[objs_idx[i]]->config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinearWGapFill)); + //disable ironing post-process + model.objects[objs_idx[i]]->config.set_key_value("ironing", new ConfigOptionBool(false)); } if (bed_shape->values.size() == 4) { model.objects[objs_idx[0]]->config.set_key_value("fill_angle", new ConfigOptionFloat(90)); diff --git a/src/slic3r/GUI/CalibrationBridgeDialog.cpp b/src/slic3r/GUI/CalibrationBridgeDialog.cpp index 9f98f855a..5ea622180 100644 --- a/src/slic3r/GUI/CalibrationBridgeDialog.cpp +++ b/src/slic3r/GUI/CalibrationBridgeDialog.cpp @@ -141,7 +141,8 @@ void CalibrationBridgeDialog::create_geometry(std::string setting_to_test, bool model.objects[objs_idx[i]]->config.set_key_value(setting_to_test, new ConfigOptionPercent(start + (add ? 1 : -1) * i * step)); model.objects[objs_idx[i]]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter / 2)); model.objects[objs_idx[i]]->config.set_key_value("no_perimeter_unsupported_algo", new ConfigOptionEnum<NoPerimeterUnsupportedAlgo>(npuaBridges)); - model.objects[objs_idx[i]]->config.set_key_value("top_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipSmooth)); + //model.objects[objs_idx[i]]->config.set_key_value("top_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipSmooth)); /not needed + model.objects[objs_idx[i]]->config.set_key_value("ironing", new ConfigOptionBool(false)); // not needed, and it slow down things. } /// if first ayer height is excatly at the wrong value, the text isn't drawed. Fix that by switching the first layer height just a little bit. double first_layer_height = print_config->get_computed_value("first_layer_height", 0); @@ -150,7 +151,7 @@ void CalibrationBridgeDialog::create_geometry(std::string setting_to_test, bool double z_step = printer_config->option<ConfigOptionFloat>("z_step")->value; if (z_step == 0) z_step = 0.1; - double max_height = printer_config->option<ConfigOptionFloats>("max_layer_height")->values[0]; + double max_height = printer_config->get_computed_value("max_layer_height",0); if (max_height > first_layer_height + z_step) for (size_t i = 0; i < nb_items; i++) model.objects[objs_idx[i]]->config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(first_layer_height + z_step, false)); diff --git a/src/slic3r/GUI/CalibrationFlowDialog.cpp b/src/slic3r/GUI/CalibrationFlowDialog.cpp index 74858ac0d..3999457f9 100644 --- a/src/slic3r/GUI/CalibrationFlowDialog.cpp +++ b/src/slic3r/GUI/CalibrationFlowDialog.cpp @@ -156,6 +156,8 @@ void CalibrationFlowDialog::create_geometry(float start, float delta) { model.objects[objs_idx[i]]->config.set_key_value("external_infill_margin", new ConfigOptionFloatOrPercent(100, true)); model.objects[objs_idx[i]]->config.set_key_value("solid_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinearWGapFill)); model.objects[objs_idx[i]]->config.set_key_value("top_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipSmooth)); + //disable ironing post-process + model.objects[objs_idx[i]]->config.set_key_value("ironing", new ConfigOptionBool(false)); //set extrusion mult: 80 90 100 110 120 model.objects[objs_idx[i]]->config.set_key_value("print_extrusion_multiplier", new ConfigOptionPercent(start + (float)i * delta)); } diff --git a/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp b/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp index e0a12e612..68ece7d45 100644 --- a/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp +++ b/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp @@ -125,6 +125,7 @@ void CalibrationOverBridgeDialog::create_geometry(bool over_bridge) { model.objects[objs_idx[i]]->config.set_key_value("fill_density", new ConfigOptionPercent(5.5)); model.objects[objs_idx[i]]->config.set_key_value("fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear)); model.objects[objs_idx[i]]->config.set_key_value("infill_dense", new ConfigOptionBool(false)); + model.objects[objs_idx[i]]->config.set_key_value("ironing", new ConfigOptionBool(false)); //calibration setting. Use 100 & 5 step as it's the numbers printed on the samples if(over_bridge) model.objects[objs_idx[i]]->config.set_key_value("over_bridge_flow_ratio", new ConfigOptionPercent(/*print_config->option<ConfigOptionPercent>("over_bridge_flow_ratio")->get_abs_value(100)*/100 + i * 5)); diff --git a/src/slic3r/GUI/CalibrationTempDialog.cpp b/src/slic3r/GUI/CalibrationTempDialog.cpp index 42b38f02e..3789fb104 100644 --- a/src/slic3r/GUI/CalibrationTempDialog.cpp +++ b/src/slic3r/GUI/CalibrationTempDialog.cpp @@ -165,6 +165,8 @@ void CalibrationTempDialog::create_geometry(wxCommandEvent& event_args) { model.objects[objs_idx[0]]->config.set_key_value("fill_density", new ConfigOptionPercent(7)); model.objects[objs_idx[0]]->config.set_key_value("solid_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinearWGapFill)); model.objects[objs_idx[0]]->config.set_key_value("top_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinearWGapFill)); + //disable ironing post-process, it only slow down things + model.objects[objs_idx[0]]->config.set_key_value("ironing", new ConfigOptionBool(false)); //update plater GLCanvas3D::set_warning_freeze(false); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index e862a385d..3a75e57ae 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -210,42 +210,6 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con apply(config, &new_conf); } - // check forgotten '%' - { - struct optDescr { - ConfigOptionFloatOrPercent* opt; - std::string name; - float min; - float max; - }; - float diameter = 0.4f; - if (config->option<ConfigOptionFloatOrPercent>("extrusion_width")->percent) { - //has to be percent - diameter = 0; - } else { - diameter = config->option<ConfigOptionFloatOrPercent>("extrusion_width")->value; - } - std::vector<optDescr> opts; - opts.push_back({ config->option<ConfigOptionFloatOrPercent>("infill_overlap"), "infill_overlap", 0, diameter * 10 }); - for (int i = 0; i < opts.size(); i++) { - if ((!opts[i].opt->percent) && (opts[i].opt->get_abs_value(diameter) < opts[i].min || opts[i].opt->get_abs_value(diameter) > opts[i].max)) { - wxString msg_text = _(L("Did you forgot to put a '%' in the " + opts[i].name + " field? " - "it's currently set to " + std::to_string(opts[i].opt->get_abs_value(diameter)) + " mm.")); - if (is_global_config) { - msg_text += "\n\n" + _(L("Shall I add the '%'?")); - wxMessageDialog dialog(nullptr, msg_text, _(L("Wipe Tower")), - wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK)); - DynamicPrintConfig new_conf = *config; - auto answer = dialog.ShowModal(); - if (answer == wxID_YES) { - new_conf.set_key_value(opts[i].name, new ConfigOptionFloatOrPercent(opts[i].opt->value * 100, true)); - apply(config, &new_conf); - } - } - } - } - } - // check changes from FloatOrPercent to percent (useful to migrate config from prusa to Slic3r) { std::vector<std::string> names; @@ -400,6 +364,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_infill_dense = config->opt_bool("infill_dense") && can_have_infill_dense; for (auto el : { "infill_dense_algo" }) toggle_field(el, have_infill_dense); + if(have_infill) + for (auto el : { "infill_every_layers", "infill_only_where_needed" }) + toggle_field(el, !have_infill_dense); bool has_spiral_vase = have_perimeters && config->opt_bool("spiral_vase"); bool has_top_solid_infill = config->opt_int("top_solid_layers") > 0 || has_spiral_vase; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 42f24a601..175757b1a 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -986,71 +986,70 @@ void Control::draw_ruler(wxDC& dc) wxCoord pos = get_position_from_value(tick); draw_ticks_pair(dc, pos, mid, 5); draw_tick_text(dc, wxPoint(mid, pos), tick); - } - else { - auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) { - while (current_tick < max_tick) { - wxCoord pos = get_position_from_value(lround(current_tick)); - draw_ticks_pair(dc, pos, mid, 2); - current_tick += m_ruler.short_step; - if (current_tick > m_max_value) - break; - } - }; - - double short_tick = std::nan(""); - int tick = 0; - double value = 0.0; - int sequence = 0; - - int prev_y_pos = -1; - wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; - int values_size = (int)m_values.size(); - - while (tick <= m_max_value) { - value += m_ruler.long_step; - if (value > m_values.back() && sequence < m_ruler.count) { - value = m_ruler.long_step; - for (; tick < values_size; tick++) - if (m_values[tick] < value) + } else { + auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) { + while (current_tick < max_tick) { + wxCoord pos = get_position_from_value(lround(current_tick)); + draw_ticks_pair(dc, pos, mid, 2); + current_tick += m_ruler.short_step; + if (current_tick > m_max_value) break; - // short ticks from the last tick to the end of current sequence - assert(! std::isnan(short_tick)); - draw_short_ticks(dc, short_tick, tick); - sequence++; - } - short_tick = tick; + } + }; + + double short_tick = std::nan(""); + int tick = 0; + double value = 0.0; + int sequence = 0; + + int prev_y_pos = -1; + wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; + int values_size = (int)m_values.size(); + + while (tick <= m_max_value) { + value += m_ruler.long_step; + if (value > m_values.back() && sequence < m_ruler.count) { + value = m_ruler.long_step; + for (; tick < values_size; tick++) + if (m_values[tick] < value) + break; + // short ticks from the last tick to the end of current sequence + //assert(!std::isnan(short_tick)); + draw_short_ticks(dc, short_tick, tick); + sequence++; + } + short_tick = tick; - for (; tick < values_size; tick++) { - if (m_values[tick] == value) - break; - if (m_values[tick] > value) { - if (tick > 0) - tick--; - break; - } - } - if (tick > m_max_value) - break; + for (; tick < values_size; tick++) { + if (m_values[tick] == value) + break; + if (m_values[tick] > value) { + if (tick > 0) + tick--; + break; + } + } + if (tick > m_max_value) + break; - wxCoord pos = get_position_from_value(tick); - draw_ticks_pair(dc, pos, mid, 5); - if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) { - draw_tick_text(dc, wxPoint(mid, pos), tick); - prev_y_pos = pos; - } + wxCoord pos = get_position_from_value(tick); + draw_ticks_pair(dc, pos, mid, 5); + if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) { + draw_tick_text(dc, wxPoint(mid, pos), tick); + prev_y_pos = pos; + } - draw_short_ticks(dc, short_tick, tick); + draw_short_ticks(dc, short_tick, tick); - if (value == m_values.back() && sequence < m_ruler.count) { - value = 0.0; - sequence++; - tick++; + if (value == m_values.back() && sequence < m_ruler.count) { + value = 0.0; + sequence++; + tick++; + } + } + // short ticks from the last tick to the end + draw_short_ticks(dc, short_tick, m_max_value); } - } - // short ticks from the last tick to the end - draw_short_ticks(dc, short_tick, m_max_value); - } dc.SetTextForeground(old_clr); } diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index b90a89e73..793652f26 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -206,8 +206,7 @@ wxString Field::get_tooltip_text(const wxString& default_string) if (tooltip.length() > 0) tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + - (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string + - (boost::iends_with(opt_id, "_gcode") ? "" : "\n") + + (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string + "\n" + _(L("parameter name")) + "\t: " + opt_id; return tooltip_text; @@ -218,6 +217,8 @@ wxString Field::get_rich_tooltip_text(const wxString& default_string) wxString tooltip_text(""); wxString tooltip = _(m_opt.tooltip); update_Slic3r_string(tooltip); + std::wstring wtooltip = tooltip.ToStdWstring(); + std::wstring wtooltip_text; std::string opt_id = m_opt_id; auto hash_pos = opt_id.find("#"); @@ -226,8 +227,20 @@ wxString Field::get_rich_tooltip_text(const wxString& default_string) opt_id += "]"; } + //add "\n" to long tooltip lines + int length = 0; + for (int i = 0; i < wtooltip.size(); i++) { + if (length >= 80 && wtooltip[i] == u' ') + wtooltip_text.push_back(u'\n'); + else + wtooltip_text.push_back(wtooltip[i]); + length++; + if (wtooltip_text.back() == u'\n') + length = 0; + } + if (tooltip.length() > 0) - tooltip_text = tooltip + "\n" + _(L("default value")) + ": " + + tooltip_text = wtooltip_text + "\n" + _(L("default value")) + ": " + (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string; return tooltip_text; @@ -252,7 +265,11 @@ void Field::set_tooltip(const wxString& default_string, wxWindow* window) { if (get_app_config()->get("use_rich_tooltip") == "1") { this->m_rich_tooltip_timer.m_value = default_string; window->Bind(wxEVT_ENTER_WINDOW, [this, window](wxMouseEvent& event) { - if (wxGetActiveWindow() && !this->m_rich_tooltip_timer.IsRunning()) { + if (!this->m_rich_tooltip_timer.IsRunning() +#ifdef __WXMSW__ + && wxGetActiveWindow() //don't activate if the currrent app is not the focus. (deactivated for linux as it check the field instead) +#endif /* __WXMSW__ */ + ) { this->m_rich_tooltip_timer.m_current_window = window; this->m_rich_tooltip_timer.m_is_rich_tooltip_ready = true; this->m_rich_tooltip_timer.StartOnce(500); @@ -263,16 +280,22 @@ void Field::set_tooltip(const wxString& default_string, wxWindow* window) { wxWindowList tipWindow = this->getWindow()->GetChildren(); if (tipWindow.size() > 0) { wxWindow* tooltipWindow = tipWindow.GetLast()->GetData(); - if (tooltipWindow && tooltipWindow == this->m_rich_tooltip_timer.m_current_rich_tooltip) + if (tooltipWindow && tooltipWindow == this->m_rich_tooltip_timer.m_current_rich_tooltip) { tooltipWindow->Hide();// DismissAndNotify(); + } } }); - }else + } else window->SetToolTip(get_tooltip_text(default_string)); } void RichTooltipTimer::Notify() { - if (wxGetActiveWindow() && this->m_is_rich_tooltip_ready && m_current_window) { + if (this->m_is_rich_tooltip_ready && m_current_window && !m_current_window->HasFocus() +#ifdef __WXMSW__ + && wxGetActiveWindow() //don't activate if the currrent app is not the focus. (deactivated for linux as it check the field instead) +#endif /* __WXMSW__ */ + ) { + this->m_previous_focus = wxGetActiveWindow()->FindFocus(); this->m_current_rich_tooltip = nullptr; wxRichToolTip richTooltip( m_field->get_rich_tooltip_title(this->m_value), @@ -281,6 +304,23 @@ void RichTooltipTimer::Notify() { richTooltip.ShowFor(m_current_window); wxWindowList tipWindow = m_current_window->GetChildren(); this->m_current_rich_tooltip = tipWindow.GetLast()->GetData(); + this->m_current_rich_tooltip->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BACKGROUND)); + this->m_current_rich_tooltip->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + this->m_current_rich_tooltip->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { + this->m_is_rich_tooltip_ready = false; + wxWindowList tipWindow = m_current_window->GetChildren(); + if (tipWindow.size() > 0) { + wxWindow* tooltipWindow = tipWindow.GetLast()->GetData(); + if (tooltipWindow && tooltipWindow == this->m_current_rich_tooltip) { + tooltipWindow->Hide();// DismissAndNotify(); + } + } + }); + this->m_current_rich_tooltip->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& event) { + CallAfter([this]() { + if (this->m_previous_focus) this->m_previous_focus->SetFocus(); + }); + }); } } @@ -405,45 +445,68 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true show_error(m_parent, _(L("Input value is out of range"))); if (m_opt.min > val) val = m_opt.min; set_value(double_to_string(val, m_opt.precision), true); - } else if (((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) || - (m_opt.sidetext.rfind("mm ") != std::string::npos && val > 1)) && - (m_value.empty() || std::string(str.ToUTF8().data()) != boost::any_cast<std::string>(m_value))) - { - // exceptions - if (std::set<t_config_option_key>{"infill_anchor", "infill_anchor_max", "avoid_crossing_perimeters_max_detour"}.count(m_opt.opt_key) > 0) { - m_value = std::string(str.ToUTF8().data()); - break; - } - if (m_opt.opt_key.find("extrusion_width") != std::string::npos || m_opt.opt_key.find("extrusion_spacing") != std::string::npos) { - const DynamicPrintConfig& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; - const std::vector<double>& nozzle_diameters = printer_config.option<ConfigOptionFloats>("nozzle_diameter")->values; - double nozzle_diameter = 0; - for (double diameter : nozzle_diameters) - nozzle_diameter = std::max(nozzle_diameter, diameter); - if (val < nozzle_diameter * 10) { - m_value = std::string(str.ToUTF8().data()); + } else if (m_value.empty() || std::string(str.ToUTF8().data()) != boost::any_cast<std::string>(m_value)) { + bool not_ok = (m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max); + if( !not_ok && m_opt.max_literal.value != 0 ) + if (m_opt.max_literal.percent) { + const DynamicPrintConfig& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + const std::vector<double>& nozzle_diameters = printer_config.option<ConfigOptionFloats>("nozzle_diameter")->values; + double nozzle_diameter = 0; + for (double diameter : nozzle_diameters) + nozzle_diameter = std::max(nozzle_diameter, diameter); + if (m_opt.max_literal.value > 0) + not_ok = val > nozzle_diameter * m_opt.max_literal.value; + else + not_ok = val < nozzle_diameter * (-m_opt.max_literal.value); + }else{ + if(m_opt.max_literal.value > 0) + not_ok = val > m_opt.max_literal.value; + else + not_ok = val < -m_opt.max_literal.value; + } + if (not_ok) { + + // if ( + // ( + // (m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) + // || + // (m_opt.sidetext.rfind("mm ") != std::string::npos && val > m_opt.max_literal) + // ) + // && + // (m_value.empty() || std::string(str.ToUTF8().data()) != boost::any_cast<std::string>(m_value))) + //{ + // if (m_opt.opt_key.find("extrusion_width") != std::string::npos || m_opt.opt_key.find("extrusion_spacing") != std::string::npos) { + // const DynamicPrintConfig& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + // const std::vector<double>& nozzle_diameters = printer_config.option<ConfigOptionFloats>("nozzle_diameter")->values; + // double nozzle_diameter = 0; + // for (double diameter : nozzle_diameters) + // nozzle_diameter = std::max(nozzle_diameter, diameter); + // if (val < nozzle_diameter * 10) { + // m_value = std::string(str.ToUTF8().data()); + // break; + // } + // } + //TODO: chack for infill_overlap from diameter% => allow max_literal to be a % + + if (!check_value) { + m_value.clear(); break; } - } - if (!check_value) { - m_value.clear(); - break; + bool infill_anchors = m_opt.opt_key == "infill_anchor" || m_opt.opt_key == "infill_anchor_max"; + + const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm"; + const wxString stVal = double_to_string(val, m_opt.precision); + const wxString msg_text = from_u8((boost::format(_utf8(L("Do you mean %s%% instead of %s %s?\n" + "Select YES if you want to change this value to %s%%, \n" + "or NO if you are sure that %s %s is a correct value."))) % stVal % stVal % sidetext % stVal % stVal % sidetext).str()); + wxMessageDialog dialog(m_parent, msg_text, _(L("Parameter validation")) + ": " + m_opt_id, wxICON_WARNING | wxYES | wxNO); + if ((!infill_anchors || val > 100) && dialog.ShowModal() == wxID_YES) { + set_value(from_u8((boost::format("%s%%") % stVal).str()), false/*true*/); + str += "%%"; + } else + set_value(stVal, false); // it's no needed but can be helpful, when inputted value contained "," instead of "." } - - bool infill_anchors = m_opt.opt_key == "infill_anchor" || m_opt.opt_key == "infill_anchor_max"; - - const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm"; - const wxString stVal = double_to_string(val, m_opt.precision); - const wxString msg_text = from_u8((boost::format(_utf8(L("Do you mean %s%% instead of %s %s?\n" - "Select YES if you want to change this value to %s%%, \n" - "or NO if you are sure that %s %s is a correct value."))) % stVal % stVal % sidetext % stVal % stVal % sidetext).str()); - wxMessageDialog dialog(m_parent, msg_text, _(L("Parameter validation")) + ": " + m_opt_id, wxICON_WARNING | wxYES | wxNO); - if ((!infill_anchors || val > 100) && dialog.ShowModal() == wxID_YES) { - set_value(from_u8((boost::format("%s%%") % stVal).str()), false/*true*/); - str += "%%"; - } else - set_value(stVal, false); // it's no needed but can be helpful, when inputted value contained "," instead of "." } } } diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index b963dd62c..3c361f94b 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -45,10 +45,11 @@ class RichTooltipTimer : public wxTimer Field* m_field; public: bool m_is_rich_tooltip_ready = false; - wxWindow* m_current_rich_tooltip = nullptr; + wxWindow* m_current_rich_tooltip = nullptr; + wxWindow* m_previous_focus = nullptr; wxString m_value; - wxWindow* m_window2 = nullptr; //for point - wxWindow* m_current_window = nullptr; //for point + wxWindow* m_window2 = nullptr; //for point + wxWindow* m_current_window = nullptr; //for point RichTooltipTimer(Field* field) : m_field(field) {}; void Notify() override; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2622af8ed..d28a999ec 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -461,36 +461,36 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G // The layer editing shader was already active. current_shader = nullptr; - const_cast<LayersEditing*>(this)->generate_layer_height_texture(); + const_cast<LayersEditing*>(this)->generate_layer_height_texture(); - // Uniforms were resolved, go ahead using the layer editing shader. + // Uniforms were resolved, go ahead using the layer editing shader. shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * float(m_object_max_z))); shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height)); shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas))); shader->set_uniform("z_cursor_band_width", float(this->band_width)); - // Initialize the layer height texture mapping. - GLsizei w = (GLsizei)m_layers_texture.width; - GLsizei h = (GLsizei)m_layers_texture.height; - GLsizei half_w = w / 2; - GLsizei half_h = h / 2; - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); - glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); - for (const GLVolume* glvolume : volumes.volumes) { - // Render the object using the layer editing shader and texture. - if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) - continue; - - shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); - shader->set_uniform("object_max_z", GLfloat(0)); - glvolume->render(); - } - // Revert back to the previous shader. - glBindTexture(GL_TEXTURE_2D, 0); + // Initialize the layer height texture mapping. + GLsizei w = (GLsizei)m_layers_texture.width; + GLsizei h = (GLsizei)m_layers_texture.height; + GLsizei half_w = w / 2; + GLsizei half_h = h / 2; + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); + glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); + for (const GLVolume* glvolume : volumes.volumes) { + // Render the object using the layer editing shader and texture. + if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) + continue; + + shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); + shader->set_uniform("object_max_z", GLfloat(0)); + glvolume->render(); + } + // Revert back to the previous shader. + glBindTexture(GL_TEXTURE_2D, 0); if (current_shader != nullptr) current_shader->start_using(); } @@ -638,12 +638,12 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool std::string text; bool error = false; switch (warning) { - case ObjectOutside: text = L("An object outside the print area was detected."); break; - case ToolpathOutside: text = L("A toolpath outside the print area was detected."); error = true; break; - case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); error = true; break; - case SomethingNotShown: text = L("Some objects are not visible."); break; + case ObjectOutside: text = _u8L("An object outside the print area was detected."); break; + case ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = true; break; + case SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = true; break; + case SomethingNotShown: text = _u8L("Some objects are not visible."); break; case ObjectClashed: - text = L( "An object outside the print area was detected.\n" + text = _u8L( "An object outside the print area was detected.\n" "Resolve the current problem to continue slicing."); error = true; break; @@ -1527,10 +1527,12 @@ bool GLCanvas3D::is_layers_editing_allowed() const void GLCanvas3D::reset_layer_height_profile() { - wxGetApp().plater()->take_snapshot(_L("Variable layer height - Reset")); - m_layers_editing.reset_layer_height_profile(*this); - m_layers_editing.state = LayersEditing::Completed; - m_dirty = true; + if (is_layers_editing_enabled()) { + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Reset")); + m_layers_editing.reset_layer_height_profile(*this); + m_layers_editing.state = LayersEditing::Completed; + m_dirty = true; + } } void GLCanvas3D::adaptive_layer_height_profile(float quality_factor) @@ -4864,7 +4866,7 @@ bool GLCanvas3D::_init_collapse_toolbar() bool GLCanvas3D::_set_current() { - return m_context != nullptr && m_canvas->SetCurrent(*m_context); + return m_context != nullptr && m_canvas->IsShownOnScreen() && m_canvas->SetCurrent(*m_context); } void GLCanvas3D::_resize(unsigned int w, unsigned int h) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a06231ef5..bcc1eef24 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3401,14 +3401,14 @@ void ObjectList::del_layer_range(const t_layer_height_range& range) static double get_min_layer_height(const int extruder_idx) { const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; - return config.opt_float("min_layer_height", std::max(0, extruder_idx - 1)); + return config.get_computed_value("min_layer_height", std::max(0, extruder_idx - 1)); } static double get_max_layer_height(const int extruder_idx) { const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; int extruder_idx_zero_based = std::max(0, extruder_idx - 1); - double max_layer_height = config.opt_float("max_layer_height", extruder_idx_zero_based); + double max_layer_height = config.get_computed_value("max_layer_height", extruder_idx_zero_based); // In case max_layer_height is set to zero, it should default to 75 % of nozzle diameter: if (max_layer_height < EPSILON) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 6a711f7f6..65008171f 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -270,6 +270,7 @@ bool Preview::init(wxWindow* parent, Model* model) _L("Internal infill") + "|1|" + _L("Solid infill") + "|1|" + _L("Top solid infill") + "|1|" + + _L("Ironing") + "|1|" + _L("Bridge infill") + "|1|" + _L("Internal bridge infill") + "|1|" + _L("Thin wall") + "|1|" + diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 4562f6b92..648ef1275 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -12,6 +12,7 @@ namespace Slic3r { namespace GUI { + static bool is_point_in_rect(const wxPoint& pt, const wxRect& rect) { return rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() && @@ -38,6 +39,8 @@ static wxString get_url(const wxString& path_end, bool get_default = false) return wxString("https://help.prusa3d.com/") + lang_marker + "/article/" + path_end; } +int OG_CustomCtrl::m_has_icon = (-1); + OG_CustomCtrl::OG_CustomCtrl( wxWindow* parent, OptionsGroup* og, const wxPoint& pos /* = wxDefaultPosition*/, @@ -47,6 +50,8 @@ OG_CustomCtrl::OG_CustomCtrl( wxWindow* parent, wxPanel(parent, wxID_ANY, pos, size, /*wxWANTS_CHARS |*/ wxBORDER_NONE | wxTAB_TRAVERSAL), opt_group(og) { + if (m_has_icon < 0) + m_has_icon = get_app_config()->get("setting_icon") == "1"; if (!wxOSX) SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX @@ -55,8 +60,9 @@ OG_CustomCtrl::OG_CustomCtrl( wxWindow* parent, m_v_gap = lround(1.0 * m_em_unit); m_h_gap = lround(0.2 * m_em_unit); - m_bmp_mode_sz = get_bitmap_size(create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12)); + m_bmp_mode_sz = (!m_has_icon) ? wxSize(0,0) : get_bitmap_size(create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12)); m_bmp_blinking_sz = get_bitmap_size(create_scaled_bitmap("search_blink", this)); + if (!m_has_icon) m_bmp_blinking_sz.x = 0; init_ctrl_lines();// from og.lines() @@ -129,7 +135,8 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) for (CtrlLine& ctrl_line : ctrl_lines) { if (&ctrl_line.og_line == &line) { - h_pos = m_bmp_mode_sz.GetWidth() + m_h_gap; + h_pos = m_bmp_mode_sz.GetWidth(); + if (h_pos) h_pos += m_h_gap; if (line.near_label_widget_win) { wxSize near_label_widget_sz = line.near_label_widget_win->GetSize(); if (field_in) @@ -145,7 +152,8 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) if (opt_group->title_width != 0) h_pos += opt_group->title_width * m_em_unit + m_h_gap; - int blinking_button_width = m_bmp_blinking_sz.GetWidth() + m_h_gap; + int blinking_button_width = m_bmp_blinking_sz.GetWidth(); + if (blinking_button_width) blinking_button_width += m_h_gap; if (line.widget) { h_pos += blinking_button_width; @@ -427,8 +435,9 @@ void OG_CustomCtrl::msw_rescale() m_v_gap = lround(1.0 * m_em_unit); m_h_gap = lround(0.2 * m_em_unit); - m_bmp_mode_sz = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize(); + m_bmp_mode_sz = (!m_has_icon) ? wxSize(0, 0) : create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize(); m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize(); + if (!m_has_icon) m_bmp_blinking_sz.x = 0; wxCoord v_pos = 0; for (CtrlLine& line : ctrl_lines) { @@ -492,7 +501,7 @@ void OG_CustomCtrl::CtrlLine::msw_rescale() { // if we have a single option with no label, no sidetext if (draw_just_act_buttons) - height = get_bitmap_size(create_scaled_bitmap("empty")).GetHeight(); + height = (!m_has_icon) ? 0 : get_bitmap_size(create_scaled_bitmap("empty")).GetHeight(); if (ctrl->opt_group->title_width != 0 && !og_line.label.IsEmpty()) { wxSize label_sz = ctrl->GetTextExtent(og_line.label); @@ -546,7 +555,8 @@ void OG_CustomCtrl::CtrlLine::update_visibility(ConfigOptionMode mode) void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos) { Field* field = ctrl->opt_group->get_field(og_line.get_options().front().opt_id); - int blinking_button_width = ctrl->m_bmp_blinking_sz.GetWidth() + ctrl->m_h_gap; + int blinking_button_width = ctrl->m_bmp_blinking_sz.GetWidth(); + if(blinking_button_width ) blinking_button_width += ctrl->m_h_gap; bool suppress_hyperlinks = get_app_config()->get("suppress_hyperlinks") == "1"; if (draw_just_act_buttons) { @@ -673,6 +683,8 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos) wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos) { + if (!m_has_icon) + return 0; if (!draw_mode_bitmap) return ctrl->m_h_gap; @@ -768,25 +780,26 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBi wxCoord h_pos = pos.x; wxCoord v_pos = pos.y + height / 2 - this->ctrl->m_bmp_blinking_sz.GetHeight() / 2; - dc.DrawBitmap(bmp_undo_to_sys, h_pos, v_pos); + if (m_has_icon) { + dc.DrawBitmap(bmp_undo_to_sys, h_pos, v_pos); - int bmp_dim = get_bitmap_size(bmp_undo_to_sys).GetWidth(); - rects_undo_to_sys_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim); + int bmp_dim = get_bitmap_size(bmp_undo_to_sys).GetWidth(); + rects_undo_to_sys_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim); - h_pos += bmp_dim + ctrl->m_h_gap; - dc.DrawBitmap(bmp_undo, h_pos, v_pos); + h_pos += bmp_dim + ctrl->m_h_gap; + dc.DrawBitmap(bmp_undo, h_pos, v_pos); - bmp_dim = get_bitmap_size(bmp_undo).GetWidth(); - rects_undo_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim); + bmp_dim = get_bitmap_size(bmp_undo).GetWidth(); + rects_undo_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim); - if(is_blinking) - draw_blinking_bmp(dc, wxPoint(h_pos, v_pos), is_blinking); + if(is_blinking) + draw_blinking_bmp(dc, wxPoint(h_pos, v_pos), is_blinking); - h_pos += bmp_dim + ctrl->m_h_gap; + h_pos += bmp_dim + ctrl->m_h_gap; - if (is_blinking) + } else if (is_blinking) { draw_blinking_bmp(dc, wxPoint(h_pos, v_pos), is_blinking); - + } return h_pos; } diff --git a/src/slic3r/GUI/OG_CustomCtrl.hpp b/src/slic3r/GUI/OG_CustomCtrl.hpp index fa3f1523a..c971f770e 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.hpp +++ b/src/slic3r/GUI/OG_CustomCtrl.hpp @@ -25,6 +25,7 @@ namespace Slic3r { namespace GUI { // Static text shown among the options. class OG_CustomCtrl :public wxPanel { + static int m_has_icon; wxFont m_font; int m_v_gap; int m_h_gap; diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 307259f47..54701eedd 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -180,7 +180,14 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_ m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); - + m_printer_name->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& e) { + if (m_printer_name->GetValue() == m_default_name) m_printer_name->SetValue(""); + e.Skip(); + }); + m_printer_name->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { + if (m_printer_name->GetValue().empty()) m_printer_name->SetValue(m_default_name); + e.Skip(); + }); PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); if (!printer) { diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 433486c9a..a48961ee1 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -38,18 +38,43 @@ static std::shared_ptr<ConfigOptionsGroup>create_options_tab(const wxString& tit std::shared_ptr<ConfigOptionsGroup> PreferencesDialog::create_general_options_group(const wxString& title, wxNotebook* tabs) { - std::shared_ptr<ConfigOptionsGroup> optgroup = std::make_shared<ConfigOptionsGroup>((wxPanel*)tabs->GetPage(0), title); - optgroup->title_width = 40; - optgroup->label_width = 40; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset") - m_values[opt_key] = boost::any_cast<bool>(value) ? "none" : "discard"; - else if (std::unordered_set<std::string>{ "splash_screen_editor", "splash_screen_gcodeviewer", "auto_switch_preview" }.count(opt_key) > 0) - m_values[opt_key] = boost::any_cast<std::string>(value); - else - m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0"; - }; - return optgroup; + std::shared_ptr<ConfigOptionsGroup> optgroup = std::make_shared<ConfigOptionsGroup>((wxPanel*)tabs->GetPage(0), title); + optgroup->title_width = 40; + optgroup->label_width = 40; + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset") + m_values[opt_key] = boost::any_cast<bool>(value) ? "none" : "discard"; + else if (std::unordered_set<std::string>{ "splash_screen_editor", "splash_screen_gcodeviewer", "auto_switch_preview" }.count(opt_key) > 0) + m_values[opt_key] = boost::any_cast<std::string>(value); + else + m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0"; + }; + return optgroup; +} +std::shared_ptr<ConfigOptionsGroup> PreferencesDialog::create_gui_options_group(const wxString& title, wxNotebook* tabs) +{ + + std::shared_ptr<ConfigOptionsGroup> optgroup = std::make_shared<ConfigOptionsGroup>((wxPanel*)tabs->GetPage(3), title); + optgroup->title_width = 40; + optgroup->label_width = 40; + optgroup->m_on_change = [this, tabs](t_config_option_key opt_key, boost::any value) { + if (opt_key == "suppress_hyperlinks") + m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : ""; + else if (opt_key.find("color") != std::string::npos) + m_values[opt_key] = boost::any_cast<std::string>(value); + else if (opt_key.find("tab_icon_size") != std::string::npos) + m_values[opt_key] = std::to_string(boost::any_cast<int>(value)); + else + m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0"; + + if (opt_key == "use_custom_toolbar_size") { + m_icon_size_sizer->ShowItems(boost::any_cast<bool>(value)); + m_optgroups_gui.front()->parent()->Layout(); + tabs->Layout(); + this->layout(); + } + }; + return optgroup; } static void activate_options_tab(std::shared_ptr<ConfigOptionsGroup> optgroup, int padding = 20) @@ -264,21 +289,6 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_new_project") == "1" }); option = Option(def, "default_action_on_new_project"); m_optgroups_general.back()->append_single_option_line(option); - - def.label = L("Use custom tooltip"); - def.type = coBool; - def.tooltip = L("On some OS like MacOS or some Linux, tooltips can't stay on for a long time. This setting replaces native tooltips with custom dialogs to improve readability (only for settings)." - "\nNote that for the number controls, you need to hover the arrows to get the custom tooltip."); - def.set_default_value(new ConfigOptionBool{ app_config->has("use_rich_tooltip") ? app_config->get("use_rich_tooltip") == "1": -#if __APPLE__ - true -#else - false -#endif - }); - option = Option(def, "use_rich_tooltip"); - m_optgroups_general.back()->append_single_option_line(option); - m_values_need_restart.push_back("use_rich_tooltip"); } #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 @@ -409,24 +419,12 @@ void PreferencesDialog::build() activate_options_tab(m_optgroup_camera); // Add "GUI" tab - m_optgroup_gui = create_options_tab(_L("GUI"), tabs); - m_optgroup_gui->m_on_change = [this, tabs](t_config_option_key opt_key, boost::any value) { - if (opt_key == "suppress_hyperlinks") - m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : ""; - else if (opt_key.find("color") != std::string::npos) - m_values[opt_key] = boost::any_cast<std::string>(value); - else if (opt_key.find("tab_icon_size") != std::string::npos) - m_values[opt_key] = std::to_string(boost::any_cast<int>(value)); - else - m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0"; + m_optgroups_gui.clear(); + m_optgroups_gui.emplace_back(create_options_tab(_L("GUI"), tabs)); + + //activate_options_tab(m_optgroups_general.back(), 3); + m_optgroups_gui.emplace_back(create_gui_options_group(_L("Controls"), tabs)); - if (opt_key == "use_custom_toolbar_size") { - m_icon_size_sizer->ShowItems(boost::any_cast<bool>(value)); - m_optgroup_gui->parent()->Layout(); - tabs->Layout(); - this->layout(); - } - }; def.label = L("Sequential slider applied only to top layer"); def.type = coBool; @@ -434,7 +432,7 @@ void PreferencesDialog::build() "If disabled, changes made using the sequential slider, in preview, apply to the whole gcode."); def.set_default_value(new ConfigOptionBool{ app_config->get("seq_top_layer_only") == "1" }); option = Option(def, "seq_top_layer_only"); - m_optgroup_gui->append_single_option_line(option); + m_optgroups_gui.back()->append_single_option_line(option); if (is_editor) { def.label = L("Show sidebar collapse/expand button"); @@ -442,7 +440,7 @@ void PreferencesDialog::build() def.tooltip = L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"); def.set_default_value(new ConfigOptionBool{ app_config->get("show_collapse_button") == "1" }); option = Option(def, "show_collapse_button"); - m_optgroup_gui->append_single_option_line(option); + m_optgroups_gui.back()->append_single_option_line(option); def.label = L("Suppress to open hyperlink in browser"); def.type = coBool; @@ -450,26 +448,59 @@ void PreferencesDialog::build() "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."); def.set_default_value(new ConfigOptionBool{ app_config->get("suppress_hyperlinks") == "1" }); option = Option(def, "suppress_hyperlinks"); - m_optgroup_gui->append_single_option_line(option); + m_optgroups_gui.back()->append_single_option_line(option); + + activate_options_tab(m_optgroups_gui.back(), 3); + m_optgroups_gui.emplace_back(create_gui_options_group(_L("Appearance"), tabs)); def.label = L("Use custom size for toolbar icons"); def.type = coBool; def.tooltip = L("If enabled, you can change size of toolbar icons manually."); def.set_default_value(new ConfigOptionBool{ app_config->get("use_custom_toolbar_size") == "1" }); option = Option(def, "use_custom_toolbar_size"); - m_optgroup_gui->append_single_option_line(option); - - def.label = L("Tab icon size"); - def.type = coInt; - def.tooltip = L("Size of the tab icons, in pixels. Set to 0 to remove icons."); - def.set_default_value(new ConfigOptionInt{ atoi(app_config->get("tab_icon_size").c_str()) }); - option = Option(def, "tab_icon_size"); - option.opt.width = 6; - m_optgroup_gui->append_single_option_line(option); + m_optgroups_gui.back()->append_single_option_line(option); + + create_icon_size_slider(m_optgroups_gui.back().get()); + m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1"); + + def.label = L("Tab icon size"); + def.type = coInt; + def.tooltip = L("Size of the tab icons, in pixels. Set to 0 to remove icons."); + def.set_default_value(new ConfigOptionInt{ atoi(app_config->get("tab_icon_size").c_str()) }); + option = Option(def, "tab_icon_size"); + option.opt.width = 6; + m_optgroups_gui.back()->append_single_option_line(option); m_values_need_restart.push_back("tab_icon_size"); + + def.label = L("Display setting icons"); + def.type = coBool; + def.tooltip = L("The settings have a lock and dot to show how they are modified. You can hide them by uncheking this option."); + def.set_default_value(new ConfigOptionBool{ app_config->get("setting_icon") == "1" }); + option = Option(def, "setting_icon"); + option.opt.width = 6; + m_optgroups_gui.back()->append_single_option_line(option); + m_values_need_restart.push_back("setting_icon"); + + def.label = L("Use custom tooltip"); + def.type = coBool; + def.tooltip = L("On some OS like MacOS or some Linux, tooltips can't stay on for a long time. This setting replaces native tooltips with custom dialogs to improve readability (only for settings)." + "\nNote that for the number controls, you need to hover the arrows to get the custom tooltip. Also, it keeps the focus but will give it back when it closes. It won't show up if you are editing the field."); + def.set_default_value(new ConfigOptionBool{ app_config->has("use_rich_tooltip") ? app_config->get("use_rich_tooltip") == "1" : +#if __APPLE__ + true +#else + false +#endif + }); + option = Option(def, "use_rich_tooltip"); + m_optgroups_gui.back()->append_single_option_line(option); + m_values_need_restart.push_back("use_rich_tooltip"); } + + activate_options_tab(m_optgroups_gui.back(), 3); + m_optgroups_gui.emplace_back(create_gui_options_group(_L("Colors"), tabs)); // color prusa -> susie eb7221 //ICON 237, 107, 33 -> ed6b21 ; 2172eb //DARK 237, 107, 33 -> ed6b21 ; 32, 113, 234 2071ea @@ -485,7 +516,7 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionString{ app_config->get("color_very_dark") }); option = Option(def, "color_very_dark"); option.opt.width = 6; - m_optgroup_gui->append_single_option_line(option); + m_optgroups_gui.back()->append_single_option_line(option); m_values_need_restart.push_back("color_very_dark"); def.label = L("Dark gui color"); @@ -496,7 +527,7 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionString{ app_config->get("color_dark") }); option = Option(def, "color_dark"); option.opt.width = 6; - m_optgroup_gui->append_single_option_line(option); + m_optgroups_gui.back()->append_single_option_line(option); m_values_need_restart.push_back("color_dark"); def.label = L("Gui color"); @@ -506,7 +537,7 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionString{ app_config->get("color") }); option = Option(def, "color"); option.opt.width = 6; - m_optgroup_gui->append_single_option_line(option); + m_optgroups_gui.back()->append_single_option_line(option); m_values_need_restart.push_back("color"); def.label = L("Light gui color"); @@ -516,7 +547,7 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionString{ app_config->get("color_light") }); option = Option(def, "color_light"); option.opt.width = 6; - m_optgroup_gui->append_single_option_line(option); + m_optgroups_gui.back()->append_single_option_line(option); m_values_need_restart.push_back("color_light"); def.label = L("Very light gui color"); @@ -527,17 +558,13 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionString{ app_config->get("color_very_light") }); option = Option(def, "color_very_light"); option.opt.width = 6; - m_optgroup_gui->append_single_option_line(option); + m_optgroups_gui.back()->append_single_option_line(option); m_values_need_restart.push_back("color_very_light"); - activate_options_tab(m_optgroup_gui); + activate_options_tab(m_optgroups_gui.back(), 3); - if (is_editor) { - create_icon_size_slider(); - m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1"); - - create_settings_mode_widget(); - } + //create layout options + create_settings_mode_widget(tabs); #if ENABLE_ENVIRONMENT_MAP if (is_editor) { @@ -651,7 +678,14 @@ void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect) } m_optgroup_paths->msw_rescale(); m_optgroup_camera->msw_rescale(); - m_optgroup_gui->msw_rescale(); + for (int i = 0; i < (int)m_optgroups_gui.size(); i++) { + if (m_optgroups_gui[i]) { + m_optgroups_gui[i]->msw_rescale(); + } else { + m_optgroups_gui.erase(m_optgroups_gui.begin() + i); + i--; + } + } msw_buttons_rescale(this, em_unit(), { wxID_OK, wxID_CANCEL }); @@ -668,7 +702,7 @@ void PreferencesDialog::layout() Refresh(); } -void PreferencesDialog::create_icon_size_slider() +void PreferencesDialog::create_icon_size_slider(ConfigOptionsGroup* container) { const auto app_config = get_app_config(); @@ -676,7 +710,7 @@ void PreferencesDialog::create_icon_size_slider() m_icon_size_sizer = new wxBoxSizer(wxHORIZONTAL); - wxWindow* parent = m_optgroup_gui->parent(); + wxWindow* parent = container->parent(); if (isOSX) // For correct rendering of the slider and value label under OSX @@ -724,13 +758,13 @@ void PreferencesDialog::create_icon_size_slider() win->SetBackgroundStyle(wxBG_STYLE_PAINT); } - m_optgroup_gui->sizer->Add(m_icon_size_sizer, 0, wxEXPAND | wxALL, em); + container->parent()->GetSizer()->Add(m_icon_size_sizer, 0, wxEXPAND | wxALL, em); } -void PreferencesDialog::create_settings_mode_widget() +void PreferencesDialog::create_settings_mode_widget(wxNotebook* tabs) { - wxString choices[] = { _L("Regular layout with the tab bar"), - _L("Old PrusaSlicer layout"), + wxString choices[] = { _L("Layout with the tab bar"), + _L("Legacy layout"), _L("Access via settings button in the top menu"), _L("Settings in non-modal window") }; @@ -745,7 +779,7 @@ void PreferencesDialog::create_settings_mode_widget() 0; #endif - wxWindow* parent = m_optgroup_gui->parent(); + wxWindow* parent = m_optgroups_gui.back()->parent(); m_layout_mode_box = new wxRadioBox(parent, wxID_ANY, _L("Layout Options"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, 4, wxRA_SPECIFY_ROWS); @@ -772,7 +806,8 @@ void PreferencesDialog::create_settings_mode_widget() auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_layout_mode_box, 1, wxALIGN_CENTER_VERTICAL); - m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND); + wxBoxSizer* parent_sizer = static_cast<wxBoxSizer*>(static_cast<wxPanel*>(tabs->GetPage(3))->GetSizer()); + parent_sizer->Add(sizer, 0, wxEXPAND); } diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index ab372601c..efb518050 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -22,7 +22,7 @@ class PreferencesDialog : public DPIDialog std::vector<std::shared_ptr<ConfigOptionsGroup>> m_optgroups_general; std::shared_ptr<ConfigOptionsGroup> m_optgroup_paths; std::shared_ptr<ConfigOptionsGroup> m_optgroup_camera; - std::shared_ptr<ConfigOptionsGroup> m_optgroup_gui; + std::vector<std::shared_ptr<ConfigOptionsGroup>> m_optgroups_gui; #if ENABLE_ENVIRONMENT_MAP std::shared_ptr<ConfigOptionsGroup> m_optgroup_render; #endif // ENABLE_ENVIRONMENT_MAP @@ -47,9 +47,10 @@ public: protected: void on_dpi_changed(const wxRect &suggested_rect) override; void layout(); - void create_icon_size_slider(); - void create_settings_mode_widget(); - std::shared_ptr<ConfigOptionsGroup> create_general_options_group(const wxString& title, wxNotebook* tabs); + void create_icon_size_slider(ConfigOptionsGroup* parent); + void create_settings_mode_widget(wxNotebook* tabs); + std::shared_ptr<ConfigOptionsGroup> create_general_options_group(const wxString& title, wxNotebook* tabs); + std::shared_ptr<ConfigOptionsGroup> create_gui_options_group(const wxString& title, wxNotebook* tabs); }; } // GUI diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 8499bdc1e..92538aaaf 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -204,8 +204,8 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle const auto &support_material_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("support_material_extrusion_width"); const auto &top_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("top_infill_extrusion_width"); const auto &first_layer_speed = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_speed"); - const auto& first_layer_infill_speed = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_infill_speed"); - const auto& first_layer_min_speed = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_infill_speed"); + const auto& first_layer_infill_speed = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_infill_speed"); + const auto& first_layer_min_speed = *print_config.option<ConfigOptionFloat>("first_layer_min_speed"); // Index of an extruder assigned to a feature. If set to 0, an active extruder will be used for a multi-material print. // If different from idx_extruder, it will not be taken into account for this hint. @@ -242,7 +242,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle // Apply the first layer limit. if (first_layer_speed.value > 0) speed_normal = std::min(first_layer_speed.get_abs_value(base_speed), speed_normal); - speed_normal = std::max(first_layer_min_speed.get_abs_value(base_speed), speed_normal); + speed_normal = std::max(first_layer_min_speed.value, speed_normal); } return (speed_normal > 0.) ? speed_normal : speed_max; }; @@ -252,7 +252,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle // Apply the first layer limit. if(first_layer_infill_speed.value > 0) speed_normal = std::min(first_layer_infill_speed.get_abs_value(base_speed), speed_normal); - speed_normal = std::max(first_layer_min_speed.get_abs_value(base_speed), speed_normal); + speed_normal = std::max(first_layer_min_speed.value, speed_normal); } return (speed_normal > 0.) ? speed_normal : speed_max; }; @@ -496,7 +496,7 @@ std::string PresetHints::top_bottom_shell_thickness_explanation(const PresetBund double layer_height = print_config.opt_float("layer_height"); bool variable_layer_height = printer_config.opt_bool("variable_layer_height"); //FIXME the following line takes into account the 1st extruder only. - double min_layer_height = variable_layer_height ? Slicing::min_layer_height_from_nozzle(printer_config, 1) : layer_height; + double min_layer_height = variable_layer_height ? Slicing::min_layer_height_from_nozzle(printer_config, 0) : layer_height; if (layer_height <= 0.f) { out += _utf8(L("Top / bottom shell thickness hint: Not available due to invalid layer height.")); diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 48025cf4e..6c75eb153 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -75,7 +75,8 @@ void change_opt_keyFoP(std::string& opt_key, DynamicPrintConfig* config, int& cn void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type type, ConfigOptionMode mode) { - auto emplace = [this, type](const std::string opt_key, const wxString& label) + const ConfigDef* defs = config->def(); + auto emplace = [this, type, defs](const std::string opt_key, const wxString& label, const ConfigOptionDef& opt) { const GroupAndCategory& gc = groups_and_categories[opt_key]; if (gc.group.IsEmpty() || gc.category.IsEmpty()) @@ -93,7 +94,8 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty options.emplace_back(Option{ boost::nowide::widen(opt_key), type, (label + suffix).ToStdWstring(), (_(label) + suffix_local).ToStdWstring(), gc.group.ToStdWstring(), _(gc.group).ToStdWstring(), - gc.category.ToStdWstring(), GUI::Tab::translate_category(gc.category, type).ToStdWstring() }); + gc.category.ToStdWstring(), GUI::Tab::translate_category(gc.category, type).ToStdWstring() , + wxString(opt.tooltip).ToStdWstring(), (_(opt.tooltip)).ToStdWstring() }); }; for (std::string opt_key : config->keys()) @@ -125,11 +127,11 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty } if (cnt == 0) - emplace(opt_key, label); + emplace(opt_key, label, opt); else for (int i = 0; i < cnt; ++i) // ! It's very important to use "#". opt_key#n is a real option key used in GroupAndCategory - emplace(opt_key + "#" + std::to_string(i), label); + emplace(opt_key + "#" + std::to_string(i), label, opt); } } @@ -219,8 +221,9 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) out += marker_by_type(opt.type, printer_technology); const std::wstring* prev = nullptr; for (const std::wstring* const s : { - view_params.category ? &opt.category_local : nullptr, - & opt.group_local, & opt.label_local }) + view_params.category ? &opt.category_local : nullptr, + view_params.category ? &opt.group_local : nullptr, + & opt.label_local }) if (s != nullptr && (prev == nullptr || *prev != *s)) { if (out.size() > 2) out += sep; @@ -237,8 +240,9 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) out += marker_by_type(opt.type, printer_technology); const std::wstring* prev = nullptr; for (const std::wstring* const s : { - view_params.category ? &opt.category : nullptr, - & opt.group, & opt.label }) + view_params.category ? &opt.category : nullptr, + view_params.category ? &opt.group : nullptr, + & opt.label }) if (s != nullptr && (prev == nullptr || *prev != *s)) { if (out.size() > 2) out += sep; @@ -250,9 +254,24 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) auto get_tooltip = [this, &sep](const Option& opt) { + //add "\n" to long tooltip lines + std::wstring tooltip; + int length = 0; + for (int i = 0; i < opt.tooltip_local.size(); i++) { + if (length >= 80 && opt.tooltip_local[i] == u' ') + tooltip.push_back(u'\n'); + else + tooltip.push_back(opt.tooltip_local[i]); + length++; + if (tooltip.back() == u'\n') + length = 0; + } + + return marker_by_type(opt.type, printer_technology) + - opt.category_local + sep + - opt.group_local + sep + opt.label_local; + opt.category_local + sep + + opt.group_local + sep + opt.label_local + + "\n\n" + tooltip; }; std::vector<uint16_t> matches, matches2; @@ -272,24 +291,44 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) int score = std::numeric_limits<int>::min(); int score2; matches.clear(); + + //search for label if(view_params.exact) strong_match(wsearch, label, score, matches); else fuzzy_match(wsearch, label, score, matches); - if ( (view_params.exact ? strong_match(wsearch, opt.opt_key, score2, matches2):fuzzy_match(wsearch, opt.opt_key, score2, matches2)) && (view_params.exact || score2 > score) ) { - for (fts::pos_type &pos : matches2) - pos += label.size() + 1; - label += L"(" + opt.opt_key + L")"; - append(matches, matches2); - score = std::max(score, score2); - } + //search in english label if (view_params.english && (view_params.exact ? strong_match(wsearch, label_english, score2, matches2) : fuzzy_match(wsearch, label_english, score2, matches2)) && score2 > score) { label = std::move(label_english); matches = std::move(matches2); score = score2; } - if (score > 90/*std::numeric_limits<int>::min()*/) { + + //search in opt_key + if ((view_params.exact ? strong_match(wsearch, opt.opt_key, score2, matches2) : fuzzy_match(wsearch, opt.opt_key, score2, matches2)) && (view_params.exact || score2 > score)) { + for (fts::pos_type& pos : matches2) + pos += label.size() + 1; + label += L"(" + opt.opt_key + L")"; + append(matches, matches2); + score = std::max(score, score2); + } + + //search in tooltip + size_t find_in_tooltip = std::wstring::npos; + if (score <= 90) { + //strong_match(wsearch, opt.tooltip_local, score2, matches2); //Too slow + find_in_tooltip = opt.tooltip_local.find(wsearch); + if (find_in_tooltip == std::wstring::npos && view_params.english) { + find_in_tooltip = opt.tooltip.find(wsearch); + } + } + if (score > 90/*std::numeric_limits<int>::min()*/ || find_in_tooltip != std::wstring::npos) { + if (score <= 90) { + score = score > 0 + ? score + int( (90.-score) * std::min(1., find_in_tooltip / 300.)) + : int(90. * std::min(1., find_in_tooltip / 300.)); + } label = mark_string(label, matches, opt.type, printer_technology); label += L" [" + std::to_wstring(score) + L"]";// add score value std::string label_u8 = into_u8(label); diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 926aad9f7..5c6acf3ff 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -37,6 +37,10 @@ struct GroupAndCategory { }; struct Option { + +//private: + //Option() {} +//public: // bool operator<(const Option& other) const { return other.label > this->label; } bool operator<(const Option& other) const { return other.opt_key > this->opt_key; } @@ -50,6 +54,8 @@ struct Option { std::wstring group_local; std::wstring category; std::wstring category_local; + std::wstring tooltip; + std::wstring tooltip_local; }; struct FoundOption { @@ -66,7 +72,7 @@ struct FoundOption { struct OptionViewParameters { - bool category {false}; + bool category {true}; bool english {false}; bool exact {false}; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 02898c981..fc789adbd 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -366,6 +366,33 @@ void Tab::create_preset_tab() if (m_btn_edit_ph_printer) m_btn_edit_ph_printer->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) { + // ask for saving modif before + if (m_presets->current_is_dirty()) { + //ok = may_discard_current_dirty_preset(nullptr, ""); + UnsavedChangesDialog dlg(Preset::Type::TYPE_PRINTER, m_presets, ""); + if (dlg.ShowModal() == wxID_CANCEL) + return; + + if (dlg.save_preset()) // save selected changes + { + const std::vector<std::string>& unselected_options = dlg.get_unselected_options(Preset::Type::TYPE_PRINTER); + const std::string& name = dlg.get_preset_name(); + + // revert unselected options to the old values + m_presets->get_edited_preset().config.apply_only(m_presets->get_selected_preset().config, unselected_options); + save_preset(name); + + for (const std::pair<std::string, Preset::Type>& nt : dlg.get_names_and_types()) + m_preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + m_preset_bundle->export_selections(*wxGetApp().app_config); + } else { + // discard all changes + m_presets->discard_current_changes(); + } + } if (m_preset_bundle->physical_printers.has_selection()) m_presets_choice->edit_physical_printer(); else @@ -1191,6 +1218,11 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) wxGetApp().mainframe->plater()->canvas3D()->set_arrange_settings(m_presets->get_edited_preset().config, m_presets->get_edited_preset().printer_technology()); } + // reset variable layer height if min/max has changed, as it's probably now invalid. + if (opt_key.find("min_layer_height") == 0 || opt_key.find("max_layer_height") == 0) { + wxPostEvent((wxEvtHandler*)wxGetApp().mainframe->plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); + } + //wxGetApp().preset_bundle->value_changed(opt_key); // update phony fields @@ -1810,6 +1842,10 @@ bool Tab::create_pages(std::string setting_type_name, int idx_page) if (current_group->sidetext_width >= 0) option.opt.sidetext_width = current_group->sidetext_width; + // global before the loop because can be overriden + if (height > 0) + option.opt.height = height; + bool need_to_notified_search = false; bool colored = false; wxString label_path; @@ -1878,13 +1914,18 @@ bool Tab::create_pages(std::string setting_type_name, int idx_page) option.opt.tooltip = (params[i].substr(8, params[i].size() - 8)); need_to_notified_search = true; } + else if (boost::starts_with(params[i], "max_literal$")) + { + if(params[i].back() == '%') + option.opt.max_literal = { boost::lexical_cast<double>(params[i].substr(12, params[i].size() - 13).c_str()), true }; + else + option.opt.max_literal = { boost::lexical_cast<double>(params[i].substr(12, params[i].size() - 12).c_str()), false }; + } } if (need_to_notified_search) Search::OptionsSearcher::register_label_override(option.opt.opt_key, option.opt.label, option.opt.full_label, option.opt.tooltip); - if(height>0) - option.opt.height = height; if (!in_line) { if (colored) { @@ -2951,7 +2992,7 @@ void TabPrinter::toggle_options() // retract lift above / below only applies if using retract lift vec.resize(0); - vec = { "retract_lift_above", "retract_lift_below", "retract_lift_top" }; + vec = { "retract_lift_above", "retract_lift_below", "retract_lift_top", "retract_lift_first_layer" }; for (auto el : vec) { field = get_field(el, i); if (field) @@ -3034,24 +3075,26 @@ void TabPrinter::toggle_options() int64_t z_step_Mlong = (int64_t)(z_step * 1000000.); DynamicPrintConfig new_conf; bool has_changed = false; - const std::vector<double>& min_layer_height = m_config->option<ConfigOptionFloats>("min_layer_height")->values; + const std::vector<double>& nozzle_diameters = m_config->option<ConfigOptionFloats>("nozzle_diameter")->values; + const std::vector<FloatOrPercent>& min_layer_height = m_config->option<ConfigOptionFloatsOrPercents>("min_layer_height")->values; for (int i = 0; i < min_layer_height.size(); i++) { - if (min_layer_height[i] != 0 && (int64_t)(min_layer_height[i] * 1000000.) % z_step_Mlong != 0) { - if (!has_changed) - new_conf = *m_config; - new_conf.option<ConfigOptionFloats>("min_layer_height")->values[i] = std::max(z_step, Slic3r::check_z_step(new_conf.option<ConfigOptionFloats>("min_layer_height")->values[i], z_step)); - has_changed = true; - } + if(!min_layer_height[i].percent) + if (min_layer_height[i].get_abs_value(nozzle_diameters[i]) != 0 && (int64_t)(min_layer_height[i].get_abs_value(nozzle_diameters[i]) * 1000000.) % z_step_Mlong != 0) { + if (!has_changed) + new_conf = *m_config; + new_conf.option<ConfigOptionFloatsOrPercents>("min_layer_height")->values[i] = FloatOrPercent{ std::max(z_step, Slic3r::check_z_step(min_layer_height[i].get_abs_value(nozzle_diameters[i]), z_step)), false }; + has_changed = true; + } } - const std::vector<double>& nozzle_diameters = m_config->option<ConfigOptionFloats>("nozzle_diameter")->values; - std::vector<double> max_layer_height = m_config->option<ConfigOptionFloats>("max_layer_height")->values; + std::vector<FloatOrPercent> max_layer_height = m_config->option<ConfigOptionFloatsOrPercents>("max_layer_height")->values; for (int i = 0; i < max_layer_height.size(); i++) { - if ((int64_t)(max_layer_height[i] * 1000000.) % z_step_Mlong != 0) { - if (!has_changed) - new_conf = *m_config; - new_conf.option<ConfigOptionFloats>("max_layer_height")->values[i] = std::max(z_step, Slic3r::check_z_step(new_conf.option<ConfigOptionFloats>("max_layer_height")->values[i], z_step)); - has_changed = true; - } + if (!max_layer_height[i].percent) + if ((int64_t)(max_layer_height[i].get_abs_value(nozzle_diameters[i]) * 1000000.) % z_step_Mlong != 0) { + if (!has_changed) + new_conf = *m_config; + new_conf.option<ConfigOptionFloatsOrPercents>("max_layer_height")->values[i] = FloatOrPercent{ std::max(z_step, Slic3r::check_z_step(new_conf.option<ConfigOptionFloats>("max_layer_height")->values[i], z_step)), false }; + has_changed = true; + } } if (has_changed) { load_config(new_conf); @@ -3455,7 +3498,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr // revert unselected options to the old values presets->get_edited_preset().config.apply_only(presets->get_selected_preset().config, unselected_options); save_preset(name); - } + } else { m_preset_bundle->save_changes_for_preset(name, presets->type(), unselected_options); @@ -3465,7 +3508,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr // but in full_config a filament_colors option aren't. if (presets->type() == Preset::TYPE_FFF_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) wxGetApp().plater()->force_filament_colors_update(); - } + } } else if (dlg.transfer_changes()) // move selected changes { @@ -3484,8 +3527,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr // copy selected options to the cache from edited preset cache_config_diff(selected_options); - } - else + } else wxGetApp().get_tab(presets->type())->cache_config_diff(selected_options); } diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index b5927b40f..d56e82d80 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -646,9 +646,13 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ }; const PresetCollection& printers = wxGetApp().preset_bundle->printers; - if (dependent_presets && (type == dependent_presets->type() ? - dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() : - printers.get_edited_preset().printer_technology() == printers.find_preset(new_selected_preset)->printer_technology())) + // get the preset we switch to (if it exists) + const Preset* new_preset = nullptr; + if (dependent_presets) + new_preset = (type == dependent_presets->type() ? dependent_presets->find_preset(new_selected_preset) : printers.find_preset(new_selected_preset)); + if (new_preset && dependent_presets && (type == dependent_presets->type() ? + dependent_presets->get_edited_preset().printer_technology() == new_preset->printer_technology() : + printers.get_edited_preset().printer_technology() == new_preset->printer_technology())) add_btn(&m_transfer_btn, m_move_btn_id, "paste_menu", Action::Transfer, _L("Transfer")); add_btn(&m_discard_btn, m_continue_btn_id, dependent_presets ? "switch_presets" : "exit", Action::Discard, _L("Discard"), false); add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, _L("Save")); @@ -1042,7 +1046,7 @@ void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent m_discard_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Discard); e.Skip(); }); - if (type == Preset::TYPE_INVALID) { + if (type == Preset::TYPE_INVALID || !dependent_presets) { m_action_line->SetLabel(header + "\n" + _L("The following presets were modified:")); } else { |