diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 93a7923fa2..9e3d0f1e05 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -43,11 +43,11 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 - - os: ubuntu-24.04 - - os: windows-latest - - os: macos-13 - arch: x86_64 + #- os: ubuntu-22.04 + #- os: ubuntu-24.04 + #- os: windows-latest + #- os: macos-13 + # arch: x86_64 - os: macos-15 arch: arm64 uses: ./.github/workflows/build_check_cache.yml diff --git a/.github/workflows/build_ubuntu.yml b/.github/workflows/build_ubuntu.yml index d2587735e2..fff488956a 100644 --- a/.github/workflows/build_ubuntu.yml +++ b/.github/workflows/build_ubuntu.yml @@ -1,6 +1,6 @@ name: Build Linux -on: [pull_request] +#on: [pull_request] jobs: appimage-builder: diff --git a/bbl/i18n/BambuStudio.pot b/bbl/i18n/BambuStudio.pot index 3dfff3f2bb..98523879ff 100644 --- a/bbl/i18n/BambuStudio.pot +++ b/bbl/i18n/BambuStudio.pot @@ -14583,175 +14583,175 @@ msgstr "" msgid "Learn more about purge mode" msgstr "" - -#: resources/data/hints.ini: [hint:How to use keyboard shortcuts] -msgid "How to use keyboard shortcuts\nDid you know that Bambu Studio offers a wide range of keyboard shortcuts and 3D scene operations." -msgstr "" - -#: resources/data/hints.ini: [hint:Cut Tool] -msgid "Cut Tool\nDid you know that you can cut a model at any angle and position with the cutting tool?" -msgstr "" - -#: resources/data/hints.ini: [hint:Fix Model] -msgid "Fix Model\nDid you know that you can fix a corrupted 3D model to avoid a lot of slicing problems on the Windows system?" -msgstr "" - -#: resources/data/hints.ini: [hint:Timelapse] -msgid "Timelapse\nDid you know that you can generate a timelapse video during each print?" -msgstr "" - -#: resources/data/hints.ini: [hint:Auto-Arrange] -msgid "Auto-Arrange\nDid you know that you can auto-arrange all objects in your project?" -msgstr "" - -#: resources/data/hints.ini: [hint:Auto-Orient] -msgid "Auto-Orient\nDid you know that you can rotate objects to an optimal orientation for printing by a simple click?" -msgstr "" - -#: resources/data/hints.ini: [hint:Lay on Face] -msgid "Lay on Face\nDid you know that you can quickly orient a model so that one of its faces sits on the print bed? Select the \"Place on face\" function or press the F key." -msgstr "" - -#: resources/data/hints.ini: [hint:Object List] -msgid "Object List\nDid you know that you can view all objects/parts in a list and change settings for each object/part?" -msgstr "" - -#: resources/data/hints.ini: [hint:Simplify Model] -msgid "Simplify Model\nDid you know that you can reduce the number of triangles in a mesh using the Simplify mesh feature? Right-click the model and select Simplify model. Read more in the documentation." -msgstr "" - -#: resources/data/hints.ini: [hint:Slicing Parameter Table] -msgid "Slicing Parameter Table\nDid you know that you can view all objects/parts on a table and change settings for each object/part?" -msgstr "" - -#: resources/data/hints.ini: [hint:Split to Objects/Parts] -msgid "Split to Objects/Parts\nDid you know that you can split a big object into small ones for easy colorizing or printing?" -msgstr "" - -#: resources/data/hints.ini: [hint:Subtract a Part] -msgid "Subtract a Part\nDid you know that you can subtract one mesh from another using the Negative part modifier? That way you can, for example, create easily resizable holes directly in Bambu Studio. Read more in the documentation." -msgstr "" - -#: resources/data/hints.ini: [hint:STEP] -msgid "STEP\nDid you know that you can improve your print quality by slicing a STEP file instead of an STL?\nBambu Studio supports slicing STEP files, providing smoother results than a lower resolution STL. Give it a try!" -msgstr "" - -#: resources/data/hints.ini: [hint:Z seam location] -msgid "Z seam location\nDid you know that you can customize the location of the Z seam, and even paint it on your print, to have it in a less visible location? This improves the overall look of your model. Check it out!" -msgstr "" - -#: resources/data/hints.ini: [hint:Fine-tuning for flow rate] -msgid "Fine-tuning for flow rate\nDid you know that flow rate can be fine-tuned for even better-looking prints? Depending on the material, you can improve the overall finish of the printed model by doing some fine-tuning." -msgstr "" - -#: resources/data/hints.ini: [hint:Split your prints into plates] -msgid "Split your prints into plates\nDid you know that you can split a model that has a lot of parts into individual plates ready to print? This will simplify the process of keeping track of all the parts." -msgstr "" - -#: resources/data/hints.ini: [hint:Speed up your print with Adaptive Layer Height] -msgid "Speed up your print with Adaptive Layer Height\nDid you know that you can print a model even faster, by using the Adaptive Layer Height option? Check it out!" -msgstr "" - -#: resources/data/hints.ini: [hint:Support painting] -msgid "Support painting\nDid you know that you can paint the location of your supports? This feature makes it easy to place the support material only on the sections of the model that actually need it." -msgstr "" - -#: resources/data/hints.ini: [hint:Different types of supports] -msgid "Different types of supports\nDid you know that you can choose from multiple types of supports? Tree supports work great for organic models, while saving filament and improving print speed. Check them out!" -msgstr "" - -#: resources/data/hints.ini: [hint:Printing Silk Filament] -msgid "Printing Silk Filament\nDid you know that Silk filament needs special consideration to print it successfully? Higher temperature and lower speed are always recommended for the best results." -msgstr "" - -#: resources/data/hints.ini: [hint:Brim for better adhesion] -msgid "Brim for better adhesion\nDid you know that when printing models have a small contact interface with the printing surface, it's recommended to use a brim?" -msgstr "" - -#: resources/data/hints.ini: [hint:Set parameters for multiple objects] -msgid "Set parameters for multiple objects\nDid you know that you can set slicing parameters for all selected objects at one time?" -msgstr "" - -#: resources/data/hints.ini: [hint:Stack objects] -msgid "Stack objects\nDid you know that you can stack objects as a whole one?" -msgstr "" - -#: resources/data/hints.ini: [hint:Flush into support/objects/infill] -msgid "Flush into support/objects/infill\nDid you know that you can save the wasted filament by flushing them into support/objects/infill during filament change?" -msgstr "" - -#: resources/data/hints.ini: [hint:Improve strength] -msgid "Improve strength\nDid you know that you can use more wall loops and higher sparse infill density to improve the strength of the model?" -msgstr "" - -#: resources/data/hints.ini: [hint:When need to print with the printer door opened] -msgid "When need to print with the printer door opened\nDid you know that opening the printer door can reduce the probability of extruder/hotend clogging when printing lower temperature filament with a higher enclosure temperature. More info about this in the Wiki." -msgstr "" - -#: resources/data/hints.ini: [hint:Avoid warping] -msgid "Avoid warping\nDid you know that when printing materials that are prone to warping such as ABS, appropriately increasing the heatbed temperature can reduce the probability of warping." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Single-Material Only] -msgid "Single-Material Only\nHelio currently simulates one material and one nozzle per job. Multi-material or multi-extruder G-code adds long pauses that break thermal continuity, so results wouldn’t be meaningful." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: One Plate per Job] -msgid "One Plate per Job\nUpload G-code with a single build plate—multi-plate projects aren’t yet supported, so only the first plate would run." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: What is the Thermal Quality Index?] -msgid "What is the Thermal Quality Index?\nThe Thermal Quality Index (scale –100 to +100) shows how hot or cold each region prints—green (≈ 0) is the “just right” zone for strong, warp-free parts. Keep most of the part green for best results." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Voxel-Level Accuracy] -msgid "Voxel-Level Accuracy\nWe predict temperature in every voxel at every time-step, and for standard jobs the forecast is typically within ±5–10 °C. Pauses, custom firmware or odd cooling can widen that margin." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Fan & Airflow Model] -msgid "Fan & Airflow Model\nA simplified fan-and-room model shows how cooling settings change part temps without slow CFD maths—great for day-to-day tuning. Chamber vortices aren’t yet simulated so runs stay fast." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: TQI Limits Explained] -msgid "TQI Limits Explained\n-100 → too cold: tensile strength is ~50 % lower than parts printed at the ideal 0 (ASTM D638 dog-bone tests). +100 → too hot: layers stay molten and may sag or collapse. Keep regions near 0 for peak strength and accuracy." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: What Drives Runtime?] -msgid "What Drives Runtime?\nExtra layers, dense infill, lots of tiny arcs (small mesh elements) or very slow printing speeds all extend simulation time because the solver must step through more seconds. Multi-core CPUs or CUDA GPUs speed things up." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Nozzle Temp Range] -msgid "Nozzle Temp Range\nSupported set-points are 190 – 320 °C; anything outside is clamped to keep physics realistic." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Debugging Flowchart] -msgid "Debugging Flowchart\nNot sure why a result looks off? Follow our step-by-step debugging flowchart to trace settings, G-code and material issues in minutes." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Why Cooling Varies] -msgid "Why Cooling Varies\nOuter walls and bridges cool fastest while thick interiors stay warmer—geometry, airflow and tool-path all play a part, and the simulation visualises these differences." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Extrusion Temp Model] -msgid "Extrusion Temp Model\nMaterial properties shape the melt curve, but printer geometry decides how much heat the filament actually gains, so the model is material-specific and printer-calibrated." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Nozzle setting of 190 °C vs 320 °C?] -msgid "Nozzle setting of 190 °C vs 320 °C?\nA 100 °C nozzle change only nudges the thermal index because extrusion temp, flow rate and post-deposition cooling dominate the part’s heat history." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Bed Temperature influence] -msgid "Bed Temperature influence\nOnly the first-layer bed temp feeds the model right now; later bed changes aren’t yet captured." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Actual Tool-Path] -msgid "Actual Tool-Path\nYes—your exact G-code path, speeds and fan commands are simulated." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Mesh Resolution] -msgid "Mesh Resolution\nThe voxel grid is finer than the G-code line spacing, capturing layer-by-layer detail without wasting compute." -msgstr "" - -#: resources/data/helio_hints.ini: [hint: Shrinkage, Warping & Stress] -msgid "Shrinkage, Warping & Stress\nBy controlling the thermal index you can remove the heat-driven causes of warp and stress." -msgstr "" + +#: resources/data/hints.ini: [hint:How to use keyboard shortcuts] +msgid "How to use keyboard shortcuts\nDid you know that Bambu Studio offers a wide range of keyboard shortcuts and 3D scene operations." +msgstr "" + +#: resources/data/hints.ini: [hint:Cut Tool] +msgid "Cut Tool\nDid you know that you can cut a model at any angle and position with the cutting tool?" +msgstr "" + +#: resources/data/hints.ini: [hint:Fix Model] +msgid "Fix Model\nDid you know that you can fix a corrupted 3D model to avoid a lot of slicing problems on the Windows system?" +msgstr "" + +#: resources/data/hints.ini: [hint:Timelapse] +msgid "Timelapse\nDid you know that you can generate a timelapse video during each print?" +msgstr "" + +#: resources/data/hints.ini: [hint:Auto-Arrange] +msgid "Auto-Arrange\nDid you know that you can auto-arrange all objects in your project?" +msgstr "" + +#: resources/data/hints.ini: [hint:Auto-Orient] +msgid "Auto-Orient\nDid you know that you can rotate objects to an optimal orientation for printing by a simple click?" +msgstr "" + +#: resources/data/hints.ini: [hint:Lay on Face] +msgid "Lay on Face\nDid you know that you can quickly orient a model so that one of its faces sits on the print bed? Select the \"Place on face\" function or press the F key." +msgstr "" + +#: resources/data/hints.ini: [hint:Object List] +msgid "Object List\nDid you know that you can view all objects/parts in a list and change settings for each object/part?" +msgstr "" + +#: resources/data/hints.ini: [hint:Simplify Model] +msgid "Simplify Model\nDid you know that you can reduce the number of triangles in a mesh using the Simplify mesh feature? Right-click the model and select Simplify model. Read more in the documentation." +msgstr "" + +#: resources/data/hints.ini: [hint:Slicing Parameter Table] +msgid "Slicing Parameter Table\nDid you know that you can view all objects/parts on a table and change settings for each object/part?" +msgstr "" + +#: resources/data/hints.ini: [hint:Split to Objects/Parts] +msgid "Split to Objects/Parts\nDid you know that you can split a big object into small ones for easy colorizing or printing?" +msgstr "" + +#: resources/data/hints.ini: [hint:Subtract a Part] +msgid "Subtract a Part\nDid you know that you can subtract one mesh from another using the Negative part modifier? That way you can, for example, create easily resizable holes directly in Bambu Studio. Read more in the documentation." +msgstr "" + +#: resources/data/hints.ini: [hint:STEP] +msgid "STEP\nDid you know that you can improve your print quality by slicing a STEP file instead of an STL?\nBambu Studio supports slicing STEP files, providing smoother results than a lower resolution STL. Give it a try!" +msgstr "" + +#: resources/data/hints.ini: [hint:Z seam location] +msgid "Z seam location\nDid you know that you can customize the location of the Z seam, and even paint it on your print, to have it in a less visible location? This improves the overall look of your model. Check it out!" +msgstr "" + +#: resources/data/hints.ini: [hint:Fine-tuning for flow rate] +msgid "Fine-tuning for flow rate\nDid you know that flow rate can be fine-tuned for even better-looking prints? Depending on the material, you can improve the overall finish of the printed model by doing some fine-tuning." +msgstr "" + +#: resources/data/hints.ini: [hint:Split your prints into plates] +msgid "Split your prints into plates\nDid you know that you can split a model that has a lot of parts into individual plates ready to print? This will simplify the process of keeping track of all the parts." +msgstr "" + +#: resources/data/hints.ini: [hint:Speed up your print with Adaptive Layer Height] +msgid "Speed up your print with Adaptive Layer Height\nDid you know that you can print a model even faster, by using the Adaptive Layer Height option? Check it out!" +msgstr "" + +#: resources/data/hints.ini: [hint:Support painting] +msgid "Support painting\nDid you know that you can paint the location of your supports? This feature makes it easy to place the support material only on the sections of the model that actually need it." +msgstr "" + +#: resources/data/hints.ini: [hint:Different types of supports] +msgid "Different types of supports\nDid you know that you can choose from multiple types of supports? Tree supports work great for organic models, while saving filament and improving print speed. Check them out!" +msgstr "" + +#: resources/data/hints.ini: [hint:Printing Silk Filament] +msgid "Printing Silk Filament\nDid you know that Silk filament needs special consideration to print it successfully? Higher temperature and lower speed are always recommended for the best results." +msgstr "" + +#: resources/data/hints.ini: [hint:Brim for better adhesion] +msgid "Brim for better adhesion\nDid you know that when printing models have a small contact interface with the printing surface, it's recommended to use a brim?" +msgstr "" + +#: resources/data/hints.ini: [hint:Set parameters for multiple objects] +msgid "Set parameters for multiple objects\nDid you know that you can set slicing parameters for all selected objects at one time?" +msgstr "" + +#: resources/data/hints.ini: [hint:Stack objects] +msgid "Stack objects\nDid you know that you can stack objects as a whole one?" +msgstr "" + +#: resources/data/hints.ini: [hint:Flush into support/objects/infill] +msgid "Flush into support/objects/infill\nDid you know that you can save the wasted filament by flushing them into support/objects/infill during filament change?" +msgstr "" + +#: resources/data/hints.ini: [hint:Improve strength] +msgid "Improve strength\nDid you know that you can use more wall loops and higher sparse infill density to improve the strength of the model?" +msgstr "" + +#: resources/data/hints.ini: [hint:When need to print with the printer door opened] +msgid "When need to print with the printer door opened\nDid you know that opening the printer door can reduce the probability of extruder/hotend clogging when printing lower temperature filament with a higher enclosure temperature. More info about this in the Wiki." +msgstr "" + +#: resources/data/hints.ini: [hint:Avoid warping] +msgid "Avoid warping\nDid you know that when printing materials that are prone to warping such as ABS, appropriately increasing the heatbed temperature can reduce the probability of warping." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Single-Material Only] +msgid "Single-Material Only\nHelio currently simulates one material and one nozzle per job. Multi-material or multi-extruder G-code adds long pauses that break thermal continuity, so results wouldn’t be meaningful." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: One Plate per Job] +msgid "One Plate per Job\nUpload G-code with a single build plate—multi-plate projects aren’t yet supported, so only the first plate would run." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: What is the Thermal Quality Index?] +msgid "What is the Thermal Quality Index?\nThe Thermal Quality Index (scale –100 to +100) shows how hot or cold each region prints—green (≈ 0) is the “just right” zone for strong, warp-free parts. Keep most of the part green for best results." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Voxel-Level Accuracy] +msgid "Voxel-Level Accuracy\nWe predict temperature in every voxel at every time-step, and for standard jobs the forecast is typically within ±5–10 °C. Pauses, custom firmware or odd cooling can widen that margin." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Fan & Airflow Model] +msgid "Fan & Airflow Model\nA simplified fan-and-room model shows how cooling settings change part temps without slow CFD maths—great for day-to-day tuning. Chamber vortices aren’t yet simulated so runs stay fast." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: TQI Limits Explained] +msgid "TQI Limits Explained\n-100 → too cold: tensile strength is ~50 % lower than parts printed at the ideal 0 (ASTM D638 dog-bone tests). +100 → too hot: layers stay molten and may sag or collapse. Keep regions near 0 for peak strength and accuracy." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: What Drives Runtime?] +msgid "What Drives Runtime?\nExtra layers, dense infill, lots of tiny arcs (small mesh elements) or very slow printing speeds all extend simulation time because the solver must step through more seconds. Multi-core CPUs or CUDA GPUs speed things up." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Nozzle Temp Range] +msgid "Nozzle Temp Range\nSupported set-points are 190 – 320 °C; anything outside is clamped to keep physics realistic." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Debugging Flowchart] +msgid "Debugging Flowchart\nNot sure why a result looks off? Follow our step-by-step debugging flowchart to trace settings, G-code and material issues in minutes." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Why Cooling Varies] +msgid "Why Cooling Varies\nOuter walls and bridges cool fastest while thick interiors stay warmer—geometry, airflow and tool-path all play a part, and the simulation visualises these differences." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Extrusion Temp Model] +msgid "Extrusion Temp Model\nMaterial properties shape the melt curve, but printer geometry decides how much heat the filament actually gains, so the model is material-specific and printer-calibrated." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Nozzle setting of 190 °C vs 320 °C?] +msgid "Nozzle setting of 190 °C vs 320 °C?\nA 100 °C nozzle change only nudges the thermal index because extrusion temp, flow rate and post-deposition cooling dominate the part’s heat history." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Bed Temperature influence] +msgid "Bed Temperature influence\nOnly the first-layer bed temp feeds the model right now; later bed changes aren’t yet captured." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Actual Tool-Path] +msgid "Actual Tool-Path\nYes—your exact G-code path, speeds and fan commands are simulated." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Mesh Resolution] +msgid "Mesh Resolution\nThe voxel grid is finer than the G-code line spacing, capturing layer-by-layer detail without wasting compute." +msgstr "" + +#: resources/data/helio_hints.ini: [hint: Shrinkage, Warping & Stress] +msgid "Shrinkage, Warping & Stress\nBy controlling the thermal index you can remove the heat-driven causes of warp and stress." +msgstr "" diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index b6300663bb..e408f05b34 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -25,1805 +25,2097 @@ #include #include #include -//BBS: add json support +// BBS: add json support #include "nlohmann/json.hpp" using namespace nlohmann; -//FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion) -// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy(). +// FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion) +// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy(). #include "PrintConfig.hpp" -namespace Slic3r { - -//BBS: add json support -//static const std::string CONFIG_VERSION_KEY = "version"; -//static const std::string CONFIG_NAME_KEY = "name"; -//static const std::string CONFIG_URL_KEY = "url"; -//static const std::string CONFIG_TYPE_KEY = "type"; -//static const std::string CONFIG_FROM_KEY = "from"; -//static const std::string CONFIG_INHERITS_KEY = "inherits"; -//static const std::string CONFIG_INSTANT_KEY = "instantiation"; - -// Escape \n, \r and backslash -std::string escape_string_cstyle(const std::string &str) +namespace Slic3r { - // Allocate a buffer twice the input string length, - // so the output will fit even if all input characters get escaped. - std::vector out(str.size() * 2, 0); - char *outptr = out.data(); - for (size_t i = 0; i < str.size(); ++ i) { - char c = str[i]; - if (c == '\r') { - (*outptr ++) = '\\'; - (*outptr ++) = 'r'; - } else if (c == '\n') { - (*outptr ++) = '\\'; - (*outptr ++) = 'n'; - } else if (c == '\\') { - (*outptr ++) = '\\'; - (*outptr ++) = '\\'; - } else - (*outptr ++) = c; - } - return std::string(out.data(), outptr - out.data()); -} -std::string escape_strings_cstyle(const std::vector &strs) -{ - // 1) Estimate the output buffer size to avoid buffer reallocation. - size_t outbuflen = 0; - for (size_t i = 0; i < strs.size(); ++ i) - // Reserve space for every character escaped + quotes + semicolon. - outbuflen += strs[i].size() * 2 + 3; - // 2) Fill in the buffer. - std::vector out(outbuflen, 0); - char *outptr = out.data(); - for (size_t j = 0; j < strs.size(); ++ j) { - if (j > 0) - // Separate the strings. - (*outptr ++) = ';'; - const std::string &str = strs[j]; - // Is the string simple or complex? Complex string contains spaces, tabs, new lines and other - // escapable characters. Empty string shall be quoted as well, if it is the only string in strs. - bool should_quote = strs.size() == 1 && str.empty(); - for (size_t i = 0; i < str.size(); ++ i) { + // BBS: add json support + // static const std::string CONFIG_VERSION_KEY = "version"; + // static const std::string CONFIG_NAME_KEY = "name"; + // static const std::string CONFIG_URL_KEY = "url"; + // static const std::string CONFIG_TYPE_KEY = "type"; + // static const std::string CONFIG_FROM_KEY = "from"; + // static const std::string CONFIG_INHERITS_KEY = "inherits"; + // static const std::string CONFIG_INSTANT_KEY = "instantiation"; + + // Escape \n, \r and backslash + std::string escape_string_cstyle(const std::string &str) + { + // Allocate a buffer twice the input string length, + // so the output will fit even if all input characters get escaped. + std::vector out(str.size() * 2, 0); + char *outptr = out.data(); + for (size_t i = 0; i < str.size(); ++i) + { char c = str[i]; - if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') { - should_quote = true; - break; + if (c == '\r') + { + (*outptr++) = '\\'; + (*outptr++) = 'r'; + } + else if (c == '\n') + { + (*outptr++) = '\\'; + (*outptr++) = 'n'; + } + else if (c == '\\') + { + (*outptr++) = '\\'; + (*outptr++) = '\\'; } + else + (*outptr++) = c; } - if (should_quote) { - (*outptr ++) = '"'; - for (size_t i = 0; i < str.size(); ++ i) { + return std::string(out.data(), outptr - out.data()); + } + + std::string escape_strings_cstyle(const std::vector &strs) + { + // 1) Estimate the output buffer size to avoid buffer reallocation. + size_t outbuflen = 0; + for (size_t i = 0; i < strs.size(); ++i) + // Reserve space for every character escaped + quotes + semicolon. + outbuflen += strs[i].size() * 2 + 3; + // 2) Fill in the buffer. + std::vector out(outbuflen, 0); + char *outptr = out.data(); + for (size_t j = 0; j < strs.size(); ++j) + { + if (j > 0) + // Separate the strings. + (*outptr++) = ';'; + const std::string &str = strs[j]; + // Is the string simple or complex? Complex string contains spaces, tabs, new lines and other + // escapable characters. Empty string shall be quoted as well, if it is the only string in strs. + bool should_quote = strs.size() == 1 && str.empty(); + for (size_t i = 0; i < str.size(); ++i) + { char c = str[i]; - if (c == '\\' || c == '"') { - (*outptr ++) = '\\'; - (*outptr ++) = c; - } else if (c == '\r') { - (*outptr ++) = '\\'; - (*outptr ++) = 'r'; - } else if (c == '\n') { - (*outptr ++) = '\\'; - (*outptr ++) = 'n'; - } else - (*outptr ++) = c; - } - (*outptr ++) = '"'; - } else { - memcpy(outptr, str.data(), str.size()); - outptr += str.size(); + if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') + { + should_quote = true; + break; + } + } + if (should_quote) + { + (*outptr++) = '"'; + for (size_t i = 0; i < str.size(); ++i) + { + char c = str[i]; + if (c == '\\' || c == '"') + { + (*outptr++) = '\\'; + (*outptr++) = c; + } + else if (c == '\r') + { + (*outptr++) = '\\'; + (*outptr++) = 'r'; + } + else if (c == '\n') + { + (*outptr++) = '\\'; + (*outptr++) = 'n'; + } + else + (*outptr++) = c; + } + (*outptr++) = '"'; + } + else + { + memcpy(outptr, str.data(), str.size()); + outptr += str.size(); + } } + return std::string(out.data(), outptr - out.data()); } - return std::string(out.data(), outptr - out.data()); -} -// Unescape \n, \r and backslash -bool unescape_string_cstyle(const std::string &str, std::string &str_out) -{ - std::vector out(str.size(), 0); - char *outptr = out.data(); - for (size_t i = 0; i < str.size(); ++ i) { - char c = str[i]; - if (c == '\\') { - if (++ i == str.size()) - return false; - c = str[i]; - if (c == 'r') - (*outptr ++) = '\r'; - else if (c == 'n') - (*outptr ++) = '\n'; + // Unescape \n, \r and backslash + bool unescape_string_cstyle(const std::string &str, std::string &str_out) + { + std::vector out(str.size(), 0); + char *outptr = out.data(); + for (size_t i = 0; i < str.size(); ++i) + { + char c = str[i]; + if (c == '\\') + { + if (++i == str.size()) + return false; + c = str[i]; + if (c == 'r') + (*outptr++) = '\r'; + else if (c == 'n') + (*outptr++) = '\n'; + else + (*outptr++) = c; + } else - (*outptr ++) = c; - } else - (*outptr ++) = c; + (*outptr++) = c; + } + str_out.assign(out.data(), outptr - out.data()); + return true; } - str_out.assign(out.data(), outptr - out.data()); - return true; -} -bool unescape_strings_cstyle(const std::string &str, std::vector &out) -{ - if (str.empty()) - return true; + bool unescape_strings_cstyle(const std::string &str, std::vector &out) + { + if (str.empty()) + return true; - size_t i = 0; - for (;;) { - // Skip white spaces. - char c = str[i]; - while (c == ' ' || c == '\t') { - if (++ i == str.size()) - return true; - c = str[i]; - } - // Start of a word. - std::vector buf; - buf.reserve(16); - // Is it enclosed in quotes? - c = str[i]; - if (c == '"') { - // Complex case, string is enclosed in quotes. - for (++ i; i < str.size(); ++ i) { + size_t i = 0; + for (;;) + { + // Skip white spaces. + char c = str[i]; + while (c == ' ' || c == '\t') + { + if (++i == str.size()) + return true; c = str[i]; - if (c == '"') { - // End of string. - break; + } + // Start of a word. + std::vector buf; + buf.reserve(16); + // Is it enclosed in quotes? + c = str[i]; + if (c == '"') + { + // Complex case, string is enclosed in quotes. + for (++i; i < str.size(); ++i) + { + c = str[i]; + if (c == '"') + { + // End of string. + break; + } + if (c == '\\') + { + if (++i == str.size()) + return false; + c = str[i]; + if (c == 'r') + c = '\r'; + else if (c == 'n') + c = '\n'; + } + buf.push_back(c); } - if (c == '\\') { - if (++ i == str.size()) - return false; + if (i == str.size()) + return false; + ++i; + } + else + { + for (; i < str.size(); ++i) + { c = str[i]; - if (c == 'r') - c = '\r'; - else if (c == 'n') - c = '\n'; + if (c == ';') + break; + buf.push_back(c); } - buf.push_back(c); } + // Store the string into the output vector. + out.push_back(std::string(buf.data(), buf.size())); if (i == str.size()) - return false; - ++ i; - } else { - for (; i < str.size(); ++ i) { + return true; + // Skip white spaces. + c = str[i]; + while (c == ' ' || c == '\t') + { + if (++i == str.size()) + // End of string. This is correct. + return true; c = str[i]; - if (c == ';') - break; - buf.push_back(c); } - } - // Store the string into the output vector. - out.push_back(std::string(buf.data(), buf.size())); - if (i == str.size()) - return true; - // Skip white spaces. - c = str[i]; - while (c == ' ' || c == '\t') { - if (++ i == str.size()) - // End of string. This is correct. + if (c != ';') + return false; + if (++i == str.size()) + { + // Emit one additional empty string. + out.push_back(std::string()); return true; - c = str[i]; + } } - if (c != ';') - return false; - if (++ i == str.size()) { - // Emit one additional empty string. - out.push_back(std::string()); - return true; + } + + std::string escape_ampersand(const std::string &str) + { + // Allocate a buffer 2 times the input string length, + // so the output will fit even if all input characters get escaped. + std::vector out(str.size() * 6, 0); + char *outptr = out.data(); + for (size_t i = 0; i < str.size(); ++i) + { + char c = str[i]; + if (c == '&') + { + (*outptr++) = '&'; + (*outptr++) = '&'; + } + else + (*outptr++) = c; } + return std::string(out.data(), outptr - out.data()); } -} -std::string escape_ampersand(const std::string& str) -{ - // Allocate a buffer 2 times the input string length, - // so the output will fit even if all input characters get escaped. - std::vector out(str.size() * 6, 0); - char* outptr = out.data(); - for (size_t i = 0; i < str.size(); ++i) { - char c = str[i]; - if (c == '&') { - (*outptr++) = '&'; - (*outptr++) = '&'; - } else - (*outptr++) = c; + void ConfigOptionDeleter::operator()(ConfigOption *p) + { + delete p; } - return std::string(out.data(), outptr - out.data()); -} -void ConfigOptionDeleter::operator()(ConfigOption* p) { - delete p; -} + std::vector ConfigOptionDef::cli_args(const std::string &key) const + { + std::vector args; + if (this->cli != ConfigOptionDef::nocli) + { + const std::string &cli = this->cli; + // FIXME What was that for? Check the "readline" documentation. + // Neither '=' nor '!' is used in any of the cli parameters currently defined by PrusaSlicer. + // std::string cli = this->cli.substr(0, this->cli.find("=")); + // boost::trim_right_if(cli, boost::is_any_of("!")); + if (cli.empty()) + { + // Convert an option key to CLI argument by replacing underscores with dashes. + std::string opt = key; + boost::replace_all(opt, "_", "-"); + args.emplace_back(std::move(opt)); + } + else + boost::split(args, cli, boost::is_any_of("|")); + } + return args; + } -std::vector ConfigOptionDef::cli_args(const std::string &key) const -{ - std::vector args; - if (this->cli != ConfigOptionDef::nocli) { - const std::string &cli = this->cli; - //FIXME What was that for? Check the "readline" documentation. - // Neither '=' nor '!' is used in any of the cli parameters currently defined by PrusaSlicer. -// std::string cli = this->cli.substr(0, this->cli.find("=")); -// boost::trim_right_if(cli, boost::is_any_of("!")); - if (cli.empty()) { - // Convert an option key to CLI argument by replacing underscores with dashes. - std::string opt = key; - boost::replace_all(opt, "_", "-"); - args.emplace_back(std::move(opt)); - } else - boost::split(args, cli, boost::is_any_of("|")); + ConfigOption *ConfigOptionDef::create_empty_option() const + { + if (this->nullable) + { + switch (this->type) + { + case coFloats: + return new ConfigOptionFloatsNullable(); + case coInts: + return new ConfigOptionIntsNullable(); + case coPercents: + return new ConfigOptionPercentsNullable(); + case coFloatsOrPercents: + return new ConfigOptionFloatsOrPercentsNullable(); + case coBools: + return new ConfigOptionBoolsNullable(); + default: + throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label); + } + } + else + { + switch (this->type) + { + case coFloat: + return new ConfigOptionFloat(); + case coFloats: + return new ConfigOptionFloats(); + case coInt: + return new ConfigOptionInt(); + case coInts: + return new ConfigOptionInts(); + case coString: + return new ConfigOptionString(); + case coStrings: + return new ConfigOptionStrings(); + case coPercent: + return new ConfigOptionPercent(); + case coPercents: + return new ConfigOptionPercents(); + case coFloatOrPercent: + return new ConfigOptionFloatOrPercent(); + case coFloatsOrPercents: + return new ConfigOptionFloatsOrPercents(); + case coPoint: + return new ConfigOptionPoint(); + case coPoints: + return new ConfigOptionPoints(); + case coPoint3: + return new ConfigOptionPoint3(); + // case coPoint3s: return new ConfigOptionPoint3s(); + case coBool: + return new ConfigOptionBool(); + case coBools: + return new ConfigOptionBools(); + case coEnum: + return new ConfigOptionEnumGeneric(this->enum_keys_map); + // BBS + case coEnums: + return new ConfigOptionEnumsGeneric(this->enum_keys_map); + default: + throw ConfigurationError(std::string("Unknown option type for option ") + this->label); + } + } } - return args; -} -ConfigOption* ConfigOptionDef::create_empty_option() const -{ - if (this->nullable) { - switch (this->type) { - case coFloats: return new ConfigOptionFloatsNullable(); - case coInts: return new ConfigOptionIntsNullable(); - case coPercents: return new ConfigOptionPercentsNullable(); - case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable(); - case coBools: return new ConfigOptionBoolsNullable(); - default: throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label); - } - } else { - switch (this->type) { - case coFloat: return new ConfigOptionFloat(); - case coFloats: return new ConfigOptionFloats(); - case coInt: return new ConfigOptionInt(); - case coInts: return new ConfigOptionInts(); - case coString: return new ConfigOptionString(); - case coStrings: return new ConfigOptionStrings(); - case coPercent: return new ConfigOptionPercent(); - case coPercents: return new ConfigOptionPercents(); - case coFloatOrPercent: return new ConfigOptionFloatOrPercent(); - case coFloatsOrPercents: return new ConfigOptionFloatsOrPercents(); - case coPoint: return new ConfigOptionPoint(); - case coPoints: return new ConfigOptionPoints(); - case coPoint3: return new ConfigOptionPoint3(); - // case coPoint3s: return new ConfigOptionPoint3s(); - case coBool: return new ConfigOptionBool(); - case coBools: return new ConfigOptionBools(); - case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); - // BBS - case coEnums: return new ConfigOptionEnumsGeneric(this->enum_keys_map); - default: throw ConfigurationError(std::string("Unknown option type for option ") + this->label); - } - } -} + ConfigOption *ConfigOptionDef::create_default_option() const + { + if (this->default_value) + { + ConfigOptionType type = this->default_value->type(); + if (type == coEnum) + return new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()); -ConfigOption* ConfigOptionDef::create_default_option() const -{ - if (this->default_value) { - ConfigOptionType type = this->default_value->type(); - if (type == coEnum) - return new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()); - - if (type == coEnums) { - auto dft = this->default_value->clone(); - if (dft->nullable()) { - ConfigOptionEnumsGenericNullable *opt = dynamic_cast(this->default_value->clone()); - opt->keys_map = this->enum_keys_map; - return opt; - } else { - ConfigOptionEnumsGeneric *opt = dynamic_cast(this->default_value->clone()); - opt->keys_map = this->enum_keys_map; - return opt; - } - delete dft; + if (type == coEnums) + { + auto dft = this->default_value->clone(); + if (dft->nullable()) + { + ConfigOptionEnumsGenericNullable *opt = dynamic_cast(this->default_value->clone()); + opt->keys_map = this->enum_keys_map; + return opt; + } + else + { + ConfigOptionEnumsGeneric *opt = dynamic_cast(this->default_value->clone()); + opt->keys_map = this->enum_keys_map; + return opt; + } + delete dft; + } + + return this->default_value->clone(); } + return this->create_empty_option(); + } - return this->default_value->clone(); + // Assignment of the serialization IDs is not thread safe. The Defs shall be initialized from the main thread! + ConfigOptionDef *ConfigDef::add(const t_config_option_key &opt_key, ConfigOptionType type) + { + static size_t serialization_key_ordinal_last = 0; + ConfigOptionDef *opt = &this->options[opt_key]; + opt->opt_key = opt_key; + opt->type = type; + opt->serialization_key_ordinal = ++serialization_key_ordinal_last; + this->by_serialization_key_ordinal[opt->serialization_key_ordinal] = opt; + return opt; } - return this->create_empty_option(); -} -// Assignment of the serialization IDs is not thread safe. The Defs shall be initialized from the main thread! -ConfigOptionDef* ConfigDef::add(const t_config_option_key &opt_key, ConfigOptionType type) -{ - static size_t serialization_key_ordinal_last = 0; - ConfigOptionDef *opt = &this->options[opt_key]; - opt->opt_key = opt_key; - opt->type = type; - opt->serialization_key_ordinal = ++ serialization_key_ordinal_last; - this->by_serialization_key_ordinal[opt->serialization_key_ordinal] = opt; - return opt; -} + ConfigOptionDef *ConfigDef::add_nullable(const t_config_option_key &opt_key, ConfigOptionType type) + { + ConfigOptionDef *def = this->add(opt_key, type); + def->nullable = true; + return def; + } -ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, ConfigOptionType type) -{ - ConfigOptionDef *def = this->add(opt_key, type); - def->nullable = true; - return def; -} + std::ostream &ConfigDef::print_cli_help(std::ostream &out, bool show_defaults, std::function filter) const + { + // prepare a function for wrapping text + auto wrap = [](std::string text, size_t line_length) -> std::string + { + std::istringstream words(text); + std::ostringstream wrapped; + std::string word; -std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function filter) const -{ - // prepare a function for wrapping text - auto wrap = [](std::string text, size_t line_length) -> std::string { - std::istringstream words(text); - std::ostringstream wrapped; - std::string word; - - if (words >> word) { - wrapped << word; - size_t space_left = line_length - word.length(); - while (words >> word) { - if (space_left < word.length() + 1) { - wrapped << '\n' << word; - space_left = line_length - word.length(); - } else { - wrapped << ' ' << word; - space_left -= word.length() + 1; + if (words >> word) + { + wrapped << word; + size_t space_left = line_length - word.length(); + while (words >> word) + { + if (space_left < word.length() + 1) + { + wrapped << '\n' + << word; + space_left = line_length - word.length(); + } + else + { + wrapped << ' ' << word; + space_left -= word.length() + 1; + } } } + return wrapped.str(); + }; + + // get the unique categories + std::set categories; + for (const auto &opt : this->options) + { + const ConfigOptionDef &def = opt.second; + if (filter(def)) + categories.insert(def.category); } - return wrapped.str(); - }; - // get the unique categories - std::set categories; - for (const auto& opt : this->options) { - const ConfigOptionDef& def = opt.second; - if (filter(def)) - categories.insert(def.category); - } + for (auto category : categories) + { + if (category != "") + { + out << category << ":" << std::endl; + } + else if (categories.size() > 1) + { + out << "Misc options:" << std::endl; + } - for (auto category : categories) { - if (category != "") { - out << category << ":" << std::endl; - } else if (categories.size() > 1) { - out << "Misc options:" << std::endl; - } + for (const auto &opt : this->options) + { + const ConfigOptionDef &def = opt.second; + if (def.category != category || def.cli == ConfigOptionDef::nocli || !filter(def)) + continue; - for (const auto& opt : this->options) { - const ConfigOptionDef& def = opt.second; - if (def.category != category || def.cli == ConfigOptionDef::nocli || !filter(def)) - continue; + // get all possible variations: --foo, --foobar, -f... + std::vector cli_args = def.cli_args(opt.first); + if (cli_args.empty()) + continue; - // get all possible variations: --foo, --foobar, -f... - std::vector cli_args = def.cli_args(opt.first); - if (cli_args.empty()) - continue; - - for (auto& arg : cli_args) { - arg.insert(0, (arg.size() == 1) ? "-" : "--"); - //BBS: refine the print help format - if (!def.cli_params.empty()) - arg += " " + def.cli_params; - /*if ( def.type == coInt || def.type == coInts) { - arg += " int_value"; - } else if (def.type == coFloat || def.type == coFloatOrPercent || def.type == coFloats) { - arg += " float_value"; - } else if (def.type == coPoint) { - arg += " X,Y"; - } else if (def.type == coPoint3) { - arg += " X,Y,Z"; - } else if (def.type == coString || def.type == coStrings) { - arg += " filename_lists"; - }*/ - } + for (auto &arg : cli_args) + { + arg.insert(0, (arg.size() == 1) ? "-" : "--"); + // BBS: refine the print help format + if (!def.cli_params.empty()) + arg += " " + def.cli_params; + /*if ( def.type == coInt || def.type == coInts) { + arg += " int_value"; + } else if (def.type == coFloat || def.type == coFloatOrPercent || def.type == coFloats) { + arg += " float_value"; + } else if (def.type == coPoint) { + arg += " X,Y"; + } else if (def.type == coPoint3) { + arg += " X,Y,Z"; + } else if (def.type == coString || def.type == coStrings) { + arg += " filename_lists"; + }*/ + } - // left: command line options - const std::string cli = boost::algorithm::join(cli_args, ", "); - out << " " << std::left << std::setw(20) << cli; - - // right: option description - std::string descr = def.tooltip; - bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility"; - if (show_defaults_this && def.default_value && def.type != coBool - && (def.type != coString || !def.default_value->serialize().empty())) { - descr += " ("; - if (!def.sidetext.empty()) { - descr += def.sidetext + ", "; - } else if (!def.enum_values.empty()) { - descr += boost::algorithm::join(def.enum_values, ", ") + "; "; + // left: command line options + const std::string cli = boost::algorithm::join(cli_args, ", "); + out << " " << std::left << std::setw(20) << cli; + + // right: option description + std::string descr = def.tooltip; + bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility"; + if (show_defaults_this && def.default_value && def.type != coBool && (def.type != coString || !def.default_value->serialize().empty())) + { + descr += " ("; + if (!def.sidetext.empty()) + { + descr += def.sidetext + ", "; + } + else if (!def.enum_values.empty()) + { + descr += boost::algorithm::join(def.enum_values, ", ") + "; "; + } + descr += "default: " + def.default_value->serialize() + ")"; } - descr += "default: " + def.default_value->serialize() + ")"; - } - // wrap lines of description - descr = wrap(descr, 80); - std::vector lines; - boost::split(lines, descr, boost::is_any_of("\n")); + // wrap lines of description + descr = wrap(descr, 80); + std::vector lines; + boost::split(lines, descr, boost::is_any_of("\n")); - // if command line options are too long, print description in new line - for (size_t i = 0; i < lines.size(); ++i) { - if (i == 0 && cli.size() > 19) - out << std::endl; - if (i > 0 || cli.size() > 19) - out << std::string(21, ' '); - out << lines[i] << std::endl; + // if command line options are too long, print description in new line + for (size_t i = 0; i < lines.size(); ++i) + { + if (i == 0 && cli.size() > 19) + out << std::endl; + if (i > 0 || cli.size() > 19) + out << std::string(21, ' '); + out << lines[i] << std::endl; + } } } + return out; } - return out; -} -void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) -{ - // loop through options and apply them - for (const t_config_option_key &opt_key : keys) { - // Create a new option with default value for the key. - // If the key is not in the parameter definition, or this ConfigBase is a static type and it does not support the parameter, - // an exception is thrown if not ignore_nonexistent. - ConfigOption *my_opt = this->option(opt_key, true); - if (my_opt == nullptr) { - // opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def(). - // This is only possible if other is of DynamicConfig type. - if (auto n = opt_key.find('#'); n != std::string::npos) { - auto opt_key2 = opt_key.substr(0, n); - auto my_opt2 = dynamic_cast(this->option(opt_key2)); - auto other_opt = other.option(opt_key2); - if (my_opt2 == nullptr && other_opt) { - my_opt2 = dynamic_cast(this->option(opt_key2, true)); - if (my_opt2->empty()) { - my_opt2->resize(1, other_opt); + void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) + { + // loop through options and apply them + for (const t_config_option_key &opt_key : keys) + { + // Create a new option with default value for the key. + // If the key is not in the parameter definition, or this ConfigBase is a static type and it does not support the parameter, + // an exception is thrown if not ignore_nonexistent. + ConfigOption *my_opt = this->option(opt_key, true); + if (my_opt == nullptr) + { + // opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def(). + // This is only possible if other is of DynamicConfig type. + if (auto n = opt_key.find('#'); n != std::string::npos) + { + auto opt_key2 = opt_key.substr(0, n); + auto my_opt2 = dynamic_cast(this->option(opt_key2)); + auto other_opt = other.option(opt_key2); + if (my_opt2 == nullptr && other_opt) + { + my_opt2 = dynamic_cast(this->option(opt_key2, true)); + if (my_opt2->empty()) + { + my_opt2->resize(1, other_opt); + } + } + if (my_opt2) + { + int index = std::atoi(opt_key.c_str() + n + 1); + if (other_opt) + my_opt2->set_at(other_opt, index, index); + continue; } - } - if (my_opt2) { - int index = std::atoi(opt_key.c_str() + n + 1); - if (other_opt) - my_opt2->set_at(other_opt, index, index); - continue; } + if (ignore_nonexistent) + continue; + throw UnknownOptionException(opt_key); } - if (ignore_nonexistent) - continue; - throw UnknownOptionException(opt_key); + const ConfigOption *other_opt = other.option(opt_key); + if (other_opt == nullptr) + { + // The key was not found in the source config, therefore it will not be initialized! + // printf("Not found, therefore not initialized: %s\n", opt_key.c_str()); + } + else + my_opt->set(other_opt); } - const ConfigOption *other_opt = other.option(opt_key); - if (other_opt == nullptr) { - // The key was not found in the source config, therefore it will not be initialized! -// printf("Not found, therefore not initialized: %s\n", opt_key.c_str()); - } else - my_opt->set(other_opt); } -} -// Are the two configs equal? Ignoring options not present in both configs. -//BBS: add skipped keys logic -bool ConfigBase::equals(const ConfigBase &other, const std::set* skipped_keys) const -{ - for (const t_config_option_key &opt_key : this->keys()) { - if (skipped_keys && (skipped_keys->count(opt_key) != 0)) - continue; - const ConfigOption *this_opt = this->option(opt_key); - const ConfigOption *other_opt = other.option(opt_key); - if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) - return false; + // Are the two configs equal? Ignoring options not present in both configs. + // BBS: add skipped keys logic + bool ConfigBase::equals(const ConfigBase &other, const std::set *skipped_keys) const + { + for (const t_config_option_key &opt_key : this->keys()) + { + if (skipped_keys && (skipped_keys->count(opt_key) != 0)) + continue; + const ConfigOption *this_opt = this->option(opt_key); + const ConfigOption *other_opt = other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) + return false; + } + return true; } - return true; -} -// Returns options differing in the two configs, ignoring options not present in both configs. -t_config_option_keys ConfigBase::diff(const ConfigBase &other) const -{ - t_config_option_keys diff; - for (const t_config_option_key &opt_key : this->keys()) { - const ConfigOption *this_opt = this->option(opt_key); - const ConfigOption *other_opt = other.option(opt_key); - if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) - diff.emplace_back(opt_key); + // Returns options differing in the two configs, ignoring options not present in both configs. + t_config_option_keys ConfigBase::diff(const ConfigBase &other) const + { + t_config_option_keys diff; + for (const t_config_option_key &opt_key : this->keys()) + { + const ConfigOption *this_opt = this->option(opt_key); + const ConfigOption *other_opt = other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) + diff.emplace_back(opt_key); + } + return diff; } - return diff; -} -// Returns options being equal in the two configs, ignoring options not present in both configs. -t_config_option_keys ConfigBase::equal(const ConfigBase &other) const -{ - t_config_option_keys equal; - for (const t_config_option_key &opt_key : this->keys()) { - const ConfigOption *this_opt = this->option(opt_key); - const ConfigOption *other_opt = other.option(opt_key); - if (this_opt != nullptr && other_opt != nullptr && *this_opt == *other_opt) - equal.emplace_back(opt_key); + // Returns options being equal in the two configs, ignoring options not present in both configs. + t_config_option_keys ConfigBase::equal(const ConfigBase &other) const + { + t_config_option_keys equal; + for (const t_config_option_key &opt_key : this->keys()) + { + const ConfigOption *this_opt = this->option(opt_key); + const ConfigOption *other_opt = other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt == *other_opt) + equal.emplace_back(opt_key); + } + return equal; } - return equal; -} - -std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const -{ - const ConfigOption* opt = this->option(opt_key); - assert(opt != nullptr); - return opt->serialize(); -} -void ConfigBase::set(const std::string &opt_key, int value, bool create) -{ - ConfigOption *opt = this->option_throw(opt_key, create); - switch (opt->type()) { - case coInt: static_cast(opt)->value = value; break; - case coFloat: static_cast(opt)->value = value; break; - case coFloatOrPercent: static_cast(opt)->value = value; static_cast(opt)->percent = false; break; - case coString: static_cast(opt)->value = std::to_string(value); break; - default: throw BadOptionTypeException("Configbase::set() - conversion from int not possible"); + std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const + { + const ConfigOption *opt = this->option(opt_key); + assert(opt != nullptr); + return opt->serialize(); } -} -void ConfigBase::set(const std::string &opt_key, double value, bool create) -{ - ConfigOption *opt = this->option_throw(opt_key, create); - switch (opt->type()) { - case coFloat: static_cast(opt)->value = value; break; - case coFloatOrPercent: static_cast(opt)->value = value; static_cast(opt)->percent = false; break; - case coString: static_cast(opt)->value = float_to_string_decimal_point(value); break; - default: throw BadOptionTypeException("Configbase::set() - conversion from float not possible"); + void ConfigBase::set(const std::string &opt_key, int value, bool create) + { + ConfigOption *opt = this->option_throw(opt_key, create); + switch (opt->type()) + { + case coInt: + static_cast(opt)->value = value; + break; + case coFloat: + static_cast(opt)->value = value; + break; + case coFloatOrPercent: + static_cast(opt)->value = value; + static_cast(opt)->percent = false; + break; + case coString: + static_cast(opt)->value = std::to_string(value); + break; + default: + throw BadOptionTypeException("Configbase::set() - conversion from int not possible"); + } } -} -bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append) -{ - t_config_option_key opt_key = opt_key_src; - std::string value = value_src; - // Both opt_key and value may be modified by handle_legacy(). - // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). - this->handle_legacy(opt_key, value); - if (opt_key.empty()) { - // Ignore the option. - //BBS: record these options, keep only one repeated opt_key - auto iter = std::find(substitutions_ctxt.unrecogized_keys.begin(), substitutions_ctxt.unrecogized_keys.end(), opt_key_src); - if (iter == substitutions_ctxt.unrecogized_keys.end()) - substitutions_ctxt.unrecogized_keys.push_back(opt_key_src); - return true; + void ConfigBase::set(const std::string &opt_key, double value, bool create) + { + ConfigOption *opt = this->option_throw(opt_key, create); + switch (opt->type()) + { + case coFloat: + static_cast(opt)->value = value; + break; + case coFloatOrPercent: + static_cast(opt)->value = value; + static_cast(opt)->percent = false; + break; + case coString: + static_cast(opt)->value = float_to_string_decimal_point(value); + break; + default: + throw BadOptionTypeException("Configbase::set() - conversion from float not possible"); + } } - return this->set_deserialize_raw(opt_key, value, substitutions_ctxt, append); -} - -void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append) -{ - if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append)) - throw BadOptionValueException(format("Invalid value provided for parameter %1%: %2%", opt_key_src, value_src)); -} - -void ConfigBase::set_deserialize(std::initializer_list items, ConfigSubstitutionContext& substitutions_ctxt) -{ - for (const SetDeserializeItem &item : items) - this->set_deserialize(item.opt_key, item.opt_value, substitutions_ctxt, item.append); -} -bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, ConfigSubstitutionContext& substitutions_ctxt, bool append) -{ - t_config_option_key opt_key = opt_key_src; - // Try to deserialize the option by its name. - const ConfigDef *def = this->def(); - if (def == nullptr) - throw NoDefinitionException(opt_key); - const ConfigOptionDef *optdef = def->get(opt_key); - if (optdef == nullptr) { - // If we didn't find an option, look for any other option having this as an alias. - for (const auto &opt : def->options) { - for (const t_config_option_key &opt_key2 : opt.second.aliases) { - if (opt_key2 == opt_key) { - opt_key = opt.first; - optdef = &opt.second; - break; - } - } - if (optdef != nullptr) - break; + bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext &substitutions_ctxt, bool append) + { + t_config_option_key opt_key = opt_key_src; + std::string value = value_src; + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). + this->handle_legacy(opt_key, value); + if (opt_key.empty()) + { + // Ignore the option. + // BBS: record these options, keep only one repeated opt_key + auto iter = std::find(substitutions_ctxt.unrecogized_keys.begin(), substitutions_ctxt.unrecogized_keys.end(), opt_key_src); + if (iter == substitutions_ctxt.unrecogized_keys.end()) + substitutions_ctxt.unrecogized_keys.push_back(opt_key_src); + return true; } - if (optdef == nullptr) - throw UnknownOptionException(opt_key); + return this->set_deserialize_raw(opt_key, value, substitutions_ctxt, append); } - if (! optdef->shortcut.empty()) { - // Aliasing for example "solid_layers" to "top_shell_layers" and "bottom_shell_layers". - for (const t_config_option_key &shortcut : optdef->shortcut) - // Recursive call. - if (! this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append)) - return false; - return true; + void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext &substitutions_ctxt, bool append) + { + if (!this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append)) + throw BadOptionValueException(format("Invalid value provided for parameter %1%: %2%", opt_key_src, value_src)); } - ConfigOption *opt = this->option(opt_key, true); - assert(opt != nullptr); - bool success = false; - bool substituted = false; - if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) { - //FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release. - bool nullable = opt->nullable(); - ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse; - if (optdef->default_value) { - // Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value. - assert(dynamic_cast*>(optdef->default_value.get())); - auto &values = static_cast*>(optdef->default_value.get())->values; - if (values.size() == 1 && values.front() == 1) - default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue; - } - auto result = nullable ? - static_cast(opt)->deserialize_with_substitutions(value, append, default_value) : - static_cast(opt)->deserialize_with_substitutions(value, append, default_value); - success = result != ConfigHelpers::DeserializationResult::Failed; - substituted = result == ConfigHelpers::DeserializationResult::Substituted; - } else { - //bool test = (opt_key == "filament_end_gcode"); - success = opt->deserialize(value, append); - if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable && - // Only allow substitutions of an enum value by another enum value or a boolean value with an enum value. - // That means, we expect enum values being added in the future and possibly booleans being converted to enums. - (optdef->type == coEnum || optdef->type == coEnums || optdef->type == coBool) /*&& ConfigHelpers::looks_like_enum_value(value)*/) { - // Deserialize failed, try to substitute with a default value. - //assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent); - if (optdef->type == coBool) - static_cast(opt)->value = ConfigHelpers::enum_looks_like_true_value(value); - else - // Just use the default of the option. - opt->set(optdef->default_value.get()); - success = true; - substituted = true; - } - } - - if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || - substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) { - // Log the substitution. - ConfigSubstitution config_substitution; - config_substitution.opt_def = optdef; - config_substitution.old_value = value; - config_substitution.new_value = ConfigOptionUniquePtr(opt->clone()); - substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution)); + void ConfigBase::set_deserialize(std::initializer_list items, ConfigSubstitutionContext &substitutions_ctxt) + { + for (const SetDeserializeItem &item : items) + this->set_deserialize(item.opt_key, item.opt_value, substitutions_ctxt, item.append); } - return success; -} -double ConfigBase::get_abs_value_at(const t_config_option_key &opt_key, size_t index) const -{ - const ConfigOption *raw_opt = this->option(opt_key); - assert(raw_opt != nullptr); - if (raw_opt->type() == coFloats) { - return static_cast(raw_opt)->get_at(index); - } - if (raw_opt->type() == coFloatsOrPercents) { + bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, ConfigSubstitutionContext &substitutions_ctxt, bool append) + { + t_config_option_key opt_key = opt_key_src; + // Try to deserialize the option by its name. 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 (def == nullptr) + throw NoDefinitionException(opt_key); + const ConfigOptionDef *optdef = def->get(opt_key); + if (optdef == nullptr) + { + // If we didn't find an option, look for any other option having this as an alias. + for (const auto &opt : def->options) + { + for (const t_config_option_key &opt_key2 : opt.second.aliases) + { + if (opt_key2 == opt_key) + { + opt_key = opt.first; + optdef = &opt.second; + break; + } + } + if (optdef != nullptr) + break; + } + if (optdef == nullptr) + throw UnknownOptionException(opt_key); + } - if (opt_def->ratio_over.empty()) { - return 0; - } else { - const ConfigOption *ratio_opt = this->option(opt_def->ratio_over); - assert(ratio_opt->type() == coFloats); - const ConfigOptionFloats *ratio_values = static_cast(ratio_opt); - return static_cast(raw_opt)->get_at(index).get_abs_value(ratio_values->get_at(index)); + if (!optdef->shortcut.empty()) + { + // Aliasing for example "solid_layers" to "top_shell_layers" and "bottom_shell_layers". + for (const t_config_option_key &shortcut : optdef->shortcut) + // Recursive call. + if (!this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append)) + return false; + return true; } - } - throw ConfigurationError("ConfigBase::get_abs_value_at(): Not a valid option type for get_abs_value_at()"); -} + ConfigOption *opt = this->option(opt_key, true); + assert(opt != nullptr); + bool success = false; + bool substituted = false; + if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) + { + // FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release. + bool nullable = opt->nullable(); + ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse; + if (optdef->default_value) + { + // Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value. + assert(dynamic_cast *>(optdef->default_value.get())); + auto &values = static_cast *>(optdef->default_value.get())->values; + if (values.size() == 1 && values.front() == 1) + default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue; + } + auto result = nullable ? static_cast(opt)->deserialize_with_substitutions(value, append, default_value) : static_cast(opt)->deserialize_with_substitutions(value, append, default_value); + success = result != ConfigHelpers::DeserializationResult::Failed; + substituted = result == ConfigHelpers::DeserializationResult::Substituted; + } + else + { + // bool test = (opt_key == "filament_end_gcode"); + success = opt->deserialize(value, append); + if (!success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable && + // Only allow substitutions of an enum value by another enum value or a boolean value with an enum value. + // That means, we expect enum values being added in the future and possibly booleans being converted to enums. + (optdef->type == coEnum || optdef->type == coEnums || optdef->type == coBool) /*&& ConfigHelpers::looks_like_enum_value(value)*/) + { + // Deserialize failed, try to substitute with a default value. + // assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent); + if (optdef->type == coBool) + static_cast(opt)->value = ConfigHelpers::enum_looks_like_true_value(value); + else + // Just use the default of the option. + opt->set(optdef->default_value.get()); + success = true; + substituted = true; + } + } -// Return an absolute value of a possibly relative config variable. -// For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height. -double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const -{ - // Get stored option value. - const ConfigOption *raw_opt = this->option(opt_key); - assert(raw_opt != nullptr); - if (raw_opt->type() == coFloat) - return static_cast(raw_opt)->value; - if (raw_opt->type() == coFloatOrPercent) { - // 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); - // Compute absolute value over the absolute value of the base option. - //FIXME there are some ratio_over chains, which end with empty ratio_with. - // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. - return opt_def->ratio_over.empty() ? 0. : - static_cast(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); + if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || + substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) + { + // Log the substitution. + ConfigSubstitution config_substitution; + config_substitution.opt_def = optdef; + config_substitution.old_value = value; + config_substitution.new_value = ConfigOptionUniquePtr(opt->clone()); + substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution)); + } + return success; } - std::string err_info = "ConfigBase::get_abs_value(): Not a valid option type for get_abs_value(), parameter : " + opt_key; - throw ConfigurationError(err_info); -} -// Return an absolute value of a possibly relative config variable. -// For example, return absolute infill extrusion width, either from an absolute value, or relative to a provided value. -double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) const -{ - // Get stored option value. - const ConfigOption *raw_opt = this->option(opt_key); - assert(raw_opt != nullptr); - if (raw_opt->type() != coFloatOrPercent) - throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); - // Compute absolute value. - return static_cast(raw_opt)->get_abs_value(ratio_over); -} + double ConfigBase::get_abs_value_at(const t_config_option_key &opt_key, size_t index) const + { + const ConfigOption *raw_opt = this->option(opt_key); + assert(raw_opt != nullptr); + if (raw_opt->type() == coFloats) + { + return static_cast(raw_opt)->get_at(index); + } + if (raw_opt->type() == coFloatsOrPercents) + { + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(opt_key); + const ConfigOptionDef *opt_def = def->get(opt_key); + assert(opt_def != nullptr); -void ConfigBase::setenv_() const -{ - t_config_option_keys opt_keys = this->keys(); - for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) { - // prepend the SLIC3R_ prefix - std::ostringstream ss; - ss << "SLIC3R_"; - ss << *it; - std::string envname = ss.str(); - - // capitalize environment variable name - for (size_t i = 0; i < envname.size(); ++i) - envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i]; - - boost::nowide::setenv(envname.c_str(), this->opt_serialize(*it).c_str(), 1); + if (opt_def->ratio_over.empty()) + { + return 0; + } + else + { + const ConfigOption *ratio_opt = this->option(opt_def->ratio_over); + assert(ratio_opt->type() == coFloats); + const ConfigOptionFloats *ratio_values = static_cast(ratio_opt); + return static_cast(raw_opt)->get_at(index).get_abs_value(ratio_values->get_at(index)); + } + } + + throw ConfigurationError("ConfigBase::get_abs_value_at(): Not a valid option type for get_abs_value_at()"); } -} -//BBS -ConfigSubstitutions ConfigBase::load_string_map(std::map& key_values, ForwardCompatibilitySubstitutionRule compatibility_rule) -{ - CNumericLocalesSetter locales_setter; - - ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); - std::map::iterator it; - for (it = key_values.begin(); it != key_values.end(); it++) { - try { - t_config_option_key opt_key = it->first; - this->set_deserialize(opt_key, it->second, substitutions_ctxt); - } - catch (UnknownOptionException& /* e */) { - // ignore + // Return an absolute value of a possibly relative config variable. + // For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height. + double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const + { + // Get stored option value. + const ConfigOption *raw_opt = this->option(opt_key); + assert(raw_opt != nullptr); + if (raw_opt->type() == coFloat) + return static_cast(raw_opt)->value; + if (raw_opt->type() == coFloatOrPercent) + { + // 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); + // Compute absolute value over the absolute value of the base option. + // FIXME there are some ratio_over chains, which end with empty ratio_with. + // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. + return opt_def->ratio_over.empty() ? 0. : static_cast(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } + std::string err_info = "ConfigBase::get_abs_value(): Not a valid option type for get_abs_value(), parameter : " + opt_key; + throw ConfigurationError(err_info); } - return std::move(substitutions_ctxt.substitutions); -} -//BBS: add json support -ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) -{ - std::map key_values; - if (is_gcode_file(file)) - return this->load_from_gcode_file(file, compatibility_rule); - else if (is_json_file(file)) { - std::string reason; - return this->load_from_json(file, compatibility_rule, key_values, reason); - } - else { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "unsupported format for config file" << file; - return ConfigSubstitutions(); - //return this->load_from_ini(file, compatibility_rule); + // Return an absolute value of a possibly relative config variable. + // For example, return absolute infill extrusion width, either from an absolute value, or relative to a provided value. + double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) const + { + // Get stored option value. + const ConfigOption *raw_opt = this->option(opt_key); + assert(raw_opt != nullptr); + if (raw_opt->type() != coFloatOrPercent) + throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); + // Compute absolute value. + return static_cast(raw_opt)->get_abs_value(ratio_over); } -} -//BBS: add json support -ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule, std::map& key_values, std::string& reason) -{ - int ret = 0; - ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); + void ConfigBase::setenv_() const + { + t_config_option_keys opt_keys = this->keys(); + for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) + { + // prepend the SLIC3R_ prefix + std::ostringstream ss; + ss << "SLIC3R_"; + ss << *it; + std::string envname = ss.str(); - ret = load_from_json(file, substitutions_ctxt, true, key_values, reason); - return std::move(substitutions_ctxt.substitutions); -} + // capitalize environment variable name + for (size_t i = 0; i < envname.size(); ++i) + envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i] - ('a' - 'A') : envname[i]; -int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map& key_values, std::string& reason) -{ - if (!boost::filesystem::exists(file)) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("%1% file not exist.") % file; - return -1; + boost::nowide::setenv(envname.c_str(), this->opt_serialize(*it).c_str(), 1); + } } - json j; - std::list different_settings_append; - std::string new_support_style; - std::string is_infill_first; - std::string get_wall_sequence; - bool is_project_settings = false; - - CNumericLocalesSetter locales_setter; - - std::function parse_str_arr = [&parse_str_arr](const json::const_iterator& it, const char single_sep,const char array_sep,const bool escape_string_style,std::string& value_str)->bool { - // must have consistent type name - std::string consistent_type; - for (auto iter = it.value().begin(); iter != it.value().end(); ++iter) { - if (consistent_type.empty()) - consistent_type = iter.value().type_name(); - else { - if (consistent_type != iter.value().type_name()) - return false; + + // BBS + ConfigSubstitutions ConfigBase::load_string_map(std::map &key_values, ForwardCompatibilitySubstitutionRule compatibility_rule) + { + CNumericLocalesSetter locales_setter; + + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); + std::map::iterator it; + for (it = key_values.begin(); it != key_values.end(); it++) + { + try + { + t_config_option_key opt_key = it->first; + this->set_deserialize(opt_key, it->second, substitutions_ctxt); + } + catch (UnknownOptionException & /* e */) + { + // ignore } } + return std::move(substitutions_ctxt.substitutions); + } + + // BBS: add json support + ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) + { + std::map key_values; + if (is_gcode_file(file)) + return this->load_from_gcode_file(file, compatibility_rule); + else if (is_json_file(file)) + { + std::string reason; + return this->load_from_json(file, compatibility_rule, key_values, reason); + } + else + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "unsupported format for config file" << file; + return ConfigSubstitutions(); + // return this->load_from_ini(file, compatibility_rule); + } + } + + // BBS: add json support + ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule, std::map &key_values, std::string &reason) + { + int ret = 0; + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); + + ret = load_from_json(file, substitutions_ctxt, true, key_values, reason); + return std::move(substitutions_ctxt.substitutions); + } + + int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext &substitution_context, bool load_inherits_to_config, std::map &key_values, std::string &reason) + { + if (!boost::filesystem::exists(file)) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("%1% file not exist.") % file; + return -1; + } + json j; + std::list different_settings_append; + std::string new_support_style; + std::string is_outer_second; + std::string is_infill_first; + std::string get_wall_sequence; + bool is_project_settings = false; + + CNumericLocalesSetter locales_setter; - bool first = true; - for (auto iter = it.value().begin(); iter != it.value().end(); iter++) { - if (iter.value().is_array()) { - if (!first) - value_str += array_sep; + std::function parse_str_arr = [&parse_str_arr](const json::const_iterator &it, const char single_sep, const char array_sep, const bool escape_string_style, std::string &value_str) -> bool + { + // must have consistent type name + std::string consistent_type; + for (auto iter = it.value().begin(); iter != it.value().end(); ++iter) + { + if (consistent_type.empty()) + consistent_type = iter.value().type_name(); else - first = false; - bool success = parse_str_arr(iter, single_sep, array_sep,escape_string_style, value_str); - if (!success) - return false; + { + if (consistent_type != iter.value().type_name()) + return false; + } } - else if (iter.value().is_string()) { - if (!first) - value_str += single_sep; + + bool first = true; + for (auto iter = it.value().begin(); iter != it.value().end(); iter++) + { + if (iter.value().is_array()) + { + if (!first) + value_str += array_sep; + else + first = false; + bool success = parse_str_arr(iter, single_sep, array_sep, escape_string_style, value_str); + if (!success) + return false; + } + else if (iter.value().is_string()) + { + if (!first) + value_str += single_sep; + else + first = false; + if (!escape_string_style) + value_str += iter.value(); + else + { + value_str += "\""; + value_str += escape_string_cstyle(iter.value()); + value_str += "\""; + } + } else - first = false; - if (!escape_string_style) - value_str += iter.value(); - else { - value_str += "\""; - value_str += escape_string_cstyle(iter.value()); - value_str += "\""; + { + // should not happen + return false; } } - else { - //should not happen - return false; - } - } - return true; + return true; }; - try { - boost::nowide::ifstream ifs(file); - ifs >> j; - ifs.close(); + try + { + boost::nowide::ifstream ifs(file); + ifs >> j; + ifs.close(); - const ConfigDef* config_def = this->def(); - if (config_def == nullptr) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no config defs!"; - return -1; - } - //parse the json elements - for (auto it = j.begin(); it != j.end(); it++) { - if (boost::iequals(it.key(),BBL_JSON_KEY_VERSION)) { - key_values.emplace(BBL_JSON_KEY_VERSION, it.value()); - } - else if (boost::iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) { - //skip it - } - else if (boost::iequals(it.key(), BBL_JSON_KEY_NAME)) { - key_values.emplace(BBL_JSON_KEY_NAME, it.value()); - if (it.value() == "project_settings") - is_project_settings = true; - } - else if (boost::iequals(it.key(), BBL_JSON_KEY_URL)) { - key_values.emplace(BBL_JSON_KEY_URL, it.value()); - } - else if (boost::iequals(it.key(), BBL_JSON_KEY_TYPE)) { - key_values.emplace(BBL_JSON_KEY_TYPE, it.value()); - } - else if (boost::iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) { - key_values.emplace(BBL_JSON_KEY_SETTING_ID, it.value()); - } - else if (boost::iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) { - key_values.emplace(BBL_JSON_KEY_FILAMENT_ID, it.value()); - } - else if (boost::iequals(it.key(), BBL_JSON_KEY_FROM)) { - key_values.emplace(BBL_JSON_KEY_FROM, it.value()); - } - else if (boost::iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) { - key_values.emplace(BBL_JSON_KEY_DESCRIPTION, it.value()); - } - else if (boost::iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) { - key_values.emplace(BBL_JSON_KEY_INSTANTIATION, it.value()); - } - else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INHERITS)) { - key_values.emplace(BBL_JSON_KEY_INHERITS, it.value()); - } - else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INCLUDES)) { - key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump()); + const ConfigDef *config_def = this->def(); + if (config_def == nullptr) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no config defs!"; + return -1; } - else { - t_config_option_key opt_key = it.key(); - std::string value_str; - - if (it.value().is_string()) { - //bool test1 = (it.key() == std::string("end_gcode")); - this->set_deserialize(opt_key, it.value(), substitution_context); - //some logic for special values - if (opt_key == "support_type") { - //std::string new_value = dynamic_cast(this->option(opt_key))->value; - if (it.value() == "hybrid(auto)") { - different_settings_append.push_back(opt_key); - different_settings_append.push_back("support_style"); - new_support_style = "tree_hybrid"; - } - } else if (opt_key == "wall_infill_order") { - //BBS: check wall_infill order to decide if it be different and append to diff_setting_append - if (it.value() == "outer wall/inner wall/infill" || it.value() == "infill/outer wall/inner wall" || it.value() == "inner-outer-inner wall/infill") { - get_wall_sequence = "wall_seq_diff_to_system"; + // parse the json elements + for (auto it = j.begin(); it != j.end(); it++) + { + if (boost::iequals(it.key(), BBL_JSON_KEY_VERSION)) + { + key_values.emplace(BBL_JSON_KEY_VERSION, it.value()); + } + else if (boost::iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) + { + // skip it + } + else if (boost::iequals(it.key(), BBL_JSON_KEY_NAME)) + { + key_values.emplace(BBL_JSON_KEY_NAME, it.value()); + if (it.value() == "project_settings") + is_project_settings = true; + } + else if (boost::iequals(it.key(), BBL_JSON_KEY_URL)) + { + key_values.emplace(BBL_JSON_KEY_URL, it.value()); + } + else if (boost::iequals(it.key(), BBL_JSON_KEY_TYPE)) + { + key_values.emplace(BBL_JSON_KEY_TYPE, it.value()); + } + else if (boost::iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) + { + key_values.emplace(BBL_JSON_KEY_SETTING_ID, it.value()); + } + else if (boost::iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) + { + key_values.emplace(BBL_JSON_KEY_FILAMENT_ID, it.value()); + } + else if (boost::iequals(it.key(), BBL_JSON_KEY_FROM)) + { + key_values.emplace(BBL_JSON_KEY_FROM, it.value()); + } + else if (boost::iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) + { + key_values.emplace(BBL_JSON_KEY_DESCRIPTION, it.value()); + } + else if (boost::iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) + { + key_values.emplace(BBL_JSON_KEY_INSTANTIATION, it.value()); + } + else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INHERITS)) + { + key_values.emplace(BBL_JSON_KEY_INHERITS, it.value()); + } + else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INCLUDES)) + { + key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump()); + } + else + { + t_config_option_key opt_key = it.key(); + std::string value_str; + + if (it.value().is_string()) + { + // bool test1 = (it.key() == std::string("end_gcode")); + this->set_deserialize(opt_key, it.value(), substitution_context); + // some logic for special values + if (opt_key == "support_type") + { + // std::string new_value = dynamic_cast(this->option(opt_key))->value; + if (it.value() == "hybrid(auto)") + { + different_settings_append.push_back(opt_key); + different_settings_append.push_back("support_style"); + new_support_style = "tree_hybrid"; + } } + else if (opt_key == "wall_infill_order") + { + // BBS: check wall_infill order to decide if it be different and append to diff_setting_append + if (it.value() == "outer wall/inner wall/infill" || it.value() == "infill/outer wall/inner wall" || it.value() == "inner-outer-inner wall/infill" || it.value() == "infill/outer wall/inner/inner wall" || it.value() == "inner-outer-inner/inner wall/infill") + { + get_wall_sequence = "wall_seq_diff_to_system"; + } + + if (it.value() == "infill/outer wall/inner wall" || it.value() == "infill/inner wall/outer wall") + { + different_settings_append.push_back("is_infill_first"); + is_infill_first = "true"; - if (it.value() == "infill/outer wall/inner wall" || it.value() == "infill/inner wall/outer wall") { - different_settings_append.push_back("is_infill_first"); - is_infill_first = "true"; + // different_settings_append.push_back("is_outer_second"); + // is_outer_second = "true"; + } } } - } - else if (it.value().is_array()) { - t_config_option_key opt_key_src = opt_key; - this->handle_legacy(opt_key, value_str); - if (opt_key.empty()) { - //BBS: record these options - substitution_context.unrecogized_keys.push_back(opt_key_src); - continue; - } - bool valid = true, first = true; - const ConfigOptionDef* optdef = config_def->get(opt_key); - if (optdef == nullptr) { - // If we didn't find an option, look for any other option having this as an alias. - for (const auto& opt : config_def->options) { - for (const t_config_option_key& opt_key2 : opt.second.aliases) { - if (opt_key2 == opt_key) { - opt_key = opt.first; - optdef = &opt.second; - break; + else if (it.value().is_array()) + { + t_config_option_key opt_key_src = opt_key; + this->handle_legacy(opt_key, value_str); + if (opt_key.empty()) + { + // BBS: record these options + substitution_context.unrecogized_keys.push_back(opt_key_src); + continue; + } + bool valid = true, first = true; + const ConfigOptionDef *optdef = config_def->get(opt_key); + if (optdef == nullptr) + { + // If we didn't find an option, look for any other option having this as an alias. + for (const auto &opt : config_def->options) + { + for (const t_config_option_key &opt_key2 : opt.second.aliases) + { + if (opt_key2 == opt_key) + { + opt_key = opt.first; + optdef = &opt.second; + break; + } } + if (optdef != nullptr) + break; } - if (optdef != nullptr) + } + + char single_sep = ','; + char array_sep = '#'; // currenty not used + bool escape_string_type = false; + if (optdef) + { + switch (optdef->type) + { + case coStrings: + escape_string_type = true; + single_sep = ';'; + break; + case coPointsGroups: + single_sep = '#'; break; + default: + break; + } } - } - char single_sep = ','; - char array_sep = '#'; // currenty not used - bool escape_string_type = false; - if (optdef) { - switch (optdef->type) + // BBS: we only support 2 depth array + valid = parse_str_arr(it, single_sep, array_sep, escape_string_type, value_str); + if (!valid) { - case coStrings: - escape_string_type = true; - single_sep = ';'; - break; - case coPointsGroups: - single_sep = '#'; - break; - default: + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file << " error, invalid json array for " << it.key(); break; } + if (valid) + this->set_deserialize(opt_key, value_str, substitution_context); } - - // BBS: we only support 2 depth array - valid = parse_str_arr(it, single_sep, array_sep,escape_string_type, value_str); - if (!valid) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file << " error, invalid json array for " << it.key(); - break; + else + { + // should not happen + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file << " error, invalid json type for " << it.key(); } - if (valid) - this->set_deserialize(opt_key, value_str, substitution_context); } - else { - //should not happen - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<* opt = this->option>("support_style", true); - opt->value = smsTreeHybrid; } + if (!different_settings_append.empty()) + { + if (!new_support_style.empty()) + { + ConfigOptionEnum *opt = this->option>("support_style", true); + opt->value = smsTreeHybrid; + } - if (!is_infill_first.empty()) { - ConfigOptionBool *opt = this->option("is_infill_first", true); - opt->value = true; - } + if (!is_outer_second.empty()) + { + ConfigOptionBool *opt = this->option("is_outer_second", true); + opt->value = true; + } - if (is_project_settings) { - std::vector& different_settings = this->option("different_settings_to_system", true)->values; - size_t size = different_settings.size(); - if (size == 0) { - size = this->option("filament_settings_id")->values.size() + 2; - different_settings.resize(size); + if (!is_infill_first.empty()) + { + ConfigOptionBool *opt = this->option("is_infill_first", true); + opt->value = true; } - std::vector is_first(size, false); - std::vector> original_diffs(size); - for (int index = 0; index < size; index++) + if (is_project_settings) { - if (different_settings[index].empty()) { - is_first[index] = true; + std::vector &different_settings = this->option("different_settings_to_system", true)->values; + size_t size = different_settings.size(); + if (size == 0) + { + size = this->option("filament_settings_id")->values.size() + 2; + different_settings.resize(size); } - else { - // remove unneeded key - if (get_wall_sequence.empty()) { - std::string wall_sqe_string = "wall_sequence"; - int pos=different_settings[index].find(wall_sqe_string); - - if (pos != different_settings[index].npos) { - int erase_len = wall_sqe_string.size(); - if (pos + erase_len < different_settings[index].size() && different_settings[index][pos + erase_len] == ';') - erase_len++; - different_settings[index].erase(pos, erase_len); - } - - } - if (different_settings[index].empty()) { + std::vector is_first(size, false); + std::vector> original_diffs(size); + for (int index = 0; index < size; index++) + { + if (different_settings[index].empty()) + { is_first[index] = true; - continue; } + else + { + // remove unneeded key + if (get_wall_sequence.empty()) + { + std::string wall_sqe_string = "wall_sequence"; + int pos = different_settings[index].find(wall_sqe_string); + + if (pos != different_settings[index].npos) + { + int erase_len = wall_sqe_string.size(); + if (pos + erase_len < different_settings[index].size() && different_settings[index][pos + erase_len] == ';') + erase_len++; + different_settings[index].erase(pos, erase_len); + } + } - Slic3r::unescape_strings_cstyle(different_settings[index], original_diffs[index]); + if (different_settings[index].empty()) + { + is_first[index] = true; + continue; + } + + Slic3r::unescape_strings_cstyle(different_settings[index], original_diffs[index]); + } } - } - for (auto diff_key : different_settings_append) - { - //get the index in the group - int index = 0; - bool need_insert = true; - if (diff_key == "support_type") - index = 0; - else if (diff_key == "support_style") - index = 0; - else if (diff_key == "is_infill_first") - index = 0; - - //check whether exist firstly - if (!original_diffs[index].empty()) { - for (int j = 0; j < original_diffs[index].size(); j++) { - if (original_diffs[index][j] == diff_key) { - need_insert = false; - break; + for (auto diff_key : different_settings_append) + { + // get the index in the group + int index = 0; + bool need_insert = true; + if (diff_key == "support_type") + index = 0; + else if (diff_key == "support_style") + index = 0; + else if (diff_key == "is_outer_second") + index = 0; + else if (diff_key == "is_infill_first") + index = 0; + + // check whether exist firstly + if (!original_diffs[index].empty()) + { + for (int j = 0; j < original_diffs[index].size(); j++) + { + if (original_diffs[index][j] == diff_key) + { + need_insert = false; + break; + } } } - } - if (!need_insert) - continue; + if (!need_insert) + continue; - //insert this key - if (!is_first[index]) - different_settings[index] += ";"; - else - is_first[index] = false; - different_settings[index] += diff_key; + // insert this key + if (!is_first[index]) + different_settings[index] += ";"; + else + is_first[index] = false; + different_settings[index] += diff_key; + } } } + return 0; } - return 0; - } - catch (const std::ifstream::failure &err) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<load(tree, compatibility_rule); + } + catch (const ConfigurationError &e) + { + throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what())); + } } - return -1; -} -ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) -{ - try { + ConfigSubstitutions ConfigBase::load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule) + { boost::property_tree::ptree tree; - boost::nowide::ifstream ifs(file); - boost::property_tree::read_ini(ifs, tree); + std::istringstream iss(data); + boost::property_tree::read_ini(iss, tree); return this->load(tree, compatibility_rule); - } catch (const ConfigurationError &e) { - throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what())); } -} - -ConfigSubstitutions ConfigBase::load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule) -{ - boost::property_tree::ptree tree; - std::istringstream iss(data); - boost::property_tree::read_ini(iss, tree); - return this->load(tree, compatibility_rule); -} - -// Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF. -// Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment). -ConfigSubstitutions ConfigBase::load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule) -{ - // Convert the "data" string into INI format by removing the semi-colons at the start of a line. - // Also the "; generated by PrusaSlicer ..." comment line will be removed. - size_t j = 0; - for (size_t i = 0; i < data.size();) - if (i == 0 || data[i] == '\n') { - // Start of a line. - if (data[i] == '\n') { - // Consume LF, don't keep empty lines. - if (j > 0 && data[j - 1] != '\n') - data[j ++] = data[i]; - ++ i; - } - // Skip all leading spaces; - for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++ i) ; - // Skip the semicolon (comment indicator). - if (i < data.size() && data[i] == ';') - ++ i; - // Skip all leading spaces after semicolon. - for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++ i) ; - if (strncmp(data.data() + i, "generated by ", 13) == 0) { - // Skip the "; generated by ..." line. - for (; i < data.size() && data[i] != '\n'; ++ i); - } - } else if (data[i] == '\r' && i + 1 < data.size() && data[i + 1] == '\n') { - // Skip CR. - ++ i; - } else { - // Consume the rest of the data. - data[j ++] = data[i ++]; - } - data.erase(data.begin() + j, data.end()); - - return this->load_from_ini_string(data, compatibility_rule); -} -ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule) -{ - ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); - for (const boost::property_tree::ptree::value_type &v : tree) { - try { - t_config_option_key opt_key = v.first; - this->set_deserialize(opt_key, v.second.get_value(), substitutions_ctxt); - } catch (UnknownOptionException & /* e */) { - // ignore - } - } - return std::move(substitutions_ctxt.substitutions); -} - -// BBS -static bool is_whitespace(char c) { return c == ' ' || c == '\t'; } -static bool is_end_of_line(char c) { return c == '\r' || c == '\n' || c == 0; } -static bool is_end_of_gcode_line(char c) { return c == ';' || is_end_of_line(c); } -static bool is_end_of_word(char c) { return is_whitespace(c) || is_end_of_gcode_line(c); } - -static const char* skip_word(const char* c) { - for (; !is_end_of_word(*c); ++c) - ; // silence -Wempty-body - return c; -} + // Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF. + // Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment). + ConfigSubstitutions ConfigBase::load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule) + { + // Convert the "data" string into INI format by removing the semi-colons at the start of a line. + // Also the "; generated by PrusaSlicer ..." comment line will be removed. + size_t j = 0; + for (size_t i = 0; i < data.size();) + if (i == 0 || data[i] == '\n') + { + // Start of a line. + if (data[i] == '\n') + { + // Consume LF, don't keep empty lines. + if (j > 0 && data[j - 1] != '\n') + data[j++] = data[i]; + ++i; + } + // Skip all leading spaces; + for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++i) + ; + // Skip the semicolon (comment indicator). + if (i < data.size() && data[i] == ';') + ++i; + // Skip all leading spaces after semicolon. + for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++i) + ; + if (strncmp(data.data() + i, "generated by ", 13) == 0) + { + // Skip the "; generated by ..." line. + for (; i < data.size() && data[i] != '\n'; ++i) + ; + } + } + else if (data[i] == '\r' && i + 1 < data.size() && data[i + 1] == '\n') + { + // Skip CR. + ++i; + } + else + { + // Consume the rest of the data. + data[j++] = data[i++]; + } + data.erase(data.begin() + j, data.end()); -static const char* skip_whitespaces(const char* c) { - for (; is_whitespace(*c); ++c) - ; // silence -Wempty-body - return c; -} + return this->load_from_ini_string(data, compatibility_rule); + } -// Load the config keys from the given string. -size_t ConfigBase::load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions) -{ - if (str == nullptr) - return 0; - - // BBS. Remove line numbers. - std::regex match_pattern("\nN[0-9]* *"); - std::string replace_pattern = "\n"; - char* result = (char*)calloc(strlen(str) + 1, 1); - std::regex_replace(result, str, str + strlen(str), match_pattern, replace_pattern); - - // Walk line by line in reverse until a non-configuration key appears. - const char *data_start = result; - data_start = skip_whitespaces(data_start); - if (std::toupper(*data_start) == 'N') - data_start = skip_word(data_start); - - // boost::nowide::ifstream seems to cook the text data somehow, so less then the 64k of characters may be retrieved. - const char *end = data_start + strlen(data_start); - size_t num_key_value_pairs = 0; - for (;;) { - // Extract next line. - for (--end; end > data_start && (*end == '\r' || *end == '\n'); --end); - if (end == data_start) - break; - const char *start = end ++; - for (; start > data_start && *start != '\r' && *start != '\n'; --start); - if (start == data_start) - break; - // Extracted a line from start to end. Extract the key = value pair. - if (end - (++ start) < 10 || start[0] != ';' || start[1] != ' ') - break; - const char *key = start + 2; - if (!((*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z'))) - // A key must start with a letter. - break; - const char *sep = key; - for (; sep != end && *sep != '='; ++ sep) ; - if (sep == end || sep[-1] != ' ' || sep[1] != ' ') - break; - const char *value = sep + 2; - if (value > end) - break; - const char *key_end = sep - 1; - if (key_end - key < 3) - break; - // The key may contain letters, digits and underscores. - for (const char *c = key; c != key_end; ++ c) - if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '_')) { - key = nullptr; - break; + ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule) + { + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); + for (const boost::property_tree::ptree::value_type &v : tree) + { + try + { + t_config_option_key opt_key = v.first; + this->set_deserialize(opt_key, v.second.get_value(), substitutions_ctxt); + } + catch (UnknownOptionException & /* e */) + { + // ignore } - if (key == nullptr) - break; - try { - config.set_deserialize(std::string(key, key_end), std::string(value, end), substitutions); - ++num_key_value_pairs; - } - catch (UnknownOptionException & /* e */) { - // ignore } - end = start; + return std::move(substitutions_ctxt.substitutions); } // BBS - free(result); - return num_key_value_pairs; -} + static bool is_whitespace(char c) { return c == ' ' || c == '\t'; } + static bool is_end_of_line(char c) { return c == '\r' || c == '\n' || c == 0; } + static bool is_end_of_gcode_line(char c) { return c == ';' || is_end_of_line(c); } + static bool is_end_of_word(char c) { return is_whitespace(c) || is_end_of_gcode_line(c); } -// Reading a config from G-code back to front for performance reasons: We don't want to scan -// hundreds of MB file for a short config block, which we expect to find at the end of the G-code. -class ReverseLineReader -{ -public: - using pos_type = boost::nowide::ifstream::pos_type; + static const char *skip_word(const char *c) + { + for (; !is_end_of_word(*c); ++c) + ; // silence -Wempty-body + return c; + } - // Stop at file_start - ReverseLineReader(boost::nowide::ifstream &ifs, pos_type file_start) : m_ifs(ifs), m_file_start(file_start) + static const char *skip_whitespaces(const char *c) { - m_ifs.seekg(0, m_ifs.end); - m_file_pos = m_ifs.tellg(); - m_block.assign(m_block_size, 0); + for (; is_whitespace(*c); ++c) + ; // silence -Wempty-body + return c; } - bool getline(std::string &out) { - out.clear(); - for (;;) { - if (m_block_len == 0) { - // Read the next block. - m_block_len = size_t(std::min(m_block_size, m_file_pos - m_file_start)); - if (m_block_len == 0) - return false; - m_file_pos -= m_block_len; - m_ifs.seekg(m_file_pos, m_ifs.beg); - if (! m_ifs.read(m_block.data(), m_block_len)) - return false; - } + // Load the config keys from the given string. + size_t ConfigBase::load_from_gcode_string_legacy(ConfigBase &config, const char *str, ConfigSubstitutionContext &substitutions) + { + if (str == nullptr) + return 0; - assert(m_block_len > 0); - // Non-empty buffer. Find another LF. - int i = int(m_block_len) - 1; - for (; i >= 0; -- i) - if (m_block[i] == '\n') + // BBS. Remove line numbers. + std::regex match_pattern("\nN[0-9]* *"); + std::string replace_pattern = "\n"; + char *result = (char *)calloc(strlen(str) + 1, 1); + std::regex_replace(result, str, str + strlen(str), match_pattern, replace_pattern); + + // Walk line by line in reverse until a non-configuration key appears. + const char *data_start = result; + data_start = skip_whitespaces(data_start); + if (std::toupper(*data_start) == 'N') + data_start = skip_word(data_start); + + // boost::nowide::ifstream seems to cook the text data somehow, so less then the 64k of characters may be retrieved. + const char *end = data_start + strlen(data_start); + size_t num_key_value_pairs = 0; + for (;;) + { + // Extract next line. + for (--end; end > data_start && (*end == '\r' || *end == '\n'); --end) + ; + if (end == data_start) + break; + const char *start = end++; + for (; start > data_start && *start != '\r' && *start != '\n'; --start) + ; + if (start == data_start) + break; + // Extracted a line from start to end. Extract the key = value pair. + if (end - (++start) < 10 || start[0] != ';' || start[1] != ' ') + break; + const char *key = start + 2; + if (!((*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z'))) + // A key must start with a letter. + break; + const char *sep = key; + for (; sep != end && *sep != '='; ++sep) + ; + if (sep == end || sep[-1] != ' ' || sep[1] != ' ') + break; + const char *value = sep + 2; + if (value > end) + break; + const char *key_end = sep - 1; + if (key_end - key < 3) + break; + // The key may contain letters, digits and underscores. + for (const char *c = key; c != key_end; ++c) + if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '_')) + { + key = nullptr; break; - // i is position of LF or -1 if not found. - if (i == -1) { - // LF not found. Just make a backup of the buffer and continue. - out.insert(out.begin(), m_block.begin(), m_block.begin() + m_block_len); - m_block_len = 0; - } else { - assert(i >= 0); - // Copy new line to the output. It may be empty. - out.insert(out.begin(), m_block.begin() + i + 1, m_block.begin() + m_block_len); - // Block length without the newline. - m_block_len = i; - // Remove CRLF from the end of the block. - if (m_block_len > 0 && m_block[m_block_len - 1] == '\r') - -- m_block_len; - return true; + } + if (key == nullptr) + break; + try + { + config.set_deserialize(std::string(key, key_end), std::string(value, end), substitutions); + ++num_key_value_pairs; + } + catch (UnknownOptionException & /* e */) + { + // ignore } + end = start; } - assert(false); - return false; + + // BBS + free(result); + return num_key_value_pairs; } -private: - boost::nowide::ifstream &m_ifs; - std::vector m_block; - size_t m_block_size = 65536; - size_t m_block_len = 0; - pos_type m_file_start; - pos_type m_file_pos = 0; -}; - -// Load the config keys from the tail of a G-code file. -ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) -{ - // Read a 64k block from the end of the G-code. - boost::nowide::ifstream ifs(file); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": before parse_file %1%") % file.c_str(); - // Look for Slic3r or BambuStudio header. - // Look for the header across the whole file as the G-code may have been extended at the start by a post-processing script or the user. - //BBS - bool has_delimiters = true; + // Reading a config from G-code back to front for performance reasons: We don't want to scan + // hundreds of MB file for a short config block, which we expect to find at the end of the G-code. + class ReverseLineReader { - //BBS - std::string bambuslicer_gcode_header = "; "; - bambuslicer_gcode_header += SLIC3R_APP_NAME; + public: + using pos_type = boost::nowide::ifstream::pos_type; - std::string header; - bool header_found = false; - while (std::getline(ifs, header)) { - // BBS - const char* line_c = skip_whitespaces(header.c_str()); - if (std::toupper(*line_c) == 'N') - line_c = skip_word(line_c); - line_c = skip_whitespaces(line_c); - // BBS - if (strncmp(bambuslicer_gcode_header.c_str(), line_c, strlen(bambuslicer_gcode_header.c_str())) == 0) { - header_found = true; - break; - } + // Stop at file_start + ReverseLineReader(boost::nowide::ifstream &ifs, pos_type file_start) : m_ifs(ifs), m_file_start(file_start) + { + m_ifs.seekg(0, m_ifs.end); + m_file_pos = m_ifs.tellg(); + m_block.assign(m_block_size, 0); } - if (!header_found) { - std::string error_message = "Not a gcode file generated by "; - error_message += SLIC3R_APP_FULL_NAME; - error_message += "."; - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << error_message; - throw Slic3r::RuntimeError(error_message.c_str()); + + bool getline(std::string &out) + { + out.clear(); + for (;;) + { + if (m_block_len == 0) + { + // Read the next block. + m_block_len = size_t(std::min(m_block_size, m_file_pos - m_file_start)); + if (m_block_len == 0) + return false; + m_file_pos -= m_block_len; + m_ifs.seekg(m_file_pos, m_ifs.beg); + if (!m_ifs.read(m_block.data(), m_block_len)) + return false; + } + + assert(m_block_len > 0); + // Non-empty buffer. Find another LF. + int i = int(m_block_len) - 1; + for (; i >= 0; --i) + if (m_block[i] == '\n') + break; + // i is position of LF or -1 if not found. + if (i == -1) + { + // LF not found. Just make a backup of the buffer and continue. + out.insert(out.begin(), m_block.begin(), m_block.begin() + m_block_len); + m_block_len = 0; + } + else + { + assert(i >= 0); + // Copy new line to the output. It may be empty. + out.insert(out.begin(), m_block.begin() + i + 1, m_block.begin() + m_block_len); + // Block length without the newline. + m_block_len = i; + // Remove CRLF from the end of the block. + if (m_block_len > 0 && m_block[m_block_len - 1] == '\r') + --m_block_len; + return true; + } + } + assert(false); + return false; } - } - ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); - size_t key_value_pairs = 0; + private: + boost::nowide::ifstream &m_ifs; + std::vector m_block; + size_t m_block_size = 65536; + size_t m_block_len = 0; + pos_type m_file_start; + pos_type m_file_pos = 0; + }; - if (has_delimiters) + // Load the config keys from the tail of a G-code file. + ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) { - //BBS - // PrusaSlicer starting with 2.4.0-alpha0 delimits the config section stored into G-code with - // ; CONFIG_BLOCK_START - // ... - // ; CONFIG_BLOCK_END - bool begin_found = false; - bool end_found = false; - std::string line; - while (std::getline(ifs, line)) { - if ( boost::starts_with(line, "; CONFIG_BLOCK_START")) { - begin_found = true; - break; + // Read a 64k block from the end of the G-code. + boost::nowide::ifstream ifs(file); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": before parse_file %1%") % file.c_str(); + // Look for Slic3r or BambuStudio header. + // Look for the header across the whole file as the G-code may have been extended at the start by a post-processing script or the user. + // BBS + bool has_delimiters = true; + { + // BBS + std::string bambuslicer_gcode_header = "; "; + bambuslicer_gcode_header += SLIC3R_APP_NAME; + + std::string header; + bool header_found = false; + while (std::getline(ifs, header)) + { + // BBS + const char *line_c = skip_whitespaces(header.c_str()); + if (std::toupper(*line_c) == 'N') + line_c = skip_word(line_c); + line_c = skip_whitespaces(line_c); + // BBS + if (strncmp(bambuslicer_gcode_header.c_str(), line_c, strlen(bambuslicer_gcode_header.c_str())) == 0) + { + header_found = true; + break; + } + } + if (!header_found) + { + std::string error_message = "Not a gcode file generated by "; + error_message += SLIC3R_APP_FULL_NAME; + error_message += "."; + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << error_message; + throw Slic3r::RuntimeError(error_message.c_str()); } } - if (!begin_found) { - //BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << format("Configuration block closing tag \"; CONFIG_BLOCK_START\" not found when reading %1%", file); - throw Slic3r::RuntimeError(format("Config tag \"; CONFIG_BLOCK_START\" not found")); - } - std::string key, value; - while (std::getline(ifs, line)) { - if (boost::starts_with(line, "; CONFIG_BLOCK_END")) { - end_found = true; - break; + + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); + size_t key_value_pairs = 0; + + if (has_delimiters) + { + // BBS + // PrusaSlicer starting with 2.4.0-alpha0 delimits the config section stored into G-code with + // ; CONFIG_BLOCK_START + // ... + // ; CONFIG_BLOCK_END + bool begin_found = false; + bool end_found = false; + std::string line; + while (std::getline(ifs, line)) + { + if (boost::starts_with(line, "; CONFIG_BLOCK_START")) + { + begin_found = true; + break; + } } - // line should be a valid key = value pair. - auto pos = line.find('='); - if (pos != std::string::npos && pos > 1 && line.front() == ';') { - key = line.substr(1, pos - 1); - value = line.substr(pos + 1); - boost::trim(key); - boost::trim(value); - try { - this->set_deserialize(key, value, substitutions_ctxt); - ++ key_value_pairs; - } catch (UnknownOptionException & /* e */) { - // ignore + if (!begin_found) + { + // BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << format("Configuration block closing tag \"; CONFIG_BLOCK_START\" not found when reading %1%", file); + throw Slic3r::RuntimeError(format("Config tag \"; CONFIG_BLOCK_START\" not found")); + } + std::string key, value; + while (std::getline(ifs, line)) + { + if (boost::starts_with(line, "; CONFIG_BLOCK_END")) + { + end_found = true; + break; } + // line should be a valid key = value pair. + auto pos = line.find('='); + if (pos != std::string::npos && pos > 1 && line.front() == ';') + { + key = line.substr(1, pos - 1); + value = line.substr(pos + 1); + boost::trim(key); + boost::trim(value); + try + { + this->set_deserialize(key, value, substitutions_ctxt); + ++key_value_pairs; + } + catch (UnknownOptionException & /* e */) + { + // ignore + } + } + } + if (!end_found) + { + // BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << format("Configuration block opening tag \"; CONFIG_BLOCK_END\" not found when reading %1%", file); + throw Slic3r::RuntimeError(format("Config tag \"; CONFIG_BLOCK_END\" not found")); } } - if (!end_found) { - //BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << format("Configuration block opening tag \"; CONFIG_BLOCK_END\" not found when reading %1%", file); - throw Slic3r::RuntimeError(format("Config tag \"; CONFIG_BLOCK_END\" not found")); + else + { + auto header_end_pos = ifs.tellg(); + // Slicer older than 2.4.0-alpha0 do not emit any delimiter. + // Try a heuristics reading the G-code from back. + ifs.seekg(0, ifs.end); + auto file_length = ifs.tellg(); + auto data_length = std::min(65535, file_length - header_end_pos); + ifs.seekg(file_length - data_length, ifs.beg); + std::vector data(size_t(data_length) + 1, 0); + ifs.read(data.data(), data_length); + ifs.close(); + key_value_pairs = load_from_gcode_string_legacy(*this, data.data(), substitutions_ctxt); } - } - else - { - auto header_end_pos = ifs.tellg(); - // Slicer older than 2.4.0-alpha0 do not emit any delimiter. - // Try a heuristics reading the G-code from back. - ifs.seekg(0, ifs.end); - auto file_length = ifs.tellg(); - auto data_length = std::min(65535, file_length - header_end_pos); - ifs.seekg(file_length - data_length, ifs.beg); - std::vector data(size_t(data_length) + 1, 0); - ifs.read(data.data(), data_length); - ifs.close(); - key_value_pairs = load_from_gcode_string_legacy(*this, data.data(), substitutions_ctxt); - } - if (key_value_pairs < 80) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs); - throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); - } + if (key_value_pairs < 80) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs); + throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); + } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished to parse_file %1%") % file.c_str(); - return std::move(substitutions_ctxt.substitutions); -} + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished to parse_file %1%") % file.c_str(); + return std::move(substitutions_ctxt.substitutions); + } -//BBS: add json support -void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const -{ - json j; - //record the headers - j[BBL_JSON_KEY_VERSION] = version; - j[BBL_JSON_KEY_NAME] = name; - j[BBL_JSON_KEY_FROM] = from; - - //record all the key-values - for (const std::string &opt_key : this->keys()) + // BBS: add json support + void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const { - if (from.compare("project") == 0) { - if (opt_key.compare("print_host") == 0 - || opt_key.compare("print_host_webui") == 0 - || opt_key.compare("printhost_apikey") == 0 - || opt_key.compare("printhost_cafile") == 0 - || opt_key.compare("printhost_user") == 0 - || opt_key.compare("printhost_password") == 0 - || opt_key.compare("printhost_port") == 0 - ) { - continue; + json j; + // record the headers + j[BBL_JSON_KEY_VERSION] = version; + j[BBL_JSON_KEY_NAME] = name; + j[BBL_JSON_KEY_FROM] = from; + + // record all the key-values + for (const std::string &opt_key : this->keys()) + { + if (from.compare("project") == 0) + { + if (opt_key.compare("print_host") == 0 || opt_key.compare("print_host_webui") == 0 || opt_key.compare("printhost_apikey") == 0 || opt_key.compare("printhost_cafile") == 0 || opt_key.compare("printhost_user") == 0 || opt_key.compare("printhost_password") == 0 || opt_key.compare("printhost_port") == 0) + { + continue; + } + } + const ConfigOption *opt = this->option(opt_key); + if (opt->is_scalar()) + { + if (opt->type() == coString) + // keep \n, \r, \t + j[opt_key] = (dynamic_cast(opt))->value; + else + j[opt_key] = opt->serialize(); } - } - const ConfigOption* opt = this->option(opt_key); - if ( opt->is_scalar() ) { - if (opt->type() == coString) - //keep \n, \r, \t - j[opt_key] = (dynamic_cast(opt))->value; else - j[opt_key] = opt->serialize(); - } - else { - const ConfigOptionVectorBase* vec = static_cast(opt); - //if (!vec->empty()) - std::vector string_values = vec->vserialize(); - - /*for (int i = 0; i < string_values.size(); i++) { - std::string string_value = escape_string_cstyle(string_values[i]); - j[opt_key][i] = string_value; - }*/ + const ConfigOptionVectorBase *vec = static_cast(opt); + // if (!vec->empty()) + std::vector string_values = vec->vserialize(); - json j_array(string_values); - j[opt_key] = j_array; + /*for (int i = 0; i < string_values.size(); i++) + { + std::string string_value = escape_string_cstyle(string_values[i]); + j[opt_key][i] = string_value; + }*/ + + json j_array(string_values); + j[opt_key] = j_array; + } } - } - boost::nowide::ofstream c; - c.open(file, std::ios::out | std::ios::trunc); - c << std::setw(4) << j << std::endl; - c.close(); + boost::nowide::ofstream c; + c.open(file, std::ios::out | std::ios::trunc); + c << std::setw(4) << j << std::endl; + c.close(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", saved config to %1%\n") % PathSanitizer::sanitize(file); -} + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", saved config to %1%\n") % PathSanitizer::sanitize(file); + } -void ConfigBase::save(const std::string &file) const -{ - boost::nowide::ofstream c; - c.open(file, std::ios::out | std::ios::trunc); - c << "# " << Slic3r::header_slic3r_generated() << std::endl; - for (const std::string &opt_key : this->keys()) - c << opt_key << " = " << this->opt_serialize(opt_key) << std::endl; - c.close(); - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file; -} + void ConfigBase::save(const std::string &file) const + { + boost::nowide::ofstream c; + c.open(file, std::ios::out | std::ios::trunc); + c << "# " << Slic3r::header_slic3r_generated() << std::endl; + for (const std::string &opt_key : this->keys()) + c << opt_key << " = " << this->opt_serialize(opt_key) << std::endl; + c.close(); + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", saved config to %1%\n") % file; + } -// Set all the nullable values to nils. -void ConfigBase::null_nullables() -{ - for (const std::string &opt_key : this->keys()) { - ConfigOption *opt = this->optptr(opt_key, false); - assert(opt != nullptr); - if (opt->nullable()) - opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable); + // Set all the nullable values to nils. + void ConfigBase::null_nullables() + { + for (const std::string &opt_key : this->keys()) + { + ConfigOption *opt = this->optptr(opt_key, false); + assert(opt != nullptr); + if (opt->nullable()) + opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable); + } } -} -DynamicConfig::DynamicConfig(const ConfigBase& rhs, const t_config_option_keys& keys) -{ - for (const t_config_option_key& opt_key : keys) - this->options[opt_key] = std::unique_ptr(rhs.option(opt_key)->clone()); -} + DynamicConfig::DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys) + { + for (const t_config_option_key &opt_key : keys) + this->options[opt_key] = std::unique_ptr(rhs.option(opt_key)->clone()); + } -bool DynamicConfig::operator==(const DynamicConfig &rhs) const -{ - auto it1 = this->options.begin(); - auto it1_end = this->options.end(); - auto it2 = rhs.options.begin(); - auto it2_end = rhs.options.end(); - for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2) - if (it1->first != it2->first || *it1->second != *it2->second) - // key or value differ - return false; - return it1 == it1_end && it2 == it2_end; -} + bool DynamicConfig::operator==(const DynamicConfig &rhs) const + { + auto it1 = this->options.begin(); + auto it1_end = this->options.end(); + auto it2 = rhs.options.begin(); + auto it2_end = rhs.options.end(); + for (; it1 != it1_end && it2 != it2_end; ++it1, ++it2) + if (it1->first != it2->first || *it1->second != *it2->second) + // key or value differ + return false; + return it1 == it1_end && it2 == it2_end; + } -// Remove options with all nil values, those are optional and it does not help to hold them. -size_t DynamicConfig::remove_nil_options() -{ - size_t cnt_removed = 0; - for (auto it = options.begin(); it != options.end();) - if (it->second->is_nil()) { - it = options.erase(it); - ++ cnt_removed; - } else - ++ it; - return cnt_removed; -} + // Remove options with all nil values, those are optional and it does not help to hold them. + size_t DynamicConfig::remove_nil_options() + { + size_t cnt_removed = 0; + for (auto it = options.begin(); it != options.end();) + if (it->second->is_nil()) + { + it = options.erase(it); + ++cnt_removed; + } + else + ++it; + return cnt_removed; + } -ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) -{ - auto it = options.find(opt_key); - if (it != options.end()) - // Option was found. - return it->second.get(); - if (! create) - // Option was not found and a new option shall not be created. - return nullptr; - // Try to create a new ConfigOption. - const ConfigDef *def = this->def(); - if (def == nullptr) - throw NoDefinitionException(opt_key); - const ConfigOptionDef *optdef = def->get(opt_key); - if (optdef == nullptr) -// throw ConfigurationError(std::string("Invalid option name: ") + opt_key); - // Let the parent decide what to do if the opt_key is not defined by this->def(). - return nullptr; - ConfigOption *opt = optdef->create_default_option(); - this->options.emplace_hint(it, opt_key, std::unique_ptr(opt)); - return opt; -} + ConfigOption *DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) + { + auto it = options.find(opt_key); + if (it != options.end()) + // Option was found. + return it->second.get(); + if (!create) + // Option was not found and a new option shall not be created. + return nullptr; + // Try to create a new ConfigOption. + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(opt_key); + const ConfigOptionDef *optdef = def->get(opt_key); + if (optdef == nullptr) + // throw ConfigurationError(std::string("Invalid option name: ") + opt_key); + // Let the parent decide what to do if the opt_key is not defined by this->def(). + return nullptr; + ConfigOption *opt = optdef->create_default_option(); + this->options.emplace_hint(it, opt_key, std::unique_ptr(opt)); + return opt; + } -const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) const -{ - auto it = options.find(opt_key); - return (it == options.end()) ? nullptr : it->second.get(); -} + const ConfigOption *DynamicConfig::optptr(const t_config_option_key &opt_key) const + { + auto it = options.find(opt_key); + return (it == options.end()) ? nullptr : it->second.get(); + } -bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys) -{ - // cache the CLI option => opt_key mapping - std::map opts; - for (const auto &oit : this->def()->options) - for (const std::string &t : oit.second.cli_args(oit.first)) - opts[t] = oit.first; - - bool parse_options = true; - for (int i = 1; i < argc; ++ i) { - std::string token = argv[i]; - // Store non-option arguments in the provided vector. - if (! parse_options || ! boost::starts_with(token, "-")) { - extra->push_back(token); - continue; - } + bool DynamicConfig::read_cli(int argc, const char *const argv[], t_config_option_keys *extra, t_config_option_keys *keys) + { + // cache the CLI option => opt_key mapping + std::map opts; + for (const auto &oit : this->def()->options) + for (const std::string &t : oit.second.cli_args(oit.first)) + opts[t] = oit.first; + + bool parse_options = true; + for (int i = 1; i < argc; ++i) + { + std::string token = argv[i]; + // Store non-option arguments in the provided vector. + if (!parse_options || !boost::starts_with(token, "-")) + { + extra->push_back(token); + continue; + } #ifdef __APPLE__ - if (boost::starts_with(token, "-psn_")) - // OSX launcher may add a "process serial number", for example "-psn_0_989382" to the command line. - // While it is supposed to be dropped since OSX 10.9, we will rather ignore it. - continue; + if (boost::starts_with(token, "-psn_")) + // OSX launcher may add a "process serial number", for example "-psn_0_989382" to the command line. + // While it is supposed to be dropped since OSX 10.9, we will rather ignore it. + continue; #endif /* __APPLE__ */ - // Stop parsing tokens as options when -- is supplied. - if (token == "--") { - parse_options = false; - continue; - } - // Remove leading dashes (one or two). - token.erase(token.begin(), token.begin() + (boost::starts_with(token, "--") ? 2 : 1)); - // Read value when supplied in the --key=value form. - std::string value; - { - size_t equals_pos = token.find("="); - if (equals_pos != std::string::npos) { - value = token.substr(equals_pos+1); - token.erase(equals_pos); + // Stop parsing tokens as options when -- is supplied. + if (token == "--") + { + parse_options = false; + continue; } - } - // Look for the cli -> option mapping. - auto it = opts.find(token); - bool no = false; - if (it == opts.end()) { - //BBS: don't use 'no-' for boolean options - boost::nowide::cerr << "Invalid option --" << token.c_str() << std::endl; - return false; - /* Remove the "no-" prefix used to negate boolean options. - std::string yes_token; - if (boost::starts_with(token, "no-")) { - yes_token = token.substr(3); - it = opts.find(yes_token); - no = true; - } - if (it == opts.end()) { + // Remove leading dashes (one or two). + token.erase(token.begin(), token.begin() + (boost::starts_with(token, "--") ? 2 : 1)); + // Read value when supplied in the --key=value form. + std::string value; + { + size_t equals_pos = token.find("="); + if (equals_pos != std::string::npos) + { + value = token.substr(equals_pos + 1); + token.erase(equals_pos); + } + } + // Look for the cli -> option mapping. + auto it = opts.find(token); + bool no = false; + if (it == opts.end()) + { + // BBS: don't use 'no-' for boolean options boost::nowide::cerr << "Invalid option --" << token.c_str() << std::endl; return false; + /* Remove the "no-" prefix used to negate boolean options. + std::string yes_token; + if (boost::starts_with(token, "no-")) { + yes_token = token.substr(3); + it = opts.find(yes_token); + no = true; + } + if (it == opts.end()) { + boost::nowide::cerr << "Invalid option --" << token.c_str() << std::endl; + return false; + } + if (no) + token = yes_token;*/ } - if (no) - token = yes_token;*/ - } - const t_config_option_key &opt_key = it->second; - const ConfigOptionDef &optdef = this->def()->options.at(opt_key); + const t_config_option_key &opt_key = it->second; + const ConfigOptionDef &optdef = this->def()->options.at(opt_key); - // If the option type expects a value and it was not already provided, - // look for it in the next token. - if (value.empty() && optdef.type != coBool && optdef.type != coBools) { - if (i == argc-1) { - boost::nowide::cerr << "Need values for option --" << token.c_str() << std::endl; - return false; + // If the option type expects a value and it was not already provided, + // look for it in the next token. + if (value.empty() && optdef.type != coBool && optdef.type != coBools) + { + if (i == argc - 1) + { + boost::nowide::cerr << "Need values for option --" << token.c_str() << std::endl; + return false; + } + value = argv[++i]; } - value = argv[++ i]; - } - /*if (no) { - assert(optdef.type == coBool || optdef.type == coBools); - if (! value.empty()) { - boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl; - return false; - } - }*/ + /*if (no) { + assert(optdef.type == coBool || optdef.type == coBools); + if (! value.empty()) { + boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl; + return false; + } + }*/ - // Store the option value. - const bool existing = this->has(opt_key); - if (keys != nullptr && ! existing) { - // Save the order of detected keys. - keys->push_back(opt_key); - } - ConfigOption *opt_base = this->option(opt_key, true); - ConfigOptionVectorBase *opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; - if (opt_vector) { - if (! existing) - // remove the default values - opt_vector->clear(); - // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters - // to the end of the value. - if (opt_base->type() == coBools && value.empty()) - static_cast(opt_base)->values.push_back(!no); - else - // Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way - // they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape - // will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be - // unescaped by the calling shell. - opt_vector->deserialize(value, true); - } else if (opt_base->type() == coBool) { - if (value.empty()) - static_cast(opt_base)->value = !no; + // Store the option value. + const bool existing = this->has(opt_key); + if (keys != nullptr && !existing) + { + // Save the order of detected keys. + keys->push_back(opt_key); + } + ConfigOption *opt_base = this->option(opt_key, true); + ConfigOptionVectorBase *opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; + if (opt_vector) + { + if (!existing) + // remove the default values + opt_vector->clear(); + // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters + // to the end of the value. + if (opt_base->type() == coBools && value.empty()) + static_cast(opt_base)->values.push_back(!no); + else + // Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way + // they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape + // will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be + // unescaped by the calling shell. + opt_vector->deserialize(value, true); + } + else if (opt_base->type() == coBool) + { + if (value.empty()) + static_cast(opt_base)->value = !no; + else + opt_base->deserialize(value); + } + else if (opt_base->type() == coString) + { + // Do not unescape single string values, the unescaping is left to the calling shell. + static_cast(opt_base)->value = value; + } else - opt_base->deserialize(value); - } else if (opt_base->type() == coString) { - // Do not unescape single string values, the unescaping is left to the calling shell. - static_cast(opt_base)->value = value; - } else { - // Just bail out if the configuration value is not understood. - ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable); - // Any scalar value of a type different from Bool and String. - if (! this->set_deserialize_nothrow(opt_key, value, context, false)) { - boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl; - return false; - } + { + // Just bail out if the configuration value is not understood. + ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable); + // Any scalar value of a type different from Bool and String. + if (!this->set_deserialize_nothrow(opt_key, value, context, false)) + { + boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl; + return false; + } + } } + return true; } - return true; -} -t_config_option_keys DynamicConfig::keys() const -{ - t_config_option_keys keys; - keys.reserve(this->options.size()); - for (const auto &opt : this->options) - keys.emplace_back(opt.first); - return keys; -} + t_config_option_keys DynamicConfig::keys() const + { + t_config_option_keys keys; + keys.reserve(this->options.size()); + for (const auto &opt : this->options) + keys.emplace_back(opt.first); + return keys; + } -void StaticConfig::set_defaults() -{ - // use defaults from definition - auto *defs = this->def(); - if (defs != nullptr) { - for (const std::string &key : this->keys()) { - const ConfigOptionDef *def = defs->get(key); - ConfigOption *opt = this->option(key); - if (def != nullptr && opt != nullptr && def->default_value) - opt->set(def->default_value.get()); + void StaticConfig::set_defaults() + { + // use defaults from definition + auto *defs = this->def(); + if (defs != nullptr) + { + for (const std::string &key : this->keys()) + { + const ConfigOptionDef *def = defs->get(key); + ConfigOption *opt = this->option(key); + if (def != nullptr && opt != nullptr && def->default_value) + opt->set(def->default_value.get()); + } } } -} -t_config_option_keys StaticConfig::keys() const -{ - t_config_option_keys keys; - assert(this->def() != nullptr); - for (const auto &opt_def : this->def()->options) - if (this->option(opt_def.first) != nullptr) - keys.push_back(opt_def.first); - return keys; -} + t_config_option_keys StaticConfig::keys() const + { + t_config_option_keys keys; + assert(this->def() != nullptr); + for (const auto &opt_def : this->def()->options) + if (this->option(opt_def.first) != nullptr) + keys.push_back(opt_def.first); + return keys; + } -// Iterate over the pairs of options with equal keys, call the fn. -// Returns true on early exit by fn(). -//BBS: add skipped key logic -template -static inline bool dynamic_config_iterate(const DynamicConfig &lhs, const DynamicConfig &rhs, Fn fn, const std::set* skipped_keys = nullptr) -{ - std::map>::const_iterator i = lhs.cbegin(); - std::map>::const_iterator j = rhs.cbegin(); - while (i != lhs.cend() && j != rhs.cend()) - if (i->first < j->first) - ++ i; - else if (i->first > j->first) - ++ j; - else { - assert(i->first == j->first); - if (skipped_keys && (skipped_keys->count(i->first) != 0)) - { - //do nothing - } - else if (fn(i->first, i->second.get(), j->second.get())) - // Early exit by fn. - return true; - ++ i; - ++ j; - } - // Finished to the end. - return false; -} + // Iterate over the pairs of options with equal keys, call the fn. + // Returns true on early exit by fn(). + // BBS: add skipped key logic + template + static inline bool dynamic_config_iterate(const DynamicConfig &lhs, const DynamicConfig &rhs, Fn fn, const std::set *skipped_keys = nullptr) + { + std::map>::const_iterator i = lhs.cbegin(); + std::map>::const_iterator j = rhs.cbegin(); + while (i != lhs.cend() && j != rhs.cend()) + if (i->first < j->first) + ++i; + else if (i->first > j->first) + ++j; + else + { + assert(i->first == j->first); + if (skipped_keys && (skipped_keys->count(i->first) != 0)) + { + // do nothing + } + else if (fn(i->first, i->second.get(), j->second.get())) + // Early exit by fn. + return true; + ++i; + ++j; + } + // Finished to the end. + return false; + } -// Are the two configs equal? Ignoring options not present in both configs. -//BBS: add skipped keys logic -bool DynamicConfig::equals(const DynamicConfig &other, const std::set* skipped_keys) const -{ - return ! dynamic_config_iterate(*this, other, - [](const t_config_option_key & /* key */, const ConfigOption *l, const ConfigOption *r) { return *l != *r; }, - skipped_keys); -} + // Are the two configs equal? Ignoring options not present in both configs. + // BBS: add skipped keys logic + bool DynamicConfig::equals(const DynamicConfig &other, const std::set *skipped_keys) const + { + return !dynamic_config_iterate(*this, other, [](const t_config_option_key & /* key */, const ConfigOption *l, const ConfigOption *r) + { return *l != *r; }, skipped_keys); + } -// Returns options differing in the two configs, ignoring options not present in both configs. -t_config_option_keys DynamicConfig::diff(const DynamicConfig &other) const -{ - t_config_option_keys diff; - dynamic_config_iterate(*this, other, - [&diff](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) { - if (*l != *r) - diff.emplace_back(key); - // Continue iterating. - return false; - }); - return diff; -} + // Returns options differing in the two configs, ignoring options not present in both configs. + t_config_option_keys DynamicConfig::diff(const DynamicConfig &other) const + { + t_config_option_keys diff; + dynamic_config_iterate(*this, other, + [&diff](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) + { + if (*l != *r) + diff.emplace_back(key); + // Continue iterating. + return false; + }); + return diff; + } -// Returns options being equal in the two configs, ignoring options not present in both configs. -t_config_option_keys DynamicConfig::equal(const DynamicConfig &other) const -{ - t_config_option_keys equal; - dynamic_config_iterate(*this, other, - [&equal](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) { - if (*l == *r) - equal.emplace_back(key); - // Continue iterating. - return false; - }); - return equal; -} + // Returns options being equal in the two configs, ignoring options not present in both configs. + t_config_option_keys DynamicConfig::equal(const DynamicConfig &other) const + { + t_config_option_keys equal; + dynamic_config_iterate(*this, other, + [&equal](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) + { + if (*l == *r) + equal.emplace_back(key); + // Continue iterating. + return false; + }); + return equal; + } -double& DynamicConfig::opt_float(const t_config_option_key &opt_key, unsigned int idx) -{ - if (ConfigOptionFloats *opt_floats = dynamic_cast(this->option(opt_key))) { - return opt_floats->get_at(idx); - } else { - ConfigOptionFloatsNullable *opt_floats_nullable = dynamic_cast(this->option(opt_key)); - assert(opt_floats_nullable != nullptr); - return opt_floats_nullable->get_at(idx); + double &DynamicConfig::opt_float(const t_config_option_key &opt_key, unsigned int idx) + { + if (ConfigOptionFloats *opt_floats = dynamic_cast(this->option(opt_key))) + { + return opt_floats->get_at(idx); + } + else + { + ConfigOptionFloatsNullable *opt_floats_nullable = dynamic_cast(this->option(opt_key)); + assert(opt_floats_nullable != nullptr); + return opt_floats_nullable->get_at(idx); + } } -} -const double& DynamicConfig::opt_float(const t_config_option_key &opt_key, unsigned int idx) const -{ - if (const ConfigOptionFloats *opt_floats = dynamic_cast(this->option(opt_key))) { - return opt_floats->get_at(idx); - } else if (const ConfigOptionFloatsNullable *opt_floats_nullable = dynamic_cast(this->option(opt_key))) { - return opt_floats_nullable->get_at(idx); - } else { - assert(false); - return 0; + const double &DynamicConfig::opt_float(const t_config_option_key &opt_key, unsigned int idx) const + { + if (const ConfigOptionFloats *opt_floats = dynamic_cast(this->option(opt_key))) + { + return opt_floats->get_at(idx); + } + else if (const ConfigOptionFloatsNullable *opt_floats_nullable = dynamic_cast(this->option(opt_key))) + { + return opt_floats_nullable->get_at(idx); + } + else + { + assert(false); + return 0; + } } -} -bool DynamicConfig::opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { - if (const ConfigOptionBools *opts = dynamic_cast(this->option(opt_key))) { - return opts->get_at(idx) != 0; - } - else { - const ConfigOptionBoolsNullable *opt_s = dynamic_cast(this->option(opt_key)); - assert(opt_s != nullptr); - return opt_s->get_at(idx) != 0; + bool DynamicConfig::opt_bool(const t_config_option_key &opt_key, unsigned int idx) const + { + if (const ConfigOptionBools *opts = dynamic_cast(this->option(opt_key))) + { + return opts->get_at(idx) != 0; + } + else + { + const ConfigOptionBoolsNullable *opt_s = dynamic_cast(this->option(opt_key)); + assert(opt_s != nullptr); + return opt_s->get_at(idx) != 0; + } } -} } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3355feb36b..4d669eb954 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -39,18 +39,18 @@ // Intel redesigned some TBB interface considerably when merging TBB with their oneAPI set of libraries, see GH #7332. // We are using quite an old TBB 2017 U7. Before we update our build servers, let's use the old API, which is deprecated in up to date TBB. -#if ! defined(TBB_VERSION_MAJOR) - #include +#if !defined(TBB_VERSION_MAJOR) +#include #endif -#if ! defined(TBB_VERSION_MAJOR) - static_assert(false, "TBB_VERSION_MAJOR not defined"); +#if !defined(TBB_VERSION_MAJOR) +static_assert(false, "TBB_VERSION_MAJOR not defined"); #endif #if TBB_VERSION_MAJOR >= 2021 - #include - using slic3r_tbb_filtermode = tbb::filter_mode; +#include +using slic3r_tbb_filtermode = tbb::filter_mode; #else - #include - using slic3r_tbb_filtermode = tbb::filter; +#include +using slic3r_tbb_filtermode = tbb::filter; #endif #include @@ -70,7 +70,8 @@ using namespace std::literals::string_view_literals; #include #include -namespace Slic3r { +namespace Slic3r +{ //! macro used to mark string used at localization, //! return same string @@ -78,160 +79,186 @@ namespace Slic3r { #define _(s) Slic3r::I18N::translate(s) #define _L(s) Slic3r::I18N::translate(s) -static const float g_min_purge_volume = 100.f; -static const float g_purge_volume_one_time = 135.f; -static const int g_max_flush_count = 4; -static const size_t g_max_label_object = 64; -static const double smooth_speed_step = 10; -static const double not_split_length = scale_(1.0); -static const double max_step_length = scale_(1.0); // cut path if the path too long -static const double min_step_length = scale_(0.4); // cut step - -Vec2d travel_point_1; -Vec2d travel_point_2; -Vec2d travel_point_3; -static std::vector get_path_of_change_filament(const Print& print) -{ - // give safe value in case there is no start_end_points in config - std::vector out_points; - out_points.emplace_back(Vec2d(54, 0)); - out_points.emplace_back(Vec2d(54, 0)); - out_points.emplace_back(Vec2d(54, 245)); - - // get the start_end_points from config (20, -3) (54, 245) - Pointfs points = print.config().start_end_points.values; - if (points.size() != 2) - return out_points; + static const float g_min_purge_volume = 100.f; + static const float g_purge_volume_one_time = 135.f; + static const int g_max_flush_count = 4; + static const size_t g_max_label_object = 64; + static const double smooth_speed_step = 10; + static const double not_split_length = scale_(1.0); + static const double max_step_length = scale_(1.0); // cut path if the path too long + static const double min_step_length = scale_(0.4); // cut step + + Vec2d travel_point_1; + Vec2d travel_point_2; + Vec2d travel_point_3; + static std::vector get_path_of_change_filament(const Print &print) + { + // give safe value in case there is no start_end_points in config + std::vector out_points; + out_points.emplace_back(Vec2d(54, 0)); + out_points.emplace_back(Vec2d(54, 0)); + out_points.emplace_back(Vec2d(54, 245)); - Vec2d start_point = points[0]; - Vec2d end_point = points[1]; + // get the start_end_points from config (20, -3) (54, 245) + Pointfs points = print.config().start_end_points.values; + if (points.size() != 2) + return out_points; - // the cutter area size(18, 28) - Pointfs excluse_area = print.config().bed_exclude_area.values; - if (excluse_area.size() != 4) - return out_points; + Vec2d start_point = points[0]; + Vec2d end_point = points[1]; + + // the cutter area size(18, 28) + Pointfs excluse_area = print.config().bed_exclude_area.values; + if (excluse_area.size() != 4) + return out_points; + + double cutter_area_x = excluse_area[2].x() + 2; + double cutter_area_y = excluse_area[2].y() + 2; + + double start_x_position = start_point.x(); + double end_x_position = end_point.x(); + double end_y_position = end_point.y(); - double cutter_area_x = excluse_area[2].x() + 2; - double cutter_area_y = excluse_area[2].y() + 2; - - double start_x_position = start_point.x(); - double end_x_position = end_point.x(); - double end_y_position = end_point.y(); - - bool can_travel_form_left = true; - - // step 1: get the x-range intervals of all objects - std::vector> object_intervals; - for (PrintObject *print_object : print.objects()) { - const PrintInstances &print_instances = print_object->instances(); - BoundingBoxf3 bounding_box = print_instances[0].model_instance->get_object()->bounding_box(); - - if (bounding_box.min.x() < start_x_position && bounding_box.min.y() < cutter_area_y) - can_travel_form_left = false; - - std::pair object_scope = std::make_pair(bounding_box.min.x() - 2, bounding_box.max.x() + 2); - if (object_intervals.empty()) - object_intervals.push_back(object_scope); - else { - std::vector> new_object_intervals; - bool intervals_intersect = false; - std::pair new_merged_scope; - for (auto object_interval : object_intervals) { - if (object_interval.second >= object_scope.first && object_interval.first <= object_scope.second) { - if (intervals_intersect) { - new_merged_scope = std::make_pair(std::min(object_interval.first, new_merged_scope.first), std::max(object_interval.second, new_merged_scope.second)); - } else { // it is the first intersection - new_merged_scope = std::make_pair(std::min(object_interval.first, object_scope.first), std::max(object_interval.second, object_scope.second)); + bool can_travel_form_left = true; + + // step 1: get the x-range intervals of all objects + std::vector> object_intervals; + for (PrintObject *print_object : print.objects()) + { + const PrintInstances &print_instances = print_object->instances(); + BoundingBoxf3 bounding_box = print_instances[0].model_instance->get_object()->bounding_box(); + + if (bounding_box.min.x() < start_x_position && bounding_box.min.y() < cutter_area_y) + can_travel_form_left = false; + + std::pair object_scope = std::make_pair(bounding_box.min.x() - 2, bounding_box.max.x() + 2); + if (object_intervals.empty()) + object_intervals.push_back(object_scope); + else + { + std::vector> new_object_intervals; + bool intervals_intersect = false; + std::pair new_merged_scope; + for (auto object_interval : object_intervals) + { + if (object_interval.second >= object_scope.first && object_interval.first <= object_scope.second) + { + if (intervals_intersect) + { + new_merged_scope = std::make_pair(std::min(object_interval.first, new_merged_scope.first), std::max(object_interval.second, new_merged_scope.second)); + } + else + { // it is the first intersection + new_merged_scope = std::make_pair(std::min(object_interval.first, object_scope.first), std::max(object_interval.second, object_scope.second)); + } + intervals_intersect = true; } - intervals_intersect = true; - } else { - new_object_intervals.push_back(object_interval); + else + { + new_object_intervals.push_back(object_interval); + } + } + + if (intervals_intersect) + { + new_object_intervals.push_back(new_merged_scope); + object_intervals = new_object_intervals; } + else + object_intervals.push_back(object_scope); } + } - if (intervals_intersect) { - new_object_intervals.push_back(new_merged_scope); - object_intervals = new_object_intervals; - } else - object_intervals.push_back(object_scope); + // step 2: get the available x-range + std::sort(object_intervals.begin(), object_intervals.end(), + [](const std::pair &left, const std::pair &right) + { + return left.first < right.first; + }); + std::vector> available_intervals; + double start_position = 0; + for (auto object_interval : object_intervals) + { + if (object_interval.first > start_position) + available_intervals.push_back(std::make_pair(start_position, object_interval.first)); + start_position = object_interval.second; } - } + available_intervals.push_back(std::make_pair(start_position, 255)); - // step 2: get the available x-range - std::sort(object_intervals.begin(), object_intervals.end(), - [](const std::pair &left, const std::pair &right) { - return left.first < right.first; - }); - std::vector> available_intervals; - double start_position = 0; - for (auto object_interval : object_intervals) { - if (object_interval.first > start_position) - available_intervals.push_back(std::make_pair(start_position, object_interval.first)); - start_position = object_interval.second; - } - available_intervals.push_back(std::make_pair(start_position, 255)); - - // step 3: get the nearest path - double new_path = 255; - for (auto available_interval : available_intervals) { - if (available_interval.first > end_x_position) { - double distance = available_interval.first - end_x_position; - new_path = abs(end_x_position - new_path) < distance ? new_path : available_interval.first; - break; - } else { - if (available_interval.second >= end_x_position) { - new_path = end_x_position; + // step 3: get the nearest path + double new_path = 255; + for (auto available_interval : available_intervals) + { + if (available_interval.first > end_x_position) + { + double distance = available_interval.first - end_x_position; + new_path = abs(end_x_position - new_path) < distance ? new_path : available_interval.first; break; - } else if (!can_travel_form_left && available_interval.second < start_x_position) { - continue; - } else { - new_path = available_interval.second; + } + else + { + if (available_interval.second >= end_x_position) + { + new_path = end_x_position; + break; + } + else if (!can_travel_form_left && available_interval.second < start_x_position) + { + continue; + } + else + { + new_path = available_interval.second; + } } } - } - // step 4: generate path points (new_path == start_x_position means not need to change path) - Vec2d out_point_1; - Vec2d out_point_2; - Vec2d out_point_3; - if (new_path < start_x_position) { - out_point_1 = Vec2d(start_x_position, cutter_area_y); - out_point_2 = Vec2d(new_path, cutter_area_y); - out_point_3 = Vec2d(new_path, end_y_position); - } else { - out_point_1 = Vec2d(new_path, 0); - out_point_2 = Vec2d(new_path, 0); - out_point_3 = Vec2d(new_path, end_y_position); - } + // step 4: generate path points (new_path == start_x_position means not need to change path) + Vec2d out_point_1; + Vec2d out_point_2; + Vec2d out_point_3; + if (new_path < start_x_position) + { + out_point_1 = Vec2d(start_x_position, cutter_area_y); + out_point_2 = Vec2d(new_path, cutter_area_y); + out_point_3 = Vec2d(new_path, end_y_position); + } + else + { + out_point_1 = Vec2d(new_path, 0); + out_point_2 = Vec2d(new_path, 0); + out_point_3 = Vec2d(new_path, end_y_position); + } - out_points.clear(); - out_points.emplace_back(out_point_1); - out_points.emplace_back(out_point_2); - out_points.emplace_back(out_point_3); + out_points.clear(); + out_points.emplace_back(out_point_1); + out_points.emplace_back(out_point_2); + out_points.emplace_back(out_point_3); - return out_points; -} + return out_points; + } -// 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) + // 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'; } - // 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) + 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) { + 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') { + while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') + { if (!std::isspace(custom_gcode[pos])) goto NEXT; } @@ -247,12 +274,13 @@ static std::vector get_path_of_change_filament(const Print& print) return ok; } - std::string OozePrevention::pre_toolchange(GCode& gcodegen) + std::string OozePrevention::pre_toolchange(GCode &gcodegen) { std::string gcode; // move to the nearest standby point - if (!this->standby_points.empty()) { + if (!this->standby_points.empty()) + { // get current position in print coordinates Vec3d writer_pos = gcodegen.writer().get_position(); Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); @@ -265,49 +293,50 @@ static std::vector get_path_of_change_filament(const Print& print) triggered by the caller) nor reduce_crossing_wall 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"); + "move to standby position"); } - if (gcodegen.config().standby_temperature_delta.value != 0) { + if (gcodegen.config().standby_temperature_delta.value != 0) + { // we assume that heating is always slower than cooling, so no need to block - gcode += gcodegen.writer().set_temperature - (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().filament()->id()); + gcode += gcodegen.writer().set_temperature(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().filament()->id()); } return gcode; } - std::string OozePrevention::post_toolchange(GCode& gcodegen) + std::string OozePrevention::post_toolchange(GCode &gcodegen) { - return (gcodegen.config().standby_temperature_delta.value != 0) ? - gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().filament()->id()) : - std::string(); + return (gcodegen.config().standby_temperature_delta.value != 0) ? gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().filament()->id()) : std::string(); } int - OozePrevention::_get_temp(GCode& gcodegen) + OozePrevention::_get_temp(GCode &gcodegen) { return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0) - ? gcodegen.config().nozzle_temperature_initial_layer.get_at(gcodegen.writer().filament()->id()) - : gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().filament()->id()); + ? gcodegen.config().nozzle_temperature_initial_layer.get_at(gcodegen.writer().filament()->id()) + : gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().filament()->id()); } std::string transform_gcode(const std::string &gcode, Vec2f pos, const Vec2f &translation, float angle) { - Vec2f extruder_offset(0, 0); + Vec2f extruder_offset(0, 0); std::istringstream gcode_str(gcode); - std::string gcode_out; - std::string line; - Vec2f transformed_pos = pos; - Vec2f old_pos(-1000.1f, -1000.1f); + std::string gcode_out; + std::string line; + Vec2f transformed_pos = pos; + Vec2f old_pos(-1000.1f, -1000.1f); - while (gcode_str) { + while (gcode_str) + { std::getline(gcode_str, line); // we read the gcode line by line - if (line.find("G1 ") == 0) { + if (line.find("G1 ") == 0) + { bool never_skip = false; - auto it = line.find(WipeTower::never_skip_tag()); - if (it != std::string::npos) { + auto it = line.find(WipeTower::never_skip_tag()); + if (it != std::string::npos) + { // remove the tag and remember we saw it never_skip = true; line.erase(it, it + WipeTower::never_skip_tag().size()); @@ -316,7 +345,8 @@ static std::vector get_path_of_change_filament(const Print& print) std::istringstream line_str(line); line_str >> std::noskipws; // don't skip whitespace char ch = 0; - while (line_str >> ch) { + while (line_str >> ch) + { if (ch == 'X' || ch == 'Y') line_str >> (ch == 'X' ? pos.x() : pos.y()); else @@ -325,12 +355,15 @@ static std::vector get_path_of_change_filament(const Print& print) transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; - if (transformed_pos != old_pos || never_skip) { + if (transformed_pos != old_pos || never_skip) + { line = line_out.str(); std::ostringstream oss; oss << std::fixed << std::setprecision(3) << "G1 "; - if (transformed_pos.x() != old_pos.x() || never_skip) oss << " X" << transformed_pos.x() - extruder_offset.x(); - if (transformed_pos.y() != old_pos.y() || never_skip) oss << " Y" << transformed_pos.y() - extruder_offset.y(); + if (transformed_pos.x() != old_pos.x() || never_skip) + oss << " X" << transformed_pos.x() - extruder_offset.x(); + if (transformed_pos.y() != old_pos.y() || never_skip) + oss << " Y" << transformed_pos.y() - extruder_offset.y(); oss << " "; line.replace(line.find("G1 "), 3, oss.str()); old_pos = transformed_pos; @@ -349,32 +382,37 @@ static std::vector get_path_of_change_filament(const Print& print) float a = 0.f, b = 0.f; a = wt_max.x() + offset; b = wt_min.x() - offset; - if (a > left && a < right) return a; - if (b > left && b < right) return b; + if (a > left && a < right) + return a; + if (b > left && b < right) + return b; return default_value; } - std::string Wipe::wipe(GCode& gcodegen, bool toolchange, bool is_last) + std::string Wipe::wipe(GCode &gcodegen, bool toolchange, bool is_last) { std::string gcode; /* 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. */ - //OrcaSlicer - double cur_speed = gcodegen.writer().get_current_speed(); - double wipe_speed = gcodegen.config().role_base_wipe_speed && cur_speed > EPSILON ? cur_speed / 60 : - gcodegen.writer().config.travel_speed.get_at(get_config_idx_for_filament(gcodegen.writer().config, gcodegen.writer().filament()->id())) * gcodegen.config().wipe_speed.value / 100; + // OrcaSlicer + double cur_speed = gcodegen.writer().get_current_speed(); + double wipe_speed = gcodegen.config().role_base_wipe_speed && cur_speed > EPSILON ? cur_speed / 60 : gcodegen.writer().config.travel_speed.get_at(get_config_idx_for_filament(gcodegen.writer().config, gcodegen.writer().filament()->id())) * gcodegen.config().wipe_speed.value / 100; - if (toolchange) { wipe_speed = gcodegen.m_print->config().prime_tower_max_speed.value; } + if (toolchange) + { + wipe_speed = gcodegen.m_print->config().prime_tower_max_speed.value; + } // get the retraction length double length = toolchange - ? gcodegen.writer().filament()->retract_length_toolchange() - : gcodegen.writer().filament()->retraction_length(); + ? gcodegen.writer().filament()->retract_length_toolchange() + : gcodegen.writer().filament()->retraction_length(); // Shorten the retraction length by the amount already retracted before wipe. length *= (1. - gcodegen.writer().filament()->retract_before_wipe()); - if (length >= 0) { + if (length >= 0) + { /* Calculate how long we need to travel in order to consume the required amount of retraction. In other words, how far do we move in XY at wipe_speed for the time needed to consume retraction_length at retraction_speed? */ @@ -387,38 +425,39 @@ static std::vector get_path_of_change_filament(const Print& print) wipe_path.append(gcodegen.last_pos()); wipe_path.append( this->path.points.begin() + 1, - this->path.points.end() - ); + this->path.points.end()); wipe_path.clip_end(wipe_path.length() - wipe_dist); // subdivide the retraction in segments - if (!wipe_path.empty()) { + if (!wipe_path.empty()) + { // BBS. Handle short path case. - if (wipe_path.length() < wipe_dist) { + if (wipe_path.length() < wipe_dist) + { wipe_dist = wipe_path.length(); - //BBS: avoid to divide 0 + // BBS: avoid to divide 0 wipe_dist = wipe_dist < EPSILON ? EPSILON : wipe_dist; } // add tag for processor gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n"; - //BBS: don't need to enable cooling makers when this is the last wipe. Because no more cooling layer will clean this "_WIPE" + // BBS: don't need to enable cooling makers when this is the last wipe. Because no more cooling layer will clean this "_WIPE" gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", (gcodegen.enable_cooling_markers() && !is_last) ? ";_WIPE" : ""); - for (const Line& line : wipe_path.lines()) { + for (const Line &line : wipe_path.lines()) + { double segment_length = line.length(); /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one due to rounding (TODO: test and/or better math for this) */ double dE = length * (segment_length / wipe_dist) * 0.95; - //BBS: fix this FIXME - //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. - // Is it here for the cooling markers? Or should it be outside of the cycle? - //gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); + // BBS: fix this FIXME + // FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. + // Is it here for the cooling markers? Or should it be outside of the cycle? + // gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); gcode += gcodegen.writer().extrude_to_xy( gcodegen.point_to_gcode(line.b), -dE, - "wipe and retract" - ); + "wipe and retract"); } // add tag for processor gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; @@ -432,17 +471,19 @@ static std::vector get_path_of_change_filament(const Print& print) return gcode; } - static inline Point wipe_tower_point_to_object_point(GCode& gcodegen, const Vec2f& wipe_tower_pt) + static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt) { return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); } // set volumetric speed of outer wall ,ignore per obejct & region ,just use default setting - static float get_outer_wall_volumetric_speed(const FullPrintConfig& config, const Print& print, int filament_id, int extruder_id) { + static float get_outer_wall_volumetric_speed(const FullPrintConfig &config, const Print &print, int filament_id, int extruder_id) + { float outer_wall_volumetric_speed = 0; float filament_max_volumetric_speed = config.filament_max_volumetric_speed.get_at(filament_id); float outer_wall_line_width = print.default_region_config().outer_wall_line_width.value; - if (outer_wall_line_width == 0.0) { + if (outer_wall_line_width == 0.0) + { float default_line_width = print.default_object_config().line_width.value; outer_wall_line_width = default_line_width == 0.0 ? config.filament_diameter.get_at(filament_id) : default_line_width; } @@ -454,7 +495,6 @@ static std::vector get_path_of_change_filament(const Print& print) return outer_wall_volumetric_speed; } - // Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code. // Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out) @@ -465,38 +505,49 @@ static std::vector get_path_of_change_filament(const Print& print) const char *ptr = gcode.data(); bool temp_set_by_gcode = false; - while (*ptr != 0) { + while (*ptr != 0) + { // Skip whitespaces. - for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + for (; *ptr == ' ' || *ptr == '\t'; ++ptr) + ; if (*ptr == 'M' || // Line starts with 'M'. It is a machine command. - (*ptr == 'G' && include_g10)) { // Only check for G10 if requested + (*ptr == 'G' && include_g10)) + { // Only check for G10 if requested bool is_gcode = *ptr == 'G'; - ++ ptr; + ++ptr; // Parse the M or G code value. char *endptr = nullptr; int mgcode = int(strtol(ptr, &endptr, 10)); if (endptr != nullptr && endptr != ptr && - is_gcode ? - // G10 found - mgcode == 10 : - // M104/M109 or M140/M190 found. - (mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) { + is_gcode + ? + // G10 found + mgcode == 10 + : + // M104/M109 or M140/M190 found. + (mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) + { ptr = endptr; - if (! is_gcode) + if (!is_gcode) // Let the caller know that the custom M-code sets the temperature. temp_set_by_gcode = true; // Now try to parse the temperature value. // While not at the end of the line: - while (strchr(";\r\n\0", *ptr) == nullptr) { + while (strchr(";\r\n\0", *ptr) == nullptr) + { // Skip whitespaces. - for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); - if (*ptr == 'S') { + for (; *ptr == ' ' || *ptr == '\t'; ++ptr) + ; + if (*ptr == 'S') + { // Skip whitespaces. - for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr); + for (++ptr; *ptr == ' ' || *ptr == '\t'; ++ptr) + ; // Parse an int. endptr = nullptr; long temp_parsed = strtol(ptr, &endptr, 10); - if (endptr > ptr) { + if (endptr > ptr) + { ptr = endptr; temp_out = temp_parsed; // Let the caller know that the custom G-code sets the temperature @@ -504,17 +555,22 @@ static std::vector get_path_of_change_filament(const Print& print) // can be used for other reasons temp_set_by_gcode = true; } - } else { + } + else + { // Skip this word. - for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); + for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ptr) + ; } } } } // Skip the rest of the line. - for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); + for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ptr) + ; // Skip the end of line indicators. - for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); + for (; *ptr == '\r' || *ptr == '\n'; ++ptr) + ; } return temp_set_by_gcode; } @@ -523,41 +579,48 @@ static std::vector get_path_of_change_filament(const Print& print) // start_pos refers to the last position before the wipe_tower. // end_pos refers to the wipe tower's start_pos. // using the print coordinate system - Polyline WipeTowerIntegration::generate_path_to_wipe_tower(const Point& start_pos,const Point &end_pos , const BoundingBox& avoid_polygon , const BoundingBox& printer_bbx) const + Polyline WipeTowerIntegration::generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const { - Polyline res; - coord_t alpha = scaled(2.f); // offset distance + Polyline res; + coord_t alpha = scaled(2.f); // offset distance BoundingBox avoid_polygon_inner = avoid_polygon; avoid_polygon_inner.offset(alpha); coord_t width = avoid_polygon_inner.max[0] - avoid_polygon_inner.min[0]; Polygon bed_polygon = printer_bbx.polygon(); - Vec2f v(1, 0); // the first print direction of end_pos. - if (abs(end_pos[0] - avoid_polygon_inner.min[0]) < width / 2) v = -v; // judge whether the wipe tower's infill goes to the left or right. + Vec2f v(1, 0); // the first print direction of end_pos. + if (abs(end_pos[0] - avoid_polygon_inner.min[0]) < width / 2) + v = -v; // judge whether the wipe tower's infill goes to the left or right. // Judge whether the avoid_polygon_inner is outside the printer_bbx. // If so, do nothing and just go directly to the end_pos. bool is_bbx_in_bed = true; - Points avoid_points = avoid_polygon_inner.polygon().points; - for (auto &wipe_tower_bbx_p : avoid_points) { - if (ClipperLib::PointInPolygon(wipe_tower_bbx_p, bed_polygon.points) != 1) { + Points avoid_points = avoid_polygon_inner.polygon().points; + for (auto &wipe_tower_bbx_p : avoid_points) + { + if (ClipperLib::PointInPolygon(wipe_tower_bbx_p, bed_polygon.points) != 1) + { is_bbx_in_bed = false; break; } } - if (!is_bbx_in_bed) { + if (!is_bbx_in_bed) + { res.points.push_back(end_pos); return res; } // Ray-Line Segment Intersection - auto ray_intersetion_line = [](const Vec2d &a, const Vec2d &v1, const Vec2d &b, const Vec2d &c) -> std::pair { - const Vec2d v2 = c - b; - double denom = cross2(v1, v2); - if (fabs(denom) < EPSILON) return {false, Point(0, 0)}; - const Vec2d v12 = (a - b); - double nume_a = cross2(v2, v12); - double nume_b = cross2(v1, v12); - double t1 = nume_a / denom; - double t2 = nume_b / denom; - if (t1 >= 0 && t2 >= 0 && t2 <= 1.) { + auto ray_intersetion_line = [](const Vec2d &a, const Vec2d &v1, const Vec2d &b, const Vec2d &c) -> std::pair + { + const Vec2d v2 = c - b; + double denom = cross2(v1, v2); + if (fabs(denom) < EPSILON) + return {false, Point(0, 0)}; + const Vec2d v12 = (a - b); + double nume_a = cross2(v2, v12); + double nume_b = cross2(v1, v12); + double t1 = nume_a / denom; + double t2 = nume_b / denom; + if (t1 >= 0 && t2 >= 0 && t2 <= 1.) + { // Get the intersection point. Vec2d res = a + t1 * v1; return std::pair(true, scaled(res)); @@ -566,21 +629,23 @@ static std::vector get_path_of_change_filament(const Print& print) }; struct Inter_info { - int inter_idx0 = -1; + int inter_idx0 = -1; Point inter_p; }; - auto calc_path_len = [](Points &points, Inter_info &beg_info, Inter_info &end_info, bool is_add) -> std::pair, double> { - int beg = is_add ? (beg_info.inter_idx0 + 1) % points.size() : beg_info.inter_idx0; - int end = is_add ? end_info.inter_idx0 : (end_info.inter_idx0 + 1) % points.size(); - int i = beg; - double len = 0; + auto calc_path_len = [](Points &points, Inter_info &beg_info, Inter_info &end_info, bool is_add) -> std::pair, double> + { + int beg = is_add ? (beg_info.inter_idx0 + 1) % points.size() : beg_info.inter_idx0; + int end = is_add ? end_info.inter_idx0 : (end_info.inter_idx0 + 1) % points.size(); + int i = beg; + double len = 0; std::vector path; path.push_back(beg_info.inter_p); len += (unscale(beg_info.inter_p) - unscale(points[beg])).squaredNorm(); - while (i != end) { - int ni = is_add ? (i + 1) % points.size() : (i - 1 + points.size()) % points.size(); - auto a = unscale(points[i]); - auto b = unscale(points[ni]); + while (i != end) + { + int ni = is_add ? (i + 1) % points.size() : (i - 1 + points.size()) % points.size(); + auto a = unscale(points[i]); + auto b = unscale(points[ni]); len += (a - b).squaredNorm(); path.push_back(points[i]); i = ni; @@ -594,46 +659,56 @@ static std::vector get_path_of_change_filament(const Print& print) // store in inter_info. // represent this intersection by 'p'. Inter_info inter_info; - for (size_t i = 0; i < avoid_points.size(); i++) { - const auto &a = avoid_points[i]; - const auto &b = avoid_points[(i + 1) % avoid_points.size()]; + for (size_t i = 0; i < avoid_points.size(); i++) + { + const auto &a = avoid_points[i]; + const auto &b = avoid_points[(i + 1) % avoid_points.size()]; auto [is_inter, inter_p] = ray_intersetion_line(unscale(end_pos), v.cast(), unscale(a), unscale(b)); - if (is_inter) { + if (is_inter) + { inter_info.inter_idx0 = i; - inter_info.inter_p = inter_p; + inter_info.inter_p = inter_p; break; } } - if (inter_info.inter_idx0 == -1) { + if (inter_info.inter_idx0 == -1) + { res.points.push_back(end_pos); return res; } // calculate the other intersection of start_to_p with the avoid_polygon. // represent this intersection by 'p_'. Inter_info inter_info2; - Linef start_to_p(unscale(start_pos), unscale(inter_info.inter_p)); - for (size_t i = 0; i < avoid_points.size(); i++) { - if (i == inter_info.inter_idx0) continue; + Linef start_to_p(unscale(start_pos), unscale(inter_info.inter_p)); + for (size_t i = 0; i < avoid_points.size(); i++) + { + if (i == inter_info.inter_idx0) + continue; Vec2d a = unscale(avoid_points[i]); Vec2d b = unscale(avoid_points[(i + 1) % avoid_points.size()]); Linef tower_edge(a, b); Vec2d inter; - if (line_alg::intersection(start_to_p, tower_edge, &inter)) { - inter_info2.inter_p = scaled(inter); + if (line_alg::intersection(start_to_p, tower_edge, &inter)) + { + inter_info2.inter_p = scaled(inter); inter_info2.inter_idx0 = i; break; } } // if p_ does not exist, go directly to p. // else p travels along the shorter path on the wipe_tower_offset_polygon to p_ - if (inter_info2.inter_idx0 == -1) { + if (inter_info2.inter_idx0 == -1) + { res.points.push_back(inter_info.inter_p); - } else { + } + else + { std::vector path; auto [path1, len1] = calc_path_len(avoid_points, inter_info2, inter_info, true); auto [path2, len2] = calc_path_len(avoid_points, inter_info2, inter_info, false); - path = len1 < len2 ? path1 : path2; - for (size_t i = 0; i < path.size(); i++) { + path = len1 < len2 ? path1 : path2; + for (size_t i = 0; i < path.size(); i++) + { res.points.push_back(path[i]); } } @@ -641,7 +716,7 @@ static std::vector get_path_of_change_filament(const Print& print) return res; } - std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const + std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_filament_id, double z) const { gcodegen.reset_last_acceleration(); if (new_filament_id != -1 && new_filament_id != tcr.new_tool) @@ -656,7 +731,8 @@ static std::vector get_path_of_change_filament(const Print& print) // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); - auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { + auto transform_wt_pt = [&alpha, this](const Vec2f &pt) -> Vec2f + { Vec2f out = Eigen::Rotation2Df(alpha) * pt; out += m_wipe_tower_pos + m_rib_offset; return out; @@ -667,13 +743,14 @@ static std::vector get_path_of_change_filament(const Print& print) Vec2f tool_change_start_pos = start_pos; if (tcr.is_tool_change) tool_change_start_pos = tcr.tool_change_start_pos; - if (! tcr.priming) { + if (!tcr.priming) + { start_pos = transform_wt_pt(start_pos); end_pos = transform_wt_pt(end_pos); tool_change_start_pos = transform_wt_pt(tool_change_start_pos); } - Vec2f wipe_tower_offset = (tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos) + m_rib_offset; + Vec2f wipe_tower_offset = (tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos) + m_rib_offset; float wipe_tower_rotation = tcr.priming ? 0.f : alpha; std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); @@ -684,7 +761,8 @@ static std::vector get_path_of_change_filament(const Print& print) // BBS: toolchange gcode will move to start_pos, // so only perform movement when printing sparse partition to support upper layer. // start_pos is the position in plate coordinate. - if (! tcr.priming && tcr.is_finish_first) { + if (!tcr.priming && tcr.is_finish_first) + { // Move over the wipe tower. gcode += gcodegen.retract(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); @@ -695,28 +773,30 @@ static std::vector get_path_of_change_filament(const Print& print) gcode += gcodegen.unretract(); } - double current_z = gcodegen.writer().get_position().z(); if (z == -1.) // in case no specific z was provided, print at current_z pos z = current_z; - if (! is_approx(z, current_z)) { + if (!is_approx(z, current_z)) + { gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); gcode += gcodegen.writer().unretract(); } - // Process the end filament gcode. - bool add_change_filament_624 = false; + bool add_change_filament_624 = false; std::string end_filament_gcode_str; - if (gcodegen.writer().filament() != nullptr) { + if (gcodegen.writer().filament() != nullptr) + { // Process the custom filament_end_gcode in case of single_extruder_multi_material. - unsigned int old_filament_id = gcodegen.writer().filament()->id(); - const std::string& filament_end_gcode = gcodegen.config().filament_end_gcode.get_at(old_filament_id); - if (gcodegen.writer().filament() != nullptr && !filament_end_gcode.empty()) { + unsigned int old_filament_id = gcodegen.writer().filament()->id(); + const std::string &filament_end_gcode = gcodegen.config().filament_end_gcode.get_at(old_filament_id); + if (gcodegen.writer().filament() != nullptr && !filament_end_gcode.empty()) + { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - if (!gcodegen.m_filament_instances_code.empty()) { + if (!gcodegen.m_filament_instances_code.empty()) + { end_filament_gcode_str += ("M624 " + gcodegen.m_filament_instances_code + "\n"); gcodegen.m_filament_instances_code = ""; add_change_filament_624 = true; @@ -726,7 +806,7 @@ static std::vector get_path_of_change_filament(const Print& print) } } - //BBS: increase toolchange count + // BBS: increase toolchange count gcodegen.m_toolchange_count++; std::string toolchange_gcode_str; @@ -740,7 +820,7 @@ static std::vector get_path_of_change_filament(const Print& print) std::string toolchange_retract_str = gcodegen.retract(tcr.is_tool_change && !is_nozzle_change, false, auto_lift_type, true); check_add_eol(toolchange_retract_str); - //BBS: if needed, write the gcode_label_objects_end then priming tower, if the retract, didn't did it. + // BBS: if needed, write the gcode_label_objects_end then priming tower, if the retract, didn't did it. std::string object_end_label_temp; gcodegen.m_writer.add_object_end_labels(object_end_label_temp); @@ -749,21 +829,23 @@ static std::vector get_path_of_change_filament(const Print& print) std::string change_filament_gcode = gcodegen.config().change_filament_gcode.value; bool is_used_travel_avoid_perimeter = gcodegen.m_config.prime_tower_skip_points.value; - if (is_nozzle_change && !tcr.nozzle_change_result.is_extruder_change) is_used_travel_avoid_perimeter = false; + if (is_nozzle_change && !tcr.nozzle_change_result.is_extruder_change) + is_used_travel_avoid_perimeter = false; // add nozzle change gcode into change filament gcode std::string nozzle_change_gcode_trans; - if (is_nozzle_change) { + if (is_nozzle_change) + { // move to start_pos before nozzle change std::string start_pos_str; start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(tcr.nozzle_change_result.start_pos) + plate_origin_2d), erMixed, - "Move to nozzle change start pos"); + "Move to nozzle change start pos"); check_add_eol(start_pos_str); nozzle_change_gcode_trans += start_pos_str; nozzle_change_gcode_trans += gcodegen.unretract(); nozzle_change_gcode_trans += transform_gcode(tcr.nozzle_change_result.gcode, tcr.nozzle_change_result.start_pos, wipe_tower_offset, wipe_tower_rotation); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(tcr.nozzle_change_result.end_pos) + plate_origin_2d)); gcodegen.m_wipe.reset_path(); - for (const Vec2f& wipe_pt : tcr.nozzle_change_result.wipe_path) + for (const Vec2f &wipe_pt : tcr.nozzle_change_result.wipe_path) gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d)); nozzle_change_gcode_trans += gcodegen.retract(tcr.is_tool_change, false, auto_lift_type, true); end_filament_gcode_str = nozzle_change_gcode_trans + end_filament_gcode_str; @@ -772,8 +854,9 @@ static std::vector get_path_of_change_filament(const Print& print) end_filament_gcode_str = toolchange_retract_str + object_end_label_temp + end_filament_gcode_str; std::string wipe_next_start_point_str; - bool need_travel_after_change_filament_gcode = false; // travel need be after the filament changed to get the correct "m_curr_extruder_id" - if (! change_filament_gcode.empty()) { + bool need_travel_after_change_filament_gcode = false; // travel need be after the filament changed to get the correct "m_curr_extruder_id" + if (!change_filament_gcode.empty()) + { DynamicConfig config; int old_filament_id = gcodegen.writer().filament() ? (int)gcodegen.writer().filament()->id() : -1; int old_extruder_id = gcodegen.writer().filament() ? (int)gcodegen.writer().filament()->extruder_id() : -1; @@ -783,11 +866,11 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); -// config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + // config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); // BBS { - GCodeWriter& gcode_writer = gcodegen.m_writer; - FullPrintConfig& full_config = gcodegen.m_config; + GCodeWriter &gcode_writer = gcodegen.m_writer; + FullPrintConfig &full_config = gcodegen.m_config; // set volumetric speed of outer wall ,ignore per obejct,just use default setting float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(full_config, *gcodegen.m_print, new_filament_id, gcodegen.get_extruder_id(new_filament_id)); @@ -798,7 +881,7 @@ static std::vector get_path_of_change_filament(const Print& print) float old_retract_length_toolchange = (old_filament_id != -1) ? full_config.retract_length_toolchange.get_at(old_filament_id) : 0; float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_filament_id); float old_filament_retract_length_nc = full_config.filament_retract_length_nc.get_at(old_filament_id); - int old_filament_temp = (old_filament_id != -1) ? (gcodegen.on_first_layer()? full_config.nozzle_temperature_initial_layer.get_at(old_filament_id) : full_config.nozzle_temperature.get_at(old_filament_id)) : 210; + int old_filament_temp = (old_filament_id != -1) ? (gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(old_filament_id) : full_config.nozzle_temperature.get_at(old_filament_id)) : 210; int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : full_config.nozzle_temperature.get_at(new_filament_id); float new_extruder_retracted_length = gcodegen.m_writer.get_extruder_retracted_length(new_filament_id); Vec3d nozzle_pos = gcode_writer.get_position(); @@ -811,9 +894,9 @@ static std::vector get_path_of_change_filament(const Print& print) old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; int new_filament_e_feedrate = (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(new_filament_id) / filament_area); new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; - float wipe_avoid_pos_x = 0.f; + float wipe_avoid_pos_x = 0.f; { - //set wipe_avoid_pos_x + // set wipe_avoid_pos_x Vec2f box_min = transform_wt_pt(m_wipe_tower_bbx.min.cast()); Vec2f box_max = transform_wt_pt(m_wipe_tower_bbx.max.cast()); wipe_avoid_pos_x = get_wipe_avoid_pos_x(box_min, box_max, 3.f); @@ -822,8 +905,8 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); config.set_key_value("relative_e_axis", new ConfigOptionBool(full_config.use_relative_e_distances)); config.set_key_value("toolchange_count", new ConfigOptionInt((int)gcodegen.m_toolchange_count)); - //BBS: fan speed is useless placeholer now, but we don't remove it to avoid - //slicing error in old change_filament_gcode in old 3MF + // BBS: fan speed is useless placeholer now, but we don't remove it to avoid + // slicing error in old change_filament_gcode in old 3MF config.set_key_value("fan_speed", new ConfigOptionInt((int)0)); config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); @@ -849,11 +932,13 @@ static std::vector get_path_of_change_filament(const Print& print) auto flush_v_speed = m_print_config->filament_flush_volumetric_speed.values; auto flush_temps = m_print_config->filament_flush_temp.values; - for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) { + for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) + { if (flush_v_speed[idx] == 0) flush_v_speed[idx] = m_print_config->filament_max_volumetric_speed.get_at(idx); } - for (size_t idx = 0; idx < flush_temps.size(); ++idx) { + for (size_t idx = 0; idx < flush_temps.size(); ++idx) + { if (flush_temps[idx] == 0) flush_temps[idx] = m_print_config->nozzle_temperature_range_high.get_at(idx); } @@ -873,8 +958,10 @@ static std::vector get_path_of_change_filament(const Print& print) else stop_pos = Vec2f(stop_pos.x() + 2.f, stop_pos.y()); BoundingBoxf printer_bbx = unscaled(get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon())); - if (stop_pos.x() < printer_bbx.min[0]) stop_pos.x() = printer_bbx.min[0]; - if (stop_pos.x() > printer_bbx.max[0]) stop_pos.x() = printer_bbx.max[0]; + if (stop_pos.x() < printer_bbx.min[0]) + stop_pos.x() = printer_bbx.min[0]; + if (stop_pos.x() > printer_bbx.max[0]) + stop_pos.x() = printer_bbx.max[0]; } config.set_key_value("wipe_tower_center_pos_x", new ConfigOptionFloat(stop_pos.x())); @@ -887,14 +974,16 @@ static std::vector get_path_of_change_filament(const Print& print) flush_count += 1; float flush_unit = purge_length / flush_count; int flush_idx = 0; - for (; flush_idx < flush_count; flush_idx++) { - char key_value[64] = { 0 }; + for (; flush_idx < flush_count; flush_idx++) + { + char key_value[64] = {0}; snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); config.set_key_value(key_value, new ConfigOptionFloat(flush_unit)); } - for (; flush_idx < g_max_flush_count; flush_idx++) { - char key_value[64] = { 0 }; + for (; flush_idx < g_max_flush_count; flush_idx++) + { + char key_value[64] = {0}; snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); config.set_key_value(key_value, new ConfigOptionFloat(0.f)); } @@ -903,15 +992,16 @@ static std::vector get_path_of_change_filament(const Print& print) check_add_eol(toolchange_gcode_str); - //BBS + // BBS { - //BBS: current position and fan_speed is unclear after interting change_filament_gcode + // BBS: current position and fan_speed is unclear after interting change_filament_gcode check_add_eol(toolchange_gcode_str); toolchange_gcode_str += ";_FORCE_RESUME_FAN_SPEED\n"; gcodegen.writer().set_current_position_clear(false); - //BBS: check whether custom gcode changes the z position. Update if changed + // BBS: check whether custom gcode changes the z position. Update if changed double temp_z_after_tool_change; - if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_str, temp_z_after_tool_change)) { + if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_str, temp_z_after_tool_change)) + { Vec3d pos = gcodegen.writer().get_position(); pos(2) = temp_z_after_tool_change; gcodegen.writer().set_position(pos); @@ -925,11 +1015,13 @@ static std::vector get_path_of_change_filament(const Print& print) toolchange_command = gcodegen.writer().toolchange(new_filament_id); if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_filament_id)) toolchange_gcode_str += toolchange_command; - else { + else + { // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. } - if (need_travel_after_change_filament_gcode) { + if (need_travel_after_change_filament_gcode) + { // After a filament change, the travel path leading to the wipe tower: // start_point inside the previous printed object, // end_point at the tower’s start_pos or at the starting point of the tower’s detour path. @@ -937,20 +1029,23 @@ static std::vector get_path_of_change_filament(const Print& print) gcodegen.m_avoid_crossing_perimeters.disable_once(); // move to start_pos for wiping after toolchange - if (!is_used_travel_avoid_perimeter) { + if (!is_used_travel_avoid_perimeter) + { std::string start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d), erMixed, "Move to start pos"); check_add_eol(start_pos_str); wipe_next_start_point_str = start_pos_str; - } else { + } + else + { // BBS:change travel_path Vec3f gcode_last_pos; GCodeProcessor::get_last_position_from_gcode(toolchange_gcode_str, gcode_last_pos); - Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]}; - Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast() + plate_origin_2d.cast()); - Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d); + Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]}; + Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast() + plate_origin_2d.cast()); + Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d); BoundingBox avoid_bbx, printer_bbx; { - //set printer_bbx + // set printer_bbx printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon()); printer_bbx.min = (wipe_tower_point_to_object_point(gcodegen, unscaled(printer_bbx.min) + plate_origin_2d)); @@ -958,20 +1053,23 @@ static std::vector get_path_of_change_filament(const Print& print) } { // set avoid_bbx - avoid_bbx = scaled(m_wipe_tower_bbx); + avoid_bbx = scaled(m_wipe_tower_bbx); Polygon avoid_points = avoid_bbx.polygon(); - for (auto &p : avoid_points.points) { + for (auto &p : avoid_points.points) + { Vec2f pp = transform_wt_pt(unscale(p).cast()); - p = wipe_tower_point_to_object_point(gcodegen, pp + plate_origin_2d); + p = wipe_tower_point_to_object_point(gcodegen, pp + plate_origin_2d); } avoid_bbx = BoundingBox(avoid_points.points); } std::string travel_to_wipe_tower_gcode; - Polyline travel_polyline = generate_path_to_wipe_tower(gcode_last_pos2d_object, start_wipe_pos, avoid_bbx, printer_bbx); + Polyline travel_polyline = generate_path_to_wipe_tower(gcode_last_pos2d_object, start_wipe_pos, avoid_bbx, printer_bbx); - for (size_t i = 0; i < travel_polyline.points.size(); ++i) { + for (size_t i = 0; i < travel_polyline.points.size(); ++i) + { const auto &p = travel_polyline.points[i]; - if (i == travel_polyline.points.size() - 1) { + if (i == travel_polyline.points.size() - 1) + { wipe_next_start_point_str = gcodegen.travel_to(p, erMixed, "Move to start pos"); check_add_eol(wipe_next_start_point_str); break; @@ -997,13 +1095,15 @@ static std::vector get_path_of_change_filament(const Print& print) // Process the start filament gcode. std::string start_filament_gcode_str; const std::string &filament_start_gcode = gcodegen.config().filament_start_gcode.get_at(new_filament_id); - if (!filament_start_gcode.empty()) { + if (!filament_start_gcode.empty()) + { // Process the filament_start_gcode for the active filament only. DynamicConfig config; config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_filament_id)); config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); start_filament_gcode_str = gcodegen.placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); - if (add_change_filament_624) { + if (add_change_filament_624) + { start_filament_gcode_str += "M625\n"; add_change_filament_624 = false; } @@ -1022,7 +1122,7 @@ static std::vector get_path_of_change_filament(const Print& print) gcode += tcr_gcode; check_add_eol(toolchange_gcode_str); - //OrcaSlicer: set new PA for new filament. BBS: never use for Bambu Printer + // OrcaSlicer: set new PA for new filament. BBS: never use for Bambu Printer if (!gcodegen.is_BBL_Printer() && gcodegen.config().enable_pressure_advance.get_at(new_filament_id)) gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_filament_id)); @@ -1031,16 +1131,18 @@ static std::vector get_path_of_change_filament(const Print& print) // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast()); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos + plate_origin_2d)); - if (!is_approx(z, current_z)) { + if (!is_approx(z, current_z)) + { gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); gcode += gcodegen.writer().unretract(); } - else { + else + { // Prepare a future wipe. gcodegen.m_wipe.reset_path(); - for (const Vec2f& wipe_pt : tcr.wipe_path) + for (const Vec2f &wipe_pt : tcr.wipe_path) gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d)); gcode += gcodegen.retract(false, false, auto_lift_type, true); } @@ -1053,7 +1155,7 @@ static std::vector get_path_of_change_filament(const Print& print) // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) - std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const + std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult &tcr, const Vec2f &translation, float angle) const { Vec2f extruder_offset; if (m_single_extruder_multi_material) @@ -1068,26 +1170,30 @@ static std::vector get_path_of_change_filament(const Print& print) Vec2f transformed_pos = pos; Vec2f old_pos(-1000.1f, -1000.1f); - while (gcode_str) { - std::getline(gcode_str, line); // we read the gcode line by line + while (gcode_str) + { + std::getline(gcode_str, line); // we read the gcode line by line // All G1 commands should be translated and rotated. X and Y coords are // only pushed to the output when they differ from last time. // WT generator can override this by appending the never_skip_tag - if (line.find("G1 ") == 0 || line.find("G2 ") == 0 || line.find("G3 ") == 0) { + if (line.find("G1 ") == 0 || line.find("G2 ") == 0 || line.find("G3 ") == 0) + { std::string cur_gcode_start = line.find("G1 ") == 0 ? "G1 " : (line.find("G2 ") == 0 ? "G2 " : "G3 "); bool never_skip = false; auto it = line.find(WipeTower::never_skip_tag()); - if (it != std::string::npos) { + if (it != std::string::npos) + { // remove the tag and remember we saw it never_skip = true; line.erase(it, it + WipeTower::never_skip_tag().size()); } std::ostringstream line_out; std::istringstream line_str(line); - line_str >> std::noskipws; // don't skip whitespace + line_str >> std::noskipws; // don't skip whitespace char ch = 0; - while (line_str >> ch) { + while (line_str >> ch) + { if (ch == 'X' || ch == 'Y') line_str >> (ch == 'X' ? pos.x() : pos.y()); else @@ -1096,7 +1202,8 @@ static std::vector get_path_of_change_filament(const Print& print) transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; - if (transformed_pos != old_pos || never_skip) { + if (transformed_pos != old_pos || never_skip) + { line = line_out.str(); std::ostringstream oss; oss << std::fixed << std::setprecision(3) << cur_gcode_start; @@ -1113,13 +1220,16 @@ static std::vector get_path_of_change_filament(const Print& print) gcode_out += line + "\n"; // If this was a toolchange command, we should change current extruder offset - if (line == "[change_filament_gcode]") { + if (line == "[change_filament_gcode]") + { // BBS - if (!m_single_extruder_multi_material) { + if (!m_single_extruder_multi_material) + { extruder_offset = m_extruder_offsets[tcr.new_tool].cast(); // If the extruder offset changed, add an extra move so everything is continuous - if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) { + if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) + { std::ostringstream oss; oss << std::fixed << std::setprecision(3) << "G1 X" << transformed_pos.x() - extruder_offset.x() @@ -1129,15 +1239,14 @@ static std::vector get_path_of_change_filament(const Print& print) } } old_pos = Vec2f{-1000.1f, -1000.1f}; - pos = tcr.tool_change_start_pos; + pos = tcr.tool_change_start_pos; transformed_pos = pos; } } return gcode_out; } - - std::string WipeTowerIntegration::prime(GCode& gcodegen) + std::string WipeTowerIntegration::prime(GCode &gcodegen) { std::string gcode; #if 0 @@ -1149,34 +1258,40 @@ static std::vector get_path_of_change_filament(const Print& print) return gcode; } - std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer) + std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { std::string gcode; assert(m_layer_idx >= 0); - if (m_layer_idx >= (int) m_tool_changes.size()) return gcode; + if (m_layer_idx >= (int)m_tool_changes.size()) + return gcode; // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, // resulting in a wipe tower with sparse layers. - double wipe_tower_z = -1; - bool ignore_sparse = false; - if (gcodegen.config().wipe_tower_no_sparse_layers.value) { - wipe_tower_z = m_last_wipe_tower_print_z; + double wipe_tower_z = -1; + bool ignore_sparse = false; + if (gcodegen.config().wipe_tower_no_sparse_layers.value) + { + wipe_tower_z = m_last_wipe_tower_print_z; ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); if (m_tool_change_idx == 0 && !ignore_sparse) wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; } - if ((m_enable_timelapse_print || m_enable_wrapping_detection) && m_is_first_print) { + if ((m_enable_timelapse_print || m_enable_wrapping_detection) && m_is_first_print) + { gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][0], m_tool_changes[m_layer_idx][0].new_tool, wipe_tower_z); m_tool_change_idx++; m_is_first_print = false; } - if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { - if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); + if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) + { + if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) + throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); - if (!ignore_sparse) { + if (!ignore_sparse) + { gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); m_last_wipe_tower_print_z = wipe_tower_z; } @@ -1188,23 +1303,27 @@ static std::vector get_path_of_change_filament(const Print& print) bool WipeTowerIntegration::is_empty_wipe_tower_gcode(GCode &gcodegen, int extruder_id, bool finish_layer) { assert(m_layer_idx >= 0); - if (m_layer_idx >= (int) m_tool_changes.size()) + if (m_layer_idx >= (int)m_tool_changes.size()) return true; - bool ignore_sparse = false; - if (gcodegen.config().wipe_tower_no_sparse_layers.value) { + bool ignore_sparse = false; + if (gcodegen.config().wipe_tower_no_sparse_layers.value) + { ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); } - if ((m_enable_timelapse_print || m_enable_wrapping_detection) && m_is_first_print) { + if ((m_enable_timelapse_print || m_enable_wrapping_detection) && m_is_first_print) + { return false; } - if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) + { if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); - if (!ignore_sparse) { + if (!ignore_sparse) + { return false; } } @@ -1213,7 +1332,7 @@ static std::vector get_path_of_change_filament(const Print& print) } // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. - std::string WipeTowerIntegration::finalize(GCode& gcodegen) + std::string WipeTowerIntegration::finalize(GCode &gcodegen) { std::string gcode; // BBS @@ -1225,513 +1344,569 @@ static std::vector get_path_of_change_filament(const Print& print) return gcode; } - const std::vector ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" }; + const std::vector ColorPrintColors::Colors = {"#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6"}; #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.filament()->extruder_id()) #define FILAMENT_CONFIG(OPT) m_config.OPT.get_at(m_writer.filament()->id()) -#define NOZZLE_CONFIG(OPT) m_config.OPT.get_at(m_config.filament_map_2.values[m_writer.filament()->id()]) +#define NOZZLE_CONFIG(OPT) m_config.OPT.get_at(m_config.filament_map_2.values[m_writer.filament()->id()]) -// Collect pairs of object_layer + support_layer sorted by print_z. -// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. -std::vector GCode::collect_layers_to_print(const PrintObject& object) -{ - std::vector layers_to_print; - layers_to_print.reserve(object.layers().size() + object.support_layers().size()); - - /* - // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. - // This is the same logic as in support generator. - //FIXME should we use the printing extruders instead? - double gap_over_supports = object.config().support_top_z_distance; - // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports. - assert(!object.has_support() || gap_over_supports != 0. || object.config().support_material_synchronize_layers); - if (gap_over_supports != 0.) { - 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)); - gap_over_supports += support_layer_height_min; - }*/ - - std::vector> warning_ranges; - - // Pair the object layers with the support layers by z. - size_t idx_object_layer = 0; - size_t idx_support_layer = 0; - const LayerToPrint* last_extrusion_layer = nullptr; - while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { - LayerToPrint layer_to_print; - double print_z_min = std::numeric_limits::max(); - if (idx_object_layer < object.layers().size()) { - layer_to_print.object_layer = object.layers()[idx_object_layer++]; - print_z_min = std::min(print_z_min, layer_to_print.object_layer->print_z); - } - - if (idx_support_layer < object.support_layers().size()) { - layer_to_print.support_layer = object.support_layers()[idx_support_layer++]; - print_z_min = std::min(print_z_min, layer_to_print.support_layer->print_z); - } - - if (layer_to_print.object_layer && layer_to_print.object_layer->print_z > print_z_min + EPSILON) { - layer_to_print.object_layer = nullptr; - --idx_object_layer; - } - - if (layer_to_print.support_layer && layer_to_print.support_layer->print_z > print_z_min + EPSILON) { - layer_to_print.support_layer = nullptr; - --idx_support_layer; - } - - layer_to_print.original_object = &object; - layers_to_print.push_back(layer_to_print); - - bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) - || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); - - // Check that there are extrusions on the very first layer. The case with empty - // first layer may result in skirt/brim in the air and maybe other issues. - if (layers_to_print.size() == 1u) { - if (!has_extrusions) - throw Slic3r::SlicingError(_(L("The following object(s) have empty initial layer and can't be printed. Please cut the bottom or enable supports.")), object.id().id); - } - - // In case there are extrusions on this layer, check there is a layer to lay it on. - if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) - // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. - || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) { - double top_cd = object.config().support_top_z_distance; - double bottom_cd = object.config().support_bottom_z_distance == 0. ? top_cd : object.config().support_bottom_z_distance; - //if (!object.print()->config().independent_support_layer_height) - { // the actual support gap may be larger than the configured one due to rounding to layer height for organic support, regardless of independent support layer height - top_cd = std::ceil(top_cd / object.config().layer_height) * object.config().layer_height; - bottom_cd = std::ceil(bottom_cd / object.config().layer_height) * object.config().layer_height; - } - double extra_gap = (layer_to_print.support_layer ? bottom_cd : top_cd); - - // raft contact distance should not trigger any warning - if (last_extrusion_layer && last_extrusion_layer->support_layer) { - double raft_gap = top_cd == 0 ? 0 : object.config().raft_contact_distance.value; - //if (!object.print()->config().independent_support_layer_height) - { - raft_gap = std::ceil(raft_gap / object.config().layer_height) * object.config().layer_height; - } - extra_gap = std::max(extra_gap, top_cd == 0 ? 0 :object.config().raft_contact_distance.value); + // Collect pairs of object_layer + support_layer sorted by print_z. + // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. + std::vector GCode::collect_layers_to_print(const PrintObject &object) + { + std::vector layers_to_print; + layers_to_print.reserve(object.layers().size() + object.support_layers().size()); + + /* + // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. + // This is the same logic as in support generator. + //FIXME should we use the printing extruders instead? + double gap_over_supports = object.config().support_top_z_distance; + // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports. + assert(!object.has_support() || gap_over_supports != 0. || object.config().support_material_synchronize_layers); + if (gap_over_supports != 0.) { + 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)); + gap_over_supports += support_layer_height_min; + }*/ + + std::vector> warning_ranges; + + // Pair the object layers with the support layers by z. + size_t idx_object_layer = 0; + size_t idx_support_layer = 0; + const LayerToPrint *last_extrusion_layer = nullptr; + while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) + { + LayerToPrint layer_to_print; + double print_z_min = std::numeric_limits::max(); + if (idx_object_layer < object.layers().size()) + { + layer_to_print.object_layer = object.layers()[idx_object_layer++]; + print_z_min = std::min(print_z_min, layer_to_print.object_layer->print_z); } - double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) - + layer_to_print.layer()->height - + std::max(0., extra_gap); - // Negative support_contact_z is not taken into account, it can result in false positives in cases - if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) - warning_ranges.emplace_back(std::make_pair((last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), layers_to_print.back().print_z())); - } - // Remember last layer with extrusions. - if (has_extrusions) - last_extrusion_layer = &layers_to_print.back(); - } + if (idx_support_layer < object.support_layers().size()) + { + layer_to_print.support_layer = object.support_layers()[idx_support_layer++]; + print_z_min = std::min(print_z_min, layer_to_print.support_layer->print_z); + } - if (! warning_ranges.empty()) { - std::string warning; - size_t i = 0; - for (i = 0; i < std::min(warning_ranges.size(), size_t(5)); ++i) - warning += Slic3r::format(_(L("Object can't be printed for empty layer between %1% and %2%.")), - warning_ranges[i].first, warning_ranges[i].second) + "\n"; - warning += Slic3r::format(_(L("Object: %1%")), object.model_object()->name) + "\n" - + _(L("Maybe parts of the object at these height are too thin, or the object has faulty mesh")); - - const_cast(object.print())->active_step_add_warning( - PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingEmptyGcodeLayers); - } + if (layer_to_print.object_layer && layer_to_print.object_layer->print_z > print_z_min + EPSILON) + { + layer_to_print.object_layer = nullptr; + --idx_object_layer; + } - return layers_to_print; -} + if (layer_to_print.support_layer && layer_to_print.support_layer->print_z > print_z_min + EPSILON) + { + layer_to_print.support_layer = nullptr; + --idx_support_layer; + } -// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z -// will be printed for all objects at once. -// Return a list of items. -std::vector>> GCode::collect_layers_to_print(const Print& print) -{ - struct OrderingItem { - coordf_t print_z; - size_t object_idx; - size_t layer_idx; - }; + layer_to_print.original_object = &object; + layers_to_print.push_back(layer_to_print); - std::vector> per_object(print.objects().size(), std::vector()); - std::vector ordering; + bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); - std::vector errors; + // Check that there are extrusions on the very first layer. The case with empty + // first layer may result in skirt/brim in the air and maybe other issues. + if (layers_to_print.size() == 1u) + { + if (!has_extrusions) + throw Slic3r::SlicingError(_(L("The following object(s) have empty initial layer and can't be printed. Please cut the bottom or enable supports.")), object.id().id); + } - for (size_t i = 0; i < print.objects().size(); ++i) { - try { - per_object[i] = collect_layers_to_print(*print.objects()[i]); - } catch (const Slic3r::SlicingError &e) { - errors.push_back(e); - continue; - } - OrderingItem ordering_item; - ordering_item.object_idx = i; - ordering.reserve(ordering.size() + per_object[i].size()); - const LayerToPrint& front = per_object[i].front(); - for (const LayerToPrint& ltp : per_object[i]) { - ordering_item.print_z = ltp.print_z(); - ordering_item.layer_idx = <p - &front; - ordering.emplace_back(ordering_item); - } - } + // In case there are extrusions on this layer, check there is a layer to lay it on. + if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. + || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) + { + double top_cd = object.config().support_top_z_distance; + double bottom_cd = object.config().support_bottom_z_distance == 0. ? top_cd : object.config().support_bottom_z_distance; + // if (!object.print()->config().independent_support_layer_height) + { // the actual support gap may be larger than the configured one due to rounding to layer height for organic support, regardless of independent support layer height + top_cd = std::ceil(top_cd / object.config().layer_height) * object.config().layer_height; + bottom_cd = std::ceil(bottom_cd / object.config().layer_height) * object.config().layer_height; + } + double extra_gap = (layer_to_print.support_layer ? bottom_cd : top_cd); - if (!errors.empty()) { throw Slic3r::SlicingErrors(errors); } - - std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; }); - - std::vector>> layers_to_print; - - // Merge numerically very close Z values. - for (size_t i = 0; i < ordering.size();) { - // Find the last layer with roughly the same print_z. - size_t j = i + 1; - coordf_t zmax = ordering[i].print_z + EPSILON; - for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j); - // Merge into layers_to_print. - std::pair> merged; - // Assign an average print_z to the set of layers with nearly equal print_z. - merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z); - merged.second.assign(print.objects().size(), LayerToPrint()); - for (; i < j; ++i) { - const OrderingItem& oi = ordering[i]; - assert(merged.second[oi.object_idx].layer() == nullptr); - merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]); - } - layers_to_print.emplace_back(std::move(merged)); - } + // raft contact distance should not trigger any warning + if (last_extrusion_layer && last_extrusion_layer->support_layer) + { + double raft_gap = top_cd == 0 ? 0 : object.config().raft_contact_distance.value; + // if (!object.print()->config().independent_support_layer_height) + { + raft_gap = std::ceil(raft_gap / object.config().layer_height) * object.config().layer_height; + } + extra_gap = std::max(extra_gap, top_cd == 0 ? 0 : object.config().raft_contact_distance.value); + } + double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_to_print.layer()->height + std::max(0., extra_gap); + // Negative support_contact_z is not taken into account, it can result in false positives in cases - return layers_to_print; -} + if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) + warning_ranges.emplace_back(std::make_pair((last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), layers_to_print.back().print_z())); + } + // Remember last layer with extrusions. + if (has_extrusions) + last_extrusion_layer = &layers_to_print.back(); + } -// free functions called by GCode::do_export() -namespace DoExport { -// static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) -// { -// const GCodeProcessorResult& result = processor.get_result(); -// print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); -// print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? -// get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; -// } - - static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics) - { - const GCodeProcessorResult& result = processor.get_result(); - print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); - print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? - get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; - - // update filament statictics - double total_extruded_volume = 0.0; - double total_used_filament = 0.0; - double total_weight = 0.0; - double total_cost = 0.0; - - for (auto volume : result.print_statistics.total_volumes_per_extruder) { - total_extruded_volume += volume.second; - - size_t extruder_id = volume.first; - auto extruder = std::find_if(extruders.begin(), extruders.end(), [extruder_id](const Extruder& extr) {return extr.id() == extruder_id; }); - if (extruder == extruders.end()) - continue; + if (!warning_ranges.empty()) + { + std::string warning; + size_t i = 0; + for (i = 0; i < std::min(warning_ranges.size(), size_t(5)); ++i) + warning += Slic3r::format(_(L("Object can't be printed for empty layer between %1% and %2%.")), + warning_ranges[i].first, warning_ranges[i].second) + + "\n"; + warning += Slic3r::format(_(L("Object: %1%")), object.model_object()->name) + "\n" + _(L("Maybe parts of the object at these height are too thin, or the object has faulty mesh")); - double s = PI * sqr(0.5* extruder->filament_diameter()); - double weight = volume.second * extruder->filament_density() * 0.001; - total_used_filament += volume.second/s; - total_weight += weight; - total_cost += weight * extruder->filament_cost() * 0.001; + const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingEmptyGcodeLayers); } - print_statistics.total_extruded_volume = total_extruded_volume; - print_statistics.total_used_filament = total_used_filament; - print_statistics.total_weight = total_weight; - print_statistics.total_cost = total_cost; - - print_statistics.filament_stats = result.print_statistics.model_volumes_per_extruder; + return layers_to_print; } - // if any reserved keyword is found, returns a std::vector containing the first MAX_COUNT keywords found - // into pairs containing: - // first: source - // second: keyword - // to be shown in the warning notification - // The returned vector is empty if no keyword has been found - static std::vector> validate_custom_gcode(const Print& print) { - static const unsigned int MAX_TAGS_COUNT = 5; - std::vector> ret; - - auto check = [&ret](const std::string& source, const std::string& gcode) { - std::vector tags; - if (GCodeProcessor::contains_reserved_tags(gcode, MAX_TAGS_COUNT, tags)) { - if (!tags.empty()) { - size_t i = 0; - while (ret.size() < MAX_TAGS_COUNT && i < tags.size()) { - ret.push_back({ source, tags[i] }); - ++i; - } - } - } + // Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z + // will be printed for all objects at once. + // Return a list of items. + std::vector>> GCode::collect_layers_to_print(const Print &print) + { + struct OrderingItem + { + coordf_t print_z; + size_t object_idx; + size_t layer_idx; }; - const GCodeConfig& config = print.config(); - check(_(L("Machine start G-code")), config.machine_start_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Machine end G-code")), config.machine_end_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Before layer change G-code")), config.before_layer_change_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Layer change G-code")), config.layer_change_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Time lapse G-code")), config.time_lapse_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Time lapse G-code")), config.wrapping_detection_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Change filament G-code")), config.change_filament_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Printing by object G-code")), config.printing_by_object_gcode.value); - //if (ret.size() < MAX_TAGS_COUNT) check(_(L("Color Change G-code")), config.color_change_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Pause G-code")), config.machine_pause_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Template Custom G-code")), config.template_custom_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) { - for (const std::string& value : config.filament_start_gcode.values) { - check(_(L("Filament start G-code")), value); - if (ret.size() == MAX_TAGS_COUNT) - break; + std::vector> per_object(print.objects().size(), std::vector()); + std::vector ordering; + + std::vector errors; + + for (size_t i = 0; i < print.objects().size(); ++i) + { + try + { + per_object[i] = collect_layers_to_print(*print.objects()[i]); } - } - if (ret.size() < MAX_TAGS_COUNT) { - for (const std::string& value : config.filament_end_gcode.values) { - check(_(L("Filament end G-code")), value); - if (ret.size() == MAX_TAGS_COUNT) - break; + catch (const Slic3r::SlicingError &e) + { + errors.push_back(e); + continue; + } + OrderingItem ordering_item; + ordering_item.object_idx = i; + ordering.reserve(ordering.size() + per_object[i].size()); + const LayerToPrint &front = per_object[i].front(); + for (const LayerToPrint <p : per_object[i]) + { + ordering_item.print_z = ltp.print_z(); + ordering_item.layer_idx = <p - &front; + ordering.emplace_back(ordering_item); } } - //BBS: no custom_gcode_per_print_z, don't need to check - //if (ret.size() < MAX_TAGS_COUNT) { - // const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z; - // for (const auto& gcode : custom_gcode_per_print_z.gcodes) { - // check(_(L("Custom G-code")), gcode.extra); - // if (ret.size() == MAX_TAGS_COUNT) - // break; - // } - //} - - return ret; - } -} // namespace DoExport - -bool GCode::is_BBL_Printer() -{ - if (m_curr_print) - return m_curr_print->is_BBL_Printer(); - return false; -} -//BBS : get the plate model's projection on first layer, contain plate offset,unscaled data -BoundingBoxf GCode::first_layer_projection(const Print& print) const -{ - BoundingBoxf bbox; - for (auto& obj : print.objects()) { - for (auto& instance : obj->instances()) { - auto instance_bbox = instance.get_bounding_box(); - bbox.merge(BoundingBoxf{ { instance_bbox.min.x(),instance_bbox.min.y() }, { instance_bbox.max.x(),instance_bbox.max.y() } }); + if (!errors.empty()) + { + throw Slic3r::SlicingErrors(errors); } - } - Pointfs points; - auto first_layer_point = print.first_layer_convex_hull().points; - points.reserve(first_layer_point.size()); - for (const auto& pt : first_layer_point) - points.emplace_back(unscale(pt.x(),pt.y())); - BoundingBoxf initial_layer_bbox(points); + std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) + { return oi1.print_z < oi2.print_z; }); - bbox.merge(initial_layer_bbox); - return bbox; -} - -void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) -{ - PROFILE_CLEAR(); + std::vector>> layers_to_print; - // BBS - m_curr_print = print; + // Merge numerically very close Z values. + for (size_t i = 0; i < ordering.size();) + { + // Find the last layer with roughly the same print_z. + size_t j = i + 1; + coordf_t zmax = ordering[i].print_z + EPSILON; + for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j) + ; + // Merge into layers_to_print. + std::pair> merged; + // Assign an average print_z to the set of layers with nearly equal print_z. + merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z); + merged.second.assign(print.objects().size(), LayerToPrint()); + for (; i < j; ++i) + { + const OrderingItem &oi = ordering[i]; + assert(merged.second[oi.object_idx].layer() == nullptr); + merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]); + } + layers_to_print.emplace_back(std::move(merged)); + } - CNumericLocalesSetter locales_setter; + return layers_to_print; + } - // Does the file exist? If so, we hope that it is still valid. - if (print->is_step_done(psGCodeExport) && boost::filesystem::exists(boost::filesystem::path(path))) - return; + // free functions called by GCode::do_export() + namespace DoExport + { + // static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) + // { + // const GCodeProcessorResult& result = processor.get_result(); + // print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); + // print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? + // get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; + // } - BOOST_LOG_TRIVIAL(info) << boost::format("Will export G-code to %1% soon") % PathSanitizer::sanitize(path); - - GCodeProcessor::s_IsBBLPrinter = print->is_BBL_Printer(); - m_writer.set_is_bbl_printer(print->is_BBL_Printer()); - print->set_started(psGCodeExport); - - // check if any custom gcode contains keywords used by the gcode processor to - // produce time estimation and gcode toolpaths - std::vector> validation_res = DoExport::validate_custom_gcode(*print); - if (!validation_res.empty()) { - std::string reports; - for (const auto& [source, keyword] : validation_res) { - reports += source + ": \"" + keyword + "\"\n"; - } - //print->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, - // _(L("In the custom G-code were found reserved keywords:")) + "\n" + - // reports + - // _(L("This may cause problems in g-code visualization and printing time estimation."))); - std::string temp = "Dangerous keywords in custom Gcode: " + reports + "\nThis may cause problems in g-code visualization and printing time estimation."; - BOOST_LOG_TRIVIAL(warning) << temp; - } + static void update_print_estimated_stats(const GCodeProcessor &processor, const std::vector &extruders, PrintStatistics &print_statistics) + { + const GCodeProcessorResult &result = processor.get_result(); + print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; - BOOST_LOG_TRIVIAL(info) << "Exporting G-code..." << log_memory_info(); + // update filament statictics + double total_extruded_volume = 0.0; + double total_used_filament = 0.0; + double total_weight = 0.0; + double total_cost = 0.0; - // Remove the old g-code if it exists. - boost::nowide::remove(path); + for (auto volume : result.print_statistics.total_volumes_per_extruder) + { + total_extruded_volume += volume.second; - fs::path file_path(path); - fs::path folder = file_path.parent_path(); - if (!fs::exists(folder)) { - fs::create_directory(folder); - BOOST_LOG_TRIVIAL(error) << "[WARNING]: the parent path " + PathSanitizer::sanitize(folder) + " is not there, create it!" << std::endl; - } + size_t extruder_id = volume.first; + auto extruder = std::find_if(extruders.begin(), extruders.end(), [extruder_id](const Extruder &extr) + { return extr.id() == extruder_id; }); + if (extruder == extruders.end()) + continue; - std::string path_tmp(path); - path_tmp += ".tmp"; + double s = PI * sqr(0.5 * extruder->filament_diameter()); + double weight = volume.second * extruder->filament_density() * 0.001; + total_used_filament += volume.second / s; + total_weight += weight; + total_cost += weight * extruder->filament_cost() * 0.001; + } - m_processor.initialize(path_tmp); - if(print->get_nozzle_group_result().has_value()) - m_processor.initialize_from_context(print->get_nozzle_group_result().value()); - GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); - if (! file.is_open()) { - BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed.\nCannot open the file for writing.\n" << std::endl; - if (!fs::exists(folder)) { - //fs::create_directory(folder); - BOOST_LOG_TRIVIAL(error) << "the parent path " + PathSanitizer::sanitize(folder) + " is not there!!!" << std::endl; - } - throw Slic3r::RuntimeError(std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed.\nCannot open the file for writing.\n"); - } + print_statistics.total_extruded_volume = total_extruded_volume; + print_statistics.total_used_filament = total_used_filament; + print_statistics.total_weight = total_weight; + print_statistics.total_cost = total_cost; - try { - m_placeholder_parser_failed_templates.clear(); - this->_do_export(*print, file, thumbnail_cb); - file.flush(); - if (file.is_error()) { - file.close(); - boost::nowide::remove(path_tmp.c_str()); - throw Slic3r::RuntimeError(std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed\nIs the disk full?\n"); + print_statistics.filament_stats = result.print_statistics.model_volumes_per_extruder; } - } catch (std::exception & /* ex */) { - // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. - // Close and remove the file. - file.close(); - boost::nowide::remove(path_tmp.c_str()); - throw; - } - file.close(); - check_placeholder_parser_failed(); - - BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); - // Post-process the G-code to update time stamps. - // BBS: FIX: layers count error, while the last layer extrude is empty - // spiral_vase_layer can't get right height - while (!m_processor.result().spiral_vase_layers.empty()) { - if (m_processor.result().spiral_vase_layers.back().first != FLT_MAX) - break; - //record last move, update prev layer move range - int last_move = m_processor.result().spiral_vase_layers.back().second.second; - m_processor.result().spiral_vase_layers.pop_back(); - m_processor.result().spiral_vase_layers.back().second.second = last_move; - } + // if any reserved keyword is found, returns a std::vector containing the first MAX_COUNT keywords found + // into pairs containing: + // first: source + // second: keyword + // to be shown in the warning notification + // The returned vector is empty if no keyword has been found + static std::vector> validate_custom_gcode(const Print &print) + { + static const unsigned int MAX_TAGS_COUNT = 5; + std::vector> ret; - m_timelapse_warning_code = 0; - if (m_config.printer_structure.value == PrinterStructure::psI3 && m_spiral_vase) { - m_timelapse_warning_code += 1; - } - if (m_config.printer_structure.value == PrinterStructure::psI3 && print->config().print_sequence == PrintSequence::ByObject) { - m_timelapse_warning_code += (1 << 1); + auto check = [&ret](const std::string &source, const std::string &gcode) + { + std::vector tags; + if (GCodeProcessor::contains_reserved_tags(gcode, MAX_TAGS_COUNT, tags)) + { + if (!tags.empty()) + { + size_t i = 0; + while (ret.size() < MAX_TAGS_COUNT && i < tags.size()) + { + ret.push_back({source, tags[i]}); + ++i; + } + } + } + }; + + const GCodeConfig &config = print.config(); + check(_(L("Machine start G-code")), config.machine_start_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Machine end G-code")), config.machine_end_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Before layer change G-code")), config.before_layer_change_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Layer change G-code")), config.layer_change_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Time lapse G-code")), config.time_lapse_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Time lapse G-code")), config.wrapping_detection_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Change filament G-code")), config.change_filament_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Printing by object G-code")), config.printing_by_object_gcode.value); + // if (ret.size() < MAX_TAGS_COUNT) check(_(L("Color Change G-code")), config.color_change_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Pause G-code")), config.machine_pause_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Template Custom G-code")), config.template_custom_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + { + for (const std::string &value : config.filament_start_gcode.values) + { + check(_(L("Filament start G-code")), value); + if (ret.size() == MAX_TAGS_COUNT) + break; + } + } + if (ret.size() < MAX_TAGS_COUNT) + { + for (const std::string &value : config.filament_end_gcode.values) + { + check(_(L("Filament end G-code")), value); + if (ret.size() == MAX_TAGS_COUNT) + break; + } + } + // BBS: no custom_gcode_per_print_z, don't need to check + // if (ret.size() < MAX_TAGS_COUNT) { + // const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z; + // for (const auto& gcode : custom_gcode_per_print_z.gcodes) { + // check(_(L("Custom G-code")), gcode.extra); + // if (ret.size() == MAX_TAGS_COUNT) + // break; + // } + // } + + return ret; + } + } // namespace DoExport + + bool GCode::is_BBL_Printer() + { + if (m_curr_print) + return m_curr_print->is_BBL_Printer(); + return false; } - if (m_config.timelapse_type.value == TimelapseType::tlSmooth && !m_config.enable_prime_tower.value) { - m_timelapse_warning_code += (1 << 2); + + // BBS : get the plate model's projection on first layer, contain plate offset,unscaled data + BoundingBoxf GCode::first_layer_projection(const Print &print) const + { + BoundingBoxf bbox; + for (auto &obj : print.objects()) + { + for (auto &instance : obj->instances()) + { + auto instance_bbox = instance.get_bounding_box(); + bbox.merge(BoundingBoxf{{instance_bbox.min.x(), instance_bbox.min.y()}, {instance_bbox.max.x(), instance_bbox.max.y()}}); + } + } + + Pointfs points; + auto first_layer_point = print.first_layer_convex_hull().points; + points.reserve(first_layer_point.size()); + for (const auto &pt : first_layer_point) + points.emplace_back(unscale(pt.x(), pt.y())); + BoundingBoxf initial_layer_bbox(points); + + bbox.merge(initial_layer_bbox); + return bbox; } - m_processor.result().timelapse_warning_code = m_timelapse_warning_code; - m_processor.result().support_traditional_timelapse = m_support_traditional_timelapse; - bool activate_long_retraction_when_cut = false; - for (const auto& filament : m_writer.extruders()) - activate_long_retraction_when_cut |= ( - m_config.long_retractions_when_cut.get_at(filament.id()) - && m_config.retraction_distances_when_cut.get_at(filament.id()) > 0 - ); + void GCode::do_export(Print *print, const char *path, GCodeProcessorResult *result, ThumbnailsGeneratorCallback thumbnail_cb) + { + PROFILE_CLEAR(); + + // BBS + m_curr_print = print; + + CNumericLocalesSetter locales_setter; + + // Does the file exist? If so, we hope that it is still valid. + if (print->is_step_done(psGCodeExport) && boost::filesystem::exists(boost::filesystem::path(path))) + return; - m_processor.result().long_retraction_when_cut = activate_long_retraction_when_cut; + BOOST_LOG_TRIVIAL(info) << boost::format("Will export G-code to %1% soon") % PathSanitizer::sanitize(path); - { //BBS:check bed and filament compatible - const ConfigOptionInts *bed_temp_opt = m_config.option(get_bed_temp_1st_layer_key(m_config.curr_bed_type)); - std::vector conflict_filament; - for(auto extruder_id : m_initial_layer_extruders){ - int cur_bed_temp = bed_temp_opt->get_at(extruder_id); - if (cur_bed_temp == 0) { - conflict_filament.push_back(extruder_id); + GCodeProcessor::s_IsBBLPrinter = print->is_BBL_Printer(); + m_writer.set_is_bbl_printer(print->is_BBL_Printer()); + print->set_started(psGCodeExport); + + // check if any custom gcode contains keywords used by the gcode processor to + // produce time estimation and gcode toolpaths + std::vector> validation_res = DoExport::validate_custom_gcode(*print); + if (!validation_res.empty()) + { + std::string reports; + for (const auto &[source, keyword] : validation_res) + { + reports += source + ": \"" + keyword + "\"\n"; } + // print->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + // _(L("In the custom G-code were found reserved keywords:")) + "\n" + + // reports + + // _(L("This may cause problems in g-code visualization and printing time estimation."))); + std::string temp = "Dangerous keywords in custom Gcode: " + reports + "\nThis may cause problems in g-code visualization and printing time estimation."; + BOOST_LOG_TRIVIAL(warning) << temp; } - m_processor.result().filament_printable_reuslt = FilamentPrintableResult(conflict_filament, bed_type_to_gcode_string(m_config.curr_bed_type)); - } - m_processor.set_filaments(m_writer.extruders()); - // check gcode is valid in machine printabele area and multi_extruder printabele area - int extruder_size = m_print->config().nozzle_diameter.values.size(); - std::vector extruder_unprintable_polys = m_print->get_extruder_unprintable_polygons(); - Pointfs plate_printable_area = m_print->config().printable_area.values; - Pointfs wrapping_exclude_area_points = m_print->config().wrapping_exclude_area.values; - m_processor.check_multi_extruder_gcode_valid(extruder_size, plate_printable_area, m_print->config().printable_height.value, wrapping_exclude_area_points, - extruder_unprintable_polys, m_print->get_extruder_printable_height(), m_print->get_filament_maps(), - m_print->get_physical_unprintable_filaments(m_print->get_slice_used_filaments(false))); - - m_processor.finalize(true); -// DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); - DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics); - if (result != nullptr) { - *result = std::move(m_processor.extract_result()); - // set the filename to the correct value - result->filename = path; - } + BOOST_LOG_TRIVIAL(info) << "Exporting G-code..." << log_memory_info(); - std::string path_safe = PathSanitizer::sanitize(path); - std::string path_tmp_safe = PathSanitizer::sanitize(path_tmp); - //BBS: add some log for error output - BOOST_LOG_TRIVIAL(debug) << boost::format("Finished processing gcode to %1% ") % path_tmp_safe; + // Remove the old g-code if it exists. + boost::nowide::remove(path); - std::error_code ret = rename_file(path_tmp, path); - if (ret) { - throw Slic3r::RuntimeError( - std::string("Failed to rename the output G-code file from ") + path_tmp_safe + " to " + path_safe + '\n' + "error code " + ret.message() + '\n' + - "Is " + path_tmp_safe + " locked?" + '\n'); - } - else { - BOOST_LOG_TRIVIAL(info) << boost::format("rename_file from %1% to %2% successfully")% path_tmp_safe % path_safe; - } + fs::path file_path(path); + fs::path folder = file_path.parent_path(); + if (!fs::exists(folder)) + { + fs::create_directory(folder); + BOOST_LOG_TRIVIAL(error) << "[WARNING]: the parent path " + PathSanitizer::sanitize(folder) + " is not there, create it!" << std::endl; + } - BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); - print->set_done(psGCodeExport); - //BBS: set enable_label_object - result->label_object_enabled = m_enable_label_object; - // Write the profiler measurements to file - PROFILE_UPDATE(); - PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str()); -} + std::string path_tmp(path); + path_tmp += ".tmp"; -// free functions called by GCode::_do_export() -namespace DoExport { - static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled,const std::vector& filaments) - { - silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware) - && config.silent_mode; - processor.reset(); - processor.apply_config(config); - processor.enable_stealth_time_estimator(silent_time_estimator_enabled); - processor.set_filaments(filaments); + m_processor.initialize(path_tmp); + if (print->get_nozzle_group_result().has_value()) + m_processor.initialize_from_context(print->get_nozzle_group_result().value()); + GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); + if (!file.is_open()) + { + BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed.\nCannot open the file for writing.\n" + << std::endl; + if (!fs::exists(folder)) + { + // fs::create_directory(folder); + BOOST_LOG_TRIVIAL(error) << "the parent path " + PathSanitizer::sanitize(folder) + " is not there!!!" << std::endl; + } + throw Slic3r::RuntimeError(std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed.\nCannot open the file for writing.\n"); + } + + try + { + m_placeholder_parser_failed_templates.clear(); + this->_do_export(*print, file, thumbnail_cb); + file.flush(); + if (file.is_error()) + { + file.close(); + boost::nowide::remove(path_tmp.c_str()); + throw Slic3r::RuntimeError(std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed\nIs the disk full?\n"); + } + } + catch (std::exception & /* ex */) + { + // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. + // Close and remove the file. + file.close(); + boost::nowide::remove(path_tmp.c_str()); + throw; + } + file.close(); + + check_placeholder_parser_failed(); + + BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); + // Post-process the G-code to update time stamps. + // BBS: FIX: layers count error, while the last layer extrude is empty + // spiral_vase_layer can't get right height + while (!m_processor.result().spiral_vase_layers.empty()) + { + if (m_processor.result().spiral_vase_layers.back().first != FLT_MAX) + break; + // record last move, update prev layer move range + int last_move = m_processor.result().spiral_vase_layers.back().second.second; + m_processor.result().spiral_vase_layers.pop_back(); + m_processor.result().spiral_vase_layers.back().second.second = last_move; + } + + m_timelapse_warning_code = 0; + if (m_config.printer_structure.value == PrinterStructure::psI3 && m_spiral_vase) + { + m_timelapse_warning_code += 1; + } + if (m_config.printer_structure.value == PrinterStructure::psI3 && print->config().print_sequence == PrintSequence::ByObject) + { + m_timelapse_warning_code += (1 << 1); + } + if (m_config.timelapse_type.value == TimelapseType::tlSmooth && !m_config.enable_prime_tower.value) + { + m_timelapse_warning_code += (1 << 2); + } + m_processor.result().timelapse_warning_code = m_timelapse_warning_code; + m_processor.result().support_traditional_timelapse = m_support_traditional_timelapse; + + bool activate_long_retraction_when_cut = false; + for (const auto &filament : m_writer.extruders()) + activate_long_retraction_when_cut |= (m_config.long_retractions_when_cut.get_at(filament.id()) && m_config.retraction_distances_when_cut.get_at(filament.id()) > 0); + + m_processor.result().long_retraction_when_cut = activate_long_retraction_when_cut; + + { // BBS:check bed and filament compatible + const ConfigOptionInts *bed_temp_opt = m_config.option(get_bed_temp_1st_layer_key(m_config.curr_bed_type)); + std::vector conflict_filament; + for (auto extruder_id : m_initial_layer_extruders) + { + int cur_bed_temp = bed_temp_opt->get_at(extruder_id); + if (cur_bed_temp == 0) + { + conflict_filament.push_back(extruder_id); + } + } + + m_processor.result().filament_printable_reuslt = FilamentPrintableResult(conflict_filament, bed_type_to_gcode_string(m_config.curr_bed_type)); + } + m_processor.set_filaments(m_writer.extruders()); + // check gcode is valid in machine printabele area and multi_extruder printabele area + int extruder_size = m_print->config().nozzle_diameter.values.size(); + std::vector extruder_unprintable_polys = m_print->get_extruder_unprintable_polygons(); + Pointfs plate_printable_area = m_print->config().printable_area.values; + Pointfs wrapping_exclude_area_points = m_print->config().wrapping_exclude_area.values; + m_processor.check_multi_extruder_gcode_valid(extruder_size, plate_printable_area, m_print->config().printable_height.value, wrapping_exclude_area_points, + extruder_unprintable_polys, m_print->get_extruder_printable_height(), m_print->get_filament_maps(), + m_print->get_physical_unprintable_filaments(m_print->get_slice_used_filaments(false))); + + m_processor.finalize(true); + // DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); + DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics); + if (result != nullptr) + { + *result = std::move(m_processor.extract_result()); + // set the filename to the correct value + result->filename = path; + } + + std::string path_safe = PathSanitizer::sanitize(path); + std::string path_tmp_safe = PathSanitizer::sanitize(path_tmp); + // BBS: add some log for error output + BOOST_LOG_TRIVIAL(debug) << boost::format("Finished processing gcode to %1% ") % path_tmp_safe; + + std::error_code ret = rename_file(path_tmp, path); + if (ret) + { + throw Slic3r::RuntimeError( + std::string("Failed to rename the output G-code file from ") + path_tmp_safe + " to " + path_safe + '\n' + "error code " + ret.message() + '\n' + + "Is " + path_tmp_safe + " locked?" + '\n'); + } + else + { + BOOST_LOG_TRIVIAL(info) << boost::format("rename_file from %1% to %2% successfully") % path_tmp_safe % path_safe; + } + + BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); + print->set_done(psGCodeExport); + // BBS: set enable_label_object + result->label_object_enabled = m_enable_label_object; + // Write the profiler measurements to file + PROFILE_UPDATE(); + PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str()); } + // free functions called by GCode::_do_export() + namespace DoExport + { + static void init_gcode_processor(const PrintConfig &config, GCodeProcessor &processor, bool &silent_time_estimator_enabled, const std::vector &filaments) + { + silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware) && config.silent_mode; + processor.reset(); + processor.apply_config(config); + processor.enable_stealth_time_estimator(silent_time_estimator_enabled); + processor.set_filaments(filaments); + } + #if 0 static double autospeed_volumetric_limit(const Print &print) { @@ -1792,27 +1967,30 @@ namespace DoExport { } #endif - static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) - { - // Calculate wiping points if needed - if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) { - Points skirt_points; - for (const ExtrusionEntity *ee : print.skirt().entities) - for (const ExtrusionPath &path : dynamic_cast(ee)->paths) - append(skirt_points, path.polyline.points); - if (! skirt_points.empty()) { - Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); - Polygons skirts; - auto filament_extruder_map = print.config().filament_map.values; // 1 based idxs - for (unsigned int filament_id : print.extruders()) { - const Vec2d& extruder_offset = print.config().extruder_offset.get_at(filament_extruder_map[filament_id] - 1); - Polygon s(outer_skirt); - s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1))); - skirts.emplace_back(std::move(s)); - } - ooze_prevention.enable = true; - ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.))); - #if 0 + static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) + { + // Calculate wiping points if needed + if (print.config().ooze_prevention.value && !print.config().single_extruder_multi_material) + { + Points skirt_points; + for (const ExtrusionEntity *ee : print.skirt().entities) + for (const ExtrusionPath &path : dynamic_cast(ee)->paths) + append(skirt_points, path.polyline.points); + if (!skirt_points.empty()) + { + Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); + Polygons skirts; + auto filament_extruder_map = print.config().filament_map.values; // 1 based idxs + for (unsigned int filament_id : print.extruders()) + { + const Vec2d &extruder_offset = print.config().extruder_offset.get_at(filament_extruder_map[filament_id] - 1); + Polygon s(outer_skirt); + s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1))); + skirts.emplace_back(std::move(s)); + } + ooze_prevention.enable = true; + ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.))); +#if 0 require "Slic3r/SVG.pm"; Slic3r::SVG::output( "ooze_prevention.svg", @@ -1820,115 +1998,122 @@ namespace DoExport { polygons => [$outer_skirt], points => $gcodegen->ooze_prevention->standby_points, ); - #endif - } - } - } +#endif + } + } + } - //BBS: add plate id for thumbnail generate param - template - static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, int plate_id, const std::vector &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) - { - // Write thumbnails using base64 encoding - if (thumbnail_cb != nullptr) - { - const size_t max_row_length = 78; - //BBS: add plate id for thumbnail generate param - ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true, plate_id }); - for (const ThumbnailData& data : thumbnails) - { - if (data.is_valid()) - { - size_t png_size = 0; - void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); - if (png_data != nullptr) - { - std::string encoded; - encoded.resize(boost::beast::detail::base64::encoded_size(png_size)); - encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size)); - - output("; THUMBNAIL_BLOCK_START\n"); - output((boost::format("; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str()); - - unsigned int row_count = 0; - //BBS: optimize performance ,reduce too much memeory operation - size_t current_index = 0; - while(current_index + static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, int plate_id, const std::vector &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) + { + // Write thumbnails using base64 encoding + if (thumbnail_cb != nullptr) + { + const size_t max_row_length = 78; + // BBS: add plate id for thumbnail generate param + ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{sizes, true, true, true, true, plate_id}); + for (const ThumbnailData &data : thumbnails) + { + if (data.is_valid()) + { + size_t png_size = 0; + void *png_data = tdefl_write_image_to_png_file_in_memory_ex((const void *)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); + if (png_data != nullptr) + { + std::string encoded; + encoded.resize(boost::beast::detail::base64::encoded_size(png_size)); + encoded.resize(boost::beast::detail::base64::encode((void *)&encoded[0], (const void *)png_data, png_size)); + + output("; THUMBNAIL_BLOCK_START\n"); + output((boost::format("; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str()); + + unsigned int row_count = 0; + // BBS: optimize performance ,reduce too much memeory operation + size_t current_index = 0; + while (current_index < encoded.size()) + { + output((boost::format("; %s\n") % encoded.substr(current_index, max_row_length)).str().c_str()); + current_index += std::min(max_row_length, encoded.size() - current_index); + ++row_count; + } + output("; thumbnail end\n"); + output("; THUMBNAIL_BLOCK_END\n\n"); - // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. - static std::string update_print_stats_and_format_filament_stats( - const bool has_wipe_tower, - const WipeTowerData &wipe_tower_data, - const std::vector &extruders, - PrintStatistics &print_statistics) - { - std::string filament_stats_string_out; - - print_statistics.clear(); - print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); - if (! extruders.empty()) { - //std::pair out_filament_used_mm ("; filament used [mm] = ", 0); - //std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); - //std::pair out_filament_used_g ("; filament used [g] = ", 0); - //std::pair out_filament_cost ("; filament cost = ", 0); - for (const Extruder &extruder : extruders) { - double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); - double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter - double filament_weight = extruded_volume * extruder.filament_density() * 0.001; - double filament_cost = filament_weight * extruder.filament_cost() * 0.001; - auto append = [&extruder](std::pair &dst, const char *tmpl, double value) { - assert(is_decimal_separator_point()); - while (dst.second < extruder.id()) { - // Fill in the non-printing extruders with zeros. - dst.first += (dst.second > 0) ? ", 0" : "0"; - ++ dst.second; - } - if (dst.second > 0) - dst.first += ", "; - char buf[64]; - sprintf(buf, tmpl, value); - dst.first += buf; - ++ dst.second; - }; - //append(out_filament_used_mm, "%.2lf", used_filament); - //append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); - if (filament_weight > 0.) { - print_statistics.total_weight = print_statistics.total_weight + filament_weight; - //append(out_filament_used_g, "%.2lf", filament_weight); - if (filament_cost > 0.) { - print_statistics.total_cost = print_statistics.total_cost + filament_cost; - //append(out_filament_cost, "%.2lf", filament_cost); - } - } - print_statistics.total_used_filament += used_filament; - print_statistics.total_extruded_volume += extruded_volume; - print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; - print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; - } - //filament_stats_string_out += out_filament_used_mm.first; - //filament_stats_string_out += "\n" + out_filament_used_cm3.first; - //if (out_filament_used_g.second) - //filament_stats_string_out += "\n" + out_filament_used_g.first; - //if (out_filament_cost.second) - // filament_stats_string_out += "\n" + out_filament_cost.first; - } - return filament_stats_string_out; + mz_free(png_data); + } + } + throw_if_canceled(); + } + } + } + + // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. + static std::string update_print_stats_and_format_filament_stats( + const bool has_wipe_tower, + const WipeTowerData &wipe_tower_data, + const std::vector &extruders, + PrintStatistics &print_statistics) + { + std::string filament_stats_string_out; + + print_statistics.clear(); + print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); + if (!extruders.empty()) + { + // std::pair out_filament_used_mm ("; filament used [mm] = ", 0); + // std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); + // std::pair out_filament_used_g ("; filament used [g] = ", 0); + // std::pair out_filament_cost ("; filament cost = ", 0); + for (const Extruder &extruder : extruders) + { + double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); + double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter + double filament_weight = extruded_volume * extruder.filament_density() * 0.001; + double filament_cost = filament_weight * extruder.filament_cost() * 0.001; + auto append = [&extruder](std::pair &dst, const char *tmpl, double value) + { + assert(is_decimal_separator_point()); + while (dst.second < extruder.id()) + { + // Fill in the non-printing extruders with zeros. + dst.first += (dst.second > 0) ? ", 0" : "0"; + ++dst.second; + } + if (dst.second > 0) + dst.first += ", "; + char buf[64]; + sprintf(buf, tmpl, value); + dst.first += buf; + ++dst.second; + }; + // append(out_filament_used_mm, "%.2lf", used_filament); + // append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); + if (filament_weight > 0.) + { + print_statistics.total_weight = print_statistics.total_weight + filament_weight; + // append(out_filament_used_g, "%.2lf", filament_weight); + if (filament_cost > 0.) + { + print_statistics.total_cost = print_statistics.total_cost + filament_cost; + // append(out_filament_cost, "%.2lf", filament_cost); + } + } + print_statistics.total_used_filament += used_filament; + print_statistics.total_extruded_volume += extruded_volume; + print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; + print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume()) * extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; + } + // filament_stats_string_out += out_filament_used_mm.first; + // filament_stats_string_out += "\n" + out_filament_used_cm3.first; + // if (out_filament_used_g.second) + // filament_stats_string_out += "\n" + out_filament_used_g.first; + // if (out_filament_cost.second) + // filament_stats_string_out += "\n" + out_filament_cost.first; + } + return filament_stats_string_out; + } } -} #if 0 // Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints. @@ -1945,264 +2130,285 @@ static inline std::vector sort_object_instances_by_max_z(c } #endif -// Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model(). -//BBS: add sort logic for seq-print -std::vector sort_object_instances_by_model_order(const Print& print, bool init_order) -{ - // Build up map from ModelInstance* to PrintInstance* - std::vector> model_instance_to_print_instance; - model_instance_to_print_instance.reserve(print.num_object_instances()); - for (const PrintObject *print_object : print.objects()) - for (const PrintInstance &print_instance : print_object->instances()) - { - if (init_order) - const_cast(print_instance.model_instance)->arrange_order = print_instance.model_instance->id().id; - model_instance_to_print_instance.emplace_back(print_instance.model_instance, &print_instance); - } - std::sort(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), [](auto &l, auto &r) { return l.first->arrange_order < r.first->arrange_order; }); - - std::vector instances; - instances.reserve(model_instance_to_print_instance.size()); - for (const ModelObject *model_object : print.model().objects) - for (const ModelInstance *model_instance : model_object->instances) { - auto it = std::lower_bound(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), std::make_pair(model_instance, nullptr), [](auto &l, auto &r) { return l.first->arrange_order < r.first->arrange_order; }); - if (it != model_instance_to_print_instance.end() && it->first == model_instance) - instances.emplace_back(it->second); - } - std::sort(instances.begin(), instances.end(), [](auto& l, auto& r) { return l->model_instance->arrange_order < r->model_instance->arrange_order; }); - return instances; -} + // Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model(). + // BBS: add sort logic for seq-print + std::vector sort_object_instances_by_model_order(const Print &print, bool init_order) + { + // Build up map from ModelInstance* to PrintInstance* + std::vector> model_instance_to_print_instance; + model_instance_to_print_instance.reserve(print.num_object_instances()); + for (const PrintObject *print_object : print.objects()) + for (const PrintInstance &print_instance : print_object->instances()) + { + if (init_order) + const_cast(print_instance.model_instance)->arrange_order = print_instance.model_instance->id().id; + model_instance_to_print_instance.emplace_back(print_instance.model_instance, &print_instance); + } + std::sort(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), [](auto &l, auto &r) + { return l.first->arrange_order < r.first->arrange_order; }); -enum BambuBedType { - bbtUnknown = 0, - bbtCoolPlate = 1, - bbtEngineeringPlate = 2, - bbtHighTemperaturePlate = 3, - bbtTexturedPEIPlate = 4, - bbtSuperTackPlate = 5, -}; + std::vector instances; + instances.reserve(model_instance_to_print_instance.size()); + for (const ModelObject *model_object : print.model().objects) + for (const ModelInstance *model_instance : model_object->instances) + { + auto it = std::lower_bound(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), std::make_pair(model_instance, nullptr), [](auto &l, auto &r) + { return l.first->arrange_order < r.first->arrange_order; }); + if (it != model_instance_to_print_instance.end() && it->first == model_instance) + instances.emplace_back(it->second); + } + std::sort(instances.begin(), instances.end(), [](auto &l, auto &r) + { return l->model_instance->arrange_order < r->model_instance->arrange_order; }); + return instances; + } -static BambuBedType to_bambu_bed_type(BedType type) -{ - BambuBedType bambu_bed_type = bbtUnknown; - if (type == btPC) - bambu_bed_type = bbtCoolPlate; - else if (type == btEP) - bambu_bed_type = bbtEngineeringPlate; - else if (type == btPEI) - bambu_bed_type = bbtHighTemperaturePlate; - else if (type == btPTE) - bambu_bed_type = bbtTexturedPEIPlate; - else if (type == btSuperTack) - bambu_bed_type = bbtSuperTackPlate; - - return bambu_bed_type; -} + enum BambuBedType + { + bbtUnknown = 0, + bbtCoolPlate = 1, + bbtEngineeringPlate = 2, + bbtHighTemperaturePlate = 3, + bbtTexturedPEIPlate = 4, + bbtSuperTackPlate = 5, + }; -void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) -{ - PROFILE_FUNC(); - - m_print = &print; - m_timelapse_pos_picker.init(&print,m_writer.get_xy_offset().cast()); - - // modifies m_silent_time_estimator_enabled - DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled, m_writer.extruders()); - // resets analyzer's tracking data - m_last_height = 0.f; - m_last_layer_z = 0.f; - m_max_layer_z = 0.f; - m_last_width = 0.f; + static BambuBedType to_bambu_bed_type(BedType type) + { + BambuBedType bambu_bed_type = bbtUnknown; + if (type == btPC) + bambu_bed_type = bbtCoolPlate; + else if (type == btEP) + bambu_bed_type = bbtEngineeringPlate; + else if (type == btPEI) + bambu_bed_type = bbtHighTemperaturePlate; + else if (type == btPTE) + bambu_bed_type = bbtTexturedPEIPlate; + else if (type == btSuperTack) + bambu_bed_type = bbtSuperTackPlate; + + return bambu_bed_type; + } + + void GCode::_do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) + { + PROFILE_FUNC(); + + m_print = &print; + m_timelapse_pos_picker.init(&print, m_writer.get_xy_offset().cast()); + + // modifies m_silent_time_estimator_enabled + DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled, m_writer.extruders()); + // resets analyzer's tracking data + m_last_height = 0.f; + m_last_layer_z = 0.f; + m_max_layer_z = 0.f; + m_last_width = 0.f; #if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_last_mm3_per_mm = 0.; + m_last_mm3_per_mm = 0.; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - // How many times will be change_layer() called? - // change_layer() in turn increments the progress bar status. - m_layer_count = 0; - if (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() > 1) { - // Add each of the object's layers separately. - for (auto object : print.objects()) { + // How many times will be change_layer() called? + // change_layer() in turn increments the progress bar status. + m_layer_count = 0; + if (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() > 1) + { + // Add each of the object's layers separately. + for (auto object : print.objects()) + { + std::vector zs; + zs.reserve(object->layers().size() + object->support_layers().size()); + for (auto layer : object->layers()) + zs.push_back(layer->print_z); + for (auto layer : object->support_layers()) + zs.push_back(layer->print_z); + std::sort(zs.begin(), zs.end()); + // BBS: merge numerically very close Z values. + auto end_it = std::unique(zs.begin(), zs.end()); + unsigned int temp_layer_count = (unsigned int)(end_it - zs.begin()); + for (auto it = zs.begin(); it != end_it - 1; it++) + { + if (abs(*it - *(it + 1)) < EPSILON) + temp_layer_count--; + } + m_layer_count += (unsigned int)(object->instances().size() * temp_layer_count); + } + } + else + { + // Print all objects with the same print_z together. std::vector zs; - zs.reserve(object->layers().size() + object->support_layers().size()); - for (auto layer : object->layers()) - zs.push_back(layer->print_z); - for (auto layer : object->support_layers()) - zs.push_back(layer->print_z); - std::sort(zs.begin(), zs.end()); - //BBS: merge numerically very close Z values. - auto end_it = std::unique(zs.begin(), zs.end()); - unsigned int temp_layer_count = (unsigned int)(end_it - zs.begin()); - for (auto it = zs.begin(); it != end_it - 1; it++) { - if (abs(*it - *(it + 1)) < EPSILON) - temp_layer_count--; - } - m_layer_count += (unsigned int)(object->instances().size() * temp_layer_count); - } - } else { - // Print all objects with the same print_z together. - std::vector zs; - for (auto object : print.objects()) { - zs.reserve(zs.size() + object->layers().size() + object->support_layers().size()); - for (auto layer : object->layers()) - zs.push_back(layer->print_z); - for (auto layer : object->support_layers()) - zs.push_back(layer->print_z); - } - if (!zs.empty()) - { - std::sort(zs.begin(), zs.end()); - //BBS: merge numerically very close Z values. - auto end_it = std::unique(zs.begin(), zs.end()); - m_layer_count = (unsigned int)(end_it - zs.begin()); - for (auto it = zs.begin(); it != end_it - 1; it++) { - if (abs(*it - *(it + 1)) < EPSILON) - m_layer_count--; + for (auto object : print.objects()) + { + zs.reserve(zs.size() + object->layers().size() + object->support_layers().size()); + for (auto layer : object->layers()) + zs.push_back(layer->print_z); + for (auto layer : object->support_layers()) + zs.push_back(layer->print_z); + } + if (!zs.empty()) + { + std::sort(zs.begin(), zs.end()); + // BBS: merge numerically very close Z values. + auto end_it = std::unique(zs.begin(), zs.end()); + m_layer_count = (unsigned int)(end_it - zs.begin()); + for (auto it = zs.begin(); it != end_it - 1; it++) + { + if (abs(*it - *(it + 1)) < EPSILON) + m_layer_count--; + } } } - } - print.throw_if_canceled(); + print.throw_if_canceled(); - m_enable_cooling_markers = true; - this->apply_print_config(print.config()); - m_config.apply(print.default_object_config()); - m_config.apply(print.default_region_config()); + m_enable_cooling_markers = true; + this->apply_print_config(print.config()); + m_config.apply(print.default_object_config()); + m_config.apply(print.default_region_config()); - //m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); - print.throw_if_canceled(); + // m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); + print.throw_if_canceled(); - if (print.config().spiral_mode.value) - m_spiral_vase = make_unique(print.config()); + if (print.config().spiral_mode.value) + m_spiral_vase = make_unique(print.config()); #ifdef HAS_PRESSURE_EQUALIZER - if (print.config().max_volumetric_extrusion_rate_slope_positive.value > 0 || - print.config().max_volumetric_extrusion_rate_slope_negative.value > 0) - m_pressure_equalizer = make_unique(&print.config()); - m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; -#else /* HAS_PRESSURE_EQUALIZER */ - m_enable_extrusion_role_markers = false; + if (print.config().max_volumetric_extrusion_rate_slope_positive.value > 0 || + print.config().max_volumetric_extrusion_rate_slope_negative.value > 0) + m_pressure_equalizer = make_unique(&print.config()); + m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; +#else /* HAS_PRESSURE_EQUALIZER */ + m_enable_extrusion_role_markers = false; #endif /* HAS_PRESSURE_EQUALIZER */ - file.write_format("; HEADER_BLOCK_START\n"); - // Write information on the generator. - file.write_format("; %s\n", Slic3r::header_slic3r_generated().c_str()); - //BBS: total estimated printing time - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); - //BBS: total layer number - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str()); - - //BBS: total filament used in mm - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder).c_str()); - //BBS: total filament used in cm3 - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Volume_Placeholder).c_str()); - //BBS: total filament used in g - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Weight_Placeholder).c_str()); - - //BBS: judge whether support skipping, if yes, list all label_object_id with sorted order here - if (print.num_object_instances() <= g_max_label_object && //Don't support too many objects on one plate - (print.num_object_instances() > 1) && //Don't support skipping single object - print.calib_params().mode == CalibMode::Calib_None) { //Don't support skipping in cali mode - m_enable_label_object = true; - m_label_objects_ids.clear(); - m_label_objects_ids.reserve(print.num_object_instances()); - for (const PrintObject* print_object : print.objects()) - for (const PrintInstance& print_instance : print_object->instances()) - m_label_objects_ids.push_back(print_instance.model_instance->get_labeled_id()); - - std::sort(m_label_objects_ids.begin(), m_label_objects_ids.end()); - - std::string objects_id_list = "; model label id: "; - for (auto it = m_label_objects_ids.begin(); it != m_label_objects_ids.end(); it++) - objects_id_list += (std::to_string(*it) + (it != m_label_objects_ids.end() - 1 ? "," : "\n")); - file.writeln(objects_id_list); - } - else { - m_enable_label_object = false; - m_label_objects_ids.clear(); - } + file.write_format("; HEADER_BLOCK_START\n"); + // Write information on the generator. + file.write_format("; %s\n", Slic3r::header_slic3r_generated().c_str()); + // BBS: total estimated printing time + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); + // BBS: total layer number + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str()); + + // BBS: total filament used in mm + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder).c_str()); + // BBS: total filament used in cm3 + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Volume_Placeholder).c_str()); + // BBS: total filament used in g + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Weight_Placeholder).c_str()); + + // BBS: judge whether support skipping, if yes, list all label_object_id with sorted order here + if (print.num_object_instances() <= g_max_label_object && // Don't support too many objects on one plate + (print.num_object_instances() > 1) && // Don't support skipping single object + print.calib_params().mode == CalibMode::Calib_None) + { // Don't support skipping in cali mode + m_enable_label_object = true; + m_label_objects_ids.clear(); + m_label_objects_ids.reserve(print.num_object_instances()); + for (const PrintObject *print_object : print.objects()) + for (const PrintInstance &print_instance : print_object->instances()) + m_label_objects_ids.push_back(print_instance.model_instance->get_labeled_id()); + + std::sort(m_label_objects_ids.begin(), m_label_objects_ids.end()); + + std::string objects_id_list = "; model label id: "; + for (auto it = m_label_objects_ids.begin(); it != m_label_objects_ids.end(); it++) + objects_id_list += (std::to_string(*it) + (it != m_label_objects_ids.end() - 1 ? "," : "\n")); + file.writeln(objects_id_list); + } + else + { + m_enable_label_object = false; + m_label_objects_ids.clear(); + } - { - std::string filament_density_list = "; filament_density: "; - (filament_density_list+=m_config.filament_density.serialize()) +='\n'; - file.writeln(filament_density_list); + { + std::string filament_density_list = "; filament_density: "; + (filament_density_list += m_config.filament_density.serialize()) += '\n'; + file.writeln(filament_density_list); - std::string filament_diameter_list = "; filament_diameter: "; - (filament_diameter_list += m_config.filament_diameter.serialize()) += '\n'; - file.writeln(filament_diameter_list); + std::string filament_diameter_list = "; filament_diameter: "; + (filament_diameter_list += m_config.filament_diameter.serialize()) += '\n'; + file.writeln(filament_diameter_list); - coordf_t max_height_z = -1; - for (const auto& object : print.objects()) - max_height_z = std::max(object->layers().back()->print_z, max_height_z); + coordf_t max_height_z = -1; + for (const auto &object : print.objects()) + max_height_z = std::max(object->layers().back()->print_z, max_height_z); - std::ostringstream max_height_z_tip; - max_height_z_tip<<"; max_z_height: " << std::fixed << std::setprecision(2) << max_height_z << '\n'; - file.writeln(max_height_z_tip.str()); - } + std::ostringstream max_height_z_tip; + max_height_z_tip << "; max_z_height: " << std::fixed << std::setprecision(2) << max_height_z << '\n'; + file.writeln(max_height_z_tip.str()); + } - { - auto used_filaments = print.get_slice_used_filaments(false); - std::ostringstream out; - out << "; filament: "; - for (size_t idx = 0; idx < used_filaments.size(); ++idx) { - if (idx != 0) - out << ','; - out << used_filaments[idx] + 1; - } - file.writeln(out.str()); - } + { + auto used_filaments = print.get_slice_used_filaments(false); + std::ostringstream out; + out << "; filament: "; + for (size_t idx = 0; idx < used_filaments.size(); ++idx) + { + if (idx != 0) + out << ','; + out << used_filaments[idx] + 1; + } + file.writeln(out.str()); + } - file.write_format("; HEADER_BLOCK_END\n\n"); + file.write_format("; HEADER_BLOCK_END\n\n"); - //BBS: write global config at the beginning of gcode file because printer need these config information - // Append full config, delimited by two 'phony' configuration keys CONFIG_BLOCK_START and CONFIG_BLOCK_END. - // The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer. - { - file.write("; CONFIG_BLOCK_START\n"); - std::string full_config; - DynamicPrintConfig print_cfg_temp = print.full_print_config(); - - {//correct the flush_volumes_matrix with flush_multiplier values - std::vector temp_cfg_flush_multiplier = print_cfg_temp.option("flush_multiplier")->values; - std::vector temp_flush_volumes_matrix = print_cfg_temp.option("flush_volumes_matrix")->values; - auto temp_filament_color = print_cfg_temp.option("filament_colour")->values; - size_t heads_count_tmp = temp_cfg_flush_multiplier.size(), - matrix_value_count = temp_flush_volumes_matrix.size() / temp_cfg_flush_multiplier.size(), - filament_count_tmp = temp_filament_color.size(); - if (filament_count_tmp * filament_count_tmp * heads_count_tmp == temp_flush_volumes_matrix.size()) { - for (size_t idx = 0; idx < heads_count_tmp; ++idx) { - double temp_cfg_flush_multiplier_idx = temp_cfg_flush_multiplier[idx]; - size_t temp_begin_t = idx * matrix_value_count, temp_end_t = (idx + 1) * matrix_value_count; - std::transform(temp_flush_volumes_matrix.begin() + temp_begin_t, - temp_flush_volumes_matrix.begin() + temp_end_t, - temp_flush_volumes_matrix.begin() + temp_begin_t, - [temp_cfg_flush_multiplier_idx](double inputx) { return inputx * temp_cfg_flush_multiplier_idx; }); + // BBS: write global config at the beginning of gcode file because printer need these config information + // Append full config, delimited by two 'phony' configuration keys CONFIG_BLOCK_START and CONFIG_BLOCK_END. + // The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer. + { + file.write("; CONFIG_BLOCK_START\n"); + std::string full_config; + DynamicPrintConfig print_cfg_temp = print.full_print_config(); + + { // correct the flush_volumes_matrix with flush_multiplier values + std::vector temp_cfg_flush_multiplier = print_cfg_temp.option("flush_multiplier")->values; + std::vector temp_flush_volumes_matrix = print_cfg_temp.option("flush_volumes_matrix")->values; + auto temp_filament_color = print_cfg_temp.option("filament_colour")->values; + size_t heads_count_tmp = temp_cfg_flush_multiplier.size(), + matrix_value_count = temp_flush_volumes_matrix.size() / temp_cfg_flush_multiplier.size(), + filament_count_tmp = temp_filament_color.size(); + if (filament_count_tmp * filament_count_tmp * heads_count_tmp == temp_flush_volumes_matrix.size()) + { + for (size_t idx = 0; idx < heads_count_tmp; ++idx) + { + double temp_cfg_flush_multiplier_idx = temp_cfg_flush_multiplier[idx]; + size_t temp_begin_t = idx * matrix_value_count, temp_end_t = (idx + 1) * matrix_value_count; + std::transform(temp_flush_volumes_matrix.begin() + temp_begin_t, + temp_flush_volumes_matrix.begin() + temp_end_t, + temp_flush_volumes_matrix.begin() + temp_begin_t, + [temp_cfg_flush_multiplier_idx](double inputx) + { return inputx * temp_cfg_flush_multiplier_idx; }); + } + print_cfg_temp.option("flush_volumes_matrix")->values = temp_flush_volumes_matrix; + } + else if (filament_count_tmp == 1 || print.calib_params().mode != CalibMode::Calib_None) + { + } // Not applicable to flush matrix situations + else + { // flush_volumes_matrix value count error? + throw Slic3r::SlicingError(_(L("Flush volumes matrix do not match to the correct size!"))); } - print_cfg_temp.option("flush_volumes_matrix")->values = temp_flush_volumes_matrix; - } else if (filament_count_tmp == 1 || print.calib_params().mode != CalibMode::Calib_None) { - }// Not applicable to flush matrix situations - else - { // flush_volumes_matrix value count error? - throw Slic3r::SlicingError(_(L("Flush volumes matrix do not match to the correct size!"))); } + print_cfg_temp.option("filament_map_2", true)->values = print.config().filament_map_2.values; + append_full_config(print_cfg_temp, full_config); + if (!full_config.empty()) + file.write(full_config); + file.write("; CONFIG_BLOCK_END\n\n"); } - print_cfg_temp.option("filament_map_2", true)->values = print.config().filament_map_2.values; - append_full_config(print_cfg_temp, full_config); - if (!full_config.empty()) - file.write(full_config); - file.write("; CONFIG_BLOCK_END\n\n"); - } - //BBS: add plate id into thumbnail render logic - if(!print.is_BBL_Printer()){ - DoExport::export_thumbnails_to_file(thumbnail_cb, print.get_plate_index(), print.full_print_config().option("thumbnail_size")->values, - [&file](const char* sz) { file.write(sz); }, - [&print]() { print.throw_if_canceled(); }); - } + // BBS: add plate id into thumbnail render logic + if (!print.is_BBL_Printer()) + { + DoExport::export_thumbnails_to_file(thumbnail_cb, print.get_plate_index(), print.full_print_config().option("thumbnail_size")->values, [&file](const char *sz) + { file.write(sz); }, [&print]() + { print.throw_if_canceled(); }); + } - // Write some terse information on the slicing parameters. - const PrintObject *first_object = print.objects().front(); - const double layer_height = first_object->config().layer_height.value; - const double initial_layer_print_height = print.config().initial_layer_print_height.value; - //BBS: remove useless information in gcode file + // Write some terse information on the slicing parameters. + const PrintObject *first_object = print.objects().front(); + const double layer_height = first_object->config().layer_height.value; + const double initial_layer_print_height = print.config().initial_layer_print_height.value; + // BBS: remove useless information in gcode file #if 0 for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) { const PrintRegion ®ion = print.get_print_region(region_id); @@ -2220,85 +2426,91 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); #endif - file.write_format("; EXECUTABLE_BLOCK_START\n"); - - // OrcaSlicer: Orca's implementation for skipping object, for klipper firmware printer only - if (this->config().exclude_object && print.config().gcode_flavor.value == gcfKlipper) - file.write(set_object_info(&print)); - - // adds tags for time estimators - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str()); - - // Prepare the helper object for replacing placeholders in custom G-code and output filename. - m_placeholder_parser = print.placeholder_parser(); - m_placeholder_parser.update_timestamp(); - m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count()); - print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); - - // Get optimal tool ordering to minimize tool switches of a multi-exruder print. - // For a print by objects, find the 1st printing object. - ToolOrdering tool_ordering; - unsigned int initial_extruder_id = (unsigned int)-1; - //BBS: first non-support filament extruder - unsigned int initial_non_support_extruder_id = (unsigned int) -1; - unsigned int final_extruder_id = (unsigned int)-1; - bool has_wipe_tower = false; - print.m_statistics_by_extruder_count.clear(); - std::vector first_filaments; - std::vector first_non_support_filaments; - std::vector print_object_instances_ordering; - std::vector::const_iterator print_object_instance_sequential_active; - std::vector::const_iterator first_has_extrude_print_object; - //resize - first_non_support_filaments.resize(print.config().nozzle_diameter.size(), -1); - first_filaments.resize(print.config().nozzle_diameter.size(), -1); - float max_additional_fan = 0.f; - if (print.config().print_sequence == PrintSequence::ByObject && print.objects().size()>1) { - // Order object instances for sequential print. - print_object_instances_ordering = sort_object_instances_by_model_order(print); -// print_object_instances_ordering = sort_object_instances_by_max_z(print); - // Find the 1st printing object, find its tool ordering and the initial extruder ID. - print_object_instance_sequential_active = print_object_instances_ordering.begin(); - first_has_extrude_print_object = print_object_instance_sequential_active; - bool find_fist_non_support_filament = false; - for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) { - tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); - - tool_ordering.sort_and_build_data(*(*print_object_instance_sequential_active)->print_object,initial_extruder_id); - float temp_max_additional_fan = tool_ordering.cal_max_additional_fan(print.config()); - if(temp_max_additional_fan > max_additional_fan ) - max_additional_fan = temp_max_additional_fan; - if (!find_fist_non_support_filament && tool_ordering.first_extruder() != (unsigned int) -1) { - //BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament - if (initial_extruder_id == (unsigned int) -1) { - initial_extruder_id = tool_ordering.first_extruder(); - first_has_extrude_print_object = print_object_instance_sequential_active; + file.write_format("; EXECUTABLE_BLOCK_START\n"); + + // OrcaSlicer: Orca's implementation for skipping object, for klipper firmware printer only + if (this->config().exclude_object && print.config().gcode_flavor.value == gcfKlipper) + file.write(set_object_info(&print)); + + // adds tags for time estimators + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str()); + + // Prepare the helper object for replacing placeholders in custom G-code and output filename. + m_placeholder_parser = print.placeholder_parser(); + m_placeholder_parser.update_timestamp(); + m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); + + // Get optimal tool ordering to minimize tool switches of a multi-exruder print. + // For a print by objects, find the 1st printing object. + ToolOrdering tool_ordering; + unsigned int initial_extruder_id = (unsigned int)-1; + // BBS: first non-support filament extruder + unsigned int initial_non_support_extruder_id = (unsigned int)-1; + unsigned int final_extruder_id = (unsigned int)-1; + bool has_wipe_tower = false; + print.m_statistics_by_extruder_count.clear(); + std::vector first_filaments; + std::vector first_non_support_filaments; + std::vector print_object_instances_ordering; + std::vector::const_iterator print_object_instance_sequential_active; + std::vector::const_iterator first_has_extrude_print_object; + // resize + first_non_support_filaments.resize(print.config().nozzle_diameter.size(), -1); + first_filaments.resize(print.config().nozzle_diameter.size(), -1); + float max_additional_fan = 0.f; + if (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() > 1) + { + // Order object instances for sequential print. + print_object_instances_ordering = sort_object_instances_by_model_order(print); + // print_object_instances_ordering = sort_object_instances_by_max_z(print); + // Find the 1st printing object, find its tool ordering and the initial extruder ID. + print_object_instance_sequential_active = print_object_instances_ordering.begin(); + first_has_extrude_print_object = print_object_instance_sequential_active; + bool find_fist_non_support_filament = false; + for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) + { + tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); + + tool_ordering.sort_and_build_data(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); + float temp_max_additional_fan = tool_ordering.cal_max_additional_fan(print.config()); + if (temp_max_additional_fan > max_additional_fan) + max_additional_fan = temp_max_additional_fan; + if (!find_fist_non_support_filament && tool_ordering.first_extruder() != (unsigned int)-1) + { + // BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament + if (initial_extruder_id == (unsigned int)-1) + { + initial_extruder_id = tool_ordering.first_extruder(); + first_has_extrude_print_object = print_object_instance_sequential_active; + } + + find_fist_non_support_filament = tool_ordering.cal_non_support_filaments(print.config(), initial_non_support_extruder_id, first_non_support_filaments, first_filaments); } + } + if (initial_extruder_id == static_cast(-1)) + // No object to print was found, cancel the G-code export. + throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small"))); + // We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode. + // Use the extruder IDs collected from Regions. + this->set_extruders(print.extruders()); - find_fist_non_support_filament = tool_ordering.cal_non_support_filaments(print.config(), initial_non_support_extruder_id, first_non_support_filaments, first_filaments); - } - } - if (initial_extruder_id == static_cast(-1)) - // No object to print was found, cancel the G-code export. - throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small"))); - // We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode. - // Use the extruder IDs collected from Regions. - this->set_extruders(print.extruders()); - - has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); - } else { - // Find tool ordering for all the objects at once, and the initial extruder ID. - // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. - tool_ordering = print.tool_ordering(); - tool_ordering.assign_custom_gcodes(print); - float temp_max_additional_fan = tool_ordering.cal_max_additional_fan(print.config()); - if(temp_max_additional_fan > max_additional_fan ) - max_additional_fan = temp_max_additional_fan; - if (tool_ordering.all_extruders().empty()) - // No object to print was found, cancel the G-code export. - throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small"))); - has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); - // BBS: priming logic is removed, so 1st layer tool_ordering also respect the object tool sequence + has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); + } + else + { + // Find tool ordering for all the objects at once, and the initial extruder ID. + // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. + tool_ordering = print.tool_ordering(); + tool_ordering.assign_custom_gcodes(print); + float temp_max_additional_fan = tool_ordering.cal_max_additional_fan(print.config()); + if (temp_max_additional_fan > max_additional_fan) + max_additional_fan = temp_max_additional_fan; + if (tool_ordering.all_extruders().empty()) + // No object to print was found, cancel the G-code export. + throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small"))); + has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); + // BBS: priming logic is removed, so 1st layer tool_ordering also respect the object tool sequence #if 0 initial_extruder_id = (has_wipe_tower && !print.config().single_extruder_multi_material_priming) ? // The priming towers will be skipped. @@ -2306,562 +2518,622 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Don't skip the priming towers. tool_ordering.first_extruder(); #else - initial_extruder_id = tool_ordering.first_extruder(); + initial_extruder_id = tool_ordering.first_extruder(); #endif - //BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament - if (initial_extruder_id != static_cast(-1)) { // BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament - // check if has non support filaments - tool_ordering.cal_non_support_filaments(print.config(), initial_non_support_extruder_id, first_non_support_filaments, first_filaments); - } - - // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z. - // Therefore initialize the printing extruders from there. - this->set_extruders(tool_ordering.all_extruders()); - // Order object instances using a nearest neighbor search. - print_object_instances_ordering = chain_print_object_instances(print); - } - if (initial_extruder_id == (unsigned int)-1) { - // Nothing to print! - initial_extruder_id = 0; - initial_non_support_extruder_id = 0; - } - - //could not find non support filmanet, use fisrt print filament - if (initial_non_support_extruder_id == (unsigned int) -1) - initial_non_support_extruder_id = initial_extruder_id; + if (initial_extruder_id != static_cast(-1)) + { + // BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament + // check if has non support filaments + tool_ordering.cal_non_support_filaments(print.config(), initial_non_support_extruder_id, first_non_support_filaments, first_filaments); + } - print.throw_if_canceled(); + // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z. + // Therefore initialize the printing extruders from there. + this->set_extruders(tool_ordering.all_extruders()); + // Order object instances using a nearest neighbor search. + print_object_instances_ordering = chain_print_object_instances(print); + } + if (initial_extruder_id == (unsigned int)-1) + { + // Nothing to print! + initial_extruder_id = 0; + initial_non_support_extruder_id = 0; + } - m_gcode_editer = make_unique(*this); - m_gcode_editer->set_current_extruder(initial_extruder_id); + // could not find non support filmanet, use fisrt print filament + if (initial_non_support_extruder_id == (unsigned int)-1) + initial_non_support_extruder_id = initial_extruder_id; - int extruder_id = get_extruder_id(initial_extruder_id); + print.throw_if_canceled(); - // Emit machine envelope limits for the Marlin firmware. - this->print_machine_envelope(file, print, initial_extruder_id); + m_gcode_editer = make_unique(*this); + m_gcode_editer->set_current_extruder(initial_extruder_id); - // Disable fan. - if (m_config.auxiliary_fan.value && print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) { - file.write(m_writer.set_fan(0)); - //BBS: disable additional fan - file.write(m_writer.set_additional_fan(0)); - } + int extruder_id = get_extruder_id(initial_extruder_id); - // Let the start-up script prime the 1st printing tool. + // Emit machine envelope limits for the Marlin firmware. + this->print_machine_envelope(file, print, initial_extruder_id); - auto match_physical_extruder_for_each_filament = [](std::vector &filaments, const FullPrintConfig &config) { - // match the filament to the physical extruder - std::vector physicial_first_filaments; - physicial_first_filaments.resize(filaments.size()); - for (int extruder_id = 0; extruder_id < filaments.size(); extruder_id++) { - physicial_first_filaments[config.physical_extruder_map.get_at(extruder_id)] = filaments[extruder_id]; + // Disable fan. + if (m_config.auxiliary_fan.value && print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) + { + file.write(m_writer.set_fan(0)); + // BBS: disable additional fan + file.write(m_writer.set_additional_fan(0)); } - filaments = physicial_first_filaments; - }; - match_physical_extruder_for_each_filament(first_filaments, m_config); - m_placeholder_parser.set("first_tools", new ConfigOptionInts(first_filaments)); - m_placeholder_parser.set("first_filaments", new ConfigOptionInts(first_filaments)); - m_placeholder_parser.set("initial_tool", initial_extruder_id); - m_placeholder_parser.set("initial_extruder", initial_extruder_id); - //BBS - match_physical_extruder_for_each_filament(first_non_support_filaments, m_config); - - m_placeholder_parser.set("first_non_support_tools", new ConfigOptionInts(first_non_support_filaments)); - m_placeholder_parser.set("first_non_support_filaments", new ConfigOptionInts(first_non_support_filaments)); - m_placeholder_parser.set("initial_no_support_tool", initial_non_support_extruder_id); - m_placeholder_parser.set("initial_no_support_extruder", initial_non_support_extruder_id); - m_placeholder_parser.set("current_extruder", initial_extruder_id); - //set the key for compatibilty - m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(initial_extruder_id)); - m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(initial_extruder_id)); - m_placeholder_parser.set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(initial_extruder_id)); - m_placeholder_parser.set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(initial_extruder_id)); - - m_placeholder_parser.set("retraction_distances_when_cut", new ConfigOptionFloatsNullable(m_config.retraction_distances_when_cut)); - m_placeholder_parser.set("long_retractions_when_cut",new ConfigOptionBoolsNullable(m_config.long_retractions_when_cut)); - m_placeholder_parser.set("retraction_distances_when_ec", new ConfigOptionFloatsNullable(m_config.retraction_distances_when_ec)); - m_placeholder_parser.set("long_retractions_when_ec",new ConfigOptionBoolsNullable(m_config.long_retractions_when_ec)); - - m_placeholder_parser.set("max_additional_fan", max_additional_fan); - - auto flush_v_speed = m_config.filament_flush_volumetric_speed.values; - auto flush_temps = m_config.filament_flush_temp.values; - for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) { - if (flush_v_speed[idx] == 0) - flush_v_speed[idx] = m_config.filament_max_volumetric_speed.get_at(idx); - } - for (size_t idx = 0; idx < flush_temps.size(); ++idx) { - if (flush_temps[idx] == 0) - flush_temps[idx] = m_config.nozzle_temperature_range_high.get_at(idx); - } - m_placeholder_parser.set("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed)); - m_placeholder_parser.set("flush_temperatures", new ConfigOptionInts(flush_temps)); - //Set variable for total layer count so it can be used in custom gcode. - m_placeholder_parser.set("total_layer_count", m_layer_count); - // Useful for sequential prints. - m_placeholder_parser.set("current_object_idx", 0); - // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. - m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); - // For the change_filament_gcode to Determine whether the current layer has a wipe tower - m_placeholder_parser.set("has_wipe_tower_this_layer", has_wipe_tower); - //m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); - m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). - Vec2f plate_offset = m_writer.get_xy_offset(); - { - BoundingBoxf bbox(print.config().printable_area.values); - m_placeholder_parser.set("print_bed_min", new ConfigOptionFloats({ bbox.min.x() - plate_offset.x(), bbox.min.y() - plate_offset.y() })); - m_placeholder_parser.set("print_bed_max", new ConfigOptionFloats({ bbox.max.x() - plate_offset.x(), bbox.max.y() - plate_offset.y() })); - m_placeholder_parser.set("print_bed_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); - } - { - // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line. - // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. - // It does NOT encompass user extrusions generated by custom G-code, - // therefore it does NOT encompass the initial purge line. - // It does NOT encompass MMU/MMU2 starting (wipe) areas. - auto pts = std::make_unique(); - pts->values.reserve(print.first_layer_convex_hull().size()); - for (const Point &pt : print.first_layer_convex_hull().points) - pts->values.emplace_back(unscale(pt)); - - BoundingBoxf bbox = first_layer_projection(print); - BoundingBoxf bbox_without_plate_offset{ - {bbox.min.x() - plate_offset.x(),bbox.min.y() - plate_offset.y()}, - {bbox.max.x() - plate_offset.x(),bbox.max.y() - plate_offset.y()} - }; - if (print.calib_params().mode == CalibMode::Calib_PA_Line) { - Pointfs bedfs = print.config().printable_area.values; - BoundingBoxf bed_bbox = BoundingBoxf(bedfs); - pts->values.clear(); - for (const Vec2d &pt : bedfs) - pts->values.emplace_back(pt); - m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); - m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({bed_bbox.min.x(), bed_bbox.min.y()})); - m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({bed_bbox.max.x(), bed_bbox.max.y()})); - m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({bed_bbox.size().x(), bed_bbox.size().y()})); - } - else { - m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); - m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({bbox_without_plate_offset.min.x(), bbox_without_plate_offset.min.y()})); - m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({bbox_without_plate_offset.max.x(), bbox_without_plate_offset.max.y()})); - m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({bbox.size().x(), bbox.size().y()})); - } - - { // BBS:deal with head wrap detect - // use first layer convex_hull union with each object's bbox to check whether in head detect zone - Polygons object_projections; - for (auto& obj : print.objects()) { - for (auto& instance : obj->instances()) { - const auto& bbox = instance.get_bounding_box(); - Point min_p{ coord_t(scale_(bbox.min.x())),coord_t(scale_(bbox.min.y())) }; - Point max_p{ coord_t(scale_(bbox.max.x())),coord_t(scale_(bbox.max.y())) }; - Polygon instance_projection = { - {min_p.x(),min_p.y()}, - {max_p.x(),min_p.y()}, - {max_p.x(),max_p.y()}, - {min_p.x(),max_p.y()} - }; - object_projections.emplace_back(std::move(instance_projection)); - } - } - object_projections.emplace_back(print.first_layer_convex_hull()); - - Polygons project_polys = union_(object_projections); - Polygon head_wrap_detect_zone; - for (auto& point : print.config().head_wrap_detect_zone.values) - head_wrap_detect_zone.append(scale_(point).cast() + scale_(plate_offset).cast()); - - m_placeholder_parser.set("in_head_wrap_detect_zone", !intersection_pl(project_polys, {head_wrap_detect_zone}).empty()); - } + // Let the start-up script prime the 1st printing tool. + auto match_physical_extruder_for_each_filament = [](std::vector &filaments, const FullPrintConfig &config) { - coordf_t max_print_z = 0; - for (auto& obj : print.objects()) { - max_print_z = std::max(max_print_z, (*std::max_element(obj->layers().begin(), obj->layers().end(), [](Layer* a, Layer* b) { return a->print_z < b->print_z; }))->print_z); + // match the filament to the physical extruder + std::vector physicial_first_filaments; + physicial_first_filaments.resize(filaments.size()); + for (int extruder_id = 0; extruder_id < filaments.size(); extruder_id++) + { + physicial_first_filaments[config.physical_extruder_map.get_at(extruder_id)] = filaments[extruder_id]; } - m_placeholder_parser.set("max_print_z", new ConfigOptionInt(std::ceil(max_print_z))); - } - - // get center without wipe tower - BoundingBoxf bbox_wo_wt;// bounding box without wipe tower - for (auto& objPtr : print.objects()) { - BBoxData data; - bbox_wo_wt.merge(unscaled(objPtr->get_first_layer_bbox(data.area, data.layer_height, data.name))); + filaments = physicial_first_filaments; + }; + match_physical_extruder_for_each_filament(first_filaments, m_config); + m_placeholder_parser.set("first_tools", new ConfigOptionInts(first_filaments)); + m_placeholder_parser.set("first_filaments", new ConfigOptionInts(first_filaments)); + m_placeholder_parser.set("initial_tool", initial_extruder_id); + m_placeholder_parser.set("initial_extruder", initial_extruder_id); + // BBS + match_physical_extruder_for_each_filament(first_non_support_filaments, m_config); + + m_placeholder_parser.set("first_non_support_tools", new ConfigOptionInts(first_non_support_filaments)); + m_placeholder_parser.set("first_non_support_filaments", new ConfigOptionInts(first_non_support_filaments)); + m_placeholder_parser.set("initial_no_support_tool", initial_non_support_extruder_id); + m_placeholder_parser.set("initial_no_support_extruder", initial_non_support_extruder_id); + m_placeholder_parser.set("current_extruder", initial_extruder_id); + // set the key for compatibilty + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(initial_extruder_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(initial_extruder_id)); + m_placeholder_parser.set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(initial_extruder_id)); + m_placeholder_parser.set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(initial_extruder_id)); + + m_placeholder_parser.set("retraction_distances_when_cut", new ConfigOptionFloatsNullable(m_config.retraction_distances_when_cut)); + m_placeholder_parser.set("long_retractions_when_cut", new ConfigOptionBoolsNullable(m_config.long_retractions_when_cut)); + m_placeholder_parser.set("retraction_distances_when_ec", new ConfigOptionFloatsNullable(m_config.retraction_distances_when_ec)); + m_placeholder_parser.set("long_retractions_when_ec", new ConfigOptionBoolsNullable(m_config.long_retractions_when_ec)); + + m_placeholder_parser.set("max_additional_fan", max_additional_fan); + + auto flush_v_speed = m_config.filament_flush_volumetric_speed.values; + auto flush_temps = m_config.filament_flush_temp.values; + for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) + { + if (flush_v_speed[idx] == 0) + flush_v_speed[idx] = m_config.filament_max_volumetric_speed.get_at(idx); } - auto center = bbox_wo_wt.center(); - m_placeholder_parser.set("first_layer_center_no_wipe_tower", new ConfigOptionFloats{ {center.x(),center.y()}}); - } + for (size_t idx = 0; idx < flush_temps.size(); ++idx) + { + if (flush_temps[idx] == 0) + flush_temps[idx] = m_config.nozzle_temperature_range_high.get_at(idx); + } + m_placeholder_parser.set("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed)); + m_placeholder_parser.set("flush_temperatures", new ConfigOptionInts(flush_temps)); + // Set variable for total layer count so it can be used in custom gcode. + m_placeholder_parser.set("total_layer_count", m_layer_count); + // Useful for sequential prints. + m_placeholder_parser.set("current_object_idx", 0); + // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. + m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); + // For the change_filament_gcode to Determine whether the current layer has a wipe tower + m_placeholder_parser.set("has_wipe_tower_this_layer", has_wipe_tower); + // m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); + m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). + Vec2f plate_offset = m_writer.get_xy_offset(); + { + BoundingBoxf bbox(print.config().printable_area.values); + m_placeholder_parser.set("print_bed_min", new ConfigOptionFloats({bbox.min.x() - plate_offset.x(), bbox.min.y() - plate_offset.y()})); + m_placeholder_parser.set("print_bed_max", new ConfigOptionFloats({bbox.max.x() - plate_offset.x(), bbox.max.y() - plate_offset.y()})); + m_placeholder_parser.set("print_bed_size", new ConfigOptionFloats({bbox.size().x(), bbox.size().y()})); + } + { + // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line. + // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. + // It does NOT encompass user extrusions generated by custom G-code, + // therefore it does NOT encompass the initial purge line. + // It does NOT encompass MMU/MMU2 starting (wipe) areas. + auto pts = std::make_unique(); + pts->values.reserve(print.first_layer_convex_hull().size()); + for (const Point &pt : print.first_layer_convex_hull().points) + pts->values.emplace_back(unscale(pt)); + + BoundingBoxf bbox = first_layer_projection(print); + BoundingBoxf bbox_without_plate_offset{ + {bbox.min.x() - plate_offset.x(), bbox.min.y() - plate_offset.y()}, + {bbox.max.x() - plate_offset.x(), bbox.max.y() - plate_offset.y()}}; + + if (print.calib_params().mode == CalibMode::Calib_PA_Line) + { + Pointfs bedfs = print.config().printable_area.values; + BoundingBoxf bed_bbox = BoundingBoxf(bedfs); + pts->values.clear(); + for (const Vec2d &pt : bedfs) + pts->values.emplace_back(pt); + m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); + m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({bed_bbox.min.x(), bed_bbox.min.y()})); + m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({bed_bbox.max.x(), bed_bbox.max.y()})); + m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({bed_bbox.size().x(), bed_bbox.size().y()})); + } + else + { + m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); + m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({bbox_without_plate_offset.min.x(), bbox_without_plate_offset.min.y()})); + m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({bbox_without_plate_offset.max.x(), bbox_without_plate_offset.max.y()})); + m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({bbox.size().x(), bbox.size().y()})); + } - int max_chamber_temp = 0; - { - BedType curr_bed_type = m_config.curr_bed_type; + { // BBS:deal with head wrap detect + // use first layer convex_hull union with each object's bbox to check whether in head detect zone + Polygons object_projections; + for (auto &obj : print.objects()) + { + for (auto &instance : obj->instances()) + { + const auto &bbox = instance.get_bounding_box(); + Point min_p{coord_t(scale_(bbox.min.x())), coord_t(scale_(bbox.min.y()))}; + Point max_p{coord_t(scale_(bbox.max.x())), coord_t(scale_(bbox.max.y()))}; + Polygon instance_projection = { + {min_p.x(), min_p.y()}, + {max_p.x(), min_p.y()}, + {max_p.x(), max_p.y()}, + {min_p.x(), max_p.y()}}; + object_projections.emplace_back(std::move(instance_projection)); + } + } + object_projections.emplace_back(print.first_layer_convex_hull()); - for (const auto& extruder : m_writer.extruders()) - max_chamber_temp = std::max(max_chamber_temp, m_config.chamber_temperatures.get_at(extruder.id())); + Polygons project_polys = union_(object_projections); + Polygon head_wrap_detect_zone; + for (auto &point : print.config().head_wrap_detect_zone.values) + head_wrap_detect_zone.append(scale_(point).cast() + scale_(plate_offset).cast()); - int min_temperature_vitrification = std::numeric_limits::max(); - for (const auto& extruder : m_writer.extruders()) - min_temperature_vitrification = std::min(min_temperature_vitrification, m_config.temperature_vitrification.get_at(extruder.id())); + m_placeholder_parser.set("in_head_wrap_detect_zone", !intersection_pl(project_polys, {head_wrap_detect_zone}).empty()); + } + { + coordf_t max_print_z = 0; + for (auto &obj : print.objects()) + { + max_print_z = std::max(max_print_z, (*std::max_element(obj->layers().begin(), obj->layers().end(), [](Layer *a, Layer *b) + { return a->print_z < b->print_z; })) + ->print_z); + } + m_placeholder_parser.set("max_print_z", new ConfigOptionInt(std::ceil(max_print_z))); + } - std::string first_layer_bed_temp_str; - const ConfigOptionInts* first_bed_temp_opt = m_config.option(get_bed_temp_1st_layer_key((BedType)curr_bed_type)); - const ConfigOptionInts* bed_temp_opt = m_config.option(get_bed_temp_key((BedType)curr_bed_type)); - int target_bed_temp = 0; - if (m_config.bed_temperature_formula == BedTempFormula::btfHighestTemp) - target_bed_temp = get_highest_bed_temperature(true, print); - else - target_bed_temp = get_bed_temperature(initial_extruder_id, true, curr_bed_type); - - m_placeholder_parser.set("bbl_bed_temperature_gcode", new ConfigOptionBool(false)); - m_placeholder_parser.set("bed_temperature_initial_layer", new ConfigOptionInts(*first_bed_temp_opt)); - m_placeholder_parser.set("bed_temperature", new ConfigOptionInts(*bed_temp_opt)); - m_placeholder_parser.set("bed_temperature_initial_layer_single", new ConfigOptionInt(target_bed_temp)); - m_placeholder_parser.set("bed_temperature_initial_layer_vector", new ConfigOptionString("")); - m_placeholder_parser.set("chamber_temperature", new ConfigOptionInts({ max_chamber_temp })); - m_placeholder_parser.set("overall_chamber_temperature", new ConfigOptionInt(max_chamber_temp)); - m_placeholder_parser.set("enable_high_low_temp_mix", new ConfigOptionBool(!print.need_check_multi_filaments_compatibility())); - m_placeholder_parser.set("min_vitrification_temperature", new ConfigOptionInt(min_temperature_vitrification)); - - //support variables `first_layer_temperature` and `first_layer_bed_temperature` - m_placeholder_parser.set("first_layer_bed_temperature", new ConfigOptionInts(*first_bed_temp_opt)); - m_placeholder_parser.set("first_layer_temperature", new ConfigOptionIntsNullable(m_config.nozzle_temperature_initial_layer)); - m_placeholder_parser.set("max_print_height", new ConfigOptionInt(m_config.printable_height)); - m_placeholder_parser.set("z_offset", new ConfigOptionFloat(0.0f)); - m_placeholder_parser.set("plate_name", new ConfigOptionString(print.get_plate_name())); - - auto used_filaments = print.get_slice_used_filaments(false); - m_placeholder_parser.set("is_all_bbl_filament", std::all_of(used_filaments.begin(), used_filaments.end(), [&](auto idx) { - return m_config.filament_vendor.values[idx] == "Bambu Lab"; - })); - - float wipe_tower_center_pos_x= -1.f, wipe_tower_center_pos_y = -1.f; - Vec2f stop_pos; // point of nozzle heating - bool wipe_tower_center_pos_valid = false; - if (has_wipe_tower) { - auto bbx = print.wipe_tower_data().bbx; - bbx.translate(print.get_fake_wipe_tower().pos.cast()); - BoundingBoxf printer_bed_bbx = get_extents(m_config.printable_area.values); - float printer_bed_mid_x = printer_bed_bbx.center().x(); - if (bbx.center().x() < printer_bed_mid_x) - stop_pos = Vec2f(bbx.max.x()+2.f, bbx.center().y()); - else - stop_pos = Vec2f(bbx.min.x()-2.f, bbx.center().y()); + // get center without wipe tower + BoundingBoxf bbox_wo_wt; // bounding box without wipe tower + for (auto &objPtr : print.objects()) + { + BBoxData data; + bbox_wo_wt.merge(unscaled(objPtr->get_first_layer_bbox(data.area, data.layer_height, data.name))); + } + auto center = bbox_wo_wt.center(); + m_placeholder_parser.set("first_layer_center_no_wipe_tower", new ConfigOptionFloats{{center.x(), center.y()}}); + } - BoundingBoxf printer_bbx = unscaled(get_extents(m_print->get_extruder_shared_printable_polygon())); - if (stop_pos.x() < printer_bbx.min[0]) stop_pos.x() = printer_bbx.min[0]; - if (stop_pos.x() > printer_bbx.max[0]) stop_pos.x() = printer_bbx.max[0]; + int max_chamber_temp = 0; + { + BedType curr_bed_type = m_config.curr_bed_type; - wipe_tower_center_pos_valid = true; - } - m_placeholder_parser.set("wipe_tower_center_pos_x", new ConfigOptionFloat(stop_pos.x())); - m_placeholder_parser.set("wipe_tower_center_pos_y", new ConfigOptionFloat(stop_pos.y())); - m_placeholder_parser.set("wipe_tower_center_pos_valid", new ConfigOptionBool(wipe_tower_center_pos_valid)); + for (const auto &extruder : m_writer.extruders()) + max_chamber_temp = std::max(max_chamber_temp, m_config.chamber_temperatures.get_at(extruder.id())); - //add during_print_exhaust_fan_speed - std::vector during_print_exhaust_fan_speed_num; - during_print_exhaust_fan_speed_num.reserve(m_config.during_print_exhaust_fan_speed.size()); - for (const auto& item : m_config.during_print_exhaust_fan_speed.values) - during_print_exhaust_fan_speed_num.emplace_back((int)(item / 100.0 * 255)); - m_placeholder_parser.set("during_print_exhaust_fan_speed_num", new ConfigOptionInts(during_print_exhaust_fan_speed_num)); + int min_temperature_vitrification = std::numeric_limits::max(); + for (const auto &extruder : m_writer.extruders()) + min_temperature_vitrification = std::min(min_temperature_vitrification, m_config.temperature_vitrification.get_at(extruder.id())); - //BBS: calculate the volumetric speed of outer wall. Ignore pre-object setting and multi-filament, and just use the default setting - float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, print, initial_non_support_extruder_id, get_extruder_id(initial_non_support_extruder_id)); - m_placeholder_parser.set("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); + std::string first_layer_bed_temp_str; + const ConfigOptionInts *first_bed_temp_opt = m_config.option(get_bed_temp_1st_layer_key((BedType)curr_bed_type)); + const ConfigOptionInts *bed_temp_opt = m_config.option(get_bed_temp_key((BedType)curr_bed_type)); + int target_bed_temp = 0; + if (m_config.bed_temperature_formula == BedTempFormula::btfHighestTemp) + target_bed_temp = get_highest_bed_temperature(true, print); + else + target_bed_temp = get_bed_temperature(initial_extruder_id, true, curr_bed_type); + + m_placeholder_parser.set("bbl_bed_temperature_gcode", new ConfigOptionBool(false)); + m_placeholder_parser.set("bed_temperature_initial_layer", new ConfigOptionInts(*first_bed_temp_opt)); + m_placeholder_parser.set("bed_temperature", new ConfigOptionInts(*bed_temp_opt)); + m_placeholder_parser.set("bed_temperature_initial_layer_single", new ConfigOptionInt(target_bed_temp)); + m_placeholder_parser.set("bed_temperature_initial_layer_vector", new ConfigOptionString("")); + m_placeholder_parser.set("chamber_temperature", new ConfigOptionInts({max_chamber_temp})); + m_placeholder_parser.set("overall_chamber_temperature", new ConfigOptionInt(max_chamber_temp)); + m_placeholder_parser.set("enable_high_low_temp_mix", new ConfigOptionBool(!print.need_check_multi_filaments_compatibility())); + m_placeholder_parser.set("min_vitrification_temperature", new ConfigOptionInt(min_temperature_vitrification)); + + // support variables `first_layer_temperature` and `first_layer_bed_temperature` + m_placeholder_parser.set("first_layer_bed_temperature", new ConfigOptionInts(*first_bed_temp_opt)); + m_placeholder_parser.set("first_layer_temperature", new ConfigOptionIntsNullable(m_config.nozzle_temperature_initial_layer)); + m_placeholder_parser.set("max_print_height", new ConfigOptionInt(m_config.printable_height)); + m_placeholder_parser.set("z_offset", new ConfigOptionFloat(0.0f)); + m_placeholder_parser.set("plate_name", new ConfigOptionString(print.get_plate_name())); + + auto used_filaments = print.get_slice_used_filaments(false); + m_placeholder_parser.set("is_all_bbl_filament", std::all_of(used_filaments.begin(), used_filaments.end(), [&](auto idx) + { return m_config.filament_vendor.values[idx] == "Bambu Lab"; })); + + float wipe_tower_center_pos_x = -1.f, wipe_tower_center_pos_y = -1.f; + Vec2f stop_pos; // point of nozzle heating + bool wipe_tower_center_pos_valid = false; + if (has_wipe_tower) + { + auto bbx = print.wipe_tower_data().bbx; + bbx.translate(print.get_fake_wipe_tower().pos.cast()); + BoundingBoxf printer_bed_bbx = get_extents(m_config.printable_area.values); + float printer_bed_mid_x = printer_bed_bbx.center().x(); + if (bbx.center().x() < printer_bed_mid_x) + stop_pos = Vec2f(bbx.max.x() + 2.f, bbx.center().y()); + else + stop_pos = Vec2f(bbx.min.x() - 2.f, bbx.center().y()); - auto first_layer_filaments = print.get_slice_used_filaments(true); - bool has_tpu_in_first_layer = std::any_of(first_layer_filaments.begin(), first_layer_filaments.end(), [&](unsigned int idx) { return m_config.filament_type.values[idx] == "TPU"; }); - m_placeholder_parser.set("has_tpu_in_first_layer", new ConfigOptionBool(has_tpu_in_first_layer)); + BoundingBoxf printer_bbx = unscaled(get_extents(m_print->get_extruder_shared_printable_polygon())); + if (stop_pos.x() < printer_bbx.min[0]) + stop_pos.x() = printer_bbx.min[0]; + if (stop_pos.x() > printer_bbx.max[0]) + stop_pos.x() = printer_bbx.max[0]; - if (print.calib_params().mode == CalibMode::Calib_PA_Line) { - m_placeholder_parser.set("scan_first_layer", new ConfigOptionBool(false)); + wipe_tower_center_pos_valid = true; + } + m_placeholder_parser.set("wipe_tower_center_pos_x", new ConfigOptionFloat(stop_pos.x())); + m_placeholder_parser.set("wipe_tower_center_pos_y", new ConfigOptionFloat(stop_pos.y())); + m_placeholder_parser.set("wipe_tower_center_pos_valid", new ConfigOptionBool(wipe_tower_center_pos_valid)); + + // add during_print_exhaust_fan_speed + std::vector during_print_exhaust_fan_speed_num; + during_print_exhaust_fan_speed_num.reserve(m_config.during_print_exhaust_fan_speed.size()); + for (const auto &item : m_config.during_print_exhaust_fan_speed.values) + during_print_exhaust_fan_speed_num.emplace_back((int)(item / 100.0 * 255)); + m_placeholder_parser.set("during_print_exhaust_fan_speed_num", new ConfigOptionInts(during_print_exhaust_fan_speed_num)); + + // BBS: calculate the volumetric speed of outer wall. Ignore pre-object setting and multi-filament, and just use the default setting + float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, print, initial_non_support_extruder_id, get_extruder_id(initial_non_support_extruder_id)); + m_placeholder_parser.set("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); + + auto first_layer_filaments = print.get_slice_used_filaments(true); + bool has_tpu_in_first_layer = std::any_of(first_layer_filaments.begin(), first_layer_filaments.end(), [&](unsigned int idx) + { return m_config.filament_type.values[idx] == "TPU"; }); + m_placeholder_parser.set("has_tpu_in_first_layer", new ConfigOptionBool(has_tpu_in_first_layer)); + + if (print.calib_params().mode == CalibMode::Calib_PA_Line) + { + m_placeholder_parser.set("scan_first_layer", new ConfigOptionBool(false)); + } } - } - { // hold chamber temp for flat print: Flag - double print_area_sum_threshold = 40000.0, pring_hight_threshold = 0.3; // thresholds in mm^2 and mm as units - - double area_sum_temp = 0.0; - coordf_t max_hight_temp = -1.0; - for (ObjectID print_object_ID_t : print.print_object_ids()) { - const PrintObject *print_object = print.get_object(print_object_ID_t); - // object hight - if (!print_object->layers().empty() && print_object->layers().back()->print_z > max_hight_temp) max_hight_temp = print_object->layers().back()->print_z; - // object area - if (!print_object->layers().empty() && print_object->layers().front()->print_z < print.config().initial_layer_print_height + EPSILON && - !print_object->layers().front()->lslices.empty()) { - ExPolygons temp_Expolys = print_object->layers().front()->lslices; - for (ExPolygon &temp_Expoly : temp_Expolys) { area_sum_temp += temp_Expoly.area(); } - } - // suport area - if (!print_object->support_layers().empty() && print_object->support_layers().front()->print_z < print.config().initial_layer_print_height + EPSILON && - !print_object->support_layers().front()->support_islands.empty()) { - ExPolygons temp_Expolys = print_object->support_layers().front()->support_islands; - for (ExPolygon &temp_Expoly : temp_Expolys) { area_sum_temp += temp_Expoly.area(); } - } - // brim area - if (print.m_brimMap.find(print_object_ID_t) != print.m_brimMap.end() && !print.m_brimMap.at(print_object_ID_t).entities.empty()) { // contain brim - for (const ExtrusionEntity *entities_temp : print.m_brimMap.at(print_object_ID_t).entities) { - Polygons temp_Expolys; - entities_temp->polygons_covered_by_spacing(temp_Expolys, 0.0f); - for (Polygon &temp_Expoly : temp_Expolys) { area_sum_temp += temp_Expoly.area(); } + { // hold chamber temp for flat print: Flag + double print_area_sum_threshold = 40000.0, pring_hight_threshold = 0.3; // thresholds in mm^2 and mm as units + + double area_sum_temp = 0.0; + coordf_t max_hight_temp = -1.0; + for (ObjectID print_object_ID_t : print.print_object_ids()) + { + const PrintObject *print_object = print.get_object(print_object_ID_t); + // object hight + if (!print_object->layers().empty() && print_object->layers().back()->print_z > max_hight_temp) + max_hight_temp = print_object->layers().back()->print_z; + // object area + if (!print_object->layers().empty() && print_object->layers().front()->print_z < print.config().initial_layer_print_height + EPSILON && + !print_object->layers().front()->lslices.empty()) + { + ExPolygons temp_Expolys = print_object->layers().front()->lslices; + for (ExPolygon &temp_Expoly : temp_Expolys) + { + area_sum_temp += temp_Expoly.area(); + } + } + // suport area + if (!print_object->support_layers().empty() && print_object->support_layers().front()->print_z < print.config().initial_layer_print_height + EPSILON && + !print_object->support_layers().front()->support_islands.empty()) + { + ExPolygons temp_Expolys = print_object->support_layers().front()->support_islands; + for (ExPolygon &temp_Expoly : temp_Expolys) + { + area_sum_temp += temp_Expoly.area(); + } + } + // brim area + if (print.m_brimMap.find(print_object_ID_t) != print.m_brimMap.end() && !print.m_brimMap.at(print_object_ID_t).entities.empty()) + { // contain brim + for (const ExtrusionEntity *entities_temp : print.m_brimMap.at(print_object_ID_t).entities) + { + Polygons temp_Expolys; + entities_temp->polygons_covered_by_spacing(temp_Expolys, 0.0f); + for (Polygon &temp_Expoly : temp_Expolys) + { + area_sum_temp += temp_Expoly.area(); + } + } } } + // wipe tower area + if (has_wipe_tower) + { + Polygon temp_Expoly = print.wipe_tower_data().wipe_tower_mesh_data->bottom; + area_sum_temp += temp_Expoly.area(); + } + bool hold_chamber_temp_for_flat_print = max_hight_temp > 0 && max_hight_temp < pring_hight_threshold && area_sum_temp > print_area_sum_threshold * 1.0e10; + m_placeholder_parser.set("hold_chamber_temp_for_flat_print", new ConfigOptionBool(hold_chamber_temp_for_flat_print)); } - // wipe tower area - if (has_wipe_tower) { - Polygon temp_Expoly = print.wipe_tower_data().wipe_tower_mesh_data->bottom; - area_sum_temp += temp_Expoly.area(); - } - bool hold_chamber_temp_for_flat_print = max_hight_temp > 0 && max_hight_temp < pring_hight_threshold && area_sum_temp > print_area_sum_threshold * 1.0e10; - m_placeholder_parser.set("hold_chamber_temp_for_flat_print", new ConfigOptionBool(hold_chamber_temp_for_flat_print)); - } + std::string machine_start_gcode = this->placeholder_parser_process("machine_start_gcode", print.config().machine_start_gcode.value, initial_extruder_id); + if (print.config().gcode_flavor != gcfKlipper) + { + // Set bed temperature if the start G-code does not contain any bed temp control G-codes. + this->_print_first_layer_bed_temperature(file, print, machine_start_gcode, initial_extruder_id, true); + // Set extruder(s) temperature before and after start G-code. + this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, false); + } - std::string machine_start_gcode = this->placeholder_parser_process("machine_start_gcode", print.config().machine_start_gcode.value, initial_extruder_id); - if (print.config().gcode_flavor != gcfKlipper) { - // Set bed temperature if the start G-code does not contain any bed temp control G-codes. - this->_print_first_layer_bed_temperature(file, print, machine_start_gcode, initial_extruder_id, true); - // Set extruder(s) temperature before and after start G-code. - this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, false); - } - - // BBS: chamber temp control for 3rd printers - if (!is_BBL_Printer() && print.config().support_chamber_temp_control.value && max_chamber_temp > 0 ){ - int temp_out =0; - if(!custom_gcode_sets_temperature(machine_start_gcode,141,191,false,temp_out)) - file.write(m_writer.set_chamber_temperature(max_chamber_temp,true)); - } - - // adds tag for processor - file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + // BBS: chamber temp control for 3rd printers + if (!is_BBL_Printer() && print.config().support_chamber_temp_control.value && max_chamber_temp > 0) + { + int temp_out = 0; + if (!custom_gcode_sets_temperature(machine_start_gcode, 141, 191, false, temp_out)) + file.write(m_writer.set_chamber_temperature(max_chamber_temp, true)); + } - // Write the custom start G-code - file.writeln(machine_start_gcode); - //BBS: mark machine start gcode - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::MachineStartGCodeEnd).c_str()); - //BBS: gcode writer doesn't know where the real position of extruder is after inserting custom gcode - m_writer.set_current_position_clear(false); - m_start_gcode_filament = GCodeProcessor::get_gcode_last_filament(machine_start_gcode); + // adds tag for processor + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); - m_writer.init_extruder(initial_non_support_extruder_id); - // add the missing filament start gcode in machine start gcode - { - DynamicConfig config; - config.set_key_value("filament_extruder_id", new ConfigOptionInt((int)(initial_non_support_extruder_id))); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - std::string filament_start_gcode = this->placeholder_parser_process("filament_start_gcode", print.config().filament_start_gcode.values.at(initial_non_support_extruder_id), initial_non_support_extruder_id,&config); - file.writeln(filament_start_gcode); - // mark the first filament used in print - file.write_format(";VT%d\n", initial_extruder_id); - } + // Write the custom start G-code + file.writeln(machine_start_gcode); + // BBS: mark machine start gcode + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::MachineStartGCodeEnd).c_str()); + // BBS: gcode writer doesn't know where the real position of extruder is after inserting custom gcode + m_writer.set_current_position_clear(false); + m_start_gcode_filament = GCodeProcessor::get_gcode_last_filament(machine_start_gcode); - // Process filament-specific gcode. - /* if (has_wipe_tower) { - // Wipe tower will control the extruder switching, it will call the filament_start_gcode. - } else { + m_writer.init_extruder(initial_non_support_extruder_id); + // add the missing filament start gcode in machine start gcode + { DynamicConfig config; - config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); - file.writeln(this->placeholder_parser_process("filament_start_gcode", print.config().filament_start_gcode.values[initial_extruder_id], initial_extruder_id, &config)); - } -*/ - this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, true); - - if (m_config.support_air_filtration.getBool() && m_config.activate_air_filtration.get_at(initial_extruder_id)) { - file.write(m_writer.set_exhaust_fan(m_config.during_print_exhaust_fan_speed.get_at(initial_extruder_id), true)); - } + config.set_key_value("filament_extruder_id", new ConfigOptionInt((int)(initial_non_support_extruder_id))); + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + std::string filament_start_gcode = this->placeholder_parser_process("filament_start_gcode", print.config().filament_start_gcode.values.at(initial_non_support_extruder_id), initial_non_support_extruder_id, &config); + file.writeln(filament_start_gcode); + // mark the first filament used in print + file.write_format(";VT%d\n", initial_extruder_id); + } + + // Process filament-specific gcode. + /* if (has_wipe_tower) { + // Wipe tower will control the extruder switching, it will call the filament_start_gcode. + } else { + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); + file.writeln(this->placeholder_parser_process("filament_start_gcode", print.config().filament_start_gcode.values[initial_extruder_id], initial_extruder_id, &config)); + } + */ + this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, true); + + if (m_config.support_air_filtration.getBool() && m_config.activate_air_filtration.get_at(initial_extruder_id)) + { + file.write(m_writer.set_exhaust_fan(m_config.during_print_exhaust_fan_speed.get_at(initial_extruder_id), true)); + } - print.throw_if_canceled(); + print.throw_if_canceled(); - // Set other general things. - file.write(this->preamble()); + // Set other general things. + file.write(this->preamble()); - // Calculate wiping points if needed - DoExport::init_ooze_prevention(print, m_ooze_prevention); - print.throw_if_canceled(); + // Calculate wiping points if needed + DoExport::init_ooze_prevention(print, m_ooze_prevention); + print.throw_if_canceled(); - // Collect custom seam data from all objects. - std::function throw_if_canceled_func = [&print]() { print.throw_if_canceled(); }; - m_seam_placer.init(print, throw_if_canceled_func); + // Collect custom seam data from all objects. + std::function throw_if_canceled_func = [&print]() + { print.throw_if_canceled(); }; + m_seam_placer.init(print, throw_if_canceled_func); - // BBS: get path for change filament - if (m_writer.multiple_extruders) { - std::vector points = get_path_of_change_filament(print); - if (points.size() == 3) { - travel_point_1 = points[0]; - travel_point_2 = points[1]; - travel_point_3 = points[2]; + // BBS: get path for change filament + if (m_writer.multiple_extruders) + { + std::vector points = get_path_of_change_filament(print); + if (points.size() == 3) + { + travel_point_1 = points[0]; + travel_point_2 = points[1]; + travel_point_3 = points[2]; + } } - } - // BBS: priming logic is removed, always set first extruer here. - //if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) - { - // Set initial extruder only after custom start G-code. - // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. - file.write(this->set_extruder(initial_extruder_id, 0.)); - } - // BBS: set that indicates objs with brim - for (auto iter = print.m_brimMap.begin(); iter != print.m_brimMap.end(); ++iter) { - if (!iter->second.empty()) - this->m_objsWithBrim.insert(iter->first); - } - for (auto iter = print.m_supportBrimMap.begin(); iter != print.m_supportBrimMap.end(); ++iter) { - if (!iter->second.empty()) - this->m_objSupportsWithBrim.insert(iter->first); - } - if (this->m_objsWithBrim.empty() && this->m_objSupportsWithBrim.empty()) m_brim_done = true; + // BBS: priming logic is removed, always set first extruer here. + // if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) + { + // Set initial extruder only after custom start G-code. + // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. + file.write(this->set_extruder(initial_extruder_id, 0.)); + } + // BBS: set that indicates objs with brim + for (auto iter = print.m_brimMap.begin(); iter != print.m_brimMap.end(); ++iter) + { + if (!iter->second.empty()) + this->m_objsWithBrim.insert(iter->first); + } + for (auto iter = print.m_supportBrimMap.begin(); iter != print.m_supportBrimMap.end(); ++iter) + { + if (!iter->second.empty()) + this->m_objSupportsWithBrim.insert(iter->first); + } + if (this->m_objsWithBrim.empty() && this->m_objSupportsWithBrim.empty()) + m_brim_done = true; - std::vector travel_accelerations; - for (auto value : m_config.travel_acceleration.values) { - travel_accelerations.emplace_back((unsigned int) floor(value + 0.5)); - } - std::vector first_layer_travel_accelerations; - for (auto value : m_config.initial_layer_travel_acceleration.values) { - first_layer_travel_accelerations.emplace_back((unsigned int) floor(value + 0.5)); - } - m_writer.set_travel_acceleration(travel_accelerations); - m_writer.set_first_layer_travel_acceleration(first_layer_travel_accelerations); - // OrcaSlicer: calib - if (print.calib_params().mode == CalibMode::Calib_PA_Line) { - std::string gcode; - gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) + "\n"; - if ((NOZZLE_CONFIG(default_acceleration) > 0 && NOZZLE_CONFIG(outer_wall_acceleration) > 0)) { - m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(outer_wall_acceleration) + 0.5)); + std::vector travel_accelerations; + for (auto value : m_config.travel_acceleration.values) + { + travel_accelerations.emplace_back((unsigned int)floor(value + 0.5)); + } + std::vector first_layer_travel_accelerations; + for (auto value : m_config.initial_layer_travel_acceleration.values) + { + first_layer_travel_accelerations.emplace_back((unsigned int)floor(value + 0.5)); } + m_writer.set_travel_acceleration(travel_accelerations); + m_writer.set_first_layer_travel_acceleration(first_layer_travel_accelerations); + // OrcaSlicer: calib + if (print.calib_params().mode == CalibMode::Calib_PA_Line) + { + std::string gcode; + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) + "\n"; + if ((NOZZLE_CONFIG(default_acceleration) > 0 && NOZZLE_CONFIG(outer_wall_acceleration) > 0)) + { + m_writer.set_acceleration((unsigned int)floor(NOZZLE_CONFIG(outer_wall_acceleration) + 0.5)); + } - if (m_config.default_jerk.value > 0 && !this->is_BBL_Printer()) { - double jerk = m_config.outer_wall_jerk.value; - gcode += m_writer.set_jerk_xy(jerk); + if (m_config.default_jerk.value > 0 && !this->is_BBL_Printer()) + { + double jerk = m_config.outer_wall_jerk.value; + gcode += m_writer.set_jerk_xy(jerk); + } + + CalibPressureAdvanceLine pa_test(this); + double filament_max_volumetric_speed = m_config.option("filament_max_volumetric_speed")->get_at(initial_extruder_id); + Flow pattern_line = Flow(pa_test.line_width(), 0.2, m_config.nozzle_diameter.get_at(0)); + auto fast_speed = std::min(print.default_region_config().outer_wall_speed.get_at(cur_config_index()), filament_max_volumetric_speed / pattern_line.mm3_per_mm()); + auto slow_speed = fast_speed / 4; /*std::max(20.0, fast_speed / 10.0);*/ + pa_test.set_speed(fast_speed, slow_speed); + pa_test.draw_numbers() = print.calib_params().print_numbers; + auto params = print.calib_params(); + gcode += pa_test.generate_test(params.start, params.step, std::llround(std::ceil((params.end - params.start) / params.step)) + 1); + + file.write(gcode); } + else + { + // BBS: open spaghetti detector + // if (print.config().spaghetti_detector.value) + if (print.is_BBL_Printer()) + file.write("M981 S1 P20000 ;open spaghetti detector\n"); + + // Do all objects for each layer. + if (print.config().print_sequence == PrintSequence::ByObject && !has_wipe_tower && print.objects().size() > 1) + { + size_t finished_objects = 0; + print_object_instance_sequential_active = first_has_extrude_print_object; + const PrintObject *prev_object = (*print_object_instance_sequential_active)->print_object; + for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) + { + const PrintObject &object = *(*print_object_instance_sequential_active)->print_object; + if (&object != prev_object || tool_ordering.first_extruder() != final_extruder_id) + { + tool_ordering = ToolOrdering(object, final_extruder_id); + tool_ordering.sort_and_build_data(object, final_extruder_id); + unsigned int new_extruder_id = tool_ordering.first_extruder(); + if (new_extruder_id == (unsigned int)-1) + // Skip this object. + continue; + initial_extruder_id = new_extruder_id; + final_extruder_id = tool_ordering.last_extruder(); + assert(final_extruder_id != (unsigned int)-1); + } + print.throw_if_canceled(); + this->set_origin(unscale((*print_object_instance_sequential_active)->shift)); - CalibPressureAdvanceLine pa_test(this); - double filament_max_volumetric_speed = m_config.option("filament_max_volumetric_speed")->get_at(initial_extruder_id); - Flow pattern_line = Flow(pa_test.line_width(), 0.2, m_config.nozzle_diameter.get_at(0)); - auto fast_speed = std::min(print.default_region_config().outer_wall_speed.get_at(cur_config_index()), filament_max_volumetric_speed / pattern_line.mm3_per_mm()); - auto slow_speed = fast_speed / 4; /*std::max(20.0, fast_speed / 10.0);*/ - pa_test.set_speed(fast_speed, slow_speed); - pa_test.draw_numbers() = print.calib_params().print_numbers; - auto params = print.calib_params(); - gcode += pa_test.generate_test(params.start, params.step, std::llround(std::ceil((params.end - params.start) / params.step)) + 1); + // BBS: prime extruder if extruder change happens before this object instance + bool prime_extruder = false; + if (finished_objects > 0) + { + // Move to the origin position for the copy we're going to print. + // 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 GCodeEditor + m_avoid_crossing_perimeters.use_external_mp_once(); + // BBS. change tool before moving to origin point. + if (m_writer.need_toolchange(initial_extruder_id)) + { + const PrintObjectConfig &object_config = object.config(); + coordf_t initial_layer_print_height = print.config().initial_layer_print_height.value; - file.write(gcode); - } - else { - // BBS: open spaghetti detector - // if (print.config().spaghetti_detector.value) - if (print.is_BBL_Printer()) file.write("M981 S1 P20000 ;open spaghetti detector\n"); - - // Do all objects for each layer. - if (print.config().print_sequence == PrintSequence::ByObject && !has_wipe_tower && print.objects().size() > 1) { - size_t finished_objects = 0; - print_object_instance_sequential_active = first_has_extrude_print_object; - const PrintObject *prev_object = (*print_object_instance_sequential_active)->print_object; - for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { - const PrintObject &object = *(*print_object_instance_sequential_active)->print_object; - if (&object != prev_object || tool_ordering.first_extruder() != final_extruder_id) { - tool_ordering = ToolOrdering(object, final_extruder_id); - tool_ordering.sort_and_build_data(object, final_extruder_id); - unsigned int new_extruder_id = tool_ordering.first_extruder(); - if (new_extruder_id == (unsigned int) -1) - // Skip this object. - continue; - initial_extruder_id = new_extruder_id; - final_extruder_id = tool_ordering.last_extruder(); - assert(final_extruder_id != (unsigned int) -1); - } - print.throw_if_canceled(); - this->set_origin(unscale((*print_object_instance_sequential_active)->shift)); - - // BBS: prime extruder if extruder change happens before this object instance - bool prime_extruder = false; - if (finished_objects > 0) { - // Move to the origin position for the copy we're going to print. - // 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 GCodeEditor - m_avoid_crossing_perimeters.use_external_mp_once(); - // BBS. change tool before moving to origin point. - if (m_writer.need_toolchange(initial_extruder_id)) { - const PrintObjectConfig &object_config = object.config(); - coordf_t initial_layer_print_height = print.config().initial_layer_print_height.value; + if (m_enable_label_object && print.config().support_object_skip_flush.value) + { + m_filament_instances_code = _encode_label_ids_to_base64({(*print_object_instance_sequential_active)->model_instance->get_labeled_id()}); + } - if (m_enable_label_object && print.config().support_object_skip_flush.value) { - m_filament_instances_code = _encode_label_ids_to_base64({(*print_object_instance_sequential_active)->model_instance->get_labeled_id()}); + file.write(this->set_extruder(initial_extruder_id, initial_layer_print_height, true)); + prime_extruder = true; } - - file.write(this->set_extruder(initial_extruder_id, initial_layer_print_height, true)); - prime_extruder = true; - } else { - file.write(this->retract()); + else + { + file.write(this->retract()); + } + file.write(m_writer.travel_to_z(m_max_layer_z + m_writer.config.z_hop.get_at(initial_extruder_id))); + file.write(this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); + m_enable_cooling_markers = true; + // Disable motion planner when traveling to first object point. + m_avoid_crossing_perimeters.disable_once(); + // Ff we are printing the bottom layer of an object, and we have already finished + // another one, set first layer temperatures. This happens before the Z move + // is triggered, so machine has more time to reach such temperatures. + m_placeholder_parser.set("current_object_idx", int(finished_objects)); + std::string printing_by_object_gcode = this->placeholder_parser_process("printing_by_object_gcode", print.config().printing_by_object_gcode.value, + initial_extruder_id); + // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. + this->_print_first_layer_bed_temperature(file, print, printing_by_object_gcode, initial_extruder_id, false); + this->_print_first_layer_extruder_temperatures(file, print, printing_by_object_gcode, initial_extruder_id, false); + file.writeln(printing_by_object_gcode); + } + // Reset the cooling buffer internal state (the current position, feed rate, accelerations). + m_gcode_editer->reset(this->writer().get_position()); + m_gcode_editer->set_current_extruder(initial_extruder_id); + // Process all layers of a single object instance (sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + tool_ordering.cal_most_used_extruder(print.config()); + m_printed_objects.emplace_back(&object); + this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file, + prime_extruder); + { + // save the flush statitics stored in tool ordering by object + print.m_statistics_by_extruder_count.stats_by_single_extruder += tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::SingleExt); + print.m_statistics_by_extruder_count.stats_by_multi_extruder_best += tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtBest); + print.m_statistics_by_extruder_count.stats_by_multi_extruder_curr += tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtCurr); + // save sorted filament sequences + const auto &layer_tools = tool_ordering.layer_tools(); + for (const auto < : layer_tools) + m_sorted_layer_filaments.emplace_back(lt.extruders); } - file.write(m_writer.travel_to_z(m_max_layer_z + m_writer.config.z_hop.get_at(initial_extruder_id))); - file.write(this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); - m_enable_cooling_markers = true; - // Disable motion planner when traveling to first object point. - m_avoid_crossing_perimeters.disable_once(); - // Ff we are printing the bottom layer of an object, and we have already finished - // another one, set first layer temperatures. This happens before the Z move - // is triggered, so machine has more time to reach such temperatures. - m_placeholder_parser.set("current_object_idx", int(finished_objects)); - std::string printing_by_object_gcode = this->placeholder_parser_process("printing_by_object_gcode", print.config().printing_by_object_gcode.value, - initial_extruder_id); - // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. - this->_print_first_layer_bed_temperature(file, print, printing_by_object_gcode, initial_extruder_id, false); - this->_print_first_layer_extruder_temperatures(file, print, printing_by_object_gcode, initial_extruder_id, false); - file.writeln(printing_by_object_gcode); - } - // Reset the cooling buffer internal state (the current position, feed rate, accelerations). - m_gcode_editer->reset(this->writer().get_position()); - m_gcode_editer->set_current_extruder(initial_extruder_id); - // Process all layers of a single object instance (sequential mode) with a parallel pipeline: - // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser - // and export G-code into file. - tool_ordering.cal_most_used_extruder(print.config()); - m_printed_objects.emplace_back(&object); - this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file, - prime_extruder); - { - // save the flush statitics stored in tool ordering by object - print.m_statistics_by_extruder_count.stats_by_single_extruder += tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::SingleExt); - print.m_statistics_by_extruder_count.stats_by_multi_extruder_best += tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtBest); - print.m_statistics_by_extruder_count.stats_by_multi_extruder_curr += tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtCurr); - // save sorted filament sequences - const auto& layer_tools = tool_ordering.layer_tools(); - for (const auto& lt : layer_tools) - m_sorted_layer_filaments.emplace_back(lt.extruders); - } - // BBS: close powerlost recovery - { - if (m_second_layer_things_done && print.is_BBL_Printer()) { - file.write("; close powerlost recovery\n"); - file.write("M1003 S0\n"); + // BBS: close powerlost recovery + { + if (m_second_layer_things_done && print.is_BBL_Printer()) + { + file.write("; close powerlost recovery\n"); + file.write("M1003 S0\n"); + } } - } #ifdef HAS_PRESSURE_EQUALIZER - if (m_pressure_equalizer) file.write(m_pressure_equalizer->process("", true)); + if (m_pressure_equalizer) + file.write(m_pressure_equalizer->process("", true)); #endif /* HAS_PRESSURE_EQUALIZER */ - ++finished_objects; - // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. - // Reset it when starting another object from 1st layer. - m_second_layer_things_done = false; - prev_object = &object; - } - } else { - // Sort layers by Z. - // All extrusion moves with the same top layer height are extruded uninterrupted. - std::vector>> layers_to_print = collect_layers_to_print(print); - // Prusa Multi-Material wipe tower. - if (has_wipe_tower && !layers_to_print.empty()) { - m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(), *print.wipe_tower_data().priming.get(), - print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get(), print.get_slice_used_filaments(false))); - m_wipe_tower->set_wipe_tower_depth(print.get_wipe_tower_depth()); - m_wipe_tower->set_wipe_tower_bbx(print.get_wipe_tower_bbx()); - m_wipe_tower->set_rib_offset(print.get_rib_offset()); - // BBS - // file.write(m_writer.travel_to_z(initial_layer_print_height + m_config.z_offset.value, "Move to the first layer height")); - file.write(m_writer.travel_to_z(initial_layer_print_height, "Move to the first layer height")); + ++finished_objects; + // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. + // Reset it when starting another object from 1st layer. + m_second_layer_things_done = false; + prev_object = &object; + } + } + else + { + // Sort layers by Z. + // All extrusion moves with the same top layer height are extruded uninterrupted. + std::vector>> layers_to_print = collect_layers_to_print(print); + // Prusa Multi-Material wipe tower. + if (has_wipe_tower && !layers_to_print.empty()) + { + m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(), *print.wipe_tower_data().priming.get(), + print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get(), print.get_slice_used_filaments(false))); + m_wipe_tower->set_wipe_tower_depth(print.get_wipe_tower_depth()); + m_wipe_tower->set_wipe_tower_bbx(print.get_wipe_tower_bbx()); + m_wipe_tower->set_rib_offset(print.get_rib_offset()); + // BBS + // file.write(m_writer.travel_to_z(initial_layer_print_height + m_config.z_offset.value, "Move to the first layer height")); + file.write(m_writer.travel_to_z(initial_layer_print_height, "Move to the first layer height")); #if 0 if (print.config().single_extruder_multi_material_priming) { file.write(m_wipe_tower->prime(*this)); @@ -2900,332 +3172,360 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato //} } #endif - print.throw_if_canceled(); - } + print.throw_if_canceled(); + } - tool_ordering.cal_most_used_extruder(print.config()); + tool_ordering.cal_most_used_extruder(print.config()); - // Process all layers of all objects (non-sequential mode) with a parallel pipeline: - // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser - // and export G-code into file. - this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); - { - //save the flush statitics stored in tool ordering - print.m_statistics_by_extruder_count.stats_by_single_extruder = tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::SingleExt); - print.m_statistics_by_extruder_count.stats_by_multi_extruder_best = tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtBest); - print.m_statistics_by_extruder_count.stats_by_multi_extruder_curr = tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtCurr); - // save sorted filament sequences - const auto& layer_tools = tool_ordering.layer_tools(); - for (const auto& lt : layer_tools) - m_sorted_layer_filaments.emplace_back(lt.extruders); - } + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); + { + // save the flush statitics stored in tool ordering + print.m_statistics_by_extruder_count.stats_by_single_extruder = tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::SingleExt); + print.m_statistics_by_extruder_count.stats_by_multi_extruder_best = tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtBest); + print.m_statistics_by_extruder_count.stats_by_multi_extruder_curr = tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtCurr); + // save sorted filament sequences + const auto &layer_tools = tool_ordering.layer_tools(); + for (const auto < : layer_tools) + m_sorted_layer_filaments.emplace_back(lt.extruders); + } - // BBS: close powerlost recovery - { - if (m_second_layer_things_done && print.is_BBL_Printer()) { - file.write("; close powerlost recovery\n"); - file.write("M1003 S0\n"); + // BBS: close powerlost recovery + { + if (m_second_layer_things_done && print.is_BBL_Printer()) + { + file.write("; close powerlost recovery\n"); + file.write("M1003 S0\n"); + } } - } #ifdef HAS_PRESSURE_EQUALIZER - if (m_pressure_equalizer) file.write(m_pressure_equalizer->process("", true)); + if (m_pressure_equalizer) + file.write(m_pressure_equalizer->process("", true)); #endif /* HAS_PRESSURE_EQUALIZER */ - if (m_wipe_tower) - // Purge the extruder, pull out the active filament. - file.write(m_wipe_tower->finalize(*this)); + if (m_wipe_tower) + // Purge the extruder, pull out the active filament. + file.write(m_wipe_tower->finalize(*this)); + } } - } - //BBS: the last retraction - // Write end commands to file. - file.write(this->retract(false, true)); + // BBS: the last retraction + // Write end commands to file. + file.write(this->retract(false, true)); - // if needed, write the gcode_label_objects_end - { - std::string gcode; - m_writer.add_object_change_labels(gcode); - file.write(gcode); - } + // if needed, write the gcode_label_objects_end + { + std::string gcode; + m_writer.add_object_change_labels(gcode); + file.write(gcode); + } - file.write(m_writer.set_fan(0)); - //BBS: make sure the additional fan is closed when end - if (m_config.auxiliary_fan.value) - file.write(m_writer.set_additional_fan(0)); - //BBS: close spaghetti detector - //Note: M981 is also used to tell xcam the last layer is finished, so we need always send it even if spaghetti option is disabled. - //if (print.config().spaghetti_detector.value) - if (print.is_BBL_Printer()) - file.write("M981 S0 P20000 ; close spaghetti detector\n"); + file.write(m_writer.set_fan(0)); + // BBS: make sure the additional fan is closed when end + if (m_config.auxiliary_fan.value) + file.write(m_writer.set_additional_fan(0)); + // BBS: close spaghetti detector + // Note: M981 is also used to tell xcam the last layer is finished, so we need always send it even if spaghetti option is disabled. + // if (print.config().spaghetti_detector.value) + if (print.is_BBL_Printer()) + file.write("M981 S0 P20000 ; close spaghetti detector\n"); - // adds tag for processor - file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + // adds tag for processor + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); - //BBS: mark machine end gcode - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::MachineEndGCodeStart).c_str()); + // BBS: mark machine end gcode + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::MachineEndGCodeStart).c_str()); - // Process filament-specific gcode in extruder order. - { - DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - //BBS - //config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2) - m_config.z_offset.value)); - config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2))); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - - if (print.config().single_extruder_multi_material) { - // Process the filament_end_gcode for the active filament only. - int extruder_id = m_writer.filament()->id(); - config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - file.writeln(this->placeholder_parser_process("filament_end_gcode", print.config().filament_end_gcode.get_at(extruder_id), extruder_id, &config)); - } else { - for (const std::string &end_gcode : print.config().filament_end_gcode.values) { - int extruder_id = (unsigned int)(&end_gcode - &print.config().filament_end_gcode.values.front()); + // Process filament-specific gcode in extruder order. + { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + // BBS + // config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2) - m_config.z_offset.value)); + config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2))); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + + if (print.config().single_extruder_multi_material) + { + // Process the filament_end_gcode for the active filament only. + int extruder_id = m_writer.filament()->id(); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - file.writeln(this->placeholder_parser_process("filament_end_gcode", end_gcode, extruder_id, &config)); + file.writeln(this->placeholder_parser_process("filament_end_gcode", print.config().filament_end_gcode.get_at(extruder_id), extruder_id, &config)); + } + else + { + for (const std::string &end_gcode : print.config().filament_end_gcode.values) + { + int extruder_id = (unsigned int)(&end_gcode - &print.config().filament_end_gcode.values.front()); + config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); + file.writeln(this->placeholder_parser_process("filament_end_gcode", end_gcode, extruder_id, &config)); + } } + file.writeln(this->placeholder_parser_process("machine_end_gcode", print.config().machine_end_gcode, m_writer.filament()->id(), &config)); } - file.writeln(this->placeholder_parser_process("machine_end_gcode", print.config().machine_end_gcode, m_writer.filament()->id(), &config)); - } - file.write(m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% - file.write(m_writer.postamble()); + file.write(m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% + file.write(m_writer.postamble()); - if (print.config().support_chamber_temp_control.value && max_chamber_temp>0) - file.write(m_writer.set_chamber_temperature(0, false)); //close chamber_temperature + if (print.config().support_chamber_temp_control.value && max_chamber_temp > 0) + file.write(m_writer.set_chamber_temperature(0, false)); // close chamber_temperature + // adds tags for time estimators + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str()); + file.write_format("; EXECUTABLE_BLOCK_END\n\n"); - // adds tags for time estimators - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str()); - file.write_format("; EXECUTABLE_BLOCK_END\n\n"); + print.throw_if_canceled(); - print.throw_if_canceled(); + // Get filament stats. + file.write(DoExport::update_print_stats_and_format_filament_stats( + // Const inputs + has_wipe_tower, print.wipe_tower_data(), + m_writer.extruders(), + // Modifies + print.m_print_statistics)); + print.m_print_statistics.initial_tool = initial_extruder_id; + + bool activate_air_filtration = false; + for (const auto &extruder : m_writer.extruders()) + activate_air_filtration |= m_config.activate_air_filtration.get_at(extruder.id()); + activate_air_filtration &= m_config.support_air_filtration.getBool(); + + if (activate_air_filtration) + { + int complete_print_exhaust_fan_speed = 0; + for (const auto &extruder : m_writer.extruders()) + if (m_config.activate_air_filtration.get_at(extruder.id())) + complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id())); + file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true)); + } - // Get filament stats. - file.write(DoExport::update_print_stats_and_format_filament_stats( - // Const inputs - has_wipe_tower, print.wipe_tower_data(), - m_writer.extruders(), - // Modifies - print.m_print_statistics)); - print.m_print_statistics.initial_tool = initial_extruder_id; - - bool activate_air_filtration = false; - for (const auto& extruder : m_writer.extruders()) - activate_air_filtration |= m_config.activate_air_filtration.get_at(extruder.id()); - activate_air_filtration &= m_config.support_air_filtration.getBool(); - - if (activate_air_filtration) { - int complete_print_exhaust_fan_speed = 0; - for (const auto& extruder : m_writer.extruders()) - if (m_config.activate_air_filtration.get_at(extruder.id())) - complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id())); - file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true)); + print.throw_if_canceled(); } - print.throw_if_canceled(); -} + // export info requested for filament change + void GCode::export_layer_filaments(GCodeProcessorResult *result) + { + if (result == nullptr) + return; -// export info requested for filament change -void GCode::export_layer_filaments(GCodeProcessorResult* result) -{ - if (result == nullptr) - return; + const std::vector filament_map = m_config.filament_map.values; // 1 based + std::vector prev_filament(m_config.nozzle_diameter.size(), -1); + for (size_t idx = 0; idx < m_sorted_layer_filaments.size(); ++idx) + { + for (auto f : m_sorted_layer_filaments[idx]) + { + int extruder_idx = filament_map[f] - 1; + if (prev_filament[extruder_idx] != -1 && f != prev_filament[extruder_idx]) + { + std::pair from_to_pair = {prev_filament[extruder_idx], f}; + auto iter = result->filament_change_count_map.find(from_to_pair); + if (iter == result->filament_change_count_map.end()) + result->filament_change_count_map.emplace(from_to_pair, 1); + else + iter->second += 1; + } + prev_filament[extruder_idx] = f; + } - const std::vectorfilament_map = m_config.filament_map.values; // 1 based - std::vectorprev_filament(m_config.nozzle_diameter.size(), -1); - for (size_t idx = 0; idx < m_sorted_layer_filaments.size(); ++idx) { - for (auto f : m_sorted_layer_filaments[idx]) { - int extruder_idx = filament_map[f] - 1; - if (prev_filament[extruder_idx] != -1 && f != prev_filament[extruder_idx]) { - std::pair from_to_pair = { prev_filament[extruder_idx],f }; - auto iter = result->filament_change_count_map.find(from_to_pair); - if (iter == result->filament_change_count_map.end()) - result->filament_change_count_map.emplace(from_to_pair, 1); + // now we do not need sorted data, so we sort the filaments in id order + auto layer_filaments = m_sorted_layer_filaments[idx]; + std::sort(layer_filaments.begin(), layer_filaments.end()); + auto iter = result->layer_filaments.find(layer_filaments); + if (iter == result->layer_filaments.end()) + { + result->layer_filaments[layer_filaments].emplace_back(idx, idx); + } + else + { + // if layer id is sequential, expand the range + if (iter->second.back().second == idx - 1) + iter->second.back().second = idx; else - iter->second += 1; + iter->second.emplace_back(idx, idx); } - prev_filament[extruder_idx] = f; } - // now we do not need sorted data, so we sort the filaments in id order - auto layer_filaments = m_sorted_layer_filaments[idx]; - std::sort(layer_filaments.begin(), layer_filaments.end()); - auto iter = result->layer_filaments.find(layer_filaments); - if (iter == result->layer_filaments.end()) { - result->layer_filaments[layer_filaments].emplace_back(idx, idx); - } - else { - // if layer id is sequential, expand the range - if (iter->second.back().second == idx - 1) - iter->second.back().second = idx; - else - iter->second.emplace_back(idx, idx); + { + result->filament_change_sequence.clear(); + std::optional prev_filament; + for (auto &layer : m_sorted_layer_filaments) + { + for (auto fidx : layer) + { + if (!prev_filament || prev_filament != fidx) + result->filament_change_sequence.emplace_back(fidx); + prev_filament = fidx; + } + } } } + // BBS + void GCode::check_placeholder_parser_failed() { - result->filament_change_sequence.clear(); - std::optional prev_filament; - for(auto& layer : m_sorted_layer_filaments){ - for(auto fidx : layer){ - if(!prev_filament || prev_filament != fidx) - result->filament_change_sequence.emplace_back(fidx); - prev_filament = fidx; + bool has_machine_gcode = false, has_filament_gcode = false; + if (!m_placeholder_parser_failed_templates.empty()) + { + // G-code export proceeded, but some of the PlaceholderParser substitutions failed. + std::string msg = Slic3r::format(_(L("Failed to generate gcode for invalid custom G-code.\n\n"))); + for (const auto &name_and_error : m_placeholder_parser_failed_templates) + { + msg += name_and_error.first + " " + name_and_error.second + "\n"; + if (("filament_end_gcode" == name_and_error.first) || ("filament_start_gcode" == name_and_error.first)) + has_filament_gcode = true; + else + has_machine_gcode = true; + } + msg += Slic3r::format(_(L("Please check the custom G-code or use the default custom G-code.\n"))); + if (has_machine_gcode) + { + if (has_filament_gcode) + msg += Slic3r::format(_(L("You can find them from 'Printer settings' -> 'Machine G-code' and 'Filament settings' -> 'Advanced'."))); + else + msg += Slic3r::format(_(L("You can find it from 'Printer settings' -> 'Machine G-code'."))); } - } - } -} - -//BBS -void GCode::check_placeholder_parser_failed() -{ - bool has_machine_gcode = false, has_filament_gcode = false; - if (! m_placeholder_parser_failed_templates.empty()) { - // G-code export proceeded, but some of the PlaceholderParser substitutions failed. - std::string msg = Slic3r::format(_(L("Failed to generate gcode for invalid custom G-code.\n\n"))); - for (const auto &name_and_error : m_placeholder_parser_failed_templates) - { - msg += name_and_error.first + " " + name_and_error.second + "\n"; - if (("filament_end_gcode" == name_and_error.first) || ("filament_start_gcode" == name_and_error.first)) - has_filament_gcode = true; - else - has_machine_gcode = true; - } - msg += Slic3r::format(_(L("Please check the custom G-code or use the default custom G-code.\n"))); - if (has_machine_gcode) { - if (has_filament_gcode) - msg += Slic3r::format(_(L("You can find them from 'Printer settings' -> 'Machine G-code' and 'Filament settings' -> 'Advanced'."))); else - msg += Slic3r::format(_(L("You can find it from 'Printer settings' -> 'Machine G-code'."))); + msg += Slic3r::format(_(L("You can find it from 'Filament settings' -> 'Advanced'."))); + throw Slic3r::PlaceholderParserError(msg); } - else - msg += Slic3r::format(_(L("You can find it from 'Filament settings' -> 'Advanced'."))); - throw Slic3r::PlaceholderParserError(msg); } -} - -size_t GCode::cur_extruder_index() const -{ - //TODO: check if the function is duplicated - //just return m_writer.filament()->extruder_id() - return get_extruder_id(m_writer.filament()->id()); -} - -size_t GCode::cur_config_index() const -{ - return m_config.filament_map_2.get_at(m_writer.filament()->id()); -} - -size_t GCode::get_extruder_id(unsigned int filament_id) const -{ - if (m_print) { - return m_print->get_extruder_id(filament_id); + size_t GCode::cur_extruder_index() const + { + // TODO: check if the function is duplicated + // just return m_writer.filament()->extruder_id() + return get_extruder_id(m_writer.filament()->id()); } - return 0; -} -void GCode::set_extrude_acceleration(bool is_first_layer) -{ - if (is_first_layer) { - m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(initial_layer_acceleration) + 0.5)); - } else { - m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(default_acceleration) + 0.5)); + size_t GCode::cur_config_index() const + { + return m_config.filament_map_2.get_at(m_writer.filament()->id()); } -} -// Process all layers of all objects (non-sequential mode) with a parallel pipeline: -// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser -// and export G-code into file. -void GCode::process_layers( - const Print &print, - const ToolOrdering &tool_ordering, - const std::vector &print_object_instances_ordering, - const std::vector>> &layers_to_print, - GCodeOutputStream &output_stream) -{ - //BBS: get object label id - size_t layer_to_print_idx = 0; - std::vector object_label; - - for (const PrintInstance *instance : print_object_instances_ordering) - object_label.push_back(instance->model_instance->get_labeled_id()); - - std::vector layers_results; - layers_results.resize(layers_to_print.size()); - - // The pipeline is variable: The vase mode filter is optional. - const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> GCode::LayerResult { - if (layer_to_print_idx == layers_to_print.size()) { - fc.stop(); - return {}; - } else { - const std::pair>& layer = layers_to_print[layer_to_print_idx++]; - const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); - print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx))); - if (m_wipe_tower && layer_tools.has_wipe_tower) - m_wipe_tower->next_layer(); - //BBS - check_placeholder_parser_failed(); - print.throw_if_canceled(); - GCode::LayerResult res = this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, tool_ordering.get_most_used_extruder(), size_t(-1)); - res.gcode_store_pos = layer_to_print_idx - 1; - return std::move(res); - } - }); - if (m_spiral_vase) { - float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); - float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter); - this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing); + size_t GCode::get_extruder_id(unsigned int filament_id) const + { + if (m_print) + { + return m_print->get_extruder_id(filament_id); + } + return 0; + } + + void GCode::set_extrude_acceleration(bool is_first_layer) + { + if (is_first_layer) + { + m_writer.set_acceleration((unsigned int)floor(NOZZLE_CONFIG(initial_layer_acceleration) + 0.5)); + } + else + { + m_writer.set_acceleration((unsigned int)floor(NOZZLE_CONFIG(default_acceleration) + 0.5)); + } } - const auto spiral_mode = tbb::make_filter( - slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), & layers_to_print](GCode::LayerResult in) -> GCode::LayerResult { + + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + void GCode::process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + const std::vector &print_object_instances_ordering, + const std::vector>> &layers_to_print, + GCodeOutputStream &output_stream) + { + // BBS: get object label id + size_t layer_to_print_idx = 0; + std::vector object_label; + + for (const PrintInstance *instance : print_object_instances_ordering) + object_label.push_back(instance->model_instance->get_labeled_id()); + + std::vector layers_results; + layers_results.resize(layers_to_print.size()); + + // The pipeline is variable: The vase mode filter is optional. + const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control &fc) -> GCode::LayerResult + { + if (layer_to_print_idx == layers_to_print.size()) + { + fc.stop(); + return {}; + } + else + { + const std::pair> &layer = layers_to_print[layer_to_print_idx++]; + const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); + print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx))); + if (m_wipe_tower && layer_tools.has_wipe_tower) + m_wipe_tower->next_layer(); + // BBS + check_placeholder_parser_failed(); + print.throw_if_canceled(); + GCode::LayerResult res = this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, tool_ordering.get_most_used_extruder(), size_t(-1)); + res.gcode_store_pos = layer_to_print_idx - 1; + return std::move(res); + } + }); + if (m_spiral_vase) + { + float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); + float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter); + this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing); + } + const auto spiral_mode = tbb::make_filter( + slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](GCode::LayerResult in) -> GCode::LayerResult + { spiral_mode.enable(in.spiral_vase_enable); bool last_layer = in.layer_id == layers_to_print.size() - 1; - return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos}; - }); - - std::vector> layers_extruder_adjustments(layers_to_print.size()); - - const auto parsing = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult{ - //record gcode - in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.not_set_additional_fan, in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false); - return std::move(in); - }); - - //step2: cooling - std::vector> layers_wall_collection(layers_to_print.size()); - - CoolingBuffer cooling_processor; - - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult { - in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]); - return std::move(in); - }); - - // step 4.1: record node date - SmoothCalculator smooth_calculator(object_label.size()); - - const auto build_node = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in){ - smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]); - layers_results[in.gcode_store_pos] = std::move(in); - return; - }); - - // step 5: rewite - const auto write_gocde= tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string { - return gcode_editer.write_layer_gcode(std::move(in.gcode), in.not_set_additional_fan, in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]); - }); - - std::vector gcode_res; - - // BBS: apply new feedrate of outwall and recalculate layer time - int layer_idx = 0; - const auto calculate_layer_time= tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &smooth_calculator, &layers_extruder_adjustments, &gcode_res](tbb::flow_control& fc) -> GCode::LayerResult { + return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos}; }); + + std::vector> layers_extruder_adjustments(layers_to_print.size()); + + const auto parsing = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult + { + // record gcode + in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.not_set_additional_fan, in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false); + return std::move(in); + }); + + // step2: cooling + std::vector> layers_wall_collection(layers_to_print.size()); + + CoolingBuffer cooling_processor; + + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult + { + in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]); + return std::move(in); + }); + + // step 4.1: record node date + SmoothCalculator smooth_calculator(object_label.size()); + + const auto build_node = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in) + { + smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]); + layers_results[in.gcode_store_pos] = std::move(in); + return; + }); + + // step 5: rewite + const auto write_gocde = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string + { + return gcode_editer.write_layer_gcode(std::move(in.gcode), in.not_set_additional_fan, in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]); + }); + + std::vector gcode_res; + + // BBS: apply new feedrate of outwall and recalculate layer time + int layer_idx = 0; + const auto calculate_layer_time = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &smooth_calculator, &layers_extruder_adjustments, &gcode_res](tbb::flow_control &fc) -> GCode::LayerResult + { if(layer_idx == gcode_res.size()){ fc.stop(); return{}; @@ -3234,141 +3534,152 @@ void GCode::process_layers( gcode_res[layer_idx].layer_time = smooth_calculator.recaculate_layer_time(layer_idx, layers_extruder_adjustments[gcode_res[layer_idx].gcode_store_pos]); } return gcode_res[layer_idx++]; - } - }); - - - const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&output_stream](std::string s) { output_stream.write(s); }); + } }); + + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) + { output_stream.write(s); }); + + // BBS: apply cooling + // The pipeline elements are joined using const references, thus no copying is performed. + if (m_spiral_vase) + tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output); + else if (!m_config.z_direction_outwall_speed_continuous) + tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output); + else + { + tbb::parallel_pipeline(12, generator & parsing & cooling & build_node); + std::string message; + message = _L("Smoothing z direction speed"); + m_print->set_status(85, message); + // append data + for (const LayerResult &res : layers_results) + { + // remove empty gcode layer caused by support independent layers + if (res.cooling_buffer_flush) + { + smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]); + gcode_res.push_back(std::move(res)); + } + } - // BBS: apply cooling - // The pipeline elements are joined using const references, thus no copying is performed. - if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output); - else if (!m_config.z_direction_outwall_speed_continuous) - tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output); - else { - tbb::parallel_pipeline(12, generator & parsing & cooling & build_node); - std::string message; - message = _L("Smoothing z direction speed"); - m_print->set_status(85, message); - //append data - for (const LayerResult &res : layers_results) { - //remove empty gcode layer caused by support independent layers - if (res.cooling_buffer_flush) { - smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]); - gcode_res.push_back(std::move(res)); - } - } - - smooth_calculator.smooth_layer_speed(); - message = _L("Exporting G-code"); - m_print->set_status(90, message); - tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output); + smooth_calculator.smooth_layer_speed(); + message = _L("Exporting G-code"); + m_print->set_status(90, message); + tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output); + } } -} - -// Process all layers of a single object instance (sequential mode) with a parallel pipeline: -// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser -// and export G-code into file. -void GCode::process_layers( - const Print &print, - const ToolOrdering &tool_ordering, - std::vector layers_to_print, - const size_t single_object_idx, - GCodeOutputStream &output_stream, - // BBS - const bool prime_extruder) -{ - // the pipeline should be - // generatotr + (spira) + parse + (cooling) + (smoothing) + rewrite - // rewrite pipeline to get better schu - - // BBS: get object label id - size_t layer_to_print_idx = 0; - std::vector object_label; - for (LayerToPrint layer : layers_to_print) - object_label.push_back(layer.original_object->instances()[single_object_idx].model_instance->get_labeled_id()); - - std::vector layers_results; - layers_results.resize(layers_to_print.size()); - - //step 1: generator - // The pipeline is variable: The vase mode filter is optional. - const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx, prime_extruder](tbb::flow_control& fc) -> GCode::LayerResult { - if (layer_to_print_idx == layers_to_print.size()) { - fc.stop(); - return {}; - } else { - LayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; - print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx))); - //BBS - check_placeholder_parser_failed(); - print.throw_if_canceled(); - GCode::LayerResult res = this->process_layer(print, {std::move(layer)}, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, tool_ordering.get_most_used_extruder(), single_object_idx, prime_extruder); - res.gcode_store_pos = layer_to_print_idx - 1; - return std::move(res); - } - }); - if (m_spiral_vase) { - float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); - float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter); - this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing); - } - const auto spiral_mode = tbb::make_filter( - slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](GCode::LayerResult in) -> GCode::LayerResult { + // Process all layers of a single object instance (sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + void GCode::process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + std::vector layers_to_print, + const size_t single_object_idx, + GCodeOutputStream &output_stream, + // BBS + const bool prime_extruder) + { + // the pipeline should be + // generatotr + (spira) + parse + (cooling) + (smoothing) + rewrite + // rewrite pipeline to get better schu + + // BBS: get object label id + size_t layer_to_print_idx = 0; + std::vector object_label; + for (LayerToPrint layer : layers_to_print) + object_label.push_back(layer.original_object->instances()[single_object_idx].model_instance->get_labeled_id()); + + std::vector layers_results; + layers_results.resize(layers_to_print.size()); + + // step 1: generator + // The pipeline is variable: The vase mode filter is optional. + const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx, prime_extruder](tbb::flow_control &fc) -> GCode::LayerResult + { + if (layer_to_print_idx == layers_to_print.size()) + { + fc.stop(); + return {}; + } + else + { + LayerToPrint &layer = layers_to_print[layer_to_print_idx++]; + print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx))); + // BBS + check_placeholder_parser_failed(); + print.throw_if_canceled(); + GCode::LayerResult res = this->process_layer(print, {std::move(layer)}, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, tool_ordering.get_most_used_extruder(), single_object_idx, prime_extruder); + res.gcode_store_pos = layer_to_print_idx - 1; + return std::move(res); + } + }); + if (m_spiral_vase) + { + float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); + float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter); + this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing); + } + const auto spiral_mode = tbb::make_filter( + slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](GCode::LayerResult in) -> GCode::LayerResult + { spiral_mode.enable(in.spiral_vase_enable); bool last_layer = in.layer_id == layers_to_print.size() - 1; - return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos}; - }); - - //BBS: get objects and nodes info, for better arrange - const ConstPrintObjectPtrsAdaptor &objects = print.objects(); - - // step 2: parse - std::vector> layers_extruder_adjustments(layers_to_print.size()); - - const auto parsing = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult{ - //record gcode - in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.not_set_additional_fan, in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false); - return std::move(in); - }); - - // step 3: cooling - std::vector> layers_wall_collection(layers_to_print.size()); - CoolingBuffer cooling_processor; - - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult { - in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]); - return std::move(in); - }); - - // step 4.1: record node date - SmoothCalculator smooth_calculator(object_label.size()); - - const auto build_node = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in){ - smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]); - layers_results[in.gcode_store_pos] = std::move(in); - return; - }); - - // step 5: rewite - const auto write_gocde= tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string { - return gcode_editer.write_layer_gcode(std::move(in.gcode), in.not_set_additional_fan, in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]); - }); - - std::vector gcode_res; - - // BBS: apply new feedrate of outwall and recalculate layer time - int layer_idx = 0; - //restart pipeline - const auto calculate_layer_time = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &gcode_res, &smooth_calculator, &layers_extruder_adjustments](tbb::flow_control& fc) -> GCode::LayerResult { + return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos}; }); + + // BBS: get objects and nodes info, for better arrange + const ConstPrintObjectPtrsAdaptor &objects = print.objects(); + + // step 2: parse + std::vector> layers_extruder_adjustments(layers_to_print.size()); + + const auto parsing = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult + { + // record gcode + in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.not_set_additional_fan, in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false); + return std::move(in); + }); + + // step 3: cooling + std::vector> layers_wall_collection(layers_to_print.size()); + CoolingBuffer cooling_processor; + + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult + { + in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]); + return std::move(in); + }); + + // step 4.1: record node date + SmoothCalculator smooth_calculator(object_label.size()); + + const auto build_node = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in) + { + smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]); + layers_results[in.gcode_store_pos] = std::move(in); + return; + }); + + // step 5: rewite + const auto write_gocde = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string + { + return gcode_editer.write_layer_gcode(std::move(in.gcode), in.not_set_additional_fan, in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]); + }); + + std::vector gcode_res; + + // BBS: apply new feedrate of outwall and recalculate layer time + int layer_idx = 0; + // restart pipeline + const auto calculate_layer_time = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &gcode_res, &smooth_calculator, &layers_extruder_adjustments](tbb::flow_control &fc) -> GCode::LayerResult + { if(layer_idx == gcode_res.size()){ fc.stop(); return{}; @@ -3377,296 +3688,317 @@ void GCode::process_layers( gcode_res[layer_idx].layer_time = smooth_calculator.recaculate_layer_time(layer_idx, layers_extruder_adjustments[gcode_res[layer_idx].gcode_store_pos]); } return gcode_res[layer_idx++]; - } - }); - - - const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&output_stream](std::string s) { output_stream.write(s); }); - - // BBS: apply cooling - // The pipeline elements are joined using const references, thus no copying is performed. - if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output); - else if (!m_config.z_direction_outwall_speed_continuous) - tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output); - else { - tbb::parallel_pipeline(12, generator & parsing & cooling & build_node); - // step 4.2: smoothing - // break pipeline and do z smoothing - // append data - for (const LayerResult &res : layers_results) { - // remove empty gcode layer caused by support independent layers - if (res.cooling_buffer_flush) { - smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]); - gcode_res.push_back(res); + } }); + + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) + { output_stream.write(s); }); + + // BBS: apply cooling + // The pipeline elements are joined using const references, thus no copying is performed. + if (m_spiral_vase) + tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output); + else if (!m_config.z_direction_outwall_speed_continuous) + tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output); + else + { + tbb::parallel_pipeline(12, generator & parsing & cooling & build_node); + // step 4.2: smoothing + // break pipeline and do z smoothing + // append data + for (const LayerResult &res : layers_results) + { + // remove empty gcode layer caused by support independent layers + if (res.cooling_buffer_flush) + { + smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]); + gcode_res.push_back(res); + } } - } - smooth_calculator.smooth_layer_speed(); + smooth_calculator.smooth_layer_speed(); - tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output); + tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output); + } } -} -std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_filament_id, const DynamicConfig *config_override) -{ - try { - return m_placeholder_parser.process(templ, current_filament_id, config_override, &m_placeholder_parser_context); - } catch (std::runtime_error &err) { - // Collect the names of failed template substitutions for error reporting. - auto it = m_placeholder_parser_failed_templates.find(name); - if (it == m_placeholder_parser_failed_templates.end()) - // Only if there was no error reported for this template, store the first error message into the map to be reported. - // We don't want to collect error message for each and every occurence of a single custom G-code section. - m_placeholder_parser_failed_templates.insert(it, std::make_pair(name, std::string(err.what()))); - // Insert the macro error message into the G-code. - return - std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + - err.what() + - "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; + std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_filament_id, const DynamicConfig *config_override) + { + try + { + return m_placeholder_parser.process(templ, current_filament_id, config_override, &m_placeholder_parser_context); + } + catch (std::runtime_error &err) + { + // Collect the names of failed template substitutions for error reporting. + auto it = m_placeholder_parser_failed_templates.find(name); + if (it == m_placeholder_parser_failed_templates.end()) + // Only if there was no error reported for this template, store the first error message into the map to be reported. + // We don't want to collect error message for each and every occurence of a single custom G-code section. + m_placeholder_parser_failed_templates.insert(it, std::make_pair(name, std::string(err.what()))); + // Insert the macro error message into the G-code. + return std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + + err.what() + + "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; + } + } + + // Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters. + // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. + void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print, int extruder_id) + { + int matched_machine_limit_idx = get_extruder_id(extruder_id) * 2; + if (print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware) + { + file.write_format("M201 X%d Y%d Z%d E%d\n", + int(print.config().machine_max_acceleration_x.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_acceleration_y.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_acceleration_z.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_acceleration_e.values[matched_machine_limit_idx] + 0.5)); + file.write_format("M203 X%d Y%d Z%d E%d\n", + int(print.config().machine_max_speed_x.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_speed_y.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_speed_z.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_speed_e.values[matched_machine_limit_idx] + 0.5)); + + // Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about + // Legacy Marlin should export travel acceleration the same as printing acceleration. + // MarlinFirmware has the two separated. + int travel_acc = print.config().gcode_flavor == gcfMarlinLegacy + ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) + : int(print.config().machine_max_acceleration_travel.values.front() + 0.5); + file.write_format("M204 P%d R%d T%d\n", + int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), + int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), + travel_acc); + + assert(is_decimal_separator_point()); + file.write_format("M205 X%.2lf Y%.2lf Z%.2lf E%.2lf\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()); + // BBS: don't support M205 Sx Tx + // file.write_format("M205 S%d T%d\n", + // int(print.config().machine_min_extruding_rate.values.front() + 0.5), + // int(print.config().machine_min_travel_rate.values.front() + 0.5)); + } } -} - - -// Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters. -// Do not process this piece of G-code by the time estimator, it already knows the values through another sources. -void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print, int extruder_id) -{ - int matched_machine_limit_idx = get_extruder_id(extruder_id) * 2; - if (print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware) { - file.write_format("M201 X%d Y%d Z%d E%d\n", - int(print.config().machine_max_acceleration_x.values[matched_machine_limit_idx] + 0.5), - int(print.config().machine_max_acceleration_y.values[matched_machine_limit_idx] + 0.5), - int(print.config().machine_max_acceleration_z.values[matched_machine_limit_idx] + 0.5), - int(print.config().machine_max_acceleration_e.values[matched_machine_limit_idx] + 0.5)); - file.write_format("M203 X%d Y%d Z%d E%d\n", - int(print.config().machine_max_speed_x.values[matched_machine_limit_idx] + 0.5), - int(print.config().machine_max_speed_y.values[matched_machine_limit_idx] + 0.5), - int(print.config().machine_max_speed_z.values[matched_machine_limit_idx] + 0.5), - int(print.config().machine_max_speed_e.values[matched_machine_limit_idx] + 0.5)); - - // Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about - // Legacy Marlin should export travel acceleration the same as printing acceleration. - // MarlinFirmware has the two separated. - int travel_acc = print.config().gcode_flavor == gcfMarlinLegacy - ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) - : int(print.config().machine_max_acceleration_travel.values.front() + 0.5); - file.write_format("M204 P%d R%d T%d\n", - int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), - int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), - travel_acc); - - assert(is_decimal_separator_point()); - file.write_format("M205 X%.2lf Y%.2lf Z%.2lf E%.2lf\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()); - //BBS: don't support M205 Sx Tx - //file.write_format("M205 S%d T%d\n", - // int(print.config().machine_min_extruding_rate.values.front() + 0.5), - // int(print.config().machine_min_travel_rate.values.front() + 0.5)); + // BBS + int GCode::get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const + { + std::string bed_temp_key = is_first_layer ? get_bed_temp_1st_layer_key(bed_type) : get_bed_temp_key(bed_type); + const ConfigOptionInts *bed_temp_opt = m_config.option(bed_temp_key); + return bed_temp_opt->get_at(extruder_id); } -} - -// BBS -int GCode::get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const -{ - std::string bed_temp_key = is_first_layer ? get_bed_temp_1st_layer_key(bed_type) : get_bed_temp_key(bed_type); - const ConfigOptionInts* bed_temp_opt = m_config.option(bed_temp_key); - return bed_temp_opt->get_at(extruder_id); -} -int GCode::get_highest_bed_temperature(const bool is_first_layer, const Print& print) const -{ - auto bed_type = m_config.curr_bed_type; - int bed_temp = 0; - for (auto fidx : print.get_slice_used_filaments(is_first_layer)) { - bed_temp = std::max(bed_temp, get_bed_temperature(fidx, is_first_layer, bed_type)); + int GCode::get_highest_bed_temperature(const bool is_first_layer, const Print &print) const + { + auto bed_type = m_config.curr_bed_type; + int bed_temp = 0; + for (auto fidx : print.get_slice_used_filaments(is_first_layer)) + { + bed_temp = std::max(bed_temp, get_bed_temperature(fidx, is_first_layer, bed_type)); + } + return bed_temp; } - return bed_temp; -} -// Write 1st layer bed temperatures into the G-code. -// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. -// M140 - Set Extruder Temperature -// M190 - Set Extruder Temperature and Wait -void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) -{ - // Initial bed temperature based on the first extruder. - // BBS - std::vector temps_per_bed; - int bed_temp = 0; - if (m_config.bed_temperature_formula.value == BedTempFormula::btfHighestTemp) { - bed_temp = get_highest_bed_temperature(true, print); - } - else { - bed_temp = get_bed_temperature(first_printing_extruder_id, true, print.config().curr_bed_type); - } - // Is the bed temperature set by the provided custom G-code? - int temp_by_gcode = -1; - bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, false, temp_by_gcode); - // BBS + // Write 1st layer bed temperatures into the G-code. + // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. + // M140 - Set Extruder Temperature + // M190 - Set Extruder Temperature and Wait + void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) + { + // Initial bed temperature based on the first extruder. + // BBS + std::vector temps_per_bed; + int bed_temp = 0; + if (m_config.bed_temperature_formula.value == BedTempFormula::btfHighestTemp) + { + bed_temp = get_highest_bed_temperature(true, print); + } + else + { + bed_temp = get_bed_temperature(first_printing_extruder_id, true, print.config().curr_bed_type); + } + // Is the bed temperature set by the provided custom G-code? + int temp_by_gcode = -1; + bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, false, temp_by_gcode); + // BBS #if 0 if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000) temp = temp_by_gcode; #endif - // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if - // the custom start G-code emited these. - std::string set_temp_gcode = m_writer.set_bed_temperature(bed_temp, wait); - if (! temp_set_by_gcode) - file.write(set_temp_gcode); -} + // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if + // the custom start G-code emited these. + std::string set_temp_gcode = m_writer.set_bed_temperature(bed_temp, wait); + if (!temp_set_by_gcode) + file.write(set_temp_gcode); + } -// Write 1st layer extruder temperatures into the G-code. -// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. -// M104 - Set Extruder Temperature -// M109 - Set Extruder Temperature and Wait -// RepRapFirmware: G10 Sxx -void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) -{ - // Is the bed temperature set by the provided custom G-code? - int temp_by_gcode = -1; - bool include_g10 = print.config().gcode_flavor == gcfRepRapFirmware; - if (custom_gcode_sets_temperature(gcode, 104, 109, include_g10, temp_by_gcode)) { - // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code. - int temp = print.config().nozzle_temperature_initial_layer.get_at(first_printing_extruder_id); - if (temp_by_gcode >= 0 && temp_by_gcode < 1000) - temp = temp_by_gcode; - m_writer.set_temperature(temp, wait, first_printing_extruder_id); - } else { - // Custom G-code does not set the extruder temperature. Do it now. - if (print.config().single_extruder_multi_material.value) { - // Set temperature of the first printing extruder only. + // Write 1st layer extruder temperatures into the G-code. + // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. + // M104 - Set Extruder Temperature + // M109 - Set Extruder Temperature and Wait + // RepRapFirmware: G10 Sxx + void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) + { + // Is the bed temperature set by the provided custom G-code? + int temp_by_gcode = -1; + bool include_g10 = print.config().gcode_flavor == gcfRepRapFirmware; + if (custom_gcode_sets_temperature(gcode, 104, 109, include_g10, temp_by_gcode)) + { + // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code. int temp = print.config().nozzle_temperature_initial_layer.get_at(first_printing_extruder_id); - if (temp > 0) - file.write(m_writer.set_temperature(temp, wait, first_printing_extruder_id)); - } else { - // Set temperatures of all the printing extruders. - for (unsigned int tool_id : print.extruders()) { - int temp = print.config().nozzle_temperature_initial_layer.get_at(tool_id); - if (print.config().ooze_prevention.value) - temp += print.config().standby_temperature_delta.value; + if (temp_by_gcode >= 0 && temp_by_gcode < 1000) + temp = temp_by_gcode; + m_writer.set_temperature(temp, wait, first_printing_extruder_id); + } + else + { + // Custom G-code does not set the extruder temperature. Do it now. + if (print.config().single_extruder_multi_material.value) + { + // Set temperature of the first printing extruder only. + int temp = print.config().nozzle_temperature_initial_layer.get_at(first_printing_extruder_id); if (temp > 0) - file.write(m_writer.set_temperature(temp, wait, tool_id)); + file.write(m_writer.set_temperature(temp, wait, first_printing_extruder_id)); + } + else + { + // Set temperatures of all the printing extruders. + for (unsigned int tool_id : print.extruders()) + { + int temp = print.config().nozzle_temperature_initial_layer.get_at(tool_id); + if (print.config().ooze_prevention.value) + temp += print.config().standby_temperature_delta.value; + if (temp > 0) + file.write(m_writer.set_temperature(temp, wait, tool_id)); + } } } } -} -inline GCode::ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects) -{ - std::vector &objects_by_extruder = by_extruder[extruder_id]; - if (objects_by_extruder.empty()) - objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder()); - return objects_by_extruder[object_idx]; -} + inline GCode::ObjectByExtruder &object_by_extruder( + std::map> &by_extruder, + unsigned int extruder_id, + size_t object_idx, + size_t num_objects) + { + std::vector &objects_by_extruder = by_extruder[extruder_id]; + if (objects_by_extruder.empty()) + objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder()); + return objects_by_extruder[object_idx]; + } + + inline std::vector &object_islands_by_extruder( + std::map> &by_extruder, + unsigned int extruder_id, + size_t object_idx, + size_t num_objects, + size_t num_islands) + { + std::vector &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands; + if (islands.empty()) + islands.assign(num_islands, GCode::ObjectByExtruder::Island()); + return islands; + } + + std::vector GCode::sort_print_object_instances( + std::vector &objects_by_extruder, + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx) + { + std::vector out; -inline std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects, - size_t num_islands) -{ - std::vector &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands; - if (islands.empty()) - islands.assign(num_islands, GCode::ObjectByExtruder::Island()); - return islands; -} + if (ordering == nullptr) + { + // Sequential print, single object is being printed. + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) + { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + // BBS:add the support of shared print object + const PrintObject *print_object = layers[layer_id].original_object; + // const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx, print_object->instances()[single_object_instance_idx].model_instance->get_labeled_id()); + } + } + else + { + // Create mapping from PrintObject* to ObjectByExtruder*. + std::vector> sorted; + sorted.reserve(objects_by_extruder.size()); + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) + { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + // BBS:add the support of shared print object + const PrintObject *print_object = layers[layer_id].original_object; + // const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + sorted.emplace_back(print_object, &object_by_extruder); + } + std::sort(sorted.begin(), sorted.end()); -std::vector GCode::sort_print_object_instances( - std::vector &objects_by_extruder, - const std::vector &layers, - // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, - // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx) -{ - std::vector out; - - if (ordering == nullptr) { - // Sequential print, single object is being printed. - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - //BBS:add the support of shared print object - const PrintObject *print_object = layers[layer_id].original_object; - //const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx, print_object->instances()[single_object_instance_idx].model_instance->get_labeled_id()); - } - } else { - // Create mapping from PrintObject* to ObjectByExtruder*. - std::vector> sorted; - sorted.reserve(objects_by_extruder.size()); - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - //BBS:add the support of shared print object - const PrintObject *print_object = layers[layer_id].original_object; - //const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - sorted.emplace_back(print_object, &object_by_extruder); - } - std::sort(sorted.begin(), sorted.end()); - - if (! sorted.empty()) { - out.reserve(sorted.size()); - for (const PrintInstance *instance : *ordering) { - const PrintObject &print_object = *instance->print_object; - //BBS:add the support of shared print object - //const PrintObject* print_obj_ptr = &print_object; - //if (print_object.get_shared_object()) - // print_obj_ptr = print_object.get_shared_object(); - std::pair key(&print_object, nullptr); - auto it = std::lower_bound(sorted.begin(), sorted.end(), key); - if (it != sorted.end() && it->first == &print_object) - // ObjectByExtruder for this PrintObject was found. - out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data(), instance->model_instance->get_labeled_id()); + if (!sorted.empty()) + { + out.reserve(sorted.size()); + for (const PrintInstance *instance : *ordering) + { + const PrintObject &print_object = *instance->print_object; + // BBS:add the support of shared print object + // const PrintObject* print_obj_ptr = &print_object; + // if (print_object.get_shared_object()) + // print_obj_ptr = print_object.get_shared_object(); + std::pair key(&print_object, nullptr); + auto it = std::lower_bound(sorted.begin(), sorted.end(), key); + if (it != sorted.end() && it->first == &print_object) + // ObjectByExtruder for this PrintObject was found. + out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data(), instance->model_instance->get_labeled_id()); + } } } + return out; } - return out; -} - -namespace ProcessLayer -{ - static std::string emit_custom_gcode_per_print_z( - GCode &gcodegen, - const CustomGCode::Item *custom_gcode, - unsigned int current_extruder_id, - // ID of the first extruder printing this layer. - unsigned int first_extruder_id, - const PrintConfig &config) + namespace ProcessLayer { - std::string gcode; - // BBS - bool single_filament_print = config.filament_diameter.size() == 1; - - if (custom_gcode != nullptr) { - // Extruder switches are processed by LayerTools, they should be filtered out. - assert(custom_gcode->type != CustomGCode::ToolChange); - - CustomGCode::Type gcode_type = custom_gcode->type; - bool color_change = gcode_type == CustomGCode::ColorChange; - bool tool_change = gcode_type == CustomGCode::ToolChange; - // Tool Change is applied as Color Change for a single extruder printer only. - assert(!tool_change || single_filament_print); - - std::string pause_print_msg; - int m600_extruder_before_layer = -1; - if (color_change && custom_gcode->extruder > 0) - m600_extruder_before_layer = custom_gcode->extruder - 1; - else if (gcode_type == CustomGCode::PausePrint) - pause_print_msg = custom_gcode->extra; - //BBS: inserting color gcode is removed + + static std::string emit_custom_gcode_per_print_z( + GCode &gcodegen, + const CustomGCode::Item *custom_gcode, + unsigned int current_extruder_id, + // ID of the first extruder printing this layer. + unsigned int first_extruder_id, + const PrintConfig &config) + { + std::string gcode; + // BBS + bool single_filament_print = config.filament_diameter.size() == 1; + + if (custom_gcode != nullptr) + { + // Extruder switches are processed by LayerTools, they should be filtered out. + assert(custom_gcode->type != CustomGCode::ToolChange); + + CustomGCode::Type gcode_type = custom_gcode->type; + bool color_change = gcode_type == CustomGCode::ColorChange; + bool tool_change = gcode_type == CustomGCode::ToolChange; + // Tool Change is applied as Color Change for a single extruder printer only. + assert(!tool_change || single_filament_print); + + std::string pause_print_msg; + int m600_extruder_before_layer = -1; + if (color_change && custom_gcode->extruder > 0) + m600_extruder_before_layer = custom_gcode->extruder - 1; + else if (gcode_type == CustomGCode::PausePrint) + pause_print_msg = custom_gcode->extra; + // BBS: inserting color gcode is removed #if 0 // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count if (color_change || tool_change) @@ -3702,1022 +4034,1146 @@ namespace ProcessLayer // add tag for processor gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n"; //! FIXME_in_fw show message during print pause - //if (!pause_print_msg.empty()) - // gcode += "M117 " + pause_print_msg + "\n"; + // if (!pause_print_msg.empty()) + // gcode += "M117 " + pause_print_msg + "\n"; gcode += gcodegen.placeholder_parser_process("machine_pause_gcode", config.machine_pause_gcode, current_extruder_id) + "\n"; } - else { + else + { // add tag for processor gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Custom_Code) + "\n"; - if (gcode_type == CustomGCode::Template) // Template Custom Gcode + if (gcode_type == CustomGCode::Template) // Template Custom Gcode gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id); - else // custom Gcode + else // custom Gcode gcode += custom_gcode->extra; - } gcode += "\n"; #if 0 } #endif - } + } - return gcode; - } -} // namespace ProcessLayer + return gcode; + } + } // namespace ProcessLayer -namespace Skirt { - static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map> &skirt_loops_per_extruder_out) + namespace Skirt { - // Prime all extruders printing over the 1st layer over the skirt lines. - size_t n_loops = print.skirt().entities.size(); - size_t n_tools = layer_tools.extruders.size(); - size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools; - - // BBS. Extrude skirt with first extruder if min_skirt_length is zero - const PrintConfig &config = print.config(); - if (Print::min_skirt_length < EPSILON) { - skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, n_loops); - } else { - for (size_t i = 0; i < n_loops; i += lines_per_extruder) - skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair(i, std::min(i + lines_per_extruder, n_loops)); + static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map> &skirt_loops_per_extruder_out) + { + // Prime all extruders printing over the 1st layer over the skirt lines. + size_t n_loops = print.skirt().entities.size(); + size_t n_tools = layer_tools.extruders.size(); + size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools; + + // BBS. Extrude skirt with first extruder if min_skirt_length is zero + const PrintConfig &config = print.config(); + if (Print::min_skirt_length < EPSILON) + { + skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, n_loops); + } + else + { + for (size_t i = 0; i < n_loops; i += lines_per_extruder) + skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair(i, std::min(i + lines_per_extruder, n_loops)); + } } - } - static std::map> make_skirt_loops_per_extruder_1st_layer( - const Print &print, - const LayerTools &layer_tools, - // Heights (print_z) at which the skirt has already been extruded. - std::vector &skirt_done) - { - // Extrude skirt at the print_z of the raft layers and normal object layers - // not at the print_z of the interlaced support material layers. - std::map> skirt_loops_per_extruder_out; - //For sequential print, the following test may fail when extruding the 2nd and other objects. - // assert(skirt_done.empty()); - if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt) { - skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); - skirt_done.emplace_back(layer_tools.print_z); - } - return skirt_loops_per_extruder_out; - } + static std::map> make_skirt_loops_per_extruder_1st_layer( + const Print &print, + const LayerTools &layer_tools, + // Heights (print_z) at which the skirt has already been extruded. + std::vector &skirt_done) + { + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + std::map> skirt_loops_per_extruder_out; + // For sequential print, the following test may fail when extruding the 2nd and other objects. + // assert(skirt_done.empty()); + if (skirt_done.empty() && print.has_skirt() && !print.skirt().entities.empty() && layer_tools.has_skirt) + { + skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); + skirt_done.emplace_back(layer_tools.print_z); + } + return skirt_loops_per_extruder_out; + } - static std::map> make_skirt_loops_per_extruder_other_layers( - const Print &print, - const LayerTools &layer_tools, - // Heights (print_z) at which the skirt has already been extruded. - std::vector &skirt_done) - { - // Extrude skirt at the print_z of the raft layers and normal object layers - // not at the print_z of the interlaced support material layers. - std::map> skirt_loops_per_extruder_out; - if (print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt && - // Not enough skirt layers printed yet. - //FIXME infinite or high skirt does not make sense for sequential print! - (skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt())) { - bool valid = ! skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON; - assert(valid); - // This print_z has not been extruded yet (sequential print) - // FIXME: The skirt_done should not be empty at this point. The check is a workaround - if (valid) { + static std::map> make_skirt_loops_per_extruder_other_layers( + const Print &print, + const LayerTools &layer_tools, + // Heights (print_z) at which the skirt has already been extruded. + std::vector &skirt_done) + { + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + std::map> skirt_loops_per_extruder_out; + if (print.has_skirt() && !print.skirt().entities.empty() && layer_tools.has_skirt && + // Not enough skirt layers printed yet. + // FIXME infinite or high skirt does not make sense for sequential print! + (skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt())) + { + bool valid = !skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON; + assert(valid); + // This print_z has not been extruded yet (sequential print) + // FIXME: The skirt_done should not be empty at this point. The check is a workaround + if (valid) + { #if 0 // Prime just the first printing extruder. This is original Slic3r's implementation. skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, print.config().skirt_loops.value); #else - // Prime all extruders planned for this layer, see - skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); + // Prime all extruders planned for this layer, see + skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); #endif - assert(!skirt_done.empty()); - skirt_done.emplace_back(layer_tools.print_z); + assert(!skirt_done.empty()); + skirt_done.emplace_back(layer_tools.print_z); + } } + return skirt_loops_per_extruder_out; } - return skirt_loops_per_extruder_out; - } -} // namespace Skirt + } // namespace Skirt -inline std::string get_instance_name(const PrintObject* object, size_t inst_id) { - auto obj_name = object->model_object()->name; - // replace space in obj_name with '-' - std::replace(obj_name.begin(), obj_name.end(), ' ', '_'); - - return (boost::format("%1%_id_%2%_copy_%3%") % obj_name % object->get_klipper_object_id() % inst_id).str(); -} - -inline std::string get_instance_name(const PrintObject* object, const PrintInstance& inst) { - return get_instance_name(object, inst.id); -} + inline std::string get_instance_name(const PrintObject *object, size_t inst_id) + { + auto obj_name = object->model_object()->name; + // replace space in obj_name with '-' + std::replace(obj_name.begin(), obj_name.end(), ' ', '_'); -// In sequential mode, process_layer is called once per each object and its copy, -// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. -// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. -// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths -// and performing the extruder specific extrusions together. -GCode::LayerResult GCode::process_layer( - const Print &print, - // Set of object & print layers of the same PrintObject and with the same print_z. - const std::vector &layers, - const LayerTools &layer_tools, - const bool last_layer, - // Pairs of PrintObject index and its instance index. - const std::vector *ordering, - const int most_used_extruder, - // If set to size_t(-1), then print all copies of all objects. - // Otherwise print a single copy of a single object. - const size_t single_object_instance_idx, - // BBS - const bool prime_extruder) -{ - assert(! layers.empty()); - // Either printing all copies of all objects, or just a single copy of a single object. - assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); - - // First object, support and raft layer, if available. - const Layer *object_layer = nullptr; - const SupportLayer *support_layer = nullptr; - const SupportLayer *raft_layer = nullptr; - for (const LayerToPrint &l : layers) { - if (l.object_layer && ! object_layer) - object_layer = l.object_layer; - if (l.support_layer) { - if (! support_layer) - support_layer = l.support_layer; - if (! raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers()) - raft_layer = support_layer; - } + return (boost::format("%1%_id_%2%_copy_%3%") % obj_name % object->get_klipper_object_id() % inst_id).str(); } - const Layer* layer_ptr = nullptr; - if (object_layer != nullptr) - layer_ptr = object_layer; - else if (support_layer != nullptr) - layer_ptr = support_layer; - const Layer& layer = *layer_ptr; - GCode::LayerResult result { {}, layer.id(), false, last_layer }; - if (layer_tools.extruders.empty()) - // Nothing to extrude. - return result; - - // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. - coordf_t print_z = layer.print_z; - //BBS: using layer id to judge whether the layer is first layer is wrong. Because if the normal - //support is attached above the object, and support layers has independent layer height, then the lowest support - //interface layer id is 0. - bool first_layer = (layer.id() == 0 && abs(layer.bottom_z()) < EPSILON); - unsigned int first_extruder_id = layer_tools.extruders.front(); - - // Initialize config with the 1st object to be printed at this layer. - m_config.apply(layer.object()->config(), true); - - // Check whether it is possible to apply the spiral vase logic for this layer. - // Just a reminder: A spiral vase mode is allowed for a single object, single material print only. - m_enable_loop_clipping = true; - if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) { - bool enable = (layer.id() > 0 || !print.has_brim()) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt()); - if (enable) { - for (const LayerRegion *layer_region : layer.regions()) - if (size_t(layer_region->region().config().bottom_shell_layers.value) > layer.id() || - layer_region->perimeters.items_count() > 1u || - layer_region->fills.items_count() > 0) { - enable = false; - break; - } + inline std::string get_instance_name(const PrintObject *object, const PrintInstance &inst) + { + return get_instance_name(object, inst.id); + } + + // In sequential mode, process_layer is called once per each object and its copy, + // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. + // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. + // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths + // and performing the extruder specific extrusions together. + GCode::LayerResult GCode::process_layer( + const Print &print, + // Set of object & print layers of the same PrintObject and with the same print_z. + const std::vector &layers, + const LayerTools &layer_tools, + const bool last_layer, + // Pairs of PrintObject index and its instance index. + const std::vector *ordering, + const int most_used_extruder, + // If set to size_t(-1), then print all copies of all objects. + // Otherwise print a single copy of a single object. + const size_t single_object_instance_idx, + // BBS + const bool prime_extruder) + { + assert(!layers.empty()); + // Either printing all copies of all objects, or just a single copy of a single object. + assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); + + // First object, support and raft layer, if available. + const Layer *object_layer = nullptr; + const SupportLayer *support_layer = nullptr; + const SupportLayer *raft_layer = nullptr; + for (const LayerToPrint &l : layers) + { + if (l.object_layer && !object_layer) + object_layer = l.object_layer; + if (l.support_layer) + { + if (!support_layer) + support_layer = l.support_layer; + if (!raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers()) + raft_layer = support_layer; + } } - result.spiral_vase_enable = enable; - // If we're going to apply spiralvase to this layer, disable loop clipping. - m_enable_loop_clipping = !enable; - } - - std::string gcode; - assert(is_decimal_separator_point()); // for the sprintfs - - // add tag for processor - gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) + "\n"; - // export layer z - char buf[64]; - sprintf(buf, "; Z_HEIGHT: %g\n", print_z); - gcode += buf; - // export layer height - float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; - sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), height); - gcode += buf; - // update caches - m_last_layer_z = static_cast(print_z); - m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); - m_last_height = height; - - // Set new layer - this will change Z and force a retraction if retract_when_changing_layer is enabled. - if (! print.config().before_layer_change_gcode.value.empty()) { - DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - gcode += this->placeholder_parser_process("before_layer_change_gcode", - print.config().before_layer_change_gcode.value, m_writer.filament()->id(), &config) - + "\n"; - } - PrinterStructure printer_structure = m_config.printer_structure.value; - PrintSequence print_sequence = m_config.print_sequence; - bool sequence_by_layer = print_sequence == PrintSequence::ByLayer; - bool is_i3_printer = printer_structure == PrinterStructure::psI3; - bool is_multi_extruder = m_config.nozzle_diameter.size() > 1; + const Layer *layer_ptr = nullptr; + if (object_layer != nullptr) + layer_ptr = object_layer; + else if (support_layer != nullptr) + layer_ptr = support_layer; + const Layer &layer = *layer_ptr; + GCode::LayerResult result{{}, layer.id(), false, last_layer}; + if (layer_tools.extruders.empty()) + // Nothing to extrude. + return result; + + // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. + coordf_t print_z = layer.print_z; + // BBS: using layer id to judge whether the layer is first layer is wrong. Because if the normal + // support is attached above the object, and support layers has independent layer height, then the lowest support + // interface layer id is 0. + bool first_layer = (layer.id() == 0 && abs(layer.bottom_z()) < EPSILON); + unsigned int first_extruder_id = layer_tools.extruders.front(); + + // Initialize config with the 1st object to be printed at this layer. + m_config.apply(layer.object()->config(), true); + + // Check whether it is possible to apply the spiral vase logic for this layer. + // Just a reminder: A spiral vase mode is allowed for a single object, single material print only. + m_enable_loop_clipping = true; + if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) + { + bool enable = (layer.id() > 0 || !print.has_brim()) && (layer.id() >= (size_t)print.config().skirt_height.value && !print.has_infinite_skirt()); + if (enable) + { + for (const LayerRegion *layer_region : layer.regions()) + if (size_t(layer_region->region().config().bottom_shell_layers.value) > layer.id() || + layer_region->perimeters.items_count() > 1u || + layer_region->fills.items_count() > 0) + { + enable = false; + break; + } + } + result.spiral_vase_enable = enable; + // If we're going to apply spiralvase to this layer, disable loop clipping. + m_enable_loop_clipping = !enable; + } - bool need_insert_timelapse_gcode_for_traditional = false; - if (!m_wipe_tower || !m_wipe_tower->enable_timelapse_print()) { - need_insert_timelapse_gcode_for_traditional = ((is_i3_printer && !m_spiral_vase)|| is_multi_extruder); - } + std::string gcode; + assert(is_decimal_separator_point()); // for the sprintfs - bool has_insert_timelapse_gcode = false; - bool has_wipe_tower = (layer_tools.has_wipe_tower && m_wipe_tower); + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) + "\n"; + // export layer z + char buf[64]; + sprintf(buf, "; Z_HEIGHT: %g\n", print_z); + gcode += buf; + // export layer height + float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; + sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), height); + gcode += buf; + // update caches + m_last_layer_z = static_cast(print_z); + m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); + m_last_height = height; + // Set new layer - this will change Z and force a retraction if retract_when_changing_layer is enabled. + if (!print.config().before_layer_change_gcode.value.empty()) + { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + gcode += this->placeholder_parser_process("before_layer_change_gcode", + print.config().before_layer_change_gcode.value, m_writer.filament()->id(), &config) + + "\n"; + } - ZHopType z_hope_type = ZHopType(FILAMENT_CONFIG(z_hop_types)); - LiftType auto_lift_type = LiftType::NormalLift; - if (z_hope_type == ZHopType::zhtAuto || z_hope_type == ZHopType::zhtSpiral || z_hope_type == ZHopType::zhtSlope) - auto_lift_type = LiftType::SpiralLift; + PrinterStructure printer_structure = m_config.printer_structure.value; + PrintSequence print_sequence = m_config.print_sequence; + bool sequence_by_layer = print_sequence == PrintSequence::ByLayer; + bool is_i3_printer = printer_structure == PrinterStructure::psI3; + bool is_multi_extruder = m_config.nozzle_diameter.size() > 1; - // BBS: don't use lazy_raise when enable spiral vase - gcode += this->change_layer(print_z); // this will increase m_layer_index - m_layer = &layer; - m_object_layer_over_raft = false; - if (! print.config().layer_change_gcode.value.empty()) { - DynamicConfig config; - config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - gcode += this->placeholder_parser_process("layer_change_gcode", - print.config().layer_change_gcode.value, m_writer.filament()->id(), &config) - + "\n"; - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - } - //BBS: set layer time fan speed after layer change gcode - gcode += ";_SET_FAN_SPEED_CHANGING_LAYER\n"; + bool need_insert_timelapse_gcode_for_traditional = false; + if (!m_wipe_tower || !m_wipe_tower->enable_timelapse_print()) + { + need_insert_timelapse_gcode_for_traditional = ((is_i3_printer && !m_spiral_vase) || is_multi_extruder); + } - m_writer.set_first_layer(this->on_first_layer()); + bool has_insert_timelapse_gcode = false; + bool has_wipe_tower = (layer_tools.has_wipe_tower && m_wipe_tower); - if (print.calib_mode() == CalibMode::Calib_PA_Tower) { - gcode += writer().set_pressure_advance(print.calib_params().start + static_cast(print_z) * print.calib_params().step); - } - else if (print.calib_mode() == CalibMode::Calib_Temp_Tower) { - auto offset = static_cast(print_z / 10.001) * 5; - gcode += writer().set_temperature(print.calib_params().start - offset); - } - else if (print.calib_mode() == CalibMode::Calib_Vol_speed_Tower) { - auto _speed = print.calib_params().start + print_z * print.calib_params().step; - m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({ std::round(_speed) })); - } - else if (print.calib_mode() == CalibMode::Calib_VFA_Tower) { - auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step; - m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({ std::round(_speed) })); - } - else if (print.calib_mode() == CalibMode::Calib_Retraction_tower) { - auto _length = print.calib_params().start + std::floor(std::max(0.0, print_z - 0.4)) * print.calib_params().step; - DynamicConfig _cfg; - _cfg.set_key_value("retraction_length", new ConfigOptionFloatsNullable{_length}); - writer().config.apply(_cfg); - sprintf(buf, "; Calib_Retraction_tower: Z_HEIGHT: %g, length:%g\n", print_z, _length); - gcode += buf; - } + ZHopType z_hope_type = ZHopType(FILAMENT_CONFIG(z_hop_types)); + LiftType auto_lift_type = LiftType::NormalLift; + if (z_hope_type == ZHopType::zhtAuto || z_hope_type == ZHopType::zhtSpiral || z_hope_type == ZHopType::zhtSlope) + auto_lift_type = LiftType::SpiralLift; - //BBS - if (first_layer) { - //BBS: set first layer global acceleration - if (NOZZLE_CONFIG(default_acceleration) > 0 && NOZZLE_CONFIG(initial_layer_acceleration) > 0) { - double acceleration = NOZZLE_CONFIG(initial_layer_acceleration); - m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + // BBS: don't use lazy_raise when enable spiral vase + gcode += this->change_layer(print_z); // this will increase m_layer_index + m_layer = &layer; + m_object_layer_over_raft = false; + if (!print.config().layer_change_gcode.value.empty()) + { + DynamicConfig config; + config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += this->placeholder_parser_process("layer_change_gcode", + print.config().layer_change_gcode.value, m_writer.filament()->id(), &config) + + "\n"; + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); } + // BBS: set layer time fan speed after layer change gcode + gcode += ";_SET_FAN_SPEED_CHANGING_LAYER\n"; - if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && !this->is_BBL_Printer()) - gcode += m_writer.set_jerk_xy(m_config.initial_layer_jerk.value); - } + m_writer.set_first_layer(this->on_first_layer()); - if (!first_layer && !m_second_layer_things_done) { - //BBS: open powerlost recovery + if (print.calib_mode() == CalibMode::Calib_PA_Tower) { - if (print.is_BBL_Printer()) { - gcode += "; open powerlost recovery\n"; - gcode += "M1003 S1\n"; - } + gcode += writer().set_pressure_advance(print.calib_params().start + static_cast(print_z) * print.calib_params().step); } - // BBS: open first layer inspection at second layer - if (print.config().scan_first_layer.value) { - // BBS: retract first to avoid droping when scan model - gcode += this->retract(); - gcode += "M976 S1 P1 ; scan model before printing 2nd layer\n"; - gcode += "M400 P100\n"; - gcode += this->unretract(); + else if (print.calib_mode() == CalibMode::Calib_Temp_Tower) + { + auto offset = static_cast(print_z / 10.001) * 5; + gcode += writer().set_temperature(print.calib_params().start - offset); } - - //BBS: reset acceleration at sencond layer - if (NOZZLE_CONFIG(default_acceleration) > 0 && NOZZLE_CONFIG(initial_layer_acceleration) > 0) { - double acceleration = NOZZLE_CONFIG(default_acceleration); - m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + else if (print.calib_mode() == CalibMode::Calib_Vol_speed_Tower) + { + auto _speed = print.calib_params().start + print_z * print.calib_params().step; + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({std::round(_speed)})); } - - if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && !this->is_BBL_Printer()) - gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); - - // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent - // nozzle_temperature_initial_layer vs. temperature settings. - for (const Extruder& extruder : m_writer.extruders()) { - if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.filament()->id()) - // In single extruder multi material mode, set the temperature for the current extruder only. - continue; - int temperature = print.config().nozzle_temperature.get_at(extruder.id()); - if (temperature > 0 && temperature != print.config().nozzle_temperature_initial_layer.get_at(extruder.id())) - gcode += m_writer.set_temperature(temperature, false, extruder.id()); + else if (print.calib_mode() == CalibMode::Calib_VFA_Tower) + { + auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step; + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({std::round(_speed)})); + } + else if (print.calib_mode() == CalibMode::Calib_Retraction_tower) + { + auto _length = print.calib_params().start + std::floor(std::max(0.0, print_z - 0.4)) * print.calib_params().step; + DynamicConfig _cfg; + _cfg.set_key_value("retraction_length", new ConfigOptionFloatsNullable{_length}); + writer().config.apply(_cfg); + sprintf(buf, "; Calib_Retraction_tower: Z_HEIGHT: %g, length:%g\n", print_z, _length); + gcode += buf; } // BBS - int bed_temp = 0; - if (m_config.bed_temperature_formula == BedTempFormula::btfHighestTemp) - bed_temp = get_highest_bed_temperature(false,print); - else - bed_temp = get_bed_temperature(first_extruder_id, false, m_config.curr_bed_type); - gcode += m_writer.set_bed_temperature(bed_temp); - // Mark the temperature transition from 1st to 2nd layer to be finished. - m_second_layer_things_done = true; - } - - // Map from extruder ID to index of skirt loops to be extruded with that extruder. - std::map> skirt_loops_per_extruder; - - if (single_object_instance_idx == size_t(-1)) { - // Normal (non-sequential) print. - gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.filament()->id(), first_extruder_id, print.config()); - } - // Extrude skirt at the print_z of the raft layers and normal object layers - // not at the print_z of the interlaced support material layers. - skirt_loops_per_extruder = first_layer ? - Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : - Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); - - // BBS: get next extruder according to flush and soluble - auto get_next_extruder = [&](int current_extruder,const std::vector&extruders) { - std::vector flush_matrix(cast(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size()))); - const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON); - // Extract purging volumes for each extruder pair: - std::vector> wipe_volumes; - for (unsigned int i = 0; i < number_of_extruders; ++i) - wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); - unsigned int next_extruder = current_extruder; - float min_flush = std::numeric_limits::max(); - for (auto extruder_id : extruders) { - if (print.config().filament_soluble.get_at(extruder_id) || extruder_id == current_extruder) - continue; - if (wipe_volumes[current_extruder][extruder_id] < min_flush) { - next_extruder = extruder_id; - min_flush = wipe_volumes[current_extruder][extruder_id]; + if (first_layer) + { + // BBS: set first layer global acceleration + if (NOZZLE_CONFIG(default_acceleration) > 0 && NOZZLE_CONFIG(initial_layer_acceleration) > 0) + { + double acceleration = NOZZLE_CONFIG(initial_layer_acceleration); + m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } + + if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && !this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.initial_layer_jerk.value); } - return next_extruder; - }; - // Group extrusions by an extruder, then by an object, an island and a region. - std::map> by_extruder; - bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); - for (const LayerToPrint &layer_to_print : layers) { - if (layer_to_print.support_layer != nullptr) { - const SupportLayer &support_layer = *layer_to_print.support_layer; - const PrintObject& object = *layer_to_print.original_object; - if (! support_layer.support_fills.entities.empty()) { - ExtrusionRole role = support_layer.support_fills.role(); - bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition; - bool has_interface = role == erMixed || role == erSupportMaterialInterface; - // Extruder ID of the support base. -1 if "don't care". - unsigned int support_extruder = object.config().support_filament.value - 1; - // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool support_dontcare = object.config().support_filament.value == 0; - // Extruder ID of the support interface. -1 if "don't care". - unsigned int interface_extruder = object.config().support_interface_filament.value - 1; - // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool interface_dontcare = object.config().support_interface_filament.value == 0; - - // BBS: apply wiping overridden extruders - WipingExtrusions& wiping_extrusions = const_cast(layer_tools).wiping_extrusions(); - if (support_dontcare) { - int extruder_override = wiping_extrusions.get_support_extruder_overrides(&object); - if (extruder_override >= 0) { - support_extruder = extruder_override; - support_dontcare = false; - } + if (!first_layer && !m_second_layer_things_done) + { + // BBS: open powerlost recovery + { + if (print.is_BBL_Printer()) + { + gcode += "; open powerlost recovery\n"; + gcode += "M1003 S1\n"; } + } + // BBS: open first layer inspection at second layer + if (print.config().scan_first_layer.value) + { + // BBS: retract first to avoid droping when scan model + gcode += this->retract(); + gcode += "M976 S1 P1 ; scan model before printing 2nd layer\n"; + gcode += "M400 P100\n"; + gcode += this->unretract(); + } - if (interface_dontcare) { - int extruder_override = wiping_extrusions.get_support_interface_extruder_overrides(&object); - if (extruder_override >= 0) { - interface_extruder = extruder_override; - interface_dontcare = false; - } - } + // BBS: reset acceleration at sencond layer + if (NOZZLE_CONFIG(default_acceleration) > 0 && NOZZLE_CONFIG(initial_layer_acceleration) > 0) + { + double acceleration = NOZZLE_CONFIG(default_acceleration); + m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + } - // BBS: try to print support base with a filament other than interface filament - if (support_dontcare && !interface_dontcare) { - unsigned int dontcare_extruder = first_extruder_id; - for (unsigned int extruder_id : layer_tools.extruders) { - if (print.config().filament_soluble.get_at(extruder_id)) - continue; + if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && !this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); - //BBS: now we don't consider interface filament used in other object - if (extruder_id == interface_extruder) - continue; + // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent + // nozzle_temperature_initial_layer vs. temperature settings. + for (const Extruder &extruder : m_writer.extruders()) + { + if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.filament()->id()) + // In single extruder multi material mode, set the temperature for the current extruder only. + continue; + int temperature = print.config().nozzle_temperature.get_at(extruder.id()); + if (temperature > 0 && temperature != print.config().nozzle_temperature_initial_layer.get_at(extruder.id())) + gcode += m_writer.set_temperature(temperature, false, extruder.id()); + } - dontcare_extruder = extruder_id; - break; + // BBS + int bed_temp = 0; + if (m_config.bed_temperature_formula == BedTempFormula::btfHighestTemp) + bed_temp = get_highest_bed_temperature(false, print); + else + bed_temp = get_bed_temperature(first_extruder_id, false, m_config.curr_bed_type); + gcode += m_writer.set_bed_temperature(bed_temp); + // Mark the temperature transition from 1st to 2nd layer to be finished. + m_second_layer_things_done = true; + } + + // Map from extruder ID to index of skirt loops to be extruded with that extruder. + std::map> skirt_loops_per_extruder; + + if (single_object_instance_idx == size_t(-1)) + { + // Normal (non-sequential) print. + gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.filament()->id(), first_extruder_id, print.config()); + } + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + skirt_loops_per_extruder = first_layer ? Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); + + // BBS: get next extruder according to flush and soluble + auto get_next_extruder = [&](int current_extruder, const std::vector &extruders) + { + std::vector flush_matrix(cast(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size()))); + const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON); + // Extract purging volumes for each extruder pair: + std::vector> wipe_volumes; + for (unsigned int i = 0; i < number_of_extruders; ++i) + wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); + unsigned int next_extruder = current_extruder; + float min_flush = std::numeric_limits::max(); + for (auto extruder_id : extruders) + { + if (print.config().filament_soluble.get_at(extruder_id) || extruder_id == current_extruder) + continue; + if (wipe_volumes[current_extruder][extruder_id] < min_flush) + { + next_extruder = extruder_id; + min_flush = wipe_volumes[current_extruder][extruder_id]; + } + } + return next_extruder; + }; + + // Group extrusions by an extruder, then by an object, an island and a region. + std::map> by_extruder; + bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); + for (const LayerToPrint &layer_to_print : layers) + { + if (layer_to_print.support_layer != nullptr) + { + const SupportLayer &support_layer = *layer_to_print.support_layer; + const PrintObject &object = *layer_to_print.original_object; + if (!support_layer.support_fills.entities.empty()) + { + ExtrusionRole role = support_layer.support_fills.role(); + bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition; + bool has_interface = role == erMixed || role == erSupportMaterialInterface; + // Extruder ID of the support base. -1 if "don't care". + unsigned int support_extruder = object.config().support_filament.value - 1; + // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + bool support_dontcare = object.config().support_filament.value == 0; + // Extruder ID of the support interface. -1 if "don't care". + unsigned int interface_extruder = object.config().support_interface_filament.value - 1; + // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + bool interface_dontcare = object.config().support_interface_filament.value == 0; + + // BBS: apply wiping overridden extruders + WipingExtrusions &wiping_extrusions = const_cast(layer_tools).wiping_extrusions(); + if (support_dontcare) + { + int extruder_override = wiping_extrusions.get_support_extruder_overrides(&object); + if (extruder_override >= 0) + { + support_extruder = extruder_override; + support_dontcare = false; + } + } + + if (interface_dontcare) + { + int extruder_override = wiping_extrusions.get_support_interface_extruder_overrides(&object); + if (extruder_override >= 0) + { + interface_extruder = extruder_override; + interface_dontcare = false; + } } - #if 0 + + // BBS: try to print support base with a filament other than interface filament + if (support_dontcare && !interface_dontcare) + { + unsigned int dontcare_extruder = first_extruder_id; + for (unsigned int extruder_id : layer_tools.extruders) + { + if (print.config().filament_soluble.get_at(extruder_id)) + continue; + + // BBS: now we don't consider interface filament used in other object + if (extruder_id == interface_extruder) + continue; + + dontcare_extruder = extruder_id; + break; + } +#if 0 //BBS: not found a suitable extruder in current layer ,dontcare_extruider==first_extruder_id==interface_extruder if (dontcare_extruder == interface_extruder && (object.config().support_interface_not_for_body && object.config().support_interface_filament.value!=0)) { // BBS : get a suitable extruder from other layer auto all_extruders = print.extruders(); dontcare_extruder = get_next_extruder(dontcare_extruder, all_extruders); } - #endif +#endif - if (support_dontcare) - support_extruder = dontcare_extruder; - } - else if (support_dontcare || interface_dontcare) { - // Some support will be printed with "don't care" material, preferably non-soluble. - // Is the current extruder assigned a soluble filament? - unsigned int dontcare_extruder = first_extruder_id; - if (print.config().filament_soluble.get_at(dontcare_extruder)) { - // The last extruder printed on the previous layer extrudes soluble filament. - // Try to find a non-soluble extruder on the same layer. - for (unsigned int extruder_id : layer_tools.extruders) - if (! print.config().filament_soluble.get_at(extruder_id)) { - dontcare_extruder = extruder_id; - break; - } + if (support_dontcare) + support_extruder = dontcare_extruder; } - if (print.config().filament_is_support.get_at(dontcare_extruder)) { - // The last extruder printed on the previous layer extrudes support filament. - // Try to find a non-support extruder on the same layer. - for (unsigned int extruder_id : layer_tools.extruders) - if (!print.config().filament_is_support.get_at(extruder_id)) { - dontcare_extruder = extruder_id; - break; - } + else if (support_dontcare || interface_dontcare) + { + // Some support will be printed with "don't care" material, preferably non-soluble. + // Is the current extruder assigned a soluble filament? + unsigned int dontcare_extruder = first_extruder_id; + if (print.config().filament_soluble.get_at(dontcare_extruder)) + { + // The last extruder printed on the previous layer extrudes soluble filament. + // Try to find a non-soluble extruder on the same layer. + for (unsigned int extruder_id : layer_tools.extruders) + if (!print.config().filament_soluble.get_at(extruder_id)) + { + dontcare_extruder = extruder_id; + break; + } + } + if (print.config().filament_is_support.get_at(dontcare_extruder)) + { + // The last extruder printed on the previous layer extrudes support filament. + // Try to find a non-support extruder on the same layer. + for (unsigned int extruder_id : layer_tools.extruders) + if (!print.config().filament_is_support.get_at(extruder_id)) + { + dontcare_extruder = extruder_id; + break; + } + } + if (support_dontcare) + support_extruder = dontcare_extruder; + if (interface_dontcare) + interface_extruder = dontcare_extruder; + } + // Both the support and the support interface are printed with the same extruder, therefore + // the interface may be interleaved with the support base. + bool single_extruder = !has_support || support_extruder == interface_extruder; + // Assign an extruder to the base. + ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size()); + obj.support = &support_layer.support_fills; + obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial; + if (!single_extruder && has_interface) + { + ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size()); + obj_interface.support = &support_layer.support_fills; + obj_interface.support_extrusion_role = erSupportMaterialInterface; } - if (support_dontcare) - support_extruder = dontcare_extruder; - if (interface_dontcare) - interface_extruder = dontcare_extruder; - } - // Both the support and the support interface are printed with the same extruder, therefore - // the interface may be interleaved with the support base. - bool single_extruder = ! has_support || support_extruder == interface_extruder; - // Assign an extruder to the base. - ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj.support = &support_layer.support_fills; - obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial; - if (! single_extruder && has_interface) { - ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj_interface.support = &support_layer.support_fills; - obj_interface.support_extrusion_role = erSupportMaterialInterface; } } - } - if (layer_to_print.object_layer != nullptr) { - const Layer &layer = *layer_to_print.object_layer; - // We now define a strategy for building perimeters and fills. The separation - // between regions doesn't matter in terms of printing order, as we follow - // another logic instead: - // - we group all extrusions by extruder so that we minimize toolchanges - // - we start from the last used extruder - // - for each extruder, we group extrusions by island - // - for each island, we extrude perimeters first, unless user set the infill_first - // option - // (Still, we have to keep track of regions because we need to apply their config) - size_t n_slices = layer.lslices.size(); - const std::vector &layer_surface_bboxes = layer.lslices_bboxes; - // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, - // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. - std::vector slices_test_order; - slices_test_order.reserve(n_slices); - for (size_t i = 0; i < n_slices; ++ i) - slices_test_order.emplace_back(i); - std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) { + if (layer_to_print.object_layer != nullptr) + { + const Layer &layer = *layer_to_print.object_layer; + // We now define a strategy for building perimeters and fills. The separation + // between regions doesn't matter in terms of printing order, as we follow + // another logic instead: + // - we group all extrusions by extruder so that we minimize toolchanges + // - we start from the last used extruder + // - for each extruder, we group extrusions by island + // - for each island, we extrude perimeters first, unless user set the infill_first + // option + // (Still, we have to keep track of regions because we need to apply their config) + size_t n_slices = layer.lslices.size(); + const std::vector &layer_surface_bboxes = layer.lslices_bboxes; + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + std::vector slices_test_order; + slices_test_order.reserve(n_slices); + for (size_t i = 0; i < n_slices; ++i) + slices_test_order.emplace_back(i); + std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) + { const Vec2d s1 = layer_surface_bboxes[i].size().cast(); const Vec2d s2 = layer_surface_bboxes[j].size().cast(); - return s1.x() * s1.y() < s2.x() * s2.y(); - }); - auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { - const BoundingBox &bbox = layer_surface_bboxes[i]; - return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && - point(1) >= bbox.min(1) && point(1) < bbox.max(1) && - layer.lslices[i].contour.contains(point); - }; - - for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { - const LayerRegion *layerm = layer.regions()[region_id]; - if (layerm == nullptr) - continue; - // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not - // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. - const PrintRegion ®ion = print.get_print_region(layerm->region().print_region_id()); - - // Now we must process perimeters and infills and create islands of extrusions in by_region std::map. - // It is also necessary to save which extrusions are part of MM wiping and which are not. - // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: - std::vector printing_extruders; - for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) { - bool is_infill = entity_type == ObjectByExtruder::Island::Region::INFILL; - for (const ExtrusionEntity *ee : is_infill ? layerm->fills.entities : layerm->perimeters.entities) { - // extrusions represents infill or perimeter extrusions of a single island. - assert(dynamic_cast(ee) != nullptr); - const auto *extrusions = static_cast(ee); - if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail. - continue; - - // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = layer_tools.extruder(*extrusions, region); + return s1.x() * s1.y() < s2.x() * s2.y(); }); + auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) + { + const BoundingBox &bbox = layer_surface_bboxes[i]; + return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && + point(1) >= bbox.min(1) && point(1) < bbox.max(1) && + layer.lslices[i].contour.contains(point); + }; - // Let's recover vector of extruder overrides: - const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr; - if (! layer_tools.has_extruder(correct_extruder_id)) { - // this entity is not overridden, but its extruder is not in layer_tools - we'll print it - // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) - correct_extruder_id = layer_tools.extruders.back(); - } - printing_extruders.clear(); - if (is_anything_overridden) { - entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, layer_to_print.original_object, correct_extruder_id, layer_to_print.object()->instances().size()); - if (entity_overrides == nullptr) { - printing_extruders.emplace_back(correct_extruder_id); - } else { - printing_extruders.reserve(entity_overrides->size()); - for (int extruder : *entity_overrides) - printing_extruders.emplace_back(extruder >= 0 ? - // at least one copy is overridden to use this extruder - extruder : - // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) - static_cast(- extruder - 1)); - Slic3r::sort_remove_duplicates(printing_extruders); + for (size_t region_id = 0; region_id < layer.regions().size(); ++region_id) + { + const LayerRegion *layerm = layer.regions()[region_id]; + if (layerm == nullptr) + continue; + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm->region().print_region_id()); + + // Now we must process perimeters and infills and create islands of extrusions in by_region std::map. + // It is also necessary to save which extrusions are part of MM wiping and which are not. + // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: + std::vector printing_extruders; + for (const ObjectByExtruder::Island::Region::Type entity_type : {ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS}) + { + bool is_infill = entity_type == ObjectByExtruder::Island::Region::INFILL; + for (const ExtrusionEntity *ee : is_infill ? layerm->fills.entities : layerm->perimeters.entities) + { + // extrusions represents infill or perimeter extrusions of a single island. + assert(dynamic_cast(ee) != nullptr); + const auto *extrusions = static_cast(ee); + if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail. + continue; + + // This extrusion is part of certain Region, which tells us which extruder should be used for it: + int correct_extruder_id = layer_tools.extruder(*extrusions, region); + + // Let's recover vector of extruder overrides: + const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr; + if (!layer_tools.has_extruder(correct_extruder_id)) + { + // this entity is not overridden, but its extruder is not in layer_tools - we'll print it + // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) + correct_extruder_id = layer_tools.extruders.back(); } - } else - printing_extruders.emplace_back(correct_extruder_id); + printing_extruders.clear(); + if (is_anything_overridden) + { + entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, layer_to_print.original_object, correct_extruder_id, layer_to_print.object()->instances().size()); + if (entity_overrides == nullptr) + { + printing_extruders.emplace_back(correct_extruder_id); + } + else + { + printing_extruders.reserve(entity_overrides->size()); + for (int extruder : *entity_overrides) + printing_extruders.emplace_back(extruder >= 0 ? + // at least one copy is overridden to use this extruder + extruder + : + // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) + static_cast(-extruder - 1)); + Slic3r::sort_remove_duplicates(printing_extruders); + } + } + else + printing_extruders.emplace_back(correct_extruder_id); - // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: - for (unsigned int extruder : printing_extruders) - { - std::vector &islands = object_islands_by_extruder( - by_extruder, - extruder, - &layer_to_print - layers.data(), - layers.size(), n_slices+1); - for (size_t i = 0; i <= n_slices; ++ i) { - bool last = i == n_slices; - size_t island_idx = last ? n_slices : slices_test_order[i]; - if (// extrusions->first_point does not fit inside any slice - last || - // extrusions->first_point fits inside ith slice - point_inside_surface(island_idx, extrusions->first_point())) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides); - break; + // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: + for (unsigned int extruder : printing_extruders) + { + std::vector &islands = object_islands_by_extruder( + by_extruder, + extruder, + &layer_to_print - layers.data(), + layers.size(), n_slices + 1); + for (size_t i = 0; i <= n_slices; ++i) + { + bool last = i == n_slices; + size_t island_idx = last ? n_slices : slices_test_order[i]; + if ( // extrusions->first_point does not fit inside any slice + last || + // extrusions->first_point fits inside ith slice + point_inside_surface(island_idx, extrusions->first_point())) + { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides); + break; + } } } } } - } - } // for regions - } - } // for objects + } // for regions + } + } // for objects - std::map> filament_to_print_instances; - { - for (unsigned int filament_id : layer_tools.extruders) { - auto objects_by_extruder_it = by_extruder.find(filament_id); - if (objects_by_extruder_it == by_extruder.end()) continue; + std::map> filament_to_print_instances; + { + for (unsigned int filament_id : layer_tools.extruders) + { + auto objects_by_extruder_it = by_extruder.find(filament_id); + if (objects_by_extruder_it == by_extruder.end()) + continue; - int plate_idx = print.get_plate_index(); - Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx)); + int plate_idx = print.get_plate_index(); + Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx)); - std::vector &objects_by_extruder = objects_by_extruder_it->second; - std::vector print_objects; - for (int obj_idx = 0; obj_idx < objects_by_extruder.size(); obj_idx++) { - auto &object_by_extruder = objects_by_extruder[obj_idx]; - if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) continue; + std::vector &objects_by_extruder = objects_by_extruder_it->second; + std::vector print_objects; + for (int obj_idx = 0; obj_idx < objects_by_extruder.size(); obj_idx++) + { + auto &object_by_extruder = objects_by_extruder[obj_idx]; + if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) + continue; - print_objects.push_back(print.get_object(obj_idx)); - } + print_objects.push_back(print.get_object(obj_idx)); + } - std::vector new_ordering = chain_print_object_instances(print_objects, &wt_pos); - std::reverse(new_ordering.begin(), new_ordering.end()); + std::vector new_ordering = chain_print_object_instances(print_objects, &wt_pos); + std::reverse(new_ordering.begin(), new_ordering.end()); - if (print.config().print_sequence == PrintSequence::ByObject) { - filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); - } else { - filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, &new_ordering, single_object_instance_idx); + if (print.config().print_sequence == PrintSequence::ByObject) + { + filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + } + else + { + filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, &new_ordering, single_object_instance_idx); + } } } - } - std::set layer_object_label_ids; - for (auto iter = filament_to_print_instances.begin(); iter != filament_to_print_instances.end(); ++iter) { - for (const InstanceToPrint &instance : iter->second) { - layer_object_label_ids.insert(instance.label_object_id); + std::set layer_object_label_ids; + for (auto iter = filament_to_print_instances.begin(); iter != filament_to_print_instances.end(); ++iter) + { + for (const InstanceToPrint &instance : iter->second) + { + layer_object_label_ids.insert(instance.label_object_id); + } } - } - auto insert_timelapse_gcode = [this, print_z, &print, &most_used_extruder, &layer_object_label_ids,&printed_objects = std::as_const(m_printed_objects)]() -> std::string { - PosPickCtx ctx; - ctx.curr_pos = { (coord_t)(scale_(m_writer.get_position().x())),(coord_t)(scale_(m_writer.get_position().y())) }; - ctx.curr_layer = this->layer(); - ctx.curr_extruder_id = m_writer.filament()->extruder_id(); - ctx.picture_extruder_id = most_used_extruder; - if (m_config.print_sequence == PrintSequence::ByObject && print.objects().size() > 1) - ctx.printed_objects = printed_objects; + auto insert_timelapse_gcode = [this, print_z, &print, &most_used_extruder, &layer_object_label_ids, &printed_objects = std::as_const(m_printed_objects)]() -> std::string + { + PosPickCtx ctx; + ctx.curr_pos = {(coord_t)(scale_(m_writer.get_position().x())), (coord_t)(scale_(m_writer.get_position().y()))}; + ctx.curr_layer = this->layer(); + ctx.curr_extruder_id = m_writer.filament()->extruder_id(); + ctx.picture_extruder_id = most_used_extruder; + if (m_config.print_sequence == PrintSequence::ByObject && print.objects().size() > 1) + ctx.printed_objects = printed_objects; + + auto timelapse_pos = m_timelapse_pos_picker.pick_pos(ctx); + + std::string timepals_gcode; + if (!print.config().time_lapse_gcode.value.empty()) + { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); + config.set_key_value("curr_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(ctx.curr_extruder_id))); + config.set_key_value("timelapse_pos_x", new ConfigOptionInt(timelapse_pos.x())); + config.set_key_value("timelapse_pos_y", new ConfigOptionInt(timelapse_pos.y())); + config.set_key_value("has_timelapse_safe_pos", new ConfigOptionBool(timelapse_pos != DefaultTimelapsePos)); + timepals_gcode = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.filament()->id(), &config) + "\n"; + } + m_writer.set_current_position_clear(false); + + double temp_z_after_tool_change; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_tool_change)) + { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_tool_change; + m_writer.set_position(pos); + } - auto timelapse_pos=m_timelapse_pos_picker.pick_pos(ctx); + // (layer_object_label_ids.size() < 64) this restriction comes from _encode_label_ids_to_base64() + if (print.is_BBL_Printer() && + (print.num_object_instances() <= g_max_label_object) && // Don't support too many objects on one plate + (print.num_object_instances() > 1) && // Don't support skipping single object + (layer_object_label_ids.size() > 0) && + (print.calib_params().mode == CalibMode::Calib_None)) + { + std::ostringstream oss; + for (auto it = layer_object_label_ids.begin(); it != layer_object_label_ids.end(); ++it) + { + if (it != layer_object_label_ids.begin()) + oss << ","; + oss << *it; + } - std::string timepals_gcode; - if (!print.config().time_lapse_gcode.value.empty()) { - DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); - config.set_key_value("curr_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(ctx.curr_extruder_id))); - config.set_key_value("timelapse_pos_x", new ConfigOptionInt(timelapse_pos.x())); - config.set_key_value("timelapse_pos_y", new ConfigOptionInt(timelapse_pos.y())); - config.set_key_value("has_timelapse_safe_pos", new ConfigOptionBool(timelapse_pos != DefaultTimelapsePos)); - timepals_gcode = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.filament()->id(), &config) + "\n"; - } - m_writer.set_current_position_clear(false); + std::string start_str = std::string("; object ids of layer ") + std::to_string(m_layer_index + 1) + (" start: ") + oss.str() + "\n"; + start_str += "M624 " + _encode_label_ids_to_base64(std::vector(layer_object_label_ids.begin(), layer_object_label_ids.end())) + "\n"; - double temp_z_after_tool_change; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_tool_change)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_tool_change; - m_writer.set_position(pos); - } + std::string end_str = std::string("; object ids of this layer") + std::to_string(m_layer_index + 1) + (" end: ") + oss.str() + "\n"; + end_str += "M625\n"; - // (layer_object_label_ids.size() < 64) this restriction comes from _encode_label_ids_to_base64() - if (print.is_BBL_Printer() && - (print.num_object_instances() <= g_max_label_object) && // Don't support too many objects on one plate - (print.num_object_instances() > 1) && // Don't support skipping single object - (layer_object_label_ids.size() > 0) && - (print.calib_params().mode == CalibMode::Calib_None)) { - std::ostringstream oss; - for (auto it = layer_object_label_ids.begin(); it != layer_object_label_ids.end(); ++it) { - if (it != layer_object_label_ids.begin()) oss << ","; - oss << *it; + timepals_gcode = start_str + timepals_gcode + end_str; } - std::string start_str = std::string("; object ids of layer ") + std::to_string(m_layer_index + 1) + (" start: ") + oss.str() + "\n"; - start_str += "M624 " + _encode_label_ids_to_base64(std::vector(layer_object_label_ids.begin(), layer_object_label_ids.end())) + "\n"; - - std::string end_str = std::string("; object ids of this layer") + std::to_string(m_layer_index + 1) + (" end: ") + oss.str() + "\n"; - end_str += "M625\n"; + return timepals_gcode; + }; - timepals_gcode = start_str + timepals_gcode + end_str; + if (!need_insert_timelapse_gcode_for_traditional) + { // Equivalent to the timelapse gcode placed in layer_change_gcode + if (FILAMENT_CONFIG(retract_when_changing_layer)) + { + gcode += this->retract(false, false, auto_lift_type, true); + } + gcode += insert_timelapse_gcode(); } - return timepals_gcode; - }; + if (m_wipe_tower) + m_wipe_tower->set_is_first_print(true); - if (!need_insert_timelapse_gcode_for_traditional) { // Equivalent to the timelapse gcode placed in layer_change_gcode - if (FILAMENT_CONFIG(retract_when_changing_layer)) { - gcode += this->retract(false, false, auto_lift_type, true); - } - gcode += insert_timelapse_gcode(); - } + auto insert_wrapping_detection_gcode = [this, &print, &print_z, &most_used_extruder]() -> std::string + { + std::string wrapping_gcode; + if (print.config().enable_wrapping_detection && !print.config().wrapping_detection_gcode.value.empty()) + { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); + config.set_key_value("curr_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(m_writer.filament()->extruder_id()))); + wrapping_gcode = this->placeholder_parser_process("wrapping_detection_gcode", print.config().wrapping_detection_gcode.value, m_writer.filament()->id(), &config) + "\n"; + } + m_writer.set_current_position_clear(false); + + double temp_z_after_tool_change; + if (GCodeProcessor::get_last_z_from_gcode(wrapping_gcode, temp_z_after_tool_change)) + { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_tool_change; + m_writer.set_position(pos); + } + return wrapping_gcode; + }; - if (m_wipe_tower) - m_wipe_tower->set_is_first_print(true); + bool has_insert_wrapping_detection_gcode = false; - auto insert_wrapping_detection_gcode = [this, &print, &print_z, &most_used_extruder]() -> std::string { - std::string wrapping_gcode; - if (print.config().enable_wrapping_detection && !print.config().wrapping_detection_gcode.value.empty()) { - DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); - config.set_key_value("curr_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(m_writer.filament()->extruder_id()))); - wrapping_gcode = this->placeholder_parser_process("wrapping_detection_gcode", print.config().wrapping_detection_gcode.value, m_writer.filament()->id(), &config) +"\n"; - } - m_writer.set_current_position_clear(false); + // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. + for (unsigned int extruder_id : layer_tools.extruders) + { + if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_label_object && print.config().support_object_skip_flush.value) + { + std::vector filament_instances_id; + for (InstanceToPrint &instance : filament_to_print_instances[extruder_id]) + filament_instances_id.emplace_back(instance.label_object_id); + m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id); + } - double temp_z_after_tool_change; - if (GCodeProcessor::get_last_z_from_gcode(wrapping_gcode, temp_z_after_tool_change)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_tool_change; - m_writer.set_position(pos); - } - return wrapping_gcode; - }; + if (has_wipe_tower) + { + if (!m_wipe_tower->is_empty_wipe_tower_gcode(*this, extruder_id, extruder_id == layer_tools.extruders.back())) + { + if (need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) + { + bool should_insert = true; + if (m_config.nozzle_diameter.values.size() == 2) + { + if (!writer().filament() || get_extruder_id(writer().filament()->id()) != most_used_extruder) + { + should_insert = false; + } + } - bool has_insert_wrapping_detection_gcode = false; + if (should_insert) + { + gcode += this->retract(false, false, auto_lift_type, true); + m_writer.add_object_change_labels(gcode); - // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. - for (unsigned int extruder_id : layer_tools.extruders) - { - if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_label_object && print.config().support_object_skip_flush.value) { - std::vector filament_instances_id; - for (InstanceToPrint &instance : filament_to_print_instances[extruder_id]) filament_instances_id.emplace_back(instance.label_object_id); - m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id); - } - - if (has_wipe_tower) { - if (!m_wipe_tower->is_empty_wipe_tower_gcode(*this, extruder_id, extruder_id == layer_tools.extruders.back())) { - if (need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) { - bool should_insert = true; - if (m_config.nozzle_diameter.values.size() == 2){ - if (!writer().filament() || get_extruder_id(writer().filament()->id()) != most_used_extruder) { - should_insert = false; + gcode += insert_timelapse_gcode(); + has_insert_timelapse_gcode = true; } } - if (should_insert) { + if (print.config().enable_wrapping_detection && !has_insert_wrapping_detection_gcode) + { gcode += this->retract(false, false, auto_lift_type, true); - m_writer.add_object_change_labels(gcode); - - gcode += insert_timelapse_gcode(); - has_insert_timelapse_gcode = true; + gcode += insert_wrapping_detection_gcode(); + has_insert_wrapping_detection_gcode = true; } + m_placeholder_parser.set("has_wipe_tower_this_layer", new ConfigOptionBool(true)); + gcode += m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()); + } + } + else + { + if (need_insert_timelapse_gcode_for_traditional && + !has_insert_timelapse_gcode && + m_writer.need_toolchange(extruder_id) && + m_config.nozzle_diameter.values.size() == 2 && + writer().filament() && + (get_extruder_id(writer().filament()->id()) == most_used_extruder)) + { + gcode += this->retract(false, false, auto_lift_type, true); + m_writer.add_object_change_labels(gcode); + + gcode += insert_timelapse_gcode(); + has_insert_timelapse_gcode = true; } - if (print.config().enable_wrapping_detection && !has_insert_wrapping_detection_gcode) { + if (print.config().enable_wrapping_detection && !has_insert_wrapping_detection_gcode) + { gcode += this->retract(false, false, auto_lift_type, true); gcode += insert_wrapping_detection_gcode(); has_insert_wrapping_detection_gcode = true; } - m_placeholder_parser.set("has_wipe_tower_this_layer", new ConfigOptionBool(true)); - gcode += m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()); - } - } else { - if (need_insert_timelapse_gcode_for_traditional && - !has_insert_timelapse_gcode && - m_writer.need_toolchange(extruder_id) && - m_config.nozzle_diameter.values.size() == 2 && - writer().filament() && - (get_extruder_id(writer().filament()->id()) == most_used_extruder)) { - gcode += this->retract(false, false, auto_lift_type, true); - m_writer.add_object_change_labels(gcode); - - gcode += insert_timelapse_gcode(); - has_insert_timelapse_gcode = true; + m_placeholder_parser.set("has_wipe_tower_this_layer", new ConfigOptionBool(false)); + gcode += this->set_extruder(extruder_id, print_z); } - if (print.config().enable_wrapping_detection && !has_insert_wrapping_detection_gcode) { - gcode += this->retract(false, false, auto_lift_type, true); - gcode += insert_wrapping_detection_gcode(); - has_insert_wrapping_detection_gcode = true; - } - m_placeholder_parser.set("has_wipe_tower_this_layer", new ConfigOptionBool(false)); - gcode += this->set_extruder(extruder_id, print_z); - } - - // let analyzer tag generator aware of a role type change - if (layer_tools.has_wipe_tower && m_wipe_tower) - m_last_processor_extrusion_role = erWipeTower; - - if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { - const std::pair loops = loops_it->second; - this->set_origin(0., 0.); - m_avoid_crossing_perimeters.use_external_mp(); - Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); - double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); - for (size_t i = loops.first; i < loops.second; ++i) { - // Adjust flow according to this layer's layer height. - ExtrusionLoop loop = *dynamic_cast(print.skirt().entities[i]); - for (ExtrusionPath &path : loop.paths) { - path.height = layer_skirt_flow.height(); - path.mm3_per_mm = mm3_per_mm; + // let analyzer tag generator aware of a role type change + if (layer_tools.has_wipe_tower && m_wipe_tower) + m_last_processor_extrusion_role = erWipeTower; + + if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) + { + const std::pair loops = loops_it->second; + this->set_origin(0., 0.); + m_avoid_crossing_perimeters.use_external_mp(); + Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); + double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); + for (size_t i = loops.first; i < loops.second; ++i) + { + // Adjust flow according to this layer's layer height. + ExtrusionLoop loop = *dynamic_cast(print.skirt().entities[i]); + for (ExtrusionPath &path : loop.paths) + { + path.height = layer_skirt_flow.height(); + path.mm3_per_mm = mm3_per_mm; + } + // FIXME using the support_speed of the 1st object printed. + gcode += this->extrude_loop(loop, "skirt", NOZZLE_CONFIG(support_speed)); } - //FIXME using the support_speed of the 1st object printed. - gcode += this->extrude_loop(loop, "skirt", NOZZLE_CONFIG(support_speed)); + m_avoid_crossing_perimeters.use_external_mp(false); + // 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(); } - m_avoid_crossing_perimeters.use_external_mp(false); - // 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(); - } - std::vector &instances_to_print = filament_to_print_instances[extruder_id]; + std::vector &instances_to_print = filament_to_print_instances[extruder_id]; - // BBS - if (print.has_skirt() && print.config().print_sequence == PrintSequence::ByObject && prime_extruder && first_layer && extruder_id == first_extruder_id) { - for (InstanceToPrint& instance_to_print : instances_to_print) { - if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && - print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities.size() > 0) - continue; + // BBS + if (print.has_skirt() && print.config().print_sequence == PrintSequence::ByObject && prime_extruder && first_layer && extruder_id == first_extruder_id) + { + for (InstanceToPrint &instance_to_print : instances_to_print) + { + if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && + print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities.size() > 0) + continue; - if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && - print.m_brimMap.at(instance_to_print.print_object.id()).entities.size() > 0) - continue; + if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && + print.m_brimMap.at(instance_to_print.print_object.id()).entities.size() > 0) + continue; - const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; - set_origin(unscaled(offset)); - for (ExtrusionEntity* ee : layer.object()->object_skirt().entities) - //FIXME using the support_speed of the 1st object printed. - gcode += this->extrude_entity(*ee, "skirt", NOZZLE_CONFIG(support_speed)); - } - } - - // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): - std::vector by_region_per_copy_cache; - 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"; - for (InstanceToPrint &instance_to_print : instances_to_print) { - const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id]; - const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; - // To control print speed of the 1st object layer printed over raft interface. - bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && - instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); - m_config.apply(print.default_region_config()); - m_config.apply(instance_to_print.print_object.config(), true); - m_layer = layer_to_print.layer(); - m_object_layer_over_raft = object_layer_over_raft; - if (m_config.reduce_crossing_wall) - m_avoid_crossing_perimeters.init_layer(*m_layer); - - //BBS: label object id, prepare for cooling - gcode += "; OBJECT_ID: " + std::to_string(instance_to_print.label_object_id) + "\n"; - std::string temp_start_str; - if (m_enable_label_object) { - std::string start_str = std::string("; start printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; - if (print.is_BBL_Printer()) { - start_str += ("M624 " + _encode_label_ids_to_base64({ instance_to_print.label_object_id })); - start_str += "\n"; - } else { - // BBS: support octoprint exclude object - start_str += std::string("; printing object ") + get_instance_name(&instance_to_print.print_object, inst.id) + "\n"; - } - temp_start_str = start_str; - m_writer.set_object_start_str(start_str); - } - //Orca's implementation for skipping object, for klipper firmware printer only - bool reset_e = false; - if (this->config().exclude_object && print.config().gcode_flavor.value == gcfKlipper) { - gcode += std::string("EXCLUDE_OBJECT_START NAME=") + - get_instance_name(&instance_to_print.print_object, inst.id) + "\n"; - reset_e = true; + const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; + set_origin(unscaled(offset)); + for (ExtrusionEntity *ee : layer.object()->object_skirt().entities) + // FIXME using the support_speed of the 1st object printed. + gcode += this->extrude_entity(*ee, "skirt", NOZZLE_CONFIG(support_speed)); } - if (reset_e && !m_config.use_relative_e_distances) - gcode += m_writer.reset_e(true); + } - // 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 this_object_copy(&instance_to_print.print_object, offset); - if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once(); - m_last_obj_copy = this_object_copy; - this->set_origin(unscale(offset)); - if (instance_to_print.object_by_extruder.support != nullptr) { - m_layer = layers[instance_to_print.layer_id].support_layer; - m_object_layer_over_raft = false; - - //BBS: print supports' brims first - if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && !print_wipe_extrusions) { - this->set_origin(0., 0.); - m_avoid_crossing_perimeters.use_external_mp(); - for (const ExtrusionEntity* ee : print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities) { - gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed)); + // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): + std::vector by_region_per_copy_cache; + 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"; + for (InstanceToPrint &instance_to_print : instances_to_print) + { + const auto &inst = instance_to_print.print_object.instances()[instance_to_print.instance_id]; + const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; + // To control print speed of the 1st object layer printed over raft interface. + bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && + instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); + m_config.apply(print.default_region_config()); + m_config.apply(instance_to_print.print_object.config(), true); + m_layer = layer_to_print.layer(); + m_object_layer_over_raft = object_layer_over_raft; + if (m_config.reduce_crossing_wall) + m_avoid_crossing_perimeters.init_layer(*m_layer); + + // BBS: label object id, prepare for cooling + gcode += "; OBJECT_ID: " + std::to_string(instance_to_print.label_object_id) + "\n"; + std::string temp_start_str; + if (m_enable_label_object) + { + std::string start_str = std::string("; start printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; + if (print.is_BBL_Printer()) + { + start_str += ("M624 " + _encode_label_ids_to_base64({instance_to_print.label_object_id})); + start_str += "\n"; } - m_avoid_crossing_perimeters.use_external_mp(false); - // Allow a straight travel move to the first object point. - m_avoid_crossing_perimeters.disable_once(); - this->m_objSupportsWithBrim.erase(instance_to_print.print_object.id()); + else + { + // BBS: support octoprint exclude object + start_str += std::string("; printing object ") + get_instance_name(&instance_to_print.print_object, inst.id) + "\n"; + } + temp_start_str = start_str; + m_writer.set_object_start_str(start_str); } + // Orca's implementation for skipping object, for klipper firmware printer only + bool reset_e = false; + if (this->config().exclude_object && print.config().gcode_flavor.value == gcfKlipper) + { + gcode += std::string("EXCLUDE_OBJECT_START NAME=") + + get_instance_name(&instance_to_print.print_object, inst.id) + "\n"; + reset_e = true; + } + if (reset_e && !m_config.use_relative_e_distances) + gcode += m_writer.reset_e(true); + // 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 this_object_copy(&instance_to_print.print_object, offset); + const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; + std::pair this_object_copy(&instance_to_print.print_object, offset); if (m_last_obj_copy != this_object_copy) m_avoid_crossing_perimeters.use_external_mp_once(); m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); - ExtrusionEntityCollection support_eec; - - // BBS - WipingExtrusions& wiping_extrusions = const_cast(layer_tools).wiping_extrusions(); - bool support_overridden = wiping_extrusions.is_support_overridden(layer_to_print.original_object); - bool support_intf_overridden = wiping_extrusions.is_support_interface_overridden(layer_to_print.original_object); - - ExtrusionRole support_extrusion_role = instance_to_print.object_by_extruder.support_extrusion_role; - bool is_overridden = support_extrusion_role == erSupportMaterialInterface ? support_intf_overridden : support_overridden; - if (is_overridden == (print_wipe_extrusions != 0)) - gcode += this->extrude_support( - // support_extrusion_role is erSupportMaterial, erSupportTransition, erSupportMaterialInterface or erMixed for all extrusion paths. - instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, support_extrusion_role)); + if (instance_to_print.object_by_extruder.support != nullptr) + { + m_layer = layers[instance_to_print.layer_id].support_layer; + m_object_layer_over_raft = false; - m_layer = layer_to_print.layer(); - m_object_layer_over_raft = object_layer_over_raft; - } - //FIXME order islands? - // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) - for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { - const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; - //BBS: add brim by obj by extruder - if (first_layer) { - if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && !print_wipe_extrusions) { + // BBS: print supports' brims first + if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && !print_wipe_extrusions) + { this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); - for (const ExtrusionEntity* ee : print.m_brimMap.at(instance_to_print.print_object.id()).entities) { + for (const ExtrusionEntity *ee : print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities) + { gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed)); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. m_avoid_crossing_perimeters.disable_once(); - this->m_objsWithBrim.erase(instance_to_print.print_object.id()); + this->m_objSupportsWithBrim.erase(instance_to_print.print_object.id()); } + // 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 this_object_copy(&instance_to_print.print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once(); + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + ExtrusionEntityCollection support_eec; + + // BBS + WipingExtrusions &wiping_extrusions = const_cast(layer_tools).wiping_extrusions(); + bool support_overridden = wiping_extrusions.is_support_overridden(layer_to_print.original_object); + bool support_intf_overridden = wiping_extrusions.is_support_interface_overridden(layer_to_print.original_object); + + ExtrusionRole support_extrusion_role = instance_to_print.object_by_extruder.support_extrusion_role; + bool is_overridden = support_extrusion_role == erSupportMaterialInterface ? support_intf_overridden : support_overridden; + if (is_overridden == (print_wipe_extrusions != 0)) + gcode += this->extrude_support( + // support_extrusion_role is erSupportMaterial, erSupportTransition, erSupportMaterialInterface or erMixed for all extrusion paths. + instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, support_extrusion_role)); + + m_layer = layer_to_print.layer(); + m_object_layer_over_raft = object_layer_over_raft; } - // 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 this_object_copy(&instance_to_print.print_object, offset); - if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once(); - m_last_obj_copy = this_object_copy; - this->set_origin(unscale(offset)); - //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. - bool is_infill_first =print.config().is_infill_first; - - auto has_infill = [](const std::vector &by_region) { - for (auto region : by_region) { - if (!region.infills.empty()) - return true; + // FIXME order islands? + // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) + for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) + { + const auto &by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; + // BBS: add brim by obj by extruder + if (first_layer) + { + if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && !print_wipe_extrusions) + { + this->set_origin(0., 0.); + m_avoid_crossing_perimeters.use_external_mp(); + for (const ExtrusionEntity *ee : print.m_brimMap.at(instance_to_print.print_object.id()).entities) + { + gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed)); + } + m_avoid_crossing_perimeters.use_external_mp(false); + // Allow a straight travel move to the first object point. + m_avoid_crossing_perimeters.disable_once(); + this->m_objsWithBrim.erase(instance_to_print.print_object.id()); + } } - return false; - }; - - //BBS: for first layer, we always print wall firstly to get better bed adhesive force - //This behaviour is same with cura - if (is_infill_first && !first_layer) { - if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && printer_structure == PrinterStructure::psI3 - && !has_insert_timelapse_gcode && has_infill(by_region_specific)) { - gcode += this->retract(false, false, auto_lift_type, true); - if (!temp_start_str.empty() && m_writer.empty_object_start_str()) { - std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; - if (print.is_BBL_Printer()) - end_str += "M625\n"; - gcode += end_str; + // 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 this_object_copy(&instance_to_print.print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once(); + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + // FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. + bool is_infill_first = print.config().is_infill_first; + + auto has_infill = [](const std::vector &by_region) + { + for (auto region : by_region) + { + if (!region.infills.empty()) + return true; } + return false; + }; - gcode += insert_timelapse_gcode(); + // BBS: for first layer, we always print wall firstly to get better bed adhesive force + // This behaviour is same with cura + if (is_infill_first && !first_layer) + { + if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && printer_structure == PrinterStructure::psI3 && !has_insert_timelapse_gcode && has_infill(by_region_specific)) + { + gcode += this->retract(false, false, auto_lift_type, true); + if (!temp_start_str.empty() && m_writer.empty_object_start_str()) + { + std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; + if (print.is_BBL_Printer()) + end_str += "M625\n"; + gcode += end_str; + } - if (!temp_start_str.empty() && m_writer.empty_object_start_str()) - gcode += temp_start_str; - temp_start_str.clear(); - has_insert_timelapse_gcode = true; - } - gcode += this->extrude_infill(print, by_region_specific, false); - gcode += this->extrude_perimeters(print, by_region_specific); - } else { - gcode += this->extrude_perimeters(print, by_region_specific); - if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && printer_structure == PrinterStructure::psI3 - && !has_insert_timelapse_gcode && has_infill(by_region_specific)) { - gcode += this->retract(false, false, auto_lift_type, true); - if (!temp_start_str.empty() && m_writer.empty_object_start_str()) { - std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; - if (print.is_BBL_Printer()) - end_str += "M625\n"; - gcode += end_str; + gcode += insert_timelapse_gcode(); + + if (!temp_start_str.empty() && m_writer.empty_object_start_str()) + gcode += temp_start_str; + temp_start_str.clear(); + has_insert_timelapse_gcode = true; } + gcode += this->extrude_infill(print, by_region_specific, false); + gcode += this->extrude_perimeters(print, by_region_specific); + } + else + { + gcode += this->extrude_perimeters(print, by_region_specific); + if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && printer_structure == PrinterStructure::psI3 && !has_insert_timelapse_gcode && has_infill(by_region_specific)) + { + gcode += this->retract(false, false, auto_lift_type, true); + if (!temp_start_str.empty() && m_writer.empty_object_start_str()) + { + std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; + if (print.is_BBL_Printer()) + end_str += "M625\n"; + gcode += end_str; + } - gcode += insert_timelapse_gcode(); + gcode += insert_timelapse_gcode(); - if (!temp_start_str.empty() && m_writer.empty_object_start_str()) - gcode += temp_start_str; - temp_start_str.clear(); - has_insert_timelapse_gcode = true; + if (!temp_start_str.empty() && m_writer.empty_object_start_str()) + gcode += temp_start_str; + temp_start_str.clear(); + has_insert_timelapse_gcode = true; + } + gcode += this->extrude_infill(print, by_region_specific, false); } - gcode += this->extrude_infill(print,by_region_specific, false); + // ironing + gcode += this->extrude_infill(print, by_region_specific, true); } - // ironing - gcode += this->extrude_infill(print,by_region_specific, true); - } - // Don't set m_gcode_label_objects_end if you don't had to write the m_gcode_label_objects_start. - if (!m_writer.empty_object_start_str()) { - m_writer.set_object_start_str(""); - } else if (m_enable_label_object) { - std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; - if (print.is_BBL_Printer()) - end_str += "M625\n"; - m_writer.set_object_end_str(end_str); - } - //Orca's implementation for skipping object, for klipper firmware printer only - if (this->config().exclude_object && print.config().gcode_flavor.value == gcfKlipper) { - gcode += std::string("EXCLUDE_OBJECT_END NAME=") + - get_instance_name(&instance_to_print.print_object, inst.id) + "\n"; - reset_e = true; + // Don't set m_gcode_label_objects_end if you don't had to write the m_gcode_label_objects_start. + if (!m_writer.empty_object_start_str()) + { + m_writer.set_object_start_str(""); + } + else if (m_enable_label_object) + { + std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; + if (print.is_BBL_Printer()) + end_str += "M625\n"; + m_writer.set_object_end_str(end_str); + } + // Orca's implementation for skipping object, for klipper firmware printer only + if (this->config().exclude_object && print.config().gcode_flavor.value == gcfKlipper) + { + gcode += std::string("EXCLUDE_OBJECT_END NAME=") + + get_instance_name(&instance_to_print.print_object, inst.id) + "\n"; + reset_e = true; + } + if (reset_e && !m_config.use_relative_e_distances) + gcode += m_writer.reset_e(true); } - if (reset_e && !m_config.use_relative_e_distances) - gcode += m_writer.reset_e(true); } } - } - if (first_layer) { - for (auto iter = by_extruder.begin(); iter != by_extruder.end(); ++iter) { - if (!iter->second.empty()) - m_initial_layer_extruders.insert(iter->first); + if (first_layer) + { + for (auto iter = by_extruder.begin(); iter != by_extruder.end(); ++iter) + { + if (!iter->second.empty()) + m_initial_layer_extruders.insert(iter->first); + } } - } #if 0 // Apply spiral vase post-processing if this layer contains suitable geometry @@ -4745,155 +5201,150 @@ GCode::LayerResult GCode::process_layer( file.write(gcode); #endif - BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << - log_memory_info(); - - if (need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) { - // The traditional model of thin-walled object will have flaws for I3 - if (m_support_traditional_timelapse - && printer_structure == PrinterStructure::psI3 - && m_config.timelapse_type.value == TimelapseType::tlTraditional) - m_support_traditional_timelapse = false; + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); - // The traditional model will have flaws for multi_extruder when switching extruder - if (m_config.nozzle_diameter.values.size() == 2 - && m_support_traditional_timelapse - && m_config.timelapse_type.value == TimelapseType::tlTraditional - && (writer().filament() && get_extruder_id(writer().filament()->id()) != most_used_extruder) - && !m_placeholder_parser.config().opt_bool("has_wipe_tower_this_layer")) + if (need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) { - m_support_traditional_timelapse = false; - } - if (FILAMENT_CONFIG(retract_when_changing_layer)) { - gcode += this->retract(false, false, auto_lift_type, true); - } - m_writer.add_object_change_labels(gcode); + // The traditional model of thin-walled object will have flaws for I3 + if (m_support_traditional_timelapse && printer_structure == PrinterStructure::psI3 && m_config.timelapse_type.value == TimelapseType::tlTraditional) + m_support_traditional_timelapse = false; - gcode += insert_timelapse_gcode(); - } + // The traditional model will have flaws for multi_extruder when switching extruder + if (m_config.nozzle_diameter.values.size() == 2 && m_support_traditional_timelapse && m_config.timelapse_type.value == TimelapseType::tlTraditional && (writer().filament() && get_extruder_id(writer().filament()->id()) != most_used_extruder) && !m_placeholder_parser.config().opt_bool("has_wipe_tower_this_layer")) + { + m_support_traditional_timelapse = false; + } + if (FILAMENT_CONFIG(retract_when_changing_layer)) + { + gcode += this->retract(false, false, auto_lift_type, true); + } + m_writer.add_object_change_labels(gcode); - result.gcode = std::move(gcode); - result.cooling_buffer_flush = object_layer || raft_layer || last_layer; - return result; -} + gcode += insert_timelapse_gcode(); + } -void GCode::apply_print_config(const PrintConfig &print_config) -{ - m_writer.apply_print_config(print_config); - m_config.apply(print_config); - m_scaled_resolution = scaled(print_config.resolution.value); -} + result.gcode = std::move(gcode); + result.cooling_buffer_flush = object_layer || raft_layer || last_layer; + return result; + } -void GCode::append_full_config(const DynamicPrintConfig &cfg, std::string &str) -{ - // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. - static const std::set banned_keys({ - "compatible_printers"sv, - "compatible_prints"sv, - "print_host"sv, - "print_host_webui"sv, - "printhost_apikey"sv, - "printhost_cafile"sv, - "printhost_user"sv, - "printhost_password"sv, - "printhost_port"sv - }); - assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); - auto is_banned = [](const std::string &key) { - return banned_keys.find(key) != banned_keys.end(); - }; - for (const std::string &key : cfg.keys()) - if (! is_banned(key) && ! cfg.option(key)->is_nil()) - str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; -} + void GCode::apply_print_config(const PrintConfig &print_config) + { + m_writer.apply_print_config(print_config); + m_config.apply(print_config); + m_scaled_resolution = scaled(print_config.resolution.value); + } -void GCode::set_extruders(const std::vector &extruder_ids) -{ - m_writer.set_extruders(extruder_ids); + void GCode::append_full_config(const DynamicPrintConfig &cfg, std::string &str) + { + // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. + static const std::set banned_keys({"compatible_printers"sv, + "compatible_prints"sv, + "print_host"sv, + "print_host_webui"sv, + "printhost_apikey"sv, + "printhost_cafile"sv, + "printhost_user"sv, + "printhost_password"sv, + "printhost_port"sv}); + assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); + auto is_banned = [](const std::string &key) + { + return banned_keys.find(key) != banned_keys.end(); + }; + for (const std::string &key : cfg.keys()) + if (!is_banned(key) && !cfg.option(key)->is_nil()) + str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; + } - // enable wipe path generation if any extruder has wipe enabled - m_wipe.enable = false; - for (auto id : extruder_ids) - if (m_config.wipe.get_at(id)) { - m_wipe.enable = true; - break; - } -} + void GCode::set_extruders(const std::vector &extruder_ids) + { + m_writer.set_extruders(extruder_ids); -void GCode::set_origin(const Vec2d &pointf) -{ - // if origin increases (goes towards right), last_pos decreases because it goes towards left - const Point translate( - scale_(m_origin(0) - pointf(0)), - scale_(m_origin(1) - pointf(1)) - ); - m_last_pos += translate; - m_wipe.path.translate(translate); - m_origin = pointf; -} + // enable wipe path generation if any extruder has wipe enabled + m_wipe.enable = false; + for (auto id : extruder_ids) + if (m_config.wipe.get_at(id)) + { + m_wipe.enable = true; + break; + } + } -std::string GCode::preamble() -{ - std::string gcode = m_writer.preamble(); + void GCode::set_origin(const Vec2d &pointf) + { + // if origin increases (goes towards right), last_pos decreases because it goes towards left + const Point translate( + scale_(m_origin(0) - pointf(0)), + scale_(m_origin(1) - pointf(1))); + m_last_pos += translate; + m_wipe.path.translate(translate); + m_origin = pointf; + } - /* Perform a *silent* move to z_offset: we need this to initialize the Z - position of our writer object so that any initial lift taking place - before the first layer change will raise the extruder from the correct - initial Z instead of 0. */ - //m_writer.travel_to_z(m_config.z_offset.value); - m_writer.travel_to_z(0.0); + std::string GCode::preamble() + { + std::string gcode = m_writer.preamble(); - return gcode; -} + /* Perform a *silent* move to z_offset: we need this to initialize the Z + position of our writer object so that any initial lift taking place + before the first layer change will raise the extruder from the correct + initial Z instead of 0. */ + // m_writer.travel_to_z(m_config.z_offset.value); + m_writer.travel_to_z(0.0); -// called by GCode::process_layer() -std::string GCode::change_layer(coordf_t print_z) -{ - std::string gcode; - if (m_layer_count > 0) - // Increment a progress bar indicator. - gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); - //BBS - //coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates - coordf_t z = print_z; // in unscaled coordinates - if (FILAMENT_CONFIG(retract_when_changing_layer) && m_writer.will_move_z(z)) { - LiftType lift_type = this->to_lift_type(ZHopType(FILAMENT_CONFIG(z_hop_types))); - //BBS: force to use SpiralLift when change layer if lift type is auto - gcode += this->retract(false, false, ZHopType(FILAMENT_CONFIG(z_hop_types)) == ZHopType::zhtAuto ? LiftType::SpiralLift : lift_type); + return gcode; } - m_writer.add_object_change_labels(gcode); - - if (m_spiral_vase) { - //BBS: force to normal lift immediately in spiral vase mode - std::ostringstream comment; - comment << "move to next layer (" << m_layer_index << ")"; - gcode += m_writer.travel_to_z(z, comment.str()); - } - else { - //BBS: set m_need_change_layer_lift_z to be true so that z lift can be done in travel_to() function - m_need_change_layer_lift_z = true; - } + // called by GCode::process_layer() + std::string GCode::change_layer(coordf_t print_z) + { + std::string gcode; + if (m_layer_count > 0) + // Increment a progress bar indicator. + gcode += m_writer.update_progress(++m_layer_index, m_layer_count); + // BBS + // coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates + coordf_t z = print_z; // in unscaled coordinates + if (FILAMENT_CONFIG(retract_when_changing_layer) && m_writer.will_move_z(z)) + { + LiftType lift_type = this->to_lift_type(ZHopType(FILAMENT_CONFIG(z_hop_types))); + // BBS: force to use SpiralLift when change layer if lift type is auto + gcode += this->retract(false, false, ZHopType(FILAMENT_CONFIG(z_hop_types)) == ZHopType::zhtAuto ? LiftType::SpiralLift : lift_type); + } - m_nominal_z = print_z; + m_writer.add_object_change_labels(gcode); - // forget last wiping path as wiping after raising Z is pointless - // BBS. Dont forget wiping path to reduce stringing. - //m_wipe.reset_path(); + if (m_spiral_vase) + { + // BBS: force to normal lift immediately in spiral vase mode + std::ostringstream comment; + comment << "move to next layer (" << m_layer_index << ")"; + gcode += m_writer.travel_to_z(z, comment.str()); + } + else + { + // BBS: set m_need_change_layer_lift_z to be true so that z lift can be done in travel_to() function + m_need_change_layer_lift_z = true; + } - return gcode; -} + m_nominal_z = print_z; + // forget last wiping path as wiping after raising Z is pointless + // BBS. Dont forget wiping path to reduce stringing. + // m_wipe.reset_path(); + return gcode; + } -static std::unique_ptr calculate_layer_edge_grid(const Layer& layer) -{ - auto out = make_unique(); + static std::unique_ptr calculate_layer_edge_grid(const Layer &layer) + { + auto out = make_unique(); - // Create the distance field for a layer below. - const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); - out->create(layer.lslices, distance_field_resolution); - out->calculate_sdf(); + // Create the distance field for a layer below. + const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); + out->create(layer.lslices, distance_field_resolution); + out->calculate_sdf(); #if 0 { static int iRun = 0; @@ -4905,1690 +5356,2203 @@ static std::unique_ptr calculate_layer_edge_grid(const Layer& la EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++)); } #endif - return out; -} - -static bool has_overhang_path_on_slope(const ExtrusionLoop &loop, double slope_length) -{ - double count_length = 0.0; - for (ExtrusionPath path : loop.paths) { - if (count_length > slope_length) - return false; - - if (path.overhang_degree > 1) - return true; - - count_length += path.length(); - } - - return false; -} - -static std::map overhang_speed_key_map = -{ - {1, "overhang_1_4_speed"}, - {2, "overhang_2_4_speed"}, - {3, "overhang_3_4_speed"}, - {4, "overhang_4_4_speed"}, - {5, "overhang_totally_speed"}, - {6, "bridge_speed"}, -}; - -double GCode::get_path_speed(const ExtrusionPath &path) -{ - double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.filament()->id())); - // set speed - double speed = 0; - if (path.role() == erPerimeter) { - speed = NOZZLE_CONFIG(inner_wall_speed); - if (NOZZLE_CONFIG(enable_overhang_speed)) { - double new_speed = 0; - new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); - speed = new_speed == 0.0 ? speed : new_speed; - } - } else if (path.role() == erExternalPerimeter) { - speed = NOZZLE_CONFIG(outer_wall_speed); - if (NOZZLE_CONFIG(enable_overhang_speed)) { - double new_speed = 0; - new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); - speed = new_speed == 0.0 ? speed : new_speed; - } - } else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) - speed = NOZZLE_CONFIG(overhang_totally_speed); - else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) { - speed = NOZZLE_CONFIG(bridge_speed); - } - auto _mm3_per_mm = path.mm3_per_mm * double(m_curr_print->calib_mode() == CalibMode::Calib_Flow_Rate ? this->config().print_flow_ratio.value : 1); - - // BBS: if not set the speed, then use the filament_max_volumetric_speed directly - double filament_max_volumetric_speed = FILAMENT_CONFIG(filament_max_volumetric_speed); - if (speed == 0) { - if (_mm3_per_mm > 0) - speed = filament_max_volumetric_speed / _mm3_per_mm; - else - speed = filament_max_volumetric_speed / path.mm3_per_mm; - } - if (this->on_first_layer()) { - // BBS: for solid infill of initial layer, speed can be higher as long as - // wall lines have be attached - if (path.role() != erBottomSurface) speed = NOZZLE_CONFIG(initial_layer_speed); + return out; } - if (filament_max_volumetric_speed > 0) { - double extrude_speed = filament_max_volumetric_speed / path.mm3_per_mm; - if (_mm3_per_mm > 0) extrude_speed = filament_max_volumetric_speed / _mm3_per_mm; + static bool has_overhang_path_on_slope(const ExtrusionLoop &loop, double slope_length) + { + double count_length = 0.0; + for (ExtrusionPath path : loop.paths) + { + if (count_length > slope_length) + return false; - // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - speed = std::min(speed, extrude_speed); - } + if (path.overhang_degree > 1) + return true; - return speed; -} + count_length += path.length(); + } -std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed) -{ - // get a copy; don't modify the orientation of the original loop object otherwise - // next copies (if any) would not detect the correct orientation - - // extrude all loops ccw - bool was_clockwise = loop.make_counter_clockwise(); - bool is_hole = loop.loop_role() & elrPerimeterHole; - // find the point of the loop that is closest to the current extruder position - // or randomize if requested - Point last_pos = this->last_pos(); - bool satisfy_scarf_seam_angle_threshold = false; - if (!m_config.spiral_mode && description == "perimeter") { - assert(m_layer != nullptr); - bool is_outer_wall_first = m_config.wall_sequence == WallSequence::OuterInner; - m_seam_placer.place_seam(m_layer, loop, is_outer_wall_first, this->last_pos(), satisfy_scarf_seam_angle_threshold); - } else - loop.split_at(last_pos, false); - - // BBS: not apply on fist layer, too small E has stick issue with hotend plate - bool override_filament_scarf_seam_setting = m_config.override_filament_scarf_seam_setting; - int filament_scarf_type = override_filament_scarf_seam_setting ? int(m_config.seam_slope_type.value) : FILAMENT_CONFIG(filament_scarf_seam_type); - bool enable_seam_slope = ((filament_scarf_type == int(SeamScarfType::External) && !is_hole) || - filament_scarf_type == int(SeamScarfType::All)) && - !m_config.spiral_mode && - (loop.role() == erExternalPerimeter || - (loop.role() == erPerimeter && m_config.seam_slope_inner_walls)) && - !on_first_layer(); - - if (enable_seam_slope && m_config.seam_slope_conditional.value) { - //BBS: the seam has been decide, only check the seam position angle - enable_seam_slope = satisfy_scarf_seam_angle_threshold; + return false; } - // clip the path to avoid the extruder to get exactly on the first point of the loop; - // if polyline was shorter than the clipping distance we'd get a null polyline, so - // we discard it in that case - const double seam_gap = scale_(EXTRUDER_CONFIG(nozzle_diameter)) * (m_config.seam_gap.value / 100); - const double clip_length = m_enable_loop_clipping && !enable_seam_slope ? seam_gap : 0; - // get paths - ExtrusionPaths paths; - bool set_holes_and_compensation_speed = loop.get_customize_flag() == CustomizeFlag::cfCircleCompensation && !loop.has_overhang_paths(); - if (set_holes_and_compensation_speed && m_config.apply_scarf_seam_on_circles.value) { - enable_seam_slope = true; - } - loop.clip_end(clip_length, &paths); - if (paths.empty()) return ""; - - double small_peri_speed=-1; - // apply the small perimeter speed - if (loop.length() <= SMALL_PERIMETER_LENGTH(NOZZLE_CONFIG(small_perimeter_threshold))) - small_peri_speed = NOZZLE_CONFIG(small_perimeter_speed).get_abs_value(NOZZLE_CONFIG(outer_wall_speed)); - - // extrude along the path - std::string gcode; - - const auto speed_for_path = [&speed, &small_peri_speed](const ExtrusionPath &path) { - // don't apply small perimeter setting for bridge/non-perimeters - const bool is_small_peri = is_perimeter(path.role()) && !is_bridge(path.role()) && small_peri_speed > 0; - return !is_small_peri ? speed : - (speed == -1) ? small_peri_speed : std::min(small_peri_speed, speed); + static std::map overhang_speed_key_map = + { + {1, "overhang_1_4_speed"}, + {2, "overhang_2_4_speed"}, + {3, "overhang_3_4_speed"}, + {4, "overhang_4_4_speed"}, + {5, "overhang_totally_speed"}, + {6, "bridge_speed"}, }; - //BBS: avoid overhang on conditional scarf mode - bool slope_has_overhang = false; - // update scarf seam - if (enable_seam_slope) { - // Create seam slope - double start_slope_ratio; - if (override_filament_scarf_seam_setting) { - if (m_config.seam_slope_start_height.percent) - start_slope_ratio = m_config.seam_slope_start_height.value / 100; - else { - start_slope_ratio = m_config.seam_slope_start_height.value / paths.front().height; - } - } else { - if (FILAMENT_CONFIG(filament_scarf_height).percent) - start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / 100; - else { - start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / paths.front().height; - } - } - - float slope_gap = override_filament_scarf_seam_setting ? m_config.seam_slope_gap.get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))) : - FILAMENT_CONFIG(filament_scarf_gap).get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))); - double scarf_seam_length = override_filament_scarf_seam_setting ? m_config.seam_slope_min_length: FILAMENT_CONFIG(filament_scarf_length); - - double loop_length = 0.; - for (const auto &path : paths) { - loop_length += unscale_(path.length()); - } - const bool slope_entire_loop = m_config.seam_slope_entire_loop; - const double slope_min_length = slope_entire_loop ? loop_length : std::min(scarf_seam_length, loop_length); - const int slope_steps = m_config.seam_slope_steps; - const double slope_max_segment_length = scale_(slope_min_length / slope_steps); - // BBS: check if has overhang on slope path - if (m_config.seam_slope_conditional.value) - slope_has_overhang = has_overhang_path_on_slope(loop.paths, slope_min_length); - if (!slope_has_overhang) { - // Calculate the sloped loop - // BBS: should has smaller e at start to get better seam - ExtrusionLoopSloped new_loop(paths, seam_gap, slope_min_length, slope_max_segment_length, start_slope_ratio, loop.loop_role()); - - // BBS: clip end and start to get better seam - new_loop.clip_slope(slope_gap); - // BBS: slowdown speed to improve seam, to be fix, cooling need to be apply correctly - // new_loop.target_speed = get_path_speed(new_loop.starts.back()); - // new_loop.slowdown_slope_speed(); - // BBS: smooth speed of discontinuity areas - if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && loop.is_set_speed_discontinuity_area()) { - // set smoothing_cof - set_smooth_coff(FILAMENT_CONFIG(filament_velocity_adaptation_factor)); - smooth_speed_discontinuity_area(new_loop.paths); - } - // Then extrude it - for (const auto &p : new_loop.get_all_paths()) { - gcode += this->_extrude(*p, description, speed_for_path(*p), set_holes_and_compensation_speed); + double GCode::get_path_speed(const ExtrusionPath &path) + { + double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.filament()->id())); + // set speed + double speed = 0; + if (path.role() == erPerimeter) + { + speed = NOZZLE_CONFIG(inner_wall_speed); + if (NOZZLE_CONFIG(enable_overhang_speed)) + { + double new_speed = 0; + new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); + speed = new_speed == 0.0 ? speed : new_speed; } - set_last_scarf_seam_flag(true); - - // Fix path for wipe - if (!new_loop.ends.empty()) { - paths.clear(); - // The start slope part is ignored as it overlaps with the end part - paths.reserve(new_loop.paths.size() + new_loop.ends.size()); - paths.insert(paths.end(), new_loop.paths.begin(), new_loop.paths.end()); - paths.insert(paths.end(), new_loop.ends.begin(), new_loop.ends.end()); + } + else if (path.role() == erExternalPerimeter) + { + speed = NOZZLE_CONFIG(outer_wall_speed); + if (NOZZLE_CONFIG(enable_overhang_speed)) + { + double new_speed = 0; + new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); + speed = new_speed == 0.0 ? speed : new_speed; } - } else { - paths.clear(); - loop.clip_end(clip_length, &paths); - if (paths.empty()) return ""; } - } - - if (!enable_seam_slope || slope_has_overhang) { - // BBS: smooth speed of discontinuity areas - if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && loop.is_set_speed_discontinuity_area()){ - // set smoothing_cof - set_smooth_coff(FILAMENT_CONFIG(filament_velocity_adaptation_factor)); - smooth_speed_discontinuity_area(paths); + else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) + speed = NOZZLE_CONFIG(overhang_totally_speed); + else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) + { + speed = NOZZLE_CONFIG(bridge_speed); } + auto _mm3_per_mm = path.mm3_per_mm * double(m_curr_print->calib_mode() == CalibMode::Calib_Flow_Rate ? this->config().print_flow_ratio.value : 1); - for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { - gcode += this->_extrude(*path, description, speed_for_path(*path), set_holes_and_compensation_speed); + // BBS: if not set the speed, then use the filament_max_volumetric_speed directly + double filament_max_volumetric_speed = FILAMENT_CONFIG(filament_max_volumetric_speed); + if (speed == 0) + { + if (_mm3_per_mm > 0) + speed = filament_max_volumetric_speed / _mm3_per_mm; + else + speed = filament_max_volumetric_speed / path.mm3_per_mm; + } + if (this->on_first_layer()) + { + // BBS: for solid infill of initial layer, speed can be higher as long as + // wall lines have be attached + if (path.role() != erBottomSurface) + speed = NOZZLE_CONFIG(initial_layer_speed); } - set_last_scarf_seam_flag(false); - } - - //BBS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. - if (!this->on_first_layer()) { - // reset acceleration - m_writer.set_acceleration((unsigned int) (NOZZLE_CONFIG(default_acceleration) + 0.5)); - if (!this->is_BBL_Printer()) - gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); - } + if (filament_max_volumetric_speed > 0) + { + double extrude_speed = filament_max_volumetric_speed / path.mm3_per_mm; + if (_mm3_per_mm > 0) + extrude_speed = filament_max_volumetric_speed / _mm3_per_mm; - // BBS - if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { - m_wipe.path = Polyline(); - for (ExtrusionPath &path : paths) { - //BBS: Don't need to save duplicated point into wipe path - if (!m_wipe.path.empty() && !path.empty() && - m_wipe.path.last_point() == path.first_point()) - m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); - else - m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path + // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + speed = std::min(speed, extrude_speed); } + + return speed; } - //BBS. move the travel path before wipe to improve the seam - //// make a little move inwards before leaving loop - //if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.wall_loops.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { - // // detect angle between last and first segment - // // the side depends on the original winding order of the polygon (left for contours, right for holes) - // //FIXME improve the algorithm in case the loop is tiny. - // //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). - // Point a = paths.front().polyline.points[1]; // second point - // Point b = *(paths.back().polyline.points.end()-3); // second to last point - // if (was_clockwise) { - // // swap points - // Point c = a; a = b; b = c; - // } - - // double angle = paths.front().first_point().ccw_angle(a, b) / 3; - - // // turn left if contour, turn right if hole - // if (was_clockwise) angle *= -1; - - // // create the destination point along the first segment and rotate it - // // we make sure we don't exceed the segment length because we don't know - // // the rotation of the second segment so we might cross the object boundary - // Vec2d p1 = paths.front().polyline.points.front().cast(); - // Vec2d p2 = paths.front().polyline.points[1].cast(); - // Vec2d v = p2 - p1; - // double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter)); - // double l2 = v.squaredNorm(); - // // Shift by no more than a nozzle diameter. - // //FIXME Hiding the seams will not work nicely for very densely discretized contours! - // //BBS. shorten the travel distant before the wipe path - // double threshold = 0.2; - // Point pt = (p1 + v * threshold).cast(); - // if (nd * nd < l2) - // pt = (p1 + threshold * v * (nd / sqrt(l2))).cast(); - // //Point pt = ((nd * nd >= l2) ? (p1+v*0.4): (p1 + 0.2 * v * (nd / sqrt(l2)))).cast(); - // pt.rotate(angle, paths.front().polyline.points.front()); - // // generate the travel move - // gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel"); - //} - - return gcode; -} + std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed) + { + // get a copy; don't modify the orientation of the original loop object otherwise + // next copies (if any) would not detect the correct orientation + + // extrude all loops ccw + bool was_clockwise = loop.make_counter_clockwise(); + bool is_hole = loop.loop_role() & elrPerimeterHole; + // find the point of the loop that is closest to the current extruder position + // or randomize if requested + Point last_pos = this->last_pos(); + bool satisfy_scarf_seam_angle_threshold = false; + if (!m_config.spiral_mode && description == "perimeter") + { + assert(m_layer != nullptr); + bool is_outer_wall_first = m_config.wall_sequence == WallSequence::OuterInner; + m_seam_placer.place_seam(m_layer, loop, is_outer_wall_first, this->last_pos(), satisfy_scarf_seam_angle_threshold); + } + else + loop.split_at(last_pos, false); + + // BBS: not apply on fist layer, too small E has stick issue with hotend plate + bool override_filament_scarf_seam_setting = m_config.override_filament_scarf_seam_setting; + int filament_scarf_type = override_filament_scarf_seam_setting ? int(m_config.seam_slope_type.value) : FILAMENT_CONFIG(filament_scarf_seam_type); + bool enable_seam_slope = ((filament_scarf_type == int(SeamScarfType::External) && !is_hole) || + filament_scarf_type == int(SeamScarfType::All)) && + !m_config.spiral_mode && + (loop.role() == erExternalPerimeter || + (loop.role() == erPerimeter && m_config.seam_slope_inner_walls)) && + !on_first_layer(); + + if (enable_seam_slope && m_config.seam_slope_conditional.value) + { + // BBS: the seam has been decide, only check the seam position angle + enable_seam_slope = satisfy_scarf_seam_angle_threshold; + } + + // clip the path to avoid the extruder to get exactly on the first point of the loop; + // if polyline was shorter than the clipping distance we'd get a null polyline, so + // we discard it in that case + const double seam_gap = scale_(EXTRUDER_CONFIG(nozzle_diameter)) * (m_config.seam_gap.value / 100); + const double clip_length = m_enable_loop_clipping && !enable_seam_slope ? seam_gap : 0; + // get paths + ExtrusionPaths paths; + bool set_holes_and_compensation_speed = loop.get_customize_flag() == CustomizeFlag::cfCircleCompensation && !loop.has_overhang_paths(); + if (set_holes_and_compensation_speed && m_config.apply_scarf_seam_on_circles.value) + { + enable_seam_slope = true; + } + loop.clip_end(clip_length, &paths); + if (paths.empty()) + return ""; -std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed) -{ - // extrude along the path - std::string gcode; - for (ExtrusionPath path : multipath.paths) - gcode += this->_extrude(path, description, speed); + double small_peri_speed = -1; + // apply the small perimeter speed + if (loop.length() <= SMALL_PERIMETER_LENGTH(NOZZLE_CONFIG(small_perimeter_threshold))) + small_peri_speed = NOZZLE_CONFIG(small_perimeter_speed).get_abs_value(NOZZLE_CONFIG(outer_wall_speed)); - // BBS - if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { - m_wipe.path = Polyline(); - for (ExtrusionPath &path : multipath.paths) { - //BBS: Don't need to save duplicated point into wipe path - if (!m_wipe.path.empty() && !path.empty() && - m_wipe.path.last_point() == path.first_point()) - m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); - else - m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path - } - m_wipe.path.reverse(); - } - //BBS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. - if (!this->on_first_layer()) { - // reset acceleration - m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(default_acceleration) + 0.5)); - if (!this->is_BBL_Printer()) - gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); - } - return gcode; -} + // extrude along the path + std::string gcode; -std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed) -{ - if (const ExtrusionPath* path = dynamic_cast(&entity)) - return this->extrude_path(*path, description, speed); - else if (const ExtrusionMultiPath* multipath = dynamic_cast(&entity)) - return this->extrude_multi_path(*multipath, description, speed); - else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) - return this->extrude_loop(*loop, description, speed); - else - throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); - return ""; -} + const auto speed_for_path = [&speed, &small_peri_speed](const ExtrusionPath &path) + { + // don't apply small perimeter setting for bridge/non-perimeters + const bool is_small_peri = is_perimeter(path.role()) && !is_bridge(path.role()) && small_peri_speed > 0; + return !is_small_peri ? speed : (speed == -1) ? small_peri_speed + : std::min(small_peri_speed, speed); + }; -std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) -{ -// description += ExtrusionEntity::role_to_string(path.role()); - bool flag = path.get_customize_flag() == CustomizeFlag::cfFloatingVerticalShell; - std::string gcode = this->_extrude(path, description, speed,flag); - if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { - m_wipe.path = path.polyline; - if (is_tree(this->config().support_type) && (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface || path.role() == erSupportTransition)) { - if ((m_wipe.path.first_point() - m_wipe.path.last_point()).cast().norm() > scale_(0.2)) { - double min_dist = scale_(0.2); - int i = 0; - for (; i < path.polyline.points.size(); i++) { - double dist = (path.polyline.points[i] - path.last_point()).cast().norm(); - if (dist < min_dist) min_dist = dist; - if (min_dist < scale_(0.2) && dist > min_dist) break; + // BBS: avoid overhang on conditional scarf mode + bool slope_has_overhang = false; + // update scarf seam + if (enable_seam_slope) + { + // Create seam slope + double start_slope_ratio; + if (override_filament_scarf_seam_setting) + { + if (m_config.seam_slope_start_height.percent) + start_slope_ratio = m_config.seam_slope_start_height.value / 100; + else + { + start_slope_ratio = m_config.seam_slope_start_height.value / paths.front().height; } - m_wipe.path = Polyline(Points(path.polyline.points.begin() + i - 1, path.polyline.points.end())); } - } else - m_wipe.path.reverse(); - } - //BBS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. - if (!this->on_first_layer()) { - // reset acceleration - m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(default_acceleration) + 0.5)); - if (!this->is_BBL_Printer()) - gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); - } - return gcode; -} - -// Extrude perimeters: Decide where to put seams (hide or align seams). -std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region) -{ - std::string gcode; - for (const ObjectByExtruder::Island::Region ®ion : by_region) - if (! region.perimeters.empty()) { - m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); - - // BBS: output merged node id - int curr_node=0; - int cooling_node = -1; - for (size_t perimeter_idx = 0; perimeter_idx < region.perimeters.size(); ++perimeter_idx) { - const ExtrusionEntity *ee = region.perimeters[perimeter_idx]; - int ee_node_id = ee->get_cooling_node(); - if (ee_node_id != cooling_node) { - gcode += "; COOLING_NODE: " + std::to_string(ee_node_id) + "\n"; + else + { + if (FILAMENT_CONFIG(filament_scarf_height).percent) + start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / 100; + else + { + start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / paths.front().height; } + } + + float slope_gap = override_filament_scarf_seam_setting ? m_config.seam_slope_gap.get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))) : FILAMENT_CONFIG(filament_scarf_gap).get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))); + double scarf_seam_length = override_filament_scarf_seam_setting ? m_config.seam_slope_min_length : FILAMENT_CONFIG(filament_scarf_length); - gcode += this->extrude_entity(*ee, "perimeter", -1.); + double loop_length = 0.; + for (const auto &path : paths) + { + loop_length += unscale_(path.length()); } - } - return gcode; -} + const bool slope_entire_loop = m_config.seam_slope_entire_loop; + const double slope_min_length = slope_entire_loop ? loop_length : std::min(scarf_seam_length, loop_length); + const int slope_steps = m_config.seam_slope_steps; + const double slope_max_segment_length = scale_(slope_min_length / slope_steps); + // BBS: check if has overhang on slope path + if (m_config.seam_slope_conditional.value) + slope_has_overhang = has_overhang_path_on_slope(loop.paths, slope_min_length); + if (!slope_has_overhang) + { + // Calculate the sloped loop + // BBS: should has smaller e at start to get better seam + ExtrusionLoopSloped new_loop(paths, seam_gap, slope_min_length, slope_max_segment_length, start_slope_ratio, loop.loop_role()); + + // BBS: clip end and start to get better seam + new_loop.clip_slope(slope_gap); + // BBS: slowdown speed to improve seam, to be fix, cooling need to be apply correctly + // new_loop.target_speed = get_path_speed(new_loop.starts.back()); + // new_loop.slowdown_slope_speed(); + // BBS: smooth speed of discontinuity areas + if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && loop.is_set_speed_discontinuity_area()) + { + // set smoothing_cof + set_smooth_coff(FILAMENT_CONFIG(filament_velocity_adaptation_factor)); + smooth_speed_discontinuity_area(new_loop.paths); + } + // Then extrude it + for (const auto &p : new_loop.get_all_paths()) + { + gcode += this->_extrude(*p, description, speed_for_path(*p), set_holes_and_compensation_speed); + } + set_last_scarf_seam_flag(true); -// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance. -std::string GCode::extrude_infill(const Print &print, const std::vector &by_region, bool ironing) -{ - std::string gcode; - ExtrusionEntitiesPtr extrusions; - const char* extrusion_name = ironing ? "ironing" : "infill"; - for (const ObjectByExtruder::Island::Region ®ion : by_region) - if (! region.infills.empty()) { - extrusions.clear(); - extrusions.reserve(region.infills.size()); - for (ExtrusionEntity *ee : region.infills) - if ((ee->role() == erIroning) == ironing) - extrusions.emplace_back(ee); - if (! extrusions.empty()) { - m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); - chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); - for (const ExtrusionEntity *fill : extrusions) { - auto *eec = dynamic_cast(fill); - if (eec) { - for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) - gcode += this->extrude_entity(*ee, extrusion_name); - } else - gcode += this->extrude_entity(*fill, extrusion_name); + // Fix path for wipe + if (!new_loop.ends.empty()) + { + paths.clear(); + // The start slope part is ignored as it overlaps with the end part + paths.reserve(new_loop.paths.size() + new_loop.ends.size()); + paths.insert(paths.end(), new_loop.paths.begin(), new_loop.paths.end()); + paths.insert(paths.end(), new_loop.ends.begin(), new_loop.ends.end()); } } + else + { + paths.clear(); + loop.clip_end(clip_length, &paths); + if (paths.empty()) + return ""; + } } - return gcode; -} -std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills) -{ - static constexpr const char *support_label = "support material"; - static constexpr const char *support_interface_label = "support material interface"; - const char* support_transition_label = "support transition"; - - std::string gcode; - if (! support_fills.entities.empty()) { - const double support_speed = NOZZLE_CONFIG(support_speed); - const double support_interface_speed = NOZZLE_CONFIG(support_interface_speed); - for (const ExtrusionEntity *ee : support_fills.entities) { - ExtrusionRole role = ee->role(); - assert(role == erSupportMaterial || role == erSupportMaterialInterface || role == erSupportTransition); - const char* label = (role == erSupportMaterial) ? support_label : - ((role == erSupportMaterialInterface) ? support_interface_label : support_transition_label); - // BBS - //const double speed = (role == erSupportMaterial) ? support_speed : support_interface_speed; - const double speed = -1.0; - const ExtrusionPath* path = dynamic_cast(ee); - const ExtrusionMultiPath* multipath = dynamic_cast(ee); - const ExtrusionLoop* loop = dynamic_cast(ee); - const ExtrusionEntityCollection* collection = dynamic_cast(ee); - if (path) - gcode += this->extrude_path(*path, label, speed); - else if (multipath) { - gcode += this->extrude_multi_path(*multipath, label, speed); - } - else if (loop) { - gcode += this->extrude_loop(*loop, label, speed); - } - else if (collection) { - gcode += extrude_support(*collection); + if (!enable_seam_slope || slope_has_overhang) + { + // BBS: smooth speed of discontinuity areas + if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && loop.is_set_speed_discontinuity_area()) + { + // set smoothing_cof + set_smooth_coff(FILAMENT_CONFIG(filament_velocity_adaptation_factor)); + smooth_speed_discontinuity_area(paths); } - else { - throw Slic3r::InvalidArgument("Unknown extrusion type"); + + for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) + { + gcode += this->_extrude(*path, description, speed_for_path(*path), set_holes_and_compensation_speed); } + set_last_scarf_seam_flag(false); } - } - return gcode; -} -bool GCode::GCodeOutputStream::is_error() const -{ - return ::ferror(this->f); -} + // BBS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. + if (!this->on_first_layer()) + { + // reset acceleration + m_writer.set_acceleration((unsigned int)(NOZZLE_CONFIG(default_acceleration) + 0.5)); + if (!this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); + } -void GCode::GCodeOutputStream::flush() -{ - ::fflush(this->f); -} + // BBS + if (m_wipe.enable && FILAMENT_CONFIG(wipe)) + { + m_wipe.path = Polyline(); + for (ExtrusionPath &path : paths) + { + // BBS: Don't need to save duplicated point into wipe path + if (!m_wipe.path.empty() && !path.empty() && + m_wipe.path.last_point() == path.first_point()) + m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); + else + m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path + } + } -void GCode::GCodeOutputStream::close() -{ - if (this->f) { - ::fclose(this->f); - this->f = nullptr; - } -} + // BBS. move the travel path before wipe to improve the seam + //// make a little move inwards before leaving loop + // if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.wall_loops.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { + // // detect angle between last and first segment + // // the side depends on the original winding order of the polygon (left for contours, right for holes) + // //FIXME improve the algorithm in case the loop is tiny. + // //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). + // Point a = paths.front().polyline.points[1]; // second point + // Point b = *(paths.back().polyline.points.end()-3); // second to last point + // if (was_clockwise) { + // // swap points + // Point c = a; a = b; b = c; + // } + + // double angle = paths.front().first_point().ccw_angle(a, b) / 3; + + // // turn left if contour, turn right if hole + // if (was_clockwise) angle *= -1; + + // // create the destination point along the first segment and rotate it + // // we make sure we don't exceed the segment length because we don't know + // // the rotation of the second segment so we might cross the object boundary + // Vec2d p1 = paths.front().polyline.points.front().cast(); + // Vec2d p2 = paths.front().polyline.points[1].cast(); + // Vec2d v = p2 - p1; + // double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter)); + // double l2 = v.squaredNorm(); + // // Shift by no more than a nozzle diameter. + // //FIXME Hiding the seams will not work nicely for very densely discretized contours! + // //BBS. shorten the travel distant before the wipe path + // double threshold = 0.2; + // Point pt = (p1 + v * threshold).cast(); + // if (nd * nd < l2) + // pt = (p1 + threshold * v * (nd / sqrt(l2))).cast(); + // //Point pt = ((nd * nd >= l2) ? (p1+v*0.4): (p1 + 0.2 * v * (nd / sqrt(l2)))).cast(); + // pt.rotate(angle, paths.front().polyline.points.front()); + // // generate the travel move + // gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel"); + //} -void GCode::GCodeOutputStream::write(const char *what) -{ - if (what != nullptr) { - const char* gcode = what; - // writes string to file - fwrite(gcode, 1, ::strlen(gcode), this->f); - //FIXME don't allocate a string, maybe process a batch of lines? - m_processor.process_buffer(std::string(gcode)); + return gcode; } -} - -void GCode::GCodeOutputStream::writeln(const std::string &what) -{ - if (! what.empty()) - this->write(what.back() == '\n' ? what : what + '\n'); -} - -void GCode::GCodeOutputStream::write_format(const char* format, ...) -{ - va_list args; - va_start(args, format); - int buflen; + std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed) { - va_list args2; - va_copy(args2, args); - buflen = - #ifdef _MSC_VER - ::_vscprintf(format, args2) - #else - ::vsnprintf(nullptr, 0, format, args2) - #endif - + 1; - va_end(args2); - } - - char buffer[1024]; - bool buffer_dynamic = buflen > 1024; - char *bufptr = buffer_dynamic ? (char*)malloc(buflen) : buffer; - int res = ::vsnprintf(bufptr, buflen, format, args); - if (res > 0) - this->write(bufptr); - - if (buffer_dynamic) - free(bufptr); - - va_end(args); -} + // extrude along the path + std::string gcode; + for (ExtrusionPath path : multipath.paths) + gcode += this->_extrude(path, description, speed); -// BBS: f(x)=2x^2 -double GCode::mapping_speed(double dist) -{ - if (dist <= 0) - return 0; - return m_smooth_coefficient * pow(dist, 2); -} + // BBS + if (m_wipe.enable && FILAMENT_CONFIG(wipe)) + { + m_wipe.path = Polyline(); + for (ExtrusionPath &path : multipath.paths) + { + // BBS: Don't need to save duplicated point into wipe path + if (!m_wipe.path.empty() && !path.empty() && + m_wipe.path.last_point() == path.first_point()) + m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); + else + m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path + } + m_wipe.path.reverse(); + } + // BBS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. + if (!this->on_first_layer()) + { + // reset acceleration + m_writer.set_acceleration((unsigned int)floor(NOZZLE_CONFIG(default_acceleration) + 0.5)); + if (!this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); + } + return gcode; + } -double GCode::get_speed_coor_x(double speed){ + std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed) + { + if (const ExtrusionPath *path = dynamic_cast(&entity)) + return this->extrude_path(*path, description, speed); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(&entity)) + return this->extrude_multi_path(*multipath, description, speed); + else if (const ExtrusionLoop *loop = dynamic_cast(&entity)) + return this->extrude_loop(*loop, description, speed); + else + throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); + return ""; + } - double temp = speed / m_smooth_coefficient; - return sqrt(temp); -} + std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) + { + // description += ExtrusionEntity::role_to_string(path.role()); + bool flag = path.get_customize_flag() == CustomizeFlag::cfFloatingVerticalShell; + std::string gcode = this->_extrude(path, description, speed, flag); + if (m_wipe.enable && FILAMENT_CONFIG(wipe)) + { + m_wipe.path = path.polyline; + if (is_tree(this->config().support_type) && (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface || path.role() == erSupportTransition)) + { + if ((m_wipe.path.first_point() - m_wipe.path.last_point()).cast().norm() > scale_(0.2)) + { + double min_dist = scale_(0.2); + int i = 0; + for (; i < path.polyline.points.size(); i++) + { + double dist = (path.polyline.points[i] - path.last_point()).cast().norm(); + if (dist < min_dist) + min_dist = dist; + if (min_dist < scale_(0.2) && dist > min_dist) + break; + } + m_wipe.path = Polyline(Points(path.polyline.points.begin() + i - 1, path.polyline.points.end())); + } + } + else + m_wipe.path.reverse(); + } + // BBS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. + if (!this->on_first_layer()) + { + // reset acceleration + m_writer.set_acceleration((unsigned int)floor(NOZZLE_CONFIG(default_acceleration) + 0.5)); + if (!this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); + } + return gcode; + } -double GCode::get_overhang_degree_corr_speed(float normal_speed, double path_degree) { + // Extrude perimeters: Decide where to put seams (hide or align seams). + std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region) + { + std::string gcode; + for (const ObjectByExtruder::Island::Region ®ion : by_region) + if (!region.perimeters.empty()) + { + m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); - //BBS: protection: overhang degree is float, make sure it not excess degree range - if (path_degree <= 0) - return normal_speed; + // BBS: output merged node id + int curr_node = 0; + int cooling_node = -1; + for (size_t perimeter_idx = 0; perimeter_idx < region.perimeters.size(); ++perimeter_idx) + { + const ExtrusionEntity *ee = region.perimeters[perimeter_idx]; + int ee_node_id = ee->get_cooling_node(); + if (ee_node_id != cooling_node) + { + gcode += "; COOLING_NODE: " + std::to_string(ee_node_id) + "\n"; + } - int lower_degree_bound = int(path_degree); - // BBS: use lower speed of 75%-100% for better cooling - if (path_degree >= 4 || path_degree == lower_degree_bound) - return m_config.get_abs_value_at(overhang_speed_key_map[lower_degree_bound].c_str(), cur_extruder_index()); + gcode += this->extrude_entity(*ee, "perimeter", -1.); + } + } + return gcode; + } - int upper_degree_bound = lower_degree_bound + 1; + // Chain the paths hierarchically by a greedy algorithm to minimize a travel distance. + std::string GCode::extrude_infill(const Print &print, const std::vector &by_region, bool ironing) + { + std::string gcode; + ExtrusionEntitiesPtr extrusions; + const char *extrusion_name = ironing ? "ironing" : "infill"; + for (const ObjectByExtruder::Island::Region ®ion : by_region) + if (!region.infills.empty()) + { + extrusions.clear(); + extrusions.reserve(region.infills.size()); + for (ExtrusionEntity *ee : region.infills) + if ((ee->role() == erIroning) == ironing) + extrusions.emplace_back(ee); + if (!extrusions.empty()) + { + m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); + chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); + for (const ExtrusionEntity *fill : extrusions) + { + auto *eec = dynamic_cast(fill); + if (eec) + { + for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) + gcode += this->extrude_entity(*ee, extrusion_name); + } + else + gcode += this->extrude_entity(*fill, extrusion_name); + } + } + } + return gcode; + } - double lower_speed_bound = lower_degree_bound == 0 ? normal_speed : m_config.get_abs_value_at(overhang_speed_key_map[lower_degree_bound].c_str(), cur_extruder_index()); - double upper_speed_bound = upper_degree_bound == 0 ? normal_speed : m_config.get_abs_value_at(overhang_speed_key_map[upper_degree_bound].c_str(), cur_extruder_index()); + std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills) + { + static constexpr const char *support_label = "support material"; + static constexpr const char *support_interface_label = "support material interface"; + const char *support_transition_label = "support transition"; - lower_speed_bound = lower_speed_bound == 0 ? normal_speed : lower_speed_bound; - upper_speed_bound = upper_speed_bound == 0 ? normal_speed : upper_speed_bound; + std::string gcode; + if (!support_fills.entities.empty()) + { + const double support_speed = NOZZLE_CONFIG(support_speed); + const double support_interface_speed = NOZZLE_CONFIG(support_interface_speed); + for (const ExtrusionEntity *ee : support_fills.entities) + { + ExtrusionRole role = ee->role(); + assert(role == erSupportMaterial || role == erSupportMaterialInterface || role == erSupportTransition); + const char *label = (role == erSupportMaterial) ? support_label : ((role == erSupportMaterialInterface) ? support_interface_label : support_transition_label); + // BBS + // const double speed = (role == erSupportMaterial) ? support_speed : support_interface_speed; + const double speed = -1.0; + const ExtrusionPath *path = dynamic_cast(ee); + const ExtrusionMultiPath *multipath = dynamic_cast(ee); + const ExtrusionLoop *loop = dynamic_cast(ee); + const ExtrusionEntityCollection *collection = dynamic_cast(ee); + if (path) + gcode += this->extrude_path(*path, label, speed); + else if (multipath) + { + gcode += this->extrude_multi_path(*multipath, label, speed); + } + else if (loop) + { + gcode += this->extrude_loop(*loop, label, speed); + } + else if (collection) + { + gcode += extrude_support(*collection); + } + else + { + throw Slic3r::InvalidArgument("Unknown extrusion type"); + } + } + } + return gcode; + } - double speed_out = lower_speed_bound + (upper_speed_bound - lower_speed_bound) * (path_degree - lower_degree_bound); - return speed_out; -} + bool GCode::GCodeOutputStream::is_error() const + { + return ::ferror(this->f); + } -static bool need_smooth_speed(const ExtrusionPath &other_path, const ExtrusionPath &this_path) -{ - if (this_path.smooth_speed - other_path.smooth_speed > smooth_speed_step) - return true; + void GCode::GCodeOutputStream::flush() + { + ::fflush(this->f); + } - return false; -} + void GCode::GCodeOutputStream::close() + { + if (this->f) + { + ::fclose(this->f); + this->f = nullptr; + } + } -void GCode::split_and_mapping_speed(double other_path_v, double final_v, ExtrusionPaths &this_path, double max_smooth_length, ExtrusionPaths &interpolated_paths, bool split_from_left) -{ - if (this_path.empty() || max_smooth_length == 0) - return; + void GCode::GCodeOutputStream::write(const char *what) + { + if (what != nullptr) + { + const char *gcode = what; + // writes string to file + fwrite(gcode, 1, ::strlen(gcode), this->f); + // FIXME don't allocate a string, maybe process a batch of lines? + m_processor.process_buffer(std::string(gcode)); + } + } - ExtrusionPaths splited_path; - - // get params - double this_path_x = scale_(get_speed_coor_x(final_v)); - double x_base = scale_(get_speed_coor_x(other_path_v)); - double smooth_length = this_path_x - x_base; - double smooth_length_count = 0; - double split_line_speed = 0; - // this length not support to get final v, adjust final v - if( smooth_length > max_smooth_length ) { - smooth_length = max_smooth_length; - final_v = mapping_speed(unscale_(x_base + max_smooth_length)); + void GCode::GCodeOutputStream::writeln(const std::string &what) + { + if (!what.empty()) + this->write(what.back() == '\n' ? what : what + '\n'); } - auto insert_speed = [this](double line_lenght, double &pos_x, double &smooth_length_count, double target_v) { - pos_x += line_lenght; - double pos_x_speed = mapping_speed(unscale_(pos_x)); - smooth_length_count += line_lenght; - if (pos_x_speed > target_v) - pos_x_speed = target_v; + void GCode::GCodeOutputStream::write_format(const char *format, ...) + { + va_list args; + va_start(args, format); - return pos_x_speed; - }; + int buflen; + { + va_list args2; + va_copy(args2, args); + buflen = +#ifdef _MSC_VER + ::_vscprintf(format, args2) +#else + ::vsnprintf(nullptr, 0, format, args2) +#endif + + 1; + va_end(args2); + } - //check line length - auto length_enough = [this](double length) { - if (length < min_step_length || length - min_step_length < min_step_length / 2) - return false; + char buffer[1024]; + bool buffer_dynamic = buflen > 1024; + char *bufptr = buffer_dynamic ? (char *)malloc(buflen) : buffer; + int res = ::vsnprintf(bufptr, buflen, format, args); + if (res > 0) + this->write(bufptr); - return true; - }; + if (buffer_dynamic) + free(bufptr); - ExtrusionPaths left_paths; - for (int idx = 0; idx < this_path.size(); idx++) { - ExtrusionPath &extrusion = this_path[idx]; + va_end(args); + } - // just in case, polyline error - if (extrusion.polyline.points.size() < 2) - continue; + // BBS: f(x)=2x^2 + double GCode::mapping_speed(double dist) + { + if (dist <= 0) + return 0; + return m_smooth_coefficient * pow(dist, 2); + } - // stop and push the paths back - if (smooth_length_count >= smooth_length) { - left_paths.insert(left_paths.end(), this_path.begin() + idx, this_path.end()); - this_path = std::move(left_paths); - break; - } + double GCode::get_speed_coor_x(double speed) + { - // the path is too short - if (!length_enough(extrusion.length())) { - split_line_speed = insert_speed(extrusion.length(), x_base, smooth_length_count, final_v); - splited_path.emplace_back(extrusion); - splited_path.back().smooth_speed = split_line_speed; - // clear this_path while the extrusion is last one - if (idx < this_path.size() - 1) - continue; + double temp = speed / m_smooth_coefficient; + return sqrt(temp); + } - this_path.clear(); - break; - } + double GCode::get_overhang_degree_corr_speed(float normal_speed, double path_degree) + { - // reverse if this slowdown the speed - Polyline input_polyline = extrusion.polyline; - if (!split_from_left) - std::reverse(input_polyline.begin(), input_polyline.end()); - - Point line_start_pt = input_polyline.points.front(); - Point line_end_pt = input_polyline.points[1]; - bool get_next_line = false; - size_t end_pt_idx = 1; - - // split long extrusion - Point last_point = line_start_pt; - while (split_line_speed < final_v && end_pt_idx < input_polyline.size()) { - // move to next line - if (get_next_line) { - line_start_pt = input_polyline.points[end_pt_idx - 1]; - line_end_pt = input_polyline.points[end_pt_idx]; - } - // This line is cut off as a speed transition area - Polyline cuted_polyline; - Line line(line_start_pt, line_end_pt); - - cuted_polyline.append(line_start_pt); - // split polyline and set speed - if (!length_enough(line.length())) { - split_line_speed = insert_speed(line.length(), x_base, smooth_length_count, final_v); - end_pt_idx++; - get_next_line = true; - cuted_polyline.append(line.b); - } else { - // path is too long, split it - double rate = min_step_length / line.length(); - Point insert_p = line.a + (line.b - line.a) * rate; - - split_line_speed = insert_speed(min_step_length, x_base, smooth_length_count, final_v); - line_start_pt = insert_p; - get_next_line = false; - cuted_polyline.append(insert_p); - } - // reverse back - last_point = cuted_polyline.last_point(); - if (!split_from_left) - std::reverse(cuted_polyline.begin(), cuted_polyline.end()); - ExtrusionPath path_step(cuted_polyline, extrusion); + // BBS: protection: overhang degree is float, make sure it not excess degree range + if (path_degree <= 0) + return normal_speed; - path_step.smooth_speed = split_line_speed; - splited_path.push_back(std::move(path_step)); - } + int lower_degree_bound = int(path_degree); + // BBS: use lower speed of 75%-100% for better cooling + if (path_degree >= 4 || path_degree == lower_degree_bound) + return m_config.get_abs_value_at(overhang_speed_key_map[lower_degree_bound].c_str(), cur_extruder_index()); - if (last_point == input_polyline.last_point()){ - if (idx == this_path.size() - 1) - this_path.clear(); - continue; - } - // split polyline - Polyline p1, p2; - extrusion.polyline.split_at(last_point, &p1, &p2); + int upper_degree_bound = lower_degree_bound + 1; - if (split_from_left) { - //update split point to avoid travel path - splited_path.back().polyline.points.back() = last_point; - ExtrusionPath polyline_left(p2, extrusion); - left_paths.emplace_back(polyline_left); - } else { - splited_path.back().polyline.points.front() = last_point; - ExtrusionPath polyline_left(p1, extrusion); - left_paths.emplace_back(polyline_left); - } + double lower_speed_bound = lower_degree_bound == 0 ? normal_speed : m_config.get_abs_value_at(overhang_speed_key_map[lower_degree_bound].c_str(), cur_extruder_index()); + double upper_speed_bound = upper_degree_bound == 0 ? normal_speed : m_config.get_abs_value_at(overhang_speed_key_map[upper_degree_bound].c_str(), cur_extruder_index()); - left_paths.insert(left_paths.end(), this_path.begin() + idx + 1, this_path.end()); + lower_speed_bound = lower_speed_bound == 0 ? normal_speed : lower_speed_bound; + upper_speed_bound = upper_speed_bound == 0 ? normal_speed : upper_speed_bound; - this_path = std::move(left_paths); - break; + double speed_out = lower_speed_bound + (upper_speed_bound - lower_speed_bound) * (path_degree - lower_degree_bound); + return speed_out; } - // set left path speed - if (!this_path.empty() && final_v != this_path.front().smooth_speed) - for (ExtrusionPath &left : this_path) - left.smooth_speed = final_v; + static bool need_smooth_speed(const ExtrusionPath &other_path, const ExtrusionPath &this_path) + { + if (this_path.smooth_speed - other_path.smooth_speed > smooth_speed_step) + return true; - interpolated_paths.insert(interpolated_paths.end(), splited_path.begin(), splited_path.end()); + return false; + } - return; -} + void GCode::split_and_mapping_speed(double other_path_v, double final_v, ExtrusionPaths &this_path, double max_smooth_length, ExtrusionPaths &interpolated_paths, bool split_from_left) + { + if (this_path.empty() || max_smooth_length == 0) + return; + + ExtrusionPaths splited_path; + + // get params + double this_path_x = scale_(get_speed_coor_x(final_v)); + double x_base = scale_(get_speed_coor_x(other_path_v)); + double smooth_length = this_path_x - x_base; + double smooth_length_count = 0; + double split_line_speed = 0; + // this length not support to get final v, adjust final v + if (smooth_length > max_smooth_length) + { + smooth_length = max_smooth_length; + final_v = mapping_speed(unscale_(x_base + max_smooth_length)); + } + auto insert_speed = [this](double line_lenght, double &pos_x, double &smooth_length_count, double target_v) + { + pos_x += line_lenght; + double pos_x_speed = mapping_speed(unscale_(pos_x)); + smooth_length_count += line_lenght; -std::vector GCode::merge_same_speed_paths(const ExtrusionPaths &paths) -{ - std::vector paths_categroy_by_speed; + if (pos_x_speed > target_v) + pos_x_speed = target_v; - std::optional path_collection; + return pos_x_speed; + }; - for(size_t path_idx=0; path_idx= smooth_length) + { + left_paths.insert(left_paths.end(), this_path.begin() + idx, this_path.end()); + this_path = std::move(left_paths); + break; } - paths_categroy_by_speed.push_back({path}); - continue; - } + // the path is too short + if (!length_enough(extrusion.length())) + { + split_line_speed = insert_speed(extrusion.length(), x_base, smooth_length_count, final_v); + splited_path.emplace_back(extrusion); + splited_path.back().smooth_speed = split_line_speed; + // clear this_path while the extrusion is last one + if (idx < this_path.size() - 1) + continue; - if (!path_collection.has_value()) { - path_collection = {path}; - continue; - } + this_path.clear(); + break; + } - if(path_collection->back().can_merge(path)){ - path_collection->emplace_back(path); - } else { - paths_categroy_by_speed.emplace_back(std::move(*path_collection)); - path_collection = std::nullopt; - path_collection = {path}; - } - } + // reverse if this slowdown the speed + Polyline input_polyline = extrusion.polyline; + if (!split_from_left) + std::reverse(input_polyline.begin(), input_polyline.end()); - if (path_collection.has_value()) - paths_categroy_by_speed.emplace_back(std::move(*path_collection)); + Point line_start_pt = input_polyline.points.front(); + Point line_end_pt = input_polyline.points[1]; + bool get_next_line = false; + size_t end_pt_idx = 1; - return paths_categroy_by_speed; -} + // split long extrusion + Point last_point = line_start_pt; + while (split_line_speed < final_v && end_pt_idx < input_polyline.size()) + { + // move to next line + if (get_next_line) + { + line_start_pt = input_polyline.points[end_pt_idx - 1]; + line_end_pt = input_polyline.points[end_pt_idx]; + } + // This line is cut off as a speed transition area + Polyline cuted_polyline; + Line line(line_start_pt, line_end_pt); -ExtrusionPaths GCode::set_speed_transition(std::vector &paths) -{ - ExtrusionPaths interpolated_paths; - for (int path_idx = 0; path_idx < paths.size(); path_idx++) { - // update path - ExtrusionPaths &path = paths[path_idx]; //paths with same speed - // 100% overhang speed will not to set smooth speed - // overhang path will not be merged to a path collection - if (path.front().role() == erOverhangPerimeter) { - interpolated_paths.insert(interpolated_paths.end(), path.begin(), path.end()); - continue; - } + cuted_polyline.append(line_start_pt); + // split polyline and set speed + if (!length_enough(line.length())) + { + split_line_speed = insert_speed(line.length(), x_base, smooth_length_count, final_v); + end_pt_idx++; + get_next_line = true; + cuted_polyline.append(line.b); + } + else + { + // path is too long, split it + double rate = min_step_length / line.length(); + Point insert_p = line.a + (line.b - line.a) * rate; + + split_line_speed = insert_speed(min_step_length, x_base, smooth_length_count, final_v); + line_start_pt = insert_p; + get_next_line = false; + cuted_polyline.append(insert_p); + } + // reverse back + last_point = cuted_polyline.last_point(); + if (!split_from_left) + std::reverse(cuted_polyline.begin(), cuted_polyline.end()); + ExtrusionPath path_step(cuted_polyline, extrusion); + + path_step.smooth_speed = split_line_speed; + splited_path.push_back(std::move(path_step)); + } - if (path.empty()) - continue; + if (last_point == input_polyline.last_point()) + { + if (idx == this_path.size() - 1) + this_path.clear(); + continue; + } + // split polyline + Polyline p1, p2; + extrusion.polyline.split_at(last_point, &p1, &p2); - bool smooth_left_path = path_idx > 0 && !interpolated_paths.empty() && need_smooth_speed(interpolated_paths.back(), path.front()); + if (split_from_left) + { + // update split point to avoid travel path + splited_path.back().polyline.points.back() = last_point; + ExtrusionPath polyline_left(p2, extrusion); + left_paths.emplace_back(polyline_left); + } + else + { + splited_path.back().polyline.points.front() = last_point; + ExtrusionPath polyline_left(p1, extrusion); + left_paths.emplace_back(polyline_left); + } - bool smooth_right_path = path_idx < paths.size() - 1 && need_smooth_speed(paths[path_idx + 1].front(), path.front()); + left_paths.insert(left_paths.end(), this_path.begin() + idx + 1, this_path.end()); - if (!smooth_left_path && !smooth_right_path) { - interpolated_paths.insert(interpolated_paths.end(), path.begin(), path.end()); - continue; + this_path = std::move(left_paths); + break; } - // get smooth length - auto get_path_length = [this](ExtrusionPaths path) { - return std::accumulate(path.begin(), path.end(), 0.0, [](double sum, const ExtrusionPath &p) { return sum + p.length(); }); - }; - double max_smooth_path_length = get_path_length(path); + // set left path speed + if (!this_path.empty() && final_v != this_path.front().smooth_speed) + for (ExtrusionPath &left : this_path) + left.smooth_speed = final_v; - if (smooth_right_path && smooth_left_path) - max_smooth_path_length /= 2; + interpolated_paths.insert(interpolated_paths.end(), splited_path.begin(), splited_path.end()); - // smooth left - ExtrusionPaths left_split_paths; - if (smooth_left_path ) { - split_and_mapping_speed(interpolated_paths.back().smooth_speed, path.front().smooth_speed, path, max_smooth_path_length, interpolated_paths); + return; + } - //update path length - if (path.empty()) - continue; + std::vector GCode::merge_same_speed_paths(const ExtrusionPaths &paths) + { + std::vector paths_categroy_by_speed; - max_smooth_path_length = get_path_length(path); - } + std::optional path_collection; - if (!smooth_right_path) { - interpolated_paths.insert(interpolated_paths.end(), path.begin(), path.end()); - continue; - } + for (size_t path_idx = 0; path_idx < paths.size(); ++path_idx) + { + ExtrusionPath path = paths[path_idx]; + path.smooth_speed = get_path_speed(path); - //BBS: FIX git 4827: if the path is too short, path is not smooth enough - // smooth right - ExtrusionPaths right_split_paths; - size_t right_end = path_idx + 1; - // get smoothing window - std::vector paths_cpoy; - paths_cpoy.push_back(&path); + if (path.role() == erOverhangPerimeter) + { + if (path_collection.has_value()) + { + paths_categroy_by_speed.emplace_back(std::move(*path_collection)); + path_collection = std::nullopt; + } - for (; right_end < paths.size() - 1; right_end++) { - if (paths[right_end].front().role() == erOverhangPerimeter) - break; + paths_categroy_by_speed.push_back({path}); + continue; + } - paths_cpoy.push_back(&paths[right_end]); - if (!need_smooth_speed(paths[right_end + 1].front(), paths[right_end].front())) - break; + if (!path_collection.has_value()) + { + path_collection = {path}; + continue; + } + + if (path_collection->back().can_merge(path)) + { + path_collection->emplace_back(path); + } + else + { + paths_categroy_by_speed.emplace_back(std::move(*path_collection)); + path_collection = std::nullopt; + path_collection = {path}; + } } - path_idx += paths_cpoy.size() - 1; - double prev_speed = paths[path_idx + 1].front().smooth_speed; + if (path_collection.has_value()) + paths_categroy_by_speed.emplace_back(std::move(*path_collection)); - // reverse paths - std::reverse(paths_cpoy.begin(), paths_cpoy.end()); - for (ExtrusionPaths *paths_temp : paths_cpoy) { std::reverse(paths_temp->begin(), paths_temp->end()); } + return paths_categroy_by_speed; + } - // smooth right path - ExtrusionPaths transition_right; - for (size_t win_pt = 0; win_pt < paths_cpoy.size(); win_pt++) { - if (win_pt != 0) { - prev_speed = transition_right.back().smooth_speed; + ExtrusionPaths GCode::set_speed_transition(std::vector &paths) + { + ExtrusionPaths interpolated_paths; + for (int path_idx = 0; path_idx < paths.size(); path_idx++) + { + // update path + ExtrusionPaths &path = paths[path_idx]; // paths with same speed + // 100% overhang speed will not to set smooth speed + // overhang path will not be merged to a path collection + if (path.front().role() == erOverhangPerimeter) + { + interpolated_paths.insert(interpolated_paths.end(), path.begin(), path.end()); + continue; } - split_and_mapping_speed(prev_speed, paths_cpoy[win_pt]->front().smooth_speed, *paths_cpoy[win_pt], get_path_length(*paths_cpoy[win_pt]), transition_right, false); + if (path.empty()) + continue; - transition_right.insert(transition_right.end(), paths_cpoy[win_pt]->begin(), paths_cpoy[win_pt]->end()); - } - std::reverse(transition_right.begin(), transition_right.end()); + bool smooth_left_path = path_idx > 0 && !interpolated_paths.empty() && need_smooth_speed(interpolated_paths.back(), path.front()); - // reverse paths back - interpolated_paths.insert(interpolated_paths.end(), transition_right.begin(), transition_right.end()); + bool smooth_right_path = path_idx < paths.size() - 1 && need_smooth_speed(paths[path_idx + 1].front(), path.front()); - } + if (!smooth_left_path && !smooth_right_path) + { + interpolated_paths.insert(interpolated_paths.end(), path.begin(), path.end()); + continue; + } + // get smooth length + auto get_path_length = [this](ExtrusionPaths path) + { + return std::accumulate(path.begin(), path.end(), 0.0, [](double sum, const ExtrusionPath &p) + { return sum + p.length(); }); + }; - return interpolated_paths; -} + double max_smooth_path_length = get_path_length(path); -void GCode::smooth_speed_discontinuity_area(ExtrusionPaths &paths) { + if (smooth_right_path && smooth_left_path) + max_smooth_path_length /= 2; - if (paths.size() <= 1 || m_smooth_coefficient == 0) - return; + // smooth left + ExtrusionPaths left_split_paths; + if (smooth_left_path) + { + split_and_mapping_speed(interpolated_paths.back().smooth_speed, path.front().smooth_speed, path, max_smooth_path_length, interpolated_paths); - //step 1 merge same speed path - size_t path_tail_pos = 0; - std::vector prepare_paths = merge_same_speed_paths(paths); + // update path length + if (path.empty()) + continue; - //step 2 split path - ExtrusionPaths inter_paths; - inter_paths = set_speed_transition(prepare_paths); - paths = std::move(inter_paths); -} -bool GCode::slowDownByHeight(double& maxSpeed, double& maxAcc, const ExtrusionPath& path) -{ - double height1, height2, speed1, speed2, acc1, acc2, desiredMaxSpeed = 1000., desiredMaxAcc = 100000; - double currentHeight = this->m_layer->print_z; - bool do_slowdown_by_height = NOZZLE_CONFIG(enable_height_slowdown); - - if (path.role() > erNone && path.role() <= erGapFill) {} - else do_slowdown_by_height = false; - - if (do_slowdown_by_height) { - height1 = NOZZLE_CONFIG(slowdown_start_height); - height2 = NOZZLE_CONFIG(slowdown_end_height); - speed1 = NOZZLE_CONFIG(slowdown_start_speed); - speed2 = NOZZLE_CONFIG(slowdown_end_speed); - acc1 = NOZZLE_CONFIG(slowdown_start_acc); - acc2 = NOZZLE_CONFIG(slowdown_end_acc); - - if (height1 >= height2 || currentHeight > height2 || currentHeight < height1) do_slowdown_by_height = false; - else { - // speed should be decreased linearly - desiredMaxSpeed = (currentHeight - height1) / (height2 - height1) * (speed2 - speed1) + speed1; - // acceleration should be decreased linearly - desiredMaxAcc = acc1 - (acc1 - acc2) / (height2 - height1) * (currentHeight - height1); - - // modify travel speed and acceleration - for (auto& av : m_writer.config.travel_speed.values) { - if (!std::isnan(av)) { - av = std::min(av, desiredMaxSpeed); - } + max_smooth_path_length = get_path_length(path); } - for (auto& bv : m_writer.config.travel_speed_z.values) { - if (!std::isnan(bv)) { - bv = std::min(bv, desiredMaxSpeed); - } - } - for (auto& ta : m_writer.get_travel_acceleration()) { - ta = std::min(ta, (unsigned int)desiredMaxAcc); + + if (!smooth_right_path) + { + interpolated_paths.insert(interpolated_paths.end(), path.begin(), path.end()); + continue; } - if (!is_BBL_Printer()) - m_config.travel_jerk.value = std::min(m_config.travel_jerk.value, desiredMaxSpeed); - } - } - maxSpeed = desiredMaxSpeed; - maxAcc = desiredMaxAcc; - return do_slowdown_by_height; -} -double GCode::calc_max_volumetric_speed(const double layer_height, const double line_width, const std::string co_str) -{ - std::vector cs; - std::stringstream ss(co_str); - std::string token; + // BBS: FIX git 4827: if the path is too short, path is not smooth enough + // smooth right + ExtrusionPaths right_split_paths; + size_t right_end = path_idx + 1; + // get smoothing window + std::vector paths_cpoy; + paths_cpoy.push_back(&path); - while (std::getline(ss, token, ' ')) { - try { - cs.push_back(std::stod(token)); - } catch (...) { - std::cerr << "Transformation failed: " << token << std::endl; - } - } - if (cs.size() != 6 || std::all_of(cs.begin(), cs.end(), [](double v) { return v == 0; })) return std::numeric_limits::max(); + for (; right_end < paths.size() - 1; right_end++) + { + if (paths[right_end].front().role() == erOverhangPerimeter) + break; + + paths_cpoy.push_back(&paths[right_end]); + if (!need_smooth_speed(paths[right_end + 1].front(), paths[right_end].front())) + break; + } - const double x = layer_height; - const double y = line_width; + path_idx += paths_cpoy.size() - 1; + double prev_speed = paths[path_idx + 1].front().smooth_speed; - double res = cs[0] * x * x + cs[1] * y * y + cs[2] * x * y + cs[3] * x + cs[4] * y + cs[5]; - return res; -} + // reverse paths + std::reverse(paths_cpoy.begin(), paths_cpoy.end()); + for (ExtrusionPaths *paths_temp : paths_cpoy) + { + std::reverse(paths_temp->begin(), paths_temp->end()); + } -std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed, bool use_seperate_speed, bool is_first_slope) -{ - std::string gcode; + // smooth right path + ExtrusionPaths transition_right; + for (size_t win_pt = 0; win_pt < paths_cpoy.size(); win_pt++) + { + if (win_pt != 0) + { + prev_speed = transition_right.back().smooth_speed; + } - if (is_bridge(path.role())) - description += " (bridge)"; + split_and_mapping_speed(prev_speed, paths_cpoy[win_pt]->front().smooth_speed, *paths_cpoy[win_pt], get_path_length(*paths_cpoy[win_pt]), transition_right, false); - const ExtrusionPathSloped *sloped = dynamic_cast(&path); - const auto get_sloped_z = [&sloped, this](double z_ratio) { - const auto height = sloped->height; - return lerp(m_nominal_z - height, m_nominal_z, z_ratio); - }; + transition_right.insert(transition_right.end(), paths_cpoy[win_pt]->begin(), paths_cpoy[win_pt]->end()); + } + std::reverse(transition_right.begin(), transition_right.end()); - auto temp_travel_speed = m_writer.config.travel_speed; - auto temp_travel_speed_z = m_writer.config.travel_speed_z; - auto temp_travel_jerk = m_config.travel_jerk; - auto temp_travel_acc = m_writer.get_travel_acceleration(); - double desiredMaxSpeed, desiredMaxAcc; - - bool do_slowdown_by_height = slowDownByHeight(desiredMaxSpeed, desiredMaxAcc, path); - - // go to first point of extrusion path - //BBS: path.first_point is 2D point. But in lazy raise case, lift z is done in travel_to function. - //Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance - if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z || (sloped != nullptr && !sloped->is_flat())) { - gcode += this->travel_to( - path.first_point(), - path.role(), - "move to first " + description + " point", - sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio) - ); - m_need_change_layer_lift_z = false; - } + // reverse paths back + interpolated_paths.insert(interpolated_paths.end(), transition_right.begin(), transition_right.end()); + } - // restore travel speed and acceleration - if (do_slowdown_by_height) { - m_writer.config.travel_speed = temp_travel_speed; - m_writer.config.travel_speed_z = temp_travel_speed_z; - m_writer.set_travel_acceleration(temp_travel_acc); - if (!is_BBL_Printer()) - m_config.travel_jerk = temp_travel_jerk; + return interpolated_paths; } - // if needed, write the gcode_label_objects_end then gcode_label_objects_start - // should be already done by travel_to, but just in case - m_writer.add_object_change_labels(gcode); + void GCode::smooth_speed_discontinuity_area(ExtrusionPaths &paths) + { - // compensate retraction - gcode += this->unretract(); - m_config.apply(m_calib_config); + if (paths.size() <= 1 || m_smooth_coefficient == 0) + return; - // adjust acceleration - if (NOZZLE_CONFIG(default_acceleration) > 0) { - double acceleration; - if (this->on_first_layer() && NOZZLE_CONFIG(initial_layer_acceleration) > 0) { - acceleration = NOZZLE_CONFIG(initial_layer_acceleration); -#if 0 - } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { - acceleration = m_config.first_layer_acceleration_over_raft.value; - } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) { - acceleration = m_config.bridge_acceleration.value; -#endif - } else if (NOZZLE_CONFIG(outer_wall_acceleration) > 0 - //BBS: FIXME, in fact,we only need to set acceleration for outer wall. But we don't know - //whether the overhang perimeter is outer or not. So using specific acceleration together. - && (path.role() == erExternalPerimeter || path.role() == erOverhangPerimeter)) { - acceleration = NOZZLE_CONFIG(outer_wall_acceleration); - } else if (NOZZLE_CONFIG(top_surface_acceleration) > 0 && is_top_surface(path.role())) { - acceleration = NOZZLE_CONFIG(top_surface_acceleration); - } else if (NOZZLE_CONFIG(inner_wall_acceleration) > 0 && path.role() == erPerimeter) { - acceleration = NOZZLE_CONFIG(inner_wall_acceleration); - } else if (m_config.get_abs_value_at("sparse_infill_acceleration", cur_config_index()) > 0 && (path.role() == erInternalInfill)) { - acceleration = m_config.get_abs_value_at("sparse_infill_acceleration", cur_config_index()); - } else { - acceleration = NOZZLE_CONFIG(default_acceleration); + // step 1 merge same speed path + size_t path_tail_pos = 0; + std::vector prepare_paths = merge_same_speed_paths(paths); + + // step 2 split path + ExtrusionPaths inter_paths; + inter_paths = set_speed_transition(prepare_paths); + paths = std::move(inter_paths); + } + bool GCode::slowDownByHeight(double &maxSpeed, double &maxAcc, const ExtrusionPath &path) + { + double height1, height2, speed1, speed2, acc1, acc2, desiredMaxSpeed = 1000., desiredMaxAcc = 100000; + double currentHeight = this->m_layer->print_z; + bool do_slowdown_by_height = NOZZLE_CONFIG(enable_height_slowdown); + + if (path.role() > erNone && path.role() <= erGapFill) + { } + else + do_slowdown_by_height = false; + if (do_slowdown_by_height) - acceleration = std::min(acceleration, desiredMaxAcc); - m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + { + height1 = NOZZLE_CONFIG(slowdown_start_height); + height2 = NOZZLE_CONFIG(slowdown_end_height); + speed1 = NOZZLE_CONFIG(slowdown_start_speed); + speed2 = NOZZLE_CONFIG(slowdown_end_speed); + acc1 = NOZZLE_CONFIG(slowdown_start_acc); + acc2 = NOZZLE_CONFIG(slowdown_end_acc); + + if (height1 >= height2 || currentHeight > height2 || currentHeight < height1) + do_slowdown_by_height = false; + else + { + // speed should be decreased linearly + desiredMaxSpeed = (currentHeight - height1) / (height2 - height1) * (speed2 - speed1) + speed1; + // acceleration should be decreased linearly + desiredMaxAcc = acc1 - (acc1 - acc2) / (height2 - height1) * (currentHeight - height1); + + // modify travel speed and acceleration + for (auto &av : m_writer.config.travel_speed.values) + { + if (!std::isnan(av)) + { + av = std::min(av, desiredMaxSpeed); + } + } + for (auto &bv : m_writer.config.travel_speed_z.values) + { + if (!std::isnan(bv)) + { + bv = std::min(bv, desiredMaxSpeed); + } + } + for (auto &ta : m_writer.get_travel_acceleration()) + { + ta = std::min(ta, (unsigned int)desiredMaxAcc); + } + if (!is_BBL_Printer()) + m_config.travel_jerk.value = std::min(m_config.travel_jerk.value, desiredMaxSpeed); + } + } + maxSpeed = desiredMaxSpeed; + maxAcc = desiredMaxAcc; + return do_slowdown_by_height; } - if (m_config.default_jerk.value > 0 && !this->is_BBL_Printer()) { - double jerk = m_config.default_jerk.value; - if (this->on_first_layer() && m_config.initial_layer_jerk.value > 0) - jerk = m_config.initial_layer_jerk.value; - else if (m_config.outer_wall_jerk.value > 0 && path.role() == erExternalPerimeter) - jerk = m_config.outer_wall_jerk.value; - else if (m_config.inner_wall_jerk.value > 0 && path.role() == erPerimeter) - jerk = m_config.inner_wall_jerk.value; - else if (m_config.infill_jerk.value > 0 && is_infill(path.role())) - jerk = m_config.infill_jerk.value; - else if (m_config.top_surface_jerk.value > 0 && is_top_surface(path.role())) - jerk = m_config.top_surface_jerk.value; + double GCode::calc_max_volumetric_speed(const double layer_height, const double line_width, const std::string co_str) + { + std::vector cs; + std::stringstream ss(co_str); + std::string token; - if (do_slowdown_by_height) - jerk = std::min(jerk, desiredMaxSpeed); - gcode += m_writer.set_jerk_xy(jerk); + while (std::getline(ss, token, ' ')) + { + try + { + cs.push_back(std::stod(token)); + } + catch (...) + { + std::cerr << "Transformation failed: " << token << std::endl; + } + } + if (cs.size() != 6 || std::all_of(cs.begin(), cs.end(), [](double v) + { return v == 0; })) + return std::numeric_limits::max(); + + const double x = layer_height; + const double y = line_width; + + double res = cs[0] * x * x + cs[1] * y * y + cs[2] * x * y + cs[3] * x + cs[4] * y + cs[5]; + return res; } - // calculate extrusion length per distance unit - auto _mm3_per_mm = path.mm3_per_mm * double(this->config().print_flow_ratio.value); - if( path.role() == erTopSolidInfill ) - _mm3_per_mm *= m_config.top_solid_infill_flow_ratio.value; - else if (this->on_first_layer()) - _mm3_per_mm *= m_config.initial_layer_flow_ratio.value; - - double e_per_mm = m_writer.filament()->e_per_mm3() * _mm3_per_mm; - - double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.filament()->id())); - // set speed - auto calc_speed_by_role = [&]() -> void { - if (path.role() == erPerimeter) { - speed = NOZZLE_CONFIG(inner_wall_speed); - //reset speed by auto compensation speed - if(use_seperate_speed) { - speed = NOZZLE_CONFIG(circle_compensation_speed); - }else if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) - speed = path.smooth_speed; - else if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) { - double new_speed = 0; - new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); - speed = new_speed == 0.0 ? speed : new_speed; + + std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed, bool use_seperate_speed, bool is_first_slope) + { + std::string gcode; + + if (is_bridge(path.role())) + description += " (bridge)"; + + const ExtrusionPathSloped *sloped = dynamic_cast(&path); + const auto get_sloped_z = [&sloped, this](double z_ratio) + { + const auto height = sloped->height; + return lerp(m_nominal_z - height, m_nominal_z, z_ratio); + }; + + auto temp_travel_speed = m_writer.config.travel_speed; + auto temp_travel_speed_z = m_writer.config.travel_speed_z; + auto temp_travel_jerk = m_config.travel_jerk; + auto temp_travel_acc = m_writer.get_travel_acceleration(); + double desiredMaxSpeed, desiredMaxAcc; + + bool do_slowdown_by_height = slowDownByHeight(desiredMaxSpeed, desiredMaxAcc, path); + + // go to first point of extrusion path + // BBS: path.first_point is 2D point. But in lazy raise case, lift z is done in travel_to function. + // Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance + if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z || (sloped != nullptr && !sloped->is_flat())) + { + gcode += this->travel_to( + path.first_point(), + path.role(), + "move to first " + description + " point", + sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio)); + m_need_change_layer_lift_z = false; + } + + // restore travel speed and acceleration + if (do_slowdown_by_height) + { + m_writer.config.travel_speed = temp_travel_speed; + m_writer.config.travel_speed_z = temp_travel_speed_z; + m_writer.set_travel_acceleration(temp_travel_acc); + if (!is_BBL_Printer()) + m_config.travel_jerk = temp_travel_jerk; + } + + // if needed, write the gcode_label_objects_end then gcode_label_objects_start + // should be already done by travel_to, but just in case + m_writer.add_object_change_labels(gcode); + + // compensate retraction + gcode += this->unretract(); + m_config.apply(m_calib_config); + + // adjust acceleration + if (NOZZLE_CONFIG(default_acceleration) > 0) + { + double acceleration; + if (this->on_first_layer() && NOZZLE_CONFIG(initial_layer_acceleration) > 0) + { + acceleration = NOZZLE_CONFIG(initial_layer_acceleration); +#if 0 + } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { + acceleration = m_config.first_layer_acceleration_over_raft.value; + } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) { + acceleration = m_config.bridge_acceleration.value; +#endif } - } else if (path.role() == erExternalPerimeter) { - speed = NOZZLE_CONFIG(outer_wall_speed); - // reset speed by auto compensation speed - if (use_seperate_speed) { - speed = NOZZLE_CONFIG(circle_compensation_speed); - } else if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) - speed = path.smooth_speed; - else if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) { - double new_speed = 0; - new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); - speed = new_speed == 0.0 ? speed : new_speed; + else if (NOZZLE_CONFIG(outer_wall_acceleration) > 0 + // BBS: FIXME, in fact,we only need to set acceleration for outer wall. But we don't know + // whether the overhang perimeter is outer or not. So using specific acceleration together. + && (path.role() == erExternalPerimeter || path.role() == erOverhangPerimeter)) + { + acceleration = NOZZLE_CONFIG(outer_wall_acceleration); } - } else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) { - speed = NOZZLE_CONFIG(overhang_totally_speed); - } else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) { - speed = NOZZLE_CONFIG(bridge_speed); - } else if (path.role() == erInternalInfill) { - speed = NOZZLE_CONFIG(sparse_infill_speed); - } else if (path.role() == erSolidInfill) { - speed = NOZZLE_CONFIG(internal_solid_infill_speed); - } else if (path.role() == erFloatingVerticalShell){ - if(use_seperate_speed){ - speed = NOZZLE_CONFIG(bridge_speed); + else if (NOZZLE_CONFIG(top_surface_acceleration) > 0 && is_top_surface(path.role())) + { + acceleration = NOZZLE_CONFIG(top_surface_acceleration); + } + else if (NOZZLE_CONFIG(inner_wall_acceleration) > 0 && path.role() == erPerimeter) + { + acceleration = NOZZLE_CONFIG(inner_wall_acceleration); } - else{ - speed = NOZZLE_CONFIG(vertical_shell_speed).get_abs_value(NOZZLE_CONFIG(internal_solid_infill_speed)); + else if (m_config.get_abs_value_at("sparse_infill_acceleration", cur_config_index()) > 0 && (path.role() == erInternalInfill)) + { + acceleration = m_config.get_abs_value_at("sparse_infill_acceleration", cur_config_index()); } + else + { + acceleration = NOZZLE_CONFIG(default_acceleration); + } + if (do_slowdown_by_height) + acceleration = std::min(acceleration, desiredMaxAcc); + m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } - else if (path.role() == erTopSolidInfill) { - speed = NOZZLE_CONFIG(top_surface_speed); - } else if (path.role() == erIroning) { - speed = m_config.get_abs_value("ironing_speed"); - } else if (path.role() == erBottomSurface) { - speed = NOZZLE_CONFIG(initial_layer_infill_speed); - } else if (path.role() == erGapFill) { - speed = NOZZLE_CONFIG(gap_infill_speed); + + if (m_config.default_jerk.value > 0 && !this->is_BBL_Printer()) + { + double jerk = m_config.default_jerk.value; + if (this->on_first_layer() && m_config.initial_layer_jerk.value > 0) + jerk = m_config.initial_layer_jerk.value; + else if (m_config.outer_wall_jerk.value > 0 && path.role() == erExternalPerimeter) + jerk = m_config.outer_wall_jerk.value; + else if (m_config.inner_wall_jerk.value > 0 && path.role() == erPerimeter) + jerk = m_config.inner_wall_jerk.value; + else if (m_config.infill_jerk.value > 0 && is_infill(path.role())) + jerk = m_config.infill_jerk.value; + else if (m_config.top_surface_jerk.value > 0 && is_top_surface(path.role())) + jerk = m_config.top_surface_jerk.value; + + if (do_slowdown_by_height) + jerk = std::min(jerk, desiredMaxSpeed); + gcode += m_writer.set_jerk_xy(jerk); } - else if (path.role() == erSupportMaterial || - path.role() == erSupportMaterialInterface) { - const double support_speed = NOZZLE_CONFIG(support_speed); - const double support_interface_speed = NOZZLE_CONFIG(support_interface_speed); - speed = (path.role() == erSupportMaterial) ? support_speed : support_interface_speed; - } else { - throw Slic3r::InvalidArgument("Invalid speed"); + // calculate extrusion length per distance unit + auto _mm3_per_mm = path.mm3_per_mm * double(this->config().print_flow_ratio.value); + if (path.role() == erTopSolidInfill) + _mm3_per_mm *= m_config.top_solid_infill_flow_ratio.value; + else if (this->on_first_layer()) + _mm3_per_mm *= m_config.initial_layer_flow_ratio.value; + + double e_per_mm = m_writer.filament()->e_per_mm3() * _mm3_per_mm; + + double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.filament()->id())); + // set speed + auto calc_speed_by_role = [&]() -> void + { + if (path.role() == erPerimeter) + { + speed = NOZZLE_CONFIG(inner_wall_speed); + // reset speed by auto compensation speed + if (use_seperate_speed) + { + speed = NOZZLE_CONFIG(circle_compensation_speed); + } + else if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) + speed = path.smooth_speed; + else if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) + { + double new_speed = 0; + new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); + speed = new_speed == 0.0 ? speed : new_speed; + } + } + else if (path.role() == erExternalPerimeter) + { + speed = NOZZLE_CONFIG(outer_wall_speed); + // reset speed by auto compensation speed + if (use_seperate_speed) + { + speed = NOZZLE_CONFIG(circle_compensation_speed); + } + else if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) + speed = path.smooth_speed; + else if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) + { + double new_speed = 0; + new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); + speed = new_speed == 0.0 ? speed : new_speed; + } + } + else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) + { + speed = NOZZLE_CONFIG(overhang_totally_speed); + } + else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) + { + speed = NOZZLE_CONFIG(bridge_speed); + } + else if (path.role() == erInternalInfill) + { + speed = NOZZLE_CONFIG(sparse_infill_speed); + } + else if (path.role() == erSolidInfill) + { + speed = NOZZLE_CONFIG(internal_solid_infill_speed); + } + else if (path.role() == erFloatingVerticalShell) + { + if (use_seperate_speed) + { + speed = NOZZLE_CONFIG(bridge_speed); + } + else + { + speed = NOZZLE_CONFIG(vertical_shell_speed).get_abs_value(NOZZLE_CONFIG(internal_solid_infill_speed)); + } + } + else if (path.role() == erTopSolidInfill) + { + speed = NOZZLE_CONFIG(top_surface_speed); + } + else if (path.role() == erIroning) + { + speed = m_config.get_abs_value("ironing_speed"); + } + else if (path.role() == erBottomSurface) + { + speed = NOZZLE_CONFIG(initial_layer_infill_speed); + } + else if (path.role() == erGapFill) + { + speed = NOZZLE_CONFIG(gap_infill_speed); + } + else if (path.role() == erSupportMaterial || + path.role() == erSupportMaterialInterface) + { + const double support_speed = NOZZLE_CONFIG(support_speed); + const double support_interface_speed = NOZZLE_CONFIG(support_interface_speed); + speed = (path.role() == erSupportMaterial) ? support_speed : support_interface_speed; + } + else + { + throw Slic3r::InvalidArgument("Invalid speed"); + } + }; + if (speed == -1) // not set speed, calc it + calc_speed_by_role(); + else if (speed > EPSILON && is_perimeter(path.role())) + { + // for perimeter, use the min speed of small_perimeter_speed and path_role speed + double previous_speed = speed; + calc_speed_by_role(); + speed = std::min(previous_speed, speed); + } // otherwise, keep previous speed in other conditions + + // BBS: if not set the speed, then use the filament_max_volumetric_speed directly + double filament_max_volumetric_speed = FILAMENT_CONFIG(filament_max_volumetric_speed); + if (FILAMENT_CONFIG(filament_adaptive_volumetric_speed)) + { + double fitted_value = calc_max_volumetric_speed(path.height, path.width, FILAMENT_CONFIG(volumetric_speed_coefficients)); + filament_max_volumetric_speed = std::min(filament_max_volumetric_speed, fitted_value); } - }; - if (speed == -1) // not set speed, calc it - calc_speed_by_role(); - else if (speed > EPSILON && is_perimeter(path.role())) { - // for perimeter, use the min speed of small_perimeter_speed and path_role speed - double previous_speed = speed; - calc_speed_by_role(); - speed = std::min(previous_speed, speed); - } //otherwise, keep previous speed in other conditions - - //BBS: if not set the speed, then use the filament_max_volumetric_speed directly - double filament_max_volumetric_speed = FILAMENT_CONFIG(filament_max_volumetric_speed); - if (FILAMENT_CONFIG(filament_adaptive_volumetric_speed)){ - double fitted_value = calc_max_volumetric_speed(path.height, path.width, FILAMENT_CONFIG(volumetric_speed_coefficients)); - filament_max_volumetric_speed = std::min(filament_max_volumetric_speed, fitted_value); - } - if( speed == 0 ) - { - if (_mm3_per_mm>0) - speed = filament_max_volumetric_speed / _mm3_per_mm; - else - speed = filament_max_volumetric_speed / path.mm3_per_mm; - } - if (this->on_first_layer()) { - //BBS: for solid infill of initial layer, speed can be higher as long as - //wall lines have be attached - if (path.role() != erBottomSurface) - speed = NOZZLE_CONFIG(initial_layer_speed); - } - //BBS: remove this config - //else if (this->object_layer_over_raft()) - // speed = m_config.get_abs_value("first_layer_speed_over_raft", speed); - //if (m_config.max_volumetric_speed.value > 0) { - // // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - // speed = std::min( - // speed, - // m_config.max_volumetric_speed.value / path.mm3_per_mm - // ); - //} - if (filament_max_volumetric_speed > 0) { - double extrude_speed = filament_max_volumetric_speed / path.mm3_per_mm; - if (_mm3_per_mm > 0) - extrude_speed = filament_max_volumetric_speed / _mm3_per_mm; - - // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - speed = std::min(speed, extrude_speed); - } + if (speed == 0) + { + if (_mm3_per_mm > 0) + speed = filament_max_volumetric_speed / _mm3_per_mm; + else + speed = filament_max_volumetric_speed / path.mm3_per_mm; + } + if (this->on_first_layer()) + { + // BBS: for solid infill of initial layer, speed can be higher as long as + // wall lines have be attached + if (path.role() != erBottomSurface) + speed = NOZZLE_CONFIG(initial_layer_speed); + } + // BBS: remove this config + // else if (this->object_layer_over_raft()) + // speed = m_config.get_abs_value("first_layer_speed_over_raft", speed); + // if (m_config.max_volumetric_speed.value > 0) { + // // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + // speed = std::min( + // speed, + // m_config.max_volumetric_speed.value / path.mm3_per_mm + // ); + // } + if (filament_max_volumetric_speed > 0) + { + double extrude_speed = filament_max_volumetric_speed / path.mm3_per_mm; + if (_mm3_per_mm > 0) + extrude_speed = filament_max_volumetric_speed / _mm3_per_mm; + + // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + speed = std::min(speed, extrude_speed); + } - if (do_slowdown_by_height) - speed = std::min(speed, desiredMaxSpeed); - double F = speed * 60; // convert mm/sec to mm/min + if (do_slowdown_by_height) + speed = std::min(speed, desiredMaxSpeed); + double F = speed * 60; // convert mm/sec to mm/min - // extrude arc or line - if (m_enable_extrusion_role_markers) - { - if (path.role() != m_last_extrusion_role) + // extrude arc or line + if (m_enable_extrusion_role_markers) { - m_last_extrusion_role = path.role(); - if (m_enable_extrusion_role_markers) + if (path.role() != m_last_extrusion_role) { - char buf[32]; - sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(m_last_extrusion_role)); - gcode += buf; + m_last_extrusion_role = path.role(); + if (m_enable_extrusion_role_markers) + { + char buf[32]; + sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(m_last_extrusion_role)); + gcode += buf; + } } } - } - - // adds processor tags and updates processor tracking data - // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height - // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines - bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower); - char buf[64]; - assert(is_decimal_separator_point()); + // adds processor tags and updates processor tracking data + // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height + // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines + bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower); + char buf[64]; + assert(is_decimal_separator_point()); - if (use_seperate_speed) - gcode += "; Slow Down Start\n"; + if (use_seperate_speed) + gcode += "; Slow Down Start\n"; - if (path.role() != m_last_processor_extrusion_role) { - m_last_processor_extrusion_role = path.role(); - sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str()); - gcode += buf; - } + if (path.role() != m_last_processor_extrusion_role) + { + m_last_processor_extrusion_role = path.role(); + sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str()); + gcode += buf; + } - if (last_was_wipe_tower || m_last_width != path.width) { - m_last_width = path.width; - sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width).c_str(), m_last_width); - gcode += buf; - } + if (last_was_wipe_tower || m_last_width != path.width) + { + m_last_width = path.width; + sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width).c_str(), m_last_width); + gcode += buf; + } #if ENABLE_GCODE_VIEWER_DATA_CHECKING - if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { - m_last_mm3_per_mm = path.mm3_per_mm; - sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); - gcode += buf; - } + if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) + { + m_last_mm3_per_mm = path.mm3_per_mm; + sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); + gcode += buf; + } #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) { - m_last_height = path.height; - sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), m_last_height); - gcode += buf; - } + if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) + { + m_last_height = path.height; + sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), m_last_height); + gcode += buf; + } - std::string comment; - bool cooling_extrude = false; - if (m_enable_cooling_markers) { - if (FILAMENT_CONFIG(enable_overhang_bridge_fan)) { - //BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter - int overhang_threshold = FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? - Overhang_threshold_none : FILAMENT_CONFIG(overhang_fan_threshold) - 1; - if ((FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter) || (path.get_overhang_degree() > overhang_threshold || - is_bridge(path.role()))) { - gcode += ";_OVERHANG_FAN_START\n"; + std::string comment; + bool cooling_extrude = false; + if (m_enable_cooling_markers) + { + if (FILAMENT_CONFIG(enable_overhang_bridge_fan)) + { + // BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter + int overhang_threshold = FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? Overhang_threshold_none : FILAMENT_CONFIG(overhang_fan_threshold) - 1; + if ((FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter) || (path.get_overhang_degree() > overhang_threshold || + is_bridge(path.role()))) + { + gcode += ";_OVERHANG_FAN_START\n"; + } } - } - int overhang_boundary_for_cooling = EXTRUDER_CONFIG(overhang_threshold_participating_cooling); + int overhang_boundary_for_cooling = EXTRUDER_CONFIG(overhang_threshold_participating_cooling); + + if (!is_bridge(path.role()) && path.get_overhang_degree() <= overhang_boundary_for_cooling) + { + cooling_extrude = true; + comment = ";_EXTRUDE_SET_SPEED"; + } - if (!is_bridge(path.role()) && path.get_overhang_degree() <= overhang_boundary_for_cooling) { - cooling_extrude = true; - comment = ";_EXTRUDE_SET_SPEED"; + if (path.role() == erExternalPerimeter) + comment += ";_EXTERNAL_PERIMETER"; } - if (path.role() == erExternalPerimeter) - comment += ";_EXTERNAL_PERIMETER"; - } + // F is mm per minute. + // if (sloped == nullptr) + gcode += m_writer.set_speed(F, "", comment); - // F is mm per minute. - //if (sloped == nullptr) - gcode += m_writer.set_speed(F, "", comment); + double path_length = 0.; + { + std::string comment = GCodeWriter::full_gcode_comment ? description : ""; + // BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode + // Attention: G2 and G3 is not supported in spiral_mode mode + if (!m_config.enable_arc_fitting || + path.polyline.fitting_result.empty() || + m_config.spiral_mode || + sloped != nullptr) + { + double path_length = 0.; + double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR; + for (const Line &line : path.polyline.lines()) + { + const double line_length = line.length() * SCALING_FACTOR; + // BBS: extursion cmd should E0 on cmd line + if (line_length < EPSILON) + continue; + path_length += line_length; - double path_length = 0.; - { - std::string comment = GCodeWriter::full_gcode_comment ? description : ""; - //BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode - //Attention: G2 and G3 is not supported in spiral_mode mode - if (!m_config.enable_arc_fitting || - path.polyline.fitting_result.empty() || - m_config.spiral_mode || - sloped != nullptr) { - double path_length = 0.; - double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR; - for (const Line &line : path.polyline.lines()) { - const double line_length = line.length() * SCALING_FACTOR; - // BBS: extursion cmd should E0 on cmd line - if (line_length < EPSILON) continue; - path_length += line_length; - - if (sloped == nullptr) { - gcode += m_writer.extrude_to_xy( - this->point_to_gcode(line.b), - e_per_mm * line_length, - comment); - } else { - // Sloped extrusion - auto dE = e_per_mm * line_length; - auto [z_ratio, e_ratio, slope_speed] = sloped->interpolate(path_length / total_length); - //FIX: cooling need to apply correctly - //gcode += m_writer.set_speed(slope_speed * 60, "", comment); - Vec2d dest2d = this->point_to_gcode(line.b); - Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio)); - //BBS: todo, should use small e at start to get good seam - double slope_e = dE * e_ratio; - gcode += m_writer.extrude_to_xyz(dest3d, slope_e); - } - } - } else { - // BBS: start to generate gcode from arc fitting data which includes line and arc - const std::vector& fitting_result = path.polyline.fitting_result; - for (size_t fitting_index = 0; fitting_index < fitting_result.size(); fitting_index++) { - switch (fitting_result[fitting_index].path_type) { - case EMovePathType::Linear_move: { - size_t start_index = fitting_result[fitting_index].start_point_index; - size_t end_index = fitting_result[fitting_index].end_point_index; - for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) { - const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]); - const double line_length = line.length() * SCALING_FACTOR; - // BBS: extursion cmd should E0 on cmd line - if (line_length < EPSILON) - continue; - path_length += line_length; + if (sloped == nullptr) + { gcode += m_writer.extrude_to_xy( this->point_to_gcode(line.b), e_per_mm * line_length, - comment, path.is_force_no_extrusion()); + comment); + } + else + { + // Sloped extrusion + auto dE = e_per_mm * line_length; + auto [z_ratio, e_ratio, slope_speed] = sloped->interpolate(path_length / total_length); + // FIX: cooling need to apply correctly + // gcode += m_writer.set_speed(slope_speed * 60, "", comment); + Vec2d dest2d = this->point_to_gcode(line.b); + Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio)); + // BBS: todo, should use small e at start to get good seam + double slope_e = dE * e_ratio; + gcode += m_writer.extrude_to_xyz(dest3d, slope_e); } - break; } - case EMovePathType::Arc_move_cw: - case EMovePathType::Arc_move_ccw: { - const ArcSegment& arc = fitting_result[fitting_index].arc_data; - const double arc_length = fitting_result[fitting_index].arc_data.length * SCALING_FACTOR; - // BBS: extursion cmd should E0 on cmd line - if (arc_length < EPSILON) - continue; - const Vec2d center_offset = this->point_to_gcode(arc.center) - this->point_to_gcode(arc.start_point); - path_length += arc_length; - gcode += m_writer.extrude_arc_to_xy( + } + else + { + // BBS: start to generate gcode from arc fitting data which includes line and arc + const std::vector &fitting_result = path.polyline.fitting_result; + for (size_t fitting_index = 0; fitting_index < fitting_result.size(); fitting_index++) + { + switch (fitting_result[fitting_index].path_type) + { + case EMovePathType::Linear_move: + { + size_t start_index = fitting_result[fitting_index].start_point_index; + size_t end_index = fitting_result[fitting_index].end_point_index; + for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) + { + const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]); + const double line_length = line.length() * SCALING_FACTOR; + // BBS: extursion cmd should E0 on cmd line + if (line_length < EPSILON) + continue; + path_length += line_length; + gcode += m_writer.extrude_to_xy( + this->point_to_gcode(line.b), + e_per_mm * line_length, + comment, path.is_force_no_extrusion()); + } + break; + } + case EMovePathType::Arc_move_cw: + case EMovePathType::Arc_move_ccw: + { + const ArcSegment &arc = fitting_result[fitting_index].arc_data; + const double arc_length = fitting_result[fitting_index].arc_data.length * SCALING_FACTOR; + // BBS: extursion cmd should E0 on cmd line + if (arc_length < EPSILON) + continue; + const Vec2d center_offset = this->point_to_gcode(arc.center) - this->point_to_gcode(arc.start_point); + path_length += arc_length; + gcode += m_writer.extrude_arc_to_xy( this->point_to_gcode(arc.end_point), center_offset, e_per_mm * arc_length, arc.direction == ArcDirection::Arc_Dir_CCW, comment, path.is_force_no_extrusion()); - break; + break; + } + default: + // BBS: should never happen that a empty path_type has been stored + assert(0); + break; + } } - default: - //BBS: should never happen that a empty path_type has been stored - assert(0); - break; + } + } + if (m_enable_cooling_markers) + { + if (cooling_extrude) + gcode += ";_EXTRUDE_END\n"; + + if (FILAMENT_CONFIG(enable_overhang_bridge_fan)) + { + // BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter + int overhang_threshold = FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? Overhang_threshold_none : FILAMENT_CONFIG(overhang_fan_threshold) - 1; + if ((FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter) || (path.get_overhang_degree() > overhang_threshold || + is_bridge(path.role()))) + gcode += ";_OVERHANG_FAN_END\n"; + } + } + + if (use_seperate_speed) + { + gcode += "; Slow Down End\n"; + } + + this->set_last_pos(path.last_point()); + return gcode; + } + + std::string encodeBase64(uint64_t value) + { + // Always use big endian mode + uint8_t src[8]; + for (size_t i = 0; i < 8; i++) + src[i] = (value >> (8 * i)) & 0xff; + + std::string dest; + dest.resize(boost::beast::detail::base64::encoded_size(sizeof(src))); + dest.resize(boost::beast::detail::base64::encode(&dest[0], src, sizeof(src))); + return dest; + } + + std::string GCode::_encode_label_ids_to_base64(std::vector ids) + { + assert(m_label_objects_ids.size() < 64); + + uint64_t bitset = 0; + for (size_t id : ids) + { + auto index = std::lower_bound(m_label_objects_ids.begin(), m_label_objects_ids.end(), id); + if (index != m_label_objects_ids.end() && *index == id) + bitset |= (1ull << (index - m_label_objects_ids.begin())); + else + throw Slic3r::LogicError("Unknown label object id!"); + } + if (bitset == 0) + throw Slic3r::LogicError("Label object id error!"); + + return encodeBase64(bitset); + } + + // This method accepts &point in print coordinates. + std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment, double z) + { + /* 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 + LiftType lift_type = LiftType::SpiralLift; + bool needs_retraction = this->needs_retraction(travel, role, lift_type); + // check whether wipe could be disabled without causing visible stringing + bool could_be_wipe_disabled = false; + // Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to. + const bool used_external_mp_once = m_avoid_crossing_perimeters.used_external_mp_once(); + + // if a retraction would be needed, try to use reduce_crossing_wall to plan a + // multi-hop travel path inside the configuration space + // if ( + if (m_config.reduce_crossing_wall && !m_avoid_crossing_perimeters.disabled_once()) + // BBS: don't generate detour travel paths when current position is unclea + { + travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); + // check again whether the new travel path still needs a retraction + needs_retraction = this->needs_retraction(travel, role, lift_type); + // if (needs_retraction && m_layer_index > 1) exit(0); + } + + // Re-allow reduce_crossing_wall for the next travel moves + m_avoid_crossing_perimeters.reset_once_modifiers(); + + // generate G-code for the travel move + std::string gcode; + if (needs_retraction) + { + if (m_config.reduce_crossing_wall && could_be_wipe_disabled && !m_last_scarf_seam_flag) + m_wipe.reset_path(); + + Point last_post_before_retract = this->last_pos(); + gcode += this->retract(false, false, lift_type); + // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. + // Because of it, it is necessary to call avoid crossing perimeters again with new starting point after calling retraction() + // FIXME Lukas H.: Try to predict if this second calling of avoid crossing perimeters will be needed or not. It could save computations. + if (last_post_before_retract != this->last_pos() && m_config.reduce_crossing_wall) + { + // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call. + if (used_external_mp_once) + m_avoid_crossing_perimeters.use_external_mp_once(); + travel = m_avoid_crossing_perimeters.travel_to(*this, point); + // If state of use_external_mp_once was changed reset it to right value. + if (used_external_mp_once) + m_avoid_crossing_perimeters.reset_once_modifiers(); + } + } + else + { + // Reset the wipe path when traveling, so one would not wipe along an old path. + m_wipe.reset_path(); + // if (m_config.reduce_crossing_wall) { + // // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call. + // if (used_external_mp_once) m_avoid_crossing_perimeters.use_external_mp_once(); + // travel = m_avoid_crossing_perimeters.travel_to(*this, point); + // // If state of use_external_mp_once was changed reset it to right value. + // if (used_external_mp_once) m_avoid_crossing_perimeters.reset_once_modifiers(); + // } + } + + // if needed, write the gcode_label_objects_end then gcode_label_objects_start + m_writer.add_object_change_labels(gcode); + + // use G1 because we rely on paths being straight (G0 may make round paths) + if (travel.size() >= 2) + { + // OrcaSlicer + if (this->on_first_layer()) + { + if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && !this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.initial_layer_jerk.value); + } + else if (m_config.default_jerk.value > 0 && m_config.travel_jerk.value > 0 && !this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.travel_jerk.value); + + if (m_spiral_vase) + { + // No lazy z lift for spiral vase mode + for (size_t i = 1; i < travel.size(); ++i) + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); + } + else + { + if (travel.size() == 2) + { + // No extra movements emitted by avoid_crossing_perimeters, simply move to the end point with z change + const auto &dest2d = this->point_to_gcode(travel.points.back()); + Vec3d dest3d(dest2d(0), dest2d(1), z == DBL_MAX ? m_nominal_z : z); + gcode += m_writer.travel_to_xyz(dest3d, comment); + } + else + { + // Extra movements emitted by avoid_crossing_perimeters, lift the z to normal height at the beginning, then apply the z + // ratio at the last point + for (size_t i = 1; i < travel.size(); ++i) + { + if (i == 1) + { + // Lift to normal z at beginning + Vec2d dest2d = this->point_to_gcode(travel.points[i]); + Vec3d dest3d(dest2d(0), dest2d(1), m_nominal_z); + gcode += m_writer.travel_to_xyz(dest3d, comment); + } + else if (z != DBL_MAX && i == travel.size() - 1) + { + // Apply z_ratio for the very last point + Vec2d dest2d = this->point_to_gcode(travel.points[i]); + Vec3d dest3d(dest2d(0), dest2d(1), z); + gcode += m_writer.travel_to_xyz(dest3d, comment); + } + else + { + // For all points in between, no z change + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); + } + } + } + } + this->set_last_pos(travel.points.back()); + } + + return gcode; + } + + void GCode::reset_last_acceleration() + { + m_writer.reset_last_acceleration(); + } + + // BBS + LiftType GCode::to_lift_type(ZHopType z_hop_types) + { + switch (z_hop_types) + { + case ZHopType::zhtNormal: + return LiftType::NormalLift; + case ZHopType::zhtSlope: + return LiftType::SlopeLift; + case ZHopType::zhtSpiral: + return LiftType::SpiralLift; + default: + // if no corresponding lift type, use normal lift + return LiftType::NormalLift; + } + }; + + bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftType &lift_type) + { + if (travel.length() < scale_(FILAMENT_CONFIG(retraction_minimum_travel))) + { + // skip retraction if the move is shorter than the configured threshold + return false; + } + + // BBS: input travel polyline must be in current plate coordinate system + auto is_through_overhang = [this](const Polyline &travel) + { + BoundingBox travel_bbox = get_extents(travel); + travel_bbox.inflated(1); + travel_bbox.defined = true; + + // do not scale for z + const float protect_z = 0.4; + std::pair z_range; + z_range.second = m_layer ? m_layer->print_z : 0.f; + z_range.first = std::max(0.f, z_range.second - protect_z); + std::vector layers_of_objects; + std::vector boundingBox_for_objects; + std::vector objects_instances_shift; + std::vector idx_of_object_sorted = m_curr_print->layers_sorted_for_object(z_range.first, z_range.second, layers_of_objects, boundingBox_for_objects, objects_instances_shift); + + std::vector is_layers_of_objects_sorted(layers_of_objects.size(), false); + + for (size_t idx : idx_of_object_sorted) + { + for (const Point &instance_shift : objects_instances_shift[idx]) + { + BoundingBox instance_bbox = boundingBox_for_objects[idx]; + if (!instance_bbox.defined) // BBS: Don't need to check when bounding box of overhang area is empty(undefined) + continue; + + instance_bbox.offset(scale_(EPSILON)); + instance_bbox.translate(instance_shift.x(), instance_shift.y()); + if (!instance_bbox.overlap(travel_bbox)) + continue; + + Polygons temp; + temp.emplace_back(std::move(instance_bbox.polygon())); + if (intersection_pl(travel, temp).empty()) + continue; + + if (!is_layers_of_objects_sorted[idx]) + { + std::sort(layers_of_objects[idx].begin(), layers_of_objects[idx].end(), [](auto left, auto right) + { return left->loverhangs_bbox.area() > right->loverhangs_bbox.area(); }); + is_layers_of_objects_sorted[idx] = true; + } + + for (const auto &layer : layers_of_objects[idx]) + { + for (ExPolygon overhang : layer->loverhangs) + { + overhang.translate(instance_shift); + BoundingBox bbox1 = get_extents(overhang); + + if (!bbox1.overlap(travel_bbox)) + continue; + + if (intersection_pl(travel, overhang).empty()) + continue; + + return true; + } + } } } + return false; + }; + + float max_z_hop = 0.f; + for (int i = 0; i < m_config.z_hop.size(); i++) + max_z_hop = std::max(max_z_hop, (float)m_config.z_hop.get_at(i)); + float travel_len_thresh = scale_(max_z_hop / tan(GCodeWriter::slope_threshold)); + float accum_len = 0.f; + Polyline clipped_travel; + + clipped_travel.append(Polyline(travel.points[0], travel.points[1])); + if (clipped_travel.length() > travel_len_thresh) + clipped_travel.points.back() = clipped_travel.points.front() + (clipped_travel.points.back() - clipped_travel.points.front()) * (travel_len_thresh / clipped_travel.length()); + // BBS: translate to current plate coordinate system + clipped_travel.translate(Point::new_scale(double(m_origin.x() - m_writer.get_xy_offset().x()), double(m_origin.y() - m_writer.get_xy_offset().y()))); + + // BBS: force to retract when leave from external perimeter for a long travel + // Better way is judging whether the travel move direction is same with last extrusion move. + if (is_perimeter(m_last_processor_extrusion_role) && m_last_processor_extrusion_role != erPerimeter) + { + if (ZHopType(FILAMENT_CONFIG(z_hop_types)) == ZHopType::zhtAuto) + { + lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::SlopeLift; + } + else + { + lift_type = to_lift_type(ZHopType(FILAMENT_CONFIG(z_hop_types))); + } + return true; + } + + if (role == erSupportMaterial || role == erSupportTransition) + { + const SupportLayer *support_layer = dynamic_cast(m_layer); + // FIXME support_layer->support_islands.contains should use some search structure! + if (support_layer != NULL) + // 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! + for (const ExPolygon &support_island : support_layer->support_islands) + if (support_island.contains(travel)) + return false; } - } - if (m_enable_cooling_markers) { - if (cooling_extrude) - gcode += ";_EXTRUDE_END\n"; + // BBS: need retract when long moving to print perimeter to avoid dropping of material + if (!is_perimeter(role) && m_config.reduce_infill_retraction && m_layer != nullptr && m_config.sparse_infill_density.value > 0 && + m_retract_when_crossing_perimeters.travel_inside_internal_regions_no_wall_crossing(*m_layer, 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; - if (FILAMENT_CONFIG(enable_overhang_bridge_fan)) { - //BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter - int overhang_threshold = FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? - Overhang_threshold_none : FILAMENT_CONFIG(overhang_fan_threshold) - 1; - if ((FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter) || (path.get_overhang_degree() > overhang_threshold || - is_bridge(path.role()))) - gcode += ";_OVERHANG_FAN_END\n"; + // retract if reduce_infill_retraction is disabled or doesn't apply when role is perimeter + if (ZHopType(FILAMENT_CONFIG(z_hop_types)) == ZHopType::zhtAuto) + { + lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::SlopeLift; + } + else + { + lift_type = to_lift_type(ZHopType(FILAMENT_CONFIG(z_hop_types))); } + return true; } - if (use_seperate_speed) { - gcode += "; Slow Down End\n"; - } + std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType lift_type, bool apply_instantly) + { + std::string gcode; - this->set_last_pos(path.last_point()); - return gcode; -} + if (m_writer.filament() == nullptr) + return gcode; -std::string encodeBase64(uint64_t value) -{ - //Always use big endian mode - uint8_t src[8]; - for (size_t i = 0; i < 8; i++) - src[i] = (value >> (8 * i)) & 0xff; - - std::string dest; - dest.resize(boost::beast::detail::base64::encoded_size(sizeof(src))); - dest.resize(boost::beast::detail::base64::encode(&dest[0], src, sizeof(src))); - return dest; -} + // wipe (if it's enabled for this extruder and we have a stored wipe path and no-zero wipe distance) + if (FILAMENT_CONFIG(wipe) && m_wipe.has_path() && scale_(FILAMENT_CONFIG(wipe_distance)) > SCALED_EPSILON) + { + gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true); + gcode += m_wipe.wipe(*this, toolchange, is_last_retraction); + } -std::string GCode::_encode_label_ids_to_base64(std::vector ids) -{ - assert(m_label_objects_ids.size() < 64); + /* The parent class will decide whether we need to perform an actual retraction + (the extruder might be already retracted fully or partially). We call these + 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(); - uint64_t bitset = 0; - for (size_t id : ids) { - auto index = std::lower_bound(m_label_objects_ids.begin(), m_label_objects_ids.end(), id); - if (index != m_label_objects_ids.end() && *index == id) - bitset |= (1ull << (index - m_label_objects_ids.begin())); - else - throw Slic3r::LogicError("Unknown label object id!"); - } - if (bitset == 0) - throw Slic3r::LogicError("Label object id error!"); + gcode += m_writer.reset_e(); + // BBS + if (m_writer.filament()->retraction_length() > 0 || m_config.use_firmware_retraction) + { + // BBS: force to use normal lift for spiral vase mode + if (apply_instantly) + gcode += m_writer.eager_lift(lift_type, toolchange); + else + gcode += m_writer.lazy_lift(lift_type, m_spiral_vase != nullptr, toolchange); + } - return encodeBase64(bitset); -} + return gcode; + } -// This method accepts &point in print coordinates. -std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment, double z ) -{ - /* 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 - LiftType lift_type = LiftType::SpiralLift; - bool needs_retraction = this->needs_retraction(travel, role, lift_type); - // check whether wipe could be disabled without causing visible stringing - bool could_be_wipe_disabled = false; - // Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to. - const bool used_external_mp_once = m_avoid_crossing_perimeters.used_external_mp_once(); - - // if a retraction would be needed, try to use reduce_crossing_wall to plan a - // multi-hop travel path inside the configuration space - // if ( - if (m_config.reduce_crossing_wall && !m_avoid_crossing_perimeters.disabled_once()) - // BBS: don't generate detour travel paths when current position is unclea + std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object) { - travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); - // check again whether the new travel path still needs a retraction - needs_retraction = this->needs_retraction(travel, role, lift_type); - //if (needs_retraction && m_layer_index > 1) exit(0); - } + int new_extruder_id = get_extruder_id(new_filament_id); + if (!m_writer.need_toolchange(new_filament_id)) + return ""; - // Re-allow reduce_crossing_wall for the next travel moves - m_avoid_crossing_perimeters.reset_once_modifiers(); - - // generate G-code for the travel move - std::string gcode; - if (needs_retraction) { - if (m_config.reduce_crossing_wall && could_be_wipe_disabled && !m_last_scarf_seam_flag) m_wipe.reset_path(); - - Point last_post_before_retract = this->last_pos(); - gcode += this->retract(false, false, lift_type); - // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. - // Because of it, it is necessary to call avoid crossing perimeters again with new starting point after calling retraction() - // FIXME Lukas H.: Try to predict if this second calling of avoid crossing perimeters will be needed or not. It could save computations. - if (last_post_before_retract != this->last_pos() && m_config.reduce_crossing_wall) { - // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call. - if (used_external_mp_once) - m_avoid_crossing_perimeters.use_external_mp_once(); - travel = m_avoid_crossing_perimeters.travel_to(*this, point); - // If state of use_external_mp_once was changed reset it to right value. - if (used_external_mp_once) - m_avoid_crossing_perimeters.reset_once_modifiers(); - } - } else { - // Reset the wipe path when traveling, so one would not wipe along an old path. - m_wipe.reset_path(); - // if (m_config.reduce_crossing_wall) { - // // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call. - // if (used_external_mp_once) m_avoid_crossing_perimeters.use_external_mp_once(); - // travel = m_avoid_crossing_perimeters.travel_to(*this, point); - // // If state of use_external_mp_once was changed reset it to right value. - // if (used_external_mp_once) m_avoid_crossing_perimeters.reset_once_modifiers(); - // } - } + // if we are running a single-extruder setup, just set the extruder and return nothing + if (!m_writer.multiple_extruders) + { + m_placeholder_parser.set("current_extruder", new_filament_id); + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(new_filament_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(new_filament_id)); + m_placeholder_parser.set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(new_filament_id)); + m_placeholder_parser.set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(new_filament_id)); + + std::string gcode; + // Append the filament start G-code. + const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(new_filament_id); + if (!filament_start_gcode.empty()) + { + // Process the filament_start_gcode for the filament. + DynamicConfig config; + if (m_layer_index >= 0) + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + else + config.set_key_value("layer_num", new ConfigOptionInt(0)); + gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); + check_add_eol(gcode); + } + // BBS: never use for Bambu Printer + if (!this->is_BBL_Printer() && m_config.enable_pressure_advance.get_at(new_filament_id)) + gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id)); - // if needed, write the gcode_label_objects_end then gcode_label_objects_start - m_writer.add_object_change_labels(gcode); + gcode += m_writer.toolchange(new_filament_id); + return gcode; + } - // use G1 because we rely on paths being straight (G0 may make round paths) - if (travel.size() >= 2) { - // OrcaSlicer - if (this->on_first_layer()) { - if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && !this->is_BBL_Printer()) - gcode += m_writer.set_jerk_xy(m_config.initial_layer_jerk.value); - } else if (m_config.default_jerk.value > 0 && m_config.travel_jerk.value > 0 && !this->is_BBL_Printer()) - gcode += m_writer.set_jerk_xy(m_config.travel_jerk.value); + // BBS. Should be placed before retract. + m_toolchange_count++; - if (m_spiral_vase) { - // No lazy z lift for spiral vase mode - for (size_t i = 1; i < travel.size(); ++i) - gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); - } else { - if (travel.size() == 2) { - // No extra movements emitted by avoid_crossing_perimeters, simply move to the end point with z change - const auto &dest2d = this->point_to_gcode(travel.points.back()); - Vec3d dest3d(dest2d(0), dest2d(1), z == DBL_MAX ? m_nominal_z : z); - gcode += m_writer.travel_to_xyz(dest3d, comment); - } else { - // Extra movements emitted by avoid_crossing_perimeters, lift the z to normal height at the beginning, then apply the z - // ratio at the last point - for (size_t i = 1; i < travel.size(); ++i) { - if (i == 1) { - // Lift to normal z at beginning - Vec2d dest2d = this->point_to_gcode(travel.points[i]); - Vec3d dest3d(dest2d(0), dest2d(1), m_nominal_z); - gcode += m_writer.travel_to_xyz(dest3d, comment); - } else if (z != DBL_MAX && i == travel.size() - 1) { - // Apply z_ratio for the very last point - Vec2d dest2d = this->point_to_gcode(travel.points[i]); - Vec3d dest3d(dest2d(0), dest2d(1), z); - gcode += m_writer.travel_to_xyz(dest3d, comment); - } else { - // For all points in between, no z change - gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment ); - } + // prepend retraction on the current extruder + std::string gcode = this->retract(true, false); + + // Always reset the extrusion path, even if the tool change retract is set to zero. + m_wipe.reset_path(); + + // BBS: insert skip object label before change filament while by object + if (by_object) + m_writer.add_object_change_labels(gcode); + else + m_writer.add_object_end_labels(gcode); + + bool add_change_filament_624 = false; + if (m_writer.filament() != nullptr) + { + // Process the custom filament_end_gcode. set_extruder() is only called if there is no wipe tower + // so it should not be injected twice. + unsigned int old_filament_id = m_writer.filament()->id(); + const std::string &filament_end_gcode = m_config.filament_end_gcode.get_at(old_filament_id); + if (!filament_end_gcode.empty()) + { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + if (!m_filament_instances_code.empty()) + { + gcode += ("M624 " + m_filament_instances_code + "\n"); + gcode += placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_filament_id, &config); + m_filament_instances_code = ""; + add_change_filament_624 = true; } + check_add_eol(gcode); } } - this->set_last_pos(travel.points.back()); - } - - return gcode; -} - -void GCode::reset_last_acceleration() -{ - m_writer.reset_last_acceleration(); -} -//BBS -LiftType GCode::to_lift_type(ZHopType z_hop_types) { - switch (z_hop_types) - { - case ZHopType::zhtNormal: - return LiftType::NormalLift; - case ZHopType::zhtSlope: - return LiftType::SlopeLift; - case ZHopType::zhtSpiral: - return LiftType::SpiralLift; - default: - // if no corresponding lift type, use normal lift - return LiftType::NormalLift; - } -}; + // If ooze prevention is enabled, park current extruder in the nearest + // standby point and set it to the standby temperature. + if (m_ooze_prevention.enable && m_writer.filament() != nullptr) + gcode += m_ooze_prevention.pre_toolchange(*this); -bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftType& lift_type) -{ - if (travel.length() < scale_(FILAMENT_CONFIG(retraction_minimum_travel))) { - // skip retraction if the move is shorter than the configured threshold - return false; - } + // BBS + float new_retract_length = m_config.retraction_length.get_at(new_filament_id); + float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(new_filament_id); + float new_extruder_retracted_length = m_writer.get_extruder_retracted_length(new_filament_id); + int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : m_config.nozzle_temperature.get_at(new_filament_id); + // BBS: if print_z == 0 use first layer temperature + if (abs(print_z) < EPSILON) + new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(new_filament_id); + + Vec3d nozzle_pos = m_writer.get_position(); + float old_retract_length, old_retract_length_toolchange, old_filament_retract_length_nc, wipe_volume; + int old_filament_temp, old_filament_e_feedrate; + + float filament_area = float((M_PI / 4.f) * pow(m_config.filament_diameter.get_at(new_filament_id), 2)); + // BBS: add handling for filament change in start gcode + int old_filament_id = -1; + if (m_writer.filament() != nullptr || m_start_gcode_filament != -1) + { + std::vector flush_matrix(cast(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, new_extruder_id, m_config.nozzle_diameter.values.size()))); + const unsigned int number_of_extruders = (unsigned int)(m_config.filament_colour.values.size()); // if is multi_extruder only use the fist extruder matrix + if (m_writer.filament() != nullptr) + assert(m_writer.filament()->id() < number_of_extruders); + else + assert(m_start_gcode_filament < number_of_extruders); - //BBS: input travel polyline must be in current plate coordinate system - auto is_through_overhang = [this](const Polyline& travel) { - BoundingBox travel_bbox = get_extents(travel); - travel_bbox.inflated(1); - travel_bbox.defined = true; - - // do not scale for z - const float protect_z = 0.4; - std::pair z_range; - z_range.second = m_layer ? m_layer->print_z : 0.f; - z_range.first = std::max(0.f, z_range.second - protect_z); - std::vector layers_of_objects; - std::vector boundingBox_for_objects; - std::vector objects_instances_shift; - std::vector idx_of_object_sorted = m_curr_print->layers_sorted_for_object(z_range.first, z_range.second, layers_of_objects, boundingBox_for_objects, objects_instances_shift); - - std::vector is_layers_of_objects_sorted(layers_of_objects.size(), false); - - for (size_t idx : idx_of_object_sorted) { - for (const Point & instance_shift : objects_instances_shift[idx]) { - BoundingBox instance_bbox = boundingBox_for_objects[idx]; - if (!instance_bbox.defined) //BBS: Don't need to check when bounding box of overhang area is empty(undefined) - continue; + old_filament_id = m_writer.filament() != nullptr ? m_writer.filament()->id() : m_start_gcode_filament; + int old_extruder_id = m_writer.filament() != nullptr ? m_writer.filament()->extruder_id() : get_extruder_id(m_start_gcode_filament); - instance_bbox.offset(scale_(EPSILON)); - instance_bbox.translate(instance_shift.x(), instance_shift.y()); - if (!instance_bbox.overlap(travel_bbox)) - continue; + old_retract_length = m_config.retraction_length.get_at(old_filament_id); + old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(old_filament_id); + old_filament_retract_length_nc = m_config.filament_retract_length_nc.get_at(old_filament_id); + old_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(old_filament_id) : m_config.nozzle_temperature.get_at(old_filament_id); - Polygons temp; - temp.emplace_back(std::move(instance_bbox.polygon())); - if (intersection_pl(travel, temp).empty()) - continue; + // During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length. + float grab_purge_volume = m_config.grab_length.get_at(new_extruder_id) * 2.4; - if (!is_layers_of_objects_sorted[idx]) { - std::sort(layers_of_objects[idx].begin(), layers_of_objects[idx].end(), [](auto left, auto right) { return left->loverhangs_bbox.area() > right->loverhangs_bbox.area();}); - is_layers_of_objects_sorted[idx] = true; + auto switch_to_nozzle = [&](int filament_id, int nozzle_id) + { + float wipe_volume = 0; + if (m_processor.get_nozzle_status().is_nozzle_empty(nozzle_id)) + wipe_volume = 0; + else + { + int old_filament_id_in_nozzle = m_processor.get_nozzle_status().get_filament_in_nozzle(nozzle_id); + wipe_volume = flush_matrix[old_filament_id_in_nozzle * number_of_extruders + new_filament_id]; + wipe_volume *= m_config.flush_multiplier.get_at(new_extruder_id); } + return wipe_volume; + }; - for (const auto& layer : layers_of_objects[idx]) { - for (ExPolygon overhang : layer->loverhangs) { - overhang.translate(instance_shift); - BoundingBox bbox1 = get_extents(overhang); - - if (!bbox1.overlap(travel_bbox)) - continue; + assert(m_print->get_nozzle_group_result().has_value()); - if (intersection_pl(travel, overhang).empty()) - continue; + int new_nozzle_id = m_print->get_nozzle_group_result()->get_nozzle_for_filament(new_filament_id)->group_id; - return true; - } - } + if (old_extruder_id != new_extruder_id || !m_print->get_nozzle_group_result()->are_filaments_same_nozzle(old_filament_id, new_filament_id)) + { + wipe_volume = switch_to_nozzle(new_filament_id, new_nozzle_id); } - } - return false; - }; + else + { + wipe_volume = flush_matrix[old_filament_id * number_of_extruders + new_filament_id]; + wipe_volume *= m_config.flush_multiplier.get_at(new_extruder_id); // if is multi_extruder only use the fist extruder matrix + } + wipe_volume = std::max(0.f, wipe_volume - grab_purge_volume); - float max_z_hop = 0.f; - for (int i = 0; i < m_config.z_hop.size(); i++) - max_z_hop = std::max(max_z_hop, (float)m_config.z_hop.get_at(i)); - float travel_len_thresh = scale_(max_z_hop / tan(GCodeWriter::slope_threshold)); - float accum_len = 0.f; - Polyline clipped_travel; - - clipped_travel.append(Polyline(travel.points[0], travel.points[1])); - if (clipped_travel.length() > travel_len_thresh) - clipped_travel.points.back() = clipped_travel.points.front()+(clipped_travel.points.back() - clipped_travel.points.front()) * (travel_len_thresh / clipped_travel.length()); - //BBS: translate to current plate coordinate system - clipped_travel.translate(Point::new_scale(double(m_origin.x() - m_writer.get_xy_offset().x()), double(m_origin.y() - m_writer.get_xy_offset().y()))); - - //BBS: force to retract when leave from external perimeter for a long travel - //Better way is judging whether the travel move direction is same with last extrusion move. - if (is_perimeter(m_last_processor_extrusion_role) && m_last_processor_extrusion_role != erPerimeter) { - if (ZHopType(FILAMENT_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { - lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::SlopeLift; + old_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(old_filament_id) / filament_area); + old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; + // BBS: must clean m_start_gcode_filament + m_start_gcode_filament = -1; } - else { - lift_type = to_lift_type(ZHopType(FILAMENT_CONFIG(z_hop_types))); + else + { + old_retract_length = 0.f; + old_retract_length_toolchange = 0.f; + old_filament_retract_length_nc = 0.f; + old_filament_temp = 0; + wipe_volume = 0.f; + old_filament_e_feedrate = 200; + } + float wipe_length = wipe_volume / filament_area; + int new_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(new_filament_id) / filament_area); + new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; + + // set volumetric speed of outer wall ,ignore per obejct,just use default setting + float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, *m_print, new_filament_id, get_extruder_id(new_filament_id)); + float wipe_avoid_pos_x = 110.f; + DynamicConfig dyn_config; + dyn_config.set_key_value("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); + dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id)); + dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int)new_filament_id)); + dyn_config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + dyn_config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + dyn_config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + dyn_config.set_key_value("relative_e_axis", new ConfigOptionBool(m_config.use_relative_e_distances)); + dyn_config.set_key_value("toolchange_count", new ConfigOptionInt((int)m_toolchange_count)); + // BBS: fan speed is useless placeholer now, but we don't remove it to avoid + // slicing error in old change_filament_gcode in old 3MF + dyn_config.set_key_value("fan_speed", new ConfigOptionInt((int)0)); + dyn_config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); + dyn_config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); + dyn_config.set_key_value("filament_retract_length_nc", new ConfigOptionFloat(old_filament_retract_length_nc)); + dyn_config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); + dyn_config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); + dyn_config.set_key_value("new_extruder_retracted_length", new ConfigOptionFloat(new_extruder_retracted_length)); + dyn_config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); + dyn_config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); + dyn_config.set_key_value("x_after_toolchange", new ConfigOptionFloat(nozzle_pos(0))); + dyn_config.set_key_value("y_after_toolchange", new ConfigOptionFloat(nozzle_pos(1))); + dyn_config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2))); + dyn_config.set_key_value("first_flush_volume", new ConfigOptionFloat(wipe_length / 2.f)); + dyn_config.set_key_value("second_flush_volume", new ConfigOptionFloat(wipe_length / 2.f)); + dyn_config.set_key_value("old_filament_e_feedrate", new ConfigOptionInt(old_filament_e_feedrate)); + dyn_config.set_key_value("new_filament_e_feedrate", new ConfigOptionInt(new_filament_e_feedrate)); + dyn_config.set_key_value("travel_point_1_x", new ConfigOptionFloat(float(travel_point_1.x()))); + dyn_config.set_key_value("travel_point_1_y", new ConfigOptionFloat(float(travel_point_1.y()))); + dyn_config.set_key_value("travel_point_2_x", new ConfigOptionFloat(float(travel_point_2.x()))); + dyn_config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y()))); + dyn_config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x()))); + dyn_config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); + dyn_config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(false)); + dyn_config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); + dyn_config.set_key_value("wipe_tower_center_pos_x", new ConfigOptionFloat(0.f)); + dyn_config.set_key_value("wipe_tower_center_pos_y", new ConfigOptionFloat(0.f)); + dyn_config.set_key_value("wipe_tower_center_pos_valid", new ConfigOptionBool(false)); + + auto flush_v_speed = m_print->config().filament_flush_volumetric_speed.values; + auto flush_temps = m_print->config().filament_flush_temp.values; + for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) + { + if (flush_v_speed[idx] == 0) + flush_v_speed[idx] = m_print->config().filament_max_volumetric_speed.get_at(idx); + } + for (size_t idx = 0; idx < flush_temps.size(); ++idx) + { + if (flush_temps[idx] == 0) + flush_temps[idx] = m_print->config().nozzle_temperature_range_high.get_at(idx); + } + dyn_config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed)); + dyn_config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps)); + dyn_config.set_key_value("flush_length", new ConfigOptionFloat(wipe_length)); + + int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time)); + // handle cases for very small purge + if (flush_count == 0 && wipe_volume > 0) + flush_count += 1; + float flush_unit = wipe_length / flush_count; + int flush_idx = 0; + for (; flush_idx < flush_count; flush_idx++) + { + char key_value[64] = {0}; + snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); + dyn_config.set_key_value(key_value, new ConfigOptionFloat(flush_unit)); } - return true; - } - if (role == erSupportMaterial || role == erSupportTransition) { - const SupportLayer* support_layer = dynamic_cast(m_layer); - //FIXME support_layer->support_islands.contains should use some search structure! - if (support_layer != NULL) - // 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! - for (const ExPolygon& support_island : support_layer->support_islands) - if (support_island.contains(travel)) - return false; - } + for (; flush_idx < g_max_flush_count; flush_idx++) + { + char key_value[64] = {0}; + snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); + dyn_config.set_key_value(key_value, new ConfigOptionFloat(0.f)); + } - //BBS: need retract when long moving to print perimeter to avoid dropping of material - if (!is_perimeter(role) && m_config.reduce_infill_retraction && m_layer != nullptr && m_config.sparse_infill_density.value > 0 && - m_retract_when_crossing_perimeters.travel_inside_internal_regions_no_wall_crossing(*m_layer, 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; + // Process the custom change_filament_gcode. + std::string change_filament_gcode = m_config.change_filament_gcode.value; - // retract if reduce_infill_retraction is disabled or doesn't apply when role is perimeter - if (ZHopType(FILAMENT_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { - lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::SlopeLift; - } - else { - lift_type = to_lift_type(ZHopType(FILAMENT_CONFIG(z_hop_types))); - } - return true; -} + // Move the lift gcode here which is in the change_filament_gcode originally + change_filament_gcode = this->retract(false, false, LiftType::SpiralLift, true) + change_filament_gcode; -std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType lift_type,bool apply_instantly) -{ - std::string gcode; + std::string toolchange_gcode_parsed; + if (!change_filament_gcode.empty()) + { + toolchange_gcode_parsed = placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_filament_id, &dyn_config); + check_add_eol(toolchange_gcode_parsed); + gcode += toolchange_gcode_parsed; - if (m_writer.filament() == nullptr) - return gcode; + // BBS + { + // BBS: gcode writer doesn't know where the extruder is and whether fan speed is changed after inserting tool change gcode + // Set this flag so that normal lift will be used the first time after tool change. + gcode += ";_FORCE_RESUME_FAN_SPEED\n"; + m_writer.set_current_position_clear(false); + // BBS: check whether custom gcode changes the z position. Update if changed + double temp_z_after_tool_change; + if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_parsed, temp_z_after_tool_change)) + { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_tool_change; + m_writer.set_position(pos); + } + } + } - // wipe (if it's enabled for this extruder and we have a stored wipe path and no-zero wipe distance) - if (FILAMENT_CONFIG(wipe) && m_wipe.has_path() && scale_(FILAMENT_CONFIG(wipe_distance)) > SCALED_EPSILON) { - gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true); - gcode += m_wipe.wipe(*this, toolchange, is_last_retraction); - } + // BBS. Reset old extruder E-value. + // Keep retract length because Custom GCode will guarantee retract length be the same as toolchange + if (m_config.single_extruder_multi_material) + { + m_writer.reset_e(); + } - /* The parent class will decide whether we need to perform an actual retraction - (the extruder might be already retracted fully or partially). We call these - 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(); - - gcode += m_writer.reset_e(); - //BBS - if (m_writer.filament()->retraction_length() > 0 || m_config.use_firmware_retraction) { - // BBS: force to use normal lift for spiral vase mode - if (apply_instantly) - gcode += m_writer.eager_lift(lift_type,toolchange); + // BBS: don't add T[next extruder] if there is no T cmd on filament change + // We inform the writer about what is happening, but we may not use the resulting gcode. + std::string toolchange_command = m_writer.toolchange(new_filament_id); + if (!custom_gcode_changes_tool(toolchange_gcode_parsed, m_writer.toolchange_prefix(), new_filament_id)) + gcode += toolchange_command; else - gcode += m_writer.lazy_lift(lift_type, m_spiral_vase != nullptr, toolchange); - - } + { + // user provided his own toolchange gcode, no need to do anything + } - return gcode; -} + // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM) + if (m_config.single_extruder_multi_material && !m_config.enable_prime_tower) + { + int temp = (m_layer_index <= 0 ? m_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : m_config.nozzle_temperature.get_at(new_filament_id)); -std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object) -{ - int new_extruder_id = get_extruder_id(new_filament_id); - if (!m_writer.need_toolchange(new_filament_id)) - return ""; + gcode += m_writer.set_temperature(temp, false); + } - // if we are running a single-extruder setup, just set the extruder and return nothing - if (!m_writer.multiple_extruders) { m_placeholder_parser.set("current_extruder", new_filament_id); m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(new_filament_id)); m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(new_filament_id)); m_placeholder_parser.set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(new_filament_id)); m_placeholder_parser.set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(new_filament_id)); - std::string gcode; // Append the filament start G-code. const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(new_filament_id); - if (! filament_start_gcode.empty()) { - // Process the filament_start_gcode for the filament. + if (!filament_start_gcode.empty()) + { + // Process the filament_start_gcode for the new filament. + // in machine start gcode, m_layer_index is -1 set to 0 DynamicConfig config; if (m_layer_index >= 0) config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); @@ -6596,441 +7560,189 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo config.set_key_value("layer_num", new ConfigOptionInt(0)); gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); + if (add_change_filament_624) + { + gcode += "M625\n"; + add_change_filament_624 = false; + } check_add_eol(gcode); } - //BBS: never use for Bambu Printer + // Set the new extruder to the operating temperature. + if (m_ooze_prevention.enable) + gcode += m_ooze_prevention.post_toolchange(*this); + // BBS: never use for Bambu Printer if (!this->is_BBL_Printer() && m_config.enable_pressure_advance.get_at(new_filament_id)) gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id)); - gcode += m_writer.toolchange(new_filament_id); return gcode; } - // BBS. Should be placed before retract. - m_toolchange_count++; - - // prepend retraction on the current extruder - std::string gcode = this->retract(true, false); - - // Always reset the extrusion path, even if the tool change retract is set to zero. - m_wipe.reset_path(); - - // BBS: insert skip object label before change filament while by object - if (by_object) - m_writer.add_object_change_labels(gcode); - else - m_writer.add_object_end_labels(gcode); - - bool add_change_filament_624 = false; - if (m_writer.filament() != nullptr) { - // Process the custom filament_end_gcode. set_extruder() is only called if there is no wipe tower - // so it should not be injected twice. - unsigned int old_filament_id = m_writer.filament()->id(); - const std::string &filament_end_gcode = m_config.filament_end_gcode.get_at(old_filament_id); - if (! filament_end_gcode.empty()) { - DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - if (!m_filament_instances_code.empty()) { - gcode += ("M624 " + m_filament_instances_code + "\n"); - gcode += placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_filament_id, &config); - m_filament_instances_code = ""; - add_change_filament_624 = true; - } - check_add_eol(gcode); - } - } - - - // If ooze prevention is enabled, park current extruder in the nearest - // standby point and set it to the standby temperature. - if (m_ooze_prevention.enable && m_writer.filament() != nullptr) - gcode += m_ooze_prevention.pre_toolchange(*this); - - // BBS - float new_retract_length = m_config.retraction_length.get_at(new_filament_id); - float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(new_filament_id); - float new_extruder_retracted_length = m_writer.get_extruder_retracted_length(new_filament_id); - int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : m_config.nozzle_temperature.get_at(new_filament_id); - // BBS: if print_z == 0 use first layer temperature - if (abs(print_z) < EPSILON) - new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(new_filament_id); - - Vec3d nozzle_pos = m_writer.get_position(); - float old_retract_length, old_retract_length_toolchange, old_filament_retract_length_nc, wipe_volume; - int old_filament_temp, old_filament_e_feedrate; - - float filament_area = float((M_PI / 4.f) * pow(m_config.filament_diameter.get_at(new_filament_id), 2)); - //BBS: add handling for filament change in start gcode - int old_filament_id = -1; - if (m_writer.filament() != nullptr || m_start_gcode_filament != -1) { - std::vector flush_matrix(cast(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, new_extruder_id, m_config.nozzle_diameter.values.size()))); - const unsigned int number_of_extruders = (unsigned int) (m_config.filament_colour.values.size()); // if is multi_extruder only use the fist extruder matrix - if (m_writer.filament() != nullptr) - assert(m_writer.filament()->id() < number_of_extruders); - else - assert(m_start_gcode_filament < number_of_extruders); - - old_filament_id = m_writer.filament() != nullptr ? m_writer.filament()->id() : m_start_gcode_filament; - int old_extruder_id = m_writer.filament() != nullptr ? m_writer.filament()->extruder_id() : get_extruder_id(m_start_gcode_filament); - - old_retract_length = m_config.retraction_length.get_at(old_filament_id); - old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(old_filament_id); - old_filament_retract_length_nc = m_config.filament_retract_length_nc.get_at(old_filament_id); - old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(old_filament_id) : m_config.nozzle_temperature.get_at(old_filament_id); - - //During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length. - float grab_purge_volume = m_config.grab_length.get_at(new_extruder_id) * 2.4; - - auto switch_to_nozzle = [&](int filament_id, int nozzle_id){ - float wipe_volume = 0; - if(m_processor.get_nozzle_status().is_nozzle_empty(nozzle_id)) - wipe_volume = 0; - else { - int old_filament_id_in_nozzle = m_processor.get_nozzle_status().get_filament_in_nozzle(nozzle_id); - wipe_volume = flush_matrix[old_filament_id_in_nozzle * number_of_extruders + new_filament_id]; - wipe_volume *= m_config.flush_multiplier.get_at(new_extruder_id); - } - return wipe_volume; - }; - - assert(m_print->get_nozzle_group_result().has_value()); - - int new_nozzle_id = m_print->get_nozzle_group_result()->get_nozzle_for_filament(new_filament_id)->group_id; - - if (old_extruder_id != new_extruder_id || !m_print->get_nozzle_group_result()->are_filaments_same_nozzle(old_filament_id,new_filament_id)) { - wipe_volume = switch_to_nozzle(new_filament_id, new_nozzle_id); - } - else { - wipe_volume = flush_matrix[old_filament_id * number_of_extruders + new_filament_id]; - wipe_volume *= m_config.flush_multiplier.get_at(new_extruder_id); // if is multi_extruder only use the fist extruder matrix + inline std::string polygon_to_string(const Polygon &polygon, Print *print) + { + std::ostringstream gcode; + gcode << "["; + for (const Point &p : polygon.points) + { + const auto v = print->translate_to_print_space(p); + gcode << "[" << v.x() << "," << v.y() << "],"; } - wipe_volume = std::max(0.f, wipe_volume-grab_purge_volume); - - old_filament_e_feedrate = (int) (60.0 * m_config.filament_max_volumetric_speed.get_at(old_filament_id) / filament_area); - old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; - //BBS: must clean m_start_gcode_filament - m_start_gcode_filament = -1; - } else { - old_retract_length = 0.f; - old_retract_length_toolchange = 0.f; - old_filament_retract_length_nc = 0.f; - old_filament_temp = 0; - wipe_volume = 0.f; - old_filament_e_feedrate = 200; - } - float wipe_length = wipe_volume / filament_area; - int new_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(new_filament_id) / filament_area); - new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; - - // set volumetric speed of outer wall ,ignore per obejct,just use default setting - float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, *m_print, new_filament_id, get_extruder_id(new_filament_id)); - float wipe_avoid_pos_x = 110.f; - DynamicConfig dyn_config; - dyn_config.set_key_value("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); - dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id)); - dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int)new_filament_id)); - dyn_config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - dyn_config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - dyn_config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - dyn_config.set_key_value("relative_e_axis", new ConfigOptionBool(m_config.use_relative_e_distances)); - dyn_config.set_key_value("toolchange_count", new ConfigOptionInt((int)m_toolchange_count)); - //BBS: fan speed is useless placeholer now, but we don't remove it to avoid - //slicing error in old change_filament_gcode in old 3MF - dyn_config.set_key_value("fan_speed", new ConfigOptionInt((int)0)); - dyn_config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); - dyn_config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); - dyn_config.set_key_value("filament_retract_length_nc", new ConfigOptionFloat(old_filament_retract_length_nc)); - dyn_config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); - dyn_config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); - dyn_config.set_key_value("new_extruder_retracted_length", new ConfigOptionFloat(new_extruder_retracted_length)); - dyn_config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); - dyn_config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); - dyn_config.set_key_value("x_after_toolchange", new ConfigOptionFloat(nozzle_pos(0))); - dyn_config.set_key_value("y_after_toolchange", new ConfigOptionFloat(nozzle_pos(1))); - dyn_config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2))); - dyn_config.set_key_value("first_flush_volume", new ConfigOptionFloat(wipe_length / 2.f)); - dyn_config.set_key_value("second_flush_volume", new ConfigOptionFloat(wipe_length / 2.f)); - dyn_config.set_key_value("old_filament_e_feedrate", new ConfigOptionInt(old_filament_e_feedrate)); - dyn_config.set_key_value("new_filament_e_feedrate", new ConfigOptionInt(new_filament_e_feedrate)); - dyn_config.set_key_value("travel_point_1_x", new ConfigOptionFloat(float(travel_point_1.x()))); - dyn_config.set_key_value("travel_point_1_y", new ConfigOptionFloat(float(travel_point_1.y()))); - dyn_config.set_key_value("travel_point_2_x", new ConfigOptionFloat(float(travel_point_2.x()))); - dyn_config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y()))); - dyn_config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x()))); - dyn_config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); - dyn_config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(false)); - dyn_config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); - dyn_config.set_key_value("wipe_tower_center_pos_x", new ConfigOptionFloat(0.f)); - dyn_config.set_key_value("wipe_tower_center_pos_y", new ConfigOptionFloat(0.f)); - dyn_config.set_key_value("wipe_tower_center_pos_valid", new ConfigOptionBool(false)); - - auto flush_v_speed = m_print->config().filament_flush_volumetric_speed.values; - auto flush_temps =m_print->config().filament_flush_temp.values; - for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) { - if (flush_v_speed[idx] == 0) - flush_v_speed[idx] = m_print->config().filament_max_volumetric_speed.get_at(idx); - } - for (size_t idx = 0; idx < flush_temps.size(); ++idx) { - if (flush_temps[idx] == 0) - flush_temps[idx] = m_print->config().nozzle_temperature_range_high.get_at(idx); - } - dyn_config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed)); - dyn_config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps)); - dyn_config.set_key_value("flush_length", new ConfigOptionFloat(wipe_length)); - - int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time)); - // handle cases for very small purge - if (flush_count == 0 && wipe_volume > 0) - flush_count += 1; - float flush_unit = wipe_length / flush_count; - int flush_idx = 0; - for (; flush_idx < flush_count; flush_idx++) { - char key_value[64] = { 0 }; - snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); - dyn_config.set_key_value(key_value, new ConfigOptionFloat(flush_unit)); - } - - for (; flush_idx < g_max_flush_count; flush_idx++) { - char key_value[64] = { 0 }; - snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); - dyn_config.set_key_value(key_value, new ConfigOptionFloat(0.f)); + const auto first_v = print->translate_to_print_space(polygon.points.front()); + gcode << "[" << first_v.x() << "," << first_v.y() << "]"; + gcode << "]"; + return gcode.str(); } - - // Process the custom change_filament_gcode. - std::string change_filament_gcode = m_config.change_filament_gcode.value; - - // Move the lift gcode here which is in the change_filament_gcode originally - change_filament_gcode = this->retract(false, false, LiftType::SpiralLift, true) + change_filament_gcode; - - std::string toolchange_gcode_parsed; - if (!change_filament_gcode.empty()) { - toolchange_gcode_parsed = placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_filament_id, &dyn_config); - check_add_eol(toolchange_gcode_parsed); - gcode += toolchange_gcode_parsed; - - //BBS + // this function iterator PrintObject and assign a seqential id to each object. + // this id is used to generate unique object id for each object. + std::string GCode::set_object_info(Print *print) + { + std::ostringstream gcode; + size_t object_id = 0; + for (PrintObject *object : print->objects()) { - //BBS: gcode writer doesn't know where the extruder is and whether fan speed is changed after inserting tool change gcode - //Set this flag so that normal lift will be used the first time after tool change. - gcode += ";_FORCE_RESUME_FAN_SPEED\n"; - m_writer.set_current_position_clear(false); - //BBS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_tool_change; - if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_parsed, temp_z_after_tool_change)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_tool_change; - m_writer.set_position(pos); + object->set_klipper_object_id(object_id++); + size_t inst_id = 0; + for (PrintInstance &inst : object->instances()) + { + inst.id = inst_id++; + if (this->config().exclude_object && print->config().gcode_flavor.value == gcfKlipper) + { + auto bbox = inst.get_bounding_box(); + auto center = print->translate_to_print_space(Vec2d(bbox.center().x(), bbox.center().y())); + gcode << "EXCLUDE_OBJECT_DEFINE NAME=" << get_instance_name(object, inst) << " CENTER=" << center.x() + << "," << center.y() << " POLYGON=" << polygon_to_string(inst.get_convex_hull_2d(), print) + << "\n"; + } } } + return gcode.str(); } - // BBS. Reset old extruder E-value. - // Keep retract length because Custom GCode will guarantee retract length be the same as toolchange - if (m_config.single_extruder_multi_material) { - m_writer.reset_e(); + // convert a model-space scaled point into G-code coordinates + Vec2d GCode::point_to_gcode(const Point &point) const + { + Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); + return unscale(point) + m_origin - extruder_offset; } - //BBS: don't add T[next extruder] if there is no T cmd on filament change - //We inform the writer about what is happening, but we may not use the resulting gcode. - std::string toolchange_command = m_writer.toolchange(new_filament_id); - if (!custom_gcode_changes_tool(toolchange_gcode_parsed, m_writer.toolchange_prefix(), new_filament_id)) - gcode += toolchange_command; - else { - // user provided his own toolchange gcode, no need to do anything + // convert a model-space scaled point into G-code coordinates + Point GCode::gcode_to_point(const Vec2d &point) const + { + Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); + return Point( + scale_(point(0) - m_origin(0) + extruder_offset(0)), + scale_(point(1) - m_origin(1) + extruder_offset(1))); } - // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM) - if (m_config.single_extruder_multi_material && !m_config.enable_prime_tower) { - int temp = (m_layer_index <= 0 ? m_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : - m_config.nozzle_temperature.get_at(new_filament_id)); + // Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed + // during infill/perimeter wiping, or normally (depends on wiping_entities parameter) + // Fills in by_region_per_copy_cache and returns its reference. + const std::vector &GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const + { + bool has_overrides = false; + for (const auto ® : by_region) + if (!reg.infills_overrides.empty() || !reg.perimeters_overrides.empty()) + { + has_overrides = true; + break; + } - gcode += m_writer.set_temperature(temp, false); - } + // Data is cleared, but the memory is not. + by_region_per_copy_cache.clear(); - m_placeholder_parser.set("current_extruder", new_filament_id); - m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(new_filament_id)); - m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(new_filament_id)); - m_placeholder_parser.set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(new_filament_id)); - m_placeholder_parser.set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(new_filament_id)); + if (!has_overrides) + // Simple case. No need to copy the regions. + return wiping_entities ? by_region_per_copy_cache : this->by_region; + // Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions. + // Some of the extrusions of some object instances are printed later - those are the clean print extrusions. + // Filter out the extrusions based on the infill_overrides / perimeter_overrides: - // Append the filament start G-code. - const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(new_filament_id); - if (! filament_start_gcode.empty()) { - // Process the filament_start_gcode for the new filament. - //in machine start gcode, m_layer_index is -1 set to 0 - DynamicConfig config; - if (m_layer_index >= 0) - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - else - config.set_key_value("layer_num", new ConfigOptionInt(0)); + for (const auto ® : by_region) + { + by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island - gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); - if (add_change_filament_624) { - gcode += "M625\n"; - add_change_filament_624 = false; - } - check_add_eol(gcode); - } - // Set the new extruder to the operating temperature. - if (m_ooze_prevention.enable) - gcode += m_ooze_prevention.post_toolchange(*this); - //BBS: never use for Bambu Printer - if (!this->is_BBL_Printer() && m_config.enable_pressure_advance.get_at(new_filament_id)) - gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id)); - - return gcode; -} + // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed + // References are used so that we don't have to repeat the same code + for (int iter = 0; iter < 2; ++iter) + { + const ExtrusionEntitiesPtr &entities = (iter ? reg.infills : reg.perimeters); + ExtrusionEntitiesPtr &target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); + const std::vector &overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides); -inline std::string polygon_to_string(const Polygon& polygon, Print* print) { - std::ostringstream gcode; - gcode << "["; - for (const Point& p : polygon.points) { - const auto v = print->translate_to_print_space(p); - gcode << "[" << v.x() << "," << v.y() << "],"; - } - const auto first_v = print->translate_to_print_space(polygon.points.front()); - gcode << "[" << first_v.x() << "," << first_v.y() << "]"; - gcode << "]"; - return gcode.str(); -} -// this function iterator PrintObject and assign a seqential id to each object. -// this id is used to generate unique object id for each object. -std::string GCode::set_object_info(Print* print) -{ - std::ostringstream gcode; - size_t object_id = 0; - for (PrintObject* object : print->objects()) { - object->set_klipper_object_id(object_id++); - size_t inst_id = 0; - for (PrintInstance& inst : object->instances()) { - inst.id = inst_id++; - if (this->config().exclude_object && print->config().gcode_flavor.value == gcfKlipper) { - auto bbox = inst.get_bounding_box(); - auto center = print->translate_to_print_space(Vec2d(bbox.center().x(), bbox.center().y())); - gcode << "EXCLUDE_OBJECT_DEFINE NAME=" << get_instance_name(object, inst) << " CENTER=" << center.x() - << "," << center.y() << " POLYGON=" << polygon_to_string(inst.get_convex_hull_2d(), print) - << "\n"; + // Now the most important thing - which extrusion should we print. + // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack. + if (wiping_entities) + { + // Apply overrides for this region. + for (unsigned int i = 0; i < overrides.size(); ++i) + { + const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + // This copy (aka object instance) should be printed with this extruder, which overrides the default one. + if (this_override != nullptr && (*this_override)[copy] == int(extruder)) + target_eec.emplace_back(entities[i]); + } + } + else + { + // Apply normal extrusions (non-overrides) for this region. + unsigned int i = 0; + for (; i < overrides.size(); ++i) + { + const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. + if (this_override == nullptr || (*this_override)[copy] == -int(extruder) - 1) + target_eec.emplace_back(entities[i]); + } + for (; i < entities.size(); ++i) + target_eec.emplace_back(entities[i]); + } } } + return by_region_per_copy_cache; } - return gcode.str(); -} - -// convert a model-space scaled point into G-code coordinates -Vec2d GCode::point_to_gcode(const Point &point) const -{ - Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); - return unscale(point) + m_origin - extruder_offset; -} -// convert a model-space scaled point into G-code coordinates -Point GCode::gcode_to_point(const Vec2d &point) const -{ - Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); - return Point( - scale_(point(0) - m_origin(0) + extruder_offset(0)), - scale_(point(1) - m_origin(1) + extruder_offset(1))); -} + // This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter) + // It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy. + void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection *eec, const WipingExtrusions::ExtruderPerCopy *copies_extruder) + { + // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves: + ExtrusionEntitiesPtr *perimeters_or_infills; + std::vector *perimeters_or_infills_overrides; -// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed -// during infill/perimeter wiping, or normally (depends on wiping_entities parameter) -// Fills in by_region_per_copy_cache and returns its reference. -const std::vector& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const -{ - bool has_overrides = false; - for (const auto& reg : by_region) - if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { - has_overrides = true; + switch (type) + { + case PERIMETERS: + perimeters_or_infills = &perimeters; + perimeters_or_infills_overrides = &perimeters_overrides; + break; + case INFILL: + perimeters_or_infills = &infills; + perimeters_or_infills_overrides = &infills_overrides; break; + default: + throw Slic3r::InvalidArgument("Unknown parameter!"); } - // Data is cleared, but the memory is not. - by_region_per_copy_cache.clear(); - - if (! has_overrides) - // Simple case. No need to copy the regions. - return wiping_entities ? by_region_per_copy_cache : this->by_region; - - // Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions. - // Some of the extrusions of some object instances are printed later - those are the clean print extrusions. - // Filter out the extrusions based on the infill_overrides / perimeter_overrides: - - for (const auto& reg : by_region) { - by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island - - // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed - // References are used so that we don't have to repeat the same code - for (int iter = 0; iter < 2; ++iter) { - const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters); - ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); - const std::vector& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides); - - // Now the most important thing - which extrusion should we print. - // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack. - if (wiping_entities) { - // Apply overrides for this region. - for (unsigned int i = 0; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which overrides the default one. - if (this_override != nullptr && (*this_override)[copy] == int(extruder)) - target_eec.emplace_back(entities[i]); - } - } else { - // Apply normal extrusions (non-overrides) for this region. - unsigned int i = 0; - for (; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. - if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1) - target_eec.emplace_back(entities[i]); - } - for (; i < entities.size(); ++ i) - target_eec.emplace_back(entities[i]); - } + // First we append the entities, there are eec->entities.size() of them: + size_t old_size = perimeters_or_infills->size(); + size_t new_size = old_size + (eec->can_sort() ? eec->entities.size() : 1); + perimeters_or_infills->reserve(new_size); + if (eec->can_sort()) + { + for (auto *ee : eec->entities) + perimeters_or_infills->emplace_back(ee); } - } - return by_region_per_copy_cache; -} - -// This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter) -// It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy. -void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder) -{ - // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves: - ExtrusionEntitiesPtr* perimeters_or_infills; - std::vector* perimeters_or_infills_overrides; - - switch (type) { - case PERIMETERS: - perimeters_or_infills = &perimeters; - perimeters_or_infills_overrides = &perimeters_overrides; - break; - case INFILL: - perimeters_or_infills = &infills; - perimeters_or_infills_overrides = &infills_overrides; - break; - default: - throw Slic3r::InvalidArgument("Unknown parameter!"); - } + else + perimeters_or_infills->emplace_back(const_cast(eec)); - // First we append the entities, there are eec->entities.size() of them: - size_t old_size = perimeters_or_infills->size(); - size_t new_size = old_size + (eec->can_sort() ? eec->entities.size() : 1); - perimeters_or_infills->reserve(new_size); - if (eec->can_sort()) { - for (auto* ee : eec->entities) - perimeters_or_infills->emplace_back(ee); - } else - perimeters_or_infills->emplace_back(const_cast(eec)); - - if (copies_extruder != nullptr) { - // Don't reallocate overrides if not needed. - // Missing overrides are implicitely considered non-overridden. - perimeters_or_infills_overrides->reserve(new_size); - perimeters_or_infills_overrides->resize(old_size, nullptr); - perimeters_or_infills_overrides->resize(new_size, copies_extruder); + if (copies_extruder != nullptr) + { + // Don't reallocate overrides if not needed. + // Missing overrides are implicitely considered non-overridden. + perimeters_or_infills_overrides->reserve(new_size); + perimeters_or_infills_overrides->resize(old_size, nullptr); + perimeters_or_infills_overrides->resize(new_size, copies_extruder); + } } -} -} // namespace Slic3r +} // namespace Slic3r diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index cbfda64ca4..b825ce79f7 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -14,646 +14,675 @@ #include static const double max_deviation = scale_(0.5); -static const double max_variance = 5 * scale_(0.01) * scale_(0.01); +static const double max_variance = 5 * scale_(0.01) * scale_(0.01); -namespace Slic3r { - -Flow LayerRegion::flow(FlowRole role) const +namespace Slic3r { - return this->flow(role, m_layer->height); -} -Flow LayerRegion::flow(FlowRole role, double layer_height) const -{ - return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0); -} + Flow LayerRegion::flow(FlowRole role) const + { + return this->flow(role, m_layer->height); + } -Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const -{ - const PrintRegion ®ion = this->region(); - const PrintRegionConfig ®ion_config = region.config(); - const PrintObject &print_object = *this->layer()->object(); - if (thick_bridge) { - // The old Slic3r way (different from all other slicers): Use rounded extrusions. - // Get the configured nozzle_diameter for the extruder associated to the flow role requested. - // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1)); - // Applies default bridge spacing. - return Flow::bridging_flow(float(sqrt(region_config.bridge_flow)) * nozzle_diameter, nozzle_diameter); - } else { - // The same way as other slicers: Use normal extrusions. Apply bridge_flow while maintaining the original spacing. - return this->flow(role).with_flow_ratio(region_config.bridge_flow); + Flow LayerRegion::flow(FlowRole role, double layer_height) const + { + return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0); } -} -// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. -void LayerRegion::slices_to_fill_surfaces_clipped() -{ - // Note: this method should be idempotent, but fill_surfaces gets modified - // in place. However we're now only using its boundaries (which are invariant) - // so we're safe. This guarantees idempotence of prepare_infill() also in case - // that combine_infill() turns some fill_surface into VOID surfaces. - // Collect polygons per surface type. - std::array by_surface; - for (Surface &surface : this->slices.surfaces) - by_surface[size_t(surface.surface_type)].emplace_back(&surface); - // Trim surfaces by the fill_boundaries. - this->fill_surfaces.surfaces.clear(); - for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) { - const SurfacesPtr &this_surfaces = by_surface[surface_type]; - if (! this_surfaces.empty()) - this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type)); + Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const + { + const PrintRegion ®ion = this->region(); + const PrintRegionConfig ®ion_config = region.config(); + const PrintObject &print_object = *this->layer()->object(); + if (thick_bridge) + { + // The old Slic3r way (different from all other slicers): Use rounded extrusions. + // Get the configured nozzle_diameter for the extruder associated to the flow role requested. + // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. + auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1)); + // Applies default bridge spacing. + return Flow::bridging_flow(float(sqrt(region_config.bridge_flow)) * nozzle_diameter, nozzle_diameter); + } + else + { + // The same way as other slicers: Use normal extrusions. Apply bridge_flow while maintaining the original spacing. + return this->flow(role).with_flow_ratio(region_config.bridge_flow); + } } -} -void LayerRegion::auto_circle_compensation(SurfaceCollection& slices, const AutoContourHolesCompensationParams &auto_contour_holes_compensation_params, float manual_offset) -{ - // filament is 1 base - int filament_idx = this->region().config().wall_filament - 1; - - double limited_speed = auto_contour_holes_compensation_params.circle_compensation_speed[filament_idx]; - double counter_speed_coef = auto_contour_holes_compensation_params.counter_speed_coef[filament_idx]; - double counter_diameter_coef = auto_contour_holes_compensation_params.counter_diameter_coef[filament_idx]; - double counter_compensate_coef = scale_(auto_contour_holes_compensation_params.counter_compensate_coef[filament_idx]); - - double hole_speed_coef = auto_contour_holes_compensation_params.hole_speed_coef[filament_idx]; - double hole_diameter_coef = auto_contour_holes_compensation_params.hole_diameter_coef[filament_idx]; - double hole_compensate_coef = scale_(auto_contour_holes_compensation_params.hole_compensate_coef[filament_idx]); - - double counter_limit_min_value = scale_(auto_contour_holes_compensation_params.counter_limit_min_value[filament_idx]); - double counter_limit_max_value = scale_(auto_contour_holes_compensation_params.counter_limit_max_value[filament_idx]); - double hole_limit_min_value = scale_(auto_contour_holes_compensation_params.hole_limit_min_value[filament_idx]); - double hole_limit_max_value = scale_(auto_contour_holes_compensation_params.hole_limit_max_value[filament_idx]); - - double diameter_limit_value = scale_(auto_contour_holes_compensation_params.diameter_limit[filament_idx]); - - for (Surface &surface : slices.surfaces) { - Point center; - double diameter = 0; - if (surface.expolygon.contour.is_approx_circle(max_deviation, max_variance, center, diameter)) { - double offset_value = scale_(counter_speed_coef * limited_speed) + counter_diameter_coef * diameter + counter_compensate_coef; - if (offset_value < counter_limit_min_value) { - offset_value = counter_limit_min_value; - } else if (offset_value > counter_limit_max_value) { - offset_value = counter_limit_max_value; - } - offset_value -= manual_offset / 2; - Polygons offseted_polys = offset(surface.expolygon.contour, offset_value); - if (offseted_polys.size() == 1) { - surface.expolygon.contour = offseted_polys[0]; - if (diameter < diameter_limit_value) - surface.counter_circle_compensation = true; - } + // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. + void LayerRegion::slices_to_fill_surfaces_clipped() + { + // Note: this method should be idempotent, but fill_surfaces gets modified + // in place. However we're now only using its boundaries (which are invariant) + // so we're safe. This guarantees idempotence of prepare_infill() also in case + // that combine_infill() turns some fill_surface into VOID surfaces. + // Collect polygons per surface type. + std::array by_surface; + for (Surface &surface : this->slices.surfaces) + by_surface[size_t(surface.surface_type)].emplace_back(&surface); + // Trim surfaces by the fill_boundaries. + this->fill_surfaces.surfaces.clear(); + for (size_t surface_type = 0; surface_type < size_t(stCount); ++surface_type) + { + const SurfacesPtr &this_surfaces = by_surface[surface_type]; + if (!this_surfaces.empty()) + this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type)); } - for (size_t i = 0; i < surface.expolygon.holes.size(); ++i) { - Polygon &hole = surface.expolygon.holes[i]; - if (hole.is_approx_circle(max_deviation, max_variance, center, diameter)) { - double offset_value = scale_(hole_speed_coef * limited_speed) + hole_diameter_coef * diameter + hole_compensate_coef; - if (offset_value < hole_limit_min_value) { - offset_value = hole_limit_min_value; - } else if (offset_value > hole_limit_max_value) { - offset_value = hole_limit_max_value; + } + + void LayerRegion::auto_circle_compensation(SurfaceCollection &slices, const AutoContourHolesCompensationParams &auto_contour_holes_compensation_params, float manual_offset) + { + // filament is 1 base + int filament_idx = this->region().config().wall_filament - 1; + + double limited_speed = auto_contour_holes_compensation_params.circle_compensation_speed[filament_idx]; + double counter_speed_coef = auto_contour_holes_compensation_params.counter_speed_coef[filament_idx]; + double counter_diameter_coef = auto_contour_holes_compensation_params.counter_diameter_coef[filament_idx]; + double counter_compensate_coef = scale_(auto_contour_holes_compensation_params.counter_compensate_coef[filament_idx]); + + double hole_speed_coef = auto_contour_holes_compensation_params.hole_speed_coef[filament_idx]; + double hole_diameter_coef = auto_contour_holes_compensation_params.hole_diameter_coef[filament_idx]; + double hole_compensate_coef = scale_(auto_contour_holes_compensation_params.hole_compensate_coef[filament_idx]); + + double counter_limit_min_value = scale_(auto_contour_holes_compensation_params.counter_limit_min_value[filament_idx]); + double counter_limit_max_value = scale_(auto_contour_holes_compensation_params.counter_limit_max_value[filament_idx]); + double hole_limit_min_value = scale_(auto_contour_holes_compensation_params.hole_limit_min_value[filament_idx]); + double hole_limit_max_value = scale_(auto_contour_holes_compensation_params.hole_limit_max_value[filament_idx]); + + double diameter_limit_value = scale_(auto_contour_holes_compensation_params.diameter_limit[filament_idx]); + + for (Surface &surface : slices.surfaces) + { + Point center; + double diameter = 0; + if (surface.expolygon.contour.is_approx_circle(max_deviation, max_variance, center, diameter)) + { + double offset_value = scale_(counter_speed_coef * limited_speed) + counter_diameter_coef * diameter + counter_compensate_coef; + if (offset_value < counter_limit_min_value) + { + offset_value = counter_limit_min_value; + } + else if (offset_value > counter_limit_max_value) + { + offset_value = counter_limit_max_value; } - // positive value means shrinking hole, which oppsite to contour - offset_value = -offset_value; offset_value -= manual_offset / 2; - Polygons offseted_polys = offset(hole, offset_value); - if (offseted_polys.size() == 1) { - hole = offseted_polys[0]; + Polygons offseted_polys = offset(surface.expolygon.contour, offset_value); + if (offseted_polys.size() == 1) + { + surface.expolygon.contour = offseted_polys[0]; if (diameter < diameter_limit_value) - surface.holes_circle_compensation.push_back(i); + surface.counter_circle_compensation = true; + } + } + for (size_t i = 0; i < surface.expolygon.holes.size(); ++i) + { + Polygon &hole = surface.expolygon.holes[i]; + if (hole.is_approx_circle(max_deviation, max_variance, center, diameter)) + { + double offset_value = scale_(hole_speed_coef * limited_speed) + hole_diameter_coef * diameter + hole_compensate_coef; + if (offset_value < hole_limit_min_value) + { + offset_value = hole_limit_min_value; + } + else if (offset_value > hole_limit_max_value) + { + offset_value = hole_limit_max_value; + } + // positive value means shrinking hole, which oppsite to contour + offset_value = -offset_value; + offset_value -= manual_offset / 2; + Polygons offseted_polys = offset(hole, offset_value); + if (offseted_polys.size() == 1) + { + hole = offseted_polys[0]; + if (diameter < diameter_limit_value) + surface.holes_circle_compensation.push_back(i); + } } } } } -} - -void LayerRegion::make_perimeters(const SurfaceCollection &slices, const PerimeterRegions &perimeter_regions, SurfaceCollection *fill_surfaces, ExPolygons *fill_no_overlap, std::vector &loop_nodes) -{ - this->perimeters.clear(); - this->thin_fills.clear(); - - const PrintConfig & print_config = this->layer()->object()->print()->config(); - const PrintRegionConfig ®ion_config = this->region().config(); - const PrintObjectConfig& object_config = this->layer()->object()->config(); - // This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer! - bool spiral_mode = print_config.spiral_mode && - //FIXME account for raft layers. - (this->layer()->id() >= size_t(region_config.bottom_shell_layers.value) && - this->layer()->print_z >= region_config.bottom_shell_thickness - EPSILON); - - PerimeterGenerator g( - // input: - &slices, - this->layer()->height, - this->flow(frPerimeter), - ®ion_config, - &this->layer()->object()->config(), - &print_config, - spiral_mode, - // output: - &this->perimeters, - &this->thin_fills, - fill_surfaces, - //BBS - fill_no_overlap, - &loop_nodes - ); - - if (this->layer()->lower_layer != nullptr) - // Cummulative sum of polygons over all the regions. - g.lower_slices = &this->layer()->lower_layer->lslices; - if (this->layer()->upper_layer != NULL) - g.upper_slices = &this->layer()->upper_layer->lslices; - - g.layer_id = (int)this->layer()->id(); - g.ext_perimeter_flow = this->flow(frExternalPerimeter); - g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges); - g.solid_infill_flow = this->flow(frSolidInfill); - g.perimeter_regions = &perimeter_regions; - - if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode) - g.process_arachne(); - else - g.process_classic(); -} + void LayerRegion::make_perimeters(const SurfaceCollection &slices, const PerimeterRegions &perimeter_regions, SurfaceCollection *fill_surfaces, ExPolygons *fill_no_overlap, std::vector &loop_nodes) + { + this->perimeters.clear(); + this->thin_fills.clear(); + + const PrintConfig &print_config = this->layer()->object()->print()->config(); + const PrintRegionConfig ®ion_config = this->region().config(); + const PrintObjectConfig &object_config = this->layer()->object()->config(); + // This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer! + bool spiral_mode = print_config.spiral_mode && + // FIXME account for raft layers. + (this->layer()->id() >= size_t(region_config.bottom_shell_layers.value) && + this->layer()->print_z >= region_config.bottom_shell_thickness - EPSILON); + + PerimeterGenerator g( + // input: + &slices, + this->layer()->height, + this->flow(frPerimeter), + ®ion_config, + &this->layer()->object()->config(), + &print_config, + spiral_mode, + // output: + &this->perimeters, + &this->thin_fills, + fill_surfaces, + // BBS + fill_no_overlap, + &loop_nodes); + + if (this->layer()->lower_layer != nullptr) + // Cummulative sum of polygons over all the regions. + g.lower_slices = &this->layer()->lower_layer->lslices; + if (this->layer()->upper_layer != NULL) + g.upper_slices = &this->layer()->upper_layer->lslices; + + g.layer_id = (int)this->layer()->id(); + g.ext_perimeter_flow = this->flow(frExternalPerimeter); + g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges); + g.solid_infill_flow = this->flow(frSolidInfill); + g.perimeter_regions = &perimeter_regions; + + if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode) + g.process_arachne(); + else + g.process_classic(); + } #if 1 -// Extract surfaces of given type from surfaces, extract fill (layer) thickness of one of the surfaces. -static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, std::initializer_list surface_types, double &thickness) -{ - size_t cnt = 0; - for (const Surface &surface : surfaces) - if (std::find(surface_types.begin(), surface_types.end(), surface.surface_type) != surface_types.end()) { - ++cnt; - thickness = surface.thickness; - } - if (cnt == 0) - return {}; - - ExPolygons out; - out.reserve(cnt); - for (Surface &surface : surfaces) - if (std::find(surface_types.begin(), surface_types.end(), surface.surface_type) != surface_types.end()) - out.emplace_back(std::move(surface.expolygon)); - return out; -} - -// Cache for detecting bridge orientation and merging regions with overlapping expansions. -struct Bridge { - ExPolygon expolygon; - uint32_t group_id; - std::vector::const_iterator bridge_expansion_begin; - std::optional angle{std::nullopt}; -}; - -// Group the bridge surfaces by overlaps. -uint32_t group_id(std::vector &bridges, uint32_t src_id) { - uint32_t group_id = bridges[src_id].group_id; - while (group_id != src_id) { - src_id = group_id; - group_id = bridges[src_id].group_id; + // Extract surfaces of given type from surfaces, extract fill (layer) thickness of one of the surfaces. + static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, std::initializer_list surface_types, double &thickness) + { + size_t cnt = 0; + for (const Surface &surface : surfaces) + if (std::find(surface_types.begin(), surface_types.end(), surface.surface_type) != surface_types.end()) + { + ++cnt; + thickness = surface.thickness; + } + if (cnt == 0) + return {}; + + ExPolygons out; + out.reserve(cnt); + for (Surface &surface : surfaces) + if (std::find(surface_types.begin(), surface_types.end(), surface.surface_type) != surface_types.end()) + out.emplace_back(std::move(surface.expolygon)); + return out; } - bridges[src_id].group_id = group_id; - return group_id; -}; -std::vector get_grouped_bridges( - ExPolygons&& bridge_expolygons, - const std::vector& bridge_expansions -) { - using namespace Algorithm; + // Cache for detecting bridge orientation and merging regions with overlapping expansions. + struct Bridge + { + ExPolygon expolygon; + uint32_t group_id; + std::vector::const_iterator bridge_expansion_begin; + std::optional angle{std::nullopt}; + }; - std::vector result; + // Group the bridge surfaces by overlaps. + uint32_t group_id(std::vector &bridges, uint32_t src_id) { - result.reserve(bridge_expansions.size()); - uint32_t group_id = 0; - using std::move_iterator; - for (ExPolygon& expolygon : bridge_expolygons) - result.push_back({ std::move(expolygon), group_id ++, bridge_expansions.end() }); - } + uint32_t group_id = bridges[src_id].group_id; + while (group_id != src_id) + { + src_id = group_id; + group_id = bridges[src_id].group_id; + } + bridges[src_id].group_id = group_id; + return group_id; + }; + std::vector get_grouped_bridges( + ExPolygons &&bridge_expolygons, + const std::vector &bridge_expansions) + { + using namespace Algorithm; - // Detect overlaps of bridge anchors inside their respective shell regions. - // bridge_expansions are sorted by boundary id and source id. - for (auto expansion_iterator = bridge_expansions.begin(); expansion_iterator != bridge_expansions.end();) { - auto boundary_region_begin = expansion_iterator; - auto boundary_region_end = std::find_if( - next(expansion_iterator), - bridge_expansions.end(), - [&](const RegionExpansionEx& expansion){ - return expansion.boundary_id != expansion_iterator->boundary_id; - } - ); - - // Cache of bboxes per expansion boundary. - std::vector bounding_boxes; - bounding_boxes.reserve(std::distance(boundary_region_begin, boundary_region_end)); - std::transform( - boundary_region_begin, - boundary_region_end, - std::back_inserter(bounding_boxes), - [](const RegionExpansionEx& expansion){ - return get_extents(expansion.expolygon.contour); - } - ); - - // For each bridge anchor of the current source: - for (;expansion_iterator != boundary_region_end; ++expansion_iterator) { - auto candidate_iterator = std::next(expansion_iterator); - for (;candidate_iterator != boundary_region_end; ++candidate_iterator) { - const BoundingBox& current_bounding_box{ - bounding_boxes[expansion_iterator - boundary_region_begin] - }; - const BoundingBox& candidate_bounding_box{ - bounding_boxes[candidate_iterator - boundary_region_begin] - }; - if ( - expansion_iterator->src_id != candidate_iterator->src_id - && current_bounding_box.overlap(candidate_bounding_box) - // One may ignore holes, they are irrelevant for intersection test. - && !intersection(expansion_iterator->expolygon.contour, candidate_iterator->expolygon.contour).empty() - ) { - // The two bridge regions intersect. Give them the same (lower) group id. - uint32_t id = group_id(result, expansion_iterator->src_id); - uint32_t id2 = group_id(result, candidate_iterator->src_id); - if (id < id2) - result[id2].group_id = id; - else - result[id].group_id = id2; + std::vector result; + { + result.reserve(bridge_expansions.size()); + uint32_t group_id = 0; + using std::move_iterator; + for (ExPolygon &expolygon : bridge_expolygons) + result.push_back({std::move(expolygon), group_id++, bridge_expansions.end()}); + } + + // Detect overlaps of bridge anchors inside their respective shell regions. + // bridge_expansions are sorted by boundary id and source id. + for (auto expansion_iterator = bridge_expansions.begin(); expansion_iterator != bridge_expansions.end();) + { + auto boundary_region_begin = expansion_iterator; + auto boundary_region_end = std::find_if( + next(expansion_iterator), + bridge_expansions.end(), + [&](const RegionExpansionEx &expansion) + { + return expansion.boundary_id != expansion_iterator->boundary_id; + }); + + // Cache of bboxes per expansion boundary. + std::vector bounding_boxes; + bounding_boxes.reserve(std::distance(boundary_region_begin, boundary_region_end)); + std::transform( + boundary_region_begin, + boundary_region_end, + std::back_inserter(bounding_boxes), + [](const RegionExpansionEx &expansion) + { + return get_extents(expansion.expolygon.contour); + }); + + // For each bridge anchor of the current source: + for (; expansion_iterator != boundary_region_end; ++expansion_iterator) + { + auto candidate_iterator = std::next(expansion_iterator); + for (; candidate_iterator != boundary_region_end; ++candidate_iterator) + { + const BoundingBox ¤t_bounding_box{ + bounding_boxes[expansion_iterator - boundary_region_begin]}; + const BoundingBox &candidate_bounding_box{ + bounding_boxes[candidate_iterator - boundary_region_begin]}; + if ( + expansion_iterator->src_id != candidate_iterator->src_id && current_bounding_box.overlap(candidate_bounding_box) + // One may ignore holes, they are irrelevant for intersection test. + && !intersection(expansion_iterator->expolygon.contour, candidate_iterator->expolygon.contour).empty()) + { + // The two bridge regions intersect. Give them the same (lower) group id. + uint32_t id = group_id(result, expansion_iterator->src_id); + uint32_t id2 = group_id(result, candidate_iterator->src_id); + if (id < id2) + result[id2].group_id = id; + else + result[id].group_id = id2; + } } } } + return result; } - return result; -} -void detect_bridge_directions( - const Algorithm::WaveSeeds& bridge_anchors, - std::vector& bridges, - const std::vector& expansion_zones -) { - if (expansion_zones.empty()) { - throw std::runtime_error("At least one expansion zone must exist!"); - } - auto it_bridge_anchor = bridge_anchors.begin(); - for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { - Bridge &bridge = bridges[bridge_id]; - Polygons anchor_areas; - int32_t last_anchor_id = -1; - for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) { - if (last_anchor_id != int(it_bridge_anchor->boundary)) { - last_anchor_id = int(it_bridge_anchor->boundary); - - unsigned start_index{}; - unsigned end_index{}; - for (const ExpansionZone& expansion_zone: expansion_zones) { - end_index += expansion_zone.expolygons.size(); - if (last_anchor_id < static_cast(end_index)) { - append(anchor_areas, to_polygons(expansion_zone.expolygons[last_anchor_id - start_index])); - break; + void detect_bridge_directions( + const Algorithm::WaveSeeds &bridge_anchors, + std::vector &bridges, + const std::vector &expansion_zones) + { + if (expansion_zones.empty()) + { + throw std::runtime_error("At least one expansion zone must exist!"); + } + auto it_bridge_anchor = bridge_anchors.begin(); + for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++bridge_id) + { + Bridge &bridge = bridges[bridge_id]; + Polygons anchor_areas; + int32_t last_anchor_id = -1; + for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++it_bridge_anchor) + { + if (last_anchor_id != int(it_bridge_anchor->boundary)) + { + last_anchor_id = int(it_bridge_anchor->boundary); + + unsigned start_index{}; + unsigned end_index{}; + for (const ExpansionZone &expansion_zone : expansion_zones) + { + end_index += expansion_zone.expolygons.size(); + if (last_anchor_id < static_cast(end_index)) + { + append(anchor_areas, to_polygons(expansion_zone.expolygons[last_anchor_id - start_index])); + break; + } + start_index += expansion_zone.expolygons.size(); } - start_index += expansion_zone.expolygons.size(); } } - } - Lines lines{to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))))}; - auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon)); - bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x()); - - if constexpr (false) { - coordf_t stroke_width = scale_(0.06); - BoundingBox bbox = get_extents(anchor_areas); - bbox.merge(get_extents(bridge.expolygon)); - bbox.offset(scale_(1.)); - ::Slic3r::SVG - svg(debug_out_path(("bridge" + std::to_string(*bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()), - bbox); - svg.draw(bridge.expolygon, "cyan"); - svg.draw(lines, "green", stroke_width); - svg.draw(anchor_areas, "red"); + Lines lines{to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))))}; + auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon)); + bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x()); + + if constexpr (false) + { + coordf_t stroke_width = scale_(0.06); + BoundingBox bbox = get_extents(anchor_areas); + bbox.merge(get_extents(bridge.expolygon)); + bbox.offset(scale_(1.)); + ::Slic3r::SVG + svg(debug_out_path(("bridge" + std::to_string(*bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()), + bbox); + svg.draw(bridge.expolygon, "cyan"); + svg.draw(lines, "green", stroke_width); + svg.draw(anchor_areas, "red"); + } } } -} -Surfaces merge_bridges( - std::vector& bridges, - const std::vector& bridge_expansions, - const float closing_radius -) { - for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) { - bridges[it->src_id].bridge_expansion_begin = it; - uint32_t src_id = it->src_id; - for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ; - } + Surfaces merge_bridges( + std::vector &bridges, + const std::vector &bridge_expansions, + const float closing_radius) + { + for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) + { + bridges[it->src_id].bridge_expansion_begin = it; + uint32_t src_id = it->src_id; + for (++it; it != bridge_expansions.end() && it->src_id == src_id; ++it) + ; + } - Surfaces result; - for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { - if (group_id(bridges, bridge_id) == bridge_id) { - // Head of the group. - Polygons acc; - for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2) - if (group_id(bridges, bridge_id2) == bridge_id) { - append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon))); - auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin; - assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2); - for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion) - append(acc, to_polygons(it_bridge_expansion->expolygon)); + Surfaces result; + for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++bridge_id) + { + if (group_id(bridges, bridge_id) == bridge_id) + { + // Head of the group. + Polygons acc; + for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++bridge_id2) + if (group_id(bridges, bridge_id2) == bridge_id) + { + append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon))); + auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin; + assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2); + for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++it_bridge_expansion) + append(acc, to_polygons(it_bridge_expansion->expolygon)); + } + // FIXME try to be smart and pick the best bridging angle for all? + if (!bridges[bridge_id].angle) + { + assert(false && "Bridge angle must be pre-calculated!"); } - //FIXME try to be smart and pick the best bridging angle for all? - if (!bridges[bridge_id].angle) { - assert(false && "Bridge angle must be pre-calculated!"); + Surface templ{stBottomBridge, {}}; + templ.bridge_angle = bridges[bridge_id].angle ? *bridges[bridge_id].angle : -1; + // NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) + // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. + // look for narrow_ensure_vertical_wall_thickness_region_radius filter. + ExPolygons final = closing_ex(acc, closing_radius); + // without safety offset, artifacts are generated (GH #2494) + // union_safety_offset_ex(acc) + for (ExPolygon &ex : final) + result.emplace_back(templ, std::move(ex)); } - Surface templ{ stBottomBridge, {} }; - templ.bridge_angle = bridges[bridge_id].angle ? *bridges[bridge_id].angle : -1; - //NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) - // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. - // look for narrow_ensure_vertical_wall_thickness_region_radius filter. - ExPolygons final = closing_ex(acc, closing_radius); - // without safety offset, artifacts are generated (GH #2494) - // union_safety_offset_ex(acc) - for (ExPolygon &ex : final) - result.emplace_back(templ, std::move(ex)); } + return result; } - return result; -} -struct ExpansionResult { - Algorithm::WaveSeeds anchors; - std::vector expansions; -}; - -ExpansionResult expand_expolygons( - const ExPolygons& expolygons, - std::vector& expansion_zones -) { - using namespace Algorithm; - WaveSeeds bridge_anchors; - std::vector bridge_expansions; - - unsigned processed_bridges_count = 0; - for (ExpansionZone& expansion_zone : expansion_zones) { - WaveSeeds seeds{wave_seeds( - expolygons, - expansion_zone.expolygons, - expansion_zone.parameters.tiny_expansion, - true - )}; - std::vector expansions{propagate_waves_ex( - seeds, - expansion_zone.expolygons, - expansion_zone.parameters - )}; - - for (WaveSeed &seed : seeds) - seed.boundary += processed_bridges_count; - for (RegionExpansionEx &expansion : expansions) - expansion.boundary_id += processed_bridges_count; - - expansion_zone.expanded_into = ! expansions.empty(); - - append(bridge_anchors, std::move(seeds)); - append(bridge_expansions, std::move(expansions)); - - processed_bridges_count += expansion_zone.expolygons.size(); + struct ExpansionResult + { + Algorithm::WaveSeeds anchors; + std::vector expansions; + }; + + ExpansionResult expand_expolygons( + const ExPolygons &expolygons, + std::vector &expansion_zones) + { + using namespace Algorithm; + WaveSeeds bridge_anchors; + std::vector bridge_expansions; + + unsigned processed_bridges_count = 0; + for (ExpansionZone &expansion_zone : expansion_zones) + { + WaveSeeds seeds{wave_seeds( + expolygons, + expansion_zone.expolygons, + expansion_zone.parameters.tiny_expansion, + true)}; + std::vector expansions{propagate_waves_ex( + seeds, + expansion_zone.expolygons, + expansion_zone.parameters)}; + + for (WaveSeed &seed : seeds) + seed.boundary += processed_bridges_count; + for (RegionExpansionEx &expansion : expansions) + expansion.boundary_id += processed_bridges_count; + + expansion_zone.expanded_into = !expansions.empty(); + + append(bridge_anchors, std::move(seeds)); + append(bridge_expansions, std::move(expansions)); + + processed_bridges_count += expansion_zone.expolygons.size(); + } + return {bridge_anchors, bridge_expansions}; } - return {bridge_anchors, bridge_expansions}; -} -// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params, -// detect bridges. -// Trim "shells" by the expanded bridges. -Surfaces expand_bridges_detect_orientations( - Surfaces &surfaces, - std::vector& expansion_zones, - const float closing_radius -) -{ - using namespace Slic3r::Algorithm; - - double thickness; - ExPolygons bridge_expolygons = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness); - if (bridge_expolygons.empty()) - return {}; - - // Calculate bridge anchors and their expansions in their respective shell region. - ExpansionResult expansion_result{expand_expolygons( - bridge_expolygons, - expansion_zones - )}; - - std::vector bridges{get_grouped_bridges( - std::move(bridge_expolygons), - expansion_result.expansions - )}; - bridge_expolygons.clear(); - - std::sort(expansion_result.anchors.begin(), expansion_result.anchors.end(), Algorithm::lower_by_src_and_boundary); - detect_bridge_directions(expansion_result.anchors, bridges, expansion_zones); - - // Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors. - std::sort(expansion_result.expansions.begin(), expansion_result.expansions.end(), [](auto &l, auto &r) { - return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id); - }); - Surfaces out{merge_bridges(bridges, expansion_result.expansions, closing_radius)}; - - // Clip by the expanded bridges. - for (ExpansionZone& expansion_zone : expansion_zones) - if (expansion_zone.expanded_into) - expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, out); - return out; -} + // Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params, + // detect bridges. + // Trim "shells" by the expanded bridges. + Surfaces expand_bridges_detect_orientations( + Surfaces &surfaces, + std::vector &expansion_zones, + const float closing_radius) + { + using namespace Slic3r::Algorithm; + + double thickness; + ExPolygons bridge_expolygons = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness); + if (bridge_expolygons.empty()) + return {}; + + // Calculate bridge anchors and their expansions in their respective shell region. + ExpansionResult expansion_result{expand_expolygons( + bridge_expolygons, + expansion_zones)}; + + std::vector bridges{get_grouped_bridges( + std::move(bridge_expolygons), + expansion_result.expansions)}; + bridge_expolygons.clear(); + + std::sort(expansion_result.anchors.begin(), expansion_result.anchors.end(), Algorithm::lower_by_src_and_boundary); + detect_bridge_directions(expansion_result.anchors, bridges, expansion_zones); + + // Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors. + std::sort(expansion_result.expansions.begin(), expansion_result.expansions.end(), [](auto &l, auto &r) + { return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id); }); + Surfaces out{merge_bridges(bridges, expansion_result.expansions, closing_radius)}; + + // Clip by the expanded bridges. + for (ExpansionZone &expansion_zone : expansion_zones) + if (expansion_zone.expanded_into) + expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, out); + return out; + } -Surfaces expand_merge_surfaces( - Surfaces &surfaces, - SurfaceType surface_type, - std::vector& expansion_zones, - const float closing_radius, - const double bridge_angle -) -{ - using namespace Slic3r::Algorithm; + Surfaces expand_merge_surfaces( + Surfaces &surfaces, + SurfaceType surface_type, + std::vector &expansion_zones, + const float closing_radius, + const double bridge_angle) + { + using namespace Slic3r::Algorithm; - double thickness; - ExPolygons src = fill_surfaces_extract_expolygons(surfaces, {surface_type}, thickness); - if (src.empty()) - return {}; + double thickness; + ExPolygons src = fill_surfaces_extract_expolygons(surfaces, {surface_type}, thickness); + if (src.empty()) + return {}; - unsigned processed_expolygons_count = 0; - std::vector expansions; - for (ExpansionZone& expansion_zone : expansion_zones) { - std::vector zone_expansions = propagate_waves(src, expansion_zone.expolygons, expansion_zone.parameters); - expansion_zone.expanded_into = !zone_expansions.empty(); + unsigned processed_expolygons_count = 0; + std::vector expansions; + for (ExpansionZone &expansion_zone : expansion_zones) + { + std::vector zone_expansions = propagate_waves(src, expansion_zone.expolygons, expansion_zone.parameters); + expansion_zone.expanded_into = !zone_expansions.empty(); - for (RegionExpansion &expansion : zone_expansions) - expansion.boundary_id += processed_expolygons_count; + for (RegionExpansion &expansion : zone_expansions) + expansion.boundary_id += processed_expolygons_count; - processed_expolygons_count += expansion_zone.expolygons.size(); - append(expansions, std::move(zone_expansions)); - } + processed_expolygons_count += expansion_zone.expolygons.size(); + append(expansions, std::move(zone_expansions)); + } - std::vector expanded = merge_expansions_into_expolygons(std::move(src), std::move(expansions)); - //NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) - // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. - // look for narrow_ensure_vertical_wall_thickness_region_radius filter. - expanded = closing_ex(expanded, closing_radius); - // Trim the zones by the expanded expolygons. - for (ExpansionZone& expansion_zone : expansion_zones) - if (expansion_zone.expanded_into) - expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, expanded); - - Surface templ{ surface_type, {} }; - templ.bridge_angle = bridge_angle; - Surfaces out; - out.reserve(expanded.size()); - for (auto &expoly : expanded) - out.emplace_back(templ, std::move(expoly)); - return out; -} + std::vector expanded = merge_expansions_into_expolygons(std::move(src), std::move(expansions)); + // NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) + // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. + // look for narrow_ensure_vertical_wall_thickness_region_radius filter. + expanded = closing_ex(expanded, closing_radius); + // Trim the zones by the expanded expolygons. + for (ExpansionZone &expansion_zone : expansion_zones) + if (expansion_zone.expanded_into) + expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, expanded); + + Surface templ{surface_type, {}}; + templ.bridge_angle = bridge_angle; + Surfaces out; + out.reserve(expanded.size()); + for (auto &expoly : expanded) + out.emplace_back(templ, std::move(expoly)); + return out; + } -void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) -{ - using namespace Slic3r::Algorithm; + void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) + { + using namespace Slic3r::Algorithm; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial"); + export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // Width of the perimeters. - float shell_width = 0; - float expansion_min = 0; - if (int num_perimeters = this->region().config().wall_loops; num_perimeters > 0) { - Flow external_perimeter_flow = this->flow(frExternalPerimeter); - Flow perimeter_flow = this->flow(frPerimeter); - shell_width = 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing(); - shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1); - expansion_min = perimeter_flow.scaled_spacing(); - } else { - // TODO: Maybe there is better solution when printing with zero perimeters, but this works reasonably well, given the situation - shell_width = float(SCALED_EPSILON); - expansion_min = float(SCALED_EPSILON);; - } - - // Scaled expansions of the respective external surfaces. - float expansion_top = shell_width * sqrt(2.); - float expansion_bottom = expansion_top; - float expansion_bottom_bridge = expansion_top; - // Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps. - static constexpr const float expansion_step = scaled(0.1); - // Don't take more than max_nr_steps for small expansion_step. - static constexpr const size_t max_nr_expansion_steps = 5; - // Radius (with added epsilon) to absorb empty regions emering from regularization of ensuring, viz const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; - const float closing_radius = 0.55f * 0.65f * 1.05f * this->flow(frSolidInfill).scaled_spacing(); - - // Expand the top / bottom / bridge surfaces into the shell thickness solid infills. - double layer_thickness; - ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, { stInternalSolid }, layer_thickness)); - ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, { stInternal }, layer_thickness)); - ExPolygons top_expolygons = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, { stTop }, layer_thickness)); - const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps); - const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); - - std::vector expansion_zones{ - ExpansionZone{std::move(shells), expansion_params_into_solid_infill}, - ExpansionZone{std::move(sparse), expansion_params_into_sparse_infill}, - ExpansionZone{std::move(top_expolygons), expansion_params_into_solid_infill} - }; + // Width of the perimeters. + float shell_width = 0; + float expansion_min = 0; + if (int num_perimeters = this->region().config().wall_loops; num_perimeters > 0) + { + Flow external_perimeter_flow = this->flow(frExternalPerimeter); + Flow perimeter_flow = this->flow(frPerimeter); + shell_width = 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing(); + shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1); + expansion_min = perimeter_flow.scaled_spacing(); + } + else + { + // TODO: Maybe there is better solution when printing with zero perimeters, but this works reasonably well, given the situation + shell_width = float(SCALED_EPSILON); + expansion_min = float(SCALED_EPSILON); + ; + } - SurfaceCollection bridges; - { - BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z; - const double custom_angle = this->region().config().bridge_angle.value; - bridges.surfaces = custom_angle > 0 ? - expand_merge_surfaces(fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) : - expand_bridges_detect_orientations(fill_surfaces.surfaces, expansion_zones, closing_radius); - BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; + // Scaled expansions of the respective external surfaces. + float expansion_top = shell_width * sqrt(2.); + float expansion_bottom = expansion_top; + float expansion_bottom_bridge = expansion_top; + // Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps. + static constexpr const float expansion_step = scaled(0.1); + // Don't take more than max_nr_steps for small expansion_step. + static constexpr const size_t max_nr_expansion_steps = 5; + // Radius (with added epsilon) to absorb empty regions emering from regularization of ensuring, viz const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; + const float closing_radius = 0.55f * 0.65f * 1.05f * this->flow(frSolidInfill).scaled_spacing(); + + // Expand the top / bottom / bridge surfaces into the shell thickness solid infills. + double layer_thickness; + ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, {stInternalSolid}, layer_thickness)); + ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, {stInternal}, layer_thickness)); + ExPolygons top_expolygons = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, {stTop}, layer_thickness)); + const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps); + const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); + + std::vector expansion_zones{ + ExpansionZone{std::move(shells), expansion_params_into_solid_infill}, + ExpansionZone{std::move(sparse), expansion_params_into_sparse_infill}, + ExpansionZone{std::move(top_expolygons), expansion_params_into_solid_infill}}; + + SurfaceCollection bridges; + { + BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z; + const double custom_angle = this->region().config().bridge_angle.value; + bridges.surfaces = custom_angle > 0 ? expand_merge_surfaces(fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) : expand_bridges_detect_orientations(fill_surfaces.surfaces, expansion_zones, closing_radius); + BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; #if 0 { static int iRun = 0; bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun++), true); } #endif - } + } - fill_surfaces.remove_types({ stTop }); - { - Surface top_templ(stTop, {}); - top_templ.thickness = layer_thickness; - fill_surfaces.append(std::move(expansion_zones.back().expolygons), top_templ); - } + fill_surfaces.remove_types({stTop}); + { + Surface top_templ(stTop, {}); + top_templ.thickness = layer_thickness; + fill_surfaces.append(std::move(expansion_zones.back().expolygons), top_templ); + } - expansion_zones.pop_back(); + expansion_zones.pop_back(); - expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps); - Surfaces bottoms = expand_merge_surfaces(fill_surfaces.surfaces, stBottom, expansion_zones, closing_radius); + expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps); + Surfaces bottoms = expand_merge_surfaces(fill_surfaces.surfaces, stBottom, expansion_zones, closing_radius); - expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps); - Surfaces tops = expand_merge_surfaces(fill_surfaces.surfaces, stTop, expansion_zones, closing_radius); + expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps); + Surfaces tops = expand_merge_surfaces(fill_surfaces.surfaces, stTop, expansion_zones, closing_radius); - //expansion_zone[0]: shell , expansion_zone[1]: sparse - //apply minimu sparse infill area logic, this should also be added in bridge_over_infill - if (!this->layer()->object()->print()->config().spiral_mode && this->region().config().sparse_infill_density.value > 0) { - auto &sparse=expansion_zones[1].expolygons; - auto &shells=expansion_zones[0].expolygons; - double min_area = scale_(scale_(this->region().config().minimum_sparse_infill_area.value)); - ExPolygons areas_to_be_solid{}; - sparse.erase(std::remove_if(sparse.begin(), sparse.end(), [min_area, &areas_to_be_solid](ExPolygon& expoly) { + // expansion_zone[0]: shell , expansion_zone[1]: sparse + // apply minimu sparse infill area logic, this should also be added in bridge_over_infill + if (!this->layer()->object()->print()->config().spiral_mode && this->region().config().sparse_infill_density.value > 0) + { + auto &sparse = expansion_zones[1].expolygons; + auto &shells = expansion_zones[0].expolygons; + double min_area = scale_(scale_(this->region().config().minimum_sparse_infill_area.value)); + ExPolygons areas_to_be_solid{}; + sparse.erase(std::remove_if(sparse.begin(), sparse.end(), [min_area, &areas_to_be_solid](ExPolygon &expoly) + { if (expoly.area() <= min_area) { areas_to_be_solid.push_back(expoly); return true; } - return false; - }), sparse.end()); - - if (!areas_to_be_solid.empty()) - shells = union_ex(shells, areas_to_be_solid); - } + return false; }), + sparse.end()); + if (!areas_to_be_solid.empty()) + shells = union_ex(shells, areas_to_be_solid); + } -// m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); - fill_surfaces.clear(); - unsigned zones_expolygons_count = 0; - for (const ExpansionZone& zone : expansion_zones) - zones_expolygons_count += zone.expolygons.size(); - reserve_more(fill_surfaces.surfaces, zones_expolygons_count + bridges.size() + bottoms.size() + tops.size()); - { - Surface solid_templ(stInternalSolid, {}); - solid_templ.thickness = layer_thickness; - fill_surfaces.append(std::move(expansion_zones[0].expolygons), solid_templ); - } - { - Surface sparse_templ(stInternal, {}); - sparse_templ.thickness = layer_thickness; - fill_surfaces.append(std::move(expansion_zones[1].expolygons), sparse_templ); - } - fill_surfaces.append(std::move(bridges.surfaces)); - fill_surfaces.append(std::move(bottoms)); - fill_surfaces.append(std::move(tops)); + // m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); + fill_surfaces.clear(); + unsigned zones_expolygons_count = 0; + for (const ExpansionZone &zone : expansion_zones) + zones_expolygons_count += zone.expolygons.size(); + reserve_more(fill_surfaces.surfaces, zones_expolygons_count + bridges.size() + bottoms.size() + tops.size()); + { + Surface solid_templ(stInternalSolid, {}); + solid_templ.thickness = layer_thickness; + fill_surfaces.append(std::move(expansion_zones[0].expolygons), solid_templ); + } + { + Surface sparse_templ(stInternal, {}); + sparse_templ.thickness = layer_thickness; + fill_surfaces.append(std::move(expansion_zones[1].expolygons), sparse_templ); + } + fill_surfaces.append(std::move(bridges.surfaces)); + fill_surfaces.append(std::move(bottoms)); + fill_surfaces.append(std::move(tops)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final"); + export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -} + } #else #endif -void LayerRegion::prepare_fill_surfaces() -{ + void LayerRegion::prepare_fill_surfaces() + { #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); - export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-initial"); + export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); + export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - /* Note: in order to make the psPrepareInfill step idempotent, we should never - alter fill_surfaces boundaries on which our idempotency relies since that's - the only meaningful information returned by psPerimeters. */ + /* Note: in order to make the psPrepareInfill step idempotent, we should never + alter fill_surfaces boundaries on which our idempotency relies since that's + the only meaningful information returned by psPerimeters. */ - bool spiral_mode = this->layer()->object()->print()->config().spiral_mode; + bool spiral_mode = this->layer()->object()->print()->config().spiral_mode; #if 0 // if no solid layers are requested, turn top/bottom surfaces to internal @@ -671,173 +700,187 @@ void LayerRegion::prepare_fill_surfaces() } #endif - // turn too small internal regions into solid regions according to the user setting - if (! spiral_mode && this->region().config().sparse_infill_density.value > 0) { - // scaling an area requires two calls! - double min_area = scale_(scale_(this->region().config().minimum_sparse_infill_area.value)); - for (Surface &surface : this->fill_surfaces.surfaces) - if (surface.surface_type == stInternal && surface.area() <= min_area) - surface.surface_type = stInternalSolid; - } + // turn too small internal regions into solid regions according to the user setting + if (!spiral_mode && this->region().config().sparse_infill_density.value > 0) + { + // scaling an area requires two calls! + double min_area = scale_(scale_(this->region().config().minimum_sparse_infill_area.value)); + for (Surface &surface : this->fill_surfaces.surfaces) + if (surface.surface_type == stInternal && surface.area() <= min_area) + surface.surface_type = stInternalSolid; + } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_slices_to_svg_debug("2_prepare_fill_surfaces-final"); - export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-final"); + export_region_slices_to_svg_debug("2_prepare_fill_surfaces-final"); + export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -} + } -double LayerRegion::infill_area_threshold() const -{ - double ss = this->flow(frSolidInfill).scaled_spacing(); - return ss*ss; -} + double LayerRegion::infill_area_threshold() const + { + double ss = this->flow(frSolidInfill).scaled_spacing(); + return ss * ss; + } -void LayerRegion::trim_surfaces(const Polygons &trimming_polygons) -{ + void LayerRegion::trim_surfaces(const Polygons &trimming_polygons) + { #ifndef NDEBUG - for (const Surface &surface : this->slices.surfaces) - assert(surface.surface_type == stInternal); + for (const Surface &surface : this->slices.surfaces) + assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - this->slices.set(intersection_ex(this->slices.surfaces, trimming_polygons), stInternal); -} + this->slices.set(intersection_ex(this->slices.surfaces, trimming_polygons), stInternal); + } -void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons) -{ + void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons) + { #ifndef NDEBUG - for (const Surface &surface : this->slices.surfaces) - assert(surface.surface_type == stInternal); + for (const Surface &surface : this->slices.surfaces) + assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - Polygons tmp = intersection(this->slices.surfaces, trimming_polygons); - append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step))); - this->slices.set(union_ex(tmp), stInternal); -} + Polygons tmp = intersection(this->slices.surfaces, trimming_polygons); + append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step))); + this->slices.set(union_ex(tmp), stInternal); + } -void LayerRegion::export_region_slices_to_svg(const char *path) const -{ - BoundingBox bbox; - for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) - bbox.merge(get_extents(surface->expolygon)); - Point legend_size = export_surface_type_legend_to_svg_box_size(); - Point legend_pos(bbox.min(0), bbox.max(1)); - bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); - - SVG svg(path, bbox); - const float transparency = 0.5f; - for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) - svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); - for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) - svg.draw(surface->expolygon.lines(), surface_type_to_color_name(surface->surface_type)); - export_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); -} + void LayerRegion::export_region_slices_to_svg(const char *path) const + { + BoundingBox bbox; + for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) + bbox.merge(get_extents(surface->expolygon)); + Point legend_size = export_surface_type_legend_to_svg_box_size(); + Point legend_pos(bbox.min(0), bbox.max(1)); + bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); + + SVG svg(path, bbox); + const float transparency = 0.5f; + for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) + svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); + for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) + svg.draw(surface->expolygon.lines(), surface_type_to_color_name(surface->surface_type)); + export_surface_type_legend_to_svg(svg, legend_pos); + svg.Close(); + } -// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. -void LayerRegion::export_region_slices_to_svg_debug(const char *name) const -{ - static std::map idx_map; - size_t &idx = idx_map[name]; - this->export_region_slices_to_svg(debug_out_path("LayerRegion-slices-%s-%d.svg", name, idx ++).c_str()); -} + // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. + void LayerRegion::export_region_slices_to_svg_debug(const char *name) const + { + static std::map idx_map; + size_t &idx = idx_map[name]; + this->export_region_slices_to_svg(debug_out_path("LayerRegion-slices-%s-%d.svg", name, idx++).c_str()); + } -void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const -{ - BoundingBox bbox; - for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) - bbox.merge(get_extents(surface->expolygon)); - Point legend_size = export_surface_type_legend_to_svg_box_size(); - Point legend_pos(bbox.min(0), bbox.max(1)); - bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); - - SVG svg(path, bbox); - const float transparency = 0.5f; - for (const Surface &surface : this->fill_surfaces.surfaces) { - svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); - svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05)); + void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const + { + BoundingBox bbox; + for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) + bbox.merge(get_extents(surface->expolygon)); + Point legend_size = export_surface_type_legend_to_svg_box_size(); + Point legend_pos(bbox.min(0), bbox.max(1)); + bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); + + SVG svg(path, bbox); + const float transparency = 0.5f; + for (const Surface &surface : this->fill_surfaces.surfaces) + { + svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); + svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05)); + } + export_surface_type_legend_to_svg(svg, legend_pos); + svg.Close(); } - export_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); -} -// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. -void LayerRegion::export_region_fill_surfaces_to_svg_debug(const char *name) const -{ - static std::map idx_map; - size_t &idx = idx_map[name]; - this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-fill_surfaces-%s-%d.svg", name, idx ++).c_str()); -} + // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. + void LayerRegion::export_region_fill_surfaces_to_svg_debug(const char *name) const + { + static std::map idx_map; + size_t &idx = idx_map[name]; + this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-fill_surfaces-%s-%d.svg", name, idx++).c_str()); + } -void LayerRegion::simplify_entity_collection(ExtrusionEntityCollection* entity_collection) -{ - for (size_t i = 0; i < entity_collection->entities.size(); i++) { - if (ExtrusionEntityCollection* collection = dynamic_cast(entity_collection->entities[i])) - this->simplify_entity_collection(collection); - else if (ExtrusionPath* path = dynamic_cast(entity_collection->entities[i])) - this->simplify_path(path); - else if (ExtrusionMultiPath* multipath = dynamic_cast(entity_collection->entities[i])) - this->simplify_multi_path(multipath); - else if (ExtrusionLoop* loop = dynamic_cast(entity_collection->entities[i])) - this->simplify_loop(loop); - else - throw Slic3r::InvalidArgument("Invalid extrusion entity supplied to simplify_entity_collection()"); + void LayerRegion::simplify_entity_collection(ExtrusionEntityCollection *entity_collection) + { + for (size_t i = 0; i < entity_collection->entities.size(); i++) + { + if (ExtrusionEntityCollection *collection = dynamic_cast(entity_collection->entities[i])) + this->simplify_entity_collection(collection); + else if (ExtrusionPath *path = dynamic_cast(entity_collection->entities[i])) + this->simplify_path(path); + else if (ExtrusionMultiPath *multipath = dynamic_cast(entity_collection->entities[i])) + this->simplify_multi_path(multipath); + else if (ExtrusionLoop *loop = dynamic_cast(entity_collection->entities[i])) + this->simplify_loop(loop); + else + throw Slic3r::InvalidArgument("Invalid extrusion entity supplied to simplify_entity_collection()"); + } } -} -void LayerRegion::simplify_path(ExtrusionPath* path) -{ - const auto print_config = this->layer()->object()->print()->config(); - const bool spiral_mode = print_config.spiral_mode; - const bool enable_arc_fitting = print_config.enable_arc_fitting; - const auto scaled_resolution = scaled(print_config.resolution.value); - - if (enable_arc_fitting && - !spiral_mode) { - if (path->role() == erInternalInfill) - path->simplify_by_fitting_arc(SCALED_SPARSE_INFILL_RESOLUTION); + void LayerRegion::simplify_path(ExtrusionPath *path) + { + const auto print_config = this->layer()->object()->print()->config(); + const bool spiral_mode = print_config.spiral_mode; + const bool enable_arc_fitting = print_config.enable_arc_fitting; + const auto scaled_resolution = scaled(print_config.resolution.value); + + if (enable_arc_fitting && + !spiral_mode) + { + if (path->role() == erInternalInfill) + path->simplify_by_fitting_arc(SCALED_SPARSE_INFILL_RESOLUTION); + else + path->simplify_by_fitting_arc(scaled_resolution); + } else - path->simplify_by_fitting_arc(scaled_resolution); - } else { - path->simplify(scaled_resolution); + { + path->simplify(scaled_resolution); + } } -} -void LayerRegion::simplify_multi_path(ExtrusionMultiPath* multipath) -{ - const auto print_config = this->layer()->object()->print()->config(); - const bool spiral_mode = print_config.spiral_mode; - const bool enable_arc_fitting = print_config.enable_arc_fitting; - const auto scaled_resolution = scaled(print_config.resolution.value); + void LayerRegion::simplify_multi_path(ExtrusionMultiPath *multipath) + { + const auto print_config = this->layer()->object()->print()->config(); + const bool spiral_mode = print_config.spiral_mode; + const bool enable_arc_fitting = print_config.enable_arc_fitting; + const auto scaled_resolution = scaled(print_config.resolution.value); - for (size_t i = 0; i < multipath->paths.size(); ++i) { - if (enable_arc_fitting && - !spiral_mode) { - if (multipath->paths[i].role() == erInternalInfill) - multipath->paths[i].simplify_by_fitting_arc(SCALED_SPARSE_INFILL_RESOLUTION); + for (size_t i = 0; i < multipath->paths.size(); ++i) + { + if (enable_arc_fitting && + !spiral_mode) + { + if (multipath->paths[i].role() == erInternalInfill) + multipath->paths[i].simplify_by_fitting_arc(SCALED_SPARSE_INFILL_RESOLUTION); + else + multipath->paths[i].simplify_by_fitting_arc(scaled_resolution); + } else - multipath->paths[i].simplify_by_fitting_arc(scaled_resolution); - } else { - multipath->paths[i].simplify(scaled_resolution); + { + multipath->paths[i].simplify(scaled_resolution); + } } } -} -void LayerRegion::simplify_loop(ExtrusionLoop* loop) -{ - const auto print_config = this->layer()->object()->print()->config(); - const bool spiral_mode = print_config.spiral_mode; - const bool enable_arc_fitting = print_config.enable_arc_fitting; - const auto scaled_resolution = scaled(print_config.resolution.value); + void LayerRegion::simplify_loop(ExtrusionLoop *loop) + { + const auto print_config = this->layer()->object()->print()->config(); + const bool spiral_mode = print_config.spiral_mode; + const bool enable_arc_fitting = print_config.enable_arc_fitting; + const auto scaled_resolution = scaled(print_config.resolution.value); - for (size_t i = 0; i < loop->paths.size(); ++i) { - if (enable_arc_fitting && - !spiral_mode) { - if (loop->paths[i].role() == erInternalInfill) - loop->paths[i].simplify_by_fitting_arc(SCALED_SPARSE_INFILL_RESOLUTION); + for (size_t i = 0; i < loop->paths.size(); ++i) + { + if (enable_arc_fitting && + !spiral_mode) + { + if (loop->paths[i].role() == erInternalInfill) + loop->paths[i].simplify_by_fitting_arc(SCALED_SPARSE_INFILL_RESOLUTION); + else + loop->paths[i].simplify_by_fitting_arc(scaled_resolution); + } else - loop->paths[i].simplify_by_fitting_arc(scaled_resolution); - } else { - loop->paths[i].simplify(scaled_resolution); + { + loop->paths[i].simplify(scaled_resolution); + } } } -} } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index da16a7aa82..775bb0c4d9 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -17,46 +17,48 @@ #include "FuzzySkin.hpp" static const double narrow_loop_length_threshold = 10; -//BBS: when the width of expolygon is smaller than -//ext_perimeter_width + ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE), -//we think it's small detail area and will generate smaller line width for it +// BBS: when the width of expolygon is smaller than +// ext_perimeter_width + ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE), +// we think it's small detail area and will generate smaller line width for it static constexpr double SMALLER_EXT_INSET_OVERLAP_TOLERANCE = 0.22; -namespace Slic3r { +namespace Slic3r +{ -// Produces a random value between 0 and 1. Thread-safe. -static double random_value() { - thread_local std::random_device rd; - // Hash thread ID for random number seed if no hardware rng seed is available - thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id())); - thread_local std::uniform_real_distribution dist(0.0, 1.0); - return dist(gen); -} + // Produces a random value between 0 and 1. Thread-safe. + static double random_value() + { + thread_local std::random_device rd; + // Hash thread ID for random number seed if no hardware rng seed is available + thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id())); + thread_local std::uniform_real_distribution dist(0.0, 1.0); + return dist(gen); + } -// Hierarchy of perimeters. -class PerimeterGeneratorLoop { -public: - // Polygon of this contour. - Polygon polygon; - // Is it a contour or a hole? - // Contours are CCW oriented, holes are CW oriented. - bool is_contour; - // BBS: is perimeter using smaller width - bool is_smaller_width_perimeter; - // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. - unsigned short depth; - // Slow down speed for circle - bool need_circle_compensation = false; - // Children contour, may be both CCW and CW oriented (outer contours or holes). - std::vector children; - - PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool is_small_width_perimeter = false, bool need_circle_compensation_ = false) : - polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), need_circle_compensation(need_circle_compensation_) {} - // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). - bool is_external() const { return this->depth == 0; } - // An island, which may have holes, but it does not have another internal island. - bool is_internal_contour() const; -}; + // Hierarchy of perimeters. + class PerimeterGeneratorLoop + { + public: + // Polygon of this contour. + Polygon polygon; + // Is it a contour or a hole? + // Contours are CCW oriented, holes are CW oriented. + bool is_contour; + // BBS: is perimeter using smaller width + bool is_smaller_width_perimeter; + // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. + unsigned short depth; + // Slow down speed for circle + bool need_circle_compensation = false; + // Children contour, may be both CCW and CW oriented (outer contours or holes). + std::vector children; + + PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool is_small_width_perimeter = false, bool need_circle_compensation_ = false) : polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), need_circle_compensation(need_circle_compensation_) {} + // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). + bool is_external() const { return this->depth == 0; } + // An island, which may have holes, but it does not have another internal island. + bool is_internal_contour() const; + }; #if 0 // Thanks Cura developers for this function. @@ -139,1750 +141,1936 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy ext_lines.junctions = std::move(out); } #endif -using PerimeterGeneratorLoops = std::vector; - -static void lowpass_filter_by_paths_overhang_degree(ExtrusionPaths& paths) { - const double filter_range = scale_(6.5); - const double threshold_length = scale_(1.2); - - //0.save old overhang series first which is input of filter - const int path_num = paths.size(); - if (path_num < 2) - //don't need to do filting if only has one path in vector - return; - std::vector old_overhang_series; - old_overhang_series.reserve(path_num); - for (int i = 0; i < path_num; i++) - old_overhang_series.push_back(paths[i].get_overhang_degree()); - - //1.lowpass filter - for (int i = 0; i < path_num; i++) { - double current_length = paths[i].length(); - int current_overhang_degree = old_overhang_series[i]; - if (current_length < threshold_length && - (paths[i].role() == erPerimeter || paths[i].role() == erExternalPerimeter)) { - double left_total_length = (filter_range - current_length) / 2; - double right_total_length = left_total_length; - - double temp_length; - int j = i - 1; - int index; - std::vector> neighbor_path; - while (left_total_length > 0) { - index = (j < 0) ? path_num - 1 : j; - if (paths[index].role() == erOverhangPerimeter) - break; - temp_length = paths[index].length(); - if (temp_length > left_total_length) - neighbor_path.emplace_back(std::pair(left_total_length, old_overhang_series[index])); - else - neighbor_path.emplace_back(std::pair(temp_length, old_overhang_series[index])); - left_total_length -= temp_length; - j = index; - j--; - } + using PerimeterGeneratorLoops = std::vector; - j = i + 1; - while (right_total_length > 0) { - index = j % path_num; - if (paths[index].role() == erOverhangPerimeter) - break; - temp_length = paths[index].length(); - if (temp_length > right_total_length) - neighbor_path.emplace_back(std::pair(right_total_length, old_overhang_series[index])); - else - neighbor_path.emplace_back(std::pair(temp_length, old_overhang_series[index])); - right_total_length -= temp_length; - j++; - } + static void lowpass_filter_by_paths_overhang_degree(ExtrusionPaths &paths) + { + const double filter_range = scale_(6.5); + const double threshold_length = scale_(1.2); + + // 0.save old overhang series first which is input of filter + const int path_num = paths.size(); + if (path_num < 2) + // don't need to do filting if only has one path in vector + return; + std::vector old_overhang_series; + old_overhang_series.reserve(path_num); + for (int i = 0; i < path_num; i++) + old_overhang_series.push_back(paths[i].get_overhang_degree()); + + // 1.lowpass filter + for (int i = 0; i < path_num; i++) + { + double current_length = paths[i].length(); + int current_overhang_degree = old_overhang_series[i]; + if (current_length < threshold_length && + (paths[i].role() == erPerimeter || paths[i].role() == erExternalPerimeter)) + { + double left_total_length = (filter_range - current_length) / 2; + double right_total_length = left_total_length; + + double temp_length; + int j = i - 1; + int index; + std::vector> neighbor_path; + while (left_total_length > 0) + { + index = (j < 0) ? path_num - 1 : j; + if (paths[index].role() == erOverhangPerimeter) + break; + temp_length = paths[index].length(); + if (temp_length > left_total_length) + neighbor_path.emplace_back(std::pair(left_total_length, old_overhang_series[index])); + else + neighbor_path.emplace_back(std::pair(temp_length, old_overhang_series[index])); + left_total_length -= temp_length; + j = index; + j--; + } + + j = i + 1; + while (right_total_length > 0) + { + index = j % path_num; + if (paths[index].role() == erOverhangPerimeter) + break; + temp_length = paths[index].length(); + if (temp_length > right_total_length) + neighbor_path.emplace_back(std::pair(right_total_length, old_overhang_series[index])); + else + neighbor_path.emplace_back(std::pair(temp_length, old_overhang_series[index])); + right_total_length -= temp_length; + j++; + } - double sum = 0; - double length_sum = 0; - for (auto it = neighbor_path.begin(); it != neighbor_path.end(); it++) { - sum += (it->first * it->second); - length_sum += it->first; + double sum = 0; + double length_sum = 0; + for (auto it = neighbor_path.begin(); it != neighbor_path.end(); it++) + { + sum += (it->first * it->second); + length_sum += it->first; + } + + double average_overhang = (double)(current_length * current_overhang_degree + sum) / (length_sum + current_length); + paths[i].set_overhang_degree((int)average_overhang); } + } - double average_overhang = (double)(current_length * current_overhang_degree + sum) / (length_sum + current_length); - paths[i].set_overhang_degree((int)average_overhang); + // 2.merge path if have same overhang degree. from back to front to avoid data copy + int last_overhang = paths[0].get_overhang_degree(); + auto it = paths.begin() + 1; + while (it != paths.end()) + { + if (last_overhang == it->get_overhang_degree()) + { + // BBS: don't need to append duplicated points, remove the last point + if ((it - 1)->polyline.last_point() == it->polyline.first_point()) + (it - 1)->polyline.points.pop_back(); + (it - 1)->polyline.append(std::move(it->polyline)); + it = paths.erase(it); + } + else + { + last_overhang = it->get_overhang_degree(); + it++; + } } } - //2.merge path if have same overhang degree. from back to front to avoid data copy - int last_overhang = paths[0].get_overhang_degree(); - auto it = paths.begin() + 1; - while (it != paths.end()) + std::pair PerimeterGenerator::dist_boundary(double width) { - if (last_overhang == it->get_overhang_degree()) { - //BBS: don't need to append duplicated points, remove the last point - if ((it-1)->polyline.last_point() == it->polyline.first_point()) - (it-1)->polyline.points.pop_back(); - (it-1)->polyline.append(std::move(it->polyline)); - it = paths.erase(it); - } else { - last_overhang = it->get_overhang_degree(); - it++; - } + std::pair out; + float nozzle_diameter = print_config->nozzle_diameter.get_at(config->wall_filament - 1); + float start_offset = -0.5 * width; + float end_offset = 0.5 * nozzle_diameter; + double degree_0 = scale_(start_offset + 0.5 * (end_offset - start_offset) / (overhang_sampling_number - 1)); + out.first = 0; + out.second = scale_(end_offset) - degree_0; + return out; } -} -std::pair PerimeterGenerator::dist_boundary(double width) -{ - std::pair out; - float nozzle_diameter = print_config->nozzle_diameter.get_at(config->wall_filament - 1); - float start_offset = -0.5 * width; - float end_offset = 0.5 * nozzle_diameter; - double degree_0 = scale_(start_offset + 0.5 * (end_offset - start_offset) / (overhang_sampling_number - 1)); - out.first = 0; - out.second = scale_(end_offset) - degree_0; - return out; -} - -static void detect_bridge_wall(const PerimeterGenerator &perimeter_generator, ExtrusionPaths &paths, const Polylines &remain_polines, ExtrusionRole role, double mm3_per_mm, float width, float height) -{ - for (Polyline poly : remain_polines) { - // check if the line is straight line, which mean if the wall is bridge - Line line(poly.first_point(), poly.last_point()); - if (line.length() < poly.length()) { + static void detect_bridge_wall(const PerimeterGenerator &perimeter_generator, ExtrusionPaths &paths, const Polylines &remain_polines, ExtrusionRole role, double mm3_per_mm, float width, float height) + { + for (Polyline poly : remain_polines) + { + // check if the line is straight line, which mean if the wall is bridge + Line line(poly.first_point(), poly.last_point()); + if (line.length() < poly.length()) + { + extrusion_paths_append(paths, + std::move(poly), + overhang_sampling_number - 1, + int(0), + role, + mm3_per_mm, + width, + height); + continue; + } + // bridge wall extrusion_paths_append(paths, std::move(poly), - overhang_sampling_number - 1, + overhang_sampling_number, int(0), role, mm3_per_mm, width, height); - continue; } - // bridge wall - extrusion_paths_append(paths, - std::move(poly), - overhang_sampling_number, - int(0), - role, - mm3_per_mm, - width, - height); } -} - -static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) -{ - // loops is an arrayref of ::Loop objects - // turn each one into an ExtrusionLoop object - ExtrusionEntityCollection coll; - for (const PerimeterGeneratorLoop &loop : loops) { - bool is_external = loop.is_external(); - bool is_small_width = loop.is_smaller_width_perimeter; - CustomizeFlag flag = loop.need_circle_compensation ? CustomizeFlag::cfCircleCompensation : CustomizeFlag::cfNone; - - ExtrusionRole role; - ExtrusionLoopRole loop_role; - role = is_external ? erExternalPerimeter : erPerimeter; - if (loop.is_internal_contour()) { - // Note that we set loop role to ContourInternalPerimeter - // also when loop is both internal and external (i.e. - // there's only one contour loop). - loop_role = elrContourInternalPerimeter; - } else { - loop_role = loop.is_contour? elrDefault: elrPerimeterHole; - } - - if( loop.depth == 1 ) { - if (loop_role == elrDefault) - loop_role = elrSecondPerimeter; + static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) + { + // loops is an arrayref of ::Loop objects + // turn each one into an ExtrusionLoop object + ExtrusionEntityCollection coll; + for (const PerimeterGeneratorLoop &loop : loops) + { + bool is_external = loop.is_external(); + bool is_small_width = loop.is_smaller_width_perimeter; + CustomizeFlag flag = loop.need_circle_compensation ? CustomizeFlag::cfCircleCompensation : CustomizeFlag::cfNone; + + ExtrusionRole role; + ExtrusionLoopRole loop_role; + role = is_external ? erExternalPerimeter : erPerimeter; + if (loop.is_internal_contour()) + { + // Note that we set loop role to ContourInternalPerimeter + // also when loop is both internal and external (i.e. + // there's only one contour loop). + loop_role = elrContourInternalPerimeter; + } else - loop_role = loop_role | elrSecondPerimeter; - } + { + loop_role = loop.is_contour ? elrDefault : elrPerimeterHole; + } - // detect overhanging/bridging perimeters - ExtrusionPaths paths; - - // BBS: get lower polygons series, width, mm3_per_mm - const std::vector *lower_polygons_series; - const std::pair *overhang_dist_boundary; - double extrusion_mm3_per_mm; - double extrusion_width; - if (is_external) { - if (is_small_width) { - //BBS: smaller width external perimeter - lower_polygons_series = &perimeter_generator.m_smaller_external_lower_polygons_series; - overhang_dist_boundary = &perimeter_generator.m_smaller_external_overhang_dist_boundary; - extrusion_mm3_per_mm = perimeter_generator.smaller_width_ext_mm3_per_mm(); - extrusion_width = perimeter_generator.smaller_ext_perimeter_flow.width(); - } else { - //BBS: normal external perimeter - lower_polygons_series = &perimeter_generator.m_external_lower_polygons_series; - overhang_dist_boundary = &perimeter_generator.m_external_overhang_dist_boundary; - extrusion_mm3_per_mm = perimeter_generator.ext_mm3_per_mm(); - extrusion_width = perimeter_generator.ext_perimeter_flow.width(); + if (loop.depth == 1) + { + if (loop_role == elrDefault) + loop_role = elrSecondPerimeter; + else + loop_role = loop_role | elrSecondPerimeter; } - } else { - //BBS: normal perimeter - lower_polygons_series = &perimeter_generator.m_lower_polygons_series; - overhang_dist_boundary = &perimeter_generator.m_lower_overhang_dist_boundary; - extrusion_mm3_per_mm = perimeter_generator.mm3_per_mm(); - extrusion_width = perimeter_generator.perimeter_flow.width(); - } - // Apply fuzzy skin if it is enabled for at least some part of the polygon. - const Polygon polygon = apply_fuzzy_skin(loop.polygon, *(perimeter_generator.config), *(perimeter_generator.perimeter_regions), - perimeter_generator.layer_id, loop.depth, loop.is_contour); - - if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) { - // get non 100% overhang paths by intersecting this loop with the grown lower slices - // prepare grown lower layer slices for overhang detection - BoundingBox bbox(polygon.points); - bbox.offset(SCALED_EPSILON); - - Polylines remain_polines; - - //BBS: don't calculate overhang degree when enable fuzzy skin. It's unmeaning - Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_polygons_series->back(), bbox); - - Polylines inside_polines = intersection_pl_2({to_polyline(polygon)}, lower_polygons_series_clipped); - - - remain_polines = diff_pl_2({to_polyline(polygon)}, lower_polygons_series_clipped); - - bool detect_overhang_speed = perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) - && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None; - - if (!detect_overhang_speed) { - if (!inside_polines.empty()) - extrusion_paths_append( - paths, - std::move(inside_polines), - 0, - int(0), - role, - extrusion_mm3_per_mm, - extrusion_width, - (float)perimeter_generator.layer_height); - } else { - Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_polygons_series->front(), bbox); - - Polylines middle_overhang_polyines = diff_pl_2(inside_polines, lower_polygons_series_clipped); - //BBS: add zero_degree_path - Polylines zero_degree_polines = intersection_pl_2(inside_polines, lower_polygons_series_clipped); - if (!zero_degree_polines.empty()) - extrusion_paths_append( - paths, - std::move(zero_degree_polines), - 0, - int(0), - role, - extrusion_mm3_per_mm, - extrusion_width, - (float)perimeter_generator.layer_height); - //BBS: detect middle line overhang - if (!middle_overhang_polyines.empty()) { - detect_overhang_degree(lower_polygons_series->front(), - role, - extrusion_mm3_per_mm, - extrusion_width, - (float) perimeter_generator.layer_height, - middle_overhang_polyines, - overhang_dist_boundary->first, - overhang_dist_boundary->second, - paths); - } + // detect overhanging/bridging perimeters + ExtrusionPaths paths; + // BBS: get lower polygons series, width, mm3_per_mm + const std::vector *lower_polygons_series; + const std::pair *overhang_dist_boundary; + double extrusion_mm3_per_mm; + double extrusion_width; + if (is_external) + { + if (is_small_width) + { + // BBS: smaller width external perimeter + lower_polygons_series = &perimeter_generator.m_smaller_external_lower_polygons_series; + overhang_dist_boundary = &perimeter_generator.m_smaller_external_overhang_dist_boundary; + extrusion_mm3_per_mm = perimeter_generator.smaller_width_ext_mm3_per_mm(); + extrusion_width = perimeter_generator.smaller_ext_perimeter_flow.width(); + } + else + { + // BBS: normal external perimeter + lower_polygons_series = &perimeter_generator.m_external_lower_polygons_series; + overhang_dist_boundary = &perimeter_generator.m_external_overhang_dist_boundary; + extrusion_mm3_per_mm = perimeter_generator.ext_mm3_per_mm(); + extrusion_width = perimeter_generator.ext_perimeter_flow.width(); + } + } + else + { + // BBS: normal perimeter + lower_polygons_series = &perimeter_generator.m_lower_polygons_series; + overhang_dist_boundary = &perimeter_generator.m_lower_overhang_dist_boundary; + extrusion_mm3_per_mm = perimeter_generator.mm3_per_mm(); + extrusion_width = perimeter_generator.perimeter_flow.width(); } - // get 100% overhang paths by checking what parts of this loop fall - // outside the grown lower slices (thus where the distance between - // the loop centerline and original lower slices is >= half nozzle diameter - if (remain_polines.size() != 0) { - if (!((perimeter_generator.object_config->enable_support || perimeter_generator.object_config->enforce_support_layers > 0) && - perimeter_generator.object_config->support_top_z_distance.value == 0)) { - //detect if the overhang perimeter is bridge - detect_bridge_wall(perimeter_generator, - paths, - remain_polines, - erOverhangPerimeter, - perimeter_generator.mm3_per_mm_overhang(), - perimeter_generator.overhang_flow.width(), - perimeter_generator.overhang_flow.height()); - } else { - detect_bridge_wall( perimeter_generator, - paths, - remain_polines, - role, - extrusion_mm3_per_mm, - extrusion_width, - (float)perimeter_generator.layer_height); - } + // Apply fuzzy skin if it is enabled for at least some part of the polygon. + const Polygon polygon = apply_fuzzy_skin(loop.polygon, *(perimeter_generator.config), *(perimeter_generator.perimeter_regions), + perimeter_generator.layer_id, loop.depth, loop.is_contour); - } + if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) + { + // get non 100% overhang paths by intersecting this loop with the grown lower slices + // prepare grown lower layer slices for overhang detection + BoundingBox bbox(polygon.points); + bbox.offset(SCALED_EPSILON); - if (paths.empty()) - continue; + Polylines remain_polines; - // Reapply the nearest point search for starting point. - // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. - chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); - } else { - ExtrusionPath path(role); - //BBS. - path.polyline = polygon.split_at_first_point(); - path.overhang_degree = 0; - path.curve_degree = 0; - path.mm3_per_mm = extrusion_mm3_per_mm; - path.width = extrusion_width; - path.height = (float)perimeter_generator.layer_height; - paths.emplace_back(std::move(path)); - } + // BBS: don't calculate overhang degree when enable fuzzy skin. It's unmeaning + Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_polygons_series->back(), bbox); - for (ExtrusionPath& path : paths) { - path.set_customize_flag(flag); - } + Polylines inside_polines = intersection_pl_2({to_polyline(polygon)}, lower_polygons_series_clipped); - coll.append(ExtrusionLoop(std::move(paths), loop_role, flag)); - } + remain_polines = diff_pl_2({to_polyline(polygon)}, lower_polygons_series_clipped); - // Append thin walls to the nearest-neighbor search (only for first iteration) - Point zero_point(0, 0); - - if (! thin_walls.empty()) { - BoundingBox bbox; - for (auto &entity : coll.entities) { bbox.merge(entity->as_polyline().bounding_box()); } - for (auto& thin_wall : thin_walls) { - // find the corner of bbox that's farthest from the thin wall - Point corner_far = bbox.min; - if ((corner_far.cast() - thin_wall.first_point().cast()).squaredNorm() < - (bbox.max.cast() - thin_wall.first_point().cast()).squaredNorm()) - corner_far = bbox.max; - zero_point = corner_far; - } - variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities); - thin_walls.clear(); - } + bool detect_overhang_speed = perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None; - // Traverse children and build the final collection. - std::vector> chain = chain_extrusion_entities(coll.entities, &zero_point); - ExtrusionEntityCollection out; - for (const std::pair &idx : chain) { - assert(coll.entities[idx.first] != nullptr); - if (idx.first >= loops.size()) { - // This is a thin wall. - out.entities.reserve(out.entities.size() + 1); - out.entities.emplace_back(coll.entities[idx.first]); - coll.entities[idx.first] = nullptr; - if (idx.second) - out.entities.back()->reverse(); - } else { - const PerimeterGeneratorLoop &loop = loops[idx.first]; - assert(thin_walls.empty()); - ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls); - out.entities.reserve(out.entities.size() + children.entities.size() + 1); - ExtrusionLoop *eloop = static_cast(coll.entities[idx.first]); - coll.entities[idx.first] = nullptr; - if (loop.is_contour) { - eloop->make_counter_clockwise(); - out.append(std::move(children.entities)); - out.entities.emplace_back(eloop); - } else { - eloop->make_clockwise(); - out.entities.emplace_back(eloop); - out.append(std::move(children.entities)); - } - } - } - return out; -} + if (!detect_overhang_speed) + { + if (!inside_polines.empty()) + extrusion_paths_append( + paths, + std::move(inside_polines), + 0, + int(0), + role, + extrusion_mm3_per_mm, + extrusion_width, + (float)perimeter_generator.layer_height); + } + else + { + Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_polygons_series->front(), bbox); + + Polylines middle_overhang_polyines = diff_pl_2(inside_polines, lower_polygons_series_clipped); + // BBS: add zero_degree_path + Polylines zero_degree_polines = intersection_pl_2(inside_polines, lower_polygons_series_clipped); + if (!zero_degree_polines.empty()) + extrusion_paths_append( + paths, + std::move(zero_degree_polines), + 0, + int(0), + role, + extrusion_mm3_per_mm, + extrusion_width, + (float)perimeter_generator.layer_height); + // BBS: detect middle line overhang + if (!middle_overhang_polyines.empty()) + { + detect_overhang_degree(lower_polygons_series->front(), + role, + extrusion_mm3_per_mm, + extrusion_width, + (float)perimeter_generator.layer_height, + middle_overhang_polyines, + overhang_dist_boundary->first, + overhang_dist_boundary->second, + paths); + } + } + // get 100% overhang paths by checking what parts of this loop fall + // outside the grown lower slices (thus where the distance between + // the loop centerline and original lower slices is >= half nozzle diameter + if (remain_polines.size() != 0) + { + if (!((perimeter_generator.object_config->enable_support || perimeter_generator.object_config->enforce_support_layers > 0) && + perimeter_generator.object_config->support_top_z_distance.value == 0)) + { + // detect if the overhang perimeter is bridge + detect_bridge_wall(perimeter_generator, + paths, + remain_polines, + erOverhangPerimeter, + perimeter_generator.mm3_per_mm_overhang(), + perimeter_generator.overhang_flow.width(), + perimeter_generator.overhang_flow.height()); + } + else + { + detect_bridge_wall(perimeter_generator, + paths, + remain_polines, + role, + extrusion_mm3_per_mm, + extrusion_width, + (float)perimeter_generator.layer_height); + } + } -struct PerimeterGeneratorArachneExtrusion -{ - Arachne::ExtrusionLine* extrusion = nullptr; - // Indicates if closed ExtrusionLine is a contour or a hole. Used it only when ExtrusionLine is a closed loop. - bool is_contour = false; - // Should this extrusion be fuzzyfied on path generation? - bool fuzzify = false; -}; + if (paths.empty()) + continue; + // Reapply the nearest point search for starting point. + // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. + chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); + } + else + { + ExtrusionPath path(role); + // BBS. + path.polyline = polygon.split_at_first_point(); + path.overhang_degree = 0; + path.curve_degree = 0; + path.mm3_per_mm = extrusion_mm3_per_mm; + path.width = extrusion_width; + path.height = (float)perimeter_generator.layer_height; + paths.emplace_back(std::move(path)); + } -static void smooth_overhang_level(ExtrusionPaths &paths) -{ - const double threshold_length = scale_(0.8); - const double filter_range = scale_(6.5); - - // 0.save old overhang series first which is input of filter - const int path_num = paths.size(); - if (path_num < 2) - // don't need to do filting if only has one path in vector - return; - std::vector old_overhang_series; - old_overhang_series.reserve(path_num); - for (int i = 0; i < path_num; i++) old_overhang_series.push_back(paths[i].get_overhang_degree()); - - for (int i = 0; i < path_num;) { - if ((paths[i].role() != erPerimeter && paths[i].role() != erExternalPerimeter)) { - i++; - continue; + for (ExtrusionPath &path : paths) + { + path.set_customize_flag(flag); + } + + coll.append(ExtrusionLoop(std::move(paths), loop_role, flag)); } - double current_length = paths[i].length(); - int current_overhang_degree = old_overhang_series[i]; - double total_lens = current_length; - int pt = i + 1; + // Append thin walls to the nearest-neighbor search (only for first iteration) + Point zero_point(0, 0); - for (; pt < path_num; pt++) { - if (paths[pt].get_overhang_degree() != current_overhang_degree || (paths[pt].role() != erPerimeter && paths[pt].role() != erExternalPerimeter)) { - break; + if (!thin_walls.empty()) + { + BoundingBox bbox; + for (auto &entity : coll.entities) + { + bbox.merge(entity->as_polyline().bounding_box()); + } + for (auto &thin_wall : thin_walls) + { + // find the corner of bbox that's farthest from the thin wall + Point corner_far = bbox.min; + if ((corner_far.cast() - thin_wall.first_point().cast()).squaredNorm() < + (bbox.max.cast() - thin_wall.first_point().cast()).squaredNorm()) + corner_far = bbox.max; + zero_point = corner_far; } - total_lens += paths[pt].length(); + variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities); + thin_walls.clear(); } - if (total_lens < threshold_length) { - double left_total_length = (filter_range - total_lens) / 2; - double right_total_length = left_total_length; - - double temp_length; - int j = i - 1; - int index; - std::vector> neighbor_path; - while (left_total_length > 0) { - index = (j < 0) ? path_num - 1 : j; - if (paths[index].role() == erOverhangPerimeter) break; - temp_length = paths[index].length(); - if (temp_length > left_total_length) - neighbor_path.emplace_back(std::pair(left_total_length, old_overhang_series[index])); + // Traverse children and build the final collection. + std::vector> chain = chain_extrusion_entities(coll.entities, &zero_point); + ExtrusionEntityCollection out; + for (const std::pair &idx : chain) + { + assert(coll.entities[idx.first] != nullptr); + if (idx.first >= loops.size()) + { + // This is a thin wall. + out.entities.reserve(out.entities.size() + 1); + out.entities.emplace_back(coll.entities[idx.first]); + coll.entities[idx.first] = nullptr; + if (idx.second) + out.entities.back()->reverse(); + } + else + { + const PerimeterGeneratorLoop &loop = loops[idx.first]; + assert(thin_walls.empty()); + ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls); + out.entities.reserve(out.entities.size() + children.entities.size() + 1); + ExtrusionLoop *eloop = static_cast(coll.entities[idx.first]); + coll.entities[idx.first] = nullptr; + if (loop.is_contour) + { + eloop->make_counter_clockwise(); + out.append(std::move(children.entities)); + out.entities.emplace_back(eloop); + } else - neighbor_path.emplace_back(std::pair(temp_length, old_overhang_series[index])); - left_total_length -= temp_length; - j = index; - j--; + { + eloop->make_clockwise(); + out.entities.emplace_back(eloop); + out.append(std::move(children.entities)); + } } + } + return out; + } - j = pt; - while (right_total_length > 0) { - index = j % path_num; - if (paths[index].role() == erOverhangPerimeter) break; - temp_length = paths[index].length(); - if (temp_length > right_total_length) - neighbor_path.emplace_back(std::pair(right_total_length, old_overhang_series[index])); - else - neighbor_path.emplace_back(std::pair(temp_length, old_overhang_series[index])); - right_total_length -= temp_length; - j++; + struct PerimeterGeneratorArachneExtrusion + { + Arachne::ExtrusionLine *extrusion = nullptr; + // Indicates if closed ExtrusionLine is a contour or a hole. Used it only when ExtrusionLine is a closed loop. + bool is_contour = false; + // Should this extrusion be fuzzyfied on path generation? + bool fuzzify = false; + }; + + static void smooth_overhang_level(ExtrusionPaths &paths) + { + const double threshold_length = scale_(0.8); + const double filter_range = scale_(6.5); + + // 0.save old overhang series first which is input of filter + const int path_num = paths.size(); + if (path_num < 2) + // don't need to do filting if only has one path in vector + return; + std::vector old_overhang_series; + old_overhang_series.reserve(path_num); + for (int i = 0; i < path_num; i++) + old_overhang_series.push_back(paths[i].get_overhang_degree()); + + for (int i = 0; i < path_num;) + { + if ((paths[i].role() != erPerimeter && paths[i].role() != erExternalPerimeter)) + { + i++; + continue; } - double sum = 0; - double length_sum = 0; - for (auto it = neighbor_path.begin(); it != neighbor_path.end(); it++) { - sum += (it->first * it->second); - length_sum += it->first; + double current_length = paths[i].length(); + int current_overhang_degree = old_overhang_series[i]; + double total_lens = current_length; + int pt = i + 1; + + for (; pt < path_num; pt++) + { + if (paths[pt].get_overhang_degree() != current_overhang_degree || (paths[pt].role() != erPerimeter && paths[pt].role() != erExternalPerimeter)) + { + break; + } + total_lens += paths[pt].length(); } - double average_overhang = (double) (total_lens * current_overhang_degree + sum) / (length_sum + total_lens); + if (total_lens < threshold_length) + { + double left_total_length = (filter_range - total_lens) / 2; + double right_total_length = left_total_length; + + double temp_length; + int j = i - 1; + int index; + std::vector> neighbor_path; + while (left_total_length > 0) + { + index = (j < 0) ? path_num - 1 : j; + if (paths[index].role() == erOverhangPerimeter) + break; + temp_length = paths[index].length(); + if (temp_length > left_total_length) + neighbor_path.emplace_back(std::pair(left_total_length, old_overhang_series[index])); + else + neighbor_path.emplace_back(std::pair(temp_length, old_overhang_series[index])); + left_total_length -= temp_length; + j = index; + j--; + } - for (int idx=i; idx 0) + { + index = j % path_num; + if (paths[index].role() == erOverhangPerimeter) + break; + temp_length = paths[index].length(); + if (temp_length > right_total_length) + neighbor_path.emplace_back(std::pair(right_total_length, old_overhang_series[index])); + else + neighbor_path.emplace_back(std::pair(temp_length, old_overhang_series[index])); + right_total_length -= temp_length; + j++; + } - i = pt; + double sum = 0; + double length_sum = 0; + for (auto it = neighbor_path.begin(); it != neighbor_path.end(); it++) + { + sum += (it->first * it->second); + length_sum += it->first; + } + + double average_overhang = (double)(total_lens * current_overhang_degree + sum) / (length_sum + total_lens); + + for (int idx = i; idx < pt; idx++) + paths[idx].set_overhang_degree((int)average_overhang); + } + + i = pt; + } } -} -static void detect_brigde_wall_arachne(const PerimeterGenerator &perimeter_generator, ExtrusionPaths &paths, const ClipperLib_Z::Paths &path_overhang, const ExtrusionRole role, const Flow &flow) -{ - for (ClipperLib_Z::Path path : path_overhang) { - // check if the line is straight line, which mean if the wall is bridge - ThickPolyline thick_polyline = Arachne::to_thick_polyline(path); + static void detect_brigde_wall_arachne(const PerimeterGenerator &perimeter_generator, ExtrusionPaths &paths, const ClipperLib_Z::Paths &path_overhang, const ExtrusionRole role, const Flow &flow) + { + for (ClipperLib_Z::Path path : path_overhang) + { + // check if the line is straight line, which mean if the wall is bridge + ThickPolyline thick_polyline = Arachne::to_thick_polyline(path); + + Line line(thick_polyline.front(), thick_polyline.back()); + if (line.length() < thick_polyline.length()) + { + extrusion_path_append(paths, + std::move(thick_polyline), + role, + flow, + overhang_sampling_number - 1); + continue; + } - Line line(thick_polyline.front(), thick_polyline.back()); - if (line.length() < thick_polyline.length()) { extrusion_path_append(paths, std::move(thick_polyline), role, flow, - overhang_sampling_number - 1); - continue; + overhang_sampling_number); } - - extrusion_path_append(paths, - std::move(thick_polyline), - role, - flow, - overhang_sampling_number); } -} -static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& perimeter_generator, std::vector& pg_extrusions) -{ - using ZPath = ClipperLib_Z::Path; - using ZPaths = ClipperLib_Z::Paths; + static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &perimeter_generator, std::vector &pg_extrusions) + { + using ZPath = ClipperLib_Z::Path; + using ZPaths = ClipperLib_Z::Paths; - ExtrusionEntityCollection extrusion_coll; - if (perimeter_generator.print_config->z_direction_outwall_speed_continuous) - extrusion_coll.loop_node_range.first = perimeter_generator.loop_nodes->size(); + ExtrusionEntityCollection extrusion_coll; + if (perimeter_generator.print_config->z_direction_outwall_speed_continuous) + extrusion_coll.loop_node_range.first = perimeter_generator.loop_nodes->size(); - for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) { - Arachne::ExtrusionLine* extrusion = pg_extrusion.extrusion; - if (extrusion->empty()) - continue; - //get extrusion date - const bool is_external = extrusion->inset_idx == 0; - ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter; - - if (is_external && perimeter_generator.print_config->z_direction_outwall_speed_continuous) { - LoopNode node; - NodeContour node_contour; - node_contour.is_loop = extrusion->is_closed; - for (size_t i = 0; i < extrusion->junctions.size(); i++) { - node_contour.pts.push_back(extrusion->junctions[i].p); - node_contour.widths.push_back(extrusion->junctions[i].w); - } - node.node_contour = node_contour; - node.node_id = perimeter_generator.loop_nodes->size(); - node.loop_id = extrusion_coll.entities.size(); - node.bbox = get_extents(node.node_contour.pts); - node.bbox.offset(perimeter_generator.config->outer_wall_line_width/2); - perimeter_generator.loop_nodes->push_back(std::move(node)); - } + for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) + { + Arachne::ExtrusionLine *extrusion = pg_extrusion.extrusion; + if (extrusion->empty()) + continue; + // get extrusion date + const bool is_external = extrusion->inset_idx == 0; + ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter; - // Apply fuzzy skin if it is enabled for at least some part of the ExtrusionLine. - *extrusion = apply_fuzzy_skin(*extrusion, *(perimeter_generator.config), *(perimeter_generator.perimeter_regions), perimeter_generator.layer_id, - pg_extrusion.extrusion->inset_idx, !pg_extrusion.extrusion->is_closed || pg_extrusion.is_contour); - - ExtrusionPaths paths; - // detect overhanging/bridging perimeters - if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) { - ClipperLib_Z::Path extrusion_path; - extrusion_path.reserve(extrusion->size()); - - double nozzle_diameter = perimeter_generator.print_config->nozzle_diameter.get_at(perimeter_generator.config->wall_filament - 1); - Polygons lower_layer_polys = perimeter_generator.lower_slices_polygons(); - - coord_t max_extrusion_width = 0; - BoundingBox extrusion_path_bbox; - for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) { - extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); - extrusion_path_bbox.merge(Point(ej.p.x(), ej.p.y())); - max_extrusion_width = std::max(max_extrusion_width, ej.w); + if (is_external && perimeter_generator.print_config->z_direction_outwall_speed_continuous) + { + LoopNode node; + NodeContour node_contour; + node_contour.is_loop = extrusion->is_closed; + for (size_t i = 0; i < extrusion->junctions.size(); i++) + { + node_contour.pts.push_back(extrusion->junctions[i].p); + node_contour.widths.push_back(extrusion->junctions[i].w); + } + node.node_contour = node_contour; + node.node_id = perimeter_generator.loop_nodes->size(); + node.loop_id = extrusion_coll.entities.size(); + node.bbox = get_extents(node.node_contour.pts); + node.bbox.offset(perimeter_generator.config->outer_wall_line_width / 2); + perimeter_generator.loop_nodes->push_back(std::move(node)); } - extrusion_path_bbox.inflated(max_extrusion_width+scale_(nozzle_diameter)); - Polygons new_lower_polys; - for (size_t idx = 0; idx < lower_layer_polys.size(); ++idx) { - auto new_poly = ClipperUtils::clip_clipper_polygon_with_subject_bbox(lower_layer_polys[idx], extrusion_path_bbox,true); - if (!new_poly.empty()) - new_lower_polys.emplace_back(new_poly); - } + // Apply fuzzy skin if it is enabled for at least some part of the ExtrusionLine. + *extrusion = apply_fuzzy_skin(*extrusion, *(perimeter_generator.config), *(perimeter_generator.perimeter_regions), perimeter_generator.layer_id, + pg_extrusion.extrusion->inset_idx, !pg_extrusion.extrusion->is_closed || pg_extrusion.is_contour); - lower_layer_polys = new_lower_polys; + ExtrusionPaths paths; + // detect overhanging/bridging perimeters + if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) + { + ClipperLib_Z::Path extrusion_path; + extrusion_path.reserve(extrusion->size()); - ZPath subject_path; - for (auto& ej : extrusion->junctions) - subject_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); + double nozzle_diameter = perimeter_generator.print_config->nozzle_diameter.get_at(perimeter_generator.config->wall_filament - 1); + Polygons lower_layer_polys = perimeter_generator.lower_slices_polygons(); - ZPaths clip_paths; - for (auto& poly : lower_layer_polys) { - clip_paths.emplace_back(); - for (auto& p : poly) - clip_paths.back().emplace_back(p.x(), p.y(), 0); - } + coord_t max_extrusion_width = 0; + BoundingBox extrusion_path_bbox; + for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) + { + extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); + extrusion_path_bbox.merge(Point(ej.p.x(), ej.p.y())); + max_extrusion_width = std::max(max_extrusion_width, ej.w); + } + extrusion_path_bbox.inflated(max_extrusion_width + scale_(nozzle_diameter)); - if (perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) - && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { - bool is_external = extrusion->inset_idx == 0; - Flow flow = is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow; - ExtrusionRole role = is_external ? ExtrusionRole::erExternalPerimeter : ExtrusionRole::erPerimeter; - paths = detect_overhang_degree(flow, role, lower_layer_polys, clip_paths, subject_path, nozzle_diameter); - } - else { - ExtrusionPaths temp_paths; - ZPaths path_non_overhang = clip_extrusion(subject_path, clip_paths, ClipperLib_Z::ctIntersection); - // get non-overhang paths by intersecting this loop with the grown lower slices - extrusion_paths_append(temp_paths, path_non_overhang, role, - is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); - paths = std::move(temp_paths); - } - // get overhang paths by checking what parts of this loop fall - // outside the grown lower slices (thus where the distance between - // the loop centerline and original lower slices is >= half nozzle diameter - // detect if the overhang perimeter is bridge - ZPaths path_overhang = clip_extrusion(subject_path, clip_paths, ClipperLib_Z::ctDifference); - bool zero_z_support = (perimeter_generator.object_config->enable_support || perimeter_generator.object_config->enforce_support_layers > 0) && perimeter_generator.object_config->support_top_z_distance.value == 0; - - if(zero_z_support) - detect_brigde_wall_arachne(perimeter_generator, paths, path_overhang, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); - else - detect_brigde_wall_arachne(perimeter_generator, paths, path_overhang, erOverhangPerimeter, perimeter_generator.overhang_flow); - // Reapply the nearest point search for starting point. - // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. - // Arachne sometimes creates extrusion with zero-length (just two same endpoints); - if (!paths.empty()) { - Point start_point = paths.front().first_point(); - if (!extrusion->is_closed) { - // Especially for open extrusion, we need to select a starting point that is at the start - // or the end of the extrusions to make one continuous line. Also, we prefer a non-overhang - // starting point. - struct PointInfo + Polygons new_lower_polys; + for (size_t idx = 0; idx < lower_layer_polys.size(); ++idx) + { + auto new_poly = ClipperUtils::clip_clipper_polygon_with_subject_bbox(lower_layer_polys[idx], extrusion_path_bbox, true); + if (!new_poly.empty()) + new_lower_polys.emplace_back(new_poly); + } + + lower_layer_polys = new_lower_polys; + + ZPath subject_path; + for (auto &ej : extrusion->junctions) + subject_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); + + ZPaths clip_paths; + for (auto &poly : lower_layer_polys) + { + clip_paths.emplace_back(); + for (auto &p : poly) + clip_paths.back().emplace_back(p.x(), p.y(), 0); + } + + if (perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) + { + bool is_external = extrusion->inset_idx == 0; + Flow flow = is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow; + ExtrusionRole role = is_external ? ExtrusionRole::erExternalPerimeter : ExtrusionRole::erPerimeter; + paths = detect_overhang_degree(flow, role, lower_layer_polys, clip_paths, subject_path, nozzle_diameter); + } + else + { + ExtrusionPaths temp_paths; + ZPaths path_non_overhang = clip_extrusion(subject_path, clip_paths, ClipperLib_Z::ctIntersection); + // get non-overhang paths by intersecting this loop with the grown lower slices + extrusion_paths_append(temp_paths, path_non_overhang, role, + is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); + paths = std::move(temp_paths); + } + // get overhang paths by checking what parts of this loop fall + // outside the grown lower slices (thus where the distance between + // the loop centerline and original lower slices is >= half nozzle diameter + // detect if the overhang perimeter is bridge + ZPaths path_overhang = clip_extrusion(subject_path, clip_paths, ClipperLib_Z::ctDifference); + bool zero_z_support = (perimeter_generator.object_config->enable_support || perimeter_generator.object_config->enforce_support_layers > 0) && perimeter_generator.object_config->support_top_z_distance.value == 0; + + if (zero_z_support) + detect_brigde_wall_arachne(perimeter_generator, paths, path_overhang, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); + else + detect_brigde_wall_arachne(perimeter_generator, paths, path_overhang, erOverhangPerimeter, perimeter_generator.overhang_flow); + // Reapply the nearest point search for starting point. + // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. + // Arachne sometimes creates extrusion with zero-length (just two same endpoints); + if (!paths.empty()) + { + Point start_point = paths.front().first_point(); + if (!extrusion->is_closed) { - size_t occurrence = 0; - bool is_overhang = false; - }; - std::unordered_map point_occurrence; - for (const ExtrusionPath& path : paths) { - ++point_occurrence[path.polyline.first_point()].occurrence; - ++point_occurrence[path.polyline.last_point()].occurrence; - if (path.role() == erOverhangPerimeter) { - point_occurrence[path.polyline.first_point()].is_overhang = true; - point_occurrence[path.polyline.last_point()].is_overhang = true; + // Especially for open extrusion, we need to select a starting point that is at the start + // or the end of the extrusions to make one continuous line. Also, we prefer a non-overhang + // starting point. + struct PointInfo + { + size_t occurrence = 0; + bool is_overhang = false; + }; + std::unordered_map point_occurrence; + for (const ExtrusionPath &path : paths) + { + ++point_occurrence[path.polyline.first_point()].occurrence; + ++point_occurrence[path.polyline.last_point()].occurrence; + if (path.role() == erOverhangPerimeter) + { + point_occurrence[path.polyline.first_point()].is_overhang = true; + point_occurrence[path.polyline.last_point()].is_overhang = true; + } } - } - // Prefer non-overhang point as a starting point. - for (const std::pair pt : point_occurrence) - if (pt.second.occurrence == 1) { - start_point = pt.first; - if (!pt.second.is_overhang) { + // Prefer non-overhang point as a starting point. + for (const std::pair pt : point_occurrence) + if (pt.second.occurrence == 1) + { start_point = pt.first; - break; + if (!pt.second.is_overhang) + { + start_point = pt.first; + break; + } } - } - } + } - chain_and_reorder_extrusion_paths(paths, &start_point); + chain_and_reorder_extrusion_paths(paths, &start_point); - if (perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) - && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { - // BBS: filter the speed - smooth_overhang_level(paths); + if (perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) + { + // BBS: filter the speed + smooth_overhang_level(paths); + } } - } - } else { - extrusion_paths_append(paths, *extrusion, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); - } + else + { + extrusion_paths_append(paths, *extrusion, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); + } - bool apply_hole_compensation = extrusion->shouldApplyHoleCompensation(); + bool apply_hole_compensation = extrusion->shouldApplyHoleCompensation(); - // Append paths to collection. - if (!paths.empty()) { - if (extrusion->is_closed) { - ExtrusionLoop extrusion_loop(std::move(paths), extrusion->is_contour()? elrDefault : elrPerimeterHole); - // Restore the orientation of the extrusion loop. - if (pg_extrusion.is_contour) - extrusion_loop.make_counter_clockwise(); - else - extrusion_loop.make_clockwise(); + // Append paths to collection. + if (!paths.empty()) + { + if (extrusion->is_closed) + { + ExtrusionLoop extrusion_loop(std::move(paths), extrusion->is_contour() ? elrDefault : elrPerimeterHole); + // Restore the orientation of the extrusion loop. + if (pg_extrusion.is_contour) + extrusion_loop.make_counter_clockwise(); + else + extrusion_loop.make_clockwise(); - for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) { - assert(it->polyline.points.size() >= 2); - assert(std::prev(it)->polyline.last_point() == it->polyline.first_point()); - } - assert(extrusion_loop.paths.front().first_point() == extrusion_loop.paths.back().last_point()); + for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) + { + assert(it->polyline.points.size() >= 2); + assert(std::prev(it)->polyline.last_point() == it->polyline.first_point()); + } + assert(extrusion_loop.paths.front().first_point() == extrusion_loop.paths.back().last_point()); - if (apply_hole_compensation) { - for (auto& path : extrusion_loop.paths) - path.set_customize_flag(CustomizeFlag::cfCircleCompensation); - extrusion_loop.set_customize_flag(CustomizeFlag::cfCircleCompensation); + if (apply_hole_compensation) + { + for (auto &path : extrusion_loop.paths) + path.set_customize_flag(CustomizeFlag::cfCircleCompensation); + extrusion_loop.set_customize_flag(CustomizeFlag::cfCircleCompensation); + } + extrusion_coll.append(std::move(extrusion_loop)); } - extrusion_coll.append(std::move(extrusion_loop)); - } - else { - // Because we are processing one ExtrusionLine all ExtrusionPaths should form one connected path. - // But there is possibility that due to numerical issue there is poss - assert([&paths = std::as_const(paths)]() -> bool { + else + { + // Because we are processing one ExtrusionLine all ExtrusionPaths should form one connected path. + // But there is possibility that due to numerical issue there is poss + assert([&paths = std::as_const(paths)]() -> bool + { for (auto it = std::next(paths.begin()); it != paths.end(); ++it) if (std::prev(it)->polyline.last_point() != it->polyline.first_point()) return false; - return true; - }()); - ExtrusionMultiPath multi_path; - multi_path.paths.emplace_back(std::move(paths.front())); - - for (auto it_path = std::next(paths.begin()); it_path != paths.end(); ++it_path) { - if (multi_path.paths.back().last_point() != it_path->first_point()) { - extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path))); - multi_path = ExtrusionMultiPath(); + return true; }()); + ExtrusionMultiPath multi_path; + multi_path.paths.emplace_back(std::move(paths.front())); + + for (auto it_path = std::next(paths.begin()); it_path != paths.end(); ++it_path) + { + if (multi_path.paths.back().last_point() != it_path->first_point()) + { + extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path))); + multi_path = ExtrusionMultiPath(); + } + multi_path.paths.emplace_back(std::move(*it_path)); } - multi_path.paths.emplace_back(std::move(*it_path)); - } - if (apply_hole_compensation) { - for (auto& path : multi_path.paths) - path.set_customize_flag(CustomizeFlag::cfCircleCompensation); - multi_path.set_customize_flag(CustomizeFlag::cfCircleCompensation); + if (apply_hole_compensation) + { + for (auto &path : multi_path.paths) + path.set_customize_flag(CustomizeFlag::cfCircleCompensation); + multi_path.set_customize_flag(CustomizeFlag::cfCircleCompensation); + } + extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path))); } - extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path))); } } + if (perimeter_generator.print_config->z_direction_outwall_speed_continuous) + extrusion_coll.loop_node_range.second = perimeter_generator.loop_nodes->size(); + return extrusion_coll; } - if (perimeter_generator.print_config->z_direction_outwall_speed_continuous) - extrusion_coll.loop_node_range.second = perimeter_generator.loop_nodes->size(); - - return extrusion_coll; -} - - -static Polygons to_polygons_with_flag(const ExPolygon& src, const bool contour_flag, const std::vector& holes_flag, std::vector& flags_out) -{ - Polygons polygons; - polygons.reserve(src.num_contours()); - polygons.push_back(src.contour); - polygons.insert(polygons.end(), src.holes.begin(), src.holes.end()); - flags_out.reserve(holes_flag.size() + 1); - if(contour_flag == true) - flags_out.emplace_back(0); - for (size_t idx = 0; idx < holes_flag.size(); ++idx) - flags_out.emplace_back(holes_flag[idx] + 1); - return polygons; -} - -void PerimeterGenerator::process_classic() -{ - // other perimeters - m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); - coord_t perimeter_width = this->perimeter_flow.scaled_width(); - coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - - // external perimeters - m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); - coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); - coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2; - // Orca: ignore precise_outer_wall if wall_sequence is not InnerOuter - if (config->precise_outer_wall) - ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.width() + this->perimeter_flow.width())); - else - ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); - - // overhang perimeters - m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); - - // solid infill - coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); - - // Calculate the minimum required spacing between two adjacent traces. - // This should be equal to the nominal flow spacing but we experiment - // with some tolerance in order to avoid triggering medial axis when - // some squishing might work. Loops are still spaced by the entire - // flow spacing; this only applies to collapsing parts. - // For ext_min_spacing we use the ext_perimeter_spacing calculated for two adjacent - // external loops (which is the correct way) instead of using ext_perimeter_spacing2 - // 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. - coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); - coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); - bool has_gap_fill = this->config->gap_infill_speed.get_at(get_config_idx_for_filament(*print_config, this->config->wall_filament - 1)) > 0; - - // BBS: this flow is for smaller external perimeter for small area - coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE)); - this->smaller_ext_perimeter_flow = this->ext_perimeter_flow; - // BBS: to be checked - this->smaller_ext_perimeter_flow = this->smaller_ext_perimeter_flow.with_width(SCALING_FACTOR * - (ext_perimeter_width - 0.5 * SMALLER_EXT_INSET_OVERLAP_TOLERANCE * ext_perimeter_spacing)); - m_ext_mm3_per_mm_smaller_width = this->smaller_ext_perimeter_flow.mm3_per_mm(); - - // prepare grown lower layer slices for overhang detection - m_lower_polygons_series = generate_lower_polygons_series(this->perimeter_flow.width()); - m_lower_overhang_dist_boundary = dist_boundary(this->perimeter_flow.width()); - if (ext_perimeter_width == perimeter_width){ - m_external_lower_polygons_series = m_lower_polygons_series; - m_external_overhang_dist_boundary=m_lower_overhang_dist_boundary; - } else { - m_external_lower_polygons_series = generate_lower_polygons_series(this->ext_perimeter_flow.width()); - m_external_overhang_dist_boundary = dist_boundary(this->ext_perimeter_flow.width()); + static Polygons to_polygons_with_flag(const ExPolygon &src, const bool contour_flag, const std::vector &holes_flag, std::vector &flags_out) + { + Polygons polygons; + polygons.reserve(src.num_contours()); + polygons.push_back(src.contour); + polygons.insert(polygons.end(), src.holes.begin(), src.holes.end()); + flags_out.reserve(holes_flag.size() + 1); + if (contour_flag == true) + flags_out.emplace_back(0); + for (size_t idx = 0; idx < holes_flag.size(); ++idx) + flags_out.emplace_back(holes_flag[idx] + 1); + return polygons; } - m_smaller_external_lower_polygons_series = generate_lower_polygons_series(this->smaller_ext_perimeter_flow.width()); - m_smaller_external_overhang_dist_boundary = dist_boundary(this->smaller_ext_perimeter_flow.width()); - // we need to process each island separately because we might have different - // extra perimeters for each one - - // BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled - double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution; - //BBS: reorder the surface to reduce the travel time - ExPolygons surface_exp; - for (const Surface &surface : this->slices->surfaces) - surface_exp.push_back(surface.expolygon); - std::vector surface_order = chain_expolygons(surface_exp); - for (size_t order_idx = 0; order_idx < surface_order.size(); order_idx++) { - const Surface &surface = this->slices->surfaces[surface_order[order_idx]]; - // detect how many perimeters must be generated for this island - int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops - //BBS: set the topmost and bottom most layer to be one wall - if (loop_number > 0 && ((this->object_config->top_one_wall_type != TopOneWallType::None && this->upper_slices == nullptr) || (this->object_config->only_one_wall_first_layer && layer_id == 0))) - loop_number = 0; - - bool counter_circle_compensation = surface.counter_circle_compensation; - std::vector compensation_holes_centers; - for (int i : surface.holes_circle_compensation) { - Point center = surface.expolygon.holes[i].centroid(); - compensation_holes_centers.emplace_back(center); + + void PerimeterGenerator::process_classic() + { + // other perimeters + m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); + coord_t perimeter_width = this->perimeter_flow.scaled_width(); + coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); + + // external perimeters + m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); + coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); + coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); + coord_t ext_perimeter_spacing2; + // Orca: ignore precise_outer_wall if wall_sequence is not InnerOuter + if (config->precise_outer_wall) + ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.width() + this->perimeter_flow.width())); + else + ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); + + // overhang perimeters + m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); + + // solid infill + coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); + + // Calculate the minimum required spacing between two adjacent traces. + // This should be equal to the nominal flow spacing but we experiment + // with some tolerance in order to avoid triggering medial axis when + // some squishing might work. Loops are still spaced by the entire + // flow spacing; this only applies to collapsing parts. + // For ext_min_spacing we use the ext_perimeter_spacing calculated for two adjacent + // external loops (which is the correct way) instead of using ext_perimeter_spacing2 + // 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. + coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); + coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); + bool has_gap_fill = this->config->gap_infill_speed.get_at(get_config_idx_for_filament(*print_config, this->config->wall_filament - 1)) > 0; + + // BBS: this flow is for smaller external perimeter for small area + coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE)); + this->smaller_ext_perimeter_flow = this->ext_perimeter_flow; + // BBS: to be checked + this->smaller_ext_perimeter_flow = this->smaller_ext_perimeter_flow.with_width(SCALING_FACTOR * + (ext_perimeter_width - 0.5 * SMALLER_EXT_INSET_OVERLAP_TOLERANCE * ext_perimeter_spacing)); + m_ext_mm3_per_mm_smaller_width = this->smaller_ext_perimeter_flow.mm3_per_mm(); + + // prepare grown lower layer slices for overhang detection + m_lower_polygons_series = generate_lower_polygons_series(this->perimeter_flow.width()); + m_lower_overhang_dist_boundary = dist_boundary(this->perimeter_flow.width()); + if (ext_perimeter_width == perimeter_width) + { + m_external_lower_polygons_series = m_lower_polygons_series; + m_external_overhang_dist_boundary = m_lower_overhang_dist_boundary; + } + else + { + m_external_lower_polygons_series = generate_lower_polygons_series(this->ext_perimeter_flow.width()); + m_external_overhang_dist_boundary = dist_boundary(this->ext_perimeter_flow.width()); } + m_smaller_external_lower_polygons_series = generate_lower_polygons_series(this->smaller_ext_perimeter_flow.width()); + m_smaller_external_overhang_dist_boundary = dist_boundary(this->smaller_ext_perimeter_flow.width()); + // we need to process each island separately because we might have different + // extra perimeters for each one + + // BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled + double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution; + // BBS: reorder the surface to reduce the travel time + ExPolygons surface_exp; + for (const Surface &surface : this->slices->surfaces) + surface_exp.push_back(surface.expolygon); + std::vector surface_order = chain_expolygons(surface_exp); + for (size_t order_idx = 0; order_idx < surface_order.size(); order_idx++) + { + const Surface &surface = this->slices->surfaces[surface_order[order_idx]]; + // detect how many perimeters must be generated for this island + int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops + // BBS: set the topmost and bottom most layer to be one wall + if (loop_number > 0 && ((this->object_config->top_one_wall_type != TopOneWallType::None && this->upper_slices == nullptr) || (this->object_config->only_one_wall_first_layer && layer_id == 0))) + loop_number = 0; + + bool counter_circle_compensation = surface.counter_circle_compensation; + std::vector compensation_holes_centers; + for (int i : surface.holes_circle_compensation) + { + Point center = surface.expolygon.holes[i].centroid(); + compensation_holes_centers.emplace_back(center); + } - double eps = 1000; - auto is_compensation_hole = [&compensation_holes_centers, &eps](const Polygon &hole) -> bool { - auto iter = std::find_if(compensation_holes_centers.begin(), compensation_holes_centers.end(), [&hole, &eps](const Point &item) { + double eps = 1000; + auto is_compensation_hole = [&compensation_holes_centers, &eps](const Polygon &hole) -> bool + { + auto iter = std::find_if(compensation_holes_centers.begin(), compensation_holes_centers.end(), [&hole, &eps](const Point &item) + { double distance = std::sqrt(std::pow(hole.centroid().x() - item.x(), 2) + std::pow(hole.centroid().y() - item.y(), 2)); - return distance < eps; - }); - - return iter != compensation_holes_centers.end(); - }; - - ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution)); - if (last.size() != 1) - counter_circle_compensation = false; - ExPolygons gaps; - ExPolygons top_fills; - ExPolygons fill_clip; - std::vector outwall_paths; - if (loop_number >= 0) { - // In case no perimeters are to be generated, loop_number will equal to -1. - std::vector contours(loop_number+1); // depth => loops - std::vector holes(loop_number+1); // depth => loops - ThickPolylines thin_walls; - // 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 - // Calculate next onion shell of perimeters. - ExPolygons offsets; - ExPolygons offsets_with_smaller_width; - if (i == 0) { - // look for thin walls - if (this->config->detect_thin_wall) { - // the minimum thickness of a single loop is: - // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - offsets = offset2_ex(last, - -float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1), - +float(ext_min_spacing / 2. - 1)); - // 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->ext_perimeter_flow.nozzle_diameter() / 3)); - ExPolygons expp = opening_ex( - // medial axis requires non-overlapping geometry - diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), - float(min_width / 2.)); - // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - for (ExPolygon &ex : expp) - ex.medial_axis(min_width, ext_perimeter_width + ext_perimeter_spacing2, &thin_walls); - } else { - coord_t ext_perimeter_smaller_width = this->smaller_ext_perimeter_flow.scaled_width(); - for (const ExPolygon& expolygon : last) { - // BBS: judge whether it's narrow but not too long island which is hard to place two line - ExPolygons expolys; - expolys.push_back(expolygon); - ExPolygons offset_result = offset2_ex(expolys, - -float(ext_perimeter_width / 2. + ext_min_spacing_smaller / 2.), - +float(ext_min_spacing_smaller / 2.)); - if (offset_result.empty() && - expolygon.area() < (double)(ext_perimeter_width + ext_min_spacing_smaller) * scale_(narrow_loop_length_threshold)) { - // BBS: for narrow external loop, use smaller line width - ExPolygons temp_result = offset_ex(expolygon, -float(ext_perimeter_smaller_width / 2.)); - offsets_with_smaller_width.insert(offsets_with_smaller_width.end(), temp_result.begin(), temp_result.end()); - } - else { - //BBS: for not narrow loop, use normal external perimeter line width - ExPolygons temp_result = offset_ex(expolygon, -float(ext_perimeter_width / 2.)); - offsets.insert(offsets.end(), temp_result.begin(), temp_result.end()); + return distance < eps; }); + + return iter != compensation_holes_centers.end(); + }; + + ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution)); + if (last.size() != 1) + counter_circle_compensation = false; + ExPolygons gaps; + ExPolygons top_fills; + ExPolygons fill_clip; + std::vector outwall_paths; + if (loop_number >= 0) + { + // In case no perimeters are to be generated, loop_number will equal to -1. + std::vector contours(loop_number + 1); // depth => loops + std::vector holes(loop_number + 1); // depth => loops + ThickPolylines thin_walls; + // 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 + // Calculate next onion shell of perimeters. + ExPolygons offsets; + ExPolygons offsets_with_smaller_width; + if (i == 0) + { + // look for thin walls + if (this->config->detect_thin_wall) + { + // the minimum thickness of a single loop is: + // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 + offsets = offset2_ex(last, + -float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1), + +float(ext_min_spacing / 2. - 1)); + // 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->ext_perimeter_flow.nozzle_diameter() / 3)); + ExPolygons expp = opening_ex( + // medial axis requires non-overlapping geometry + diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), + float(min_width / 2.)); + // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop + for (ExPolygon &ex : expp) + ex.medial_axis(min_width, ext_perimeter_width + ext_perimeter_spacing2, &thin_walls); + } + else + { + coord_t ext_perimeter_smaller_width = this->smaller_ext_perimeter_flow.scaled_width(); + for (const ExPolygon &expolygon : last) + { + // BBS: judge whether it's narrow but not too long island which is hard to place two line + ExPolygons expolys; + expolys.push_back(expolygon); + ExPolygons offset_result = offset2_ex(expolys, + -float(ext_perimeter_width / 2. + ext_min_spacing_smaller / 2.), + +float(ext_min_spacing_smaller / 2.)); + if (offset_result.empty() && + expolygon.area() < (double)(ext_perimeter_width + ext_min_spacing_smaller) * scale_(narrow_loop_length_threshold)) + { + // BBS: for narrow external loop, use smaller line width + ExPolygons temp_result = offset_ex(expolygon, -float(ext_perimeter_smaller_width / 2.)); + offsets_with_smaller_width.insert(offsets_with_smaller_width.end(), temp_result.begin(), temp_result.end()); + } + else + { + // BBS: for not narrow loop, use normal external perimeter line width + ExPolygons temp_result = offset_ex(expolygon, -float(ext_perimeter_width / 2.)); + offsets.insert(offsets.end(), temp_result.begin(), temp_result.end()); + } } } + if (m_spiral_vase && (offsets.size() > 1 || offsets_with_smaller_width.size() > 1)) + { + // Remove all but the largest area polygon. + keep_largest_contour_only(offsets); + // BBS + if (offsets.empty()) + // BBS: only have small width loop, then keep the largest in spiral vase mode + keep_largest_contour_only(offsets_with_smaller_width); + else + // BBS: have large area, clean the small width loop + offsets_with_smaller_width.clear(); + } } - if (m_spiral_vase && (offsets.size() > 1 || offsets_with_smaller_width.size() > 1)) { - // Remove all but the largest area polygon. - keep_largest_contour_only(offsets); - //BBS - if (offsets.empty()) - //BBS: only have small width loop, then keep the largest in spiral vase mode - keep_largest_contour_only(offsets_with_smaller_width); - else - //BBS: have large area, clean the small width loop - offsets_with_smaller_width.clear(); + else + { + // FIXME Is this offset correct if the line width of the inner perimeters differs + // from the line width of the infill? + coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + // BBS + // offsets = this->config->thin_walls ? + // This path will ensure, that the perimeters do not overfill, as in + // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters + // excessively, creating gaps, which then need to be filled in by the not very + // reliable gap fill algorithm. + // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than + // the original. + // offset2_ex(last, + // - float(distance + min_spacing / 2. - 1.), + // float(min_spacing / 2. - 1.)) : + // If "detect thin walls" is not enabled, this paths will be entered, which + // leads to overflows, as in prusa3d/Slic3r GH #32 + // offset_ex(last, - float(distance)); + + // BBS: For internal perimeter, we should "enable" thin wall strategy in which offset2 is used to + // remove too closed line, so that gap fill can be used for such internal narrow area in following + // handling. + offsets = offset2_ex(last, + -float(distance + min_spacing / 2. - 1.), + float(min_spacing / 2. - 1.)); + // look for gaps + if (has_gap_fill) + // 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, -float(0.5 * distance)), + offset(offsets, float(0.5 * distance + 10)))); // safety offset } - } else { - //FIXME Is this offset correct if the line width of the inner perimeters differs - // from the line width of the infill? - coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - //BBS - //offsets = this->config->thin_walls ? - // This path will ensure, that the perimeters do not overfill, as in - // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters - // excessively, creating gaps, which then need to be filled in by the not very - // reliable gap fill algorithm. - // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than - // the original. - //offset2_ex(last, - // - float(distance + min_spacing / 2. - 1.), - // float(min_spacing / 2. - 1.)) : - // If "detect thin walls" is not enabled, this paths will be entered, which - // leads to overflows, as in prusa3d/Slic3r GH #32 - //offset_ex(last, - float(distance)); - - //BBS: For internal perimeter, we should "enable" thin wall strategy in which offset2 is used to - // remove too closed line, so that gap fill can be used for such internal narrow area in following - // handling. - offsets = offset2_ex(last, - -float(distance + min_spacing / 2. - 1.), - float(min_spacing / 2. - 1.)); - // look for gaps - if (has_gap_fill) - // 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, - float(0.5 * distance)), - offset(offsets, float(0.5 * distance + 10)))); // safety offset - } - if (offsets.empty() && offsets_with_smaller_width.empty()) { - // Store the number of loops actually generated. - loop_number = i - 1; - // No region left to be filled in. - last.clear(); - break; - } else if (i > loop_number) { - // If i > loop_number, we were looking just for gaps. - break; - } - { - for (const ExPolygon& expolygon : offsets) { - // Outer contour may overlap with an inner contour, - // inner contour may overlap with another inner contour, - // outer contour may overlap with itself. - //FIXME evaluate the overlaps, annotate each point with an overlap depth, - // compensate for the depth of intersection. - contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, false, counter_circle_compensation)); - if (!expolygon.holes.empty()) { - holes[i].reserve(holes[i].size() + expolygon.holes.size()); - for (const Polygon &hole : expolygon.holes) - holes[i].emplace_back(hole, i, false, false, is_compensation_hole(hole)); - } + if (offsets.empty() && offsets_with_smaller_width.empty()) + { + // Store the number of loops actually generated. + loop_number = i - 1; + // No region left to be filled in. + last.clear(); + break; } - - //BBS: save perimeter loop which use smaller width - if (i == 0) { - //store outer wall - if (print_config->z_direction_outwall_speed_continuous) { - // not loop - for (const ThickPolyline &polyline : thin_walls) { - NodeContour node_contour; - node_contour.is_loop = false; - node_contour.pts = polyline.points; - node_contour.widths.insert(node_contour.widths.end(), polyline.width.begin(), polyline.width.end()); - outwall_paths.push_back(node_contour); + else if (i > loop_number) + { + // If i > loop_number, we were looking just for gaps. + break; + } + { + for (const ExPolygon &expolygon : offsets) + { + // Outer contour may overlap with an inner contour, + // inner contour may overlap with another inner contour, + // outer contour may overlap with itself. + // FIXME evaluate the overlaps, annotate each point with an overlap depth, + // compensate for the depth of intersection. + contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, false, counter_circle_compensation)); + if (!expolygon.holes.empty()) + { + holes[i].reserve(holes[i].size() + expolygon.holes.size()); + for (const Polygon &hole : expolygon.holes) + holes[i].emplace_back(hole, i, false, false, is_compensation_hole(hole)); } + } - // loop - for (const Polyline &polyline : to_polylines(offsets_with_smaller_width)) { - NodeContour node_contour; - node_contour.is_loop = true; - node_contour.pts = polyline.points; - node_contour.widths.push_back(scale_(this->smaller_ext_perimeter_flow.width())); - outwall_paths.push_back(node_contour); + // BBS: save perimeter loop which use smaller width + if (i == 0) + { + // store outer wall + if (print_config->z_direction_outwall_speed_continuous) + { + // not loop + for (const ThickPolyline &polyline : thin_walls) + { + NodeContour node_contour; + node_contour.is_loop = false; + node_contour.pts = polyline.points; + node_contour.widths.insert(node_contour.widths.end(), polyline.width.begin(), polyline.width.end()); + outwall_paths.push_back(node_contour); + } + + // loop + for (const Polyline &polyline : to_polylines(offsets_with_smaller_width)) + { + NodeContour node_contour; + node_contour.is_loop = true; + node_contour.pts = polyline.points; + node_contour.widths.push_back(scale_(this->smaller_ext_perimeter_flow.width())); + outwall_paths.push_back(node_contour); + } + + for (const Polyline &polyline : to_polylines(offsets)) + { + NodeContour node_contour; + node_contour.is_loop = true; + node_contour.pts = polyline.points; + node_contour.widths.push_back(scale_(this->ext_perimeter_flow.width())); + outwall_paths.push_back(node_contour); + } } - for (const Polyline &polyline : to_polylines(offsets)) { - NodeContour node_contour; - node_contour.is_loop = true; - node_contour.pts = polyline.points; - node_contour.widths.push_back(scale_(this->ext_perimeter_flow.width())); - outwall_paths.push_back(node_contour); + for (const ExPolygon &expolygon : offsets_with_smaller_width) + { + contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, true, counter_circle_compensation)); + if (!expolygon.holes.empty()) + { + holes[i].reserve(holes[i].size() + expolygon.holes.size()); + for (const Polygon &hole : expolygon.holes) + holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, true, is_compensation_hole(hole))); + } } } + } - for (const ExPolygon& expolygon : offsets_with_smaller_width) { - contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, true, counter_circle_compensation)); - if (!expolygon.holes.empty()) { - holes[i].reserve(holes[i].size() + expolygon.holes.size()); - for (const Polygon& hole : expolygon.holes) - holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, true, is_compensation_hole(hole))); - } + last = std::move(offsets); + + // BBS: refer to superslicer + // store surface for top infill if only_one_wall_top + if (i == 0 && i != loop_number && this->object_config->top_one_wall_type == TopOneWallType::Alltop && this->upper_slices != NULL) + { + // split the polygons with top/not_top + // get the offset from solid surface anchor + coord_t offset_top_surface = scale_(1.5 * (config->wall_loops.value == 0 ? 0. : unscaled(double(ext_perimeter_width + perimeter_spacing * int(int(config->wall_loops.value) - int(1)))))); + // if possible, try to not push the extra perimeters inside the sparse infill + if (offset_top_surface > 0.9 * (config->wall_loops.value <= 1 ? 0. : (perimeter_spacing * (config->wall_loops.value - 1)))) + offset_top_surface -= coord_t(0.9 * (config->wall_loops.value <= 1 ? 0. : (perimeter_spacing * (config->wall_loops.value - 1)))); + else + offset_top_surface = 0; + // don't takes into account too thin areas + double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(ext_perimeter_spacing / 2.0, perimeter_width / 2.0); + + // BBS: get boungding box of last + BoundingBox last_box = get_extents(last); + last_box.offset(SCALED_EPSILON); + + // BBS: get the Polygons upper the polygon this layer + Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, last_box); + upper_polygons_series_clipped = offset(upper_polygons_series_clipped, min_width_top_surface); + + // set the clip to a virtual "second perimeter" + fill_clip = offset_ex(last, -double(ext_perimeter_spacing)); + // get the real top surface + ExPolygons grown_lower_slices; + ExPolygons bridge_checker; + + ExPolygons top_polygons = diff_ex(last, upper_polygons_series_clipped, ApplySafetyOffset::Yes); + // get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the min_width_top_surface we removed a bit before) + ExPolygons temp_gap = diff_ex(top_polygons, fill_clip); + ExPolygons inner_polygons = diff_ex(last, + offset_ex(top_polygons, offset_top_surface + min_width_top_surface - double(ext_perimeter_spacing / 2)), + ApplySafetyOffset::Yes); + // BBS: check whether surface be bridge or not + if (this->lower_slices != NULL) + { + // BBS: get the Polygons below the polygon this layer + Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, last_box); + + double bridge_offset = std::max(double(ext_perimeter_spacing), (double(perimeter_width))); + bridge_checker = offset_ex(diff_ex(last, lower_polygons_series_clipped, ApplySafetyOffset::Yes), 1.5 * bridge_offset); + + // BBS: Check which piece the bridge belongs to. If the bridge has a connection with the non-top area, it should belong to the non-top area, otherwise it should belong to the top area to get a better surface. + if (!bridge_checker.empty() && !intersection_ex(bridge_checker, inner_polygons).empty()) + inner_polygons = union_ex(inner_polygons, bridge_checker); } + // get the enlarged top surface, by using inner_polygons instead of upper_slices, and clip it for it to be exactly the polygons to fill. + top_polygons = diff_ex(fill_clip, inner_polygons, ApplySafetyOffset::Yes); + // increase by half peri the inner space to fill the frontier between last and stored. + top_fills = union_ex(top_fills, top_polygons); + // set the clip to the external wall but go back inside by infill_extrusion_width/2 to be sure the extrusion won't go outside even with a 100% overlap. + double infill_spacing_unscaled = this->config->sparse_infill_line_width.value; + fill_clip = offset_ex(last, double(ext_perimeter_spacing / 2) - scale_(infill_spacing_unscaled / 2)); + last = intersection_ex(inner_polygons, last); + if (has_gap_fill) + last = union_ex(last, temp_gap); + //{ + // std::stringstream stri; + // stri << this->layer->id() << "_1_"<< i <<"_only_one_peri"<< ".svg"; + // SVG svg(stri.str()); + // svg.draw(to_polylines(top_fills), "green"); + // svg.draw(to_polylines(inner_polygons), "yellow"); + // svg.draw(to_polylines(top_polygons), "cyan"); + // svg.draw(to_polylines(oldLast), "orange"); + // svg.draw(to_polylines(last), "red"); + // svg.Close(); + //} } - } - last = std::move(offsets); - - //BBS: refer to superslicer - //store surface for top infill if only_one_wall_top - if (i == 0 && i != loop_number && this->object_config->top_one_wall_type == TopOneWallType::Alltop && this->upper_slices != NULL) { - //split the polygons with top/not_top - //get the offset from solid surface anchor - coord_t offset_top_surface = scale_(1.5 * (config->wall_loops.value == 0 ? 0. : unscaled(double(ext_perimeter_width + perimeter_spacing * int(int(config->wall_loops.value) - int(1)))))); - // if possible, try to not push the extra perimeters inside the sparse infill - if (offset_top_surface > 0.9 * (config->wall_loops.value <= 1 ? 0. : (perimeter_spacing * (config->wall_loops.value - 1)))) - offset_top_surface -= coord_t(0.9 * (config->wall_loops.value <= 1 ? 0. : (perimeter_spacing * (config->wall_loops.value - 1)))); - else - offset_top_surface = 0; - //don't takes into account too thin areas - double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(ext_perimeter_spacing / 2.0, perimeter_width / 2.0); - - //BBS: get boungding box of last - BoundingBox last_box = get_extents(last); - last_box.offset(SCALED_EPSILON); - - // BBS: get the Polygons upper the polygon this layer - Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, last_box); - upper_polygons_series_clipped = offset(upper_polygons_series_clipped, min_width_top_surface); - - //set the clip to a virtual "second perimeter" - fill_clip = offset_ex(last, -double(ext_perimeter_spacing)); - // get the real top surface - ExPolygons grown_lower_slices; - ExPolygons bridge_checker; - - ExPolygons top_polygons = diff_ex(last, upper_polygons_series_clipped, ApplySafetyOffset::Yes); - //get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the min_width_top_surface we removed a bit before) - ExPolygons temp_gap = diff_ex(top_polygons, fill_clip); - ExPolygons inner_polygons = diff_ex(last, - offset_ex(top_polygons, offset_top_surface + min_width_top_surface - double(ext_perimeter_spacing / 2)), - ApplySafetyOffset::Yes); - // BBS: check whether surface be bridge or not - if (this->lower_slices != NULL) { - // BBS: get the Polygons below the polygon this layer - Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, last_box); - - double bridge_offset = std::max(double(ext_perimeter_spacing), (double(perimeter_width))); - bridge_checker = offset_ex(diff_ex(last, lower_polygons_series_clipped, ApplySafetyOffset::Yes), 1.5 * bridge_offset); - - // BBS: Check which piece the bridge belongs to. If the bridge has a connection with the non-top area, it should belong to the non-top area, otherwise it should belong to the top area to get a better surface. - if (!bridge_checker.empty() && !intersection_ex(bridge_checker, inner_polygons).empty()) - inner_polygons = union_ex(inner_polygons, bridge_checker); + if (i == loop_number && (!has_gap_fill || this->config->sparse_infill_density.value == 0)) + { + // The last run of this loop is executed to collect gaps for gap fill. + // As the gap fill is either disabled or not + break; } - // get the enlarged top surface, by using inner_polygons instead of upper_slices, and clip it for it to be exactly the polygons to fill. - top_polygons = diff_ex(fill_clip, inner_polygons, ApplySafetyOffset::Yes); - // increase by half peri the inner space to fill the frontier between last and stored. - top_fills = union_ex(top_fills, top_polygons); - //set the clip to the external wall but go back inside by infill_extrusion_width/2 to be sure the extrusion won't go outside even with a 100% overlap. - double infill_spacing_unscaled = this->config->sparse_infill_line_width.value; - fill_clip = offset_ex(last, double(ext_perimeter_spacing / 2) - scale_(infill_spacing_unscaled / 2)); - last = intersection_ex(inner_polygons, last); - if (has_gap_fill) - last = union_ex(last,temp_gap); - //{ - // std::stringstream stri; - // stri << this->layer->id() << "_1_"<< i <<"_only_one_peri"<< ".svg"; - // SVG svg(stri.str()); - // svg.draw(to_polylines(top_fills), "green"); - // svg.draw(to_polylines(inner_polygons), "yellow"); - // svg.draw(to_polylines(top_polygons), "cyan"); - // svg.draw(to_polylines(oldLast), "orange"); - // svg.draw(to_polylines(last), "red"); - // svg.Close(); - //} } - if (i == loop_number && (! has_gap_fill || this->config->sparse_infill_density.value == 0)) { - // The last run of this loop is executed to collect gaps for gap fill. - // As the gap fill is either disabled or not - break; - } - } - - // nest loops: holes first - for (int d = 0; d <= loop_number; ++ 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]; - // find the hole loop that contains this one, if any - 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]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - holes_d.erase(holes_d.begin() + i); - -- i; - goto NEXT_LOOP; + // nest loops: holes first + for (int d = 0; d <= loop_number; ++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]; + // find the hole loop that contains this one, if any + 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]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) + { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + --i; + goto NEXT_LOOP; + } } } - } - // 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]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - holes_d.erase(holes_d.begin() + i); - -- i; - goto NEXT_LOOP; + // 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]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) + { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + --i; + goto NEXT_LOOP; + } } } + NEXT_LOOP:; } - NEXT_LOOP: ; } - } - // nest contour loops - for (int d = loop_number; d >= 1; -- 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]; - // 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]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - contours_d.erase(contours_d.begin() + i); - -- i; - goto NEXT_CONTOUR; + // nest contour loops + for (int d = loop_number; d >= 1; --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]; + // 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]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) + { + candidate_parent.children.push_back(loop); + contours_d.erase(contours_d.begin() + i); + --i; + goto NEXT_CONTOUR; + } } } + NEXT_CONTOUR:; } - NEXT_CONTOUR: ; } - } - // at this point, all loops should be in contours[0] - ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls); - // 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 - bool is_outer_wall_first = - this->config->wall_sequence == WallSequence::OuterInner; - if (is_outer_wall_first || - //BBS: always print outer wall first when there indeed has brim. - (this->layer_id == 0 && - this->object_config->brim_type == BrimType::btOuterOnly && - this->object_config->brim_width.value > 0)) - entities.reverse(); - //BBS. adjust wall generate seq - else if (this->config->wall_sequence == WallSequence::InnerOuterInner) - if (entities.entities.size() > 1){ - int second_wall = -1; - ExtrusionEntitiesPtr entities_reorder; - ExtrusionEntitiesPtr entities_second_wall; - for (int entity_idx = 0; entity_idx < entities.entities.size(); ++entity_idx) { - ExtrusionLoop *eloop = static_cast(entities.entities[entity_idx]); - if (eloop->loop_role() & elrSecondPerimeter) { - entities_second_wall.push_back(entities.entities[entity_idx]); - } else { - entities_reorder.push_back(entities.entities[entity_idx]); - if (entities.entities[entity_idx]->role() == erExternalPerimeter && !entities_second_wall.empty()) { - entities_reorder.insert(entities_reorder.end(), entities_second_wall.begin(), entities_second_wall.end()); - entities_second_wall.clear(); + // at this point, all loops should be in contours[0] + ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls); + // 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 + bool is_outer_wall_first = + this->config->wall_sequence == WallSequence::OuterInner; + if (is_outer_wall_first || + // BBS: always print outer wall first when there indeed has brim. + (this->layer_id == 0 && + this->object_config->brim_type == BrimType::btOuterOnly && + this->object_config->brim_width.value > 0)) + entities.reverse(); + // BBS. adjust wall generate seq + else if (this->config->wall_sequence == WallSequence::InnerOuterInner) + if (entities.entities.size() > 1) + { + int second_wall = -1; + ExtrusionEntitiesPtr entities_reorder; + ExtrusionEntitiesPtr entities_second_wall; + for (int entity_idx = 0; entity_idx < entities.entities.size(); ++entity_idx) + { + ExtrusionLoop *eloop = static_cast(entities.entities[entity_idx]); + if (eloop->loop_role() & elrSecondPerimeter) + { + entities_second_wall.push_back(entities.entities[entity_idx]); + } + else + { + entities_reorder.push_back(entities.entities[entity_idx]); + if (entities.entities[entity_idx]->role() == erExternalPerimeter && !entities_second_wall.empty()) + { + entities_reorder.insert(entities_reorder.end(), entities_second_wall.begin(), entities_second_wall.end()); + entities_second_wall.clear(); + } } } + entities.entities = std::move(entities_reorder); } - entities.entities = std::move( entities_reorder); - } - //BBS: add node for loops - if (!outwall_paths.empty() && this->layer_id > 0) { - entities.loop_node_range.first = this->loop_nodes->size(); - if (outwall_paths.size() == 1) { - LoopNode node; - node.node_id = this->loop_nodes->size(); - node.loop_id = 0; - node.node_contour = outwall_paths.front(); - node.bbox = get_extents(node.node_contour.pts); - node.bbox.offset(SCALED_EPSILON); - this->loop_nodes->push_back(node); - } else { - std::vector matched; - matched.resize(outwall_paths.size(), false); - for (int entity_idx = 0; entity_idx < entities.entities.size(); ++entity_idx) { - //skip inner wall - if(entities.entities[entity_idx]->role() == erPerimeter) - continue; - - for (size_t lines_idx = 0; lines_idx < outwall_paths.size(); ++lines_idx) { - if(matched[lines_idx]) - continue; - - if (entities.entities[entity_idx]->first_point().is_in_lines(outwall_paths[lines_idx].pts)) { - matched[lines_idx] = true; - LoopNode node; - node.node_id = this->loop_nodes->size(); - node.loop_id = entity_idx; - node.node_contour = outwall_paths[lines_idx]; - node.bbox = get_extents(node.node_contour.pts); - node.bbox.offset(SCALED_EPSILON); - this->loop_nodes->push_back(node); - break; - } + // BBS: add node for loops + if (!outwall_paths.empty() && this->layer_id > 0) + { + entities.loop_node_range.first = this->loop_nodes->size(); + if (outwall_paths.size() == 1) + { + LoopNode node; + node.node_id = this->loop_nodes->size(); + node.loop_id = 0; + node.node_contour = outwall_paths.front(); + node.bbox = get_extents(node.node_contour.pts); + node.bbox.offset(SCALED_EPSILON); + this->loop_nodes->push_back(node); + } + else + { + std::vector matched; + matched.resize(outwall_paths.size(), false); + for (int entity_idx = 0; entity_idx < entities.entities.size(); ++entity_idx) + { + // skip inner wall + if (entities.entities[entity_idx]->role() == erPerimeter) + continue; + + for (size_t lines_idx = 0; lines_idx < outwall_paths.size(); ++lines_idx) + { + if (matched[lines_idx]) + continue; + + if (entities.entities[entity_idx]->first_point().is_in_lines(outwall_paths[lines_idx].pts)) + { + matched[lines_idx] = true; + LoopNode node; + node.node_id = this->loop_nodes->size(); + node.loop_id = entity_idx; + node.node_contour = outwall_paths[lines_idx]; + node.bbox = get_extents(node.node_contour.pts); + node.bbox.offset(SCALED_EPSILON); + this->loop_nodes->push_back(node); + break; + } + } } } + entities.loop_node_range.second = this->loop_nodes->size(); } - entities.loop_node_range.second = this->loop_nodes->size(); - } + // append perimeters for this slice as a collection + if (!entities.empty()) + this->loops->append(entities); + } // for each loop of an island - // append perimeters for this slice as a collection - if (! entities.empty()) - this->loops->append(entities); - } // for each loop of an island - - // fill gaps - if (! gaps.empty()) { - // collapse - double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); - double max = 2. * perimeter_spacing; - ExPolygons gaps_ex = diff_ex( - //FIXME offset2 would be enough and cheaper. - opening_ex(gaps, float(min / 2.)), - offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); - ThickPolylines polylines; - for (ExPolygon& ex : gaps_ex) { - //BBS: Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well. - ex.douglas_peucker(surface_simplify_resolution); - ex.medial_axis(min, max, &polylines); - } + // fill gaps + if (!gaps.empty()) + { + // collapse + double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); + double max = 2. * perimeter_spacing; + ExPolygons gaps_ex = diff_ex( + // FIXME offset2 would be enough and cheaper. + opening_ex(gaps, float(min / 2.)), + offset2_ex(gaps, -float(max / 2.), float(max / 2. + ClipperSafetyOffset))); + ThickPolylines polylines; + for (ExPolygon &ex : gaps_ex) + { + // BBS: Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well. + ex.douglas_peucker(surface_simplify_resolution); + ex.medial_axis(min, max, &polylines); + } #ifdef GAPS_OF_PERIMETER_DEBUG_TO_SVG - { - static int irun = 0; - BoundingBox bbox_svg; - bbox_svg.merge(get_extents(gaps_ex)); { - std::stringstream stri; - stri << "debug_gaps_ex_" << irun << ".svg"; - SVG svg(stri.str(), bbox_svg); - svg.draw(to_polylines(gaps_ex), "blue", 0.5); - svg.Close(); + static int irun = 0; + BoundingBox bbox_svg; + bbox_svg.merge(get_extents(gaps_ex)); + { + std::stringstream stri; + stri << "debug_gaps_ex_" << irun << ".svg"; + SVG svg(stri.str(), bbox_svg); + svg.draw(to_polylines(gaps_ex), "blue", 0.5); + svg.Close(); + } + ++irun; } - ++ irun; - } #endif - // OrcaSlicer: filter out tiny gap fills - polylines.erase(std::remove_if(polylines.begin(), polylines.end(), [&](const ThickPolyline &p) { - return p.length()< scale_(this->config->filter_out_gap_fill.value); - }), polylines.end()); - - if (! polylines.empty()) { - ExtrusionEntityCollection gap_fill; - variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities); - /* Make sure we don't infill narrow parts that are already gap-filled - (we only consider this surface's gaps to reduce the diff() complexity). - Growing actual extrusions ensures that gaps not filled by medial axis - 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(last, gap_fill.polygons_covered_by_width(10.f)); - this->gap_fill->append(std::move(gap_fill.entities)); - } - } + // OrcaSlicer: filter out tiny gap fills + polylines.erase(std::remove_if(polylines.begin(), polylines.end(), [&](const ThickPolyline &p) + { return p.length() < scale_(this->config->filter_out_gap_fill.value); }), + polylines.end()); - // create one more offset to be used as boundary for fill - // 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 = - (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing / 2 : - // two or more loops? - perimeter_spacing / 2; - // only apply infill overlap if we actually have one perimeter - coord_t infill_peri_overlap = 0; - if (inset > 0) { - infill_peri_overlap = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale(inset + solid_infill_spacing / 2)))); - inset -= infill_peri_overlap; - } - // simplify infill contours according to resolution - Polygons pp; - for (ExPolygon &ex : last) - ex.simplify_p(m_scaled_resolution, &pp); - ExPolygons not_filled_exp = union_ex(pp); - // collapse too narrow infill areas - coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - - ExPolygons infill_exp = offset2_ex( - not_filled_exp, - float(-inset - min_perimeter_infill_spacing / 2.), - float(min_perimeter_infill_spacing / 2.)); - // append infill areas to fill_surfaces - //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))); - } - this->fill_surfaces->append(infill_exp, stInternal); + if (!polylines.empty()) + { + ExtrusionEntityCollection gap_fill; + variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities); + /* Make sure we don't infill narrow parts that are already gap-filled + (we only consider this surface's gaps to reduce the diff() complexity). + Growing actual extrusions ensures that gaps not filled by medial axis + 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(last, gap_fill.polygons_covered_by_width(10.f)); + this->gap_fill->append(std::move(gap_fill.entities)); + } + } - // BBS: get the no-overlap infill expolygons - { - ExPolygons polyWithoutOverlap; - if (min_perimeter_infill_spacing / 2 > infill_peri_overlap) - polyWithoutOverlap = offset2_ex( - not_filled_exp, - float(-inset - min_perimeter_infill_spacing / 2.), - float(min_perimeter_infill_spacing / 2 - infill_peri_overlap)); - else - polyWithoutOverlap = offset_ex( - not_filled_exp, - double(-inset - infill_peri_overlap)); + // create one more offset to be used as boundary for fill + // 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 = + (loop_number < 0) ? 0 : (loop_number == 0) ? + // one loop + ext_perimeter_spacing / 2 + : + // two or more loops? + perimeter_spacing / 2; + // only apply infill overlap if we actually have one perimeter + coord_t infill_peri_overlap = 0; + if (inset > 0) + { + infill_peri_overlap = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale(inset + solid_infill_spacing / 2)))); + inset -= infill_peri_overlap; + } + // simplify infill contours according to resolution + Polygons pp; + for (ExPolygon &ex : last) + ex.simplify_p(m_scaled_resolution, &pp); + ExPolygons not_filled_exp = union_ex(pp); + // collapse too narrow infill areas + coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + + ExPolygons infill_exp = offset2_ex( + not_filled_exp, + float(-inset - min_perimeter_infill_spacing / 2.), + float(min_perimeter_infill_spacing / 2.)); + // append infill areas to fill_surfaces + // 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()) - polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp); - this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end()); - } + { + infill_exp = union_ex(infill_exp, offset_ex(top_infill_exp, double(infill_peri_overlap))); + } + this->fill_surfaces->append(infill_exp, stInternal); - } // for each island -} + // BBS: get the no-overlap infill expolygons + { + ExPolygons polyWithoutOverlap; + if (min_perimeter_infill_spacing / 2 > infill_peri_overlap) + polyWithoutOverlap = offset2_ex( + not_filled_exp, + float(-inset - min_perimeter_infill_spacing / 2.), + float(min_perimeter_infill_spacing / 2 - infill_peri_overlap)); + else + polyWithoutOverlap = offset_ex( + not_filled_exp, + double(-inset - infill_peri_overlap)); + if (!top_fills.empty()) + polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp); + this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end()); + } -//BBS: -void PerimeterGenerator::add_infill_contour_for_arachne( ExPolygons infill_contour, - int loops, - coord_t ext_perimeter_spacing, - coord_t perimeter_spacing, - coord_t min_perimeter_infill_spacing, - coord_t spacing, - bool is_inner_part) -{ - if( offset_ex(infill_contour, -float(spacing / 2.)).empty() ) - { - infill_contour.clear(); // Infill region is too small, so let's filter it out. + } // for each island } - // create one more offset to be used as boundary for fill - // 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 insert = (loops < 0) ? 0: ext_perimeter_spacing; - if (is_inner_part || loops > 0) - insert = perimeter_spacing; + // BBS: + void PerimeterGenerator::add_infill_contour_for_arachne(ExPolygons infill_contour, + int loops, + coord_t ext_perimeter_spacing, + coord_t perimeter_spacing, + coord_t min_perimeter_infill_spacing, + coord_t spacing, + bool is_inner_part) + { + if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) + { + infill_contour.clear(); // Infill region is too small, so let's filter it out. + } - insert = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale(insert)))); + // create one more offset to be used as boundary for fill + // 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 insert = (loops < 0) ? 0 : ext_perimeter_spacing; + if (is_inner_part || loops > 0) + insert = perimeter_spacing; - Polygons inner_pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(m_scaled_resolution, &inner_pp); + insert = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale(insert)))); - this->fill_surfaces->append(offset2_ex(union_ex(inner_pp), float(-min_perimeter_infill_spacing / 2.), float(insert + min_perimeter_infill_spacing / 2.)), stInternal); + Polygons inner_pp; + for (ExPolygon &ex : infill_contour) + ex.simplify_p(m_scaled_resolution, &inner_pp); - append(*this->fill_no_overlap, offset2_ex(union_ex(inner_pp), float(-min_perimeter_infill_spacing / 2.), float(+min_perimeter_infill_spacing / 2.))); -} + this->fill_surfaces->append(offset2_ex(union_ex(inner_pp), float(-min_perimeter_infill_spacing / 2.), float(insert + min_perimeter_infill_spacing / 2.)), stInternal); -// Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper -// "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling" -void PerimeterGenerator::process_arachne() -{ - // other perimeters - m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); - coord_t perimeter_width = this->perimeter_flow.scaled_width(); - coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - - // external perimeters - m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); - coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); - coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); - - // overhang perimeters - m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); - - // solid infill - coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); - - // prepare grown lower layer slices for overhang detection - if (this->lower_slices != nullptr && this->config->detect_overhang_wall) { - // We consider overhang any part where the entire nozzle diameter is not supported by the - // lower layer, so we take lower slices and offset them by half the nozzle diameter used - // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1); - m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2))); + append(*this->fill_no_overlap, offset2_ex(union_ex(inner_pp), float(-min_perimeter_infill_spacing / 2.), float(+min_perimeter_infill_spacing / 2.))); } + // Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper + // "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling" + void PerimeterGenerator::process_arachne() + { + // other perimeters + m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); + coord_t perimeter_width = this->perimeter_flow.scaled_width(); + coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - // BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled - double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution; - // we need to process each island separately because we might have different - // extra perimeters for each one - - bool apply_precise_outer_wall = config->precise_outer_wall; - for (const Surface& surface : this->slices->surfaces) { - // detect how many perimeters must be generated for this island - int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops - - bool apply_circle_compensation = true; - // Orca: properly adjust offset for the outer wall if precise_outer_wall is enabled. - ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution), - apply_precise_outer_wall ? -float(ext_perimeter_width - ext_perimeter_spacing) : - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); - int new_size = std::accumulate(last.begin(), last.end(), 0, [](int prev, const ExPolygon& expoly) { return prev + expoly.num_contours(); }); - if (last.size() != 1 || new_size != surface.expolygon.num_contours()) - apply_circle_compensation = false; - - std::vector circle_poly_indices; - Polygons last_p; - if (apply_circle_compensation) - last_p = to_polygons_with_flag(last.front(), surface.counter_circle_compensation, surface.holes_circle_compensation, circle_poly_indices); - else - last_p = to_polygons(last); + // external perimeters + m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); + coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); + coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); + coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); + + // overhang perimeters + m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); + + // solid infill + coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); + + // prepare grown lower layer slices for overhang detection + if (this->lower_slices != nullptr && this->config->detect_overhang_wall) + { + // We consider overhang any part where the entire nozzle diameter is not supported by the + // lower layer, so we take lower slices and offset them by half the nozzle diameter used + // in the current layer + double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1); + m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2))); + } - std::vector total_perimeters; - ExPolygons infill_contour; + // BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled + double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution; + // we need to process each island separately because we might have different + // extra perimeters for each one - if (loop_number >= 0) { - bool generate_one_wall_by_first_layer = this->object_config->only_one_wall_first_layer && layer_id == 0; - bool generate_one_wall_by_top_most = this->object_config->top_one_wall_type != TopOneWallType::None && this->upper_slices == nullptr; - bool generate_one_wall_by_top = this->object_config->top_one_wall_type == TopOneWallType::Alltop && this->upper_slices != nullptr; + bool apply_precise_outer_wall = config->precise_outer_wall; + for (const Surface &surface : this->slices->surfaces) + { + // detect how many perimeters must be generated for this island + int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops + + bool apply_circle_compensation = true; + // Orca: properly adjust offset for the outer wall if precise_outer_wall is enabled. + ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution), + apply_precise_outer_wall ? -float(ext_perimeter_width - ext_perimeter_spacing) : -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); + int new_size = std::accumulate(last.begin(), last.end(), 0, [](int prev, const ExPolygon &expoly) + { return prev + expoly.num_contours(); }); + if (last.size() != 1 || new_size != surface.expolygon.num_contours()) + apply_circle_compensation = false; + + std::vector circle_poly_indices; + Polygons last_p; + if (apply_circle_compensation) + last_p = to_polygons_with_flag(last.front(), surface.counter_circle_compensation, surface.holes_circle_compensation, circle_poly_indices); + else + last_p = to_polygons(last); - bool is_one_wall = loop_number == 0 || generate_one_wall_by_first_layer || generate_one_wall_by_top_most; - // whether to seperate the generatation of wall into two parts,first generate outer wall,then generate the remaining wall - bool seperate_wall_generation = !is_one_wall && generate_one_wall_by_top; + std::vector total_perimeters; + ExPolygons infill_contour; - double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end()); - Arachne::WallToolPathsParams input_params; + if (loop_number >= 0) { - if (const auto& min_feature_size_opt = object_config->min_feature_size) - input_params.min_feature_size = min_feature_size_opt.value * 0.01 * min_nozzle_diameter; + bool generate_one_wall_by_first_layer = this->object_config->only_one_wall_first_layer && layer_id == 0; + bool generate_one_wall_by_top_most = this->object_config->top_one_wall_type != TopOneWallType::None && this->upper_slices == nullptr; + bool generate_one_wall_by_top = this->object_config->top_one_wall_type == TopOneWallType::Alltop && this->upper_slices != nullptr; - if (const auto& min_bead_width_opt = object_config->min_bead_width) - input_params.min_bead_width = min_bead_width_opt.value * 0.01 * min_nozzle_diameter; + bool is_one_wall = loop_number == 0 || generate_one_wall_by_first_layer || generate_one_wall_by_top_most; + // whether to seperate the generatation of wall into two parts,first generate outer wall,then generate the remaining wall + bool seperate_wall_generation = !is_one_wall && generate_one_wall_by_top; - if (const auto& wall_transition_filter_deviation_opt = object_config->wall_transition_filter_deviation) - input_params.wall_transition_filter_deviation = wall_transition_filter_deviation_opt.value * 0.01 * min_nozzle_diameter; + double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end()); + Arachne::WallToolPathsParams input_params; + { + if (const auto &min_feature_size_opt = object_config->min_feature_size) + input_params.min_feature_size = min_feature_size_opt.value * 0.01 * min_nozzle_diameter; - if (const auto& wall_transition_length_opt = object_config->wall_transition_length) - input_params.wall_transition_length = wall_transition_length_opt.value * 0.01 * min_nozzle_diameter; + if (const auto &min_bead_width_opt = object_config->min_bead_width) + input_params.min_bead_width = min_bead_width_opt.value * 0.01 * min_nozzle_diameter; - input_params.wall_transition_angle = this->object_config->wall_transition_angle.value; - input_params.wall_distribution_count = this->object_config->wall_distribution_count.value; - } + if (const auto &wall_transition_filter_deviation_opt = object_config->wall_transition_filter_deviation) + input_params.wall_transition_filter_deviation = wall_transition_filter_deviation_opt.value * 0.01 * min_nozzle_diameter; - // these variables are only valid if need to seperate wall generation - ExPolygons top_expolys_by_one_wall; - std::vector first_perimeters; - ExPolygons infill_contour_by_one_wall; + if (const auto &wall_transition_length_opt = object_config->wall_transition_length) + input_params.wall_transition_length = wall_transition_length_opt.value * 0.01 * min_nozzle_diameter; - coord_t wall_0_inset = 0; - if (apply_precise_outer_wall) - wall_0_inset = -coord_t(ext_perimeter_width / 2 - ext_perimeter_spacing / 2); + input_params.wall_transition_angle = this->object_config->wall_transition_angle.value; + input_params.wall_distribution_count = this->object_config->wall_distribution_count.value; + } - // do detail check whether to enable one wall - if (seperate_wall_generation) { - Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, wall_0_inset, layer_height, input_params); - if (apply_circle_compensation) - one_wall_paths.EnableHoleCompensation(true, circle_poly_indices); + // these variables are only valid if need to seperate wall generation + ExPolygons top_expolys_by_one_wall; + std::vector first_perimeters; + ExPolygons infill_contour_by_one_wall; - first_perimeters = one_wall_paths.getToolPaths(); - infill_contour_by_one_wall = union_ex(one_wall_paths.getInnerContour()); + coord_t wall_0_inset = 0; + if (apply_precise_outer_wall) + wall_0_inset = -coord_t(ext_perimeter_width / 2 - ext_perimeter_spacing / 2); - BoundingBox infill_bbox = get_extents(infill_contour_by_one_wall); - infill_bbox.offset(EPSILON); + // do detail check whether to enable one wall + if (seperate_wall_generation) + { + Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, wall_0_inset, layer_height, input_params); + if (apply_circle_compensation) + one_wall_paths.EnableHoleCompensation(true, circle_poly_indices); - Polygons upper_polygons_clipped; - if (this->upper_slices) - upper_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, infill_bbox); - top_expolys_by_one_wall = diff_ex(infill_contour_by_one_wall, upper_polygons_clipped); + first_perimeters = one_wall_paths.getToolPaths(); + infill_contour_by_one_wall = union_ex(one_wall_paths.getInnerContour()); - Polygons lower_polygons_clipped; - if (this->lower_slices) - lower_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, infill_bbox); - ExPolygons bottom_expolys = diff_ex(top_expolys_by_one_wall, lower_polygons_clipped); + BoundingBox infill_bbox = get_extents(infill_contour_by_one_wall); + infill_bbox.offset(EPSILON); - top_expolys_by_one_wall = diff_ex(top_expolys_by_one_wall, bottom_expolys); - seperate_wall_generation = should_enable_top_one_wall(last, top_expolys_by_one_wall); - } + Polygons upper_polygons_clipped; + if (this->upper_slices) + upper_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, infill_bbox); + top_expolys_by_one_wall = diff_ex(infill_contour_by_one_wall, upper_polygons_clipped); + + Polygons lower_polygons_clipped; + if (this->lower_slices) + lower_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, infill_bbox); + ExPolygons bottom_expolys = diff_ex(top_expolys_by_one_wall, lower_polygons_clipped); + top_expolys_by_one_wall = diff_ex(top_expolys_by_one_wall, bottom_expolys); + seperate_wall_generation = should_enable_top_one_wall(last, top_expolys_by_one_wall); + } - if (seperate_wall_generation) { - // only generate one wall around top areas - // keep the first generated wall - total_perimeters = first_perimeters; - infill_contour = union_ex(infill_contour_by_one_wall); - // deal with remaining walls to be generated - if (loop_number > 0) { - last = diff_ex(infill_contour_by_one_wall, top_expolys_by_one_wall); - last_p = to_polygons(last); // disable contour compensation in remaining walls - Arachne::WallToolPaths paths_new(last_p, perimeter_spacing, perimeter_spacing, loop_number, wall_0_inset, layer_height, input_params); - auto new_perimeters = paths_new.getToolPaths(); - for (auto& perimeters : new_perimeters) { - if (!perimeters.empty()) { - for (auto& p : perimeters) { - p.inset_idx += 1; + if (seperate_wall_generation) + { + // only generate one wall around top areas + // keep the first generated wall + total_perimeters = first_perimeters; + infill_contour = union_ex(infill_contour_by_one_wall); + // deal with remaining walls to be generated + if (loop_number > 0) + { + last = diff_ex(infill_contour_by_one_wall, top_expolys_by_one_wall); + last_p = to_polygons(last); // disable contour compensation in remaining walls + Arachne::WallToolPaths paths_new(last_p, perimeter_spacing, perimeter_spacing, loop_number, wall_0_inset, layer_height, input_params); + auto new_perimeters = paths_new.getToolPaths(); + for (auto &perimeters : new_perimeters) + { + if (!perimeters.empty()) + { + for (auto &p : perimeters) + { + p.inset_idx += 1; + } + total_perimeters.emplace_back(std::move(perimeters)); } - total_perimeters.emplace_back(std::move(perimeters)); } + infill_contour = union_ex(union_ex(paths_new.getInnerContour()), top_expolys_by_one_wall); + infill_contour = intersection_ex(infill_contour, infill_contour_by_one_wall); } - infill_contour = union_ex(union_ex(paths_new.getInnerContour()), top_expolys_by_one_wall); - infill_contour = intersection_ex(infill_contour, infill_contour_by_one_wall); } - } - else { - if (is_one_wall) { - // plan wall width as one wall - Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, wall_0_inset, layer_height, input_params); - if (apply_circle_compensation) - one_wall_paths.EnableHoleCompensation(true, circle_poly_indices); - total_perimeters = one_wall_paths.getToolPaths(); - infill_contour = union_ex(one_wall_paths.getInnerContour()); - } - else { - // plan wall width as noraml - Arachne::WallToolPaths normal_paths(last_p, ext_perimeter_spacing, perimeter_spacing, loop_number + 1, wall_0_inset, layer_height, input_params); - if (apply_circle_compensation) - normal_paths.EnableHoleCompensation(true, circle_poly_indices); - total_perimeters = normal_paths.getToolPaths(); - infill_contour = union_ex(normal_paths.getInnerContour()); + else + { + if (is_one_wall) + { + // plan wall width as one wall + Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, wall_0_inset, layer_height, input_params); + if (apply_circle_compensation) + one_wall_paths.EnableHoleCompensation(true, circle_poly_indices); + total_perimeters = one_wall_paths.getToolPaths(); + infill_contour = union_ex(one_wall_paths.getInnerContour()); + } + else + { + // plan wall width as noraml + Arachne::WallToolPaths normal_paths(last_p, ext_perimeter_spacing, perimeter_spacing, loop_number + 1, wall_0_inset, layer_height, input_params); + if (apply_circle_compensation) + normal_paths.EnableHoleCompensation(true, circle_poly_indices); + total_perimeters = normal_paths.getToolPaths(); + infill_contour = union_ex(normal_paths.getInnerContour()); + } } } - } - else { - infill_contour = last; - } + else + { + infill_contour = last; + } #ifdef ARACHNE_DEBUG - { - static int iRun = 0; - export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); - } + { + static int iRun = 0; + export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); + } #endif - // All closed ExtrusionLine should have the same the first and the last point. - // But in rare cases, Arachne produce ExtrusionLine marked as closed but without - // equal the first and the last point. - assert([&total_perimeters = std::as_const(total_perimeters)]() -> bool { + // All closed ExtrusionLine should have the same the first and the last point. + // But in rare cases, Arachne produce ExtrusionLine marked as closed but without + // equal the first and the last point. + assert([&total_perimeters = std::as_const(total_perimeters)]() -> bool + { for (const Arachne::VariableWidthLines& perimeter : total_perimeters) for (const Arachne::ExtrusionLine& el : perimeter) if (el.is_closed && el.junctions.front().p != el.junctions.back().p) return false; - return true; - }()); - - int start_perimeter = int(total_perimeters.size()) - 1; - int end_perimeter = -1; - int direction = -1; - - bool is_outer_wall_first = - this->config->wall_sequence == WallSequence::OuterInner || this->config->wall_sequence == WallSequence::InnerOuterInner; - if (layer_id == 0) { - is_outer_wall_first = this->config->wall_sequence == WallSequence::OuterInner; - } - if (is_outer_wall_first) { - start_perimeter = 0; - end_perimeter = int(total_perimeters.size()); - direction = 1; - } + return true; }()); - std::vector all_extrusions; - for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { - if (total_perimeters[perimeter_idx].empty()) - continue; - for (Arachne::ExtrusionLine& wall : total_perimeters[perimeter_idx]) - all_extrusions.emplace_back(&wall); - } + int start_perimeter = int(total_perimeters.size()) - 1; + int end_perimeter = -1; + int direction = -1; - // Find topological order with constraints from extrusions_constrains. - std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. - std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. - std::unordered_map map_extrusion_to_idx; - for (size_t idx = 0; idx < all_extrusions.size(); idx++) - map_extrusion_to_idx.emplace(all_extrusions[idx], idx); - - auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, is_outer_wall_first); - for (auto [before, after] : extrusions_constrains) { - auto after_it = map_extrusion_to_idx.find(after); - ++blocked[after_it->second]; - blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); - } + bool is_outer_wall_first = + this->config->wall_sequence == WallSequence::OuterInner || this->config->wall_sequence == WallSequence::InnerOuterInner; + if (layer_id == 0) + { + is_outer_wall_first = this->config->wall_sequence == WallSequence::OuterInner; + } + if (is_outer_wall_first) + { + start_perimeter = 0; + end_perimeter = int(total_perimeters.size()); + direction = 1; + } - std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. - Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. - std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. - ordered_extrusions.reserve(all_extrusions.size()); - - while (ordered_extrusions.size() < all_extrusions.size()) { - size_t best_candidate = 0; - double best_distance_sqr = std::numeric_limits::max(); - bool is_best_closed = false; - - std::vector available_candidates; - for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { - if (processed[candidate] || blocked[candidate]) - continue; // Not a valid candidate. - available_candidates.push_back(candidate); + std::vector all_extrusions; + for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) + { + if (total_perimeters[perimeter_idx].empty()) + continue; + for (Arachne::ExtrusionLine &wall : total_perimeters[perimeter_idx]) + all_extrusions.emplace_back(&wall); } - std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { - return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; - }); + // Find topological order with constraints from extrusions_constrains. + std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. + std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. + std::unordered_map map_extrusion_to_idx; + for (size_t idx = 0; idx < all_extrusions.size(); idx++) + map_extrusion_to_idx.emplace(all_extrusions[idx], idx); - for (const size_t candidate_path_idx : available_candidates) { - auto& path = all_extrusions[candidate_path_idx]; + auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, is_outer_wall_first); + for (auto [before, after] : extrusions_constrains) + { + auto after_it = map_extrusion_to_idx.find(after); + ++blocked[after_it->second]; + blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); + } - if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. - if (best_distance_sqr == std::numeric_limits::max()) { - best_candidate = candidate_path_idx; - is_best_closed = path->is_closed; - } - continue; + std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. + Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. + std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. + ordered_extrusions.reserve(all_extrusions.size()); + + while (ordered_extrusions.size() < all_extrusions.size()) + { + size_t best_candidate = 0; + double best_distance_sqr = std::numeric_limits::max(); + bool is_best_closed = false; + + std::vector available_candidates; + for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) + { + if (processed[candidate] || blocked[candidate]) + continue; // Not a valid candidate. + available_candidates.push_back(candidate); } - const Point candidate_position = path->junctions.front().p; - double distance_sqr = (current_position - candidate_position).cast().norm(); - if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. - if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { - best_candidate = candidate_path_idx; - best_distance_sqr = distance_sqr; - is_best_closed = path->is_closed; + std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool + { return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; }); + + for (const size_t candidate_path_idx : available_candidates) + { + auto &path = all_extrusions[candidate_path_idx]; + + if (path->junctions.empty()) + { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. + if (best_distance_sqr == std::numeric_limits::max()) + { + best_candidate = candidate_path_idx; + is_best_closed = path->is_closed; + } + continue; + } + + const Point candidate_position = path->junctions.front().p; + double distance_sqr = (current_position - candidate_position).cast().norm(); + if (distance_sqr < best_distance_sqr) + { // Closer than the best candidate so far. + if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) + { + best_candidate = candidate_path_idx; + best_distance_sqr = distance_sqr; + is_best_closed = path->is_closed; + } } } - } - auto& best_path = all_extrusions[best_candidate]; - ordered_extrusions.push_back({ best_path, best_path->is_contour(), false }); - processed[best_candidate] = true; - for (size_t unlocked_idx : blocking[best_candidate]) - blocked[unlocked_idx]--; + auto &best_path = all_extrusions[best_candidate]; + ordered_extrusions.push_back({best_path, best_path->is_contour(), false}); + processed[best_candidate] = true; + for (size_t unlocked_idx : blocking[best_candidate]) + blocked[unlocked_idx]--; - if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. - if (best_path->is_closed) - current_position = best_path->junctions[0].p; //We end where we started. - else - current_position = best_path->junctions.back().p; //Pick the other end from where we started. + if (!best_path->junctions.empty()) + { // If all paths were empty, the best path is still empty. We don't upate the current position then. + if (best_path->is_closed) + current_position = best_path->junctions[0].p; // We end where we started. + else + current_position = best_path->junctions.back().p; // Pick the other end from where we started. + } } - } - // BBS. adjust wall generate seq - if (this->config->wall_sequence == WallSequence::InnerOuterInner) { - if (ordered_extrusions.size() > 2) { // 3 walls minimum needed to do inner outer inner ordering - int position = 0; // index to run the re-ordering for multiple external perimeters in a single island. - int arr_i = 0; // index to run through the walls - int outer, first_internal, second_internal; // allocate index values - // run the re-ordering for all wall loops in the same island - while (position < ordered_extrusions.size()) { - outer = first_internal = second_internal = -1; // initialise all index values to -1 - // run through the walls to get the index values that need re-ordering until the first one for each - // is found. Start at "position" index to enable the for loop to iterate for multiple external - // perimeters in a single island - for (arr_i = position; arr_i < ordered_extrusions.size(); ++arr_i) { - switch (ordered_extrusions[arr_i].extrusion->inset_idx) { - case 0: // external perimeter - if (outer == -1) - outer = arr_i; - break; - case 1: // first internal wall - if (first_internal==-1 && arr_i>outer && outer!=-1) - first_internal = arr_i; - break; - case 2: // second internal wall - if (ordered_extrusions[arr_i].extrusion->inset_idx == 2 && second_internal == -1 && - arr_i > first_internal && outer!=-1) - second_internal = arr_i; - break; + // BBS. adjust wall generate seq + if (this->config->wall_sequence == WallSequence::InnerOuterInner) + { + if (ordered_extrusions.size() > 2) + { // 3 walls minimum needed to do inner outer inner ordering + int position = 0; // index to run the re-ordering for multiple external perimeters in a single island. + int arr_i = 0; // index to run through the walls + int outer, first_internal, second_internal; // allocate index values + // run the re-ordering for all wall loops in the same island + while (position < ordered_extrusions.size()) + { + outer = first_internal = second_internal = -1; // initialise all index values to -1 + // run through the walls to get the index values that need re-ordering until the first one for each + // is found. Start at "position" index to enable the for loop to iterate for multiple external + // perimeters in a single island + for (arr_i = position; arr_i < ordered_extrusions.size(); ++arr_i) + { + switch (ordered_extrusions[arr_i].extrusion->inset_idx) + { + case 0: // external perimeter + if (outer == -1) + outer = arr_i; + break; + case 1: // first internal wall + if (first_internal == -1 && arr_i > outer && outer != -1) + first_internal = arr_i; + break; + case 2: // second internal wall + if (ordered_extrusions[arr_i].extrusion->inset_idx == 2 && second_internal == -1 && + arr_i > first_internal && outer != -1) + second_internal = arr_i; + break; + } + if (outer > -1 && first_internal > -1 && second_internal > -1) + break; // found all three perimeters to re-order } - if (outer >-1 && first_internal>-1 && second_internal>-1) - break; // found all three perimeters to re-order + if (outer > -1 && first_internal > -1 && second_internal > -1) + { // found perimeters to re-order? + + if (ordered_extrusions.size() > 3 && this->config->is_outer_second) + { + if (outer != 1) + { + auto outer_wall = ordered_extrusions[outer]; + ordered_extrusions.erase(ordered_extrusions.begin() + outer); + ordered_extrusions.insert(ordered_extrusions.begin() + 1, outer_wall); + } + } + else + { + const auto temp = ordered_extrusions[second_internal]; + ordered_extrusions[second_internal] = ordered_extrusions[first_internal]; + ordered_extrusions[first_internal] = ordered_extrusions[outer]; + ordered_extrusions[outer] = temp; + } + } + else + break; // did not find any more candidates to re-order, so stop the while loop early + // go to the next perimeter to continue scanning for external walls in the same island + position = arr_i + 1; } - if (outer > -1 && first_internal > -1 && second_internal > -1) { // found perimeters to re-order? - const auto temp = ordered_extrusions[second_internal]; - ordered_extrusions[second_internal] = ordered_extrusions[first_internal]; - ordered_extrusions[first_internal] = ordered_extrusions[outer]; - ordered_extrusions[outer] = temp; - } else - break; // did not find any more candidates to re-order, so stop the while loop early - // go to the next perimeter to continue scanning for external walls in the same island - position = arr_i + 1; } } - } - - if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions); !extrusion_coll.empty()) - this->loops->append(extrusion_coll); - const coord_t spacing = (total_perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions); !extrusion_coll.empty()) + this->loops->append(extrusion_coll); - // collapse too narrow infill areas - const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - // append infill areas to fill_surfaces - add_infill_contour_for_arachne(infill_contour, loop_number, ext_perimeter_spacing, perimeter_spacing, min_perimeter_infill_spacing, spacing, false); + const coord_t spacing = (total_perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + // collapse too narrow infill areas + const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + // append infill areas to fill_surfaces + add_infill_contour_for_arachne(infill_contour, loop_number, ext_perimeter_spacing, perimeter_spacing, min_perimeter_infill_spacing, spacing, false); + } } -} -// expand the top expoly and determine whether to enable top one wall feature -bool PerimeterGenerator::should_enable_top_one_wall(const ExPolygons& original_expolys, ExPolygons& top) -{ - coord_t perimeter_width = this->perimeter_flow.scaled_width(); - coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); + // expand the top expoly and determine whether to enable top one wall feature + bool PerimeterGenerator::should_enable_top_one_wall(const ExPolygons &original_expolys, ExPolygons &top) + { + coord_t perimeter_width = this->perimeter_flow.scaled_width(); + coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - auto get_expolygs_area = [](const ExPolygons& expolys)->double{ - return std::accumulate(expolys.begin(), expolys.end(), (double)(0), [](double val, const ExPolygon& expoly) { - return val + expoly.area(); - }); - }; + auto get_expolygs_area = [](const ExPolygons &expolys) -> double + { + return std::accumulate(expolys.begin(), expolys.end(), (double)(0), [](double val, const ExPolygon &expoly) + { return val + expoly.area(); }); + }; - //BBS: filter small area and extend top surface a bit to hide the wall line - double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(ext_perimeter_spacing / 2.0, perimeter_width / 2.0); - auto shrunk_top = offset_ex(top, - min_width_top_surface); - double shrunk_area = get_expolygs_area(shrunk_top); - double original_area = get_expolygs_area(original_expolys); - - if (shrunk_area / (original_area + EPSILON) < 0.1 || original_area < scale_(1)*scale_(1)) - top.clear(); - else - top = offset_ex(shrunk_top, min_width_top_surface + perimeter_width); - return !top.empty(); -} + // BBS: filter small area and extend top surface a bit to hide the wall line + double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(ext_perimeter_spacing / 2.0, perimeter_width / 2.0); + auto shrunk_top = offset_ex(top, -min_width_top_surface); + double shrunk_area = get_expolygs_area(shrunk_top); + double original_area = get_expolygs_area(original_expolys); -bool PerimeterGeneratorLoop::is_internal_contour() const -{ - // An internal contour is a contour containing no other contours - if (! this->is_contour) - return false; - for (const PerimeterGeneratorLoop &loop : this->children) - if (loop.is_contour) + if (shrunk_area / (original_area + EPSILON) < 0.1 || original_area < scale_(1) * scale_(1)) + top.clear(); + else + top = offset_ex(shrunk_top, min_width_top_surface + perimeter_width); + return !top.empty(); + } + + bool PerimeterGeneratorLoop::is_internal_contour() const + { + // An internal contour is a contour containing no other contours + if (!this->is_contour) return false; - return true; -} + for (const PerimeterGeneratorLoop &loop : this->children) + if (loop.is_contour) + return false; + return true; + } -std::vector PerimeterGenerator::generate_lower_polygons_series(float width) -{ - float nozzle_diameter = print_config->nozzle_diameter.get_at(config->wall_filament - 1); - float start_offset = -0.5 * width; - float end_offset = 0.5 * nozzle_diameter; - - assert(overhang_sampling_number >= 3); - // generate offsets - std::vector offset_series; - offset_series.reserve(2); - - offset_series.push_back(start_offset + 0.5 * (end_offset - start_offset) / (overhang_sampling_number - 1)); - offset_series.push_back(end_offset); - std::vector lower_polygons_series; - if (this->lower_slices == NULL) { + std::vector PerimeterGenerator::generate_lower_polygons_series(float width) + { + float nozzle_diameter = print_config->nozzle_diameter.get_at(config->wall_filament - 1); + float start_offset = -0.5 * width; + float end_offset = 0.5 * nozzle_diameter; + + assert(overhang_sampling_number >= 3); + // generate offsets + std::vector offset_series; + offset_series.reserve(2); + + offset_series.push_back(start_offset + 0.5 * (end_offset - start_offset) / (overhang_sampling_number - 1)); + offset_series.push_back(end_offset); + std::vector lower_polygons_series; + if (this->lower_slices == NULL) + { + return lower_polygons_series; + } + + // offset expolygon to generate series of polygons + for (int i = 0; i < offset_series.size(); i++) + { + lower_polygons_series.emplace_back(offset(*this->lower_slices, float(scale_(offset_series[i])))); + } return lower_polygons_series; } - // offset expolygon to generate series of polygons - for (int i = 0; i < offset_series.size(); i++) { - lower_polygons_series.emplace_back(offset(*this->lower_slices, float(scale_(offset_series[i])))); + PerimeterRegion::PerimeterRegion(const LayerRegion &layer_region) : region(&layer_region.region()) + { + this->expolygons = to_expolygons(layer_region.slices.surfaces); + this->bbox = get_extents(this->expolygons); } - return lower_polygons_series; -} - -PerimeterRegion::PerimeterRegion(const LayerRegion &layer_region) : region(&layer_region.region()) -{ - this->expolygons = to_expolygons(layer_region.slices.surfaces); - this->bbox = get_extents(this->expolygons); -} -bool PerimeterRegion::has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config) -{ - return config.fuzzy_skin == other_config.fuzzy_skin && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness - && config.fuzzy_skin_point_distance == other_config.fuzzy_skin_point_distance; -} + bool PerimeterRegion::has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config) + { + return config.fuzzy_skin == other_config.fuzzy_skin && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness && config.fuzzy_skin_point_distance == other_config.fuzzy_skin_point_distance; + } -void PerimeterRegion::merge_compatible_perimeter_regions(PerimeterRegions &perimeter_regions) -{ - if (perimeter_regions.size() <= 1) { return; } - - PerimeterRegions perimeter_regions_merged; - for (auto it_curr_region = perimeter_regions.begin(); it_curr_region != perimeter_regions.end();) { - PerimeterRegion current_merge = *it_curr_region; - auto it_next_region = std::next(it_curr_region); - for (; it_next_region != perimeter_regions.end() && has_compatible_perimeter_regions(it_next_region->region->config(), it_curr_region->region->config()); - ++it_next_region) { - Slic3r::append(current_merge.expolygons, std::move(it_next_region->expolygons)); - current_merge.bbox.merge(it_next_region->bbox); + void PerimeterRegion::merge_compatible_perimeter_regions(PerimeterRegions &perimeter_regions) + { + if (perimeter_regions.size() <= 1) + { + return; } - if (std::distance(it_curr_region, it_next_region) > 1) { current_merge.expolygons = union_ex(current_merge.expolygons); } + PerimeterRegions perimeter_regions_merged; + for (auto it_curr_region = perimeter_regions.begin(); it_curr_region != perimeter_regions.end();) + { + PerimeterRegion current_merge = *it_curr_region; + auto it_next_region = std::next(it_curr_region); + for (; it_next_region != perimeter_regions.end() && has_compatible_perimeter_regions(it_next_region->region->config(), it_curr_region->region->config()); + ++it_next_region) + { + Slic3r::append(current_merge.expolygons, std::move(it_next_region->expolygons)); + current_merge.bbox.merge(it_next_region->bbox); + } + + if (std::distance(it_curr_region, it_next_region) > 1) + { + current_merge.expolygons = union_ex(current_merge.expolygons); + } - perimeter_regions_merged.emplace_back(std::move(current_merge)); - it_curr_region = it_next_region; - } + perimeter_regions_merged.emplace_back(std::move(current_merge)); + it_curr_region = it_next_region; + } - perimeter_regions = perimeter_regions_merged; -} + perimeter_regions = perimeter_regions_merged; + } } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 3fb1278084..d25bb74e7a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -6,9 +6,9 @@ #include "AppConfig.hpp" #ifdef _MSC_VER - #define WIN32_LEAN_AND_MEAN - #define NOMINMAX - #include +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include #endif /* _MSC_VER */ // instead of #include "slic3r/GUI/I18N.hpp" : @@ -30,7 +30,7 @@ #include #include #include -//BBS: add regex +// BBS: add regex #include #include @@ -49,383 +49,453 @@ using boost::property_tree::ptree; -namespace Slic3r { - -//BBS: add a function to load the version from xxx.json -Semver get_version_from_json(std::string file_path) +namespace Slic3r { - try { - boost::nowide::ifstream ifs(file_path); - json j; - ifs >> j; - std::string version_str = j.at(BBL_JSON_KEY_VERSION); - - auto config_version = Semver::parse(version_str); - if (! config_version) { + + // BBS: add a function to load the version from xxx.json + Semver get_version_from_json(std::string file_path) + { + try + { + boost::nowide::ifstream ifs(file_path); + json j; + ifs >> j; + std::string version_str = j.at(BBL_JSON_KEY_VERSION); + + auto config_version = Semver::parse(version_str); + if (!config_version) + { + return Semver(); + } + else + { + return *config_version; + } + } + catch (nlohmann::detail::parse_error &err) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path << " got a nlohmann::detail::parse_error, reason = " << err.what(); return Semver(); - } else { - return *config_version; + // throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file_path, err.what())); } } - catch(nlohmann::detail::parse_error &err) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<& keys, std::map& key_values) -{ - try { - boost::nowide::ifstream ifs(file_path); - json j; - ifs >> j; - for (int i=0; i < keys.size(); i++) + // BBS: add a function to load the key-values from xxx.json + int get_values_from_json(std::string file_path, std::vector &keys, std::map &key_values) + { + try { - if (j.contains(keys[i])) { - std::string value = j.at(keys[i]); - key_values.emplace(keys[i], value); + boost::nowide::ifstream ifs(file_path); + json j; + ifs >> j; + + for (int i = 0; i < keys.size(); i++) + { + if (j.contains(keys[i])) + { + std::string value = j.at(keys[i]); + key_values.emplace(keys[i], value); + } } } + catch (nlohmann::detail::parse_error &err) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path << " got a nlohmann::detail::parse_error, reason = " << err.what(); + // throw ConfigurationError(format("Failed loading json file \"%1%\": %2%", file_path, err.what())); + return 0; + } + return key_values.size(); } - catch(nlohmann::detail::parse_error &err) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "< bundle && app_config > config) ? CONFIG_FILE_TYPE_APP_CONFIG : - (bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG; -} - - -VendorProfile VendorProfile::from_ini(const boost::filesystem::path &path, bool load_all) -{ - ptree tree; - boost::filesystem::ifstream ifs(path); - boost::property_tree::read_ini(ifs, tree); - return VendorProfile::from_ini(tree, path, load_all); -} - -static const std::unordered_map pre_family_model_map {{ - { "MK3", "MK3" }, - { "MK3MMU2", "MK3" }, - { "MK2.5", "MK2.5" }, - { "MK2.5MMU2", "MK2.5" }, - { "MK2S", "MK2" }, - { "MK2SMM", "MK2" }, - { "SL1", "SL1" }, -}}; - - -// 中间版本兼容性处理,如果是nil值,先改成default值,再进行扩展 -void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil_to_default, const DynamicPrintConfig& defaults) -{ - constexpr int default_param_length = 1; - int filament_variant_length = default_param_length; - int process_variant_length = default_param_length; - int machine_variant_length = default_param_length; - - if(config.has("filament_extruder_variant")) - filament_variant_length = config.option("filament_extruder_variant")->size(); - if(config.has("print_extruder_variant")) - process_variant_length = config.option("print_extruder_variant")->size(); - if(config.has("printer_extruder_variant")) - machine_variant_length = config.option("printer_extruder_variant")->size(); - - auto replace_nil_and_resize = [&](const std::string & key, int length){ - ConfigOption* raw_ptr = config.option(key); - ConfigOptionVectorBase* opt_vec = static_cast(raw_ptr); - if(set_nil_to_default && raw_ptr->is_nil() && defaults.has(key) && std::find(filament_extruder_override_keys.begin(), filament_extruder_override_keys.end(), key) == filament_extruder_override_keys.end()){ - opt_vec->clear(); - opt_vec->resize(length, defaults.option(key)); - } - else{ - opt_vec->resize(length, raw_ptr); + v.first == "last_export_path") + ++app_config; + else if (v.first == "nozzle_diameter" || + v.first == "filament_diameter") + ++config; + } + else if (boost::algorithm::starts_with(v.first, "print:") || + boost::algorithm::starts_with(v.first, "filament:") || + boost::algorithm::starts_with(v.first, "printer:") || + v.first == "settings") + ++bundle; + else if (v.first == "presets") + { + ++app_config; + ++bundle; + } + else if (v.first == "recent") + { + for (auto &kvp : v.second) + if (kvp.first == "settings_folder" || kvp.first == "last_opened_folder") + ++app_config; + } } - }; + return (app_config > bundle && app_config > config) ? CONFIG_FILE_TYPE_APP_CONFIG : (bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE + : CONFIG_FILE_TYPE_CONFIG; + } + + VendorProfile VendorProfile::from_ini(const boost::filesystem::path &path, bool load_all) + { + ptree tree; + boost::filesystem::ifstream ifs(path); + boost::property_tree::read_ini(ifs, tree); + return VendorProfile::from_ini(tree, path, load_all); + } + + static const std::unordered_map pre_family_model_map{{ + {"MK3", "MK3"}, + {"MK3MMU2", "MK3"}, + {"MK2.5", "MK2.5"}, + {"MK2.5MMU2", "MK2.5"}, + {"MK2S", "MK2"}, + {"MK2SMM", "MK2"}, + {"SL1", "SL1"}, + }}; + + // 中间版本兼容性处理,如果是nil值,先改成default值,再进行扩展 + void extend_default_config_length(DynamicPrintConfig &config, const bool set_nil_to_default, const DynamicPrintConfig &defaults) + { + constexpr int default_param_length = 1; + int filament_variant_length = default_param_length; + int process_variant_length = default_param_length; + int machine_variant_length = default_param_length; + + if (config.has("filament_extruder_variant")) + filament_variant_length = config.option("filament_extruder_variant")->size(); + if (config.has("print_extruder_variant")) + process_variant_length = config.option("print_extruder_variant")->size(); + if (config.has("printer_extruder_variant")) + machine_variant_length = config.option("printer_extruder_variant")->size(); + + auto replace_nil_and_resize = [&](const std::string &key, int length) + { + ConfigOption *raw_ptr = config.option(key); + ConfigOptionVectorBase *opt_vec = static_cast(raw_ptr); + if (set_nil_to_default && raw_ptr->is_nil() && defaults.has(key) && std::find(filament_extruder_override_keys.begin(), filament_extruder_override_keys.end(), key) == filament_extruder_override_keys.end()) + { + opt_vec->clear(); + opt_vec->resize(length, defaults.option(key)); + } + else + { + opt_vec->resize(length, raw_ptr); + } + }; + + for (auto &key : config.keys()) + { + if (auto iter = print_options_with_variant.find(key); iter != print_options_with_variant.end()) + { + replace_nil_and_resize(key, process_variant_length); + } + else if (auto iter = filament_options_with_variant.find(key); iter != filament_options_with_variant.end()) + { + replace_nil_and_resize(key, filament_variant_length); + } + else if (auto iter = printer_options_with_variant_1.find(key); iter != printer_options_with_variant_1.end()) + { + replace_nil_and_resize(key, machine_variant_length); + } + else if (auto iter = printer_options_with_variant_2.find(key); iter != printer_options_with_variant_2.end()) + { + replace_nil_and_resize(key, machine_variant_length * 2); + } + } + } - for(auto& key :config.keys()){ - if(auto iter = print_options_with_variant.find(key); iter != print_options_with_variant.end()){ - replace_nil_and_resize(key, process_variant_length); + std::map VendorProfile::PrinterModel::get_bed_texture_maps() const + { + std::map maps; + if (use_double_extruder_default_texture.size() > 0) + { + maps["use_double_extruder_default_texture"] = use_double_extruder_default_texture; + } + if (bottom_texture_end_name.size() > 0) + { + maps["bottom_texture_end_name"] = bottom_texture_end_name; } - else if(auto iter = filament_options_with_variant.find(key); iter != filament_options_with_variant.end()){ - replace_nil_and_resize(key, filament_variant_length); + if (bottom_texture_rect.size() > 0) + { + maps["bottom_texture_rect"] = bottom_texture_rect; } - else if(auto iter = printer_options_with_variant_1.find(key); iter != printer_options_with_variant_1.end()){ - replace_nil_and_resize(key, machine_variant_length); + if (bottom_texture_rect_longer.size() > 0) + { + maps["bottom_texture_rect_longer"] = bottom_texture_rect_longer; } - else if(auto iter = printer_options_with_variant_2.find(key); iter != printer_options_with_variant_2.end()){ - replace_nil_and_resize(key, machine_variant_length * 2); + if (middle_texture_rect.size() > 0) + { + maps["middle_texture_rect"] = middle_texture_rect; } + return maps; } -} -std::map VendorProfile::PrinterModel::get_bed_texture_maps() const -{ - std::map maps; - if (use_double_extruder_default_texture.size() > 0) { maps["use_double_extruder_default_texture"] = use_double_extruder_default_texture; } - if (bottom_texture_end_name.size() > 0) { maps["bottom_texture_end_name"] = bottom_texture_end_name; } - if (bottom_texture_rect.size() > 0) { maps["bottom_texture_rect"] = bottom_texture_rect; } - if (bottom_texture_rect_longer.size() > 0) { maps["bottom_texture_rect_longer"] = bottom_texture_rect_longer; } - if (middle_texture_rect.size() > 0) { maps["middle_texture_rect"] = middle_texture_rect; } - return maps; -} - -VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all) -{ - static const std::string printer_model_key = "printer_model:"; - static const std::string filaments_section = "default_filaments"; - static const std::string materials_section = "default_sla_materials"; + VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all) + { + static const std::string printer_model_key = "printer_model:"; + static const std::string filaments_section = "default_filaments"; + static const std::string materials_section = "default_sla_materials"; - const std::string id = path.stem().string(); + const std::string id = path.stem().string(); - if (! boost::filesystem::exists(path)) { - throw Slic3r::RuntimeError((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); - } + if (!boost::filesystem::exists(path)) + { + throw Slic3r::RuntimeError((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); + } - VendorProfile res(id); + VendorProfile res(id); - // Helper to get compulsory fields - auto get_or_throw = [&](const ptree &tree, const std::string &key) -> ptree::const_assoc_iterator - { - auto res = tree.find(key); - if (res == tree.not_found()) { - throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); - } - return res; - }; + // Helper to get compulsory fields + auto get_or_throw = [&](const ptree &tree, const std::string &key) -> ptree::const_assoc_iterator + { + auto res = tree.find(key); + if (res == tree.not_found()) + { + throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); + } + return res; + }; - // Load the header - const auto &vendor_section = get_or_throw(tree, "vendor")->second; - res.name = get_or_throw(vendor_section, "name")->second.data(); + // Load the header + const auto &vendor_section = get_or_throw(tree, "vendor")->second; + res.name = get_or_throw(vendor_section, "name")->second.data(); - auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data(); - auto config_version = Semver::parse(config_version_str); - if (! config_version) { - throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); - } else { - res.config_version = std::move(*config_version); - } + auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data(); + auto config_version = Semver::parse(config_version_str); + if (!config_version) + { + throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); + } + else + { + res.config_version = std::move(*config_version); + } - // Load URLs - const auto config_update_url = vendor_section.find("config_update_url"); - if (config_update_url != vendor_section.not_found()) { - res.config_update_url = config_update_url->second.data(); - } + // Load URLs + const auto config_update_url = vendor_section.find("config_update_url"); + if (config_update_url != vendor_section.not_found()) + { + res.config_update_url = config_update_url->second.data(); + } - const auto changelog_url = vendor_section.find("changelog_url"); - if (changelog_url != vendor_section.not_found()) { - res.changelog_url = changelog_url->second.data(); - } + const auto changelog_url = vendor_section.find("changelog_url"); + if (changelog_url != vendor_section.not_found()) + { + res.changelog_url = changelog_url->second.data(); + } - if (! load_all) { - return res; - } + if (!load_all) + { + return res; + } - // Load printer models - for (auto §ion : tree) { - if (boost::starts_with(section.first, printer_model_key)) { - VendorProfile::PrinterModel model; - model.id = section.first.substr(printer_model_key.size()); - model.name = section.second.get("name", model.id); + // Load printer models + for (auto §ion : tree) + { + if (boost::starts_with(section.first, printer_model_key)) + { + VendorProfile::PrinterModel model; + model.id = section.first.substr(printer_model_key.size()); + model.name = section.second.get("name", model.id); - const char *technology_fallback = boost::algorithm::starts_with(model.id, "SL") ? "SLA" : "FFF"; + const char *technology_fallback = boost::algorithm::starts_with(model.id, "SL") ? "SLA" : "FFF"; - auto technology_field = section.second.get("technology", technology_fallback); - if (! ConfigOptionEnum::from_string(technology_field, model.technology)) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Invalid printer technology field: `%2%`") % id % technology_field; - model.technology = ptFFF; - } + auto technology_field = section.second.get("technology", technology_fallback); + if (!ConfigOptionEnum::from_string(technology_field, model.technology)) + { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Invalid printer technology field: `%2%`") % id % technology_field; + model.technology = ptFFF; + } - model.family = section.second.get("family", std::string()); - if (model.family.empty() && res.name == "BBL") { - // If no family is specified, it can be inferred for known printers - const auto from_pre_map = pre_family_model_map.find(model.id); - if (from_pre_map != pre_family_model_map.end()) { model.family = from_pre_map->second; } - } + model.family = section.second.get("family", std::string()); + if (model.family.empty() && res.name == "BBL") + { + // If no family is specified, it can be inferred for known printers + const auto from_pre_map = pre_family_model_map.find(model.id); + if (from_pre_map != pre_family_model_map.end()) + { + model.family = from_pre_map->second; + } + } #if 0 // Remove SLA printers from the initial alpha. if (model.technology == ptSLA) continue; #endif - section.second.get("variants", ""); - const auto variants_field = section.second.get("variants", ""); - std::vector variants; - if (Slic3r::unescape_strings_cstyle(variants_field, variants)) { - for (const std::string &variant_name : variants) { - if (model.variant(variant_name) == nullptr) - model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name)); + section.second.get("variants", ""); + const auto variants_field = section.second.get("variants", ""); + std::vector variants; + if (Slic3r::unescape_strings_cstyle(variants_field, variants)) + { + for (const std::string &variant_name : variants) + { + if (model.variant(variant_name) == nullptr) + model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name)); + } } - } else { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed variants field: `%2%`") % id % variants_field; - } - auto default_materials_field = section.second.get("default_materials", ""); - if (default_materials_field.empty()) - default_materials_field = section.second.get("default_filaments", ""); - if (Slic3r::unescape_strings_cstyle(default_materials_field, model.default_materials)) { - Slic3r::sort_remove_duplicates(model.default_materials); - if (! model.default_materials.empty() && model.default_materials.front().empty()) - // An empty material was inserted into the list of default materials. Remove it. - model.default_materials.erase(model.default_materials.begin()); - } else { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed default_materials field: `%2%`") % id % default_materials_field; + else + { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed variants field: `%2%`") % id % variants_field; + } + auto default_materials_field = section.second.get("default_materials", ""); + if (default_materials_field.empty()) + default_materials_field = section.second.get("default_filaments", ""); + if (Slic3r::unescape_strings_cstyle(default_materials_field, model.default_materials)) + { + Slic3r::sort_remove_duplicates(model.default_materials); + if (!model.default_materials.empty() && model.default_materials.front().empty()) + // An empty material was inserted into the list of default materials. Remove it. + model.default_materials.erase(model.default_materials.begin()); + } + else + { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed default_materials field: `%2%`") % id % default_materials_field; + } + model.bed_model = section.second.get("bed_model", ""); + model.bed_texture = section.second.get("bed_texture", ""); + if (!model.id.empty() && !model.variants.empty()) + res.models.push_back(std::move(model)); } - model.bed_model = section.second.get("bed_model", ""); - model.bed_texture = section.second.get("bed_texture", ""); - if (! model.id.empty() && ! model.variants.empty()) - res.models.push_back(std::move(model)); } - } - // Load filaments and sla materials to be installed by default - const auto filaments = tree.find(filaments_section); - if (filaments != tree.not_found()) { - for (auto &pair : filaments->second) { - if (pair.second.data() == "1") { - res.default_filaments.insert(pair.first); + // Load filaments and sla materials to be installed by default + const auto filaments = tree.find(filaments_section); + if (filaments != tree.not_found()) + { + for (auto &pair : filaments->second) + { + if (pair.second.data() == "1") + { + res.default_filaments.insert(pair.first); + } } } - } - const auto materials = tree.find(materials_section); - if (materials != tree.not_found()) { - for (auto &pair : materials->second) { - if (pair.second.data() == "1") { - res.default_sla_materials.insert(pair.first); + const auto materials = tree.find(materials_section); + if (materials != tree.not_found()) + { + for (auto &pair : materials->second) + { + if (pair.second.data() == "1") + { + res.default_sla_materials.insert(pair.first); + } } } - } - return res; -} + return res; + } -std::vector VendorProfile::families() const -{ - std::vector res; - unsigned num_familiies = 0; + std::vector VendorProfile::families() const + { + std::vector res; + unsigned num_familiies = 0; - for (auto &model : models) { - if (std::find(res.begin(), res.end(), model.family) == res.end()) { - res.push_back(model.family); - num_familiies++; + for (auto &model : models) + { + if (std::find(res.begin(), res.end(), model.family) == res.end()) + { + res.push_back(model.family); + num_familiies++; + } } - } - return res; -} + return res; + } -// Suffix to be added to a modified preset name in the combo box. -static std::string g_suffix_modified = " (modified)"; -const std::string& Preset::suffix_modified() -{ - return g_suffix_modified; -} + // Suffix to be added to a modified preset name in the combo box. + static std::string g_suffix_modified = " (modified)"; + const std::string &Preset::suffix_modified() + { + return g_suffix_modified; + } -void Preset::update_suffix_modified(const std::string& new_suffix_modified) -{ - g_suffix_modified = new_suffix_modified; -} -// Remove an optional "(modified)" suffix from a name. -// This converts a UI name to a unique preset identifier. -std::string Preset::remove_suffix_modified(const std::string &name) -{ - return boost::algorithm::starts_with(name, g_suffix_modified) ? - name.substr(g_suffix_modified.size()) : - name; -} + void Preset::update_suffix_modified(const std::string &new_suffix_modified) + { + g_suffix_modified = new_suffix_modified; + } + // Remove an optional "(modified)" suffix from a name. + // This converts a UI name to a unique preset identifier. + std::string Preset::remove_suffix_modified(const std::string &name) + { + return boost::algorithm::starts_with(name, g_suffix_modified) ? name.substr(g_suffix_modified.size()) : name; + } -// Update new extruder fields at the printer profile. -void Preset::normalize(DynamicPrintConfig &config) -{ - // BBS - auto* filament_diameter = dynamic_cast(config.option("filament_diameter")); - //not use any more - /*if (filament_diameter != nullptr) - // Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values. - config.set_num_filaments((unsigned int)filament_diameter->values.size());*/ - - if (filament_diameter) { - // This config contains single or multiple filament presets. - // Ensure that the filament preset vector options contain the correct number of values. + // Update new extruder fields at the printer profile. + void Preset::normalize(DynamicPrintConfig &config) + { // BBS - size_t n = (filament_diameter == nullptr) ? 1 : filament_diameter->values.size(); - const auto &defaults = FullPrintConfig::defaults(); - for (const std::string &key : Preset::filament_options()) { - if (key == "compatible_prints" || key == "compatible_printers") - continue; - if (filament_options_with_variant.find(key) != filament_options_with_variant.end()) - continue; - auto *opt = config.option(key, false); - /*assert(opt != nullptr); - assert(opt->is_vector());*/ - if (opt != nullptr && opt->is_vector()) - static_cast(opt)->resize(n, defaults.option(key)); - } - // The following keys are mandatory for the UI, but they are not part of FullPrintConfig, therefore they are handled separately. - for (const std::string &key : { "filament_settings_id" }) { - auto *opt = config.option(key, false); - assert(opt == nullptr || opt->type() == coStrings); - if (opt != nullptr && opt->type() == coStrings) - static_cast(opt)->values.resize(n, std::string()); + auto *filament_diameter = dynamic_cast(config.option("filament_diameter")); + // not use any more + /*if (filament_diameter != nullptr) + // Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values. + config.set_num_filaments((unsigned int)filament_diameter->values.size());*/ + + if (filament_diameter) + { + // This config contains single or multiple filament presets. + // Ensure that the filament preset vector options contain the correct number of values. + // BBS + size_t n = (filament_diameter == nullptr) ? 1 : filament_diameter->values.size(); + const auto &defaults = FullPrintConfig::defaults(); + for (const std::string &key : Preset::filament_options()) + { + if (key == "compatible_prints" || key == "compatible_printers") + continue; + if (filament_options_with_variant.find(key) != filament_options_with_variant.end()) + continue; + auto *opt = config.option(key, false); + /*assert(opt != nullptr); + assert(opt->is_vector());*/ + if (opt != nullptr && opt->is_vector()) + static_cast(opt)->resize(n, defaults.option(key)); + } + // The following keys are mandatory for the UI, but they are not part of FullPrintConfig, therefore they are handled separately. + for (const std::string &key : {"filament_settings_id"}) + { + auto *opt = config.option(key, false); + assert(opt == nullptr || opt->type() == coStrings); + if (opt != nullptr && opt->type() == coStrings) + static_cast(opt)->values.resize(n, std::string()); + } } - } - handle_legacy_sla(config); -} + handle_legacy_sla(config); + } -std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config) -{ - std::string incorrect_keys; - for (const std::string &key : config.keys()) - if (! default_config.has(key)) { - if (incorrect_keys.empty()) - incorrect_keys = key; - else { - incorrect_keys += ", "; - incorrect_keys += key; + std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config) + { + std::string incorrect_keys; + for (const std::string &key : config.keys()) + if (!default_config.has(key)) + { + if (incorrect_keys.empty()) + incorrect_keys = key; + else + { + incorrect_keys += ", "; + incorrect_keys += key; + } + config.erase(key); } - config.erase(key); - } - return incorrect_keys; -} + return incorrect_keys; + } -std::string Preset::get_type_string(Preset::Type type) -{ - switch (type) { + std::string Preset::get_type_string(Preset::Type type) + { + switch (type) + { case Preset::Type::TYPE_FILAMENT: return PRESET_FILAMENT_NAME; case Preset::Type::TYPE_PRINT: @@ -438,1968 +508,2175 @@ std::string Preset::get_type_string(Preset::Type type) return "invalid"; default: return "invalid"; + } } -} -std::string Preset::get_iot_type_string(Preset::Type type) -{ - switch (type) { - case Preset::Type::TYPE_FILAMENT: - return PRESET_IOT_FILAMENT_TYPE; - case Preset::Type::TYPE_PRINT: - return PRESET_IOT_PRINT_TYPE; - case Preset::Type::TYPE_PRINTER: - return PRESET_IOT_PRINTER_TYPE; + std::string Preset::get_iot_type_string(Preset::Type type) + { + switch (type) + { + case Preset::Type::TYPE_FILAMENT: + return PRESET_IOT_FILAMENT_TYPE; + case Preset::Type::TYPE_PRINT: + return PRESET_IOT_PRINT_TYPE; + case Preset::Type::TYPE_PRINTER: + return PRESET_IOT_PRINTER_TYPE; - default: - return "invalid"; + default: + return "invalid"; + } } -} -//make the type string compatibility with local and iot type string -Preset::Type Preset::get_type_from_string(std::string type_str) -{ - if (type_str.compare(PRESET_PRINT_NAME) == 0 || type_str.compare(PRESET_IOT_PRINT_TYPE) == 0) - return Preset::Type::TYPE_PRINT; - else if (type_str.compare(PRESET_FILAMENT_NAME) == 0 || type_str.compare(PRESET_IOT_FILAMENT_TYPE) == 0) - return Preset::Type::TYPE_FILAMENT; - else if (type_str.compare(PRESET_PRINTER_NAME) == 0 || type_str.compare(PRESET_IOT_PRINTER_TYPE) == 0) - return Preset::Type::TYPE_PRINTER; - else - return Preset::Type::TYPE_INVALID; -} - - -void Preset::load_info(const std::string& file) -{ - try { - boost::property_tree::ptree tree; - boost::nowide::ifstream ifs(file); - boost::property_tree::read_ini(ifs, tree); - if (tree.empty()) return; - for (const boost::property_tree::ptree::value_type &v : tree) { - if (v.first.compare("sync_info") == 0) - this->sync_info = v.second.get_value(); - else if (v.first.compare("user_id") == 0) - this->user_id = v.second.get_value(); - else if (v.first.compare("setting_id") == 0) { - this->setting_id = v.second.get_value(); - if (this->setting_id.compare("null") == 0) - this->setting_id.clear(); - } - else if (v.first.compare("base_id") == 0) { - this->base_id = v.second.get_value(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << PathSanitizer::sanitize(file) << " and base_id: " << this->base_id; - if (this->base_id.compare("null") == 0) - this->base_id.clear(); - } - else if (v.first.compare("updated_time") == 0) { - std::string time = v.second.get_value(); - this->updated_time = std::atoll(time.c_str()); - } - } - } - catch (...) { - return; + // make the type string compatibility with local and iot type string + Preset::Type Preset::get_type_from_string(std::string type_str) + { + if (type_str.compare(PRESET_PRINT_NAME) == 0 || type_str.compare(PRESET_IOT_PRINT_TYPE) == 0) + return Preset::Type::TYPE_PRINT; + else if (type_str.compare(PRESET_FILAMENT_NAME) == 0 || type_str.compare(PRESET_IOT_FILAMENT_TYPE) == 0) + return Preset::Type::TYPE_FILAMENT; + else if (type_str.compare(PRESET_PRINTER_NAME) == 0 || type_str.compare(PRESET_IOT_PRINTER_TYPE) == 0) + return Preset::Type::TYPE_PRINTER; + else + return Preset::Type::TYPE_INVALID; } - //TODO: workaround for current info file convert, will remove it later - if (this->updated_time == 0) { - this->updated_time = (long long)Slic3r::Utils::get_current_time_utc(); - //this->sync_info = "update"; - BOOST_LOG_TRIVIAL(info) << boost::format("old info file, updated time to %1%") % this->updated_time; - save_info(); + void Preset::load_info(const std::string &file) + { + try + { + boost::property_tree::ptree tree; + boost::nowide::ifstream ifs(file); + boost::property_tree::read_ini(ifs, tree); + if (tree.empty()) + return; + for (const boost::property_tree::ptree::value_type &v : tree) + { + if (v.first.compare("sync_info") == 0) + this->sync_info = v.second.get_value(); + else if (v.first.compare("user_id") == 0) + this->user_id = v.second.get_value(); + else if (v.first.compare("setting_id") == 0) + { + this->setting_id = v.second.get_value(); + if (this->setting_id.compare("null") == 0) + this->setting_id.clear(); + } + else if (v.first.compare("base_id") == 0) + { + this->base_id = v.second.get_value(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << PathSanitizer::sanitize(file) << " and base_id: " << this->base_id; + if (this->base_id.compare("null") == 0) + this->base_id.clear(); + } + else if (v.first.compare("updated_time") == 0) + { + std::string time = v.second.get_value(); + this->updated_time = std::atoll(time.c_str()); + } + } + } + catch (...) + { + return; + } + + // TODO: workaround for current info file convert, will remove it later + if (this->updated_time == 0) + { + this->updated_time = (long long)Slic3r::Utils::get_current_time_utc(); + // this->sync_info = "update"; + BOOST_LOG_TRIVIAL(info) << boost::format("old info file, updated time to %1%") % this->updated_time; + save_info(); + } } -} -void Preset::save_info(std::string file) -{ - //BBS: add project embedded preset logic - if (this->is_project_embedded) - return; - if (file.empty()) { - fs::path idx_file(this->file); - idx_file.replace_extension(".info"); - file = idx_file.string(); - } - - boost::nowide::ofstream c; - c.open(file, std::ios::out | std::ios::trunc); - std::string sync_info_to_save; - //BBS: hold is used for stop requesting to server this time - if (this->sync_info.compare("hold") != 0) - sync_info_to_save = this->sync_info; - c << "sync_info" << " = " << sync_info_to_save << std::endl; - c << "user_id" << " = " << this->user_id << std::endl; - c << "setting_id" << " = " << this->setting_id << std::endl; - c << "base_id" << " = " << this->base_id << std::endl; - c << "updated_time" << " = " << std::to_string(this->updated_time) << std::endl; - c.close(); -} - -void Preset::remove_files() -{ - //BBS: add project embedded preset logic - if (this->is_project_embedded) - return; - // Erase the preset file. - boost::nowide::remove(this->file.c_str()); - fs::path idx_path(this->file); - idx_path.replace_extension(".info"); - if (fs::exists(idx_path)) - boost::nowide::remove(idx_path.string().c_str()); -} - -//BBS: add logic for only difference save -bool Preset::save(DynamicPrintConfig* parent_config) -{ - //BBS: add project embedded preset logic - if (this->is_project_embedded) - return false; - //BBS: change to json format - //this->config.save(this->file); - std::string from_str; - if (this->is_user()) - from_str = std::string("User"); - else if (this->is_project_embedded) - from_str = std::string("Project"); - else if (this->is_system) - from_str = std::string("System"); - else - from_str = std::string("Default"); - - boost::system::error_code ec; - if (!boost::filesystem::exists(fs::path(this->file).parent_path()) && !boost::filesystem::create_directories(fs::path(this->file).parent_path(), ec)) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << this->file << " " << ec.message(); - return false; + void Preset::save_info(std::string file) + { + // BBS: add project embedded preset logic + if (this->is_project_embedded) + return; + if (file.empty()) + { + fs::path idx_file(this->file); + idx_file.replace_extension(".info"); + file = idx_file.string(); + } + + boost::nowide::ofstream c; + c.open(file, std::ios::out | std::ios::trunc); + std::string sync_info_to_save; + // BBS: hold is used for stop requesting to server this time + if (this->sync_info.compare("hold") != 0) + sync_info_to_save = this->sync_info; + c << "sync_info" << " = " << sync_info_to_save << std::endl; + c << "user_id" << " = " << this->user_id << std::endl; + c << "setting_id" << " = " << this->setting_id << std::endl; + c << "base_id" << " = " << this->base_id << std::endl; + c << "updated_time" << " = " << std::to_string(this->updated_time) << std::endl; + c.close(); + } + + void Preset::remove_files() + { + // BBS: add project embedded preset logic + if (this->is_project_embedded) + return; + // Erase the preset file. + boost::nowide::remove(this->file.c_str()); + fs::path idx_path(this->file); + idx_path.replace_extension(".info"); + if (fs::exists(idx_path)) + boost::nowide::remove(idx_path.string().c_str()); } - //BBS: only save difference if it has parent - if (parent_config) { - DynamicPrintConfig temp_config; - std::vector dirty_options = config.diff(*parent_config); - std::string extruder_id_name, extruder_variant_name; - std::set *key_set1 = nullptr, *key_set2 = nullptr; - Preset::get_extruder_names_and_keysets(type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + // BBS: add logic for only difference save + bool Preset::save(DynamicPrintConfig *parent_config) + { + // BBS: add project embedded preset logic + if (this->is_project_embedded) + return false; + // BBS: change to json format + // this->config.save(this->file); + std::string from_str; + if (this->is_user()) + from_str = std::string("User"); + else if (this->is_project_embedded) + from_str = std::string("Project"); + else if (this->is_system) + from_str = std::string("System"); + else + from_str = std::string("Default"); - if (!extruder_id_name.empty()) { - dirty_options.emplace_back(extruder_id_name); - } - if (!extruder_variant_name.empty()) { - dirty_options.emplace_back(extruder_variant_name); + boost::system::error_code ec; + if (!boost::filesystem::exists(fs::path(this->file).parent_path()) && !boost::filesystem::create_directories(fs::path(this->file).parent_path(), ec)) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << this->file << " " << ec.message(); + return false; } - for (auto option: dirty_options) + // BBS: only save difference if it has parent + if (parent_config) { - ConfigOption *opt_src = config.option(option); - ConfigOption *opt_dst = temp_config.option(option, true); - if (opt_dst->is_scalar() || !(opt_dst->nullable())) - opt_dst->set(opt_src); - else { - ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); - ConfigOptionVectorBase* opt_vec_dst = static_cast(opt_dst); - ConfigOptionVectorBase* opt_vec_inherit = static_cast(parent_config->option(option)); - if (opt_vec_src->size() == 1) + DynamicPrintConfig temp_config; + std::vector dirty_options = config.diff(*parent_config); + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + + if (!extruder_id_name.empty()) + { + dirty_options.emplace_back(extruder_id_name); + } + if (!extruder_variant_name.empty()) + { + dirty_options.emplace_back(extruder_variant_name); + } + + for (auto option : dirty_options) + { + ConfigOption *opt_src = config.option(option); + ConfigOption *opt_dst = temp_config.option(option, true); + if (opt_dst->is_scalar() || !(opt_dst->nullable())) opt_dst->set(opt_src); - else if (key_set1->find(option) != key_set1->end()) { - opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 1); - } - else if (key_set2->find(option) != key_set2->end()) { - opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 2); - } else - opt_dst->set(opt_src); + { + ConfigOptionVectorBase *opt_vec_src = static_cast(opt_src); + ConfigOptionVectorBase *opt_vec_dst = static_cast(opt_dst); + ConfigOptionVectorBase *opt_vec_inherit = static_cast(parent_config->option(option)); + if (opt_vec_src->size() == 1) + opt_dst->set(opt_src); + else if (key_set1->find(option) != key_set1->end()) + { + opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 1); + } + else if (key_set2->find(option) != key_set2->end()) + { + opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 2); + } + else + opt_dst->set(opt_src); + } } + temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); } - temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); - } else if (!filament_id.empty() && inherits().empty()) { - DynamicPrintConfig temp_config = config; - temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id)); - temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); - } else { - this->config.save_to_json(this->file, this->name, from_str, this->version.to_string()); - } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " save config for: " << this->name << " and filament_id: " << filament_id << " and base_id: " << this->base_id; - - fs::path idx_file(this->file); - idx_file.replace_extension(".info"); - this->save_info(idx_file.string()); - return true; -} + else if (!filament_id.empty() && inherits().empty()) + { + DynamicPrintConfig temp_config = config; + temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id)); + temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); + } + else + { + this->config.save_to_json(this->file, this->name, from_str, this->version.to_string()); + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " save config for: " << this->name << " and filament_id: " << filament_id << " and base_id: " << this->base_id; -void Preset::reload(Preset const &parent) -{ - DynamicPrintConfig config; - // BBS: change to json format - // ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule); - std::map key_values; - std::string reason; - ForwardCompatibilitySubstitutionRule substitution_rule = ForwardCompatibilitySubstitutionRule::Disable; - try { - ConfigSubstitutions config_substitutions = config.load_from_json(file, substitution_rule, key_values, reason); - this->config = parent.config; - this->config.apply(std::move(config)); - } catch (const std::exception &err) { - BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%") % file % err.what(); - } -} - -// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. -std::string Preset::label(bool no_alias) const -{ - return (this->is_dirty ? g_suffix_modified : "") - + ((no_alias || this->alias.empty()) ? this->name : this->alias); -} + fs::path idx_file(this->file); + idx_file.replace_extension(".info"); + this->save_info(idx_file.string()); + return true; + } -bool is_compatible_with_print(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer) -{ - if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) - // The current profile has a vendor assigned and it is different from the active print's vendor. - return false; - auto &condition = preset.preset.compatible_prints_condition(); - auto *compatible_prints = dynamic_cast(preset.preset.config.option("compatible_prints")); - bool has_compatible_prints = compatible_prints != nullptr && ! compatible_prints->values.empty(); - if (! has_compatible_prints && ! condition.empty()) { - try { - return PlaceholderParser::evaluate_boolean_expression(condition, active_print.preset.config); - } catch (const std::runtime_error &err) { - //FIXME in case of an error, return "compatible with everything". - printf("Preset::is_compatible_with_print - parsing error of compatible_prints_condition %s:\n%s\n", active_print.preset.name.c_str(), err.what()); - return true; + void Preset::reload(Preset const &parent) + { + DynamicPrintConfig config; + // BBS: change to json format + // ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule); + std::map key_values; + std::string reason; + ForwardCompatibilitySubstitutionRule substitution_rule = ForwardCompatibilitySubstitutionRule::Disable; + try + { + ConfigSubstitutions config_substitutions = config.load_from_json(file, substitution_rule, key_values, reason); + this->config = parent.config; + this->config.apply(std::move(config)); + } + catch (const std::exception &err) + { + BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%") % file % err.what(); } } - return preset.preset.is_default || active_print.preset.name.empty() || ! has_compatible_prints || - std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.preset.name) != - compatible_prints->values.end(); -} -//BBS: If one filament or process preset is compatible with one system printer preset, -// then we think this filament or process preset should be compatible with all -// user printer preset which is inherited from this system printer preset. -// Because printer_model and nozzle_diameter in BBL system machine preset -// can't be changed by user. -bool is_compatible_with_parent_printer(const PresetWithVendorProfile& preset, const PresetWithVendorProfile& active_printer) -{ - auto *compatible_printers = dynamic_cast(preset.preset.config.option("compatible_printers")); - bool has_compatible_printers = compatible_printers != nullptr && ! compatible_printers->values.empty(); - //BBS: FIXME only check the parent now, but should check grand-parent as well. - return has_compatible_printers && - std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.preset.inherits()) != - compatible_printers->values.end(); -} - -bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config) -{ - if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) - // The current profile has a vendor assigned and it is different from the active print's vendor. - return false; - auto &condition = preset.preset.compatible_printers_condition(); - auto *compatible_printers = dynamic_cast(preset.preset.config.option("compatible_printers")); - bool has_compatible_printers = compatible_printers != nullptr && ! compatible_printers->values.empty(); - if (! has_compatible_printers && ! condition.empty()) { - try { - return PlaceholderParser::evaluate_boolean_expression(condition, active_printer.preset.config, extra_config); - } catch (const std::runtime_error &err) { - //FIXME in case of an error, return "compatible with everything". - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": parsing error of compatible_printers_condition %1%: %2%")%active_printer.preset.name %err.what(); - return true; + // Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. + std::string Preset::label(bool no_alias) const + { + return (this->is_dirty ? g_suffix_modified : "") + ((no_alias || this->alias.empty()) ? this->name : this->alias); + } + + bool is_compatible_with_print(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer) + { + if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) + // The current profile has a vendor assigned and it is different from the active print's vendor. + return false; + auto &condition = preset.preset.compatible_prints_condition(); + auto *compatible_prints = dynamic_cast(preset.preset.config.option("compatible_prints")); + bool has_compatible_prints = compatible_prints != nullptr && !compatible_prints->values.empty(); + if (!has_compatible_prints && !condition.empty()) + { + try + { + return PlaceholderParser::evaluate_boolean_expression(condition, active_print.preset.config); + } + catch (const std::runtime_error &err) + { + // FIXME in case of an error, return "compatible with everything". + printf("Preset::is_compatible_with_print - parsing error of compatible_prints_condition %s:\n%s\n", active_print.preset.name.c_str(), err.what()); + return true; + } } + return preset.preset.is_default || active_print.preset.name.empty() || !has_compatible_prints || + std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.preset.name) != + compatible_prints->values.end(); } - return preset.preset.is_default || active_printer.preset.name.empty() || !has_compatible_printers || - std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.preset.name) != - compatible_printers->values.end() - //BBS - || (!active_printer.preset.is_system && is_compatible_with_parent_printer(preset, active_printer)); -} -bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer) -{ - DynamicPrintConfig config; - config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name)); - const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter"); - if (opt) - config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); - return is_compatible_with_printer(preset, active_printer, &config); -} - -void Preset::set_visible_from_appconfig(const AppConfig &app_config) -{ - //BBS: add config related log - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": name %1%, is_visible %2%")%name % is_visible; - if (vendor == nullptr) { return; } - - if (type == TYPE_PRINTER) { - const std::string &model = config.opt_string("printer_model"); - const std::string &variant = config.opt_string("printer_variant"); - if (model.empty() || variant.empty()) - return; - is_visible = app_config.get_variant(vendor->id, model, variant); - } else if (type == TYPE_FILAMENT || type == TYPE_SLA_MATERIAL) { - const std::string §ion_name = (type == TYPE_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; - if (app_config.has_section(section_name)) { - // Check whether this profile is marked as "installed" in PrusaSlicer.ini, - // or whether a profile is marked as "installed", which this profile may have been renamed from. - const std::map &installed = app_config.get_section(section_name); - auto has = [&installed](const std::string &name) { - auto it = installed.find(name); - return it != installed.end() && ! it->second.empty(); - }; - is_visible = has(this->name); - for (auto it = this->renamed_from.begin(); ! is_visible && it != this->renamed_from.end(); ++ it) - is_visible = has(*it); - } - } - //BBS: add config related log - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": name %1%, is_visible set to %2%")%name % is_visible; -} - -std::string Preset::get_filament_type(std::string &display_filament_type) -{ - return config.get_filament_type(display_filament_type); -} + // BBS: If one filament or process preset is compatible with one system printer preset, + // then we think this filament or process preset should be compatible with all + // user printer preset which is inherited from this system printer preset. + // Because printer_model and nozzle_diameter in BBL system machine preset + // can't be changed by user. + bool is_compatible_with_parent_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer) + { + auto *compatible_printers = dynamic_cast(preset.preset.config.option("compatible_printers")); + bool has_compatible_printers = compatible_printers != nullptr && !compatible_printers->values.empty(); + // BBS: FIXME only check the parent now, but should check grand-parent as well. + return has_compatible_printers && + std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.preset.inherits()) != + compatible_printers->values.end(); + } -std::string Preset::get_printer_type(PresetBundle *preset_bundle) -{ - if (preset_bundle) { - auto config = &preset_bundle->printers.get_edited_preset().config; - std::string vendor_name; - for (auto vendor_profile : preset_bundle->vendors) { - for (auto vendor_model : vendor_profile.second.models) - if (vendor_model.name == config->opt_string("printer_model")) + bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config) + { + if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) + // The current profile has a vendor assigned and it is different from the active print's vendor. + return false; + auto &condition = preset.preset.compatible_printers_condition(); + auto *compatible_printers = dynamic_cast(preset.preset.config.option("compatible_printers")); + bool has_compatible_printers = compatible_printers != nullptr && !compatible_printers->values.empty(); + if (!has_compatible_printers && !condition.empty()) + { + try + { + return PlaceholderParser::evaluate_boolean_expression(condition, active_printer.preset.config, extra_config); + } + catch (const std::runtime_error &err) + { + // FIXME in case of an error, return "compatible with everything". + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": parsing error of compatible_printers_condition %1%: %2%") % active_printer.preset.name % err.what(); + return true; + } + } + return preset.preset.is_default || active_printer.preset.name.empty() || !has_compatible_printers || + std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.preset.name) != + compatible_printers->values.end() + // BBS + || (!active_printer.preset.is_system && is_compatible_with_parent_printer(preset, active_printer)); + } + + bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer) + { + DynamicPrintConfig config; + config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name)); + const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter"); + if (opt) + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + return is_compatible_with_printer(preset, active_printer, &config); + } + + void Preset::set_visible_from_appconfig(const AppConfig &app_config) + { + // BBS: add config related log + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": name %1%, is_visible %2%") % name % is_visible; + if (vendor == nullptr) + { + return; + } + + if (type == TYPE_PRINTER) + { + const std::string &model = config.opt_string("printer_model"); + const std::string &variant = config.opt_string("printer_variant"); + if (model.empty() || variant.empty()) + return; + is_visible = app_config.get_variant(vendor->id, model, variant); + } + else if (type == TYPE_FILAMENT || type == TYPE_SLA_MATERIAL) + { + const std::string §ion_name = (type == TYPE_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; + if (app_config.has_section(section_name)) + { + // Check whether this profile is marked as "installed" in PrusaSlicer.ini, + // or whether a profile is marked as "installed", which this profile may have been renamed from. + const std::map &installed = app_config.get_section(section_name); + auto has = [&installed](const std::string &name) { - vendor_name = vendor_profile.first; - return vendor_model.model_id; - } + auto it = installed.find(name); + return it != installed.end() && !it->second.empty(); + }; + is_visible = has(this->name); + for (auto it = this->renamed_from.begin(); !is_visible && it != this->renamed_from.end(); ++it) + is_visible = has(*it); + } } + // BBS: add config related log + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": name %1%, is_visible set to %2%") % name % is_visible; } - return ""; -} -std::string Preset::get_printer_name(PresetBundle *preset_bundle) -{ - if (preset_bundle) { - auto config = &preset_bundle->printers.get_edited_preset().config; - std::string vendor_name; - for (auto vendor_profile : preset_bundle->vendors) { - for (auto vendor_model : vendor_profile.second.models) - if (vendor_model.name == config->opt_string("printer_model")) { - return vendor_model.name; - } + std::string Preset::get_filament_type(std::string &display_filament_type) + { + return config.get_filament_type(display_filament_type); + } + + std::string Preset::get_printer_type(PresetBundle *preset_bundle) + { + if (preset_bundle) + { + auto config = &preset_bundle->printers.get_edited_preset().config; + std::string vendor_name; + for (auto vendor_profile : preset_bundle->vendors) + { + for (auto vendor_model : vendor_profile.second.models) + if (vendor_model.name == config->opt_string("printer_model")) + { + vendor_name = vendor_profile.first; + return vendor_model.model_id; + } + } } + return ""; } - return ""; -} -std::string Preset::get_current_printer_type(PresetBundle *preset_bundle) -{ - if (preset_bundle) { - auto config = &(this->config); - std::string vendor_name; - for (auto vendor_profile : preset_bundle->vendors) { - for (auto vendor_model : vendor_profile.second.models) - if (vendor_model.name == config->opt_string("printer_model")) { - vendor_name = vendor_profile.first; - return vendor_model.model_id; - } + std::string Preset::get_printer_name(PresetBundle *preset_bundle) + { + if (preset_bundle) + { + auto config = &preset_bundle->printers.get_edited_preset().config; + std::string vendor_name; + for (auto vendor_profile : preset_bundle->vendors) + { + for (auto vendor_model : vendor_profile.second.models) + if (vendor_model.name == config->opt_string("printer_model")) + { + return vendor_model.name; + } + } } + return ""; } - return ""; -} -void Preset::get_extruder_names_and_keysets(Type type, std::string& extruder_id_name, std::string& extruder_variant_name, std::set** p_key_set1, std::set** p_key_set2) -{ - if (type == Preset::TYPE_PRINT) { - extruder_id_name = "print_extruder_id"; - extruder_variant_name = "print_extruder_variant"; - *p_key_set1 = &print_options_with_variant; - *p_key_set2 = &empty_options; - } - else if (type == Preset::TYPE_PRINTER) { - extruder_id_name = "printer_extruder_id"; - extruder_variant_name = "printer_extruder_variant"; - *p_key_set1 = &printer_options_with_variant_1; - *p_key_set2 = &printer_options_with_variant_2; - } - else if (type == Preset::TYPE_FILAMENT) { - extruder_variant_name = "filament_extruder_variant"; - *p_key_set1 = &filament_options_with_variant; - *p_key_set2 = &empty_options; - } - else { - *p_key_set1 = &empty_options; - *p_key_set2 = &empty_options; - } -} - -bool Preset::has_lidar(PresetBundle *preset_bundle) -{ - bool has_lidar = false; - if (preset_bundle) { - auto config = &preset_bundle->printers.get_edited_preset().config; - std::string vendor_name; - for (auto vendor_profile : preset_bundle->vendors) { - for (auto vendor_model : vendor_profile.second.models) - if (vendor_model.name == config->opt_string("printer_model")) { - vendor_name = vendor_profile.first; - break; - } + std::string Preset::get_current_printer_type(PresetBundle *preset_bundle) + { + if (preset_bundle) + { + auto config = &(this->config); + std::string vendor_name; + for (auto vendor_profile : preset_bundle->vendors) + { + for (auto vendor_model : vendor_profile.second.models) + if (vendor_model.name == config->opt_string("printer_model")) + { + vendor_name = vendor_profile.first; + return vendor_model.model_id; + } + } } - if (!vendor_name.empty()) - has_lidar = vendor_name.compare("BBL") == 0 ? true : false; + return ""; } - return has_lidar; -} -// The method previously only supports to be called on preset_bundle->printers.get_edited_preset() -// I extened to support call on all presets -bool Preset::is_bbl_vendor_preset(PresetBundle *preset_bundle) -{ - bool is_bbl_vendor_preset = false; - if (preset_bundle) { - auto config = &this->config; - if (type != TYPE_PRINTER) { - auto printers = config->opt("compatible_printers"); - if (printers && !printers->values.empty()) { - auto printer = preset_bundle->printers.find_preset(printers->values.front()); - if (printer) - config = &printer->config; - } - } - auto printer_model_opt = config->opt("printer_model"); - if (printer_model_opt) { + void Preset::get_extruder_names_and_keysets(Type type, std::string &extruder_id_name, std::string &extruder_variant_name, std::set **p_key_set1, std::set **p_key_set2) + { + if (type == Preset::TYPE_PRINT) + { + extruder_id_name = "print_extruder_id"; + extruder_variant_name = "print_extruder_variant"; + *p_key_set1 = &print_options_with_variant; + *p_key_set2 = &empty_options; + } + else if (type == Preset::TYPE_PRINTER) + { + extruder_id_name = "printer_extruder_id"; + extruder_variant_name = "printer_extruder_variant"; + *p_key_set1 = &printer_options_with_variant_1; + *p_key_set2 = &printer_options_with_variant_2; + } + else if (type == Preset::TYPE_FILAMENT) + { + extruder_variant_name = "filament_extruder_variant"; + *p_key_set1 = &filament_options_with_variant; + *p_key_set2 = &empty_options; + } + else + { + *p_key_set1 = &empty_options; + *p_key_set2 = &empty_options; + } + } + + bool Preset::has_lidar(PresetBundle *preset_bundle) + { + bool has_lidar = false; + if (preset_bundle) + { + auto config = &preset_bundle->printers.get_edited_preset().config; std::string vendor_name; - for (auto vendor_profile : preset_bundle->vendors) { + for (auto vendor_profile : preset_bundle->vendors) + { for (auto vendor_model : vendor_profile.second.models) - if (vendor_model.name == printer_model_opt->value) + if (vendor_model.name == config->opt_string("printer_model")) { vendor_name = vendor_profile.first; break; } } if (!vendor_name.empty()) - is_bbl_vendor_preset = (vendor_name.compare("BBL") == 0); + has_lidar = vendor_name.compare("BBL") == 0 ? true : false; } + return has_lidar; } - return is_bbl_vendor_preset; -} -BedType Preset::get_default_bed_type(PresetBundle* preset_bundle) -{ - if (config.has("default_bed_type") && !config.opt_string("default_bed_type").empty()) { - try { - std::string str_bed_type = config.opt_string("default_bed_type"); - int bed_type_value = atoi(str_bed_type.c_str()); - return BedType(bed_type_value); - } catch(...) { - ; + // The method previously only supports to be called on preset_bundle->printers.get_edited_preset() + // I extened to support call on all presets + bool Preset::is_bbl_vendor_preset(PresetBundle *preset_bundle) + { + bool is_bbl_vendor_preset = false; + if (preset_bundle) + { + auto config = &this->config; + if (type != TYPE_PRINTER) + { + auto printers = config->opt("compatible_printers"); + if (printers && !printers->values.empty()) + { + auto printer = preset_bundle->printers.find_preset(printers->values.front()); + if (printer) + config = &printer->config; + } + } + auto printer_model_opt = config->opt("printer_model"); + if (printer_model_opt) + { + std::string vendor_name; + for (auto vendor_profile : preset_bundle->vendors) + { + for (auto vendor_model : vendor_profile.second.models) + if (vendor_model.name == printer_model_opt->value) + { + vendor_name = vendor_profile.first; + break; + } + } + if (!vendor_name.empty()) + is_bbl_vendor_preset = (vendor_name.compare("BBL") == 0); + } } + return is_bbl_vendor_preset; } - std::string model_id = this->get_printer_type(preset_bundle); - if (model_id == "BL-P001" || model_id == "BL-P002" || model_id == "C13") { - return BedType::btPC; - } else if (model_id == "C11") { + BedType Preset::get_default_bed_type(PresetBundle *preset_bundle) + { + if (config.has("default_bed_type") && !config.opt_string("default_bed_type").empty()) + { + try + { + std::string str_bed_type = config.opt_string("default_bed_type"); + int bed_type_value = atoi(str_bed_type.c_str()); + return BedType(bed_type_value); + } + catch (...) + { + ; + } + } + + std::string model_id = this->get_printer_type(preset_bundle); + if (model_id == "BL-P001" || model_id == "BL-P002" || model_id == "C13") + { + return BedType::btPC; + } + else if (model_id == "C11") + { + return BedType::btPEI; + } return BedType::btPEI; } - return BedType::btPEI; -} -bool Preset::has_cali_lines(PresetBundle* preset_bundle) -{ - std::string model_id = this->get_printer_type(preset_bundle); - if (model_id == "BL-P001" || model_id == "BL-P002" || model_id == "C13") { - return true; + bool Preset::has_cali_lines(PresetBundle *preset_bundle) + { + std::string model_id = this->get_printer_type(preset_bundle); + if (model_id == "BL-P001" || model_id == "BL-P002" || model_id == "C13") + { + return true; + } + return false; } - return false; -} - -static std::vector s_Preset_print_options { - "layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "slicing_mode", - "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", - "detect_overhang_wall", "top_color_penetration_layers", "bottom_color_penetration_layers", - "infill_instead_top_bottom_surfaces", - "smooth_speed_discontinuity_area","smooth_coefficient", "seam_position", "seam_placement_away_from_overhangs", - "wall_sequence", "is_infill_first", "sparse_infill_density", "fill_multiline", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_pattern", - "locked_skin_infill_pattern", "locked_skeleton_infill_pattern", - "bottom_surface_pattern", "internal_solid_infill_pattern", "infill_direction", "bridge_angle", "infill_shift_step", "skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density", - "infill_rotate_step", - "symmetric_infill_y_axis", - "minimum_sparse_infill_area", "reduce_infill_retraction", "ironing_pattern", "ironing_type", - "ironing_flow", "ironing_speed", "ironing_spacing","ironing_direction", "ironing_inset", - "max_travel_detour_distance", "avoid_crossing_wall_includes_support", - "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", + + static std::vector s_Preset_print_options{ + "layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "slicing_mode", + "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", + "detect_overhang_wall", "top_color_penetration_layers", "bottom_color_penetration_layers", + "infill_instead_top_bottom_surfaces", + "smooth_speed_discontinuity_area", "smooth_coefficient", "seam_position", "seam_placement_away_from_overhangs", + "wall_sequence", "is_outer_second", "is_infill_first", "sparse_infill_density", "fill_multiline", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_pattern", + "locked_skin_infill_pattern", "locked_skeleton_infill_pattern", + "bottom_surface_pattern", "internal_solid_infill_pattern", "infill_direction", "bridge_angle", "infill_shift_step", "skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density", + "infill_rotate_step", + "symmetric_infill_y_axis", + "minimum_sparse_infill_area", "reduce_infill_retraction", "ironing_pattern", "ironing_type", + "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_direction", "ironing_inset", + "max_travel_detour_distance", "avoid_crossing_wall_includes_support", + "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", #ifdef HAS_PRESSURE_EQUALIZER - "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", + "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", #endif /* HAS_PRESSURE_EQUALIZER */ - "inner_wall_speed", "outer_wall_speed", "sparse_infill_speed", "internal_solid_infill_speed", - "top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap","support_interface_speed", - "bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed", "outer_wall_acceleration", - "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "travel_acceleration", "initial_layer_travel_acceleration", "inner_wall_acceleration", "sparse_infill_acceleration", - "accel_to_decel_enable", "accel_to_decel_factor", "skirt_loops", "skirt_distance", - "skirt_height", "draft_shield", - "brim_width", "brim_object_gap", "brim_type", "enable_support", "support_type", "support_threshold_angle", "enforce_support_layers", - "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", - "support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style", - // BBS - "print_extruder_id", "print_extruder_variant", "independent_support_layer_height", - "top_z_overrides_xy_distance", - "support_angle", "support_interface_top_layers", "support_interface_bottom_layers", - "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", - "support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "support_remove_small_overhang", - "bridge_no_support", "thick_bridges", "max_bridge_length", "print_sequence", - "filename_format", "wall_filament", "support_bottom_z_distance", - "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body", - "ooze_prevention", "standby_temperature_delta", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", - "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", - "skin_infill_line_width","skeleton_infill_line_width", - "top_surface_line_width", "support_line_width", "infill_wall_overlap", "bridge_flow", - "elefant_foot_compensation", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower", "prime_tower_enable_framework", - "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points","prime_tower_max_speed", - "prime_tower_rib_wall","prime_tower_extra_rib_length","prime_tower_rib_width","prime_tower_fillet_wall","prime_tower_infill_gap","prime_tower_lift_speed","prime_tower_lift_height", - "prime_tower_flat_ironing","enable_circle_compensation", "circle_compensation_manual_offset", "apply_scarf_seam_on_circles", - "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", - "flush_into_infill", "flush_into_objects", "flush_into_support","process_notes", - // BBS - "tree_support_branch_angle", "tree_support_wall_count", "tree_support_branch_distance", "tree_support_branch_diameter", - "tree_support_branch_diameter_angle", - "detect_narrow_internal_solid_infill", - "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination", /*"adaptive_layer_height",*/ - "support_bottom_interface_spacing", "enable_overhang_speed", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", "overhang_totally_speed", - "enable_height_slowdown","slowdown_start_height", "slowdown_start_speed", "slowdown_start_acc", "slowdown_end_height", "slowdown_end_speed", "slowdown_end_acc", - "initial_layer_infill_speed", "top_one_wall_type", "top_area_threshold", "only_one_wall_first_layer", - "timelapse_type", "internal_bridge_support_thickness", - "wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", - "seam_gap", "wipe_speed", "top_solid_infill_flow_ratio", "initial_layer_flow_ratio", - "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk", - "filter_out_gap_fill", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", - "small_perimeter_speed", "small_perimeter_threshold", "z_direction_outwall_speed_continuous", - "vertical_shell_speed","detect_floating_vertical_shell", "enable_wrapping_detection", - // calib - "print_flow_ratio", - //Orca - "exclude_object", "override_filament_scarf_seam_setting", "seam_slope_type", "seam_slope_conditional", "scarf_angle_threshold", - "seam_slope_start_height", "seam_slope_entire_loop", "seam_slope_min_length", - "seam_slope_steps", "seam_slope_inner_walls", "role_base_wipe_speed", "seam_slope_gap", "precise_outer_wall", - "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width", "embedding_wall_into_infill" }; - -static std::vector s_Preset_filament_options{/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "volumetric_speed_coefficients", "filament_type", - "filament_soluble", "filament_is_support", "filament_printable", "filament_scarf_seam_type", "filament_scarf_height", - "filament_scarf_gap", "filament_scarf_length", - "filament_max_volumetric_speed", "impact_strength_z", "filament_ramming_volumetric_speed","filament_ramming_volumetric_speed_nc", "filament_adaptive_volumetric_speed", - "filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower", - "nozzle_temperature", "nozzle_temperature_initial_layer", - // BBS - "cool_plate_temp", "eng_plate_temp", "hot_plate_temp", "textured_plate_temp", "cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer","textured_plate_temp_initial_layer", - "supertack_plate_temp_initial_layer", "supertack_plate_temp", - "circle_compensation_speed", "counter_coef_1", "counter_coef_2", "counter_coef_3", "hole_coef_1", "hole_coef_2", "hole_coef_3", - "counter_limit_min", "counter_limit_max", "hole_limit_min", "hole_limit_max", "diameter_limit", - // "bed_type", - //BBS:temperature_vitrification - "temperature_vitrification", "reduce_fan_stop_start_freq", "slow_down_for_layer_cooling", "no_slow_down_for_cooling_on_outwalls", "fan_min_speed","filament_ramming_travel_time","filament_pre_cooling_temperature","filament_ramming_travel_time_nc","filament_pre_cooling_temperature_nc", - "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "pre_start_fan_time", "overhang_fan_threshold", "overhang_threshold_participating_cooling","close_fan_the_first_x_layers","first_x_layer_fan_speed", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", - "filament_start_gcode", "filament_end_gcode", - //exhaust fan control - "activate_air_filtration","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", - // Retract overrides - "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_length_nc", "filament_retract_restart_extra", "filament_retraction_minimum_travel", - "filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe", - // Profile compatibility - "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", - //BBS - "filament_wipe_distance", "additional_cooling_fan_speed", - "nozzle_temperature_range_low", "nozzle_temperature_range_high", - "filament_extruder_variant", - //OrcaSlicer - "enable_pressure_advance", "pressure_advance", "chamber_temperatures","filament_notes", - "filament_long_retractions_when_cut","filament_retraction_distances_when_cut","filament_shrink", "filament_velocity_adaptation_factor", - //BBS filament change length while the extruder color - "filament_change_length","filament_change_length_nc","filament_prime_volume","filament_prime_volume_nc","filament_flush_volumetric_speed","filament_flush_temp", - "long_retractions_when_ec", "retraction_distances_when_ec", "filament_cooling_before_tower" -}; - -static std::vector s_Preset_machine_limits_options { - "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel", - "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", - "machine_max_speed_x", "machine_max_speed_y", "machine_max_speed_z", "machine_max_speed_e", - "machine_min_extruding_rate", "machine_min_travel_rate", - "machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e", -}; - -static std::vector s_Preset_printer_options { - "printer_technology", - "printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", - "single_extruder_multi_material", "machine_start_gcode", "machine_end_gcode","printing_by_object_gcode","before_layer_change_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", - "printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type", - "printable_height", "extruder_printable_height", "extruder_clearance_dist_to_rod", "extruder_clearance_max_radius","extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", - "nozzle_height", "master_extruder_id", - "default_print_profile", "inherits", - "silent_mode", - // BBS - "scan_first_layer", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode", "template_custom_gcode","machine_hotend_change_time", - "nozzle_type","auxiliary_fan", "fan_direction", "nozzle_volume","upward_compatible_machine", "z_hop_types","support_chamber_temp_control","support_air_filtration","support_cooling_filter","cooling_filter_enabled","auto_disable_filter_on_overheat","printer_structure","thumbnail_size", - "best_object_pos", "head_wrap_detect_zone","printer_notes", - "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", - //OrcaSlicer - "host_type", "print_host", "printhost_apikey", - "print_host_webui", - "printhost_cafile","printhost_port","printhost_authorization_type", - "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", - "use_relative_e_distances", "extruder_type","use_firmware_retraction", - "grab_length","machine_switch_extruder_time","hotend_cooling_rate","hotend_heating_rate","enable_pre_heating", "support_object_skip_flush","physical_extruder_map", - "bed_temperature_formula","machine_prepare_compensation_time", "nozzle_flush_dataset","apply_top_surface_compensation", - "group_algo_with_time","extruder_max_nozzle_count" -}; - -static std::vector s_Preset_sla_print_options { - "layer_height", - "faded_layers", - "supports_enable", - "support_head_front_diameter", - "support_head_penetration", - "support_head_width", - "support_pillar_diameter", - "support_small_pillar_diameter_percent", - "support_max_bridges_on_pillar", - "support_pillar_connection_mode", - "support_buildplate_only", - "support_pillar_widening_factor", - "support_base_diameter", - "support_base_height", - "support_base_safety_distance", - "support_critical_angle", - "support_max_bridge_length", - "support_max_pillar_link_distance", - "support_object_elevation", - "support_points_density_relative", - "support_points_minimal_distance", - "slice_closing_radius", - "pad_enable", - "pad_wall_thickness", - "pad_wall_height", - "pad_brim_size", - "pad_max_merge_distance", - // "pad_edge_radius", - "pad_wall_slope", - "pad_object_gap", - "pad_around_object", - "pad_around_object_everywhere", - "pad_object_connector_stride", - "pad_object_connector_width", - "pad_object_connector_penetration", - "hollowing_enable", - "hollowing_min_thickness", - "hollowing_quality", - "hollowing_closing_distance", - "filename_format", - "default_sla_print_profile", - "compatible_printers", - "compatible_printers_condition", - "inherits" -}; - -static std::vector s_Preset_sla_material_options { - "material_colour", - "material_type", - "initial_layer_height", - "bottle_cost", - "bottle_volume", - "bottle_weight", - "material_density", - "exposure_time", - "initial_exposure_time", - "material_correction", - "material_correction_x", - "material_correction_y", - "material_correction_z", - "material_vendor", - "material_print_speed", - "default_sla_material_profile", - "compatible_prints", "compatible_prints_condition", - "compatible_printers", "compatible_printers_condition", "inherits" -}; - -static std::vector s_Preset_sla_printer_options { - "printer_technology", - "printable_area","bed_custom_texture", "bed_custom_model", "printable_height", - "display_width", "display_height", "display_pixels_x", "display_pixels_y", - "display_mirror_x", "display_mirror_y", - "display_orientation", - "fast_tilt_time", "slow_tilt_time", "area_fill", - "relative_correction", - "relative_correction_x", - "relative_correction_y", - "relative_correction_z", - "absolute_correction", - "elefant_foot_compensation", - "elefant_foot_min_width", - "gamma_correction", - "min_exposure_time", "max_exposure_time", - "min_initial_exposure_time", "max_initial_exposure_time", - "inherits" -}; - -const std::vector& Preset::print_options() { return s_Preset_print_options; } -const std::vector& Preset::filament_options() { return s_Preset_filament_options; } -const std::vector& Preset::machine_limits_options() { return s_Preset_machine_limits_options; } -// The following nozzle options of a printer profile will be adjusted to match the size -// of the nozzle_diameter vector. -const std::vector& Preset::nozzle_options() { return print_config_def.extruder_option_keys(); } -const std::vector& Preset::sla_print_options() { return s_Preset_sla_print_options; } -const std::vector& Preset::sla_material_options() { return s_Preset_sla_material_options; } -const std::vector& Preset::sla_printer_options() { return s_Preset_sla_printer_options; } - -const std::vector& Preset::printer_options() -{ - static std::vector s_opts = [](){ - std::vector opts = s_Preset_printer_options; - append(opts, s_Preset_machine_limits_options); - append(opts, Preset::nozzle_options()); - return opts; - }(); - return s_opts; -} - -PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : - m_type(type), - m_edited_preset(type, "", false), - m_saved_preset(type, "", false), - m_idx_selected(0) -{ - // Insert just the default preset. - this->add_default_preset(keys, defaults, default_name); - m_edited_preset.config.apply(m_presets.front().config); - update_saved_preset_from_current_preset(); -} - - //BBS: add operator= implemention -PresetCollection& PresetCollection::operator=(const PresetCollection &rhs) -{ - m_type = rhs.m_type; - m_presets = rhs.m_presets; - m_map_alias_to_profile_name = rhs.m_map_alias_to_profile_name; - m_map_system_profile_renamed = rhs.m_map_system_profile_renamed; - m_edited_preset = rhs.m_edited_preset; - m_saved_preset = rhs.m_saved_preset; - m_idx_selected = rhs.m_idx_selected; - m_default_suppressed = rhs.m_default_suppressed; - m_num_default_presets = rhs.m_num_default_presets; - m_dir_path = rhs.m_dir_path; - - return *this; -} - -void PresetCollection::reset(bool delete_files) -{ - //BBS: add lock logic for sync preset in background - lock(); - if (m_presets.size() > m_num_default_presets) { - if (delete_files) { - // Erase the preset files. - for (Preset &preset : m_presets) - if (! preset.is_default && ! preset.is_external && ! preset.is_system) { - //BBS remove idx and ini files - preset.remove_files(); - } - } - // Don't use m_presets.resize() here as it requires a default constructor for Preset. - m_presets.erase(m_presets.begin() + m_num_default_presets, m_presets.end()); - this->select_preset(0); + "inner_wall_speed", "outer_wall_speed", "sparse_infill_speed", "internal_solid_infill_speed", + "top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed", + "bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed", "outer_wall_acceleration", + "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "travel_acceleration", "initial_layer_travel_acceleration", "inner_wall_acceleration", "sparse_infill_acceleration", + "accel_to_decel_enable", "accel_to_decel_factor", "skirt_loops", "skirt_distance", + "skirt_height", "draft_shield", + "brim_width", "brim_object_gap", "brim_type", "enable_support", "support_type", "support_threshold_angle", "enforce_support_layers", + "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", + "support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style", + // BBS + "print_extruder_id", "print_extruder_variant", "independent_support_layer_height", + "top_z_overrides_xy_distance", + "support_angle", "support_interface_top_layers", "support_interface_bottom_layers", + "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", + "support_top_z_distance", "support_on_build_plate_only", "support_critical_regions_only", "support_remove_small_overhang", + "bridge_no_support", "thick_bridges", "max_bridge_length", "print_sequence", + "filename_format", "wall_filament", "support_bottom_z_distance", + "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament", "support_interface_not_for_body", + "ooze_prevention", "standby_temperature_delta", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", + "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", + "skin_infill_line_width", "skeleton_infill_line_width", + "top_surface_line_width", "support_line_width", "infill_wall_overlap", "bridge_flow", + "elefant_foot_compensation", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower", "prime_tower_enable_framework", + "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_tower_max_speed", + "prime_tower_rib_wall", "prime_tower_extra_rib_length", "prime_tower_rib_width", "prime_tower_fillet_wall", "prime_tower_infill_gap", "prime_tower_lift_speed", "prime_tower_lift_height", + "prime_tower_flat_ironing", "enable_circle_compensation", "circle_compensation_manual_offset", "apply_scarf_seam_on_circles", + "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", + "flush_into_infill", "flush_into_objects", "flush_into_support", "process_notes", + // BBS + "tree_support_branch_angle", "tree_support_wall_count", "tree_support_branch_distance", "tree_support_branch_diameter", + "tree_support_branch_diameter_angle", + "detect_narrow_internal_solid_infill", + "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination", /*"adaptive_layer_height",*/ + "support_bottom_interface_spacing", "enable_overhang_speed", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", "overhang_totally_speed", + "enable_height_slowdown", "slowdown_start_height", "slowdown_start_speed", "slowdown_start_acc", "slowdown_end_height", "slowdown_end_speed", "slowdown_end_acc", + "initial_layer_infill_speed", "top_one_wall_type", "top_area_threshold", "only_one_wall_first_layer", + "timelapse_type", "internal_bridge_support_thickness", + "wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", + "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", + "seam_gap", "wipe_speed", "top_solid_infill_flow_ratio", "initial_layer_flow_ratio", + "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk", + "filter_out_gap_fill", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", + "small_perimeter_speed", "small_perimeter_threshold", "z_direction_outwall_speed_continuous", + "vertical_shell_speed", "detect_floating_vertical_shell", "enable_wrapping_detection", + // calib + "print_flow_ratio", + // Orca + "exclude_object", "override_filament_scarf_seam_setting", "seam_slope_type", "seam_slope_conditional", "scarf_angle_threshold", + "seam_slope_start_height", "seam_slope_entire_loop", "seam_slope_min_length", + "seam_slope_steps", "seam_slope_inner_walls", "role_base_wipe_speed", "seam_slope_gap", "precise_outer_wall", + "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width", "embedding_wall_into_infill"}; + + static std::vector s_Preset_filament_options{/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "volumetric_speed_coefficients", "filament_type", + "filament_soluble", "filament_is_support", "filament_printable", "filament_scarf_seam_type", "filament_scarf_height", + "filament_scarf_gap", "filament_scarf_length", + "filament_max_volumetric_speed", "impact_strength_z", "filament_ramming_volumetric_speed", "filament_ramming_volumetric_speed_nc", "filament_adaptive_volumetric_speed", + "filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower", + "nozzle_temperature", "nozzle_temperature_initial_layer", + // BBS + "cool_plate_temp", "eng_plate_temp", "hot_plate_temp", "textured_plate_temp", "cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer", "textured_plate_temp_initial_layer", + "supertack_plate_temp_initial_layer", "supertack_plate_temp", + "circle_compensation_speed", "counter_coef_1", "counter_coef_2", "counter_coef_3", "hole_coef_1", "hole_coef_2", "hole_coef_3", + "counter_limit_min", "counter_limit_max", "hole_limit_min", "hole_limit_max", "diameter_limit", + // "bed_type", + // BBS:temperature_vitrification + "temperature_vitrification", "reduce_fan_stop_start_freq", "slow_down_for_layer_cooling", "no_slow_down_for_cooling_on_outwalls", "fan_min_speed", "filament_ramming_travel_time", "filament_pre_cooling_temperature", "filament_ramming_travel_time_nc", "filament_pre_cooling_temperature_nc", + "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "pre_start_fan_time", "overhang_fan_threshold", "overhang_threshold_participating_cooling", "close_fan_the_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", + "filament_start_gcode", "filament_end_gcode", + // exhaust fan control + "activate_air_filtration", "during_print_exhaust_fan_speed", "complete_print_exhaust_fan_speed", + // Retract overrides + "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_length_nc", "filament_retract_restart_extra", "filament_retraction_minimum_travel", + "filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe", + // Profile compatibility + "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", + // BBS + "filament_wipe_distance", "additional_cooling_fan_speed", + "nozzle_temperature_range_low", "nozzle_temperature_range_high", + "filament_extruder_variant", + // OrcaSlicer + "enable_pressure_advance", "pressure_advance", "chamber_temperatures", "filament_notes", + "filament_long_retractions_when_cut", "filament_retraction_distances_when_cut", "filament_shrink", "filament_velocity_adaptation_factor", + // BBS filament change length while the extruder color + "filament_change_length", "filament_change_length_nc", "filament_prime_volume", "filament_prime_volume_nc", "filament_flush_volumetric_speed", "filament_flush_temp", + "long_retractions_when_ec", "retraction_distances_when_ec", "filament_cooling_before_tower"}; + + static std::vector s_Preset_machine_limits_options{ + "machine_max_acceleration_extruding", + "machine_max_acceleration_retracting", + "machine_max_acceleration_travel", + "machine_max_acceleration_x", + "machine_max_acceleration_y", + "machine_max_acceleration_z", + "machine_max_acceleration_e", + "machine_max_speed_x", + "machine_max_speed_y", + "machine_max_speed_z", + "machine_max_speed_e", + "machine_min_extruding_rate", + "machine_min_travel_rate", + "machine_max_jerk_x", + "machine_max_jerk_y", + "machine_max_jerk_z", + "machine_max_jerk_e", + }; + + static std::vector s_Preset_printer_options{ + "printer_technology", + "printable_area", "extruder_printable_area", "bed_exclude_area", "bed_custom_texture", "bed_custom_model", "gcode_flavor", + "single_extruder_multi_material", "machine_start_gcode", "machine_end_gcode", "printing_by_object_gcode", "before_layer_change_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", + "printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type", + "printable_height", "extruder_printable_height", "extruder_clearance_dist_to_rod", "extruder_clearance_max_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", + "nozzle_height", "master_extruder_id", + "default_print_profile", "inherits", + "silent_mode", + // BBS + "scan_first_layer", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode", "template_custom_gcode", "machine_hotend_change_time", + "nozzle_type", "auxiliary_fan", "fan_direction", "nozzle_volume", "upward_compatible_machine", "z_hop_types", "support_chamber_temp_control", "support_air_filtration", "support_cooling_filter", "cooling_filter_enabled", "auto_disable_filter_on_overheat", "printer_structure", "thumbnail_size", + "best_object_pos", "head_wrap_detect_zone", "printer_notes", + "enable_long_retraction_when_cut", "long_retractions_when_cut", "retraction_distances_when_cut", + // OrcaSlicer + "host_type", "print_host", "printhost_apikey", + "print_host_webui", + "printhost_cafile", "printhost_port", "printhost_authorization_type", + "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", + "use_relative_e_distances", "extruder_type", "use_firmware_retraction", + "grab_length", "machine_switch_extruder_time", "hotend_cooling_rate", "hotend_heating_rate", "enable_pre_heating", "support_object_skip_flush", "physical_extruder_map", + "bed_temperature_formula", "machine_prepare_compensation_time", "nozzle_flush_dataset", "apply_top_surface_compensation", + "group_algo_with_time", "extruder_max_nozzle_count"}; + + static std::vector s_Preset_sla_print_options{ + "layer_height", + "faded_layers", + "supports_enable", + "support_head_front_diameter", + "support_head_penetration", + "support_head_width", + "support_pillar_diameter", + "support_small_pillar_diameter_percent", + "support_max_bridges_on_pillar", + "support_pillar_connection_mode", + "support_buildplate_only", + "support_pillar_widening_factor", + "support_base_diameter", + "support_base_height", + "support_base_safety_distance", + "support_critical_angle", + "support_max_bridge_length", + "support_max_pillar_link_distance", + "support_object_elevation", + "support_points_density_relative", + "support_points_minimal_distance", + "slice_closing_radius", + "pad_enable", + "pad_wall_thickness", + "pad_wall_height", + "pad_brim_size", + "pad_max_merge_distance", + // "pad_edge_radius", + "pad_wall_slope", + "pad_object_gap", + "pad_around_object", + "pad_around_object_everywhere", + "pad_object_connector_stride", + "pad_object_connector_width", + "pad_object_connector_penetration", + "hollowing_enable", + "hollowing_min_thickness", + "hollowing_quality", + "hollowing_closing_distance", + "filename_format", + "default_sla_print_profile", + "compatible_printers", + "compatible_printers_condition", + "inherits"}; + + static std::vector s_Preset_sla_material_options{ + "material_colour", + "material_type", + "initial_layer_height", + "bottle_cost", + "bottle_volume", + "bottle_weight", + "material_density", + "exposure_time", + "initial_exposure_time", + "material_correction", + "material_correction_x", + "material_correction_y", + "material_correction_z", + "material_vendor", + "material_print_speed", + "default_sla_material_profile", + "compatible_prints", "compatible_prints_condition", + "compatible_printers", "compatible_printers_condition", "inherits"}; + + static std::vector s_Preset_sla_printer_options{ + "printer_technology", + "printable_area", "bed_custom_texture", "bed_custom_model", "printable_height", + "display_width", "display_height", "display_pixels_x", "display_pixels_y", + "display_mirror_x", "display_mirror_y", + "display_orientation", + "fast_tilt_time", "slow_tilt_time", "area_fill", + "relative_correction", + "relative_correction_x", + "relative_correction_y", + "relative_correction_z", + "absolute_correction", + "elefant_foot_compensation", + "elefant_foot_min_width", + "gamma_correction", + "min_exposure_time", "max_exposure_time", + "min_initial_exposure_time", "max_initial_exposure_time", + "inherits"}; + + const std::vector &Preset::print_options() { return s_Preset_print_options; } + const std::vector &Preset::filament_options() { return s_Preset_filament_options; } + const std::vector &Preset::machine_limits_options() { return s_Preset_machine_limits_options; } + // The following nozzle options of a printer profile will be adjusted to match the size + // of the nozzle_diameter vector. + const std::vector &Preset::nozzle_options() { return print_config_def.extruder_option_keys(); } + const std::vector &Preset::sla_print_options() { return s_Preset_sla_print_options; } + const std::vector &Preset::sla_material_options() { return s_Preset_sla_material_options; } + const std::vector &Preset::sla_printer_options() { return s_Preset_sla_printer_options; } + + const std::vector &Preset::printer_options() + { + static std::vector s_opts = []() + { + std::vector opts = s_Preset_printer_options; + append(opts, s_Preset_machine_limits_options); + append(opts, Preset::nozzle_options()); + return opts; + }(); + return s_opts; + } + + PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : m_type(type), + m_edited_preset(type, "", false), + m_saved_preset(type, "", false), + m_idx_selected(0) + { + // Insert just the default preset. + this->add_default_preset(keys, defaults, default_name); + m_edited_preset.config.apply(m_presets.front().config); + update_saved_preset_from_current_preset(); } - //BBS: add lock logic for sync preset in background - unlock(); - m_map_alias_to_profile_name.clear(); - m_map_system_profile_renamed.clear(); -} -void PresetCollection::add_default_preset(const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name) -{ - // Insert just the default preset. - m_presets.emplace_back(Preset(this->type(), preset_name, true)); - m_presets.back().config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); - m_presets.back().loaded = true; - ++ m_num_default_presets; -} - -// Load all presets found in dir_path. -// Throws an exception on error. -void PresetCollection::load_presets( - const std::string &dir_path, const std::string &subdir, - PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule) -{ - // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, - // see https://github.com/prusa3d/PrusaSlicer/issues/732 - boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred(); + // BBS: add operator= implemention + PresetCollection &PresetCollection::operator=(const PresetCollection &rhs) + { + m_type = rhs.m_type; + m_presets = rhs.m_presets; + m_map_alias_to_profile_name = rhs.m_map_alias_to_profile_name; + m_map_system_profile_renamed = rhs.m_map_system_profile_renamed; + m_edited_preset = rhs.m_edited_preset; + m_saved_preset = rhs.m_saved_preset; + m_idx_selected = rhs.m_idx_selected; + m_default_suppressed = rhs.m_default_suppressed; + m_num_default_presets = rhs.m_num_default_presets; + m_dir_path = rhs.m_dir_path; - // Load custom roots first - if (fs::exists(dir / "base")) { - load_presets(dir.string(), "base", substitutions, substitution_rule); + return *this; } - //BBS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, load presets from %1%, current type %2%") % PathSanitizer::sanitize(dir) % Preset::get_type_string(m_type); - //BBS do not parse folder if not exists - m_dir_path = dir.string(); - if (!fs::exists(dir)) { - fs::create_directory(dir); - return; + void PresetCollection::reset(bool delete_files) + { + // BBS: add lock logic for sync preset in background + lock(); + if (m_presets.size() > m_num_default_presets) + { + if (delete_files) + { + // Erase the preset files. + for (Preset &preset : m_presets) + if (!preset.is_default && !preset.is_external && !preset.is_system) + { + // BBS remove idx and ini files + preset.remove_files(); + } + } + // Don't use m_presets.resize() here as it requires a default constructor for Preset. + m_presets.erase(m_presets.begin() + m_num_default_presets, m_presets.end()); + this->select_preset(0); + } + // BBS: add lock logic for sync preset in background + unlock(); + m_map_alias_to_profile_name.clear(); + m_map_system_profile_renamed.clear(); } - std::string errors_cummulative; - // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. - // (see the "Preset already present, not loading" message). - std::deque presets_loaded; + void PresetCollection::add_default_preset(const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name) + { + // Insert just the default preset. + m_presets.emplace_back(Preset(this->type(), preset_name, true)); + m_presets.back().config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); + m_presets.back().loaded = true; + ++m_num_default_presets; + } + + // Load all presets found in dir_path. + // Throws an exception on error. + void PresetCollection::load_presets( + const std::string &dir_path, const std::string &subdir, + PresetsConfigSubstitutions &substitutions, ForwardCompatibilitySubstitutionRule substitution_rule) + { + // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, + // see https://github.com/prusa3d/PrusaSlicer/issues/732 + boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred(); - //BBS: get the extruder related info for this preset collection - std::string extruder_id_name, extruder_variant_name; - std::set *key_set1 = nullptr, *key_set2 = nullptr; - Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + // Load custom roots first + if (fs::exists(dir / "base")) + { + load_presets(dir.string(), "base", substitutions, substitution_rule); + } - //BBS: change to json format - for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) - { - std::string file_name = dir_entry.path().filename().string(); - //if (Slic3r::is_ini_file(dir_entry)) { - if (Slic3r::is_json_file(file_name)) { - // Remove the .ini suffix. - std::string name = file_name.erase(file_name.size() - 5); - if (this->find_preset(name, false)) { - // This happens when there's is a preset (most likely legacy one) with the same name as a system preset - // that's already been loaded from a bundle. - BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; - continue; - } - try { - Preset preset(m_type, name, false); - preset.file = dir_entry.path().string(); - // Load the preset file, apply preset values on top of defaults. - try { - fs::path idx_path(preset.file); - idx_path.replace_extension(".info"); - if (fs::exists(idx_path)) { - preset.load_info(idx_path.string()); + // BBS: add config related logs + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, load presets from %1%, current type %2%") % PathSanitizer::sanitize(dir) % Preset::get_type_string(m_type); + // BBS do not parse folder if not exists + m_dir_path = dir.string(); + if (!fs::exists(dir)) + { + fs::create_directory(dir); + return; + } + + std::string errors_cummulative; + // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. + // (see the "Preset already present, not loading" message). + std::deque presets_loaded; + + // BBS: get the extruder related info for this preset collection + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + + // BBS: change to json format + for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) + { + std::string file_name = dir_entry.path().filename().string(); + // if (Slic3r::is_ini_file(dir_entry)) { + if (Slic3r::is_json_file(file_name)) + { + // Remove the .ini suffix. + std::string name = file_name.erase(file_name.size() - 5); + if (this->find_preset(name, false)) + { + // This happens when there's is a preset (most likely legacy one) with the same name as a system preset + // that's already been loaded from a bundle. + BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; + continue; + } + try + { + Preset preset(m_type, name, false); + preset.file = dir_entry.path().string(); + // Load the preset file, apply preset values on top of defaults. + try + { + fs::path idx_path(preset.file); + idx_path.replace_extension(".info"); + if (fs::exists(idx_path)) + { + preset.load_info(idx_path.string()); + } + DynamicPrintConfig config; + // BBS: change to json format + // ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule); + std::map key_values; + std::string reason; + ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason); + if (!config_substitutions.empty()) + substitutions.push_back({preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions)}); + if (!reason.empty()) + { + fs::path file_path(preset.file); + if (fs::exists(file_path)) + fs::remove(file_path); + file_path.replace_extension(".info"); + if (fs::exists(file_path)) + fs::remove(file_path); + BOOST_LOG_TRIVIAL(error) << boost::format("parse config %1% failed") % PathSanitizer::sanitize(preset.file); + continue; + } + + std::string version_str = key_values[BBL_JSON_KEY_VERSION]; + boost::optional version = Semver::parse(version_str); + if (!version) + continue; + Semver app_version = *(Semver::parse(SLIC3R_VERSION)); + if (version->maj() > app_version.maj()) + { + BOOST_LOG_TRIVIAL(warning) << "Preset incompatibla, not loading: " << name; + continue; + } + preset.version = *version; + + if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end()) + preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID]; + if (key_values.find(BBL_JSON_KEY_DESCRIPTION) != key_values.end()) + preset.description = key_values[BBL_JSON_KEY_DESCRIPTION]; + if (key_values.find(BBL_JSON_KEY_INSTANTIATION) != key_values.end()) + preset.is_visible = key_values[BBL_JSON_KEY_INSTANTIATION] != "false"; + + // BBS: use inherit config as the base + Preset *inherit_preset = nullptr; + ConfigOption *inherits_config = config.option(BBL_JSON_KEY_INHERITS); + + // check inherits_config + if (inherits_config) + { + ConfigOptionString *option_str = dynamic_cast(inherits_config); + std::string inherits_value = option_str->value; + inherit_preset = this->find_preset(inherits_value, false, true); + } + else + { + ; + } + const Preset &default_preset = this->default_preset_for(config); + if (inherit_preset) + { + preset.config = inherit_preset->config; + preset.filament_id = inherit_preset->filament_id; + extend_default_config_length(config, false, {}); + preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); + } + else + { + auto inherits_config2 = dynamic_cast(inherits_config); + if ((inherits_config2 && !inherits_config2->value.empty())) + { + BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent %1% for config %2%!") % inherits_config2->value % PathSanitizer::sanitize(preset.file); + continue; + } + // We support custom root preset now + // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. + preset.config = default_preset.config; + preset.config.apply(std::move(config)); + extend_default_config_length(preset.config, true, default_preset.config); + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id; + + Preset::normalize(preset.config); + // Report configuration fields, which are misplaced into a wrong group. + std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config); + if (!incorrect_keys.empty()) + BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << PathSanitizer::sanitize(preset.file) + << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; + + if (preset.type == Preset::TYPE_FILAMENT && preset.is_user() && preset.inherits().empty()) + { + auto compatible_printers = dynamic_cast(preset.config.option("compatible_printers", true)); + if (compatible_printers && compatible_printers->values.empty()) + { + size_t at_pos = name.find('@'); + if (at_pos != std::string::npos && at_pos + 1 < name.length()) + { + compatible_printers->values.push_back(name.substr(at_pos + 1)); + preset.save(nullptr); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << name; + } + } + } + + preset.loaded = true; + // BBS: add some workaround for previous incorrect settings + if ((!preset.setting_id.empty()) && (preset.setting_id == preset.base_id)) + preset.setting_id.clear(); + // BBS: add config related logs + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, is_system %3%, is_default %4%, is_visible %5%") % Preset::get_type_string(m_type) % preset.name % preset.is_system % preset.is_default % preset.is_visible; + // add alias for custom filament preset + set_custom_preset_alias(preset); } - DynamicPrintConfig config; - //BBS: change to json format - //ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule); - std::map key_values; - std::string reason; - ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason); - if (! config_substitutions.empty()) - substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) }); - if (!reason.empty()) { + catch (const std::ifstream::failure &err) + { + BOOST_LOG_TRIVIAL(error) << boost::format("The user-config cannot be loaded: %1%. Reason: %2%") % PathSanitizer::sanitize(preset.file) % err.what(); fs::path file_path(preset.file); if (fs::exists(file_path)) fs::remove(file_path); file_path.replace_extension(".info"); if (fs::exists(file_path)) fs::remove(file_path); - BOOST_LOG_TRIVIAL(error) << boost::format("parse config %1% failed") % PathSanitizer::sanitize(preset.file); - continue; - } - - std::string version_str = key_values[BBL_JSON_KEY_VERSION]; - boost::optional version = Semver::parse(version_str); - if (!version) continue; - Semver app_version = *(Semver::parse(SLIC3R_VERSION)); - if ( version->maj() > app_version.maj()) { - BOOST_LOG_TRIVIAL(warning) << "Preset incompatibla, not loading: " << name; - continue; - } - preset.version = *version; - - if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end()) - preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID]; - if (key_values.find(BBL_JSON_KEY_DESCRIPTION) != key_values.end()) - preset.description = key_values[BBL_JSON_KEY_DESCRIPTION]; - if (key_values.find(BBL_JSON_KEY_INSTANTIATION) != key_values.end()) - preset.is_visible = key_values[BBL_JSON_KEY_INSTANTIATION] != "false"; - - //BBS: use inherit config as the base - Preset* inherit_preset = nullptr; - ConfigOption* inherits_config = config.option(BBL_JSON_KEY_INHERITS); - - // check inherits_config - if (inherits_config) { - ConfigOptionString * option_str = dynamic_cast (inherits_config); - std::string inherits_value = option_str->value; - inherit_preset = this->find_preset(inherits_value, false, true); - } else { - ; - } - const Preset& default_preset = this->default_preset_for(config); - if (inherit_preset) { - preset.config = inherit_preset->config; - preset.filament_id = inherit_preset->filament_id; - extend_default_config_length(config, false, {}); - preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); - } - else { - auto inherits_config2 = dynamic_cast(inherits_config); - if ((inherits_config2 && !inherits_config2->value.empty())) { - BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent %1% for config %2%!") % inherits_config2->value % PathSanitizer::sanitize(preset.file); - continue; - } - // We support custom root preset now - // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. - preset.config = default_preset.config; - preset.config.apply(std::move(config)); - extend_default_config_length(preset.config, true, default_preset.config); + // throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id; - - Preset::normalize(preset.config); - // Report configuration fields, which are misplaced into a wrong group. - std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config); - if (!incorrect_keys.empty()) - BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << PathSanitizer::sanitize(preset.file) - << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; - - if (preset.type == Preset::TYPE_FILAMENT && preset.is_user() && preset.inherits().empty()) { - auto compatible_printers = dynamic_cast(preset.config.option("compatible_printers", true)); - if (compatible_printers && compatible_printers->values.empty()) { - size_t at_pos = name.find('@'); - if (at_pos != std::string::npos && at_pos + 1 < name.length()) { - compatible_printers->values.push_back(name.substr(at_pos + 1)); - preset.save(nullptr); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << name; - } - } + catch (const std::runtime_error &err) + { + BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%") % PathSanitizer::sanitize(preset.file) % err.what(); + // throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); + fs::path file_path(preset.file); + if (fs::exists(file_path)) + fs::remove(file_path); + file_path.replace_extension(".info"); + if (fs::exists(file_path)) + fs::remove(file_path); } - preset.loaded = true; - //BBS: add some workaround for previous incorrect settings - if ((!preset.setting_id.empty())&&(preset.setting_id == preset.base_id)) - preset.setting_id.clear(); - //BBS: add config related logs - BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, is_system %3%, is_default %4%, is_visible %5%")%Preset::get_type_string(m_type) %preset.name %preset.is_system %preset.is_default %preset.is_visible; - // add alias for custom filament preset - set_custom_preset_alias(preset); - } catch (const std::ifstream::failure &err) { - BOOST_LOG_TRIVIAL(error) << boost::format("The user-config cannot be loaded: %1%. Reason: %2%") % PathSanitizer::sanitize(preset.file) % err.what(); - fs::path file_path(preset.file); - if (fs::exists(file_path)) - fs::remove(file_path); - file_path.replace_extension(".info"); - if (fs::exists(file_path)) - fs::remove(file_path); - //throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); - } catch (const std::runtime_error &err) { - BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%") % PathSanitizer::sanitize(preset.file) % err.what(); - //throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); - fs::path file_path(preset.file); - if (fs::exists(file_path)) - fs::remove(file_path); - file_path.replace_extension(".info"); - if (fs::exists(file_path)) - fs::remove(file_path); + presets_loaded.emplace_back(preset); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name; + } + catch (const std::runtime_error &err) + { + errors_cummulative += err.what(); + errors_cummulative += "\n"; } - - presets_loaded.emplace_back(preset); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name; - } catch (const std::runtime_error &err) { - errors_cummulative += err.what(); - errors_cummulative += "\n"; } } - } - if (presets_loaded.size() > 0) - m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); - std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); - //BBS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ - << boost::format(": loaded %1% presets from %2%, type %3%") % presets_loaded.size() % PathSanitizer::sanitize(dir) % Preset::get_type_string(m_type); - //this->select_preset(first_visible_idx()); - if (! errors_cummulative.empty()) - throw Slic3r::RuntimeError(errors_cummulative); -} - -//BBS: add function to generate differed preset for save -//the pointer should be freed by the caller -Preset* PresetCollection::get_preset_differed_for_save(Preset& preset) -{ - if (preset.is_system || preset.is_default) - return nullptr; + if (presets_loaded.size() > 0) + m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); + std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); + // BBS: add config related logs + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ + << boost::format(": loaded %1% presets from %2%, type %3%") % presets_loaded.size() % PathSanitizer::sanitize(dir) % Preset::get_type_string(m_type); + // this->select_preset(first_visible_idx()); + if (!errors_cummulative.empty()) + throw Slic3r::RuntimeError(errors_cummulative); + } + + // BBS: add function to generate differed preset for save + // the pointer should be freed by the caller + Preset *PresetCollection::get_preset_differed_for_save(Preset &preset) + { + if (preset.is_system || preset.is_default) + return nullptr; - Preset* new_preset = nullptr; - //BBS: only save difference for user preset - std::string& inherits = preset.inherits(); - Preset* parent_preset = nullptr; - if (!inherits.empty()) { - parent_preset = this->find_preset(inherits, false, true); - } - if (parent_preset) { - new_preset = new Preset(); - *new_preset = preset; + Preset *new_preset = nullptr; + // BBS: only save difference for user preset + std::string &inherits = preset.inherits(); + Preset *parent_preset = nullptr; + if (!inherits.empty()) + { + parent_preset = this->find_preset(inherits, false, true); + } + if (parent_preset) + { + new_preset = new Preset(); + *new_preset = preset; - DynamicPrintConfig temp_config; - std::vector dirty_options = preset.config.diff(parent_preset->config); + DynamicPrintConfig temp_config; + std::vector dirty_options = preset.config.diff(parent_preset->config); - std::string extruder_id_name, extruder_variant_name; - std::set *key_set1 = nullptr, *key_set2 = nullptr; - Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); - if (!extruder_id_name.empty()) { - dirty_options.emplace_back(extruder_id_name); - } - if (!extruder_variant_name.empty()) { - dirty_options.emplace_back(extruder_variant_name); - } + if (!extruder_id_name.empty()) + { + dirty_options.emplace_back(extruder_id_name); + } + if (!extruder_variant_name.empty()) + { + dirty_options.emplace_back(extruder_variant_name); + } - for (auto option: dirty_options) - { - ConfigOption *opt_src = preset.config.option(option); - ConfigOption *opt_dst = temp_config.option(option, true); - if (opt_dst->is_scalar() || !(opt_dst->nullable())) - opt_dst->set(opt_src); - else { - ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); - ConfigOptionVectorBase* opt_vec_dst = static_cast(opt_dst); - ConfigOptionVectorBase* opt_vec_inherit = static_cast(parent_preset->config.option(option)); - if (opt_vec_src->size() == 1) + for (auto option : dirty_options) + { + ConfigOption *opt_src = preset.config.option(option); + ConfigOption *opt_dst = temp_config.option(option, true); + if (opt_dst->is_scalar() || !(opt_dst->nullable())) opt_dst->set(opt_src); - else if (key_set1->find(option) != key_set1->end()) { - opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 1); - } - else if (key_set2->find(option) != key_set2->end()) { - opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 2); - } else - opt_dst->set(opt_src); + { + ConfigOptionVectorBase *opt_vec_src = static_cast(opt_src); + ConfigOptionVectorBase *opt_vec_dst = static_cast(opt_dst); + ConfigOptionVectorBase *opt_vec_inherit = static_cast(parent_preset->config.option(option)); + if (opt_vec_src->size() == 1) + opt_dst->set(opt_src); + else if (key_set1->find(option) != key_set1->end()) + { + opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 1); + } + else if (key_set2->find(option) != key_set2->end()) + { + opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 2); + } + else + opt_dst->set(opt_src); + } } + + new_preset->config = temp_config; } - new_preset->config = temp_config; + return new_preset; } - return new_preset; -} - -//BBS:get the differencen values to update -int PresetCollection::get_differed_values_to_update(Preset& preset, std::map& key_values) -{ - if (preset.is_system || preset.is_default || preset.is_project_embedded) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" Error: not a user preset! Should not happen, name %1%") %preset.name; - return -1; - } + // BBS:get the differencen values to update + int PresetCollection::get_differed_values_to_update(Preset &preset, std::map &key_values) + { + if (preset.is_system || preset.is_default || preset.is_project_embedded) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" Error: not a user preset! Should not happen, name %1%") % preset.name; + return -1; + } - //BBS: only save difference for user preset - std::string& inherit_preset = preset.inherits(); - Preset* parent_preset = nullptr; - if (!inherit_preset.empty()) { - parent_preset = this->find_preset(inherit_preset, false, true); - } - if (parent_preset) { - DynamicPrintConfig temp_config; - std::vector dirty_options = preset.config.diff(parent_preset->config); + // BBS: only save difference for user preset + std::string &inherit_preset = preset.inherits(); + Preset *parent_preset = nullptr; + if (!inherit_preset.empty()) + { + parent_preset = this->find_preset(inherit_preset, false, true); + } + if (parent_preset) + { + DynamicPrintConfig temp_config; + std::vector dirty_options = preset.config.diff(parent_preset->config); + + for (auto option : dirty_options) + { + ConfigOption *opt_src = preset.config.option(option); + if (opt_src) + key_values[option] = opt_src->serialize(); + } + } + else + { + for (auto iter = preset.config.cbegin(); iter != preset.config.cend(); ++iter) + { + key_values[iter->first] = iter->second->serialize(); + } + } - for (auto option: dirty_options) + // add other values + key_values[BBL_JSON_KEY_VERSION] = preset.version.to_string(); + if (!preset.base_id.empty()) { - ConfigOption *opt_src = preset.config.option(option); - if (opt_src) - key_values[option] = opt_src->serialize(); + key_values[BBL_JSON_KEY_BASE_ID] = preset.base_id; } - } - else { - for (auto iter = preset.config.cbegin(); iter != preset.config.cend(); ++iter) + else { - key_values[iter->first] = iter->second->serialize(); + key_values.erase(BBL_JSON_KEY_BASE_ID); + if (get_preset_base(preset) == &preset && !preset.filament_id.empty()) + { + key_values[BBL_JSON_KEY_FILAMENT_ID] = preset.filament_id; + } } - } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " uploading user preset name is: " << preset.name << "and create filament_id is: " << preset.filament_id + << " and base_id is: " << preset.base_id; + key_values[BBL_JSON_KEY_UPDATE_TIME] = std::to_string(preset.updated_time); + key_values[BBL_JSON_KEY_TYPE] = Preset::get_iot_type_string(preset.type); - //add other values - key_values[BBL_JSON_KEY_VERSION] = preset.version.to_string(); - if (!preset.base_id.empty()) { - key_values[BBL_JSON_KEY_BASE_ID] = preset.base_id; - } else { - key_values.erase(BBL_JSON_KEY_BASE_ID); - if (get_preset_base(preset) == &preset && !preset.filament_id.empty()) { - key_values[BBL_JSON_KEY_FILAMENT_ID] = preset.filament_id; + int update_size = 0; + for (const auto &pair : key_values) + { + update_size += pair.first.size(); + update_size += pair.second.size(); } - } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " uploading user preset name is: " << preset.name << "and create filament_id is: " << preset.filament_id - << " and base_id is: " << preset.base_id; - key_values[BBL_JSON_KEY_UPDATE_TIME] = std::to_string(preset.updated_time); - key_values[BBL_JSON_KEY_TYPE] = Preset::get_iot_type_string(preset.type); + if (update_size > 350 * 1024) + return -2; - int update_size = 0; - for (const auto &pair : key_values) { - update_size += pair.first.size(); - update_size += pair.second.size(); + return 0; } - if (update_size > 350 * 1024) return -2; - return 0; -} + // BBS: save user presets to local + void PresetCollection::load_project_embedded_presets(std::vector &project_presets, const std::string &type, PresetsConfigSubstitutions &substitutions, ForwardCompatibilitySubstitutionRule rule) + { + std::string errors_cummulative; + // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. + // (see the "Preset already present, not loading" message). + std::deque presets_loaded; + std::vector::iterator it; -//BBS: save user presets to local -void PresetCollection::load_project_embedded_presets(std::vector& project_presets, const std::string& type, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule) -{ - std::string errors_cummulative; - // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. - // (see the "Preset already present, not loading" message). - std::deque presets_loaded; - std::vector::iterator it; - - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , total preset counts %2%")%Preset::get_type_string(m_type) %project_presets.size(); - std::string extruder_id_name, extruder_variant_name; - std::set *key_set1 = nullptr, *key_set2 = nullptr; - Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); - - lock(); - for (it = project_presets.begin(); it != project_presets.end(); it++) { - Preset* preset = *it; - if (preset->type != Preset::get_type_from_string(type)) continue; - if (!preset->is_project_embedded) continue; - std::string name = preset->name; - if (this->find_preset(name, false)) { - BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; - continue; - } - try { - DynamicPrintConfig config = preset->config; - if (preset->loading_substitutions && ! preset->loading_substitutions->empty()) { - substitutions.push_back({ preset->name, m_type, PresetConfigSubstitutions::Source::ProjectFile, preset->name, std::move(*(preset->loading_substitutions))}); - free(preset->loading_substitutions); - preset->loading_substitutions = NULL; - } - //BBS: use inherit config as the base - Preset* inherit_preset = nullptr; - ConfigOption* inherits_config = config.option(BBL_JSON_KEY_INHERITS); - if (inherits_config) { - ConfigOptionString * option_str = dynamic_cast (inherits_config); - std::string inherits_value = option_str->value; - /*size_t pos = inherits_value.find_first_of('*'); - if (pos != std::string::npos) { - inherits_value.replace(pos, 1, 1, '~'); - option_str->value = inherits_value; - }*/ - inherit_preset = this->find_preset(inherits_value, false, true); - } - const Preset& default_preset = this->default_preset_for(config); - if (inherit_preset) { - preset->config = inherit_preset->config; - preset->filament_id = inherit_preset->filament_id; - } - else { - // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. - //BBS 202407: don't load project embedded preset when can not find inherit - //preset->config = default_preset.config; - BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent for config %1%!")%preset->file; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , total preset counts %2%") % Preset::get_type_string(m_type) % project_presets.size(); + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + + lock(); + for (it = project_presets.begin(); it != project_presets.end(); it++) + { + Preset *preset = *it; + if (preset->type != Preset::get_type_from_string(type)) + continue; + if (!preset->is_project_embedded) + continue; + std::string name = preset->name; + if (this->find_preset(name, false)) + { + BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; continue; } - preset->config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); - //preset->config.apply(std::move(config)); - Preset::normalize(preset->config); - // Report configuration fields, which are misplaced into a wrong group. - std::string incorrect_keys = Preset::remove_invalid_keys(preset->config, default_preset.config); - if (! incorrect_keys.empty()) - BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << - preset->name << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; - preset->loaded = true; - presets_loaded.emplace_back(*preset); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", %1% got preset, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset->name %preset->file %preset->is_system %preset->is_default %preset->is_visible; - } catch (const std::runtime_error &err) { - errors_cummulative += err.what(); - errors_cummulative += "\n"; + try + { + DynamicPrintConfig config = preset->config; + if (preset->loading_substitutions && !preset->loading_substitutions->empty()) + { + substitutions.push_back({preset->name, m_type, PresetConfigSubstitutions::Source::ProjectFile, preset->name, std::move(*(preset->loading_substitutions))}); + free(preset->loading_substitutions); + preset->loading_substitutions = NULL; + } + // BBS: use inherit config as the base + Preset *inherit_preset = nullptr; + ConfigOption *inherits_config = config.option(BBL_JSON_KEY_INHERITS); + if (inherits_config) + { + ConfigOptionString *option_str = dynamic_cast(inherits_config); + std::string inherits_value = option_str->value; + /*size_t pos = inherits_value.find_first_of('*'); + if (pos != std::string::npos) { + inherits_value.replace(pos, 1, 1, '~'); + option_str->value = inherits_value; + }*/ + inherit_preset = this->find_preset(inherits_value, false, true); + } + const Preset &default_preset = this->default_preset_for(config); + if (inherit_preset) + { + preset->config = inherit_preset->config; + preset->filament_id = inherit_preset->filament_id; + } + else + { + // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. + // BBS 202407: don't load project embedded preset when can not find inherit + // preset->config = default_preset.config; + BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent for config %1%!") % preset->file; + continue; + } + preset->config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); + // preset->config.apply(std::move(config)); + Preset::normalize(preset->config); + // Report configuration fields, which are misplaced into a wrong group. + std::string incorrect_keys = Preset::remove_invalid_keys(preset->config, default_preset.config); + if (!incorrect_keys.empty()) + BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << preset->name << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; + preset->loaded = true; + presets_loaded.emplace_back(*preset); + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", %1% got preset, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%") % Preset::get_type_string(m_type) % preset->name % preset->file % preset->is_system % preset->is_default % preset->is_visible; + } + catch (const std::runtime_error &err) + { + errors_cummulative += err.what(); + errors_cummulative += "\n"; + } } - } - m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); - std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); - //don't select it here - //this->select_preset(first_visible_idx()); - unlock(); + m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); + std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); + // don't select it here + // this->select_preset(first_visible_idx()); + unlock(); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, %1% got %2% presets, errors_cummulative %3%")%Preset::get_type_string(m_type) %presets_loaded.size() %errors_cummulative; - if (! errors_cummulative.empty()) - throw Slic3r::RuntimeError(errors_cummulative); -} + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, %1% got %2% presets, errors_cummulative %3%") % Preset::get_type_string(m_type) % presets_loaded.size() % errors_cummulative; + if (!errors_cummulative.empty()) + throw Slic3r::RuntimeError(errors_cummulative); + } -//BBS: get project embedded presets from -std::vector PresetCollection::get_project_embedded_presets() -{ - std::vector project_presets; + // BBS: get project embedded presets from + std::vector PresetCollection::get_project_embedded_presets() + { + std::vector project_presets; - lock(); - for (Preset &preset : m_presets) { - //if (preset.type != Preset::get_type_from_string(type)) continue; - if (!preset.is_project_embedded) continue; + lock(); + for (Preset &preset : m_presets) + { + // if (preset.type != Preset::get_type_from_string(type)) continue; + if (!preset.is_project_embedded) + continue; - Preset* new_preset = get_preset_differed_for_save(preset); + Preset *new_preset = get_preset_differed_for_save(preset); - if (new_preset) - project_presets.push_back(new_preset); + if (new_preset) + project_presets.push_back(new_preset); + } + unlock(); + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , total preset counts %2%") % Preset::get_type_string(m_type) % project_presets.size(); + return project_presets; } - unlock(); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , total preset counts %2%")%Preset::get_type_string(m_type) %project_presets.size(); - return project_presets; -} - -//BBS: reset project embedded presets -bool PresetCollection::reset_project_embedded_presets() -{ - std::deque::iterator it = m_presets.begin(); - bool re_select = false; - int count = -1; - lock(); - while ( it!=m_presets.end() ) + // BBS: reset project embedded presets + bool PresetCollection::reset_project_embedded_presets() { - count++; - //if (preset.type != Preset::get_type_from_string(type)) continue; - if (it->is_project_embedded) { - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" type %1% , delete preset %2%")%Preset::get_type_string(m_type) % it->name; - if ((!re_select) && (m_idx_selected == count)) - re_select = true; - if (m_idx_selected > count) { - m_idx_selected--; - count--; + std::deque::iterator it = m_presets.begin(); + bool re_select = false; + int count = -1; + + lock(); + while (it != m_presets.end()) + { + count++; + // if (preset.type != Preset::get_type_from_string(type)) continue; + if (it->is_project_embedded) + { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" type %1% , delete preset %2%") % Preset::get_type_string(m_type) % it->name; + if ((!re_select) && (m_idx_selected == count)) + re_select = true; + if (m_idx_selected > count) + { + m_idx_selected--; + count--; + } + it = m_presets.erase(it); } - it = m_presets.erase(it); + else + it++; } - else - it++; - } - if (re_select) - m_idx_selected = -1; + if (re_select) + m_idx_selected = -1; - unlock(); + unlock(); - return re_select; -} + return re_select; + } -void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time) -{ - lock(); - for (auto it = m_presets.begin(); it != m_presets.end(); it++) { - Preset* preset = &m_presets[it - m_presets.begin()]; - if (preset->name == name) { - if (syncinfo.empty()) - preset->sync_info.clear(); - else - preset->sync_info = syncinfo; - if (get_preset_base(*preset) == preset) { - for (auto & preset2 : m_presets) - if (preset2.inherits() == preset->name) { - preset2.base_id = setting_id; - preset2.save_info(); - } + void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time) + { + lock(); + for (auto it = m_presets.begin(); it != m_presets.end(); it++) + { + Preset *preset = &m_presets[it - m_presets.begin()]; + if (preset->name == name) + { + if (syncinfo.empty()) + preset->sync_info.clear(); + else + preset->sync_info = syncinfo; + if (get_preset_base(*preset) == preset) + { + for (auto &preset2 : m_presets) + if (preset2.inherits() == preset->name) + { + preset2.base_id = setting_id; + preset2.save_info(); + } + } + preset->setting_id = setting_id; + if (update_time > 0) + preset->updated_time = update_time; + if (preset->sync_info == "update") + preset->save(nullptr); + else + preset->save_info(); + break; } - preset->setting_id = setting_id; - if (update_time > 0) - preset->updated_time = update_time; - if (preset->sync_info == "update") - preset->save(nullptr); - else - preset->save_info(); - break; } + unlock(); } - unlock(); -} -bool PresetCollection::need_sync(std::string name, std::string setting_id, long long update_time) -{ - lock(); - auto preset = find_preset(name, false, true); - bool need = preset == nullptr || preset->setting_id != setting_id || preset->updated_time < update_time; - unlock(); - return need; -} - -//BBS: get user presets -int PresetCollection::get_user_presets(PresetBundle *preset_bundle, std::vector &result_presets) -{ - int count = 0; - result_presets.clear(); + bool PresetCollection::need_sync(std::string name, std::string setting_id, long long update_time) + { + lock(); + auto preset = find_preset(name, false, true); + bool need = preset == nullptr || preset->setting_id != setting_id || preset->updated_time < update_time; + unlock(); + return need; + } - lock(); - for (Preset &preset : m_presets) { - if (!preset.is_user()) continue; - if (preset.base_id.empty() && preset.inherits() != "") continue; - if (!preset.setting_id.empty() && preset.sync_info.empty()) continue; - //if (!preset.is_bbl_vendor_preset(preset_bundle)) continue; - if (preset.sync_info == "hold") continue; + // BBS: get user presets + int PresetCollection::get_user_presets(PresetBundle *preset_bundle, std::vector &result_presets) + { + int count = 0; + result_presets.clear(); - result_presets.push_back(preset); - count++; - } - unlock(); + lock(); + for (Preset &preset : m_presets) + { + if (!preset.is_user()) + continue; + if (preset.base_id.empty() && preset.inherits() != "") + continue; + if (!preset.setting_id.empty() && preset.sync_info.empty()) + continue; + // if (!preset.is_bbl_vendor_preset(preset_bundle)) continue; + if (preset.sync_info == "hold") + continue; + + result_presets.push_back(preset); + count++; + } + unlock(); - return count; -} + return count; + } -//BBS: update user presets directory -void PresetCollection::update_user_presets_directory(const std::string& dir_path, const std::string& type) -{ - boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / type).make_preferred(); + // BBS: update user presets directory + void PresetCollection::update_user_presets_directory(const std::string &dir_path, const std::string &type) + { + boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / type).make_preferred(); - if (!fs::exists(dir)) - fs::create_directory(dir); + if (!fs::exists(dir)) + fs::create_directory(dir); - m_dir_path = dir.string(); -} + m_dir_path = dir.string(); + } -//BBS: save user presets to local -void PresetCollection::save_user_presets(const std::string& dir_path, const std::string& type, std::vector& need_to_delete_list) -{ - boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / type).make_preferred(); + // BBS: save user presets to local + void PresetCollection::save_user_presets(const std::string &dir_path, const std::string &type, std::vector &need_to_delete_list) + { + boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / type).make_preferred(); - if (!fs::exists(dir)) - fs::create_directory(dir); + if (!fs::exists(dir)) + fs::create_directory(dir); - m_dir_path = dir.string(); + m_dir_path = dir.string(); - std::vector delete_name_list; - //std::map::iterator it; - //for (it = my_presets.begin(); it != my_presets.end(); it++) { - for (auto it = m_presets.begin(); it != m_presets.end(); it++) { - Preset* preset = &m_presets[it - m_presets.begin()]; - if (!preset->is_user()) continue; - if (preset->sync_info != "save") continue; - preset->sync_info.clear(); - preset->file = path_for_preset(*preset); + std::vector delete_name_list; + // std::map::iterator it; + // for (it = my_presets.begin(); it != my_presets.end(); it++) { + for (auto it = m_presets.begin(); it != m_presets.end(); it++) + { + Preset *preset = &m_presets[it - m_presets.begin()]; + if (!preset->is_user()) + continue; + if (preset->sync_info != "save") + continue; + preset->sync_info.clear(); + preset->file = path_for_preset(*preset); + + // BBS: only save difference for user preset + std::string inherits = Preset::inherits(preset->config); + if (inherits.empty()) + { + // We support custom root preset now + // BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" can not find inherits for %1% , should not happen")%preset->name; + //// BBS add sync info + // preset->sync_info = "delete"; + // need_to_delete_list.push_back(preset->setting_id); + // delete_name_list.push_back(preset->name); + preset->save(nullptr); + continue; + } + Preset *parent_preset = this->find_preset(inherits, false, true); + if (!parent_preset) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" can not find parent preset for %1% , inherits %2%") % preset->name % inherits; + continue; + } - //BBS: only save difference for user preset - std::string inherits = Preset::inherits(preset->config); - if (inherits.empty()) { - // We support custom root preset now - //BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" can not find inherits for %1% , should not happen")%preset->name; - //// BBS add sync info - //preset->sync_info = "delete"; - //need_to_delete_list.push_back(preset->setting_id); - //delete_name_list.push_back(preset->name); - preset->save(nullptr); - continue; + if (preset->base_id.empty()) + preset->base_id = parent_preset->setting_id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << preset->name << " filament_id: " << preset->filament_id << " base_id: " << preset->base_id; + preset->save(&(parent_preset->config)); } - Preset* parent_preset = this->find_preset(inherits, false, true); - if (!parent_preset) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" can not find parent preset for %1% , inherits %2%")%preset->name %inherits; - continue; + + for (auto delete_name : delete_name_list) + { + this->delete_preset(delete_name); } + delete_name_list.clear(); - if (preset->base_id.empty()) - preset->base_id = parent_preset->setting_id; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << preset->name << " filament_id: " << preset->filament_id << " base_id: " << preset->base_id; - preset->save(&(parent_preset->config)); + return; } - for (auto delete_name: delete_name_list) + // BBS: load one user preset from key-values + bool PresetCollection::load_user_preset(std::string name, std::map preset_values, PresetsConfigSubstitutions &substitutions, ForwardCompatibilitySubstitutionRule rule) { - this->delete_preset(delete_name); - } - delete_name_list.clear(); + std::string errors_cummulative; + // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. + // (see the "Preset already present, not loading" message). + // std::deque presets_loaded; + int count = 0; - return; -} + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%") % name % preset_values.size(); -//BBS: load one user preset from key-values -bool PresetCollection::load_user_preset(std::string name, std::map preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule) -{ - std::string errors_cummulative; - // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. - // (see the "Preset already present, not loading" message). - //std::deque presets_loaded; - int count = 0; - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%")%name %preset_values.size(); + // if the version is not matching, skip it + if (preset_values.find(BBL_JSON_KEY_VERSION) == preset_values.end()) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%") % name; + return false; + } + std::string version_str = preset_values[BBL_JSON_KEY_VERSION]; + boost::optional cloud_version = Semver::parse(version_str); + if (!cloud_version) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%") % version_str % name; + return false; + } + Semver app_version = *(Semver::parse(SLIC3R_VERSION)); + if (cloud_version->maj() > app_version.maj()) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("version %1% mismatch with app version %2%, not loading for user preset %3%") % version_str % SLIC3R_VERSION % name; + return false; + } - //if the version is not matching, skip it - if (preset_values.find(BBL_JSON_KEY_VERSION) == preset_values.end()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%")%name; - return false; - } - std::string version_str = preset_values[BBL_JSON_KEY_VERSION]; - boost::optional cloud_version = Semver::parse(version_str); - if (!cloud_version) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%")%version_str %name; - return false; - } - Semver app_version = *(Semver::parse(SLIC3R_VERSION)); - if ( cloud_version->maj() > app_version.maj()) { - BOOST_LOG_TRIVIAL(warning)<< __FUNCTION__ << boost::format("version %1% mismatch with app version %2%, not loading for user preset %3%")%version_str %SLIC3R_VERSION %name; - return false; - } + // setting_id + if (preset_values.find(BBL_JSON_KEY_SETTING_ID) == preset_values.end()) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%") % name; + return false; + } + std::string cloud_setting_id = preset_values[BBL_JSON_KEY_SETTING_ID]; - //setting_id - if (preset_values.find(BBL_JSON_KEY_SETTING_ID) == preset_values.end()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%")%name; - return false; - } - std::string cloud_setting_id = preset_values[BBL_JSON_KEY_SETTING_ID]; + // update_time + long long cloud_update_time = 0; + if (preset_values.find(BBL_JSON_KEY_UPDATE_TIME) != preset_values.end()) + { + cloud_update_time = std::atoll(preset_values[BBL_JSON_KEY_UPDATE_TIME].c_str()); + } - //update_time - long long cloud_update_time = 0; - if (preset_values.find(BBL_JSON_KEY_UPDATE_TIME) != preset_values.end()) { - cloud_update_time = std::atoll(preset_values[BBL_JSON_KEY_UPDATE_TIME].c_str()); - } + // user_id + if (preset_values.find(BBL_JSON_KEY_USER_ID) == preset_values.end()) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find user_id, not loading for user preset %1%") % name; + return false; + } + std::string cloud_user_id = preset_values[BBL_JSON_KEY_USER_ID]; - //user_id - if (preset_values.find(BBL_JSON_KEY_USER_ID) == preset_values.end()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find user_id, not loading for user preset %1%")%name; - return false; - } - std::string cloud_user_id = preset_values[BBL_JSON_KEY_USER_ID]; - - lock(); - //std::string name = preset->name; - auto iter = this->find_preset_internal(name); - bool need_update = false; - if ((iter != m_presets.end()) && (iter->name == name)) { - BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << name; - //BBS: we should compare the time between cloud and local - if ((cloud_update_time == 0) || (cloud_update_time <= iter->updated_time)) { - if (cloud_update_time < iter->updated_time) - iter->sync_info = "update"; + lock(); + // std::string name = preset->name; + auto iter = this->find_preset_internal(name); + bool need_update = false; + if ((iter != m_presets.end()) && (iter->name == name)) + { + BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << name; + // BBS: we should compare the time between cloud and local + if ((cloud_update_time == 0) || (cloud_update_time <= iter->updated_time)) + { + if (cloud_update_time < iter->updated_time) + iter->sync_info = "update"; + else + iter->sync_info.clear(); + // Fixup possible data lost + iter->setting_id = cloud_setting_id; + fs::path idx_file(iter->file); + idx_file.replace_extension(".info"); + iter->save_info(idx_file.string()); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%") % name % cloud_update_time % iter->updated_time; + unlock(); + return false; + } else - iter->sync_info.clear(); - // Fixup possible data lost - iter->setting_id = cloud_setting_id; - fs::path idx_file(iter->file); - idx_file.replace_extension(".info"); - iter->save_info(idx_file.string()); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%")%name %cloud_update_time %iter->updated_time; + { + // update the one from cloud which is newer + need_update = true; + } + } + + // base_id + if (preset_values.find(BBL_JSON_KEY_BASE_ID) == preset_values.end()) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find base_id, not loading for user preset %1%") % name; unlock(); return false; } - else { - //update the one from cloud which is newer - need_update = true; + std::string cloud_base_id = preset_values[BBL_JSON_KEY_BASE_ID]; + + // filament_id + std::string cloud_filament_id; + if ((m_type == Preset::TYPE_FILAMENT) && preset_values.find(BBL_JSON_KEY_FILAMENT_ID) != preset_values.end()) + { + cloud_filament_id = preset_values[BBL_JSON_KEY_FILAMENT_ID]; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << name << " filament_id: " << cloud_filament_id << " base_id: " << cloud_base_id; } - } - // base_id - if (preset_values.find(BBL_JSON_KEY_BASE_ID) == preset_values.end()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find base_id, not loading for user preset %1%") % name; - unlock(); - return false; - } - std::string cloud_base_id = preset_values[BBL_JSON_KEY_BASE_ID]; - - //filament_id - std::string cloud_filament_id; - if ((m_type == Preset::TYPE_FILAMENT) && preset_values.find(BBL_JSON_KEY_FILAMENT_ID) != preset_values.end()) { - cloud_filament_id = preset_values[BBL_JSON_KEY_FILAMENT_ID]; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << name << " filament_id: " << cloud_filament_id << " base_id: " << cloud_base_id; - } - - DynamicPrintConfig new_config, cloud_config; - try { - ConfigSubstitutions config_substitutions = cloud_config.load_string_map(preset_values, rule); - if (! config_substitutions.empty()) - substitutions.push_back({ name, m_type, PresetConfigSubstitutions::Source::UserCloud, name, std::move(config_substitutions) }); - - //BBS: use inherit config as the base - Preset* inherit_preset = nullptr; - ConfigOption* inherits_config = cloud_config.option(BBL_JSON_KEY_INHERITS); - if (inherits_config) { - ConfigOptionString * option_str = dynamic_cast (inherits_config); - std::string inherits_value = option_str->value; - /*size_t pos = inherits_value.find_first_of('*'); - if (pos != std::string::npos) { - inherits_value.replace(pos, 1, 1, '~'); - option_str->value = inherits_value; - }*/ - inherit_preset = this->find_preset(inherits_value, false, true); - } - const Preset& default_preset = this->default_preset_for(cloud_config); - if (inherit_preset) { - new_config = inherit_preset->config; - if (cloud_filament_id == "null") { - cloud_filament_id = inherit_preset->filament_id; - } - } - else { - // We support custom root preset now - auto inherits_config2 = dynamic_cast(inherits_config); - if (inherits_config2 && !inherits_config2->value.empty()) { - //we should skip this preset here - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip")%name; - unlock(); - return false; + DynamicPrintConfig new_config, cloud_config; + try + { + ConfigSubstitutions config_substitutions = cloud_config.load_string_map(preset_values, rule); + if (!config_substitutions.empty()) + substitutions.push_back({name, m_type, PresetConfigSubstitutions::Source::UserCloud, name, std::move(config_substitutions)}); + + // BBS: use inherit config as the base + Preset *inherit_preset = nullptr; + ConfigOption *inherits_config = cloud_config.option(BBL_JSON_KEY_INHERITS); + if (inherits_config) + { + ConfigOptionString *option_str = dynamic_cast(inherits_config); + std::string inherits_value = option_str->value; + /*size_t pos = inherits_value.find_first_of('*'); + if (pos != std::string::npos) { + inherits_value.replace(pos, 1, 1, '~'); + option_str->value = inherits_value; + }*/ + inherit_preset = this->find_preset(inherits_value, false, true); + } + const Preset &default_preset = this->default_preset_for(cloud_config); + if (inherit_preset) + { + new_config = inherit_preset->config; + if (cloud_filament_id == "null") + { + cloud_filament_id = inherit_preset->filament_id; + } + } + else + { + // We support custom root preset now + auto inherits_config2 = dynamic_cast(inherits_config); + if (inherits_config2 && !inherits_config2->value.empty()) + { + // we should skip this preset here + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip") % name; + unlock(); + return false; + } + // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. + new_config = default_preset.config; + } + + extend_default_config_length(cloud_config, false, {}); + + if (inherit_preset) + { + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + + new_config.update_diff_values_to_child_config(cloud_config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); } - // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. - new_config = default_preset.config; + else + { + new_config.apply(std::move(cloud_config)); + extend_default_config_length(new_config, true, default_preset.config); + } + Preset::normalize(new_config); + // Report configuration fields, which are misplaced into a wrong group. + std::string incorrect_keys = Preset::remove_invalid_keys(new_config, default_preset.config); + if (!incorrect_keys.empty()) + BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << name << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; + if (need_update) + { + if (iter->name == m_edited_preset.name && iter->is_dirty) + { + // Keep modifies when update from remote + new_config.apply_only(m_edited_preset.config, m_edited_preset.config.diff(iter->config)); + } + iter->config = new_config; + iter->updated_time = cloud_update_time; + iter->sync_info = "save"; + iter->version = cloud_version.value(); + iter->user_id = cloud_user_id; + iter->setting_id = cloud_setting_id; + iter->base_id = cloud_base_id; + iter->filament_id = cloud_filament_id; + // presets_loaded.emplace_back(*it->second); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%") % iter->name % Preset::get_type_string(m_type) % iter->setting_id % iter->base_id % iter->sync_info % iter->inherits() % iter->filament_id; + } + else + { + // create a new one + Preset preset(m_type, name, false); + preset.is_system = false; + preset.loaded = true; + preset.config = new_config; + preset.updated_time = cloud_update_time; + preset.sync_info = "save"; + preset.version = cloud_version.value(); + preset.user_id = cloud_user_id; + preset.setting_id = cloud_setting_id; + preset.base_id = cloud_base_id; + preset.filament_id = cloud_filament_id; + + size_t cur_index = iter - m_presets.begin(); + m_presets.insert(iter, preset); + // m_presets.emplace_back (preset); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", insert a new user preset %1%, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%") % preset.name % Preset::get_type_string(m_type) % preset.setting_id % preset.base_id % preset.sync_info % preset.inherits() % preset.filament_id; + if (cur_index <= m_idx_selected) + { + m_idx_selected++; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", increase m_idx_selected to %1%, due to user preset inserted") % m_idx_selected; + } + } + } + catch (const std::runtime_error &err) + { + errors_cummulative += err.what(); + errors_cummulative += "\n"; } - extend_default_config_length(cloud_config, false, {}); + unlock(); + + if (!errors_cummulative.empty()) + throw Slic3r::RuntimeError(errors_cummulative); - if (inherit_preset) { - std::string extruder_id_name, extruder_variant_name; - std::set *key_set1 = nullptr, *key_set2 = nullptr; - Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, load user preset %1% , type %2%, errors_cummulative %3%") % name % Preset::get_type_string(m_type) % errors_cummulative; + return (need_update) ? false : true; + } - new_config.update_diff_values_to_child_config(cloud_config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); - } - else{ - new_config.apply(std::move(cloud_config)); - extend_default_config_length(new_config, true, default_preset.config); - } - Preset::normalize(new_config); - // Report configuration fields, which are misplaced into a wrong group. - std::string incorrect_keys = Preset::remove_invalid_keys(new_config, default_preset.config); - if (! incorrect_keys.empty()) - BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << - name << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; - if (need_update) { - if (iter->name == m_edited_preset.name && iter->is_dirty) { - // Keep modifies when update from remote - new_config.apply_only(m_edited_preset.config, m_edited_preset.config.diff(iter->config)); - } - iter->config = new_config; - iter->updated_time = cloud_update_time; - iter->sync_info = "save"; - iter->version = cloud_version.value(); - iter->user_id = cloud_user_id; - iter->setting_id = cloud_setting_id; - iter->base_id = cloud_base_id; - iter->filament_id = cloud_filament_id; - //presets_loaded.emplace_back(*it->second); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%") - % iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id; - } - else { - //create a new one - Preset preset(m_type, name, false); - preset.is_system = false; - preset.loaded = true; - preset.config = new_config; - preset.updated_time = cloud_update_time; - preset.sync_info = "save"; - preset.version = cloud_version.value(); - preset.user_id = cloud_user_id; - preset.setting_id = cloud_setting_id; - preset.base_id = cloud_base_id; - preset.filament_id = cloud_filament_id; - - size_t cur_index = iter - m_presets.begin(); - m_presets.insert(iter, preset); - //m_presets.emplace_back (preset); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", insert a new user preset %1%, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%") - %preset.name %Preset::get_type_string(m_type) %preset.setting_id %preset.base_id %preset.sync_info %preset.inherits() %preset.filament_id; - if (cur_index <= m_idx_selected) { - m_idx_selected ++; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", increase m_idx_selected to %1%, due to user preset inserted")%m_idx_selected; - } - } - } catch (const std::runtime_error &err) { - errors_cummulative += err.what(); - errors_cummulative += "\n"; - } - - unlock(); - - if (! errors_cummulative.empty()) - throw Slic3r::RuntimeError(errors_cummulative); - - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, load user preset %1% , type %2%, errors_cummulative %3%")%name %Preset::get_type_string(m_type) %errors_cummulative; - return (need_update)?false:true; -} - -//re-sort and re-select -void PresetCollection::update_after_user_presets_loaded() -{ - lock(); - std::string selected_name = get_selected_preset_name(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", before sort, type %1%, selected_idx %2%, selected_name %3%") %m_type %m_idx_selected %selected_name; - std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); - this->select_preset_by_name(selected_name, false); - unlock(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", after sort, type %1%, selected_idx %2%") %m_type %m_idx_selected; - - return; -} - -//BBS: validate_preset -bool PresetCollection::validate_preset(const std::string &preset_name, std::string &inherit_name) -{ - std::deque::iterator it = this->find_preset_internal(preset_name); - bool found = (it != m_presets.end()) && (it->name == preset_name) && (it->is_system || it->is_default); - if (!found) { - it = this->find_preset_renamed(preset_name); - found = it != m_presets.end() && (it->is_system || it->is_default); - } - if (!found) { - if (!inherit_name.empty()) { - it = this->find_preset_internal(inherit_name); - found = it != m_presets.end() && it->name == inherit_name && (it->is_system || it->is_default); - if (found) - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, found inherit in list")%preset_name %inherit_name; + // re-sort and re-select + void PresetCollection::update_after_user_presets_loaded() + { + lock(); + std::string selected_name = get_selected_preset_name(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", before sort, type %1%, selected_idx %2%, selected_name %3%") % m_type % m_idx_selected % selected_name; + std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); + this->select_preset_by_name(selected_name, false); + unlock(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", after sort, type %1%, selected_idx %2%") % m_type % m_idx_selected; + + return; + } + + // BBS: validate_preset + bool PresetCollection::validate_preset(const std::string &preset_name, std::string &inherit_name) + { + std::deque::iterator it = this->find_preset_internal(preset_name); + bool found = (it != m_presets.end()) && (it->name == preset_name) && (it->is_system || it->is_default); + if (!found) + { + it = this->find_preset_renamed(preset_name); + found = it != m_presets.end() && (it->is_system || it->is_default); + } + if (!found) + { + if (!inherit_name.empty()) + { + it = this->find_preset_internal(inherit_name); + found = it != m_presets.end() && it->name == inherit_name && (it->is_system || it->is_default); + if (found) + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, found inherit in list") % preset_name % inherit_name; + else + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, can not found preset and inherit in list") % preset_name % inherit_name; + } else - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, can not found preset and inherit in list")%preset_name %inherit_name; + { + // inherit is null , should not happen , just consider it as valid + found = false; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": preset_name %1%, no inherit, set to not found") % preset_name; + } } - else { - //inherit is null , should not happen , just consider it as valid - found = false; - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": preset_name %1%, no inherit, set to not found")%preset_name; + else + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, found in list") % preset_name; } + + return found; } - else { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, found in list")%preset_name; + + // Load a preset from an already parsed config file, insert it into the sorted sequence of presets + // and select it, losing previous modifications. + Preset &PresetCollection::load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select, Semver file_version) + { + DynamicPrintConfig cfg(this->default_preset().config); + cfg.apply_only(config, cfg.keys(), true); + return this->load_preset(path, name, std::move(cfg), select, file_version); } - return found; -} + static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const DynamicPrintConfig &cfg_new) + { + t_config_option_keys diff = cfg_old.diff(cfg_new); + // Following keys are used by the UI, not by the slicing core, therefore they are not important + // when comparing profiles for equality. Ignore them. + for (const char *key : {"compatible_prints", "compatible_prints_condition", + "compatible_printers", "compatible_printers_condition", "inherits", + "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", + "printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile"}) + diff.erase(std::remove(diff.begin(), diff.end(), key), diff.end()); + // Preset with the same name as stored inside the config exists. + return diff.empty(); + } + + // Load a preset from an already parsed config file, insert it into the sorted sequence of presets + // and select it, losing previous modifications. + // Only a single profile could be edited at at the same time, which introduces complexity when loading + // filament profiles for multi-extruder printers. + std::pair PresetCollection::load_external_preset( + // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) + const std::string &path, + // Name of the profile, derived from the source file name. + const std::string &name, + // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. + const std::string &original_name, + // Config to initialize the preset from. It may contain configs of all presets merged in a single dictionary! + const DynamicPrintConfig &combined_config, + // different settings list + const std::set &different_settings_list, + // Select the preset after loading? + LoadAndSelect select, + const Semver file_version, + const std::string filament_id) + { + // Load the preset over a default preset, so that the missing fields are filled in from the default preset. + DynamicPrintConfig cfg(this->default_preset_for(combined_config).config); + // OrcaSlicer: ignore print connection info from project + cfg.erase("print_host"); + cfg.erase("print_host_webui"); + cfg.erase("printhost_apikey"); + cfg.erase("printhost_cafile"); + cfg.erase("printhost_user"); + cfg.erase("printhost_password"); + cfg.erase("printhost_port"); + + const auto &keys = cfg.keys(); + cfg.apply_only(combined_config, keys, true); + std::string &inherits = Preset::inherits(cfg); + + // BBS: add different settings check logic, replace the old system preset's default value with new system preset's default values + std::deque::iterator it = this->find_preset_internal(original_name); + bool found = it != m_presets.end() && it->name == original_name; + if (!found) + { + // Try to match the original_name against the "renamed_from" profile names of loaded system profiles. + it = this->find_preset_renamed(original_name); + found = it != m_presets.end(); + } + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); -// Load a preset from an already parsed config file, insert it into the sorted sequence of presets -// and select it, losing previous modifications. -Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select, Semver file_version) -{ - DynamicPrintConfig cfg(this->default_preset().config); - cfg.apply_only(config, cfg.keys(), true); - return this->load_preset(path, name, std::move(cfg), select, file_version); -} + if (!inherits.empty() && (different_settings_list.size() > 0)) + { + auto iter = this->find_preset_internal(inherits); + if (iter != m_presets.end() && iter->name == inherits) + { + // std::vector dirty_options = cfg.diff(iter->config); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values") % original_name % inherits % path; + cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); + } + } + else if (found && it->is_system && (different_settings_list.size() > 0)) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% 's value to %2% 's values") % original_name % path; + cfg.update_non_diff_values_to_base_config(it->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); + } -static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const DynamicPrintConfig &cfg_new) -{ - t_config_option_keys diff = cfg_old.diff(cfg_new); - // Following keys are used by the UI, not by the slicing core, therefore they are not important - // when comparing profiles for equality. Ignore them. - for (const char *key : { "compatible_prints", "compatible_prints_condition", - "compatible_printers", "compatible_printers_condition", "inherits", - "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", - "printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile" - }) - diff.erase(std::remove(diff.begin(), diff.end(), key), diff.end()); - // Preset with the same name as stored inside the config exists. - return diff.empty(); -} - -// Load a preset from an already parsed config file, insert it into the sorted sequence of presets -// and select it, losing previous modifications. -// Only a single profile could be edited at at the same time, which introduces complexity when loading -// filament profiles for multi-extruder printers. -std::pair PresetCollection::load_external_preset( - // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) - const std::string &path, - // Name of the profile, derived from the source file name. - const std::string &name, - // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. - const std::string &original_name, - // Config to initialize the preset from. It may contain configs of all presets merged in a single dictionary! - const DynamicPrintConfig &combined_config, - //different settings list - const std::set &different_settings_list, - // Select the preset after loading? - LoadAndSelect select, - const Semver file_version, - const std::string filament_id) -{ - // Load the preset over a default preset, so that the missing fields are filled in from the default preset. - DynamicPrintConfig cfg(this->default_preset_for(combined_config).config); - // OrcaSlicer: ignore print connection info from project - cfg.erase("print_host"); - cfg.erase("print_host_webui"); - cfg.erase("printhost_apikey"); - cfg.erase("printhost_cafile"); - cfg.erase("printhost_user"); - cfg.erase("printhost_password"); - cfg.erase("printhost_port"); - - const auto &keys = cfg.keys(); - cfg.apply_only(combined_config, keys, true); - std::string &inherits = Preset::inherits(cfg); - - //BBS: add different settings check logic, replace the old system preset's default value with new system preset's default values - std::deque::iterator it = this->find_preset_internal(original_name); - bool found = it != m_presets.end() && it->name == original_name; - if (! found) { - // Try to match the original_name against the "renamed_from" profile names of loaded system profiles. - it = this->find_preset_renamed(original_name); - found = it != m_presets.end(); - } - - std::string extruder_id_name, extruder_variant_name; - std::set *key_set1 = nullptr, *key_set2 = nullptr; - Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); - - if (!inherits.empty() && (different_settings_list.size() > 0)) { - auto iter = this->find_preset_internal(inherits); - if (iter != m_presets.end() && iter->name == inherits) { - //std::vector dirty_options = cfg.diff(iter->config); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path; - cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); - } - } - else if (found && it->is_system && (different_settings_list.size() > 0)) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path; - cfg.update_non_diff_values_to_base_config(it->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); - } - - //BBS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , path %2%, name %3%, original_name %4%, inherits %5%")%Preset::get_type_string(m_type) %path %name %original_name %inherits; - if (select == LoadAndSelect::Never) { - // Some filament profile has been selected and modified already. - // Check whether this profile is equal to the modified edited profile. - const Preset &edited = this->get_edited_preset(); - if ((edited.name == original_name || edited.name == inherits) && profile_print_params_same(edited.config, cfg)) { - // Just point to that already selected and edited profile. - //BBS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" Just point to that already selected and edited profile %1%")%edited.name; - return std::make_pair(&(*this->find_preset_internal(edited.name)), false); - } - } - // Is there a preset already loaded with the name stored inside the config? - /*std::deque::iterator it = this->find_preset_internal(original_name); - bool found = it != m_presets.end() && it->name == original_name; - if (! found) { - // Try to match the original_name against the "renamed_from" profile names of loaded system profiles. - it = this->find_preset_renamed(original_name); - found = it != m_presets.end(); - }*/ - if (found && profile_print_params_same(it->config, cfg)) { - // The preset exists and it matches the values stored inside config. - if (select == LoadAndSelect::Always) - this->select_preset(it - m_presets.begin()); - //BBS: set the preset to visible - if ( !it->is_visible ) { - it->is_visible = true; - //AppConfig* app_config = get_app_config(); - //if (app_config) - // app_config->set(AppConfig::SECTION_FILAMENTS, it->name, "1"); - } - //BBS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" The preset exists and it matches the values stored inside config. using original_name %1%")%original_name; - return std::make_pair(&(*it), false); - } - if (! found && select != LoadAndSelect::Never && ! inherits.empty()) { - // Try to use a system profile as a base to select the system profile - // and override its settings with the loaded ones. - assert(it == m_presets.end()); - it = this->find_preset_internal(inherits); - found = it != m_presets.end() && it->name == inherits; - if (found && profile_print_params_same(it->config, cfg)) { - // The system preset exists and it matches the values stored inside config. + // BBS: add config related logs + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , path %2%, name %3%, original_name %4%, inherits %5%") % Preset::get_type_string(m_type) % path % name % original_name % inherits; + if (select == LoadAndSelect::Never) + { + // Some filament profile has been selected and modified already. + // Check whether this profile is equal to the modified edited profile. + const Preset &edited = this->get_edited_preset(); + if ((edited.name == original_name || edited.name == inherits) && profile_print_params_same(edited.config, cfg)) + { + // Just point to that already selected and edited profile. + // BBS: add config related logs + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" Just point to that already selected and edited profile %1%") % edited.name; + return std::make_pair(&(*this->find_preset_internal(edited.name)), false); + } + } + // Is there a preset already loaded with the name stored inside the config? + /*std::deque::iterator it = this->find_preset_internal(original_name); + bool found = it != m_presets.end() && it->name == original_name; + if (! found) { + // Try to match the original_name against the "renamed_from" profile names of loaded system profiles. + it = this->find_preset_renamed(original_name); + found = it != m_presets.end(); + }*/ + if (found && profile_print_params_same(it->config, cfg)) + { + // The preset exists and it matches the values stored inside config. if (select == LoadAndSelect::Always) this->select_preset(it - m_presets.begin()); - //BBS: set the preset to visible - if ( !it->is_visible ) { + // BBS: set the preset to visible + if (!it->is_visible) + { it->is_visible = true; - //AppConfig* app_config = get_app_config(); - //if (app_config) - // app_config->set(AppConfig::SECTION_FILAMENTS, it->name, "1"); + // AppConfig* app_config = get_app_config(); + // if (app_config) + // app_config->set(AppConfig::SECTION_FILAMENTS, it->name, "1"); } - //BBS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" The preset exists and it matches the values stored inside config. using inherits %1%")%inherits; + // BBS: add config related logs + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" The preset exists and it matches the values stored inside config. using original_name %1%") % original_name; return std::make_pair(&(*it), false); } - } - if (found) { - //BBS: only select preset for always - //if (select != LoadAndSelect::Never) { - if (select == LoadAndSelect::Always) { - // Select the existing preset and override it with new values, so that - // the differences will be shown in the preset editor against the referenced profile. - this->select_preset(it - m_presets.begin()); - // The source config may contain keys from many possible preset types. Just copy those that relate to this preset. - //this->get_edited_preset().config.apply_only(combined_config, keys, true); - this->get_edited_preset().config.apply_only(cfg, keys, true); - this->update_dirty(); - update_saved_preset_from_current_preset(); - assert(this->get_edited_preset().is_dirty); - //BBS: set the preset to visible - if ( !it->is_visible ) { - it->is_visible = true; - //AppConfig* app_config = get_app_config(); - //if (app_config) - // app_config->set(AppConfig::SECTION_FILAMENTS, it->name, "1"); + if (!found && select != LoadAndSelect::Never && !inherits.empty()) + { + // Try to use a system profile as a base to select the system profile + // and override its settings with the loaded ones. + assert(it == m_presets.end()); + it = this->find_preset_internal(inherits); + found = it != m_presets.end() && it->name == inherits; + if (found && profile_print_params_same(it->config, cfg)) + { + // The system preset exists and it matches the values stored inside config. + if (select == LoadAndSelect::Always) + this->select_preset(it - m_presets.begin()); + // BBS: set the preset to visible + if (!it->is_visible) + { + it->is_visible = true; + // AppConfig* app_config = get_app_config(); + // if (app_config) + // app_config->set(AppConfig::SECTION_FILAMENTS, it->name, "1"); + } + // BBS: add config related logs + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" The preset exists and it matches the values stored inside config. using inherits %1%") % inherits; + return std::make_pair(&(*it), false); } - //BBS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" Select the existing preset %1% and override it with new values")%it->name; - return std::make_pair(&(*it), this->get_edited_preset().is_dirty); } + if (found) + { + // BBS: only select preset for always + // if (select != LoadAndSelect::Never) { + if (select == LoadAndSelect::Always) + { + // Select the existing preset and override it with new values, so that + // the differences will be shown in the preset editor against the referenced profile. + this->select_preset(it - m_presets.begin()); + // The source config may contain keys from many possible preset types. Just copy those that relate to this preset. + // this->get_edited_preset().config.apply_only(combined_config, keys, true); + this->get_edited_preset().config.apply_only(cfg, keys, true); + this->update_dirty(); + update_saved_preset_from_current_preset(); + assert(this->get_edited_preset().is_dirty); + // BBS: set the preset to visible + if (!it->is_visible) + { + it->is_visible = true; + // AppConfig* app_config = get_app_config(); + // if (app_config) + // app_config->set(AppConfig::SECTION_FILAMENTS, it->name, "1"); + } + // BBS: add config related logs + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" Select the existing preset %1% and override it with new values") % it->name; + return std::make_pair(&(*it), this->get_edited_preset().is_dirty); + } - //BBS: for other filaments under AMS - if (it->is_project_embedded) { - //update the properties back to the preset - it->config.apply_only(cfg, keys, true); - it->is_dirty = false; + // BBS: for other filaments under AMS + if (it->is_project_embedded) + { + // update the properties back to the preset + it->config.apply_only(cfg, keys, true); + it->is_dirty = false; - return std::make_pair(&(*it), false); - } - if (inherits.empty()) { - // Update the "inherits" field. - // There is a profile with the same name already loaded. Should we update the "inherits" field? - inherits = it->vendor ? it->name : it->inherits(); + return std::make_pair(&(*it), false); + } + if (inherits.empty()) + { + // Update the "inherits" field. + // There is a profile with the same name already loaded. Should we update the "inherits" field? + inherits = it->vendor ? it->name : it->inherits(); + } } - } - // The external preset does not match an internal preset, load the external preset. - std::string new_name; - //BBS: add project embedded preset logic - //BBS: refine the name logic - for (size_t idx = 0;; ++ idx) { - std::string prefix; - if (original_name.empty()) { - if (!inherits.empty()) { - if (idx == 0) - prefix = inherits; + // The external preset does not match an internal preset, load the external preset. + std::string new_name; + // BBS: add project embedded preset logic + // BBS: refine the name logic + for (size_t idx = 0;; ++idx) + { + std::string prefix; + if (original_name.empty()) + { + if (!inherits.empty()) + { + if (idx == 0) + prefix = inherits; + else + prefix = inherits + "-" + std::to_string(idx); + } else - prefix = inherits + "-" + std::to_string(idx); - } - else { - if (idx > 0) - prefix = std::to_string(idx); - } - } else { - std::string reduced_name = original_name; - //TODO - //boost::regex rx("3mf\(*\)"); - //boost::iterator_range result = boost::algorithm::find_regex(reduced_name, rx); - //if (!result.empty()) { - // reduced_name = std::string(result.begin(), result.end()); - //} - - if (idx == 0) - prefix = reduced_name; + { + if (idx > 0) + prefix = std::to_string(idx); + } + } else - prefix = reduced_name + "-" + std::to_string(idx) ; - } - //new_name = name + suffix; - new_name = prefix + "(" + name + ")"; - it = this->find_preset_internal(new_name); - if (it == m_presets.end() || it->name != new_name) - // Unique profile name. Insert a new profile. - break; - if (profile_print_params_same(it->config, cfg)) { - // The preset exists and it matches the values stored inside config. - if (select == LoadAndSelect::Always) - this->select_preset(it - m_presets.begin()); - //BBS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" The preset %1% exists and it matches the values stored inside config.")%new_name; - return std::make_pair(&(*it), false); + { + std::string reduced_name = original_name; + // TODO + // boost::regex rx("3mf\(*\)"); + // boost::iterator_range result = boost::algorithm::find_regex(reduced_name, rx); + // if (!result.empty()) { + // reduced_name = std::string(result.begin(), result.end()); + // } + + if (idx == 0) + prefix = reduced_name; + else + prefix = reduced_name + "-" + std::to_string(idx); + } + // new_name = name + suffix; + new_name = prefix + "(" + name + ")"; + it = this->find_preset_internal(new_name); + if (it == m_presets.end() || it->name != new_name) + // Unique profile name. Insert a new profile. + break; + if (profile_print_params_same(it->config, cfg)) + { + // The preset exists and it matches the values stored inside config. + if (select == LoadAndSelect::Always) + this->select_preset(it - m_presets.begin()); + // BBS: add config related logs + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" The preset %1% exists and it matches the values stored inside config.") % new_name; + return std::make_pair(&(*it), false); + } + // Form another profile name. } - // Form another profile name. - } - // Insert a new profile. - //BBS: add project embedded preset logic - bool from_project = boost::algorithm::iends_with(name, ".3mf"); - if (m_type == Preset::TYPE_PRINT) - cfg.option("print_settings_id", true)->value = new_name; - else if (m_type == Preset::TYPE_FILAMENT) - cfg.option("filament_settings_id", true)->values[0] = new_name; - else if (m_type == Preset::TYPE_PRINTER) - cfg.option("printer_settings_id", true)->value = new_name; - Preset &preset = this->load_preset(path, new_name, std::move(cfg), select == LoadAndSelect::Always); - preset.is_external = true; - preset.version = file_version; - if (!filament_id.empty()) - preset.filament_id = filament_id; - else { - if (!inherits.empty()) { - Preset *parent = this->find_preset(inherits, false, true); - if (parent) - preset.filament_id = parent->filament_id; - } - } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << preset.name << " filament_id: " << preset.filament_id << " base_id: " << preset.base_id; - if (from_project) { - preset.is_project_embedded = true; - } - else { - //external config - preset.file = path_for_preset(preset); - //BBS: save full config here for external - //we can not reach here - preset.save(nullptr); - } - if (&this->get_selected_preset() == &preset) { - this->get_edited_preset().is_external = true; - this->get_edited_preset().is_project_embedded = preset.is_project_embedded; - } - - //BBS: add config related logs - BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", type %1% added a preset, name %2%, is_system %3%, is_default %4%, is_external %5%")%Preset::get_type_string(m_type) %preset.name %preset.is_system %preset.is_default %preset.is_external; - return std::make_pair(&preset, false); -} - -Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, Semver file_version) -{ - lock(); - auto it = this->find_preset_internal(name); - if (it == m_presets.end() || it->name != name) { - // The preset was not found. Create a new preset. - if (m_presets.begin() + m_idx_selected >= it) - ++m_idx_selected; - it = m_presets.emplace(it, Preset(m_type, name, false)); - } - Preset &preset = *it; - preset.file = path; - preset.config = std::move(config); - preset.loaded = true; - preset.is_dirty = false; - - //BBS - if (file_version.valid()) - preset.version = file_version; - if (select) - this->select_preset_by_name(name, true); - unlock(); - //BBS: add config related logs - BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, is_system %3%, is_default %4%, is_visible %5%")%Preset::get_type_string(m_type) %preset.name %preset.is_system %preset.is_default %preset.is_visible; - return preset; -} - -bool PresetCollection::clone_presets(std::vector const &presets, std::vector &failures, std::function modifier, bool force_rewritten) -{ - std::vector new_presets; - for (auto curr_preset : presets) { - new_presets.push_back(*curr_preset); - auto &preset = new_presets.back(); - preset.vendor = nullptr; - preset.renamed_from.clear(); - preset.setting_id.clear(); - preset.inherits().clear(); - preset.is_default = false; - preset.is_system = false; - preset.is_external = false; - preset.is_visible = true; - preset.is_project_embedded = false; - modifier(preset, m_type); - if (find_preset(preset.name) && !force_rewritten) { - failures.push_back(preset.name); - } - preset.file = this->path_for_preset(preset); + // Insert a new profile. + // BBS: add project embedded preset logic + bool from_project = boost::algorithm::iends_with(name, ".3mf"); if (m_type == Preset::TYPE_PRINT) - preset.config.option("print_settings_id", true)->value = preset.name; + cfg.option("print_settings_id", true)->value = new_name; else if (m_type == Preset::TYPE_FILAMENT) - preset.config.option("filament_settings_id", true)->values[0] = preset.name; + cfg.option("filament_settings_id", true)->values[0] = new_name; else if (m_type == Preset::TYPE_PRINTER) - preset.config.option("printer_settings_id", true)->value = preset.name; + cfg.option("printer_settings_id", true)->value = new_name; + Preset &preset = this->load_preset(path, new_name, std::move(cfg), select == LoadAndSelect::Always); + preset.is_external = true; + preset.version = file_version; + if (!filament_id.empty()) + preset.filament_id = filament_id; + else + { + if (!inherits.empty()) + { + Preset *parent = this->find_preset(inherits, false, true); + if (parent) + preset.filament_id = parent->filament_id; + } + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << preset.name << " filament_id: " << preset.filament_id << " base_id: " << preset.base_id; + if (from_project) + { + preset.is_project_embedded = true; + } + else + { + // external config + preset.file = path_for_preset(preset); + // BBS: save full config here for external + // we can not reach here + preset.save(nullptr); + } + if (&this->get_selected_preset() == &preset) + { + this->get_edited_preset().is_external = true; + this->get_edited_preset().is_project_embedded = preset.is_project_embedded; + } + + // BBS: add config related logs + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", type %1% added a preset, name %2%, is_system %3%, is_default %4%, is_external %5%") % Preset::get_type_string(m_type) % preset.name % preset.is_system % preset.is_default % preset.is_external; + return std::make_pair(&preset, false); } - if (!failures.empty() && !force_rewritten) - return false; - lock(); - auto old_name = this->get_edited_preset().name; - for (auto preset : new_presets) { - preset.alias.clear(); - set_custom_preset_alias(preset); - preset.base_id.clear(); - auto it = this->find_preset_internal(preset.name); - assert((it == m_presets.end() || it->name != preset.name) || force_rewritten); - if (it == m_presets.end() || it->name != preset.name) { - Preset &new_preset = *m_presets.insert(it, preset); - new_preset.save(nullptr); - } else if (force_rewritten) { - *it = preset; - (*it).save(nullptr); - } - } - this->select_preset_by_name(old_name, true); - unlock(); - return true; -} - -bool PresetCollection::clone_presets_for_printer(std::vector const & templates, - std::vector & failures, - std::string const & printer, - std::function create_filament_id, - bool force_rewritten) -{ - return clone_presets(templates, failures, [printer, create_filament_id](Preset &preset, Preset::Type &type) { + + Preset &PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, Semver file_version) + { + lock(); + auto it = this->find_preset_internal(name); + if (it == m_presets.end() || it->name != name) + { + // The preset was not found. Create a new preset. + if (m_presets.begin() + m_idx_selected >= it) + ++m_idx_selected; + it = m_presets.emplace(it, Preset(m_type, name, false)); + } + Preset &preset = *it; + preset.file = path; + preset.config = std::move(config); + preset.loaded = true; + preset.is_dirty = false; + + // BBS + if (file_version.valid()) + preset.version = file_version; + if (select) + this->select_preset_by_name(name, true); + unlock(); + // BBS: add config related logs + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, is_system %3%, is_default %4%, is_visible %5%") % Preset::get_type_string(m_type) % preset.name % preset.is_system % preset.is_default % preset.is_visible; + return preset; + } + + bool PresetCollection::clone_presets(std::vector const &presets, std::vector &failures, std::function modifier, bool force_rewritten) + { + std::vector new_presets; + for (auto curr_preset : presets) + { + new_presets.push_back(*curr_preset); + auto &preset = new_presets.back(); + preset.vendor = nullptr; + preset.renamed_from.clear(); + preset.setting_id.clear(); + preset.inherits().clear(); + preset.is_default = false; + preset.is_system = false; + preset.is_external = false; + preset.is_visible = true; + preset.is_project_embedded = false; + modifier(preset, m_type); + if (find_preset(preset.name) && !force_rewritten) + { + failures.push_back(preset.name); + } + preset.file = this->path_for_preset(preset); + if (m_type == Preset::TYPE_PRINT) + preset.config.option("print_settings_id", true)->value = preset.name; + else if (m_type == Preset::TYPE_FILAMENT) + preset.config.option("filament_settings_id", true)->values[0] = preset.name; + else if (m_type == Preset::TYPE_PRINTER) + preset.config.option("printer_settings_id", true)->value = preset.name; + } + if (!failures.empty() && !force_rewritten) + return false; + lock(); + auto old_name = this->get_edited_preset().name; + for (auto preset : new_presets) + { + preset.alias.clear(); + set_custom_preset_alias(preset); + preset.base_id.clear(); + auto it = this->find_preset_internal(preset.name); + assert((it == m_presets.end() || it->name != preset.name) || force_rewritten); + if (it == m_presets.end() || it->name != preset.name) + { + Preset &new_preset = *m_presets.insert(it, preset); + new_preset.save(nullptr); + } + else if (force_rewritten) + { + *it = preset; + (*it).save(nullptr); + } + } + this->select_preset_by_name(old_name, true); + unlock(); + return true; + } + + bool PresetCollection::clone_presets_for_printer(std::vector const &templates, + std::vector &failures, + std::string const &printer, + std::function create_filament_id, + bool force_rewritten) + { + return clone_presets(templates, failures, [printer, create_filament_id](Preset &preset, Preset::Type &type) + { std::string prefix = preset.name.substr(0, preset.name.find(" @")); std::replace(prefix.begin(), prefix.end(), '/', '-'); preset.name = prefix + " @" + printer; @@ -2409,20 +2686,20 @@ bool PresetCollection::clone_presets_for_printer(std::vector con if (type == Preset::TYPE_FILAMENT) { preset.filament_id = create_filament_id(prefix); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << preset.name << " create filament_id: " << preset.filament_id; - } - }, force_rewritten); -} + } }, force_rewritten); + } -bool PresetCollection::clone_presets_for_filament(Preset const *const & preset, - std::vector &failures, - std::string const & filament_name, - std::string const & filament_id, - const DynamicConfig & dynamic_config, - const std::string & compatible_printers, - bool force_rewritten) -{ - std::vector const presets = {preset}; - return clone_presets(presets, failures, [&filament_name, &filament_id, &dynamic_config, &compatible_printers](Preset &preset, Preset::Type &type) { + bool PresetCollection::clone_presets_for_filament(Preset const *const &preset, + std::vector &failures, + std::string const &filament_name, + std::string const &filament_id, + const DynamicConfig &dynamic_config, + const std::string &compatible_printers, + bool force_rewritten) + { + std::vector const presets = {preset}; + return clone_presets(presets, failures, [&filament_name, &filament_id, &dynamic_config, &compatible_printers](Preset &preset, Preset::Type &type) + { preset.name = filament_name + " @" + compatible_printers; if (type == Preset::TYPE_FILAMENT) { preset.config.apply_only(dynamic_config, {"filament_vendor", "compatible_printers", "filament_type"},true); @@ -2434,1205 +2711,1358 @@ bool PresetCollection::clone_presets_for_filament(Preset const *const & pres compatible->values.push_back(compatible_printers); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << preset.name << " is cloned and filament_id: " << filament_id; - } - }, - force_rewritten); -} + } }, force_rewritten); + } -std::map> PresetCollection::get_filament_presets() const -{ - std::map> filament_presets; - for (auto &preset : m_presets) { - if (preset.is_user()) { - if (preset.inherits() == "") { filament_presets[preset.filament_id].push_back(&preset); } - continue; + std::map> PresetCollection::get_filament_presets() const + { + std::map> filament_presets; + for (auto &preset : m_presets) + { + if (preset.is_user()) + { + if (preset.inherits() == "") + { + filament_presets[preset.filament_id].push_back(&preset); + } + continue; + } + if (get_preset_base(preset) == &preset) + { + filament_presets[preset.filament_id].push_back(&preset); + } } - if (get_preset_base(preset) == &preset) { filament_presets[preset.filament_id].push_back(&preset); } + return filament_presets; } - return filament_presets; -} -//BBS: add project embedded preset logic -void PresetCollection::save_current_preset(const std::string &new_name, bool detach, bool save_to_project, Preset *_curr_preset, std::map *extra_map) -{ - Preset curr_preset = _curr_preset ? *_curr_preset : m_edited_preset; - //BBS: add lock logic for sync preset in background - std::string final_inherits; - lock(); - // 1) Find the preset with a new_name or create a new one, - // initialize it with the edited config. - auto it = this->find_preset_internal(new_name); - if (it != m_presets.end() && it->name == new_name) { - // Preset with the same name found. - Preset &preset = *it; - //BBS: add project embedded preset logic - if (preset.is_default || preset.is_system) { - //if (preset.is_default || preset.is_external || preset.is_system) - // Cannot overwrite the default preset. - //BBS: add lock logic for sync preset in background + // BBS: add project embedded preset logic + void PresetCollection::save_current_preset(const std::string &new_name, bool detach, bool save_to_project, Preset *_curr_preset, std::map *extra_map) + { + Preset curr_preset = _curr_preset ? *_curr_preset : m_edited_preset; + // BBS: add lock logic for sync preset in background + std::string final_inherits; + lock(); + // 1) Find the preset with a new_name or create a new one, + // initialize it with the edited config. + auto it = this->find_preset_internal(new_name); + if (it != m_presets.end() && it->name == new_name) + { + // Preset with the same name found. + Preset &preset = *it; + // BBS: add project embedded preset logic + if (preset.is_default || preset.is_system) + { + // if (preset.is_default || preset.is_external || preset.is_system) + // Cannot overwrite the default preset. + // BBS: add lock logic for sync preset in background + unlock(); + return; + } + // Overwriting an existing preset. + preset.config = std::move(curr_preset.config); + // The newly saved preset will be activated -> make it visible. + preset.is_visible = true; + // TODO: remove the detach logic + if (detach) + { + // Clear the link to the parent profile. + preset.vendor = nullptr; + preset.inherits().clear(); + preset.alias.clear(); + preset.renamed_from.clear(); + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": save preset %1% , with detach") % new_name; + } + // BBS: add lock logic for sync preset in background + + if (m_type == Preset::TYPE_PRINT) + preset.config.option("print_settings_id", true)->value = new_name; + else if (m_type == Preset::TYPE_FILAMENT) + preset.config.option("filament_settings_id", true)->values[0] = new_name; + else if (m_type == Preset::TYPE_PRINTER) + { + preset.config.option("printer_settings_id", true)->value = new_name; + if (extra_map) + { + for (auto iter : *extra_map) + { + preset.config.option(iter.first, true)->value = iter.second; + } + } + } + final_inherits = preset.inherits(); unlock(); - return; + // TODO: apply change from custom root to devided presets. + if (preset.inherits().empty()) + { + for (auto &preset2 : m_presets) + if (preset2.inherits() == preset.name) + preset2.reload(preset); + } } - // Overwriting an existing preset. - preset.config = std::move(curr_preset.config); - // The newly saved preset will be activated -> make it visible. - preset.is_visible = true; - //TODO: remove the detach logic - if (detach) { - // Clear the link to the parent profile. + else + { + // Creating a new preset. + Preset &preset = *m_presets.insert(it, curr_preset); + std::string &inherits = preset.inherits(); + std::string old_name = preset.name; + preset.name = new_name; preset.vendor = nullptr; - preset.inherits().clear(); - preset.alias.clear(); - preset.renamed_from.clear(); - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": save preset %1% , with detach")%new_name; - } - //BBS: add lock logic for sync preset in background - - if (m_type == Preset::TYPE_PRINT) - preset.config.option("print_settings_id", true)->value = new_name; - else if (m_type == Preset::TYPE_FILAMENT) - preset.config.option("filament_settings_id", true)->values[0] = new_name; - else if (m_type == Preset::TYPE_PRINTER) { - preset.config.option("printer_settings_id", true)->value = new_name; - if (extra_map) { - for (auto iter : *extra_map) { - preset.config.option(iter.first, true)->value = iter.second; + preset.alias.clear(); + preset.renamed_from.clear(); + preset.setting_id.clear(); + if (detach) + { + // Clear the link to the parent profile. + inherits.clear(); + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": save preset %1% , with detach") % new_name; + } + else if (is_base_preset(preset)) + { + inherits = old_name; + } + preset.is_default = false; + preset.is_system = false; + preset.is_external = false; + preset.file = this->path_for_preset(preset); + // The newly saved preset will be activated -> make it visible. + preset.is_visible = true; + // Just system presets have aliases + preset.alias.clear(); + // BBS: add project embedded preset logic + if (save_to_project) + { + preset.is_project_embedded = true; + } + else + preset.is_project_embedded = false; + if (m_type == Preset::TYPE_PRINT) + preset.config.option("print_settings_id", true)->value = new_name; + else if (m_type == Preset::TYPE_FILAMENT) + preset.config.option("filament_settings_id", true)->values[0] = new_name; + else if (m_type == Preset::TYPE_PRINTER) + { + preset.config.option("printer_settings_id", true)->value = new_name; + if (extra_map) + { + for (auto iter : *extra_map) + { + preset.config.option(iter.first, true)->value = iter.second; + } } } + // BBS: add lock logic for sync preset in background + final_inherits = inherits; + unlock(); } - final_inherits = preset.inherits(); - unlock(); - // TODO: apply change from custom root to devided presets. - if (preset.inherits().empty()) { - for (auto &preset2 : m_presets) - if (preset2.inherits() == preset.name) - preset2.reload(preset); - } - } else { - // Creating a new preset. - Preset &preset = *m_presets.insert(it, curr_preset); - std::string &inherits = preset.inherits(); - std::string old_name = preset.name; - preset.name = new_name; - preset.vendor = nullptr; - preset.alias.clear(); - preset.renamed_from.clear(); - preset.setting_id.clear(); - if (detach) { - // Clear the link to the parent profile. - inherits.clear(); - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": save preset %1% , with detach")%new_name; - } else if (is_base_preset(preset)) { - inherits = old_name; - } - preset.is_default = false; - preset.is_system = false; - preset.is_external = false; - preset.file = this->path_for_preset(preset); - // The newly saved preset will be activated -> make it visible. - preset.is_visible = true; - // Just system presets have aliases - preset.alias.clear(); - //BBS: add project embedded preset logic - if (save_to_project) { - preset.is_project_embedded = true; + // 2) Activate the saved preset. + this->select_preset_by_name(new_name, true); + // 2) Store the active preset to disk. + // BBS: only save difference for user preset + Preset *parent_preset = nullptr; + if (!final_inherits.empty()) + { + parent_preset = this->find_preset(final_inherits, false, true); + if (parent_preset && this->get_selected_preset().base_id.empty()) + { + this->get_selected_preset().base_id = parent_preset->setting_id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " base_id: " << parent_preset->setting_id; + } } + if (parent_preset) + this->get_selected_preset().save(&(parent_preset->config)); else - preset.is_project_embedded = false; - if (m_type == Preset::TYPE_PRINT) - preset.config.option("print_settings_id", true)->value = new_name; - else if (m_type == Preset::TYPE_FILAMENT) - preset.config.option("filament_settings_id", true)->values[0] = new_name; - else if (m_type == Preset::TYPE_PRINTER) { - preset.config.option("printer_settings_id", true)->value = new_name; - if (extra_map) { - for (auto iter : *extra_map) { - preset.config.option(iter.first, true)->value = iter.second; - } - } + this->get_selected_preset().save(nullptr); + } + + bool PresetCollection::delete_current_preset() + { + Preset &selected = this->get_selected_preset(); + if (selected.is_default) + return false; + + if (get_preset_base(selected) == &selected) + { + for (auto &preset2 : m_presets) + if (preset2.inherits() == selected.name) + return false; + } + + // BBS: add project embedded preset logic and refine is_external + // if (! selected.is_external && ! selected.is_system) { + if (!selected.is_system) + { + // BBS Erase the preset file. + selected.remove_files(); } - //BBS: add lock logic for sync preset in background - final_inherits = inherits; + // BBS: add lock logic for sync preset in background + lock(); + // Remove the preset from the list. + m_presets.erase(m_presets.begin() + m_idx_selected); unlock(); + + // Find the next visible preset. + size_t new_selected_idx = m_idx_selected; + if (new_selected_idx < m_presets.size()) + for (; new_selected_idx < m_presets.size() && !m_presets[new_selected_idx].is_visible; ++new_selected_idx) + ; + if (new_selected_idx == m_presets.size()) + for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx) + ; + this->select_preset(new_selected_idx); + return true; } - // 2) Activate the saved preset. - this->select_preset_by_name(new_name, true); - // 2) Store the active preset to disk. - //BBS: only save difference for user preset - Preset* parent_preset = nullptr; - if (!final_inherits.empty()) { - parent_preset = this->find_preset(final_inherits, false, true); - if (parent_preset && this->get_selected_preset().base_id.empty()) { - this->get_selected_preset().base_id = parent_preset->setting_id; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " base_id: " << parent_preset->setting_id; - } - } - if (parent_preset) - this->get_selected_preset().save(&(parent_preset->config)); - else - this->get_selected_preset().save(nullptr); -} - -bool PresetCollection::delete_current_preset() -{ - Preset &selected = this->get_selected_preset(); - if (selected.is_default) - return false; - if (get_preset_base(selected) == &selected) { - for (auto &preset2 : m_presets) - if (preset2.inherits() == selected.name) - return false; + bool PresetCollection::delete_preset(const std::string &name) + { + auto it = this->find_preset_internal(name); + + Preset &preset = *it; + if (preset.is_default) + return false; + // BBS: add project embedded preset logic and refine is_external + // if (!preset.is_external && !preset.is_system) { + if (!preset.is_system) + { + preset.remove_files(); + } + // BBS: add lock logic for sync preset in background + lock(); + set_printer_hold_alias(it->alias, *it, true); + it = m_presets.erase(it); + if (std::distance(m_presets.begin(), it) < m_idx_selected) + --m_idx_selected; + unlock(); + + return true; } - //BBS: add project embedded preset logic and refine is_external - //if (! selected.is_external && ! selected.is_system) { - if (! selected.is_system) { - //BBS Erase the preset file. - selected.remove_files(); - } - //BBS: add lock logic for sync preset in background - lock(); - // Remove the preset from the list. - m_presets.erase(m_presets.begin() + m_idx_selected); - unlock(); - - // Find the next visible preset. - size_t new_selected_idx = m_idx_selected; - if (new_selected_idx < m_presets.size()) - for (; new_selected_idx < m_presets.size() && ! m_presets[new_selected_idx].is_visible; ++ new_selected_idx) ; - if (new_selected_idx == m_presets.size()) - for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx); - this->select_preset(new_selected_idx); - return true; -} - -bool PresetCollection::delete_preset(const std::string& name) -{ - auto it = this->find_preset_internal(name); + const Preset *PresetCollection::get_selected_preset_parent() const + { + if (this->get_selected_idx() == size_t(-1)) + // This preset collection has no preset activated yet. Only the get_edited_preset() is valid. + return nullptr; - Preset& preset = *it; - if (preset.is_default) - return false; - //BBS: add project embedded preset logic and refine is_external - //if (!preset.is_external && !preset.is_system) { - if (! preset.is_system) { - preset.remove_files(); - } - //BBS: add lock logic for sync preset in background - lock(); - set_printer_hold_alias(it->alias, *it, true); - it = m_presets.erase(it); - if (std::distance(m_presets.begin(), it) < m_idx_selected) - --m_idx_selected; - unlock(); - - return true; -} - -const Preset* PresetCollection::get_selected_preset_parent() const -{ - if (this->get_selected_idx() == size_t(-1)) - // This preset collection has no preset activated yet. Only the get_edited_preset() is valid. - return nullptr; + const Preset &selected_preset = this->get_selected_preset(); + if (get_preset_base(selected_preset) == &selected_preset) + return &selected_preset; - const Preset &selected_preset = this->get_selected_preset(); - if (get_preset_base(selected_preset) == &selected_preset) - return &selected_preset; + const Preset &edited_preset = this->get_edited_preset(); + const std::string &inherits = edited_preset.inherits(); + const Preset *preset = nullptr; + if (inherits.empty()) + { + if (selected_preset.is_external) + return nullptr; + preset = &this->default_preset(m_type == Preset::Type::TYPE_PRINTER && edited_preset.printer_technology() == ptSLA ? 1 : 0); + } + else + preset = this->find_preset(inherits, false); + if (preset == nullptr) + { + // Resolve the "renamed_from" field. + assert(!inherits.empty()); + auto it = this->find_preset_renamed(inherits); + if (it != m_presets.end()) + preset = &(*it); + } + // BBS: add project embedded preset logic and refine is_external + return (preset == nullptr /* || preset->is_default || preset->is_external*/) ? nullptr : preset; + // return (preset == nullptr/* || preset->is_default*/ || preset->is_external) ? nullptr : preset; + } - const Preset &edited_preset = this->get_edited_preset(); - const std::string &inherits = edited_preset.inherits(); - const Preset *preset = nullptr; - if (inherits.empty()) { - if (selected_preset.is_external) + const Preset *PresetCollection::get_preset_parent(const Preset &child) const + { + const std::string &inherits = child.inherits(); + if (inherits.empty()) + // return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; return nullptr; - preset = &this->default_preset(m_type == Preset::Type::TYPE_PRINTER && edited_preset.printer_technology() == ptSLA ? 1 : 0); - } else - preset = this->find_preset(inherits, false); - if (preset == nullptr) { - // Resolve the "renamed_from" field. - assert(! inherits.empty()); - auto it = this->find_preset_renamed(inherits); - if (it != m_presets.end()) - preset = &(*it); - } - //BBS: add project embedded preset logic and refine is_external - return (preset == nullptr/* || preset->is_default || preset->is_external*/) ? nullptr : preset; - //return (preset == nullptr/* || preset->is_default*/ || preset->is_external) ? nullptr : preset; -} - -const Preset* PresetCollection::get_preset_parent(const Preset& child) const -{ - const std::string &inherits = child.inherits(); - if (inherits.empty()) -// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + const Preset *preset = this->find_preset(inherits, false); + if (preset == nullptr) + { + auto it = this->find_preset_renamed(inherits); + if (it != m_presets.end()) + preset = &(*it); + } + return + // not found + (preset == nullptr /* || preset->is_default */ || + // this should not happen, user profile should not derive from an external profile + // BBS: add project embedded preset logic and refine is_external + /*preset->is_external ||*/ + // this should not happen, however people are creative, see GH #4996 + preset == &child) + ? nullptr + : preset; + } + + const Preset *PresetCollection::get_preset_base(const Preset &child) const + { + if (child.is_system || child.is_default) + return &child; + // Handle user preset + if (child.inherits().empty()) + return &child; // this is user root + auto inherits = find_preset(child.inherits()); + return inherits ? get_preset_base(*inherits) : nullptr; + } + + // Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist. + PresetWithVendorProfile PresetCollection::get_preset_with_vendor_profile(const Preset &preset) const + { + const Preset *p = &preset; + const VendorProfile *v = nullptr; + do + { + if (p->vendor != nullptr) + { + v = p->vendor; + break; + } + p = this->get_preset_parent(*p); + } while (p != nullptr); + return PresetWithVendorProfile(preset, v); + } + + const std::string &PresetCollection::get_preset_name_by_alias(const std::string &alias) const + { + for ( + // Find the 1st profile name with the alias. + auto it = Slic3r::lower_bound_by_predicate(m_map_alias_to_profile_name.begin(), m_map_alias_to_profile_name.end(), [&alias](auto &l) + { return l.first < alias; }); + // Continue over all profile names with the same alias. + it != m_map_alias_to_profile_name.end() && it->first == alias; ++it) + for (const std::string &preset_name : it->second) + { + if (auto it_preset = this->find_preset_internal(preset_name); + it_preset != m_presets.end() && it_preset->name == preset_name && + it_preset->is_visible && (it_preset->is_compatible || size_t(it_preset - m_presets.begin()) == m_idx_selected)) + return it_preset->name; + } + + return alias; + } + + const std::string *PresetCollection::get_preset_name_renamed(const std::string &old_name) const + { + auto it_renamed = m_map_system_profile_renamed.find(old_name); + if (it_renamed != m_map_system_profile_renamed.end()) + return &it_renamed->second; return nullptr; - const Preset* preset = this->find_preset(inherits, false); - if (preset == nullptr) { - auto it = this->find_preset_renamed(inherits); - if (it != m_presets.end()) - preset = &(*it); - } - return - // not found - (preset == nullptr/* || preset->is_default */|| - // this should not happen, user profile should not derive from an external profile - //BBS: add project embedded preset logic and refine is_external - /*preset->is_external ||*/ - // this should not happen, however people are creative, see GH #4996 - preset == &child) ? - nullptr : - preset; -} - -const Preset *PresetCollection::get_preset_base(const Preset &child) const -{ - if (child.is_system || child.is_default) - return &child; - // Handle user preset - if (child.inherits().empty()) - return &child; // this is user root - auto inherits = find_preset(child.inherits()); - return inherits ? get_preset_base(*inherits) : nullptr; -} - -// Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist. -PresetWithVendorProfile PresetCollection::get_preset_with_vendor_profile(const Preset &preset) const -{ - const Preset *p = &preset; - const VendorProfile *v = nullptr; - do { - if (p->vendor != nullptr) { - v = p->vendor; - break; - } - p = this->get_preset_parent(*p); - } while (p != nullptr); - return PresetWithVendorProfile(preset, v); -} - -const std::string& PresetCollection::get_preset_name_by_alias(const std::string& alias) const -{ - for ( - // Find the 1st profile name with the alias. - auto it = Slic3r::lower_bound_by_predicate(m_map_alias_to_profile_name.begin(), m_map_alias_to_profile_name.end(), [&alias](auto &l){ return l.first < alias; }); - // Continue over all profile names with the same alias. - it != m_map_alias_to_profile_name.end() && it->first == alias; ++ it) - for (const std::string &preset_name : it->second) { - if (auto it_preset = this->find_preset_internal(preset_name); - it_preset != m_presets.end() && it_preset->name == preset_name && - it_preset->is_visible && (it_preset->is_compatible || size_t(it_preset - m_presets.begin()) == m_idx_selected)) - return it_preset->name; - } - - return alias; -} - -const std::string* PresetCollection::get_preset_name_renamed(const std::string &old_name) const -{ - auto it_renamed = m_map_system_profile_renamed.find(old_name); - if (it_renamed != m_map_system_profile_renamed.end()) - return &it_renamed->second; - return nullptr; -} + } -bool PresetCollection::is_alias_exist(const std::string &alias, Preset* preset) -{ - auto it = m_map_alias_to_profile_name.find(alias); - if (m_map_alias_to_profile_name.end() == it) return false; - if (!preset) return true; - - auto compatible_printers = dynamic_cast(preset->config.option("compatible_printers")); - if (compatible_printers == nullptr) return true; - - for (const std::string &printer_name : compatible_printers->values) { - auto printer_iter = m_printer_hold_alias.find(printer_name); - if (m_printer_hold_alias.end() != printer_iter) { - auto alias_iter = m_printer_hold_alias[printer_name].find(alias); - if (m_printer_hold_alias[printer_name].end() != alias_iter) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << " The alias already exists: " << alias << " and the preset name: " << preset->name; - return true; + bool PresetCollection::is_alias_exist(const std::string &alias, Preset *preset) + { + auto it = m_map_alias_to_profile_name.find(alias); + if (m_map_alias_to_profile_name.end() == it) + return false; + if (!preset) + return true; + + auto compatible_printers = dynamic_cast(preset->config.option("compatible_printers")); + if (compatible_printers == nullptr) + return true; + + for (const std::string &printer_name : compatible_printers->values) + { + auto printer_iter = m_printer_hold_alias.find(printer_name); + if (m_printer_hold_alias.end() != printer_iter) + { + auto alias_iter = m_printer_hold_alias[printer_name].find(alias); + if (m_printer_hold_alias[printer_name].end() != alias_iter) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << " The alias already exists: " << alias << " and the preset name: " << preset->name; + return true; + } } } + return false; } - return false; -} -const std::string& PresetCollection::get_suffix_modified() { - return g_suffix_modified; -} + const std::string &PresetCollection::get_suffix_modified() + { + return g_suffix_modified; + } -// Return a preset by its name. If the preset is active, a temporary copy is returned. -// If a preset is not found by its name, null is returned. -Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found, bool real) -{ - Preset key(m_type, name, false); - auto it = this->find_preset_internal(name); - // Ensure that a temporary copy is returned if the preset found is currently selected. - return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin(), real) : - first_visible_if_not_found ? &this->first_visible() : nullptr; -} - -// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. -size_t PresetCollection::first_visible_idx() const -{ - //BBS: set first visible filament to fla - size_t first_visible = -1; - size_t idx = m_default_suppressed ? m_num_default_presets : 0; - for (; idx < m_presets.size(); ++ idx) - if (m_presets[idx].is_visible && m_presets[idx].get_printer_id() == "BBL") { - if (first_visible == -1) - first_visible = idx; - if (m_type != Preset::TYPE_FILAMENT) - break; - else { - if (m_presets[idx].name.find("PLA") != std::string::npos) { + // Return a preset by its name. If the preset is active, a temporary copy is returned. + // If a preset is not found by its name, null is returned. + Preset *PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found, bool real) + { + Preset key(m_type, name, false); + auto it = this->find_preset_internal(name); + // Ensure that a temporary copy is returned if the preset found is currently selected. + return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin(), real) : first_visible_if_not_found ? &this->first_visible() + : nullptr; + } + + // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. + size_t PresetCollection::first_visible_idx() const + { + // BBS: set first visible filament to fla + size_t first_visible = -1; + size_t idx = m_default_suppressed ? m_num_default_presets : 0; + for (; idx < m_presets.size(); ++idx) + if (m_presets[idx].is_visible && m_presets[idx].get_printer_id() == "BBL") + { + if (first_visible == -1) first_visible = idx; + if (m_type != Preset::TYPE_FILAMENT) break; + else + { + if (m_presets[idx].name.find("PLA") != std::string::npos) + { + first_visible = idx; + break; + } } } + if (first_visible == -1) + { + if (m_presets.size() > 1 && m_default_suppressed) + first_visible = m_presets.size() == m_num_default_presets ? 0 : m_num_default_presets; + else + first_visible = 0; } - if (first_visible == -1) { - if (m_presets.size() > 1 && m_default_suppressed) - first_visible = m_presets.size() == m_num_default_presets ? 0 : m_num_default_presets; - else - first_visible = 0; + return first_visible; } - return first_visible; -} -std::vector PresetCollection::diameters_of_selected_printer() -{ - std::set diameters; - auto printer_model = m_edited_preset.config.opt_string("printer_model"); - for (auto &preset : m_presets) { - if (preset.config.opt_string("printer_model") == printer_model) - diameters.insert(preset.config.opt_string("printer_variant")); + std::vector PresetCollection::diameters_of_selected_printer() + { + std::set diameters; + auto printer_model = m_edited_preset.config.opt_string("printer_model"); + for (auto &preset : m_presets) + { + if (preset.config.opt_string("printer_model") == printer_model) + diameters.insert(preset.config.opt_string("printer_variant")); + } + return std::vector{diameters.begin(), diameters.end()}; } - return std::vector{diameters.begin(), diameters.end()}; -} -void PresetCollection::set_default_suppressed(bool default_suppressed) -{ - if (m_default_suppressed != default_suppressed) { - m_default_suppressed = default_suppressed; - bool default_visible = ! default_suppressed || m_idx_selected < m_num_default_presets; - for (size_t i = 0; i < m_num_default_presets; ++ i) - m_presets[i].is_visible = default_visible; + void PresetCollection::set_default_suppressed(bool default_suppressed) + { + if (m_default_suppressed != default_suppressed) + { + m_default_suppressed = default_suppressed; + bool default_visible = !default_suppressed || m_idx_selected < m_num_default_presets; + for (size_t i = 0; i < m_num_default_presets; ++i) + m_presets[i].is_visible = default_visible; + } } -} -size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType unselect_if_incompatible) -{ - DynamicPrintConfig config; - config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name)); - const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter"); - if (opt) - config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); - int some_compatible = 0; - - if (active_print) - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": active printer %1%, print %2%, unselect_if_incompatible %3%")%active_printer.preset.name %active_print->preset.name % (int)unselect_if_incompatible; - else - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": active printer %1%, unselect_if_incompatible %2%")%active_printer.preset.name % (int)unselect_if_incompatible; - for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) { - bool selected = idx_preset == m_idx_selected; - Preset &preset_selected = m_presets[idx_preset]; - Preset &preset_edited = selected ? m_edited_preset : preset_selected; - - const PresetWithVendorProfile this_preset_with_vendor_profile = this->get_preset_with_vendor_profile(preset_edited); - bool was_compatible = preset_edited.is_compatible; - preset_edited.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer, &config); - if (preset_edited.is_compatible) - some_compatible++; - if (active_print != nullptr) - preset_edited.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print, active_printer); - if (! preset_edited.is_compatible && selected && - (unselect_if_incompatible == PresetSelectCompatibleType::Always || (unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible && was_compatible))) - { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": type %1% , previous selected %2% becomes uncompatible, will select later")%Preset::get_type_string(m_type) %m_idx_selected; - m_idx_selected = size_t(-1); - } - if (selected) - preset_selected.is_compatible = preset_edited.is_compatible; - } - // Update visibility of the default profiles here if the defaults are suppressed, the current profile is not compatible and we don't want to select another compatible profile. - if (m_idx_selected >= m_num_default_presets && m_default_suppressed) - { - for (size_t i = 0; i < m_num_default_presets; ++ i) - { - m_presets[i].is_visible = (some_compatible == 0); - } - } - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": type %1% returned m_idx_selected %2%, some_compatible %3%")%Preset::get_type_string(m_type) %m_idx_selected %some_compatible; - return m_idx_selected; -} - -// Update a dirty flag of the current preset -// Return true if the dirty flag changed. -bool PresetCollection::update_dirty() -{ - bool was_dirty = this->get_selected_preset().is_dirty; - bool is_dirty = current_is_dirty(); - this->get_selected_preset().is_dirty = is_dirty; - this->get_edited_preset().is_dirty = is_dirty; + size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType unselect_if_incompatible) + { + DynamicPrintConfig config; + config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name)); + const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter"); + if (opt) + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + int some_compatible = 0; + + if (active_print) + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": active printer %1%, print %2%, unselect_if_incompatible %3%") % active_printer.preset.name % active_print->preset.name % (int)unselect_if_incompatible; + else + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": active printer %1%, unselect_if_incompatible %2%") % active_printer.preset.name % (int)unselect_if_incompatible; + for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++idx_preset) + { + bool selected = idx_preset == m_idx_selected; + Preset &preset_selected = m_presets[idx_preset]; + Preset &preset_edited = selected ? m_edited_preset : preset_selected; + + const PresetWithVendorProfile this_preset_with_vendor_profile = this->get_preset_with_vendor_profile(preset_edited); + bool was_compatible = preset_edited.is_compatible; + preset_edited.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer, &config); + if (preset_edited.is_compatible) + some_compatible++; + if (active_print != nullptr) + preset_edited.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print, active_printer); + if (!preset_edited.is_compatible && selected && + (unselect_if_incompatible == PresetSelectCompatibleType::Always || (unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible && was_compatible))) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": type %1% , previous selected %2% becomes uncompatible, will select later") % Preset::get_type_string(m_type) % m_idx_selected; + m_idx_selected = size_t(-1); + } + if (selected) + preset_selected.is_compatible = preset_edited.is_compatible; + } + // Update visibility of the default profiles here if the defaults are suppressed, the current profile is not compatible and we don't want to select another compatible profile. + if (m_idx_selected >= m_num_default_presets && m_default_suppressed) + { + for (size_t i = 0; i < m_num_default_presets; ++i) + { + m_presets[i].is_visible = (some_compatible == 0); + } + } - return was_dirty != is_dirty; -} + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": type %1% returned m_idx_selected %2%, some_compatible %3%") % Preset::get_type_string(m_type) % m_idx_selected % some_compatible; + return m_idx_selected; + } -template -void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase &this_c, bool strict) -{ - const T* opt_init = static_cast(other.option(opt_key)); - const T* opt_cur = static_cast(this_c.option(opt_key)); - int opt_init_max_id = opt_init->values.size() - 1; - if (opt_init_max_id < 0) { - for (int i = 0; i < int(opt_cur->values.size()); i++) - vec.emplace_back(opt_key + "#" + std::to_string(i)); - return; + // Update a dirty flag of the current preset + // Return true if the dirty flag changed. + bool PresetCollection::update_dirty() + { + bool was_dirty = this->get_selected_preset().is_dirty; + bool is_dirty = current_is_dirty(); + this->get_selected_preset().is_dirty = is_dirty; + this->get_edited_preset().is_dirty = is_dirty; + + return was_dirty != is_dirty; } - for (int i = 0; i < int(opt_cur->values.size()); i++) + template + void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys &vec, const ConfigBase &other, const ConfigBase &this_c, bool strict) { - int init_id = i <= opt_init_max_id ? i : 0; - if (opt_cur->values[i] != opt_init->values[init_id]) { - if (opt_cur->nullable()) { - if (opt_cur->is_nil(i)) { - if (strict && !opt_init->is_nil(init_id)) - vec.emplace_back(opt_key + "#" + std::to_string(i)); - } else { - if (strict || !opt_init->is_nil(init_id)) - vec.emplace_back(opt_key + "#" + std::to_string(i)); - } - } else { + const T *opt_init = static_cast(other.option(opt_key)); + const T *opt_cur = static_cast(this_c.option(opt_key)); + int opt_init_max_id = opt_init->values.size() - 1; + if (opt_init_max_id < 0) + { + for (int i = 0; i < int(opt_cur->values.size()); i++) vec.emplace_back(opt_key + "#" + std::to_string(i)); + return; + } + + for (int i = 0; i < int(opt_cur->values.size()); i++) + { + int init_id = i <= opt_init_max_id ? i : 0; + if (opt_cur->values[i] != opt_init->values[init_id]) + { + if (opt_cur->nullable()) + { + if (opt_cur->is_nil(i)) + { + if (strict && !opt_init->is_nil(init_id)) + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } + else + { + if (strict || !opt_init->is_nil(init_id)) + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } + } + else + { + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } } } } -} -// Use deep_diff to correct return of changed options, considering individual options for each extruder. -inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other, bool strict = true) -{ - t_config_option_keys diff; - t_config_option_keys keys; - if (strict) { - t_config_option_keys keys_this = config_this.keys(); - t_config_option_keys keys_other = config_other.keys(); - std::set_union(keys_this.begin(), keys_this.end(), keys_other.begin(), keys_other.end(), std::back_inserter(keys)); - } else { - keys = config_this.keys(); - } - for (const t_config_option_key &opt_key : keys) { - const ConfigOption *this_opt = config_this.option(opt_key); - const ConfigOption *other_opt = config_other.option(opt_key); - if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) - { - //BBS: add bed_exclude_area - if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers"|| opt_key == "thumbnail_size" || opt_key == "wrapping_exclude_area") { - // Scalar variable, or a vector variable, which is independent from number of extruders, - // thus the vector is presented to the user as a single input. - diff.emplace_back(opt_key); - } else if (opt_key == "default_filament_profile") { - // Ignore this field, it is not presented to the user, therefore showing a "modified" flag for this parameter does not help. - // Also the length of this field may differ, which may lead to a crash if the block below is used. - } else { - switch (other_opt->type()) { - case coInts: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; - case coBools: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; - case coFloats: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; - case coStrings: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; - case coPercents:add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; - case coFloatsOrPercents: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; - case coPoints: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; - // BBS - case coEnums: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; - default: diff.emplace_back(opt_key); break; + // Use deep_diff to correct return of changed options, considering individual options for each extruder. + inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other, bool strict = true) + { + t_config_option_keys diff; + t_config_option_keys keys; + if (strict) + { + t_config_option_keys keys_this = config_this.keys(); + t_config_option_keys keys_other = config_other.keys(); + std::set_union(keys_this.begin(), keys_this.end(), keys_other.begin(), keys_other.end(), std::back_inserter(keys)); + } + else + { + keys = config_this.keys(); + } + for (const t_config_option_key &opt_key : keys) + { + const ConfigOption *this_opt = config_this.option(opt_key); + const ConfigOption *other_opt = config_other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) + { + // BBS: add bed_exclude_area + if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnail_size" || opt_key == "wrapping_exclude_area") + { + // Scalar variable, or a vector variable, which is independent from number of extruders, + // thus the vector is presented to the user as a single input. + diff.emplace_back(opt_key); + } + else if (opt_key == "default_filament_profile") + { + // Ignore this field, it is not presented to the user, therefore showing a "modified" flag for this parameter does not help. + // Also the length of this field may differ, which may lead to a crash if the block below is used. + } + else + { + switch (other_opt->type()) + { + case coInts: + add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); + break; + case coBools: + add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); + break; + case coFloats: + add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); + break; + case coStrings: + add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); + break; + case coPercents: + add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); + break; + case coFloatsOrPercents: + add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); + break; + case coPoints: + add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); + break; + // BBS + case coEnums: + add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); + break; + default: + diff.emplace_back(opt_key); + break; + } } } - } - else if (strict) { - const ConfigOption *opt = nullptr; - if (this_opt != nullptr && other_opt == nullptr) - opt = this_opt; - else if (this_opt == nullptr && other_opt != nullptr) - opt = other_opt; - if (opt) { - if (opt->type() & coVectorType) { - auto vec = dynamic_cast(opt); - for (size_t i = 0; i < vec->size(); i++) - diff.push_back(opt_key + "#" + std::to_string(i)); - } else { + else if (strict) + { + const ConfigOption *opt = nullptr; + if (this_opt != nullptr && other_opt == nullptr) + opt = this_opt; + else if (this_opt == nullptr && other_opt != nullptr) + opt = other_opt; + if (opt) + { + if (opt->type() & coVectorType) + { + auto vec = dynamic_cast(opt); + for (size_t i = 0; i < vec->size(); i++) + diff.push_back(opt_key + "#" + std::to_string(i)); + } + else + { diff.push_back(opt_key); + } } } } + return diff; } - return diff; -} -static constexpr const std::initializer_list optional_keys { "compatible_prints", "compatible_printers" }; -//BBS: skip these keys for dirty check -static std::set skipped_in_dirty = {"printer_settings_id", "print_settings_id", "filament_settings_id"}; + static constexpr const std::initializer_list optional_keys{"compatible_prints", "compatible_printers"}; + // BBS: skip these keys for dirty check + static std::set skipped_in_dirty = {"printer_settings_id", "print_settings_id", "filament_settings_id"}; -bool PresetCollection::is_dirty(const Preset *edited, const Preset *reference) -{ - if (edited != nullptr && reference != nullptr) { - // Only compares options existing in both configs. - if (! reference->config.equals(edited->config, &skipped_in_dirty)) - return true; - // The "compatible_printers" option key is handled differently from the others: - // It is not mandatory. If the key is missing, it means it is compatible with any printer. - // If the key exists and it is empty, it means it is compatible with no printer. - for (auto &opt_key : optional_keys) - if (reference->config.has(opt_key) != edited->config.has(opt_key)) + bool PresetCollection::is_dirty(const Preset *edited, const Preset *reference) + { + if (edited != nullptr && reference != nullptr) + { + // Only compares options existing in both configs. + if (!reference->config.equals(edited->config, &skipped_in_dirty)) return true; + // The "compatible_printers" option key is handled differently from the others: + // It is not mandatory. If the key is missing, it means it is compatible with any printer. + // If the key exists and it is empty, it means it is compatible with no printer. + for (auto &opt_key : optional_keys) + if (reference->config.has(opt_key) != edited->config.has(opt_key)) + return true; + } + return false; } - return false; -} -std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/) -{ - std::vector changed; - if (edited != nullptr && reference != nullptr) { - // Only compares options existing in both configs. - changed = deep_compare ? - deep_diff(edited->config, reference->config) : - reference->config.diff(edited->config); - // The "compatible_printers" option key is handled differently from the others: - // It is not mandatory. If the key is missing, it means it is compatible with any printer. - // If the key exists and it is empty, it means it is compatible with no printer. - for (auto &opt_key : optional_keys) - if (reference->config.has(opt_key) != edited->config.has(opt_key)) - changed.emplace_back(opt_key); - } - return changed; -} - -//BBS: add function for dirty_options_without_option_list -std::vector PresetCollection::dirty_options_without_option_list(const Preset *edited, const Preset *reference, const std::set& option_ignore_list, const bool deep_compare) -{ - std::vector changed; - if (edited != nullptr && reference != nullptr) { - // Only compares options existing in both configs. - changed = deep_compare ? - deep_diff(edited->config, reference->config) : - reference->config.diff(edited->config); - // The "compatible_printers" option key is handled differently from the others: - // It is not mandatory. If the key is missing, it means it is compatible with any printer. - // If the key exists and it is empty, it means it is compatible with no printer. - for (auto &opt_key : optional_keys) { - if (reference->config.has(opt_key) != edited->config.has(opt_key)) - changed.emplace_back(opt_key); - } - auto iter = changed.begin(); - while (iter != changed.end()) { - if (option_ignore_list.find(*iter) != option_ignore_list.end()) { - iter = changed.erase(iter); - } - else { - ++iter; - } - } - } - return changed; -} - -// Select a new preset. This resets all the edits done to the currently selected preset. -// If the preset with index idx does not exist, a first visible preset is selected. -Preset& PresetCollection::select_preset(size_t idx) -{ - //BBS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% try to select preset %2%")%Preset::get_type_string(m_type) %idx; - for (Preset &preset : m_presets) - preset.is_dirty = false; - if (idx >= m_presets.size()) - idx = first_visible_idx(); - m_idx_selected = idx; - m_edited_preset = m_presets[idx]; - update_saved_preset_from_current_preset(); - bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; - for (size_t i = 0; i < m_num_default_presets; ++i) - m_presets[i].is_visible = default_visible; - - //set this preset to true - if (!m_presets[idx].is_visible) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% set %2%, idx %3% to visible") % Preset::get_type_string(m_type) % m_presets[idx].name % idx; - m_presets[idx].is_visible = true; - } - //BBS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% select success, m_idx_selected %2%, name %3%, is_system %4%, is_default %5%")%Preset::get_type_string(m_type) % m_idx_selected % m_edited_preset.name % m_edited_preset.is_system % m_edited_preset.is_default; - return m_presets[idx]; -} - -bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) -{ - //BBS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%, force %3%")%Preset::get_type_string(m_type) %name_w_suffix %force; - std::string name = Preset::remove_suffix_modified(name_w_suffix); - // 1) Try to find the preset by its name. - auto it = this->find_preset_internal(name); - size_t idx = 0; - if (it != m_presets.end() && it->name == name && it->is_visible) - // Preset found by its name and it is visible. - idx = it - m_presets.begin(); - else { - // Find the first visible preset. - for (size_t i = m_default_suppressed ? m_num_default_presets : 0; i < m_presets.size(); ++ i) - if (m_presets[i].is_visible) { - idx = i; - break; + std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/) + { + std::vector changed; + if (edited != nullptr && reference != nullptr) + { + // Only compares options existing in both configs. + changed = deep_compare ? deep_diff(edited->config, reference->config) : reference->config.diff(edited->config); + // The "compatible_printers" option key is handled differently from the others: + // It is not mandatory. If the key is missing, it means it is compatible with any printer. + // If the key exists and it is empty, it means it is compatible with no printer. + for (auto &opt_key : optional_keys) + if (reference->config.has(opt_key) != edited->config.has(opt_key)) + changed.emplace_back(opt_key); + } + return changed; + } + + // BBS: add function for dirty_options_without_option_list + std::vector PresetCollection::dirty_options_without_option_list(const Preset *edited, const Preset *reference, const std::set &option_ignore_list, const bool deep_compare) + { + std::vector changed; + if (edited != nullptr && reference != nullptr) + { + // Only compares options existing in both configs. + changed = deep_compare ? deep_diff(edited->config, reference->config) : reference->config.diff(edited->config); + // The "compatible_printers" option key is handled differently from the others: + // It is not mandatory. If the key is missing, it means it is compatible with any printer. + // If the key exists and it is empty, it means it is compatible with no printer. + for (auto &opt_key : optional_keys) + { + if (reference->config.has(opt_key) != edited->config.has(opt_key)) + changed.emplace_back(opt_key); + } + auto iter = changed.begin(); + while (iter != changed.end()) + { + if (option_ignore_list.find(*iter) != option_ignore_list.end()) + { + iter = changed.erase(iter); + } + else + { + ++iter; + } } - // If the first visible preset was not found, return the 0th element, which is the default preset. + } + return changed; } - // 2) Select the new preset. - if (m_idx_selected != idx || force) { - this->select_preset(idx); - //BBS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, success")%Preset::get_type_string(m_type) %name_w_suffix; - return true; + // Select a new preset. This resets all the edits done to the currently selected preset. + // If the preset with index idx does not exist, a first visible preset is selected. + Preset &PresetCollection::select_preset(size_t idx) + { + // BBS: add config related logs + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% try to select preset %2%") % Preset::get_type_string(m_type) % idx; + for (Preset &preset : m_presets) + preset.is_dirty = false; + if (idx >= m_presets.size()) + idx = first_visible_idx(); + m_idx_selected = idx; + m_edited_preset = m_presets[idx]; + update_saved_preset_from_current_preset(); + bool default_visible = !m_default_suppressed || m_idx_selected < m_num_default_presets; + for (size_t i = 0; i < m_num_default_presets; ++i) + m_presets[i].is_visible = default_visible; + + // set this preset to true + if (!m_presets[idx].is_visible) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% set %2%, idx %3% to visible") % Preset::get_type_string(m_type) % m_presets[idx].name % idx; + m_presets[idx].is_visible = true; + } + // BBS: add config related logs + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% select success, m_idx_selected %2%, name %3%, is_system %4%, is_default %5%") % Preset::get_type_string(m_type) % m_idx_selected % m_edited_preset.name % m_edited_preset.is_system % m_edited_preset.is_default; + return m_presets[idx]; } - //BBS: add config related logs - if (m_idx_selected == idx) - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, already selected before") % Preset::get_type_string(m_type); - else - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, failed")%Preset::get_type_string(m_type) %name_w_suffix; - return false; -} + bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) + { + // BBS: add config related logs + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%, force %3%") % Preset::get_type_string(m_type) % name_w_suffix % force; + std::string name = Preset::remove_suffix_modified(name_w_suffix); + // 1) Try to find the preset by its name. + auto it = this->find_preset_internal(name); + size_t idx = 0; + if (it != m_presets.end() && it->name == name && it->is_visible) + // Preset found by its name and it is visible. + idx = it - m_presets.begin(); + else + { + // Find the first visible preset. + for (size_t i = m_default_suppressed ? m_num_default_presets : 0; i < m_presets.size(); ++i) + if (m_presets[i].is_visible) + { + idx = i; + break; + } + // If the first visible preset was not found, return the 0th element, which is the default preset. + } + + // 2) Select the new preset. + if (m_idx_selected != idx || force) + { + this->select_preset(idx); + // BBS: add config related logs + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, success") % Preset::get_type_string(m_type) % name_w_suffix; + return true; + } -bool PresetCollection::select_preset_by_name_strict(const std::string &name) -{ - //BBS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%")%Preset::get_type_string(m_type) %name; - // 1) Try to find the preset by its name. - auto it = this->find_preset_internal(name); - - size_t idx = (size_t)-1; - if (it != m_presets.end() && it->name == name && it->is_visible) - // Preset found by its name. - idx = it - m_presets.begin(); - // 2) Select the new preset. - if (idx != (size_t)-1) { - this->select_preset(idx); - //BBS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, success")%Preset::get_type_string(m_type) %name; - return true; + // BBS: add config related logs + if (m_idx_selected == idx) + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, already selected before") % Preset::get_type_string(m_type); + else + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, failed") % Preset::get_type_string(m_type) % name_w_suffix; + return false; } - m_idx_selected = idx; - //BBS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, failed")%Preset::get_type_string(m_type) %name; - return false; -} -// Merge one vendor's presets with the other vendor's presets, report duplicates. -std::vector PresetCollection::merge_presets(PresetCollection &&other, const VendorMap &new_vendors) -{ - std::vector duplicates; - for (Preset &preset : other.m_presets) { - if (preset.is_default || preset.is_external) - continue; - Preset key(m_type, preset.name); - auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key); - if (it == m_presets.end() || it->name != preset.name) { - if (preset.vendor != nullptr) { + bool PresetCollection::select_preset_by_name_strict(const std::string &name) + { + // BBS: add config related logs + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%") % Preset::get_type_string(m_type) % name; + // 1) Try to find the preset by its name. + auto it = this->find_preset_internal(name); + + size_t idx = (size_t)-1; + if (it != m_presets.end() && it->name == name && it->is_visible) + // Preset found by its name. + idx = it - m_presets.begin(); + // 2) Select the new preset. + if (idx != (size_t)-1) + { + this->select_preset(idx); + // BBS: add config related logs + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, success") % Preset::get_type_string(m_type) % name; + return true; + } + m_idx_selected = idx; + // BBS: add config related logs + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, failed") % Preset::get_type_string(m_type) % name; + return false; + } + + // Merge one vendor's presets with the other vendor's presets, report duplicates. + std::vector PresetCollection::merge_presets(PresetCollection &&other, const VendorMap &new_vendors) + { + std::vector duplicates; + for (Preset &preset : other.m_presets) + { + if (preset.is_default || preset.is_external) + continue; + Preset key(m_type, preset.name); + auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key); + if (it == m_presets.end() || it->name != preset.name) + { + if (preset.vendor != nullptr) + { + // Re-assign a pointer to the vendor structure in the new PresetBundle. + auto it = new_vendors.find(preset.vendor->id); + assert(it != new_vendors.end()); + preset.vendor = &it->second; + } + m_presets.emplace(it, std::move(preset)); + } + else + duplicates.emplace_back(std::move(preset.name)); + } + return duplicates; + } + + void PresetCollection::update_vendor_ptrs_after_copy(const VendorMap &new_vendors) + { + for (Preset &preset : m_presets) + if (preset.vendor != nullptr) + { + assert(!preset.is_default && !preset.is_external); // Re-assign a pointer to the vendor structure in the new PresetBundle. auto it = new_vendors.find(preset.vendor->id); assert(it != new_vendors.end()); preset.vendor = &it->second; } - m_presets.emplace(it, std::move(preset)); - } else - duplicates.emplace_back(std::move(preset.name)); } - return duplicates; -} -void PresetCollection::update_vendor_ptrs_after_copy(const VendorMap &new_vendors) -{ - for (Preset &preset : m_presets) - if (preset.vendor != nullptr) { - assert(! preset.is_default && ! preset.is_external); - // Re-assign a pointer to the vendor structure in the new PresetBundle. - auto it = new_vendors.find(preset.vendor->id); - assert(it != new_vendors.end()); - preset.vendor = &it->second; + void PresetCollection::update_map_alias_to_profile_name() + { + m_map_alias_to_profile_name.clear(); + for (const Preset &preset : m_presets) + { + m_map_alias_to_profile_name[preset.alias].push_back(preset.name); } -} + // now m_map_alias_to_profile_name is map, not need sort + // std::sort(m_map_alias_to_profile_name.begin(), m_map_alias_to_profile_name.end(), [](auto &l, auto &r) { return l.first < r.first; }); + } -void PresetCollection::update_map_alias_to_profile_name() -{ - m_map_alias_to_profile_name.clear(); - for (const Preset &preset : m_presets) { - m_map_alias_to_profile_name[preset.alias].push_back(preset.name); + void PresetCollection::update_map_system_profile_renamed() + { + m_map_system_profile_renamed.clear(); + for (Preset &preset : m_presets) + for (const std::string &renamed_from : preset.renamed_from) + { + const auto [it, success] = m_map_system_profile_renamed.insert(std::pair(renamed_from, preset.name)); + if (!success) + BOOST_LOG_TRIVIAL(error) << boost::format("Preset name \"%1%\" was marked as renamed from \"%2%\", though preset name \"%3%\" was marked as renamed from \"%2%\" as well.") % preset.name % renamed_from % it->second; + } } - // now m_map_alias_to_profile_name is map, not need sort - //std::sort(m_map_alias_to_profile_name.begin(), m_map_alias_to_profile_name.end(), [](auto &l, auto &r) { return l.first < r.first; }); -} -void PresetCollection::update_map_system_profile_renamed() -{ - m_map_system_profile_renamed.clear(); - for (Preset &preset : m_presets) - for (const std::string &renamed_from : preset.renamed_from) { - const auto [it, success] = m_map_system_profile_renamed.insert(std::pair(renamed_from, preset.name)); - if (! success) - BOOST_LOG_TRIVIAL(error) << boost::format("Preset name \"%1%\" was marked as renamed from \"%2%\", though preset name \"%3%\" was marked as renamed from \"%2%\" as well.") % preset.name % renamed_from % it->second; - } -} - -void PresetCollection::set_custom_preset_alias(Preset &preset) -{ - if (m_type == Preset::Type::TYPE_FILAMENT && preset.is_user() && preset.config.has(BBL_JSON_KEY_INHERITS) && - preset.config.option(BBL_JSON_KEY_INHERITS)->value.empty()) { + void PresetCollection::set_custom_preset_alias(Preset &preset) + { + if (m_type == Preset::Type::TYPE_FILAMENT && preset.is_user() && preset.config.has(BBL_JSON_KEY_INHERITS) && + preset.config.option(BBL_JSON_KEY_INHERITS)->value.empty()) + { + std::string alias_name; + std::string preset_name = preset.name; + if (alias_name.empty()) + { + size_t end_pos = preset_name.find_first_of("@"); + if (end_pos != std::string::npos) + { + alias_name = preset_name.substr(0, end_pos); + boost::trim_right(alias_name); + } + } + if (alias_name.empty() || is_alias_exist(alias_name, &preset)) + preset.alias = ""; + else + { + preset.alias = std::move(alias_name); + m_map_alias_to_profile_name[preset.alias].push_back(preset.name); + set_printer_hold_alias(preset.alias, preset); + } + } + } + + void PresetCollection::set_printer_hold_alias(const std::string &alias, Preset &preset, bool remove) + { + auto compatible_printers = dynamic_cast(preset.config.option("compatible_printers")); + if (compatible_printers == nullptr) + return; + for (const std::string &printer_name : compatible_printers->values) + { + auto printer_iter = m_printer_hold_alias.find(printer_name); + bool insert_success = false, remove_success = false; + if (m_printer_hold_alias.end() == printer_iter) + { + if (!remove) + { + insert_success = true; + m_printer_hold_alias[printer_name].insert(alias); + } + } + else + { + auto &printer_filament_alias = m_printer_hold_alias[printer_name]; + auto alias_iter = printer_filament_alias.find(alias); + if (printer_filament_alias.end() == alias_iter) + { + if (!remove) + { + insert_success = true; + printer_filament_alias.insert(alias); + } + } + else + { + if (remove) + { + if (preset.inherits() == "") + { + remove_success = true; + printer_filament_alias.erase(alias); + } + if (auto alias_iter = m_map_alias_to_profile_name.find(alias); alias_iter != m_map_alias_to_profile_name.end()) + { + auto &presets = alias_iter->second; + auto new_end = std::remove(presets.begin(), presets.end(), preset.name); + presets.erase(new_end, presets.end()); + if (presets.empty()) + { + m_map_alias_to_profile_name.erase(alias); + } + } + } + } + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << " preset name : " << preset.name << " remove action: " << remove << " insert success: " + << insert_success << " remove success: " << remove_success << " alias: " << alias; + } + } + + std::string PresetCollection::get_preset_alias(Preset &preset, bool force) + { + if (!preset.alias.empty()) + return preset.alias; + else + set_custom_preset_alias(preset); + + if (!preset.alias.empty() || !force) + return preset.alias; + std::string alias_name; std::string preset_name = preset.name; - if (alias_name.empty()) { - size_t end_pos = preset_name.find_first_of("@"); - if (end_pos != std::string::npos) { - alias_name = preset_name.substr(0, end_pos); - boost::trim_right(alias_name); - } + size_t end_pos = preset_name.find_first_of("@"); + if (end_pos != std::string::npos) + { + alias_name = preset_name.substr(0, end_pos); + boost::trim_right(alias_name); } - if (alias_name.empty() || is_alias_exist(alias_name, &preset)) - preset.alias = ""; - else { - preset.alias = std::move(alias_name); - m_map_alias_to_profile_name[preset.alias].push_back(preset.name); - set_printer_hold_alias(preset.alias, preset); + return alias_name; + } + + std::string PresetCollection::name() const + { + switch (this->type()) + { + case Preset::TYPE_PRINT: + return L(PRESET_PRINT_NAME); + case Preset::TYPE_FILAMENT: + return L(PRESET_FILAMENT_NAME); + // case Preset::TYPE_SLA_PRINT: return L("SLA print"); + // case Preset::TYPE_SLA_MATERIAL: return L("SLA material"); + case Preset::TYPE_PRINTER: + return L(PRESET_PRINTER_NAME); + default: + return "invalid"; } } -} -void PresetCollection::set_printer_hold_alias(const std::string &alias, Preset &preset, bool remove) -{ - auto compatible_printers = dynamic_cast(preset.config.option("compatible_printers")); - if (compatible_printers == nullptr) return; - for (const std::string &printer_name : compatible_printers->values) { - auto printer_iter = m_printer_hold_alias.find(printer_name); - bool insert_success = false, remove_success = false; - if (m_printer_hold_alias.end() == printer_iter) { - if (!remove) { - insert_success = true; - m_printer_hold_alias[printer_name].insert(alias); - } - } else { - auto &printer_filament_alias = m_printer_hold_alias[printer_name]; - auto alias_iter = printer_filament_alias.find(alias); - if (printer_filament_alias.end() == alias_iter) { - if (!remove) { - insert_success = true; - printer_filament_alias.insert(alias); - } - } else { - if (remove) { - if (preset.inherits() == "") { - remove_success = true; - printer_filament_alias.erase(alias); - } - if (auto alias_iter = m_map_alias_to_profile_name.find(alias); alias_iter != m_map_alias_to_profile_name.end()) { - auto& presets = alias_iter->second; - auto new_end = std::remove(presets.begin(), presets.end(), preset.name); - presets.erase(new_end, presets.end()); - if (presets.empty()) { m_map_alias_to_profile_name.erase(alias); } - } - } - } + // BBS: change directoties by design + std::string PresetCollection::section_name() const + { + switch (this->type()) + { + case Preset::TYPE_PRINT: + return PRESET_PRINT_NAME; + case Preset::TYPE_FILAMENT: + return PRESET_FILAMENT_NAME; + // case Preset::TYPE_SLA_PRINT: return PRESET_SLA_PRINT_NAME; + // case Preset::TYPE_SLA_MATERIAL: return PRESET_SLA_MATERIALS_NAME; + case Preset::TYPE_PRINTER: + return PRESET_PRINTER_NAME; + default: + return "invalid"; } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << " preset name : " << preset.name << " remove action: " << remove << " insert success: " - << insert_success << " remove success: " << remove_success << " alias: " << alias; } -} - -std::string PresetCollection::get_preset_alias(Preset &preset, bool force) -{ - if (!preset.alias.empty()) - return preset.alias; - else - set_custom_preset_alias(preset); - - if (!preset.alias.empty() || !force) - return preset.alias; - std::string alias_name; - std::string preset_name = preset.name; - size_t end_pos = preset_name.find_first_of("@"); - if (end_pos != std::string::npos) { - alias_name = preset_name.substr(0, end_pos); - boost::trim_right(alias_name); + // Used for validating the "inherits" flag when importing user's config bundles. + // Returns names of all system presets including the former names of these presets. + std::vector PresetCollection::system_preset_names() const + { + size_t num = 0; + for (const Preset &preset : m_presets) + if (preset.is_system) + ++num; + std::vector out; + out.reserve(num); + for (const Preset &preset : m_presets) + if (preset.is_system) + { + out.emplace_back(preset.name); + out.insert(out.end(), preset.renamed_from.begin(), preset.renamed_from.end()); + } + std::sort(out.begin(), out.end()); + return out; } - return alias_name; -} -std::string PresetCollection::name() const -{ - switch (this->type()) { - case Preset::TYPE_PRINT: return L(PRESET_PRINT_NAME); - case Preset::TYPE_FILAMENT: return L(PRESET_FILAMENT_NAME); - //case Preset::TYPE_SLA_PRINT: return L("SLA print"); - //case Preset::TYPE_SLA_MATERIAL: return L("SLA material"); - case Preset::TYPE_PRINTER: return L(PRESET_PRINTER_NAME); - default: return "invalid"; - } -} - -//BBS: change directoties by design -std::string PresetCollection::section_name() const -{ - switch (this->type()) { - case Preset::TYPE_PRINT: return PRESET_PRINT_NAME; - case Preset::TYPE_FILAMENT: return PRESET_FILAMENT_NAME; - //case Preset::TYPE_SLA_PRINT: return PRESET_SLA_PRINT_NAME; - //case Preset::TYPE_SLA_MATERIAL: return PRESET_SLA_MATERIALS_NAME; - case Preset::TYPE_PRINTER: return PRESET_PRINTER_NAME; - default: return "invalid"; - } -} - -// Used for validating the "inherits" flag when importing user's config bundles. -// Returns names of all system presets including the former names of these presets. -std::vector PresetCollection::system_preset_names() const -{ - size_t num = 0; - for (const Preset &preset : m_presets) - if (preset.is_system) - ++ num; - std::vector out; - out.reserve(num); - for (const Preset &preset : m_presets) - if (preset.is_system) { - out.emplace_back(preset.name); - out.insert(out.end(), preset.renamed_from.begin(), preset.renamed_from.end()); - } - std::sort(out.begin(), out.end()); - return out; -} - -// Generate a file path from a profile name. Add the ".ini" suffix if it is missing. -std::string PresetCollection::path_from_name(const std::string &new_name, bool detach) const -{ - //BBS: change to json format - //std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); - std::string file_name = boost::iends_with(new_name, ".json") ? new_name : (new_name + ".json"); - if (detach) - return (boost::filesystem::path(m_dir_path) / "base" / file_name).make_preferred().string(); - else - return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); -} + // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. + std::string PresetCollection::path_from_name(const std::string &new_name, bool detach) const + { + // BBS: change to json format + // std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); + std::string file_name = boost::iends_with(new_name, ".json") ? new_name : (new_name + ".json"); + if (detach) + return (boost::filesystem::path(m_dir_path) / "base" / file_name).make_preferred().string(); + else + return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); + } -std::string PresetCollection::path_for_preset(const Preset &preset) const -{ - return path_from_name(preset.name, is_base_preset(preset)); -} + std::string PresetCollection::path_for_preset(const Preset &preset) const + { + return path_from_name(preset.name, is_base_preset(preset)); + } -const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const -{ - const ConfigOptionEnumGeneric *opt_printer_technology = config.opt("printer_technology"); - return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1); -} + const Preset &PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const + { + const ConfigOptionEnumGeneric *opt_printer_technology = config.opt("printer_technology"); + return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1); + } -const Preset* PrinterPresetCollection::find_system_preset_by_model_and_variant(const std::string &model_id, const std::string& variant) const -{ - if (model_id.empty()) { return nullptr; } + const Preset *PrinterPresetCollection::find_system_preset_by_model_and_variant(const std::string &model_id, const std::string &variant) const + { + if (model_id.empty()) + { + return nullptr; + } - const auto it = std::find_if(cbegin(), cend(), [&](const Preset &preset) { + const auto it = std::find_if(cbegin(), cend(), [&](const Preset &preset) + { if (!preset.is_system || preset.config.opt_string("printer_model") != model_id) return false; if (variant.empty()) return true; - return preset.config.opt_string("printer_variant") == variant; - }); + return preset.config.opt_string("printer_variant") == variant; }); - return it != cend() ? &*it : nullptr; -} + return it != cend() ? &*it : nullptr; + } -const Preset *PrinterPresetCollection::find_custom_preset_by_model_and_variant(const std::string &model_id, const std::string &variant) const -{ - if (model_id.empty()) { return nullptr; } + const Preset *PrinterPresetCollection::find_custom_preset_by_model_and_variant(const std::string &model_id, const std::string &variant) const + { + if (model_id.empty()) + { + return nullptr; + } - const auto it = std::find_if(cbegin(), cend(), [&](const Preset &preset) { + const auto it = std::find_if(cbegin(), cend(), [&](const Preset &preset) + { if (preset.config.opt_string("printer_model") != model_id) return false; if (variant.empty()) return true; - return preset.config.opt_string("printer_variant") == variant; - }); + return preset.config.opt_string("printer_variant") == variant; }); - return it != cend() ? &*it : nullptr; -} + return it != cend() ? &*it : nullptr; + } -bool PrinterPresetCollection::only_default_printers() const -{ - for (const auto& printer : get_presets()) { - if (!boost::starts_with(printer.name,"Default") && printer.is_visible) - return false; + bool PrinterPresetCollection::only_default_printers() const + { + for (const auto &printer : get_presets()) + { + if (!boost::starts_with(printer.name, "Default") && printer.is_visible) + return false; + } + return true; } - return true; -} -// ------------------------- -// *** PhysicalPrinter *** -// ------------------------- + // ------------------------- + // *** PhysicalPrinter *** + // ------------------------- -std::string PhysicalPrinter::separator() -{ - return " * "; -} - -static std::vector s_PhysicalPrinter_opts { - "preset_name", // temporary option to compatibility with older Slicer - "preset_names", - "printer_technology", - "host_type", - "print_host", - "printhost_apikey", - "printhost_cafile", - "printhost_port", - "printhost_authorization_type", - // HTTP digest authentization (RFC 2617) - "printhost_user", - "printhost_password", - "printhost_ssl_ignore_revoke" -}; - -const std::vector& PhysicalPrinter::printer_options() -{ - return s_PhysicalPrinter_opts; -} + std::string PhysicalPrinter::separator() + { + return " * "; + } + + static std::vector s_PhysicalPrinter_opts{ + "preset_name", // temporary option to compatibility with older Slicer + "preset_names", + "printer_technology", + "host_type", + "print_host", + "printhost_apikey", + "printhost_cafile", + "printhost_port", + "printhost_authorization_type", + // HTTP digest authentization (RFC 2617) + "printhost_user", + "printhost_password", + "printhost_ssl_ignore_revoke"}; + + const std::vector &PhysicalPrinter::printer_options() + { + return s_PhysicalPrinter_opts; + } -std::vector PhysicalPrinter::presets_with_print_host_information(const PrinterPresetCollection& printer_presets) -{ - std::vector presets; - for (const Preset& preset : printer_presets) - if (has_print_host_information(preset.config)) - presets.emplace_back(preset.name); + std::vector PhysicalPrinter::presets_with_print_host_information(const PrinterPresetCollection &printer_presets) + { + std::vector presets; + for (const Preset &preset : printer_presets) + if (has_print_host_information(preset.config)) + presets.emplace_back(preset.name); - return presets; -} + return presets; + } -bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config) -{ - return false; -} + bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig &config) + { + return false; + } -const std::set& PhysicalPrinter::get_preset_names() const -{ - return preset_names; -} + const std::set &PhysicalPrinter::get_preset_names() const + { + return preset_names; + } -// temporary workaround for compatibility with older Slicer -static void update_preset_name_option(const std::set& preset_names, DynamicPrintConfig& config) -{ - std::string name; - for (auto el : preset_names) - name += el + ";"; - name.pop_back(); - config.set_key_value("preset_name", new ConfigOptionString(name)); -} - -void PhysicalPrinter::update_preset_names_in_config() -{ - if (!preset_names.empty()) { - std::vector& values = config.option("preset_names")->values; - values.clear(); - for (auto preset : preset_names) - values.push_back(preset); + // temporary workaround for compatibility with older Slicer + static void update_preset_name_option(const std::set &preset_names, DynamicPrintConfig &config) + { + std::string name; + for (auto el : preset_names) + name += el + ";"; + name.pop_back(); + config.set_key_value("preset_name", new ConfigOptionString(name)); + } + + void PhysicalPrinter::update_preset_names_in_config() + { + if (!preset_names.empty()) + { + std::vector &values = config.option("preset_names")->values; + values.clear(); + for (auto preset : preset_names) + values.push_back(preset); - // temporary workaround for compatibility with older Slicer - update_preset_name_option(preset_names, config); + // temporary workaround for compatibility with older Slicer + update_preset_name_option(preset_names, config); + } } -} -void PhysicalPrinter::save(const std::string& file_name_from, const std::string& file_name_to) -{ - // rename the file - boost::nowide::rename(file_name_from.data(), file_name_to.data()); - this->file = file_name_to; - // save configuration - //BBS: change to save - //this->config.save(this->file); - this->config.save_to_json(this->file, std::string("Physical_Printer"), std::string("User"), std::string(SLIC3R_VERSION)); -} - -void PhysicalPrinter::update_from_preset(const Preset& preset) -{ - config.apply_only(preset.config, printer_options(), true); - // add preset names to the options list - preset_names.emplace(preset.name); - update_preset_names_in_config(); -} + void PhysicalPrinter::save(const std::string &file_name_from, const std::string &file_name_to) + { + // rename the file + boost::nowide::rename(file_name_from.data(), file_name_to.data()); + this->file = file_name_to; + // save configuration + // BBS: change to save + // this->config.save(this->file); + this->config.save_to_json(this->file, std::string("Physical_Printer"), std::string("User"), std::string(SLIC3R_VERSION)); + } -void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) -{ - config.apply_only(new_config, printer_options(), false); + void PhysicalPrinter::update_from_preset(const Preset &preset) + { + config.apply_only(preset.config, printer_options(), true); + // add preset names to the options list + preset_names.emplace(preset.name); + update_preset_names_in_config(); + } - const std::vector& values = config.option("preset_names")->values; + void PhysicalPrinter::update_from_config(const DynamicPrintConfig &new_config) + { + config.apply_only(new_config, printer_options(), false); + + const std::vector &values = config.option("preset_names")->values; - if (values.empty()) - preset_names.clear(); - else { - for (const std::string& val : values) - preset_names.emplace(val); - // temporary workaround for compatibility with older Slicer - update_preset_name_option(preset_names, config); + if (values.empty()) + preset_names.clear(); + else + { + for (const std::string &val : values) + preset_names.emplace(val); + // temporary workaround for compatibility with older Slicer + update_preset_name_option(preset_names, config); + } } -} -void PhysicalPrinter::reset_presets() -{ - return preset_names.clear(); -} + void PhysicalPrinter::reset_presets() + { + return preset_names.clear(); + } -bool PhysicalPrinter::add_preset(const std::string& preset_name) -{ - return preset_names.emplace(preset_name).second; -} + bool PhysicalPrinter::add_preset(const std::string &preset_name) + { + return preset_names.emplace(preset_name).second; + } -bool PhysicalPrinter::delete_preset(const std::string& preset_name) -{ - return preset_names.erase(preset_name) > 0; -} + bool PhysicalPrinter::delete_preset(const std::string &preset_name) + { + return preset_names.erase(preset_name) > 0; + } -PhysicalPrinter::PhysicalPrinter(const std::string& name, const DynamicPrintConfig& default_config) : - name(name), config(default_config) -{ - update_from_config(config); -} + PhysicalPrinter::PhysicalPrinter(const std::string &name, const DynamicPrintConfig &default_config) : name(name), config(default_config) + { + update_from_config(config); + } -PhysicalPrinter::PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset) : - name(name), config(default_config) -{ - update_from_preset(preset); -} + PhysicalPrinter::PhysicalPrinter(const std::string &name, const DynamicPrintConfig &default_config, const Preset &preset) : name(name), config(default_config) + { + update_from_preset(preset); + } -void PhysicalPrinter::set_name(const std::string& name) -{ - this->name = name; -} + void PhysicalPrinter::set_name(const std::string &name) + { + this->name = name; + } -std::string PhysicalPrinter::get_full_name(std::string preset_name) const -{ - return name + separator() + preset_name; -} + std::string PhysicalPrinter::get_full_name(std::string preset_name) const + { + return name + separator() + preset_name; + } -std::string PhysicalPrinter::get_short_name(std::string full_name) -{ - int pos = full_name.find(separator()); - if (pos > 0) - boost::erase_tail(full_name, full_name.length() - pos); - return full_name; -} + std::string PhysicalPrinter::get_short_name(std::string full_name) + { + int pos = full_name.find(separator()); + if (pos > 0) + boost::erase_tail(full_name, full_name.length() - pos); + return full_name; + } -std::string PhysicalPrinter::get_preset_name(std::string name) -{ - int pos = name.find(separator()); - boost::erase_head(name, pos + 3); - return Preset::remove_suffix_modified(name); -} + std::string PhysicalPrinter::get_preset_name(std::string name) + { + int pos = name.find(separator()); + boost::erase_head(name, pos + 3); + return Preset::remove_suffix_modified(name); + } + // ----------------------------------- + // *** PhysicalPrinterCollection *** + // ----------------------------------- -// ----------------------------------- -// *** PhysicalPrinterCollection *** -// ----------------------------------- + PhysicalPrinterCollection::PhysicalPrinterCollection(const std::vector &keys) + { + // Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options(). + for (const std::string &key : keys) + { + const ConfigOptionDef *opt = print_config_def.get(key); + assert(opt); + assert(opt->default_value); + m_default_config.set_key_value(key, opt->default_value->clone()); + } + } -PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector& keys) -{ - // Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options(). - for (const std::string &key : keys) { - const ConfigOptionDef *opt = print_config_def.get(key); - assert(opt); - assert(opt->default_value); - m_default_config.set_key_value(key, opt->default_value->clone()); - } -} - -// Load all printers found in dir_path. -// Throws an exception on error. -void PhysicalPrinterCollection::load_printers( - const std::string& dir_path, const std::string& subdir, - PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule) -{ - // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, - // see https://github.com/prusa3d/PrusaSlicer/issues/732 - boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred(); - m_dir_path = dir.string(); - std::string errors_cummulative; - // Store the loaded printers into a new vector, otherwise the binary search for already existing presets would be broken. - std::deque printers_loaded; - //BBS: change to json format - for (auto& dir_entry : boost::filesystem::directory_iterator(dir)) - { - std::string file_name = dir_entry.path().filename().string(); - //if (Slic3r::is_ini_file(dir_entry)) { - if (Slic3r::is_json_file(file_name)) { - // Remove the .json suffix. - std::string name = file_name.erase(file_name.size() - 5); - if (this->find_printer(name, false)) { - // This happens when there's is a preset (most likely legacy one) with the same name as a system preset - // that's already been loaded from a bundle. - BOOST_LOG_TRIVIAL(warning) << "Printer already present, not loading: " << name; - continue; - } - try { - PhysicalPrinter printer(name, this->default_config()); - printer.file = dir_entry.path().string(); - // Load the preset file, apply preset values on top of defaults. - try { - DynamicPrintConfig config; - //ConfigSubstitutions config_substitutions = config.load_from_ini(printer.file, substitution_rule); - std::map key_values; - std::string reason; - ConfigSubstitutions config_substitutions = config.load_from_json(printer.file, substitution_rule, key_values, reason); - if (! config_substitutions.empty()) - substitutions.push_back({ name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::UserFile, printer.file, std::move(config_substitutions) }); - printer.update_from_config(config); - printer.loaded = true; + // Load all printers found in dir_path. + // Throws an exception on error. + void PhysicalPrinterCollection::load_printers( + const std::string &dir_path, const std::string &subdir, + PresetsConfigSubstitutions &substitutions, ForwardCompatibilitySubstitutionRule substitution_rule) + { + // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, + // see https://github.com/prusa3d/PrusaSlicer/issues/732 + boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred(); + m_dir_path = dir.string(); + std::string errors_cummulative; + // Store the loaded printers into a new vector, otherwise the binary search for already existing presets would be broken. + std::deque printers_loaded; + // BBS: change to json format + for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) + { + std::string file_name = dir_entry.path().filename().string(); + // if (Slic3r::is_ini_file(dir_entry)) { + if (Slic3r::is_json_file(file_name)) + { + // Remove the .json suffix. + std::string name = file_name.erase(file_name.size() - 5); + if (this->find_printer(name, false)) + { + // This happens when there's is a preset (most likely legacy one) with the same name as a system preset + // that's already been loaded from a bundle. + BOOST_LOG_TRIVIAL(warning) << "Printer already present, not loading: " << name; + continue; } - catch (const std::ifstream::failure& err) { - throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); + try + { + PhysicalPrinter printer(name, this->default_config()); + printer.file = dir_entry.path().string(); + // Load the preset file, apply preset values on top of defaults. + try + { + DynamicPrintConfig config; + // ConfigSubstitutions config_substitutions = config.load_from_ini(printer.file, substitution_rule); + std::map key_values; + std::string reason; + ConfigSubstitutions config_substitutions = config.load_from_json(printer.file, substitution_rule, key_values, reason); + if (!config_substitutions.empty()) + substitutions.push_back({name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::UserFile, printer.file, std::move(config_substitutions)}); + printer.update_from_config(config); + printer.loaded = true; + } + catch (const std::ifstream::failure &err) + { + throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); + } + catch (const std::runtime_error &err) + { + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); + } + printers_loaded.emplace_back(printer); } - catch (const std::runtime_error& err) { - throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); + catch (const std::runtime_error &err) + { + errors_cummulative += err.what(); + errors_cummulative += "\n"; } - printers_loaded.emplace_back(printer); - } - catch (const std::runtime_error& err) { - errors_cummulative += err.what(); - errors_cummulative += "\n"; } } + m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end())); + std::sort(m_printers.begin(), m_printers.end()); + if (!errors_cummulative.empty()) + throw Slic3r::RuntimeError(errors_cummulative); } - m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end())); - std::sort(m_printers.begin(), m_printers.end()); - if (!errors_cummulative.empty()) - throw Slic3r::RuntimeError(errors_cummulative); -} -void PhysicalPrinterCollection::load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save/* = false*/) -{ - auto it = this->find_printer_internal(name); - if (it == m_printers.end() || it->name != name) { - // The preset was not found. Create a new preset. - it = m_printers.emplace(it, PhysicalPrinter(name, config)); - } - - it->file = path; - it->config = std::move(config); - it->loaded = true; - if (select) - this->select_printer(*it); - - if (save) - it->save(nullptr); -} - -// if there is saved user presets, contains information about "Print Host upload", -// Create default printers with this presets -// Note! "Print Host upload" options will be cleared after physical printer creations -void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection& printer_presets) -{ -//BBS + void PhysicalPrinterCollection::load_printer(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, bool save /* = false*/) + { + auto it = this->find_printer_internal(name); + if (it == m_printers.end() || it->name != name) + { + // The preset was not found. Create a new preset. + it = m_printers.emplace(it, PhysicalPrinter(name, config)); + } + + it->file = path; + it->config = std::move(config); + it->loaded = true; + if (select) + this->select_printer(*it); + + if (save) + it->save(nullptr); + } + + // if there is saved user presets, contains information about "Print Host upload", + // Create default printers with this presets + // Note! "Print Host upload" options will be cleared after physical printer creations + void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection &printer_presets) + { +// BBS #if 0 int cnt=0; for (Preset& preset: printer_presets) { @@ -3673,271 +4103,285 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti } } #endif -} - -PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool case_sensitive_search) -{ - auto it = this->find_printer_internal(name, case_sensitive_search); - - // Ensure that a temporary copy is returned if the preset found is currently selected. - auto is_equal_name = [name, case_sensitive_search](const std::string& in_name) { - if (case_sensitive_search) - return in_name == name; - return boost::to_lower_copy(in_name) == boost::to_lower_copy(name); - }; - - if (it == m_printers.end() || !is_equal_name(it->name)) - return nullptr; - return &this->printer(it - m_printers.begin()); -} + } -std::deque::iterator PhysicalPrinterCollection::find_printer_internal(const std::string& name, bool case_sensitive_search/* = true*/) -{ - if (case_sensitive_search) - return Slic3r::lower_bound_by_predicate(m_printers.begin(), m_printers.end(), [&name](const auto& l) { return l.name < name; }); + PhysicalPrinter *PhysicalPrinterCollection::find_printer(const std::string &name, bool case_sensitive_search) + { + auto it = this->find_printer_internal(name, case_sensitive_search); - std::string low_name = boost::to_lower_copy(name); + // Ensure that a temporary copy is returned if the preset found is currently selected. + auto is_equal_name = [name, case_sensitive_search](const std::string &in_name) + { + if (case_sensitive_search) + return in_name == name; + return boost::to_lower_copy(in_name) == boost::to_lower_copy(name); + }; - size_t i = 0; - for (const PhysicalPrinter& printer : m_printers) { - if (boost::to_lower_copy(printer.name) == low_name) - break; - i++; + if (it == m_printers.end() || !is_equal_name(it->name)) + return nullptr; + return &this->printer(it - m_printers.begin()); } - if (i == m_printers.size()) - return m_printers.end(); - return m_printers.begin() + i; -} + std::deque::iterator PhysicalPrinterCollection::find_printer_internal(const std::string &name, bool case_sensitive_search /* = true*/) + { + if (case_sensitive_search) + return Slic3r::lower_bound_by_predicate(m_printers.begin(), m_printers.end(), [&name](const auto &l) + { return l.name < name; }); -// Generate a file path from a profile name. Add the ".ini" suffix if it is missing. -std::string PhysicalPrinterCollection::path_from_name(const std::string& new_name) const -{ - //BBS: change to json format - //std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); - std::string file_name = boost::iends_with(new_name, ".json") ? new_name : (new_name + ".json"); - return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); -} + std::string low_name = boost::to_lower_copy(name); -void PhysicalPrinterCollection::save_printer(PhysicalPrinter& edited_printer, const std::string& renamed_from/* = ""*/) -{ - // controll and update preset_names in edited_printer config - edited_printer.update_preset_names_in_config(); - - std::string name = renamed_from.empty() ? edited_printer.name : renamed_from; - // 1) Find the printer with a new_name or create a new one, - // initialize it with the edited config. - auto it = this->find_printer_internal(name); - if (it != m_printers.end() && it->name == name) { - // Printer with the same name found. - // Overwriting an existing preset. - it->config = std::move(edited_printer.config); - it->name = edited_printer.name; - it->preset_names = edited_printer.preset_names; - // sort printers and get new it - std::sort(m_printers.begin(), m_printers.end()); - it = this->find_printer_internal(edited_printer.name); + size_t i = 0; + for (const PhysicalPrinter &printer : m_printers) + { + if (boost::to_lower_copy(printer.name) == low_name) + break; + i++; + } + if (i == m_printers.size()) + return m_printers.end(); + + return m_printers.begin() + i; } - else { - // Creating a new printer. - it = m_printers.emplace(it, edited_printer); + + // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. + std::string PhysicalPrinterCollection::path_from_name(const std::string &new_name) const + { + // BBS: change to json format + // std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); + std::string file_name = boost::iends_with(new_name, ".json") ? new_name : (new_name + ".json"); + return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } - assert(it != m_printers.end()); - // 2) Save printer - PhysicalPrinter& printer = *it; - if (printer.file.empty()) - printer.file = this->path_from_name(printer.name); + void PhysicalPrinterCollection::save_printer(PhysicalPrinter &edited_printer, const std::string &renamed_from /* = ""*/) + { + // controll and update preset_names in edited_printer config + edited_printer.update_preset_names_in_config(); + + std::string name = renamed_from.empty() ? edited_printer.name : renamed_from; + // 1) Find the printer with a new_name or create a new one, + // initialize it with the edited config. + auto it = this->find_printer_internal(name); + if (it != m_printers.end() && it->name == name) + { + // Printer with the same name found. + // Overwriting an existing preset. + it->config = std::move(edited_printer.config); + it->name = edited_printer.name; + it->preset_names = edited_printer.preset_names; + // sort printers and get new it + std::sort(m_printers.begin(), m_printers.end()); + it = this->find_printer_internal(edited_printer.name); + } + else + { + // Creating a new printer. + it = m_printers.emplace(it, edited_printer); + } + assert(it != m_printers.end()); - if (printer.file == this->path_from_name(printer.name)) - printer.save(nullptr); - else - // if printer was renamed, we should rename a file and than save the config - printer.save(printer.file, this->path_from_name(printer.name)); + // 2) Save printer + PhysicalPrinter &printer = *it; + if (printer.file.empty()) + printer.file = this->path_from_name(printer.name); - // update idx_selected - m_idx_selected = it - m_printers.begin(); -} + if (printer.file == this->path_from_name(printer.name)) + printer.save(nullptr); + else + // if printer was renamed, we should rename a file and than save the config + printer.save(printer.file, this->path_from_name(printer.name)); -bool PhysicalPrinterCollection::delete_printer(const std::string& name) -{ - auto it = this->find_printer_internal(name); - if (it == m_printers.end()) - return false; + // update idx_selected + m_idx_selected = it - m_printers.begin(); + } - const PhysicalPrinter& printer = *it; - // Erase the preset file. - boost::nowide::remove(printer.file.c_str()); - m_printers.erase(it); - return true; -} + bool PhysicalPrinterCollection::delete_printer(const std::string &name) + { + auto it = this->find_printer_internal(name); + if (it == m_printers.end()) + return false; -bool PhysicalPrinterCollection::delete_selected_printer() -{ - if (!has_selection()) - return false; - const PhysicalPrinter& printer = this->get_selected_printer(); + const PhysicalPrinter &printer = *it; + // Erase the preset file. + boost::nowide::remove(printer.file.c_str()); + m_printers.erase(it); + return true; + } - // Erase the preset file. - boost::nowide::remove(printer.file.c_str()); - // Remove the preset from the list. - m_printers.erase(m_printers.begin() + m_idx_selected); - // unselect all printers - unselect_printer(); + bool PhysicalPrinterCollection::delete_selected_printer() + { + if (!has_selection()) + return false; + const PhysicalPrinter &printer = this->get_selected_printer(); - return true; -} + // Erase the preset file. + boost::nowide::remove(printer.file.c_str()); + // Remove the preset from the list. + m_printers.erase(m_printers.begin() + m_idx_selected); + // unselect all printers + unselect_printer(); -bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& preset_name) -{ - std::vector printers_for_delete; - for (PhysicalPrinter& printer : m_printers) { - if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) - printers_for_delete.emplace_back(printer.name); - else if (printer.delete_preset(preset_name)) - save_printer(printer); + return true; } - if (!printers_for_delete.empty()) - for (const std::string& printer_name : printers_for_delete) - delete_printer(printer_name); - - unselect_printer(); - return true; -} + bool PhysicalPrinterCollection::delete_preset_from_printers(const std::string &preset_name) + { + std::vector printers_for_delete; + for (PhysicalPrinter &printer : m_printers) + { + if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) + printers_for_delete.emplace_back(printer.name); + else if (printer.delete_preset(preset_name)) + save_printer(printer); + } -// Get list of printers which have more than one preset and "preset_names" preset is one of them -std::vector PhysicalPrinterCollection::get_printers_with_preset(const std::string& preset_name) -{ - std::vector printers; + if (!printers_for_delete.empty()) + for (const std::string &printer_name : printers_for_delete) + delete_printer(printer_name); - for (auto printer : m_printers) { - if (printer.preset_names.size() == 1) - continue; - if (printer.preset_names.find(preset_name) != printer.preset_names.end()) - printers.emplace_back(printer.name); + unselect_printer(); + return true; } - return printers; -} + // Get list of printers which have more than one preset and "preset_names" preset is one of them + std::vector PhysicalPrinterCollection::get_printers_with_preset(const std::string &preset_name) + { + std::vector printers; -// Get list of printers which has only "preset_names" preset -std::vector PhysicalPrinterCollection::get_printers_with_only_preset(const std::string& preset_name) -{ - std::vector printers; + for (auto printer : m_printers) + { + if (printer.preset_names.size() == 1) + continue; + if (printer.preset_names.find(preset_name) != printer.preset_names.end()) + printers.emplace_back(printer.name); + } - for (auto printer : m_printers) - if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) - printers.emplace_back(printer.name); + return printers; + } - return printers; -} + // Get list of printers which has only "preset_names" preset + std::vector PhysicalPrinterCollection::get_printers_with_only_preset(const std::string &preset_name) + { + std::vector printers; -std::string PhysicalPrinterCollection::get_selected_full_printer_name() const -{ - return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); -} + for (auto printer : m_printers) + if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) + printers.emplace_back(printer.name); -void PhysicalPrinterCollection::select_printer(const std::string& full_name) -{ - std::string printer_name = PhysicalPrinter::get_short_name(full_name); - auto it = this->find_printer_internal(printer_name); - if (it == m_printers.end()) { - unselect_printer(); - return; + return printers; } - // update idx_selected - m_idx_selected = it - m_printers.begin(); + std::string PhysicalPrinterCollection::get_selected_full_printer_name() const + { + return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); + } - // update name of the currently selected preset - if (printer_name == full_name) - // use first preset in the list - m_selected_preset = *it->preset_names.begin(); - else - m_selected_preset = it->get_preset_name(full_name); -} + void PhysicalPrinterCollection::select_printer(const std::string &full_name) + { + std::string printer_name = PhysicalPrinter::get_short_name(full_name); + auto it = this->find_printer_internal(printer_name); + if (it == m_printers.end()) + { + unselect_printer(); + return; + } -void PhysicalPrinterCollection::select_printer(const std::string& printer_name, const std::string& preset_name) -{ - if (preset_name.empty()) - return select_printer(printer_name); - return select_printer(printer_name + PhysicalPrinter::separator() + preset_name); -} + // update idx_selected + m_idx_selected = it - m_printers.begin(); -void PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer) -{ - return select_printer(printer.name); -} + // update name of the currently selected preset + if (printer_name == full_name) + // use first preset in the list + m_selected_preset = *it->preset_names.begin(); + else + m_selected_preset = it->get_preset_name(full_name); + } -bool PhysicalPrinterCollection::has_selection() const -{ - return m_idx_selected != size_t(-1); -} + void PhysicalPrinterCollection::select_printer(const std::string &printer_name, const std::string &preset_name) + { + if (preset_name.empty()) + return select_printer(printer_name); + return select_printer(printer_name + PhysicalPrinter::separator() + preset_name); + } -void PhysicalPrinterCollection::unselect_printer() -{ - m_idx_selected = size_t(-1); - m_selected_preset.clear(); -} + void PhysicalPrinterCollection::select_printer(const PhysicalPrinter &printer) + { + return select_printer(printer.name); + } -bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIterator it, const std::string& preset_name) const -{ - return m_idx_selected == size_t(it - m_printers.begin()) && - m_selected_preset == preset_name; -} - - -namespace PresetUtils { - const VendorProfile::PrinterModel* system_printer_model(const Preset &preset) - { - const VendorProfile::PrinterModel *out = nullptr; - if (preset.vendor != nullptr) { - auto *printer_model = preset.config.opt("printer_model"); - if (printer_model != nullptr && ! printer_model->value.empty()) { - auto it = std::find_if(preset.vendor->models.begin(), preset.vendor->models.end(), [printer_model](const VendorProfile::PrinterModel &pm) { return pm.id == printer_model->value; }); - if (it != preset.vendor->models.end()) - out = &(*it); - } - } - return out; - } - - std::string system_printer_bed_model(const Preset& preset) - { - std::string out; - const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset); - if (pm != nullptr && !pm->bed_model.empty()) { - out = Slic3r::data_dir() + "/vendor/" + preset.vendor->id + "/" + pm->bed_model; - if (!boost::filesystem::exists(boost::filesystem::path(out))) - out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_model; - } - return out; + bool PhysicalPrinterCollection::has_selection() const + { + return m_idx_selected != size_t(-1); } - std::string system_printer_bed_texture(const Preset& preset) + void PhysicalPrinterCollection::unselect_printer() { - std::string out; - const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset); - if (pm != nullptr && !pm->bed_texture.empty()) { - out = Slic3r::data_dir() + "/vendor/" + preset.vendor->id + "/" + pm->bed_texture; - if (!boost::filesystem::exists(boost::filesystem::path(out))) - out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture; - } - return out; + m_idx_selected = size_t(-1); + m_selected_preset.clear(); } - std::string system_printer_hotend_model(const Preset& preset) + bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIterator it, const std::string &preset_name) const { - std::string out; - const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset); - if (pm != nullptr && !pm->hotend_model.empty()) { - out = Slic3r::data_dir() + "/vendor/" + preset.vendor->id + "/" + pm->hotend_model; - if (!boost::filesystem::exists(boost::filesystem::path(out))) - out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->hotend_model; - } - return out; + return m_idx_selected == size_t(it - m_printers.begin()) && + m_selected_preset == preset_name; } -} // namespace PresetUtils + + namespace PresetUtils + { + const VendorProfile::PrinterModel *system_printer_model(const Preset &preset) + { + const VendorProfile::PrinterModel *out = nullptr; + if (preset.vendor != nullptr) + { + auto *printer_model = preset.config.opt("printer_model"); + if (printer_model != nullptr && !printer_model->value.empty()) + { + auto it = std::find_if(preset.vendor->models.begin(), preset.vendor->models.end(), [printer_model](const VendorProfile::PrinterModel &pm) + { return pm.id == printer_model->value; }); + if (it != preset.vendor->models.end()) + out = &(*it); + } + } + return out; + } + + std::string system_printer_bed_model(const Preset &preset) + { + std::string out; + const VendorProfile::PrinterModel *pm = PresetUtils::system_printer_model(preset); + if (pm != nullptr && !pm->bed_model.empty()) + { + out = Slic3r::data_dir() + "/vendor/" + preset.vendor->id + "/" + pm->bed_model; + if (!boost::filesystem::exists(boost::filesystem::path(out))) + out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_model; + } + return out; + } + + std::string system_printer_bed_texture(const Preset &preset) + { + std::string out; + const VendorProfile::PrinterModel *pm = PresetUtils::system_printer_model(preset); + if (pm != nullptr && !pm->bed_texture.empty()) + { + out = Slic3r::data_dir() + "/vendor/" + preset.vendor->id + "/" + pm->bed_texture; + if (!boost::filesystem::exists(boost::filesystem::path(out))) + out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture; + } + return out; + } + + std::string system_printer_hotend_model(const Preset &preset) + { + std::string out; + const VendorProfile::PrinterModel *pm = PresetUtils::system_printer_model(preset); + if (pm != nullptr && !pm->hotend_model.empty()) + { + out = Slic3r::data_dir() + "/vendor/" + preset.vendor->id + "/" + pm->hotend_model; + if (!boost::filesystem::exists(boost::filesystem::path(out))) + out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->hotend_model; + } + return out; + } + } // namespace PresetUtils } // namespace Slic3r diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 2b12953bb8..168753dbfb 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -31,7 +31,7 @@ #include "format.hpp" -//BBS: add json support +// BBS: add json support #include "nlohmann/json.hpp" #include "GCode/ConflictChecker.hpp" @@ -44,678 +44,628 @@ using namespace nlohmann; // Mark string for localization and translate. #define L(s) Slic3r::I18N::translate(s) -namespace Slic3r { +namespace Slic3r +{ -template class PrintState; -template class PrintState; + template class PrintState; + template class PrintState; -PrintRegion::PrintRegion(const PrintRegionConfig &config) : PrintRegion(config, config.hash()) {} -PrintRegion::PrintRegion(PrintRegionConfig &&config) : PrintRegion(std::move(config), config.hash()) {} + PrintRegion::PrintRegion(const PrintRegionConfig &config) : PrintRegion(config, config.hash()) {} + PrintRegion::PrintRegion(PrintRegionConfig &&config) : PrintRegion(std::move(config), config.hash()) {} -//BBS -float Print::min_skirt_length = 0; + // BBS + float Print::min_skirt_length = 0; -void Print::clear() -{ - std::scoped_lock lock(this->state_mutex()); - // The following call should stop background processing if it is running. - this->invalidate_all_steps(); - for (PrintObject *object : m_objects) - delete object; - m_objects.clear(); - m_print_regions.clear(); - m_model.clear_objects(); - m_statistics_by_extruder_count.clear(); - m_nozzle_group_result.reset(); -} + void Print::clear() + { + std::scoped_lock lock(this->state_mutex()); + // The following call should stop background processing if it is running. + this->invalidate_all_steps(); + for (PrintObject *object : m_objects) + delete object; + m_objects.clear(); + m_print_regions.clear(); + m_model.clear_objects(); + m_statistics_by_extruder_count.clear(); + m_nozzle_group_result.reset(); + } -bool Print::has_tpu_filament() const -{ - for (unsigned int filament_id : m_wipe_tower_data.tool_ordering.all_extruders()) { - std::string filament_name = m_config.filament_type.get_at(filament_id); - if (filament_name == "TPU") { - return true; + bool Print::has_tpu_filament() const + { + for (unsigned int filament_id : m_wipe_tower_data.tool_ordering.all_extruders()) + { + std::string filament_name = m_config.filament_type.get_at(filament_id); + if (filament_name == "TPU") + { + return true; + } } + return false; } - return false; -} -// Called by Print::apply(). -// This method only accepts PrintConfig option keys. -bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* new_config */, const std::vector &opt_keys) -{ - if (opt_keys.empty()) - return false; + // Called by Print::apply(). + // This method only accepts PrintConfig option keys. + bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* new_config */, const std::vector &opt_keys) + { + if (opt_keys.empty()) + return false; - // Cache the plenty of parameters, which influence the G-code generator only, - // or they are only notes not influencing the generated G-code. - static std::unordered_set steps_gcode = { - //BBS - "additional_cooling_fan_speed", - "reduce_crossing_wall", - "max_travel_detour_distance", - "avoid_crossing_wall_includes_support", - "printable_area", - //BBS: add bed_exclude_area - "bed_exclude_area", - "thumbnail_size", - "before_layer_change_gcode", - "enable_pressure_advance", - "pressure_advance", - "enable_overhang_bridge_fan" - "overhang_fan_speed", - "pre_start_fan_time", - "overhang_fan_threshold", - "overhang_threshold_participating_cooling", - "slow_down_for_layer_cooling", - "no_slow_down_for_cooling_on_outwalls", - "deretraction_speed", - "close_fan_the_first_x_layers", - "first_x_layer_fan_speed", - "machine_end_gcode", - "printing_by_object_gcode", - "filament_end_gcode", - "post_process", - "extruder_clearance_height_to_rod", - "extruder_clearance_height_to_lid", - "extruder_clearance_dist_to_rod", - "nozzle_height", - "extruder_clearance_max_radius", - "extruder_colour", - "extruder_offset", - "filament_flow_ratio", - "reduce_fan_stop_start_freq", - "fan_cooling_layer_time", - "full_fan_speed_layer", - "filament_colour", - "default_filament_colour", - "filament_diameter", - "volumetric_speed_coefficients", - "filament_density", - "filament_cost", - "initial_layer_acceleration", - "outer_wall_acceleration", - "top_surface_acceleration", - "accel_to_decel_enable", - "accel_to_decel_factor", - // BBS - "supertack_plate_temp_initial_layer", - "cool_plate_temp_initial_layer", - "eng_plate_temp_initial_layer", - "hot_plate_temp_initial_layer", - "textured_plate_temp_initial_layer", - "gcode_add_line_number", - "layer_change_gcode", - "time_lapse_gcode", - "wrapping_detection_gcode", - "fan_min_speed", - "fan_max_speed", - "printable_height", - "slow_down_min_speed", + // Cache the plenty of parameters, which influence the G-code generator only, + // or they are only notes not influencing the generated G-code. + static std::unordered_set steps_gcode = { + // BBS + "additional_cooling_fan_speed", + "reduce_crossing_wall", + "max_travel_detour_distance", + "avoid_crossing_wall_includes_support", + "printable_area", + // BBS: add bed_exclude_area + "bed_exclude_area", + "thumbnail_size", + "before_layer_change_gcode", + "enable_pressure_advance", + "pressure_advance", + "enable_overhang_bridge_fan" + "overhang_fan_speed", + "pre_start_fan_time", + "overhang_fan_threshold", + "overhang_threshold_participating_cooling", + "slow_down_for_layer_cooling", + "no_slow_down_for_cooling_on_outwalls", + "deretraction_speed", + "close_fan_the_first_x_layers", + "first_x_layer_fan_speed", + "machine_end_gcode", + "printing_by_object_gcode", + "filament_end_gcode", + "post_process", + "extruder_clearance_height_to_rod", + "extruder_clearance_height_to_lid", + "extruder_clearance_dist_to_rod", + "nozzle_height", + "extruder_clearance_max_radius", + "extruder_colour", + "extruder_offset", + "filament_flow_ratio", + "reduce_fan_stop_start_freq", + "fan_cooling_layer_time", + "full_fan_speed_layer", + "filament_colour", + "default_filament_colour", + "filament_diameter", + "volumetric_speed_coefficients", + "filament_density", + "filament_cost", + "initial_layer_acceleration", + "outer_wall_acceleration", + "top_surface_acceleration", + "accel_to_decel_enable", + "accel_to_decel_factor", + // BBS + "supertack_plate_temp_initial_layer", + "cool_plate_temp_initial_layer", + "eng_plate_temp_initial_layer", + "hot_plate_temp_initial_layer", + "textured_plate_temp_initial_layer", + "gcode_add_line_number", + "layer_change_gcode", + "time_lapse_gcode", + "wrapping_detection_gcode", + "fan_min_speed", + "fan_max_speed", + "printable_height", + "slow_down_min_speed", #ifdef HAS_PRESSURE_EQUALIZER - "max_volumetric_extrusion_rate_slope_positive", - "max_volumetric_extrusion_rate_slope_negative", + "max_volumetric_extrusion_rate_slope_positive", + "max_volumetric_extrusion_rate_slope_negative", #endif /* HAS_PRESSURE_EQUALIZER */ - "reduce_infill_retraction", - "filename_format", - "retraction_minimum_travel", - "retract_before_wipe", - "retract_when_changing_layer", - "retraction_length", - "retract_length_toolchange", - "z_hop", - "filament_retract_length_nc", - "retract_restart_extra", - "retract_restart_extra_toolchange", - "retraction_speed", - "retract_lift_above", - "retract_lift_below", - "slow_down_layer_time", - "standby_temperature_delta", - "machine_start_gcode", - "filament_start_gcode", - "change_filament_gcode", - "wipe", - // BBS - "wipe_distance", - "curr_bed_type", - "nozzle_volume", - "chamber_temperatures", - "required_nozzle_HRC", - "upward_compatible_machine", - "is_infill_first", - //OrcaSlicer - "seam_gap", - "wipe_speed" - "default_jerk", - "outer_wall_jerk", - "inner_wall_jerk", - "infill_jerk", - "top_surface_jerk", - "initial_layer_jerk", - "travel_jerk", - "inner_wall_acceleration", - "sparse_infill_acceleration", - "exclude_object", - "use_relative_e_distances", - "activate_air_filtration", - "during_print_exhaust_fan_speed", - "complete_print_exhaust_fan_speed", - "use_firmware_retraction", - "enable_long_retraction_when_cut", - "long_retractions_when_cut", - "retraction_distances_when_cut", - "filament_long_retractions_when_cut", - "filament_retraction_distances_when_cut", - "grab_length", - "bed_temperature_formula", - "filament_notes", - "process_notes", - "printer_notes", - "filament_velocity_adaptation_factor" - }; - - static std::unordered_set steps_ignore; - - std::vector steps; - std::vector osteps; - bool invalidated = false; - - for (const t_config_option_key &opt_key : opt_keys) { - if (steps_gcode.find(opt_key) != steps_gcode.end()) { - // These options only affect G-code export or they are just notes without influence on the generated G-code, - // so there is nothing to invalidate. - steps.emplace_back(psGCodeExport); - } else if (steps_ignore.find(opt_key) != steps_ignore.end()) { - // These steps have no influence on the G-code whatsoever. Just ignore them. - } else if ( - opt_key == "skirt_loops" - || opt_key == "skirt_height" - || opt_key == "draft_shield" - || opt_key == "skirt_distance" - || opt_key == "ooze_prevention" - || opt_key == "wipe_tower_x" - || opt_key == "wipe_tower_y" - || opt_key == "wipe_tower_rotation_angle") { - steps.emplace_back(psSkirtBrim); - } else if ( - opt_key == "initial_layer_print_height" - || opt_key == "nozzle_diameter" - || opt_key == "resolution" - || opt_key == "precise_z_height" - || opt_key == "filament_shrink" - || opt_key == "enable_circle_compensation" - || opt_key == "circle_compensation_manual_offset" - || opt_key == "circle_compensation_speed" - || opt_key == "counter_coef_1" - || opt_key == "counter_coef_2" - || opt_key == "counter_coef_3" - || opt_key == "hole_coef_1" - || opt_key == "hole_coef_2" - || opt_key == "hole_coef_3" - || opt_key == "counter_limit_min" - || opt_key == "counter_limit_max" - || opt_key == "hole_limit_min" - || opt_key == "hole_limit_max" - || opt_key == "diameter_limit" - // Spiral Vase forces different kind of slicing than the normal model: - // In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer. - // Therefore toggling the Spiral Vase on / off requires complete reslicing. - || opt_key == "spiral_mode") { - osteps.emplace_back(posSlice); - } else if ( - opt_key == "print_sequence" - || opt_key == "filament_type" - || opt_key == "nozzle_temperature_initial_layer" - || opt_key == "filament_minimal_purge_on_wipe_tower" - || opt_key == "filament_max_volumetric_speed" - || opt_key == "filament_adaptive_volumetric_speed" - || opt_key == "filament_ramming_volumetric_speed" - || opt_key == "filament_ramming_volumetric_speed_nc" - || opt_key == "gcode_flavor" - || opt_key == "single_extruder_multi_material" - || opt_key == "nozzle_temperature" - || opt_key == "filament_pre_cooling_temperature" - || opt_key == "filament_pre_cooling_temperature_nc" - || opt_key == "filament_ramming_travel_time" - || opt_key == "filament_ramming_travel_time_nc" + "reduce_infill_retraction", + "filename_format", + "retraction_minimum_travel", + "retract_before_wipe", + "retract_when_changing_layer", + "retraction_length", + "retract_length_toolchange", + "z_hop", + "filament_retract_length_nc", + "retract_restart_extra", + "retract_restart_extra_toolchange", + "retraction_speed", + "retract_lift_above", + "retract_lift_below", + "slow_down_layer_time", + "standby_temperature_delta", + "machine_start_gcode", + "filament_start_gcode", + "change_filament_gcode", + "wipe", // BBS - || opt_key == "supertack_plate_temp" - || opt_key == "cool_plate_temp" - || opt_key == "eng_plate_temp" - || opt_key == "hot_plate_temp" - || opt_key == "textured_plate_temp" - || opt_key == "enable_prime_tower" - || opt_key == "enable_wrapping_detection" - || opt_key == "prime_tower_enable_framework" - || opt_key == "prime_tower_width" - || opt_key == "prime_tower_max_speed" - || opt_key == "prime_tower_lift_speed" - || opt_key == "prime_tower_lift_height" - || opt_key == "prime_tower_brim_width" - || opt_key == "prime_tower_skip_points" - || opt_key == "prime_tower_flat_ironing" - || opt_key == "prime_tower_rib_wall" - || opt_key == "prime_tower_extra_rib_length" - || opt_key == "prime_tower_rib_width" - || opt_key == "prime_tower_fillet_wall" - || opt_key == "first_layer_print_sequence" - || opt_key == "other_layers_print_sequence" - || opt_key == "other_layers_print_sequence_nums" - || opt_key == "extruder_ams_count" - || opt_key == "extruder_nozzle_stats" - || opt_key == "filament_cooling_before_tower" - || opt_key == "prime_volume_mode" - || opt_key == "filament_map_mode" - || opt_key == "filament_map" - || opt_key == "filament_nozzle_map" - || opt_key == "filament_volume_map" - || opt_key == "filament_adhesiveness_category" - //|| opt_key == "wipe_tower_bridging" - || opt_key == "wipe_tower_no_sparse_layers" - || opt_key == "flush_volumes_matrix" - || opt_key == "filament_prime_volume" - || opt_key == "filament_prime_volume_nc" - || opt_key == "flush_into_infill" - || opt_key == "flush_into_support" - || opt_key == "initial_layer_infill_speed" - || opt_key == "travel_speed" - || opt_key == "travel_speed_z" - || opt_key == "initial_layer_speed" - || opt_key == "default_acceleration" - || opt_key == "travel_acceleration" - || opt_key == "initial_layer_travel_acceleration") { - //|| opt_key == "z_offset") { - steps.emplace_back(psWipeTower); - steps.emplace_back(psSkirtBrim); - } else if (opt_key == "filament_soluble" - || opt_key == "filament_is_support" - || opt_key == "filament_printable" - || opt_key == "impact_strength_z" - || opt_key == "filament_scarf_seam_type" - || opt_key == "filament_scarf_height" - || opt_key == "filament_scarf_gap" - || opt_key == "filament_scarf_length" - || opt_key == "filament_change_length" - || opt_key == "independent_support_layer_height" - || opt_key == "top_z_overrides_xy_distance" - || opt_key == "filament_change_length_nc") { - steps.emplace_back(psWipeTower); - // Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers. - // Thus switching between soluble / non-soluble interface layer material may require recalculation of supports. - //FIXME Killing supports on any change of "filament_soluble" is rough. We should check for each object whether that is necessary. - osteps.emplace_back(posSupportMaterial); - osteps.emplace_back(posSimplifySupportPath); - } else if ( - opt_key == "initial_layer_line_width" - || opt_key == "min_layer_height" - || opt_key == "max_layer_height" - //|| opt_key == "resolution" - //BBS: when enable arc fitting, we must re-generate perimeter - || opt_key == "enable_arc_fitting" - || opt_key == "wall_sequence" - || opt_key == "z_direction_outwall_speed_continuous" - || opt_key == "override_filament_scarf_seam_setting" - || opt_key == "seam_slope_type" - || opt_key == "seam_slope_start_height" - || opt_key == "seam_slope_gap" - || opt_key == "seam_slope_min_length" - || opt_key == "embedding_wall_into_infill") { - osteps.emplace_back(posPerimeters); - osteps.emplace_back(posInfill); - osteps.emplace_back(posSupportMaterial); - osteps.emplace_back(posSimplifyWall); - osteps.emplace_back(posSimplifyInfill); - osteps.emplace_back(posSimplifySupportPath); - steps.emplace_back(psSkirtBrim); - } - else if (opt_key == "z_hop_types") { - osteps.emplace_back(posDetectOverhangsForLift); - } else { - // for legacy, if we can't handle this option let's invalidate all steps - //FIXME invalidate all steps of all objects as well? - invalidated |= this->invalidate_all_steps(); - // Continue with the other opt_keys to possibly invalidate any object specific steps. + "wipe_distance", + "curr_bed_type", + "nozzle_volume", + "chamber_temperatures", + "required_nozzle_HRC", + "upward_compatible_machine", + "is_outer_second", + "is_infill_first", + // OrcaSlicer + "seam_gap", + "wipe_speed" + "default_jerk", + "outer_wall_jerk", + "inner_wall_jerk", + "infill_jerk", + "top_surface_jerk", + "initial_layer_jerk", + "travel_jerk", + "inner_wall_acceleration", + "sparse_infill_acceleration", + "exclude_object", + "use_relative_e_distances", + "activate_air_filtration", + "during_print_exhaust_fan_speed", + "complete_print_exhaust_fan_speed", + "use_firmware_retraction", + "enable_long_retraction_when_cut", + "long_retractions_when_cut", + "retraction_distances_when_cut", + "filament_long_retractions_when_cut", + "filament_retraction_distances_when_cut", + "grab_length", + "bed_temperature_formula", + "filament_notes", + "process_notes", + "printer_notes", + "filament_velocity_adaptation_factor"}; + + static std::unordered_set steps_ignore; + + std::vector steps; + std::vector osteps; + bool invalidated = false; + + for (const t_config_option_key &opt_key : opt_keys) + { + if (steps_gcode.find(opt_key) != steps_gcode.end()) + { + // These options only affect G-code export or they are just notes without influence on the generated G-code, + // so there is nothing to invalidate. + steps.emplace_back(psGCodeExport); + } + else if (steps_ignore.find(opt_key) != steps_ignore.end()) + { + // These steps have no influence on the G-code whatsoever. Just ignore them. + } + else if ( + opt_key == "skirt_loops" || opt_key == "skirt_height" || opt_key == "draft_shield" || opt_key == "skirt_distance" || opt_key == "ooze_prevention" || opt_key == "wipe_tower_x" || opt_key == "wipe_tower_y" || opt_key == "wipe_tower_rotation_angle") + { + steps.emplace_back(psSkirtBrim); + } + else if ( + opt_key == "initial_layer_print_height" || opt_key == "nozzle_diameter" || opt_key == "resolution" || opt_key == "precise_z_height" || opt_key == "filament_shrink" || opt_key == "enable_circle_compensation" || opt_key == "circle_compensation_manual_offset" || opt_key == "circle_compensation_speed" || opt_key == "counter_coef_1" || opt_key == "counter_coef_2" || opt_key == "counter_coef_3" || opt_key == "hole_coef_1" || opt_key == "hole_coef_2" || opt_key == "hole_coef_3" || opt_key == "counter_limit_min" || opt_key == "counter_limit_max" || opt_key == "hole_limit_min" || opt_key == "hole_limit_max" || opt_key == "diameter_limit" + // Spiral Vase forces different kind of slicing than the normal model: + // In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer. + // Therefore toggling the Spiral Vase on / off requires complete reslicing. + || opt_key == "spiral_mode") + { + osteps.emplace_back(posSlice); + } + else if ( + opt_key == "print_sequence" || opt_key == "filament_type" || opt_key == "nozzle_temperature_initial_layer" || opt_key == "filament_minimal_purge_on_wipe_tower" || opt_key == "filament_max_volumetric_speed" || opt_key == "filament_adaptive_volumetric_speed" || opt_key == "filament_ramming_volumetric_speed" || opt_key == "filament_ramming_volumetric_speed_nc" || opt_key == "gcode_flavor" || opt_key == "single_extruder_multi_material" || opt_key == "nozzle_temperature" || opt_key == "filament_pre_cooling_temperature" || opt_key == "filament_pre_cooling_temperature_nc" || opt_key == "filament_ramming_travel_time" || opt_key == "filament_ramming_travel_time_nc" + // BBS + || opt_key == "supertack_plate_temp" || opt_key == "cool_plate_temp" || opt_key == "eng_plate_temp" || opt_key == "hot_plate_temp" || opt_key == "textured_plate_temp" || opt_key == "enable_prime_tower" || opt_key == "enable_wrapping_detection" || opt_key == "prime_tower_enable_framework" || opt_key == "prime_tower_width" || opt_key == "prime_tower_max_speed" || opt_key == "prime_tower_lift_speed" || opt_key == "prime_tower_lift_height" || opt_key == "prime_tower_brim_width" || opt_key == "prime_tower_skip_points" || opt_key == "prime_tower_flat_ironing" || opt_key == "prime_tower_rib_wall" || opt_key == "prime_tower_extra_rib_length" || opt_key == "prime_tower_rib_width" || opt_key == "prime_tower_fillet_wall" || opt_key == "first_layer_print_sequence" || opt_key == "other_layers_print_sequence" || opt_key == "other_layers_print_sequence_nums" || opt_key == "extruder_ams_count" || opt_key == "extruder_nozzle_stats" || opt_key == "filament_cooling_before_tower" || opt_key == "prime_volume_mode" || opt_key == "filament_map_mode" || opt_key == "filament_map" || opt_key == "filament_nozzle_map" || opt_key == "filament_volume_map" || opt_key == "filament_adhesiveness_category" + //|| opt_key == "wipe_tower_bridging" + || opt_key == "wipe_tower_no_sparse_layers" || opt_key == "flush_volumes_matrix" || opt_key == "filament_prime_volume" || opt_key == "filament_prime_volume_nc" || opt_key == "flush_into_infill" || opt_key == "flush_into_support" || opt_key == "initial_layer_infill_speed" || opt_key == "travel_speed" || opt_key == "travel_speed_z" || opt_key == "initial_layer_speed" || opt_key == "default_acceleration" || opt_key == "travel_acceleration" || opt_key == "initial_layer_travel_acceleration") + { + //|| opt_key == "z_offset") { + steps.emplace_back(psWipeTower); + steps.emplace_back(psSkirtBrim); + } + else if (opt_key == "filament_soluble" || opt_key == "filament_is_support" || opt_key == "filament_printable" || opt_key == "impact_strength_z" || opt_key == "filament_scarf_seam_type" || opt_key == "filament_scarf_height" || opt_key == "filament_scarf_gap" || opt_key == "filament_scarf_length" || opt_key == "filament_change_length" || opt_key == "independent_support_layer_height" || opt_key == "top_z_overrides_xy_distance" || opt_key == "filament_change_length_nc") + { + steps.emplace_back(psWipeTower); + // Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers. + // Thus switching between soluble / non-soluble interface layer material may require recalculation of supports. + // FIXME Killing supports on any change of "filament_soluble" is rough. We should check for each object whether that is necessary. + osteps.emplace_back(posSupportMaterial); + osteps.emplace_back(posSimplifySupportPath); + } + else if ( + opt_key == "initial_layer_line_width" || opt_key == "min_layer_height" || opt_key == "max_layer_height" + //|| opt_key == "resolution" + // BBS: when enable arc fitting, we must re-generate perimeter + || opt_key == "enable_arc_fitting" || opt_key == "wall_sequence" || opt_key == "z_direction_outwall_speed_continuous" || opt_key == "override_filament_scarf_seam_setting" || opt_key == "seam_slope_type" || opt_key == "seam_slope_start_height" || opt_key == "seam_slope_gap" || opt_key == "seam_slope_min_length" || opt_key == "embedding_wall_into_infill") + { + osteps.emplace_back(posPerimeters); + osteps.emplace_back(posInfill); + osteps.emplace_back(posSupportMaterial); + osteps.emplace_back(posSimplifyWall); + osteps.emplace_back(posSimplifyInfill); + osteps.emplace_back(posSimplifySupportPath); + steps.emplace_back(psSkirtBrim); + } + else if (opt_key == "z_hop_types") + { + osteps.emplace_back(posDetectOverhangsForLift); + } + else + { + // for legacy, if we can't handle this option let's invalidate all steps + // FIXME invalidate all steps of all objects as well? + invalidated |= this->invalidate_all_steps(); + // Continue with the other opt_keys to possibly invalidate any object specific steps. + } } - } - sort_remove_duplicates(steps); - for (PrintStep step : steps) - invalidated |= this->invalidate_step(step); - sort_remove_duplicates(osteps); - for (PrintObjectStep ostep : osteps) - for (PrintObject *object : m_objects) - invalidated |= object->invalidate_step(ostep); + sort_remove_duplicates(steps); + for (PrintStep step : steps) + invalidated |= this->invalidate_step(step); + sort_remove_duplicates(osteps); + for (PrintObjectStep ostep : osteps) + for (PrintObject *object : m_objects) + invalidated |= object->invalidate_step(ostep); - return invalidated; -} + return invalidated; + } -void Print::set_calib_params(const Calib_Params ¶ms) -{ - m_calib_params = params; -} + void Print::set_calib_params(const Calib_Params ¶ms) + { + m_calib_params = params; + } -bool Print::invalidate_step(PrintStep step) -{ - bool invalidated = Inherited::invalidate_step(step); - // Propagate to dependent steps. - if (step != psGCodeExport) - invalidated |= Inherited::invalidate_step(psGCodeExport); - return invalidated; -} + bool Print::invalidate_step(PrintStep step) + { + bool invalidated = Inherited::invalidate_step(step); + // Propagate to dependent steps. + if (step != psGCodeExport) + invalidated |= Inherited::invalidate_step(psGCodeExport); + return invalidated; + } -// returns true if an object step is done on all objects -// and there's at least one object -bool Print::is_step_done(PrintObjectStep step) const -{ - if (m_objects.empty()) - return false; - std::scoped_lock lock(this->state_mutex()); - for (const PrintObject *object : m_objects) - if (! object->is_step_done_unguarded(step)) + // returns true if an object step is done on all objects + // and there's at least one object + bool Print::is_step_done(PrintObjectStep step) const + { + if (m_objects.empty()) return false; - return true; -} + std::scoped_lock lock(this->state_mutex()); + for (const PrintObject *object : m_objects) + if (!object->is_step_done_unguarded(step)) + return false; + return true; + } -// returns 0-based indices of used extruders -std::vector Print::object_extruders() const -{ - std::vector extruders; - extruders.reserve(m_print_regions.size() * m_objects.size() * 3); - // BBS + // returns 0-based indices of used extruders + std::vector Print::object_extruders() const + { + std::vector extruders; + extruders.reserve(m_print_regions.size() * m_objects.size() * 3); + // BBS #if 0 for (const PrintObject *object : m_objects) for (const PrintRegion ®ion : object->all_regions()) region.collect_object_printing_extruders(*this, extruders); #else - for (const PrintObject* object : m_objects) { - const ModelObject* mo = object->model_object(); - for (const ModelVolume* mv : mo->volumes) { - std::vector volume_extruders = mv->get_extruders(); - for (int extruder : volume_extruders) { - assert(extruder > 0); - extruders.push_back(extruder - 1); + for (const PrintObject *object : m_objects) + { + const ModelObject *mo = object->model_object(); + for (const ModelVolume *mv : mo->volumes) + { + std::vector volume_extruders = mv->get_extruders(); + for (int extruder : volume_extruders) + { + assert(extruder > 0); + extruders.push_back(extruder - 1); + } } - } - // layer range - for (auto layer_range : mo->layer_config_ranges) { - if (layer_range.second.has("extruder")) { - //BBS: actually when user doesn't change filament by height range(value is default 0), height range should not save key "extruder". - //Don't know why height range always save key "extruder" because of no change(should only save difference)... - //Add protection here to avoid overflow - auto value = layer_range.second.option("extruder")->getInt(); - if (value > 0) - extruders.push_back(value - 1); + // layer range + for (auto layer_range : mo->layer_config_ranges) + { + if (layer_range.second.has("extruder")) + { + // BBS: actually when user doesn't change filament by height range(value is default 0), height range should not save key "extruder". + // Don't know why height range always save key "extruder" because of no change(should only save difference)... + // Add protection here to avoid overflow + auto value = layer_range.second.option("extruder")->getInt(); + if (value > 0) + extruders.push_back(value - 1); + } } } - } #endif - sort_remove_duplicates(extruders); - return extruders; -} + sort_remove_duplicates(extruders); + return extruders; + } -// returns 0-based indices of used extruders -std::vector Print::support_material_extruders() const -{ - std::vector extruders; - bool support_uses_current_extruder = false; - // BBS - auto num_extruders = (unsigned int)m_config.filament_diameter.size(); + // returns 0-based indices of used extruders + std::vector Print::support_material_extruders() const + { + std::vector extruders; + bool support_uses_current_extruder = false; + // BBS + auto num_extruders = (unsigned int)m_config.filament_diameter.size(); - for (PrintObject *object : m_objects) { - if (object->has_support_material()) { - assert(object->config().support_filament >= 0); - if (object->config().support_filament == 0) - support_uses_current_extruder = true; - else { - unsigned int i = (unsigned int)object->config().support_filament - 1; - extruders.emplace_back((i >= num_extruders) ? 0 : i); - } - assert(object->config().support_interface_filament >= 0); - if (object->config().support_interface_filament == 0) - support_uses_current_extruder = true; - else { - unsigned int i = (unsigned int)object->config().support_interface_filament - 1; - extruders.emplace_back((i >= num_extruders) ? 0 : i); + for (PrintObject *object : m_objects) + { + if (object->has_support_material()) + { + assert(object->config().support_filament >= 0); + if (object->config().support_filament == 0) + support_uses_current_extruder = true; + else + { + unsigned int i = (unsigned int)object->config().support_filament - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } + assert(object->config().support_interface_filament >= 0); + if (object->config().support_interface_filament == 0) + support_uses_current_extruder = true; + else + { + unsigned int i = (unsigned int)object->config().support_interface_filament - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } } } - } - if (support_uses_current_extruder) - // Add all object extruders to the support extruders as it is not know which one will be used to print supports. - append(extruders, this->object_extruders()); + if (support_uses_current_extruder) + // Add all object extruders to the support extruders as it is not know which one will be used to print supports. + append(extruders, this->object_extruders()); - sort_remove_duplicates(extruders); - return extruders; -} + sort_remove_duplicates(extruders); + return extruders; + } -// returns 0-based indices of used extruders -std::vector Print::extruders(bool conside_custom_gcode) const -{ - std::vector extruders = this->object_extruders(); - append(extruders, this->support_material_extruders()); + // returns 0-based indices of used extruders + std::vector Print::extruders(bool conside_custom_gcode) const + { + std::vector extruders = this->object_extruders(); + append(extruders, this->support_material_extruders()); - if (conside_custom_gcode) { - //BBS - int num_extruders = m_config.filament_colour.size(); - if (m_model.plates_custom_gcodes.find(m_model.curr_plate_index) != m_model.plates_custom_gcodes.end()) { - for (auto item : m_model.plates_custom_gcodes.at(m_model.curr_plate_index).gcodes) { - if (item.type == CustomGCode::Type::ToolChange && item.extruder <= num_extruders) - extruders.push_back((unsigned int)(item.extruder - 1)); + if (conside_custom_gcode) + { + // BBS + int num_extruders = m_config.filament_colour.size(); + if (m_model.plates_custom_gcodes.find(m_model.curr_plate_index) != m_model.plates_custom_gcodes.end()) + { + for (auto item : m_model.plates_custom_gcodes.at(m_model.curr_plate_index).gcodes) + { + if (item.type == CustomGCode::Type::ToolChange && item.extruder <= num_extruders) + extruders.push_back((unsigned int)(item.extruder - 1)); + } } } - } - - sort_remove_duplicates(extruders); - return extruders; -} -unsigned int Print::num_object_instances() const -{ - unsigned int instances = 0; - for (const PrintObject *print_object : m_objects) - instances += (unsigned int)print_object->instances().size(); - return instances; -} + sort_remove_duplicates(extruders); + return extruders; + } -double Print::max_allowed_layer_height() const -{ - double nozzle_diameter_max = 0.; - for (unsigned int extruder_id : this->extruders()) - nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(extruder_id)); - return nozzle_diameter_max; -} + unsigned int Print::num_object_instances() const + { + unsigned int instances = 0; + for (const PrintObject *print_object : m_objects) + instances += (unsigned int)print_object->instances().size(); + return instances; + } -std::vector Print::print_object_ids() const -{ - std::vector out; - // Reserve one more for the caller to append the ID of the Print itself. - out.reserve(m_objects.size() + 1); - for (const PrintObject *print_object : m_objects) - out.emplace_back(print_object->id()); - return out; -} + double Print::max_allowed_layer_height() const + { + double nozzle_diameter_max = 0.; + for (unsigned int extruder_id : this->extruders()) + nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(extruder_id)); + return nozzle_diameter_max; + } -bool Print::has_infinite_skirt() const -{ - return (m_config.draft_shield == dsEnabled && m_config.skirt_loops > 0) || (m_config.ooze_prevention && this->extruders().size() > 1); -} + std::vector Print::print_object_ids() const + { + std::vector out; + // Reserve one more for the caller to append the ID of the Print itself. + out.reserve(m_objects.size() + 1); + for (const PrintObject *print_object : m_objects) + out.emplace_back(print_object->id()); + return out; + } -bool Print::has_skirt() const -{ - return (m_config.skirt_height > 0 && m_config.skirt_loops > 0) || m_config.draft_shield != dsDisabled; -} + bool Print::has_infinite_skirt() const + { + return (m_config.draft_shield == dsEnabled && m_config.skirt_loops > 0) || (m_config.ooze_prevention && this->extruders().size() > 1); + } -bool Print::has_brim() const -{ - return std::any_of(m_objects.begin(), m_objects.end(), [](PrintObject *object) { return object->has_brim(); }); -} + bool Print::has_skirt() const + { + return (m_config.skirt_height > 0 && m_config.skirt_loops > 0) || m_config.draft_shield != dsDisabled; + } -//BBS -std::vector Print::layers_sorted_for_object(float start, float end, std::vector &layers_of_objects, std::vector &boundingBox_for_objects, std::vector &objects_instances_shift) -{ - std::vector idx_of_object_sorted; - size_t idx = 0; - for (const auto &object : m_objects) { - idx_of_object_sorted.push_back(idx++); - object->get_certain_layers(start, end, layers_of_objects, boundingBox_for_objects); + bool Print::has_brim() const + { + return std::any_of(m_objects.begin(), m_objects.end(), [](PrintObject *object) + { return object->has_brim(); }); } - std::sort(idx_of_object_sorted.begin(), idx_of_object_sorted.end(), - [boundingBox_for_objects](auto left, auto right) { return boundingBox_for_objects[left].area() > boundingBox_for_objects[right].area(); }); - objects_instances_shift.clear(); - objects_instances_shift.reserve(m_objects.size()); - for (const auto& object : m_objects) - objects_instances_shift.emplace_back(object->get_instances_shift_without_plate_offset()); + // BBS + std::vector Print::layers_sorted_for_object(float start, float end, std::vector &layers_of_objects, std::vector &boundingBox_for_objects, std::vector &objects_instances_shift) + { + std::vector idx_of_object_sorted; + size_t idx = 0; + for (const auto &object : m_objects) + { + idx_of_object_sorted.push_back(idx++); + object->get_certain_layers(start, end, layers_of_objects, boundingBox_for_objects); + } + std::sort(idx_of_object_sorted.begin(), idx_of_object_sorted.end(), + [boundingBox_for_objects](auto left, auto right) + { return boundingBox_for_objects[left].area() > boundingBox_for_objects[right].area(); }); - return idx_of_object_sorted; -}; + objects_instances_shift.clear(); + objects_instances_shift.reserve(m_objects.size()); + for (const auto &object : m_objects) + objects_instances_shift.emplace_back(object->get_instances_shift_without_plate_offset()); -StringObjectException Print::sequential_print_clearance_valid(const Print &print, Polygons *polygons, std::vector>* height_polygons) -{ - StringObjectException single_object_exception; - auto print_config = print.config(); - Pointfs excluse_area_points = print_config.bed_exclude_area.values; - Polygons exclude_polys; - Polygon exclude_poly; - const Vec3d print_origin = print.get_plate_origin(); - for (int i = 0; i < excluse_area_points.size(); i++) { - auto pt = excluse_area_points[i]; - exclude_poly.points.emplace_back(scale_(pt.x() + print_origin.x()), scale_(pt.y() + print_origin.y())); - if (i % 4 == 3) { // exclude areas are always rectangle - exclude_polys.push_back(exclude_poly); - exclude_poly.points.clear(); - } - } + return idx_of_object_sorted; + }; - std::map map_model_object_to_convex_hull; - struct print_instance_info + StringObjectException Print::sequential_print_clearance_valid(const Print &print, Polygons *polygons, std::vector> *height_polygons) { - const PrintInstance *print_instance; - BoundingBox bounding_box; - Polygon hull_polygon; - int object_index; - double arrange_score; - double height; - }; - auto find_object_index = [](const Model& model, const ModelObject* obj) { - for (int index = 0; index < model.objects.size(); index++) + StringObjectException single_object_exception; + auto print_config = print.config(); + Pointfs excluse_area_points = print_config.bed_exclude_area.values; + Polygons exclude_polys; + Polygon exclude_poly; + const Vec3d print_origin = print.get_plate_origin(); + for (int i = 0; i < excluse_area_points.size(); i++) { - if (model.objects[index] == obj) - return index; + auto pt = excluse_area_points[i]; + exclude_poly.points.emplace_back(scale_(pt.x() + print_origin.x()), scale_(pt.y() + print_origin.y())); + if (i % 4 == 3) + { // exclude areas are always rectangle + exclude_polys.push_back(exclude_poly); + exclude_poly.points.clear(); + } } - return -1; - }; - std::vector print_instance_with_bounding_box; + std::map map_model_object_to_convex_hull; + struct print_instance_info + { + const PrintInstance *print_instance; + BoundingBox bounding_box; + Polygon hull_polygon; + int object_index; + double arrange_score; + double height; + }; + auto find_object_index = [](const Model &model, const ModelObject *obj) + { + for (int index = 0; index < model.objects.size(); index++) + { + if (model.objects[index] == obj) + return index; + } + return -1; + }; + std::vector print_instance_with_bounding_box; - bool all_objects_are_short = print.is_all_objects_are_short(); - float obj_distance = all_objects_are_short ? scale_(0.5*MAX_OUTER_NOZZLE_RADIUS-0.1) : scale_(0.5*print.config().extruder_clearance_max_radius.value-0.1); - { - // sequential_print_horizontal_clearance_valid - Polygons convex_hulls_other; - if (polygons != nullptr) - polygons->clear(); - std::vector intersecting_idxs; - - // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects - // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. - for (const PrintObject *print_object : print.objects()) { - assert(! print_object->model_object()->instances.empty()); - assert(! print_object->instances().empty()); - ObjectID model_object_id = print_object->model_object()->id(); - auto it_convex_hull = map_model_object_to_convex_hull.find(model_object_id); - // Get convex hull of all printable volumes assigned to this print object. - ModelInstance *model_instance0 = print_object->model_object()->instances.front(); - if (it_convex_hull == map_model_object_to_convex_hull.end()) { - // Calculate the convex hull of a printable object. - // Grow convex hull with the clearance margin. - // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) - // which causes that the warning will be showed after arrangement with the - // appropriate object distance. Even if I set this to jtMiter the warning still shows up. - Geometry::Transformation new_trans(model_instance0->get_transformation()); - new_trans.set_offset({0.0, 0.0, model_instance0->get_offset().z()}); - it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, - print_object->model_object()->convex_hull_2d(new_trans.get_matrix())); - } - // Make a copy, so it may be rotated for instances. - Polygon convex_hull0 = it_convex_hull->second; - const double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation()); - if (std::abs(z_diff) > EPSILON) - convex_hull0.rotate(z_diff); - // Now we check that no instance of convex_hull intersects any of the previously checked object instances. - for (const PrintInstance &instance : print_object->instances()) { - Polygon convex_hull_no_offset = convex_hull0, convex_hull; - auto tmp = offset(convex_hull_no_offset, obj_distance, jtRound, scale_(0.1)); - if (!tmp.empty()) { // tmp may be empty due to clipper's bug, see STUDIO-2452 - convex_hull = tmp.front(); - // instance.shift is a position of a centered object, while model object may not be centered. - // Convert the shift from the PrintObject's coordinates into ModelObject's coordinates by removing the centering offset. - convex_hull.translate(instance.shift - print_object->center_offset()); + bool all_objects_are_short = print.is_all_objects_are_short(); + float obj_distance = all_objects_are_short ? scale_(0.5 * MAX_OUTER_NOZZLE_RADIUS - 0.1) : scale_(0.5 * print.config().extruder_clearance_max_radius.value - 0.1); + { + // sequential_print_horizontal_clearance_valid + Polygons convex_hulls_other; + if (polygons != nullptr) + polygons->clear(); + std::vector intersecting_idxs; + + // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects + // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. + for (const PrintObject *print_object : print.objects()) + { + assert(!print_object->model_object()->instances.empty()); + assert(!print_object->instances().empty()); + ObjectID model_object_id = print_object->model_object()->id(); + auto it_convex_hull = map_model_object_to_convex_hull.find(model_object_id); + // Get convex hull of all printable volumes assigned to this print object. + ModelInstance *model_instance0 = print_object->model_object()->instances.front(); + if (it_convex_hull == map_model_object_to_convex_hull.end()) + { + // Calculate the convex hull of a printable object. + // Grow convex hull with the clearance margin. + // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) + // which causes that the warning will be showed after arrangement with the + // appropriate object distance. Even if I set this to jtMiter the warning still shows up. + Geometry::Transformation new_trans(model_instance0->get_transformation()); + new_trans.set_offset({0.0, 0.0, model_instance0->get_offset().z()}); + it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, + print_object->model_object()->convex_hull_2d(new_trans.get_matrix())); } - convex_hull_no_offset.translate(instance.shift - print_object->center_offset()); - //juedge the exclude area - if (!intersection(exclude_polys, convex_hull_no_offset).empty()) { - if (single_object_exception.string.empty()) { - single_object_exception.string = (boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str(); - single_object_exception.object = instance.model_instance->get_object(); - } - else { - single_object_exception.string += "\n"+(boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str(); - single_object_exception.object = nullptr; + // Make a copy, so it may be rotated for instances. + Polygon convex_hull0 = it_convex_hull->second; + const double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation()); + if (std::abs(z_diff) > EPSILON) + convex_hull0.rotate(z_diff); + // Now we check that no instance of convex_hull intersects any of the previously checked object instances. + for (const PrintInstance &instance : print_object->instances()) + { + Polygon convex_hull_no_offset = convex_hull0, convex_hull; + auto tmp = offset(convex_hull_no_offset, obj_distance, jtRound, scale_(0.1)); + if (!tmp.empty()) + { // tmp may be empty due to clipper's bug, see STUDIO-2452 + convex_hull = tmp.front(); + // instance.shift is a position of a centered object, while model object may not be centered. + // Convert the shift from the PrintObject's coordinates into ModelObject's coordinates by removing the centering offset. + convex_hull.translate(instance.shift - print_object->center_offset()); } - //if (polygons) { - // intersecting_idxs.emplace_back(convex_hulls_other.size()); - //} - } - - // if output needed, collect indices (inside convex_hulls_other) of intersecting hulls - for (size_t i = 0; i < convex_hulls_other.size(); ++i) { - if (! intersection(convex_hulls_other[i], convex_hull).empty()) { - bool has_exception = false; - if (single_object_exception.string.empty()) { - single_object_exception.string = (boost::format(L("%1% is too close to others, and collisions may be caused.")) %instance.model_instance->get_object()->name).str(); + convex_hull_no_offset.translate(instance.shift - print_object->center_offset()); + // juedge the exclude area + if (!intersection(exclude_polys, convex_hull_no_offset).empty()) + { + if (single_object_exception.string.empty()) + { + single_object_exception.string = (boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) % instance.model_instance->get_object()->name).str(); single_object_exception.object = instance.model_instance->get_object(); - has_exception = true; } - else { - single_object_exception.string += "\n"+(boost::format(L("%1% is too close to others, and collisions may be caused.")) %instance.model_instance->get_object()->name).str(); + else + { + single_object_exception.string += "\n" + (boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) % instance.model_instance->get_object()->name).str(); single_object_exception.object = nullptr; - has_exception = true; } + // if (polygons) { + // intersecting_idxs.emplace_back(convex_hulls_other.size()); + // } + } - if (polygons) { - intersecting_idxs.emplace_back(i); - intersecting_idxs.emplace_back(convex_hulls_other.size()); + // if output needed, collect indices (inside convex_hulls_other) of intersecting hulls + for (size_t i = 0; i < convex_hulls_other.size(); ++i) + { + if (!intersection(convex_hulls_other[i], convex_hull).empty()) + { + bool has_exception = false; + if (single_object_exception.string.empty()) + { + single_object_exception.string = (boost::format(L("%1% is too close to others, and collisions may be caused.")) % instance.model_instance->get_object()->name).str(); + single_object_exception.object = instance.model_instance->get_object(); + has_exception = true; + } + else + { + single_object_exception.string += "\n" + (boost::format(L("%1% is too close to others, and collisions may be caused.")) % instance.model_instance->get_object()->name).str(); + single_object_exception.object = nullptr; + has_exception = true; + } + + if (polygons) + { + intersecting_idxs.emplace_back(i); + intersecting_idxs.emplace_back(convex_hulls_other.size()); + } + + if (has_exception) + break; } - - if (has_exception) break; } + struct print_instance_info print_info{&instance, convex_hull.bounding_box(), convex_hull}; + print_info.height = instance.print_object->height(); + print_info.object_index = find_object_index(print.model(), print_object->model_object()); + print_instance_with_bounding_box.push_back(std::move(print_info)); + convex_hulls_other.emplace_back(std::move(convex_hull)); } - struct print_instance_info print_info {&instance, convex_hull.bounding_box(), convex_hull}; - print_info.height = instance.print_object->height(); - print_info.object_index = find_object_index(print.model(), print_object->model_object()); - print_instance_with_bounding_box.push_back(std::move(print_info)); - convex_hulls_other.emplace_back(std::move(convex_hull)); } - } - if (!intersecting_idxs.empty()) { - // use collected indices (inside convex_hulls_other) to update output - std::sort(intersecting_idxs.begin(), intersecting_idxs.end()); - intersecting_idxs.erase(std::unique(intersecting_idxs.begin(), intersecting_idxs.end()), intersecting_idxs.end()); - for (size_t i : intersecting_idxs) { - polygons->emplace_back(std::move(convex_hulls_other[i])); + if (!intersecting_idxs.empty()) + { + // use collected indices (inside convex_hulls_other) to update output + std::sort(intersecting_idxs.begin(), intersecting_idxs.end()); + intersecting_idxs.erase(std::unique(intersecting_idxs.begin(), intersecting_idxs.end()), intersecting_idxs.end()); + for (size_t i : intersecting_idxs) + { + polygons->emplace_back(std::move(convex_hulls_other[i])); + } } } - } - // calc sort order - double hc1 = scale_(print.config().extruder_clearance_height_to_lid); // height to lid - double hc2 = scale_(print.config().extruder_clearance_height_to_rod); // height to rod - double printable_height = scale_(print.config().printable_height); + // calc sort order + double hc1 = scale_(print.config().extruder_clearance_height_to_lid); // height to lid + double hc2 = scale_(print.config().extruder_clearance_height_to_rod); // height to rod + double printable_height = scale_(print.config().printable_height); -#if 0 //do not sort anymore, use the order in object list +#if 0 // do not sort anymore, use the order in object list auto bed_points = get_bed_shape(print_config); float bed_width = bed_points[1].x() - bed_points[0].x(); // 如果扩大以后的多边形的距离小于这个值,就需要严格保证从左到右的打印顺序,否则会撞工具头右侧 @@ -808,503 +758,564 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print BOOST_LOG_TRIVIAL(debug) << "after sorting print_instance " << inst.print_instance->model_instance->get_object()->name << ", score: " << inst.arrange_score << ", height:"<< inst.height; #else - // sort the print instance - std::sort(print_instance_with_bounding_box.begin(), print_instance_with_bounding_box.end(), - [](print_instance_info& l, print_instance_info& r) {return l.object_index < r.object_index;}); + // sort the print instance + std::sort(print_instance_with_bounding_box.begin(), print_instance_with_bounding_box.end(), + [](print_instance_info &l, print_instance_info &r) + { return l.object_index < r.object_index; }); - for (auto &inst : print_instance_with_bounding_box) - BOOST_LOG_TRIVIAL(debug) << "after sorting print_instance " << inst.print_instance->model_instance->get_object()->name << ", object_index: " << inst.object_index - << ", height:"<< inst.height; + for (auto &inst : print_instance_with_bounding_box) + BOOST_LOG_TRIVIAL(debug) << "after sorting print_instance " << inst.print_instance->model_instance->get_object()->name << ", object_index: " << inst.object_index + << ", height:" << inst.height; #endif - // sequential_print_vertical_clearance_valid - { - // Ignore the last instance printed. - //print_instance_with_bounding_box.pop_back(); - /*bool has_interlaced_objects = false; - for (int k = 0; k < print_instance_count; k++) - { - auto inst = print_instance_with_bounding_box[k].print_instance; - auto bbox = print_instance_with_bounding_box[k].bounding_box; - auto iy1 = bbox.min.y(); - auto iy2 = bbox.max.y(); - - for (int i = 0; i < k; i++) - { - auto& p = print_instance_with_bounding_box[i].print_instance; - auto bbox2 = print_instance_with_bounding_box[i].bounding_box; - auto py1 = bbox2.min.y(); - auto py2 = bbox2.max.y(); - auto inter_min = std::max(iy1, py1); // min y of intersection - auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists - if (inter_max - inter_min > 0) { - has_interlaced_objects = true; - break; + // sequential_print_vertical_clearance_valid + { + // Ignore the last instance printed. + // print_instance_with_bounding_box.pop_back(); + /*bool has_interlaced_objects = false; + for (int k = 0; k < print_instance_count; k++) + { + auto inst = print_instance_with_bounding_box[k].print_instance; + auto bbox = print_instance_with_bounding_box[k].bounding_box; + auto iy1 = bbox.min.y(); + auto iy2 = bbox.max.y(); + + for (int i = 0; i < k; i++) + { + auto& p = print_instance_with_bounding_box[i].print_instance; + auto bbox2 = print_instance_with_bounding_box[i].bounding_box; + auto py1 = bbox2.min.y(); + auto py2 = bbox2.max.y(); + auto inter_min = std::max(iy1, py1); // min y of intersection + auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists + if (inter_max - inter_min > 0) { + has_interlaced_objects = true; + break; + } } + if (has_interlaced_objects) + break; + }*/ + + // if objects are not overlapped on y-axis, they will not collide even if they are taller than extruder_clearance_height_to_rod + int print_instance_count = print_instance_with_bounding_box.size(); + std::map> too_tall_instances; + for (int k = 0; k < print_instance_count; k++) + { + // 只需要考虑喷嘴到滑杆的偏移量,这个比整个工具头的碰撞半径要小得多 + BoundingBox &bbox = print_instance_with_bounding_box[k].bounding_box; + bbox.offset(scale_(print_config.extruder_clearance_dist_to_rod.value * 0.5) - obj_distance); } - if (has_interlaced_objects) - break; - }*/ + for (int k = 0; k < print_instance_count; k++) + { + auto inst = print_instance_with_bounding_box[k].print_instance; + auto bbox = print_instance_with_bounding_box[k].bounding_box; + auto iy1 = bbox.min.y(); + auto iy2 = bbox.max.y(); + (const_cast(inst->model_instance))->arrange_order = k + 1; + double height = (k == (print_instance_count - 1)) ? printable_height : hc1; + /*if (has_interlaced_objects) { + if ((k < (print_instance_count - 1)) && (inst->print_object->height() > hc2)) { + too_tall_instances[inst] = std::make_pair(print_instance_with_bounding_box[k].hull_polygon, unscaled(hc2)); + } + } + else { + if ((k < (print_instance_count - 1)) && (inst->print_object->height() > hc1)) { + too_tall_instances[inst] = std::make_pair(print_instance_with_bounding_box[k].hull_polygon, unscaled(hc1)); + } + }*/ - // if objects are not overlapped on y-axis, they will not collide even if they are taller than extruder_clearance_height_to_rod - int print_instance_count = print_instance_with_bounding_box.size(); - std::map> too_tall_instances; - for (int k = 0; k < print_instance_count; k++) - { - // 只需要考虑喷嘴到滑杆的偏移量,这个比整个工具头的碰撞半径要小得多 - BoundingBox& bbox = print_instance_with_bounding_box[k].bounding_box; - bbox.offset( scale_(print_config.extruder_clearance_dist_to_rod.value*0.5) - obj_distance); - } - for (int k = 0; k < print_instance_count; k++) - { - auto inst = print_instance_with_bounding_box[k].print_instance; - auto bbox = print_instance_with_bounding_box[k].bounding_box; - auto iy1 = bbox.min.y(); - auto iy2 = bbox.max.y(); - (const_cast(inst->model_instance))->arrange_order = k+1; - double height = (k == (print_instance_count - 1))?printable_height:hc1; - /*if (has_interlaced_objects) { - if ((k < (print_instance_count - 1)) && (inst->print_object->height() > hc2)) { - too_tall_instances[inst] = std::make_pair(print_instance_with_bounding_box[k].hull_polygon, unscaled(hc2)); + for (int i = k + 1; i < print_instance_count; i++) + { + auto &p = print_instance_with_bounding_box[i].print_instance; + auto bbox2 = print_instance_with_bounding_box[i].bounding_box; + auto py1 = bbox2.min.y(); + auto py2 = bbox2.max.y(); + auto inter_min = std::max(iy1, py1); // min y of intersection + auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists + if (inter_max - inter_min > 0) + { + height = hc2; + break; + } } + if (height < inst->print_object->max_z()) + too_tall_instances[inst] = std::make_pair(print_instance_with_bounding_box[k].hull_polygon, unscaled(height)); } - else { - if ((k < (print_instance_count - 1)) && (inst->print_object->height() > hc1)) { - too_tall_instances[inst] = std::make_pair(print_instance_with_bounding_box[k].hull_polygon, unscaled(hc1)); - } - }*/ - for (int i = k+1; i < print_instance_count; i++) + if (too_tall_instances.size() > 0) { - auto& p = print_instance_with_bounding_box[i].print_instance; - auto bbox2 = print_instance_with_bounding_box[i].bounding_box; - auto py1 = bbox2.min.y(); - auto py2 = bbox2.max.y(); - auto inter_min = std::max(iy1, py1); // min y of intersection - auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists - if (inter_max - inter_min > 0) { - height = hc2; - break; + // return {, inst->model_instance->get_object()}; + for (auto &iter : too_tall_instances) + { + if (single_object_exception.string.empty()) + { + single_object_exception.string = (boost::format(L("%1% is too tall, and collisions will be caused.")) % iter.first->model_instance->get_object()->name).str(); + single_object_exception.object = iter.first->model_instance->get_object(); + } + else + { + single_object_exception.string += "\n" + (boost::format(L("%1% is too tall, and collisions will be caused.")) % iter.first->model_instance->get_object()->name).str(); + single_object_exception.object = nullptr; + } + if (height_polygons) + height_polygons->emplace_back(std::move(iter.second)); } } - if (height < inst->print_object->max_z()) - too_tall_instances[inst] = std::make_pair(print_instance_with_bounding_box[k].hull_polygon, unscaled(height)); } - if (too_tall_instances.size() > 0) { - //return {, inst->model_instance->get_object()}; - for (auto& iter: too_tall_instances) { - if (single_object_exception.string.empty()) { - single_object_exception.string = (boost::format(L("%1% is too tall, and collisions will be caused.")) %iter.first->model_instance->get_object()->name).str(); - single_object_exception.object = iter.first->model_instance->get_object(); - } - else { - single_object_exception.string += "\n" + (boost::format(L("%1% is too tall, and collisions will be caused.")) %iter.first->model_instance->get_object()->name).str(); - single_object_exception.object = nullptr; - } - if (height_polygons) - height_polygons->emplace_back(std::move(iter.second)); + return single_object_exception; + } + + // BBS + static StringObjectException layered_print_cleareance_valid(const Print &print, StringObjectException *warning) + { + std::vector print_instances_ordered = sort_object_instances_by_model_order(print, true); + if (print_instances_ordered.size() < 1) + return {}; + + auto print_config = print.config(); + Pointfs excluse_area_points = print_config.bed_exclude_area.values; + Polygons exclude_polys; + Polygon exclude_poly; + const Vec3d print_origin = print.get_plate_origin(); + for (int i = 0; i < excluse_area_points.size(); i++) + { + auto pt = excluse_area_points[i]; + exclude_poly.points.emplace_back(scale_(pt.x() + print_origin.x()), scale_(pt.y() + print_origin.y())); + if (i % 4 == 3) + { // exclude areas are always rectangle + exclude_polys.push_back(exclude_poly); + exclude_poly.points.clear(); } } - } - return single_object_exception; -} + Pointfs wrapping_detection_area = print_config.wrapping_exclude_area.values; + Polygon wrapping_poly; + for (size_t i = 0; i < wrapping_detection_area.size(); ++i) + { + auto pt = wrapping_detection_area[i]; + wrapping_poly.points.emplace_back(scale_(pt.x() + print_origin.x()), scale_(pt.y() + print_origin.y())); + } -//BBS -static StringObjectException layered_print_cleareance_valid(const Print &print, StringObjectException *warning) -{ - std::vector print_instances_ordered = sort_object_instances_by_model_order(print, true); - if (print_instances_ordered.size() < 1) - return {}; + std::map map_model_volume_to_convex_hull; + Polygons convex_hulls_other; + for (auto &inst : print_instances_ordered) + { + for (const ModelVolume *v : inst->print_object->model_object()->volumes) + { + if (!v->is_model_part()) + continue; + auto it_convex_hull = map_model_volume_to_convex_hull.find(v); + if (it_convex_hull == map_model_volume_to_convex_hull.end()) + { + auto volume_hull = v->get_convex_hull_2d(Geometry::assemble_transform(Vec3d::Zero(), inst->model_instance->get_rotation(), + inst->model_instance->get_scaling_factor(), inst->model_instance->get_mirror())); + volume_hull.translate(inst->shift - inst->print_object->center_offset()); + + it_convex_hull = map_model_volume_to_convex_hull.emplace_hint(it_convex_hull, v, volume_hull); + } + Polygon &convex_hull = it_convex_hull->second; + Polygons convex_hulls_temp; + convex_hulls_temp.push_back(convex_hull); + if (!intersection(exclude_polys, convex_hull).empty()) + { + return {inst->model_instance->get_object()->name + L(" is too close to exclusion area, there may be collisions when printing.") + "\n", + inst->model_instance->get_object()}; + } - auto print_config = print.config(); - Pointfs excluse_area_points = print_config.bed_exclude_area.values; - Polygons exclude_polys; - Polygon exclude_poly; - const Vec3d print_origin = print.get_plate_origin(); - for (int i = 0; i < excluse_area_points.size(); i++) { - auto pt = excluse_area_points[i]; - exclude_poly.points.emplace_back(scale_(pt.x() + print_origin.x()), scale_(pt.y() + print_origin.y())); - if (i % 4 == 3) { // exclude areas are always rectangle - exclude_polys.push_back(exclude_poly); - exclude_poly.points.clear(); + if (print_config.enable_wrapping_detection.value && !intersection(wrapping_poly, convex_hull).empty()) + { + return {inst->model_instance->get_object()->name + L(" is too close to clumping detection area, there may be collisions when printing.") + "\n", + inst->model_instance->get_object()}; + } + convex_hulls_other.emplace_back(convex_hull); + } } - } - Pointfs wrapping_detection_area = print_config.wrapping_exclude_area.values; - Polygon wrapping_poly; - for (size_t i = 0; i < wrapping_detection_area.size(); ++i) { - auto pt = wrapping_detection_area[i]; - wrapping_poly.points.emplace_back(scale_(pt.x() + print_origin.x()), scale_(pt.y() + print_origin.y())); - } + // BBS: add the wipe tower check logic + const PrintConfig &config = print.config(); + int filaments_count = print.extruders().size(); + int plate_index = print.get_plate_index(); + const Vec3d plate_origin = print.get_plate_origin(); + float x = config.wipe_tower_x.get_at(plate_index) + plate_origin(0); + float y = config.wipe_tower_y.get_at(plate_index) + plate_origin(1); + float width = config.prime_tower_width.value; + float a = config.wipe_tower_rotation_angle.value; + // float v = config.wiping_volume.value; + + float depth = print.wipe_tower_data(filaments_count).depth; + // float brim_width = print.wipe_tower_data(filaments_count).brim_width; - std::map map_model_volume_to_convex_hull; - Polygons convex_hulls_other; - for (auto& inst : print_instances_ordered) { - for (const ModelVolume *v : inst->print_object->model_object()->volumes) { - if (!v->is_model_part()) continue; - auto it_convex_hull = map_model_volume_to_convex_hull.find(v); - if (it_convex_hull == map_model_volume_to_convex_hull.end()) { - auto volume_hull = v->get_convex_hull_2d(Geometry::assemble_transform(Vec3d::Zero(), inst->model_instance->get_rotation(), - inst->model_instance->get_scaling_factor(), inst->model_instance->get_mirror())); - volume_hull.translate(inst->shift - inst->print_object->center_offset()); + if (config.prime_tower_rib_wall.value) + width = depth; - it_convex_hull = map_model_volume_to_convex_hull.emplace_hint(it_convex_hull, v, volume_hull); + Polygons convex_hulls_temp; + if (print.has_wipe_tower()) + { + if (!print.is_step_done(psWipeTower)) + { + Polygon wipe_tower_convex_hull; + wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y)); + wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y)); + wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y + depth)); + wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y + depth)); + wipe_tower_convex_hull.rotate(a); + convex_hulls_temp.push_back(wipe_tower_convex_hull); } - Polygon &convex_hull = it_convex_hull->second; - Polygons convex_hulls_temp; - convex_hulls_temp.push_back(convex_hull); - if (!intersection(exclude_polys, convex_hull).empty()) { - return {inst->model_instance->get_object()->name + L(" is too close to exclusion area, there may be collisions when printing.") + "\n", - inst->model_instance->get_object()}; + else + { + // here, wipe_tower_polygon is not always convex. + Polygon wipe_tower_polygon; + if (print.wipe_tower_data().wipe_tower_mesh_data) + wipe_tower_polygon = print.wipe_tower_data().wipe_tower_mesh_data->bottom; + wipe_tower_polygon.translate(Point(scale_(x), scale_(y))); + convex_hulls_temp.push_back(wipe_tower_polygon); } - - if (print_config.enable_wrapping_detection.value && !intersection(wrapping_poly, convex_hull).empty()) { - return {inst->model_instance->get_object()->name + L(" is too close to clumping detection area, there may be collisions when printing.") + "\n", - inst->model_instance->get_object()}; + } + if (!intersection(convex_hulls_other, convex_hulls_temp).empty()) + { + if (warning) + { + warning->string += L("Prime Tower") + L(" is too close to others, and collisions may be caused.\n"); } - convex_hulls_other.emplace_back(convex_hull); } - } - - //BBS: add the wipe tower check logic - const PrintConfig & config = print.config(); - int filaments_count = print.extruders().size(); - int plate_index = print.get_plate_index(); - const Vec3d plate_origin = print.get_plate_origin(); - float x = config.wipe_tower_x.get_at(plate_index) + plate_origin(0); - float y = config.wipe_tower_y.get_at(plate_index) + plate_origin(1); - float width = config.prime_tower_width.value; - float a = config.wipe_tower_rotation_angle.value; - //float v = config.wiping_volume.value; - - float depth = print.wipe_tower_data(filaments_count).depth; - //float brim_width = print.wipe_tower_data(filaments_count).brim_width; - - if (config.prime_tower_rib_wall.value) - width = depth; - - Polygons convex_hulls_temp; - if (print.has_wipe_tower()) { - if (!print.is_step_done(psWipeTower)) { - Polygon wipe_tower_convex_hull; - wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y)); - wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y)); - wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y + depth)); - wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y + depth)); - wipe_tower_convex_hull.rotate(a); - convex_hulls_temp.push_back(wipe_tower_convex_hull); - } else { - //here, wipe_tower_polygon is not always convex. - Polygon wipe_tower_polygon; - if (print.wipe_tower_data().wipe_tower_mesh_data) - wipe_tower_polygon = print.wipe_tower_data().wipe_tower_mesh_data->bottom; - wipe_tower_polygon.translate(Point(scale_(x), scale_(y))); - convex_hulls_temp.push_back(wipe_tower_polygon); + if (!intersection(exclude_polys, convex_hulls_temp).empty()) + { + /*if (warning) { + warning->string += L("Prime Tower is too close to exclusion area, there may be collisions when printing.\n"); + }*/ + return {L("Prime Tower") + L(" is too close to exclusion area, and collisions will be caused.\n")}; } - } - if (!intersection(convex_hulls_other, convex_hulls_temp).empty()) { - if (warning) { - warning->string += L("Prime Tower") + L(" is too close to others, and collisions may be caused.\n"); + if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, convex_hulls_temp).empty()) + { + return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")}; } + return {}; } - if (!intersection(exclude_polys, convex_hulls_temp).empty()) { - /*if (warning) { - warning->string += L("Prime Tower is too close to exclusion area, there may be collisions when printing.\n"); - }*/ - return {L("Prime Tower") + L(" is too close to exclusion area, and collisions will be caused.\n")}; - } - if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, convex_hulls_temp).empty()) { - return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")}; - } - return {}; -} -FilamentCompatibilityType Print::check_multi_filaments_compatibility(const std::vector& filament_types) -{ - bool has_high_temperature_filament = false; - bool has_low_temperature_filament = false; - bool has_mid_temperature_filament = false; - - for (const auto& type : filament_types) { - if (get_filament_temp_type(type) ==FilamentTempType::HighTemp) - has_high_temperature_filament = true; - else if (get_filament_temp_type(type) == FilamentTempType::LowTemp) - has_low_temperature_filament = true; - else if (get_filament_temp_type(type) == FilamentTempType::HighLowCompatible) - has_mid_temperature_filament = true; - } + FilamentCompatibilityType Print::check_multi_filaments_compatibility(const std::vector &filament_types) + { + bool has_high_temperature_filament = false; + bool has_low_temperature_filament = false; + bool has_mid_temperature_filament = false; - if (has_high_temperature_filament && has_low_temperature_filament) - return FilamentCompatibilityType::HighLowMixed; - else if (has_high_temperature_filament && has_mid_temperature_filament) - return FilamentCompatibilityType::HighMidMixed; - else if (has_low_temperature_filament && has_mid_temperature_filament) - return FilamentCompatibilityType::LowMidMixed; - else - return FilamentCompatibilityType::Compatible; -} + for (const auto &type : filament_types) + { + if (get_filament_temp_type(type) == FilamentTempType::HighTemp) + has_high_temperature_filament = true; + else if (get_filament_temp_type(type) == FilamentTempType::LowTemp) + has_low_temperature_filament = true; + else if (get_filament_temp_type(type) == FilamentTempType::HighLowCompatible) + has_mid_temperature_filament = true; + } -bool Print::is_filaments_compatible(const std::vector& filament_types) -{ - bool has_high_temperature_filament = false; - bool has_low_temperature_filament = false; - - for (const auto& type : filament_types) { - if (type == FilamentTempType::HighTemp) - has_high_temperature_filament = true; - else if (type == FilamentTempType::LowTemp) - has_low_temperature_filament = true; + if (has_high_temperature_filament && has_low_temperature_filament) + return FilamentCompatibilityType::HighLowMixed; + else if (has_high_temperature_filament && has_mid_temperature_filament) + return FilamentCompatibilityType::HighMidMixed; + else if (has_low_temperature_filament && has_mid_temperature_filament) + return FilamentCompatibilityType::LowMidMixed; + else + return FilamentCompatibilityType::Compatible; } - if (has_high_temperature_filament && has_low_temperature_filament) - return false; + bool Print::is_filaments_compatible(const std::vector &filament_types) + { + bool has_high_temperature_filament = false; + bool has_low_temperature_filament = false; - return true; -} -int Print::get_compatible_filament_type(const std::set& filament_types) -{ - bool has_high_temperature_filament = false; - bool has_low_temperature_filament = false; - - for (const auto& type : filament_types) { - if (type == FilamentTempType::HighTemp) - has_high_temperature_filament = true; - else if (type == FilamentTempType::LowTemp) - has_low_temperature_filament = true; + for (const auto &type : filament_types) + { + if (type == FilamentTempType::HighTemp) + has_high_temperature_filament = true; + else if (type == FilamentTempType::LowTemp) + has_low_temperature_filament = true; + } + + if (has_high_temperature_filament && has_low_temperature_filament) + return false; + + return true; } + int Print::get_compatible_filament_type(const std::set &filament_types) + { + bool has_high_temperature_filament = false; + bool has_low_temperature_filament = false; - if (has_high_temperature_filament && has_low_temperature_filament) + for (const auto &type : filament_types) + { + if (type == FilamentTempType::HighTemp) + has_high_temperature_filament = true; + else if (type == FilamentTempType::LowTemp) + has_low_temperature_filament = true; + } + + if (has_high_temperature_filament && has_low_temperature_filament) + return HighLowCompatible; + else if (has_high_temperature_filament) + return HighTemp; + else if (has_low_temperature_filament) + return LowTemp; return HighLowCompatible; - else if (has_high_temperature_filament) - return HighTemp; - else if (has_low_temperature_filament) - return LowTemp; - return HighLowCompatible; -} + } -//BBS: this function is used to check whether multi filament can be printed -StringObjectException Print::check_multi_filament_valid(const Print& print) -{ - auto print_config = print.config(); - if(print_config.print_sequence == PrintSequence::ByObject) {// use ByObject valid under ByObject print sequence - std::set Compatibility_each_obj; - bool enable_mix_printing = !print.need_check_multi_filaments_compatibility(); + // BBS: this function is used to check whether multi filament can be printed + StringObjectException Print::check_multi_filament_valid(const Print &print) + { + auto print_config = print.config(); + if (print_config.print_sequence == PrintSequence::ByObject) + { // use ByObject valid under ByObject print sequence + std::set Compatibility_each_obj; + bool enable_mix_printing = !print.need_check_multi_filaments_compatibility(); - for (const auto &objectID_t : print.print_object_ids()) { - std::set obj_used_extruder_ids; - auto print_object = print.get_object(objectID_t);// current object - if (print_object){ - auto object_extruders_t = print_object->object_extruders(); // object used extruder - for (int extruder : object_extruders_t) { - assert(extruder > 0); - obj_used_extruder_ids.insert(extruder); + for (const auto &objectID_t : print.print_object_ids()) + { + std::set obj_used_extruder_ids; + auto print_object = print.get_object(objectID_t); // current object + if (print_object) + { + auto object_extruders_t = print_object->object_extruders(); // object used extruder + for (int extruder : object_extruders_t) + { + assert(extruder > 0); + obj_used_extruder_ids.insert(extruder); + } } - } - if (print_object->has_support_material()) { // extruder used by supports - auto num_extruders = (unsigned int) print_config.filament_diameter.size(); - assert(print_object->config().support_filament >= 0); - if (print_object->config().support_filament >= 1 && (unsigned int)print_object->config().support_filament < num_extruders + 1) - obj_used_extruder_ids.insert((unsigned int) print_object->config().support_filament - 1);//0-based extruder id - assert(print_object->config().support_interface_filament >= 0); - if (print_object->config().support_interface_filament >= 1 && (unsigned int)print_object->config().support_interface_filament < num_extruders + 1) - obj_used_extruder_ids.insert((unsigned int) print_object->config().support_interface_filament - 1); - } - std::vector filament_types; - filament_types.reserve(obj_used_extruder_ids.size()); - for (const auto &extruder_idx : obj_used_extruder_ids) filament_types.push_back(print_config.filament_type.get_at(extruder_idx)); + if (print_object->has_support_material()) + { // extruder used by supports + auto num_extruders = (unsigned int)print_config.filament_diameter.size(); + assert(print_object->config().support_filament >= 0); + if (print_object->config().support_filament >= 1 && (unsigned int)print_object->config().support_filament < num_extruders + 1) + obj_used_extruder_ids.insert((unsigned int)print_object->config().support_filament - 1); // 0-based extruder id + assert(print_object->config().support_interface_filament >= 0); + if (print_object->config().support_interface_filament >= 1 && (unsigned int)print_object->config().support_interface_filament < num_extruders + 1) + obj_used_extruder_ids.insert((unsigned int)print_object->config().support_interface_filament - 1); + } + std::vector filament_types; + filament_types.reserve(obj_used_extruder_ids.size()); + for (const auto &extruder_idx : obj_used_extruder_ids) + filament_types.push_back(print_config.filament_type.get_at(extruder_idx)); - auto compatibility = check_multi_filaments_compatibility(filament_types);// check for each object - Compatibility_each_obj.insert(compatibility); - } - StringObjectException ret; - std::string hypertext = "filament_mix_print"; - if (Compatibility_each_obj.count(FilamentCompatibilityType::HighLowMixed)){// at least one object has HighLowMixed - if (enable_mix_printing) { - ret.string = L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage."); + auto compatibility = check_multi_filaments_compatibility(filament_types); // check for each object + Compatibility_each_obj.insert(compatibility); + } + StringObjectException ret; + std::string hypertext = "filament_mix_print"; + if (Compatibility_each_obj.count(FilamentCompatibilityType::HighLowMixed)) + { // at least one object has HighLowMixed + if (enable_mix_printing) + { + ret.string = L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage."); + ret.is_warning = true; + ret.hypetext = hypertext; + } + else + ret.string = L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage. If you still want to print, you can enable the option in Preferences."); + } + else if (Compatibility_each_obj.count(FilamentCompatibilityType::LowMidMixed) || Compatibility_each_obj.count(FilamentCompatibilityType::HighMidMixed)) + { // at least one object has other Mixed ret.is_warning = true; - ret.hypetext = hypertext; - } else - ret.string = L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage. If you still want to print, you can enable the option in Preferences."); - }else if (Compatibility_each_obj.count(FilamentCompatibilityType::LowMidMixed) || Compatibility_each_obj.count(FilamentCompatibilityType::HighMidMixed)){// at least one object has other Mixed - ret.is_warning = true; - ret.hypetext = hypertext; - ret.string = L("Printing different-temp filaments together may cause nozzle clogging or printer damage."); + ret.hypetext = hypertext; + ret.string = L("Printing different-temp filaments together may cause nozzle clogging or printer damage."); + } + return ret; } - return ret; - } - std::vector extruders = print.extruders(); - std::vector filament_types; - filament_types.reserve(extruders.size()); - for (const auto& extruder_idx : extruders) - filament_types.push_back(print_config.filament_type.get_at(extruder_idx)); + std::vector extruders = print.extruders(); + std::vector filament_types; + filament_types.reserve(extruders.size()); + for (const auto &extruder_idx : extruders) + filament_types.push_back(print_config.filament_type.get_at(extruder_idx)); - auto compatibility = check_multi_filaments_compatibility(filament_types); - bool enable_mix_printing = !print.need_check_multi_filaments_compatibility(); + auto compatibility = check_multi_filaments_compatibility(filament_types); + bool enable_mix_printing = !print.need_check_multi_filaments_compatibility(); - StringObjectException ret; + StringObjectException ret; - std::string hypertext = "filament_mix_print"; + std::string hypertext = "filament_mix_print"; - if(compatibility == FilamentCompatibilityType::HighLowMixed){ - if(enable_mix_printing){ - ret.string =L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage."); + if (compatibility == FilamentCompatibilityType::HighLowMixed) + { + if (enable_mix_printing) + { + ret.string = L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage."); + ret.is_warning = true; + ret.hypetext = hypertext; + } + else + { + ret.string = L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage. If you still want to print, you can enable the option in Preferences."); + } + } + else if (compatibility == FilamentCompatibilityType::HighMidMixed) + { ret.is_warning = true; - ret.hypetext = hypertext; + ret.hypetext = hypertext; + ret.string = L("Printing high-temp and mid-temp filaments together may cause nozzle clogging or printer damage."); } - else{ - ret.string =L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage. If you still want to print, you can enable the option in Preferences."); + else if (compatibility == FilamentCompatibilityType::LowMidMixed) + { + ret.is_warning = true; + ret.hypetext = hypertext; + ret.string = L("Printing mid-temp and low-temp filaments together may cause nozzle clogging or printer damage."); } - } - else if (compatibility == FilamentCompatibilityType::HighMidMixed) { - ret.is_warning = true; - ret.hypetext = hypertext; - ret.string =L("Printing high-temp and mid-temp filaments together may cause nozzle clogging or printer damage."); + return ret; } - else if (compatibility == FilamentCompatibilityType::LowMidMixed) { - ret.is_warning = true; - ret.hypetext = hypertext; - ret.string = L("Printing mid-temp and low-temp filaments together may cause nozzle clogging or printer damage."); - } - - return ret; -} -// Precondition: Print::validate() requires the Print::apply() to be called its invocation. -//BBS: refine seq-print validation logic.....FIXME:StringObjectException *warning can only contain one warning, but there might be many warnings, need a vector -StringObjectException Print::validate(StringObjectException *warning, Polygons* collison_polygons, std::vector>* height_polygons) const -{ - std::vector extruders = this->extruders(); + // Precondition: Print::validate() requires the Print::apply() to be called its invocation. + // BBS: refine seq-print validation logic.....FIXME:StringObjectException *warning can only contain one warning, but there might be many warnings, need a vector + StringObjectException Print::validate(StringObjectException *warning, Polygons *collison_polygons, std::vector> *height_polygons) const + { + std::vector extruders = this->extruders(); - if (m_objects.empty()) - return {std::string()}; + if (m_objects.empty()) + return {std::string()}; - if (extruders.empty()) - return { L("No extrusions under current settings.") }; + if (extruders.empty()) + return {L("No extrusions under current settings.")}; - if (extruders.size() > 1) { - auto ret = check_multi_filament_valid(*this); - if (!ret.string.empty()) + if (extruders.size() > 1) { - ret.type = STRING_EXCEPT_FILAMENTS_DIFFERENT_TEMP; - if (ret.is_warning && warning != nullptr) { - *warning = ret; - //return {}; - }else - return ret; + auto ret = check_multi_filament_valid(*this); + if (!ret.string.empty()) + { + ret.type = STRING_EXCEPT_FILAMENTS_DIFFERENT_TEMP; + if (ret.is_warning && warning != nullptr) + { + *warning = ret; + // return {}; + } + else + return ret; + } } - } - if (m_config.print_sequence == PrintSequence::ByObject && m_objects.size() > 1) { - if (m_config.timelapse_type == TimelapseType::tlSmooth) - return {L("Smooth mode of timelapse is not supported when \"by object\" sequence is enabled.")}; + if (m_config.print_sequence == PrintSequence::ByObject && m_objects.size() > 1) + { + if (m_config.timelapse_type == TimelapseType::tlSmooth) + return {L("Smooth mode of timelapse is not supported when \"by object\" sequence is enabled.")}; - if (m_config.enable_wrapping_detection) { - StringObjectException clumping_detection_setting_err; - clumping_detection_setting_err.string = L("Clumping detection is not supported when \"by object\" sequence is enabled."); - clumping_detection_setting_err.opt_key = "enable_wrapping_detection"; - return clumping_detection_setting_err; - } + if (m_config.enable_wrapping_detection) + { + StringObjectException clumping_detection_setting_err; + clumping_detection_setting_err.string = L("Clumping detection is not supported when \"by object\" sequence is enabled."); + clumping_detection_setting_err.opt_key = "enable_wrapping_detection"; + return clumping_detection_setting_err; + } - //BBS: refine seq-print validation logic - auto ret = sequential_print_clearance_valid(*this, collison_polygons, height_polygons); - if (!ret.string.empty()) { - ret.type = STRING_EXCEPT_OBJECT_COLLISION_IN_SEQ_PRINT; - return ret; + // BBS: refine seq-print validation logic + auto ret = sequential_print_clearance_valid(*this, collison_polygons, height_polygons); + if (!ret.string.empty()) + { + ret.type = STRING_EXCEPT_OBJECT_COLLISION_IN_SEQ_PRINT; + return ret; + } } - } - else { - //BBS - auto ret = layered_print_cleareance_valid(*this, warning); - if (!ret.string.empty()) { - ret.type = STRING_EXCEPT_OBJECT_COLLISION_IN_LAYER_PRINT; - return ret; + else + { + // BBS + auto ret = layered_print_cleareance_valid(*this, warning); + if (!ret.string.empty()) + { + ret.type = STRING_EXCEPT_OBJECT_COLLISION_IN_LAYER_PRINT; + return ret; + } } - } - if (m_config.enable_prime_tower) { - } else { - if (m_config.enable_wrapping_detection && warning!=nullptr) { - StringObjectException warningtemp; - warningtemp.string = L("Prime tower is required for clumping detection; otherwise, there may be flaws on the model."); - warningtemp.opt_key = "enable_prime_tower"; - warningtemp.is_warning = true; - *warning = warningtemp; + if (m_config.enable_prime_tower) + { + } + else + { + if (m_config.enable_wrapping_detection && warning != nullptr) + { + StringObjectException warningtemp; + warningtemp.string = L("Prime tower is required for clumping detection; otherwise, there may be flaws on the model."); + warningtemp.opt_key = "enable_prime_tower"; + warningtemp.is_warning = true; + *warning = warningtemp; + } } - } - if (m_config.spiral_mode) { - size_t total_copies_count = 0; - for (const PrintObject* object : m_objects) - total_copies_count += object->instances().size(); - // #4043 - if (total_copies_count > 1 && m_config.print_sequence != PrintSequence::ByObject) - return {L("Please select \"By object\" print sequence to print multiple objects in spiral vase mode."), nullptr, "spiral_mode"}; - bool SFFF_enabled = false; - for (const PrintObject *object : m_objects) { - auto cfg = object->object_extruders(); - if (cfg.size() > 1) SFFF_enabled = true; - } - assert(m_objects.size() == 1); - if (m_objects.front()->all_regions().size() > 1 || SFFF_enabled) - return {L("The spiral vase mode does not work when an object contains more than one materials."), nullptr, "spiral_mode"}; - } + if (m_config.spiral_mode) + { + size_t total_copies_count = 0; + for (const PrintObject *object : m_objects) + total_copies_count += object->instances().size(); + // #4043 + if (total_copies_count > 1 && m_config.print_sequence != PrintSequence::ByObject) + return {L("Please select \"By object\" print sequence to print multiple objects in spiral vase mode."), nullptr, "spiral_mode"}; + bool SFFF_enabled = false; + for (const PrintObject *object : m_objects) + { + auto cfg = object->object_extruders(); + if (cfg.size() > 1) + SFFF_enabled = true; + } + assert(m_objects.size() == 1); + if (m_objects.front()->all_regions().size() > 1 || SFFF_enabled) + return {L("The spiral vase mode does not work when an object contains more than one materials."), nullptr, "spiral_mode"}; + } - // Cache of layer height profiles for checking: - // 1) Whether all layers are synchronized if printing with wipe tower and / or unsynchronized supports. - // 2) Whether layer height is constant for Organic supports. - // 3) Whether build volume Z is not violated. - std::vector> layer_height_profiles; - auto layer_height_profile = [this, &layer_height_profiles](const size_t print_object_idx) -> const std::vector& { - const PrintObject &print_object = *m_objects[print_object_idx]; - if (layer_height_profiles.empty()) - layer_height_profiles.assign(m_objects.size(), std::vector()); - std::vector &profile = layer_height_profiles[print_object_idx]; - if (profile.empty()) - PrintObject::update_layer_height_profile(*print_object.model_object(), print_object.slicing_parameters(), profile); - return profile; - }; + // Cache of layer height profiles for checking: + // 1) Whether all layers are synchronized if printing with wipe tower and / or unsynchronized supports. + // 2) Whether layer height is constant for Organic supports. + // 3) Whether build volume Z is not violated. + std::vector> layer_height_profiles; + auto layer_height_profile = [this, &layer_height_profiles](const size_t print_object_idx) -> const std::vector & + { + const PrintObject &print_object = *m_objects[print_object_idx]; + if (layer_height_profiles.empty()) + layer_height_profiles.assign(m_objects.size(), std::vector()); + std::vector &profile = layer_height_profiles[print_object_idx]; + if (profile.empty()) + PrintObject::update_layer_height_profile(*print_object.model_object(), print_object.slicing_parameters(), profile); + return profile; + }; + + // Custom layering is not allowed for tree supports as of now. + for (size_t print_object_idx = 0; print_object_idx < m_objects.size(); ++print_object_idx) + { + PrintObject &print_object = *m_objects[print_object_idx]; + print_object.has_variable_layer_heights = false; + if (print_object.has_support_material() && is_tree(print_object.config().support_type.value) && + print_object.model_object()->has_custom_layering()) + { + if (const std::vector &layers = layer_height_profile(print_object_idx); !layers.empty()) + if (!check_object_layers_fixed(print_object.slicing_parameters(), layers)) + { + print_object.has_variable_layer_heights = true; + BOOST_LOG_TRIVIAL(warning) << "print_object: " << print_object.model_object()->name + << " has_variable_layer_heights: " << print_object.has_variable_layer_heights; + if (print_object.config().support_style.value == smsTreeOrganic) + return {L("Variable layer height is not supported with Organic supports.")}; + } + } + } + if (this->has_wipe_tower() && !m_objects.empty()) + { + // Make sure all extruders use same diameter filament and have the same nozzle diameter + // EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments + double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front()); + double first_filament_diam = m_config.filament_diameter.get_at(extruders.front()); + for (const auto &extruder_idx : extruders) + { + double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx); + double filament_diam = m_config.filament_diameter.get_at(extruder_idx); + if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam || std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) + // BBS: remove L() + return {L("Different nozzle diameters and different filament diameters is not allowed when prime tower is enabled.")}; + } - // Custom layering is not allowed for tree supports as of now. - for (size_t print_object_idx = 0; print_object_idx < m_objects.size(); ++print_object_idx) { - PrintObject &print_object = *m_objects[print_object_idx]; - print_object.has_variable_layer_heights = false; - if (print_object.has_support_material() && is_tree(print_object.config().support_type.value) && - print_object.model_object()->has_custom_layering()) { - if (const std::vector &layers = layer_height_profile(print_object_idx); !layers.empty()) - if (!check_object_layers_fixed(print_object.slicing_parameters(), layers)) { - print_object.has_variable_layer_heights = true; - BOOST_LOG_TRIVIAL(warning) << "print_object: " << print_object.model_object()->name - << " has_variable_layer_heights: " << print_object.has_variable_layer_heights; - if (print_object.config().support_style.value == smsTreeOrganic) return {L("Variable layer height is not supported with Organic supports.")}; - } - } - } + if (!m_config.use_relative_e_distances) + return {L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).")}; + if (m_config.ooze_prevention) + return {L("Ooze prevention is currently not supported with the prime tower enabled.")}; - if (this->has_wipe_tower() && ! m_objects.empty()) { - // Make sure all extruders use same diameter filament and have the same nozzle diameter - // EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments - double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front()); - double first_filament_diam = m_config.filament_diameter.get_at(extruders.front()); - for (const auto& extruder_idx : extruders) { - double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx); - double filament_diam = m_config.filament_diameter.get_at(extruder_idx); - if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam - || std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) - // BBS: remove L() - return { L("Different nozzle diameters and different filament diameters is not allowed when prime tower is enabled.") }; - } - - if (! m_config.use_relative_e_distances) - return { L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).") }; - if (m_config.ooze_prevention) - return { L("Ooze prevention is currently not supported with the prime tower enabled.") }; - - // BBS: remove following logic and _L() + // BBS: remove following logic and _L() #if 0 if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlinLegacy && m_config.gcode_flavor != gcfMarlinFirmware) @@ -1331,79 +1342,89 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } #endif - if (m_objects.size() > 1) { - // Some of the objects has variable layer height applied by painting or by a table. - bool has_custom_layering = std::any_of(m_objects.begin(), m_objects.end(), - [](const PrintObject* object) { return object->model_object()->has_custom_layering(); }); - - const SlicingParameters &slicing_params0 = m_objects.front()->slicing_parameters(); - size_t tallest_object_idx = 0; - for (size_t i = 1; i < m_objects.size(); ++ i) { - const PrintObject *object = m_objects[i]; - const SlicingParameters &slicing_params = object->slicing_parameters(); - if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON || - std::abs(slicing_params.layer_height - slicing_params0.layer_height ) > EPSILON) - return {L("The prime tower requires that all objects have the same layer heights"), object, "initial_layer_print_height"}; - if (slicing_params.raft_layers() != slicing_params0.raft_layers()) - return {L("The prime tower requires that all objects are printed over the same number of raft layers"), object, "raft_layers"}; - // BBS: support gap can be multiple of object layer height, remove _L() + if (m_objects.size() > 1) + { + // Some of the objects has variable layer height applied by painting or by a table. + bool has_custom_layering = std::any_of(m_objects.begin(), m_objects.end(), + [](const PrintObject *object) + { return object->model_object()->has_custom_layering(); }); + + const SlicingParameters &slicing_params0 = m_objects.front()->slicing_parameters(); + size_t tallest_object_idx = 0; + for (size_t i = 1; i < m_objects.size(); ++i) + { + const PrintObject *object = m_objects[i]; + const SlicingParameters &slicing_params = object->slicing_parameters(); + if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON || + std::abs(slicing_params.layer_height - slicing_params0.layer_height) > EPSILON) + return {L("The prime tower requires that all objects have the same layer heights"), object, "initial_layer_print_height"}; + if (slicing_params.raft_layers() != slicing_params0.raft_layers()) + return {L("The prime tower requires that all objects are printed over the same number of raft layers"), object, "raft_layers"}; + // BBS: support gap can be multiple of object layer height, remove _L() #if 0 if (slicing_params0.gap_object_support != slicing_params.gap_object_support || slicing_params0.gap_support_object != slicing_params.gap_support_object) return {("The prime tower is only supported for multiple objects if they are printed with the same support_top_z_distance"), object}; #endif - if (!equal_layering(slicing_params, slicing_params0)) - return { L("The prime tower requires that all objects are sliced with the same layer heights."), object }; - if (has_custom_layering) { - auto &lh = layer_height_profile(i); - auto &lh_tallest = layer_height_profile(tallest_object_idx); - if (*(lh.end()-2) > *(lh_tallest.end()-2)) - tallest_object_idx = i; + if (!equal_layering(slicing_params, slicing_params0)) + return {L("The prime tower requires that all objects are sliced with the same layer heights."), object}; + if (has_custom_layering) + { + auto &lh = layer_height_profile(i); + auto &lh_tallest = layer_height_profile(tallest_object_idx); + if (*(lh.end() - 2) > *(lh_tallest.end() - 2)) + tallest_object_idx = i; + } } - } - // BBS: remove obsolete logics and _L() - if (has_custom_layering) { - std::vector> layer_z_series; - layer_z_series.assign(m_objects.size(), std::vector()); + // BBS: remove obsolete logics and _L() + if (has_custom_layering) + { + std::vector> layer_z_series; + layer_z_series.assign(m_objects.size(), std::vector()); - for (size_t idx_object = 0; idx_object < m_objects.size(); ++idx_object) { - layer_z_series[idx_object] = generate_object_layers(m_objects[idx_object]->slicing_parameters(), layer_height_profiles[idx_object], m_objects[idx_object]->config().precise_z_height.value); - } + for (size_t idx_object = 0; idx_object < m_objects.size(); ++idx_object) + { + layer_z_series[idx_object] = generate_object_layers(m_objects[idx_object]->slicing_parameters(), layer_height_profiles[idx_object], m_objects[idx_object]->config().precise_z_height.value); + } - for (size_t idx_object = 0; idx_object < m_objects.size(); ++idx_object) { - if (idx_object == tallest_object_idx) continue; - // Check that the layer height profiles are equal. This will happen when one object is - // a copy of another, or when a layer height modifier is used the same way on both objects. - // The latter case might create a floating point inaccuracy mismatch, so compare - // element-wise using an epsilon check. - size_t i = 0; - const coordf_t eps = 0.5 * EPSILON; // layers closer than EPSILON will be merged later. Let's make - // this check a bit more sensitive to make sure we never consider two different layers as one. - while (i < layer_z_series[idx_object].size() && i < layer_z_series[tallest_object_idx].size()) { - // BBS: remove the break condition, because a variable layer height object and a new object will not be checked when slicing - //if (i % 2 == 0 && layer_height_profiles[tallest_object_idx][i] > layer_height_profiles[idx_object][layer_height_profiles[idx_object].size() - 2]) - // break; - if (std::abs(layer_z_series[idx_object][i] - layer_z_series[tallest_object_idx][i]) > eps) - return {L("The prime tower is only supported if all objects have the same variable layer height")}; - ++i; + for (size_t idx_object = 0; idx_object < m_objects.size(); ++idx_object) + { + if (idx_object == tallest_object_idx) + continue; + // Check that the layer height profiles are equal. This will happen when one object is + // a copy of another, or when a layer height modifier is used the same way on both objects. + // The latter case might create a floating point inaccuracy mismatch, so compare + // element-wise using an epsilon check. + size_t i = 0; + const coordf_t eps = 0.5 * EPSILON; // layers closer than EPSILON will be merged later. Let's make + // this check a bit more sensitive to make sure we never consider two different layers as one. + while (i < layer_z_series[idx_object].size() && i < layer_z_series[tallest_object_idx].size()) + { + // BBS: remove the break condition, because a variable layer height object and a new object will not be checked when slicing + // if (i % 2 == 0 && layer_height_profiles[tallest_object_idx][i] > layer_height_profiles[idx_object][layer_height_profiles[idx_object].size() - 2]) + // break; + if (std::abs(layer_z_series[idx_object][i] - layer_z_series[tallest_object_idx][i]) > eps) + return {L("The prime tower is only supported if all objects have the same variable layer height")}; + ++i; + } } } } } - } - { - // Find the smallest used nozzle diameter and the number of unique nozzle diameters. - double min_nozzle_diameter = std::numeric_limits::max(); - double max_nozzle_diameter = 0; - for (unsigned int extruder_id : extruders) { - double dmr = m_config.nozzle_diameter.get_at(extruder_id); - min_nozzle_diameter = std::min(min_nozzle_diameter, dmr); - max_nozzle_diameter = std::max(max_nozzle_diameter, dmr); - } - - // BBS: remove L() + { + // Find the smallest used nozzle diameter and the number of unique nozzle diameters. + double min_nozzle_diameter = std::numeric_limits::max(); + double max_nozzle_diameter = 0; + for (unsigned int extruder_id : extruders) + { + double dmr = m_config.nozzle_diameter.get_at(extruder_id); + min_nozzle_diameter = std::min(min_nozzle_diameter, dmr); + max_nozzle_diameter = std::max(max_nozzle_diameter, dmr); + } + + // BBS: remove L() #if 0 // We currently allow one to assign extruders with a higher index than the number // of physical extruders the machine is equipped with, as the Printer::apply() clamps them. @@ -1413,27 +1434,35 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* return ("One or more object were assigned an extruder that the printer does not have."); #endif - auto validate_extrusion_width = [/*min_nozzle_diameter,*/ max_nozzle_diameter](const ConfigBase &config, const char *opt_key, double layer_height, std::string &err_msg) -> bool { - // This may change in the future, if we switch to "extrusion width wrt. nozzle diameter" - // instead of currently used logic "extrusion width wrt. layer height", see GH issues #1923 #2829. -// double extrusion_width_min = config.get_abs_value(opt_key, min_nozzle_diameter); -// double extrusion_width_max = config.get_abs_value(opt_key, max_nozzle_diameter); - double extrusion_width_min = config.get_abs_value(opt_key); - double extrusion_width_max = config.get_abs_value(opt_key); - if (extrusion_width_min == 0) { - // Default "auto-generated" extrusion width is always valid. - } else if (extrusion_width_min <= layer_height) { - err_msg = L("Too small line width"); - return false; - } else if (extrusion_width_max > max_nozzle_diameter * 2.5) { - err_msg = L("Too large line width"); - return false; - } - return true; - }; - for (PrintObject *object : m_objects) { - if (object->has_support_material()) { - // BBS: remove useless logics and L() + auto validate_extrusion_width = [/*min_nozzle_diameter,*/ max_nozzle_diameter](const ConfigBase &config, const char *opt_key, double layer_height, std::string &err_msg) -> bool + { + // This may change in the future, if we switch to "extrusion width wrt. nozzle diameter" + // instead of currently used logic "extrusion width wrt. layer height", see GH issues #1923 #2829. + // double extrusion_width_min = config.get_abs_value(opt_key, min_nozzle_diameter); + // double extrusion_width_max = config.get_abs_value(opt_key, max_nozzle_diameter); + double extrusion_width_min = config.get_abs_value(opt_key); + double extrusion_width_max = config.get_abs_value(opt_key); + if (extrusion_width_min == 0) + { + // Default "auto-generated" extrusion width is always valid. + } + else if (extrusion_width_min <= layer_height) + { + err_msg = L("Too small line width"); + return false; + } + else if (extrusion_width_max > max_nozzle_diameter * 2.5) + { + err_msg = L("Too large line width"); + return false; + } + return true; + }; + for (PrintObject *object : m_objects) + { + if (object->has_support_material()) + { + // BBS: remove useless logics and L() #if 0 if ((object->config().support_filament == 0 || object->config().support_interface_filament == 0) && max_nozzle_diameter - min_nozzle_diameter > EPSILON) { // The object has some form of support and either support_filament or support_interface_filament @@ -1445,100 +1474,109 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } #endif - // BBS + // BBS #if 0 if (this->has_wipe_tower() && object->config().independent_support_layer_height) { return {L("The prime tower requires that support has the same layer height as object."), object, "support_filament"}; } #endif - } + } - // Do we have custom support data that would not be used? - // Notify the user in that case. - if (! object->has_support() && warning) { - for (const ModelVolume* mv : object->model_object()->volumes) { - bool has_enforcers = mv->is_support_enforcer() || - (mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER)); - if (has_enforcers) { - StringObjectException warningtemp; - warningtemp.string = L("Support enforcers are used but support is not enabled. Please enable support."); - warningtemp.object = object; - *warning = warningtemp; - break; + // Do we have custom support data that would not be used? + // Notify the user in that case. + if (!object->has_support() && warning) + { + for (const ModelVolume *mv : object->model_object()->volumes) + { + bool has_enforcers = mv->is_support_enforcer() || + (mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER)); + if (has_enforcers) + { + StringObjectException warningtemp; + warningtemp.string = L("Support enforcers are used but support is not enabled. Please enable support."); + warningtemp.object = object; + *warning = warningtemp; + break; + } } } - } - double initial_layer_print_height = m_config.initial_layer_print_height.value; - double first_layer_min_nozzle_diameter; - if (object->has_raft()) { - // if we have raft layers, only support material extruder is used on first layer - size_t first_layer_extruder = object->config().raft_layers == 1 - ? object->config().support_interface_filament-1 - : object->config().support_filament-1; - first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ? - min_nozzle_diameter : - m_config.nozzle_diameter.get_at(first_layer_extruder); - } else { - // if we don't have raft layers, any nozzle diameter is potentially used in first layer - first_layer_min_nozzle_diameter = min_nozzle_diameter; - } - if (initial_layer_print_height > first_layer_min_nozzle_diameter) - return {L("Layer height cannot exceed nozzle diameter"), object, "initial_layer_print_height"}; - - // validate layer_height - double layer_height = object->config().layer_height.value; - if (layer_height > min_nozzle_diameter) - return {L("Layer height cannot exceed nozzle diameter"), object, "layer_height"}; - - // Validate extrusion widths. - std::string err_msg; - if (!validate_extrusion_width(object->config(), "line_width", layer_height, err_msg)) - return {err_msg, object, "line_width"}; - if (object->has_support() || object->has_raft()) { - if (!validate_extrusion_width(object->config(), "support_line_width", layer_height, err_msg)) - return {err_msg, object, "support_line_width"}; - } - for (const char *opt_key : { "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "top_surface_line_width","skin_infill_line_width" ,"skeleton_infill_line_width"}) - for (const PrintRegion ®ion : object->all_regions()) - if (!validate_extrusion_width(region.config(), opt_key, layer_height, err_msg)) - return {err_msg, object, opt_key}; + double initial_layer_print_height = m_config.initial_layer_print_height.value; + double first_layer_min_nozzle_diameter; + if (object->has_raft()) + { + // if we have raft layers, only support material extruder is used on first layer + size_t first_layer_extruder = object->config().raft_layers == 1 + ? object->config().support_interface_filament - 1 + : object->config().support_filament - 1; + first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ? min_nozzle_diameter : m_config.nozzle_diameter.get_at(first_layer_extruder); + } + else + { + // if we don't have raft layers, any nozzle diameter is potentially used in first layer + first_layer_min_nozzle_diameter = min_nozzle_diameter; + } + if (initial_layer_print_height > first_layer_min_nozzle_diameter) + return {L("Layer height cannot exceed nozzle diameter"), object, "initial_layer_print_height"}; + + // validate layer_height + double layer_height = object->config().layer_height.value; + if (layer_height > min_nozzle_diameter) + return {L("Layer height cannot exceed nozzle diameter"), object, "layer_height"}; + + // Validate extrusion widths. + std::string err_msg; + if (!validate_extrusion_width(object->config(), "line_width", layer_height, err_msg)) + return {err_msg, object, "line_width"}; + if (object->has_support() || object->has_raft()) + { + if (!validate_extrusion_width(object->config(), "support_line_width", layer_height, err_msg)) + return {err_msg, object, "support_line_width"}; + } + for (const char *opt_key : {"inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "top_surface_line_width", "skin_infill_line_width", "skeleton_infill_line_width"}) + for (const PrintRegion ®ion : object->all_regions()) + if (!validate_extrusion_width(region.config(), opt_key, layer_height, err_msg)) + return {err_msg, object, opt_key}; + } } - } - - const ConfigOptionDef* bed_type_def = print_config_def.get("curr_bed_type"); - assert(bed_type_def != nullptr); + const ConfigOptionDef *bed_type_def = print_config_def.get("curr_bed_type"); + assert(bed_type_def != nullptr); - const t_config_enum_values* bed_type_keys_map = bed_type_def->enum_keys_map; - for (unsigned int extruder_id : extruders) { - const ConfigOptionInts* bed_temp_opt = m_config.option(get_bed_temp_key(m_config.curr_bed_type)); - for (unsigned int extruder_id : extruders) { - int curr_bed_temp = bed_temp_opt->get_at(extruder_id); - if (curr_bed_temp == 0 && bed_type_keys_map != nullptr) { - std::string bed_type_name; - for (auto item : *bed_type_keys_map) { - if (item.second == m_config.curr_bed_type) { - bed_type_name = item.first; - break; + const t_config_enum_values *bed_type_keys_map = bed_type_def->enum_keys_map; + for (unsigned int extruder_id : extruders) + { + const ConfigOptionInts *bed_temp_opt = m_config.option(get_bed_temp_key(m_config.curr_bed_type)); + for (unsigned int extruder_id : extruders) + { + int curr_bed_temp = bed_temp_opt->get_at(extruder_id); + if (curr_bed_temp == 0 && bed_type_keys_map != nullptr) + { + std::string bed_type_name; + for (auto item : *bed_type_keys_map) + { + if (item.second == m_config.curr_bed_type) + { + bed_type_name = item.first; + break; + } } - } - StringObjectException except; - except.string = Slic3r::format(L("Plate %d: %s does not support filament %s"), this->get_plate_index() + 1, L(bed_type_name), extruder_id + 1); - except.string += "\n"; - except.type = STRING_EXCEPT_FILAMENT_NOT_MATCH_BED_TYPE; - except.params.push_back(std::to_string(this->get_plate_index() + 1)); - except.params.push_back(L(bed_type_name)); - except.params.push_back(std::to_string(extruder_id+1)); - except.object = nullptr; - return except; + StringObjectException except; + except.string = Slic3r::format(L("Plate %d: %s does not support filament %s"), this->get_plate_index() + 1, L(bed_type_name), extruder_id + 1); + except.string += "\n"; + except.type = STRING_EXCEPT_FILAMENT_NOT_MATCH_BED_TYPE; + except.params.push_back(std::to_string(this->get_plate_index() + 1)); + except.params.push_back(L(bed_type_name)); + except.params.push_back(std::to_string(extruder_id + 1)); + except.object = nullptr; + return except; + } } } - } - return {}; -} + return {}; + } #if 0 // the bounding box of objects placed in copies position @@ -1597,558 +1635,618 @@ BoundingBox Print::total_bounding_box() const } #endif -double Print::skirt_first_layer_height() const -{ - return m_config.initial_layer_print_height.value; -} - -Flow Print::brim_flow() const -{ - ConfigOptionFloat width = m_config.initial_layer_line_width; - if (width.value == 0) - width = m_print_regions.front()->config().inner_wall_line_width; - if (width.value == 0) - width = m_objects.front()->config().line_width; - - /* We currently use a random region's perimeter extruder. - While this works for most cases, we should probably consider all of the perimeter - extruders and take the one with, say, the smallest index. - The same logic should be applied to the code that selects the extruder during G-code - generation as well. */ - return Flow::new_from_config_width( - frPerimeter, - width, - (float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().wall_filament-1), - (float)this->skirt_first_layer_height()); -} - -Flow Print::skirt_flow() const -{ - ConfigOptionFloat width = m_config.initial_layer_line_width; - if (width.value == 0) - width = m_objects.front()->config().line_width; - - /* We currently use a random object's support material extruder. - While this works for most cases, we should probably consider all of the support material - extruders and take the one with, say, the smallest index; - The same logic should be applied to the code that selects the extruder during G-code - generation as well. */ - return Flow::new_from_config_width( - frPerimeter, - width, - (float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_filament-1), - (float)this->skirt_first_layer_height()); -} + double Print::skirt_first_layer_height() const + { + return m_config.initial_layer_print_height.value; + } -bool Print::has_support_material() const -{ - for (const PrintObject *object : m_objects) - if (object->has_support_material()) - return true; - return false; -} + Flow Print::brim_flow() const + { + ConfigOptionFloat width = m_config.initial_layer_line_width; + if (width.value == 0) + width = m_print_regions.front()->config().inner_wall_line_width; + if (width.value == 0) + width = m_objects.front()->config().line_width; + + /* We currently use a random region's perimeter extruder. + While this works for most cases, we should probably consider all of the perimeter + extruders and take the one with, say, the smallest index. + The same logic should be applied to the code that selects the extruder during G-code + generation as well. */ + return Flow::new_from_config_width( + frPerimeter, + width, + (float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().wall_filament - 1), + (float)this->skirt_first_layer_height()); + } + + Flow Print::skirt_flow() const + { + ConfigOptionFloat width = m_config.initial_layer_line_width; + if (width.value == 0) + width = m_objects.front()->config().line_width; + + /* We currently use a random object's support material extruder. + While this works for most cases, we should probably consider all of the support material + extruders and take the one with, say, the smallest index; + The same logic should be applied to the code that selects the extruder during G-code + generation as well. */ + return Flow::new_from_config_width( + frPerimeter, + width, + (float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_filament - 1), + (float)this->skirt_first_layer_height()); + } + + bool Print::has_support_material() const + { + for (const PrintObject *object : m_objects) + if (object->has_support_material()) + return true; + return false; + } -/* This method assigns extruders to the volumes having a material - but not having extruders set in the volume config. */ -void Print::auto_assign_extruders(ModelObject* model_object) const -{ - // only assign extruders if object has more than one volume - if (model_object->volumes.size() < 2) - return; + /* This method assigns extruders to the volumes having a material + but not having extruders set in the volume config. */ + void Print::auto_assign_extruders(ModelObject *model_object) const + { + // only assign extruders if object has more than one volume + if (model_object->volumes.size() < 2) + return; -// size_t extruders = m_config.nozzle_diameter.values.size(); - for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { - ModelVolume *volume = model_object->volumes[volume_id]; - //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned. - if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder")) - volume->config.set("extruder", int(volume_id + 1)); + // size_t extruders = m_config.nozzle_diameter.values.size(); + for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++volume_id) + { + ModelVolume *volume = model_object->volumes[volume_id]; + // FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned. + if ((volume->is_model_part() || volume->is_modifier()) && !volume->material_id().empty() && !volume->config.has("extruder")) + volume->config.set("extruder", int(volume_id + 1)); + } } -} -void PrintObject::set_shared_object(PrintObject *object) -{ - m_shared_object = object; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, found shared object from %2%")%this%m_shared_object; -} + void PrintObject::set_shared_object(PrintObject *object) + { + m_shared_object = object; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, found shared object from %2%") % this % m_shared_object; + } -void PrintObject::clear_shared_object() -{ - if (m_shared_object) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, clear previous shared object data %2%")%this %m_shared_object; - m_layers.clear(); - m_support_layers.clear(); + void PrintObject::clear_shared_object() + { + if (m_shared_object) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, clear previous shared object data %2%") % this % m_shared_object; + m_layers.clear(); + m_support_layers.clear(); - m_shared_object = nullptr; + m_shared_object = nullptr; - invalidate_all_steps_without_cancel(); + invalidate_all_steps_without_cancel(); + } } -} -void PrintObject::copy_layers_from_shared_object() -{ - if (m_shared_object) { - m_layers.clear(); - m_support_layers.clear(); + void PrintObject::copy_layers_from_shared_object() + { + if (m_shared_object) + { + m_layers.clear(); + m_support_layers.clear(); - firstLayerObjSliceByVolume.clear(); - firstLayerObjSliceByGroups.clear(); + firstLayerObjSliceByVolume.clear(); + firstLayerObjSliceByGroups.clear(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, copied layers from object %2%")%this%m_shared_object; - m_layers = m_shared_object->layers(); - m_support_layers = m_shared_object->support_layers(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, copied layers from object %2%") % this % m_shared_object; + m_layers = m_shared_object->layers(); + m_support_layers = m_shared_object->support_layers(); - firstLayerObjSliceByVolume = m_shared_object->firstLayerObjSlice(); - firstLayerObjSliceByGroups = m_shared_object->firstLayerObjGroups(); + firstLayerObjSliceByVolume = m_shared_object->firstLayerObjSlice(); + firstLayerObjSliceByGroups = m_shared_object->firstLayerObjGroups(); + } } -} -void PrintObject::copy_layers_overhang_from_shared_object() -{ - if (m_shared_object) { - for (size_t index = 0; index < m_layers.size() && index < m_shared_object->m_layers.size(); index++) + void PrintObject::copy_layers_overhang_from_shared_object() + { + if (m_shared_object) { - Layer* layer_src = m_layers[index]; - layer_src->loverhangs = m_shared_object->m_layers[index]->loverhangs; - layer_src->loverhangs_bbox = m_shared_object->m_layers[index]->loverhangs_bbox; + for (size_t index = 0; index < m_layers.size() && index < m_shared_object->m_layers.size(); index++) + { + Layer *layer_src = m_layers[index]; + layer_src->loverhangs = m_shared_object->m_layers[index]->loverhangs; + layer_src->loverhangs_bbox = m_shared_object->m_layers[index]->loverhangs_bbox; + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, copied layer overhang from object %2%") % this % m_shared_object; } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, copied layer overhang from object %2%")%this%m_shared_object; } -} - -// BBS -BoundingBox PrintObject::get_first_layer_bbox(float& a, float& layer_height, std::string& name) -{ - BoundingBox bbox; - a = 0; - name = this->model_object()->name; - if (layer_count() > 0) { - auto layer = get_layer(0); - layer_height = layer->height; - // only work for object with single instance - auto shift = instances()[0].shift_without_plate_offset(); - for (auto bb : layer->lslices_bboxes) - { - bb.translate(shift.x(), shift.y()); - bbox.merge(bb); - } - for (auto slice : layer->lslices) { - a += area(slice); + // BBS + BoundingBox PrintObject::get_first_layer_bbox(float &a, float &layer_height, std::string &name) + { + BoundingBox bbox; + a = 0; + name = this->model_object()->name; + if (layer_count() > 0) + { + auto layer = get_layer(0); + layer_height = layer->height; + // only work for object with single instance + auto shift = instances()[0].shift_without_plate_offset(); + for (auto bb : layer->lslices_bboxes) + { + bb.translate(shift.x(), shift.y()); + bbox.merge(bb); + } + for (auto slice : layer->lslices) + { + a += area(slice); + } } + if (has_brim()) + bbox = firstLayerObjectBrimBoundingBox; + return bbox; } - if (has_brim()) - bbox = firstLayerObjectBrimBoundingBox; - return bbox; -} -// BBS: map print object with its first layer's first extruder -std::map getObjectExtruderMap(const Print& print) { - std::map objectExtruderMap; - for (const PrintObject* object : print.objects()) { - // BBS - if (object->object_first_layer_wall_extruders.empty()){ - unsigned int objectFirstLayerFirstExtruder = print.config().filament_diameter.size(); - auto firstLayerRegions = object->layers().front()->regions(); - if (!firstLayerRegions.empty()) { - for (const LayerRegion* regionPtr : firstLayerRegions) { - if (regionPtr->has_extrusions()) - objectFirstLayerFirstExtruder = std::min(objectFirstLayerFirstExtruder, - regionPtr->region().extruder(frExternalPerimeter)); + // BBS: map print object with its first layer's first extruder + std::map getObjectExtruderMap(const Print &print) + { + std::map objectExtruderMap; + for (const PrintObject *object : print.objects()) + { + // BBS + if (object->object_first_layer_wall_extruders.empty()) + { + unsigned int objectFirstLayerFirstExtruder = print.config().filament_diameter.size(); + auto firstLayerRegions = object->layers().front()->regions(); + if (!firstLayerRegions.empty()) + { + for (const LayerRegion *regionPtr : firstLayerRegions) + { + if (regionPtr->has_extrusions()) + objectFirstLayerFirstExtruder = std::min(objectFirstLayerFirstExtruder, + regionPtr->region().extruder(frExternalPerimeter)); + } } + objectExtruderMap.insert(std::make_pair(object->id(), objectFirstLayerFirstExtruder)); + } + else + { + objectExtruderMap.insert(std::make_pair(object->id(), object->object_first_layer_wall_extruders.front())); } - objectExtruderMap.insert(std::make_pair(object->id(), objectFirstLayerFirstExtruder)); - } - else { - objectExtruderMap.insert(std::make_pair(object->id(), object->object_first_layer_wall_extruders.front())); } - } - return objectExtruderMap; -} - -// Slicing process, running at a background thread. -void Print::process(std::unordered_map* slice_time, bool use_cache) -{ - long long start_time = 0, end_time = 0; - if (slice_time) { - (*slice_time)[TIME_USING_CACHE] = 0; - (*slice_time)[TIME_MAKE_PERIMETERS] = 0; - (*slice_time)[TIME_INFILL] = 0; - (*slice_time)[TIME_GENERATE_SUPPORT] = 0; + return objectExtruderMap; } + // Slicing process, running at a background thread. + void Print::process(std::unordered_map *slice_time, bool use_cache) + { + long long start_time = 0, end_time = 0; + if (slice_time) + { + (*slice_time)[TIME_USING_CACHE] = 0; + (*slice_time)[TIME_MAKE_PERIMETERS] = 0; + (*slice_time)[TIME_INFILL] = 0; + (*slice_time)[TIME_GENERATE_SUPPORT] = 0; + } - name_tbb_thread_pool_threads_set_locale(); + name_tbb_thread_pool_threads_set_locale(); - //compute the PrintObject with the same geometries - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, enter, use_cache=%2%, object size=%3%")%this%use_cache%m_objects.size(); - if (m_objects.empty()) - return; + // compute the PrintObject with the same geometries + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, enter, use_cache=%2%, object size=%3%") % this % use_cache % m_objects.size(); + if (m_objects.empty()) + return; - for (PrintObject *obj : m_objects) - obj->clear_shared_object(); + for (PrintObject *obj : m_objects) + obj->clear_shared_object(); - //add the print_object share check logic - auto is_print_object_the_same = [this](const PrintObject* object1, const PrintObject* object2) -> bool{ - if (object1->trafo().matrix() != object2->trafo().matrix()) - return false; - const ModelObject* model_obj1 = object1->model_object(); - const ModelObject* model_obj2 = object2->model_object(); - if (model_obj1->volumes.size() != model_obj2->volumes.size()) - return false; - bool has_extruder1 = model_obj1->config.has("extruder"); - bool has_extruder2 = model_obj2->config.has("extruder"); - if ((has_extruder1 != has_extruder2) - || (has_extruder1 && model_obj1->config.extruder() != model_obj2->config.extruder())) - return false; - for (int index = 0; index < model_obj1->volumes.size(); index++) { - const ModelVolume &model_volume1 = *model_obj1->volumes[index]; - const ModelVolume &model_volume2 = *model_obj2->volumes[index]; - if (model_volume1.type() != model_volume2.type()) - return false; - if (model_volume1.mesh_ptr() != model_volume2.mesh_ptr()) - return false; - if (!(model_volume1.get_transformation() == model_volume2.get_transformation())) - return false; - has_extruder1 = model_volume1.config.has("extruder"); - has_extruder2 = model_volume2.config.has("extruder"); - if ((has_extruder1 != has_extruder2) - || (has_extruder1 && model_volume1.config.extruder() != model_volume2.config.extruder())) - return false; - if (!model_volume1.supported_facets.equals(model_volume2.supported_facets)) + // add the print_object share check logic + auto is_print_object_the_same = [this](const PrintObject *object1, const PrintObject *object2) -> bool + { + if (object1->trafo().matrix() != object2->trafo().matrix()) return false; - if (!model_volume1.fuzzy_skin_facets.equals(model_volume2.fuzzy_skin_facets)) + const ModelObject *model_obj1 = object1->model_object(); + const ModelObject *model_obj2 = object2->model_object(); + if (model_obj1->volumes.size() != model_obj2->volumes.size()) return false; - if (!model_volume1.seam_facets.equals(model_volume2.seam_facets)) + bool has_extruder1 = model_obj1->config.has("extruder"); + bool has_extruder2 = model_obj2->config.has("extruder"); + if ((has_extruder1 != has_extruder2) || (has_extruder1 && model_obj1->config.extruder() != model_obj2->config.extruder())) return false; - if (!model_volume1.mmu_segmentation_facets.equals(model_volume2.mmu_segmentation_facets)) + for (int index = 0; index < model_obj1->volumes.size(); index++) + { + const ModelVolume &model_volume1 = *model_obj1->volumes[index]; + const ModelVolume &model_volume2 = *model_obj2->volumes[index]; + if (model_volume1.type() != model_volume2.type()) + return false; + if (model_volume1.mesh_ptr() != model_volume2.mesh_ptr()) + return false; + if (!(model_volume1.get_transformation() == model_volume2.get_transformation())) + return false; + has_extruder1 = model_volume1.config.has("extruder"); + has_extruder2 = model_volume2.config.has("extruder"); + if ((has_extruder1 != has_extruder2) || (has_extruder1 && model_volume1.config.extruder() != model_volume2.config.extruder())) + return false; + if (!model_volume1.supported_facets.equals(model_volume2.supported_facets)) + return false; + if (!model_volume1.fuzzy_skin_facets.equals(model_volume2.fuzzy_skin_facets)) + return false; + if (!model_volume1.seam_facets.equals(model_volume2.seam_facets)) + return false; + if (!model_volume1.mmu_segmentation_facets.equals(model_volume2.mmu_segmentation_facets)) + return false; + if (model_volume1.config.get() != model_volume2.config.get()) + return false; + } + // if (!object1->config().equals(object2->config())) + // return false; + if (model_obj1->layer_height_profile.get() != model_obj2->layer_height_profile.get()) return false; - if (model_volume1.config.get() != model_volume2.config.get()) + if (model_obj1->config.get() != model_obj2->config.get()) return false; - } - //if (!object1->config().equals(object2->config())) - // return false; - if (model_obj1->layer_height_profile.get() != model_obj2->layer_height_profile.get()) - return false; - if (model_obj1->config.get() != model_obj2->config.get()) - return false; - return true; - }; - int object_count = m_objects.size(); - std::set need_slicing_objects; - //std::set re_slicing_objects; - m_reslicing_objects.clear(); - if (!use_cache) { - for (int index = 0; index < object_count; index++) - { - PrintObject *obj = m_objects[index]; - for (PrintObject *slicing_obj : need_slicing_objects) - { - if (is_print_object_the_same(obj, slicing_obj)) { - obj->set_shared_object(slicing_obj); - break; - } - } - if (!obj->get_shared_object()) { - need_slicing_objects.insert(obj); - m_reslicing_objects.insert(obj); - } - } - } - else { - for (int index = 0; index < object_count; index++) - { - PrintObject *obj = m_objects[index]; - if (obj->layer_count() > 0) - need_slicing_objects.insert(obj); - } - for (int index = 0; index < object_count; index++) + return true; + }; + int object_count = m_objects.size(); + std::set need_slicing_objects; + // std::set re_slicing_objects; + m_reslicing_objects.clear(); + if (!use_cache) { - PrintObject *obj = m_objects[index]; - bool found_shared = false; - if (need_slicing_objects.find(obj) == need_slicing_objects.end()) { + for (int index = 0; index < object_count; index++) + { + PrintObject *obj = m_objects[index]; for (PrintObject *slicing_obj : need_slicing_objects) { - if (is_print_object_the_same(obj, slicing_obj)) { + if (is_print_object_the_same(obj, slicing_obj)) + { obj->set_shared_object(slicing_obj); - found_shared = true; break; } } - if (!found_shared) { - BOOST_LOG_TRIVIAL(warning) << boost::format("Also can not find the shared object, identify_id %1%, maybe shared object is skipped")%obj->model_object()->instances[0]->loaded_id; - //throw Slic3r::SlicingError("Can not find the cached data."); - //don't report errot, set use_cache to false, and reslice these objects + if (!obj->get_shared_object()) + { need_slicing_objects.insert(obj); m_reslicing_objects.insert(obj); - //use_cache = false; } } } - } - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": total object counts %1% in current print, need to slice %2%")%m_objects.size()%need_slicing_objects.size(); - BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); - - const AutoContourHolesCompensationParams &auto_contour_holes_compensation_params = AutoContourHolesCompensationParams(m_config); - if (!use_cache) { - - if (slice_time) { - start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); - } - - - for (PrintObject* obj : m_objects) { - if (need_slicing_objects.count(obj) != 0) { - obj->set_auto_circle_compenstaion_params(auto_contour_holes_compensation_params); - obj->make_perimeters(); + else + { + for (int index = 0; index < object_count; index++) + { + PrintObject *obj = m_objects[index]; + if (obj->layer_count() > 0) + need_slicing_objects.insert(obj); } - else { - if (obj->set_started(posSlice)) - obj->set_done(posSlice); - if (obj->set_started(posPerimeters)) - obj->set_done(posPerimeters); + for (int index = 0; index < object_count; index++) + { + PrintObject *obj = m_objects[index]; + bool found_shared = false; + if (need_slicing_objects.find(obj) == need_slicing_objects.end()) + { + for (PrintObject *slicing_obj : need_slicing_objects) + { + if (is_print_object_the_same(obj, slicing_obj)) + { + obj->set_shared_object(slicing_obj); + found_shared = true; + break; + } + } + if (!found_shared) + { + BOOST_LOG_TRIVIAL(warning) << boost::format("Also can not find the shared object, identify_id %1%, maybe shared object is skipped") % obj->model_object()->instances[0]->loaded_id; + // throw Slic3r::SlicingError("Can not find the cached data."); + // don't report errot, set use_cache to false, and reslice these objects + need_slicing_objects.insert(obj); + m_reslicing_objects.insert(obj); + // use_cache = false; + } + } } } - if (slice_time) { - end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); - (*slice_time)[TIME_MAKE_PERIMETERS] = (*slice_time)[TIME_MAKE_PERIMETERS] + end_time - start_time; - start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); - } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": total object counts %1% in current print, need to slice %2%") % m_objects.size() % need_slicing_objects.size(); + BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); - for (PrintObject *obj : m_objects) { - if (need_slicing_objects.count(obj) != 0) { - obj->infill(); - } - else { - if (obj->set_started(posPrepareInfill)) - obj->set_done(posPrepareInfill); - if (obj->set_started(posInfill)) - obj->set_done(posInfill); + const AutoContourHolesCompensationParams &auto_contour_holes_compensation_params = AutoContourHolesCompensationParams(m_config); + if (!use_cache) + { + + if (slice_time) + { + start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); } - } - if (slice_time) { - end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); - (*slice_time)[TIME_INFILL] = (*slice_time)[TIME_INFILL] + end_time - start_time; - } + for (PrintObject *obj : m_objects) + { + if (need_slicing_objects.count(obj) != 0) + { + obj->set_auto_circle_compenstaion_params(auto_contour_holes_compensation_params); + obj->make_perimeters(); + } + else + { + if (obj->set_started(posSlice)) + obj->set_done(posSlice); + if (obj->set_started(posPerimeters)) + obj->set_done(posPerimeters); + } + } - for (PrintObject *obj : m_objects) { - if (need_slicing_objects.count(obj) != 0) { - obj->ironing(); + if (slice_time) + { + end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); + (*slice_time)[TIME_MAKE_PERIMETERS] = (*slice_time)[TIME_MAKE_PERIMETERS] + end_time - start_time; + start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); } - else { - if (obj->set_started(posIroning)) - obj->set_done(posIroning); + + for (PrintObject *obj : m_objects) + { + if (need_slicing_objects.count(obj) != 0) + { + obj->infill(); + } + else + { + if (obj->set_started(posPrepareInfill)) + obj->set_done(posPrepareInfill); + if (obj->set_started(posInfill)) + obj->set_done(posInfill); + } } - } - if (slice_time) { - start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); - } + if (slice_time) + { + end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); + (*slice_time)[TIME_INFILL] = (*slice_time)[TIME_INFILL] + end_time - start_time; + } - tbb::parallel_for(tbb::blocked_range(0, int(m_objects.size())), - [this, need_slicing_objects](const tbb::blocked_range& range) { - for (int i = range.begin(); i < range.end(); i++) { - PrintObject* obj = m_objects[i]; - if (need_slicing_objects.count(obj) != 0) { - obj->generate_support_material(); - } - else { - if (obj->set_started(posSupportMaterial)) - obj->set_done(posSupportMaterial); - } + for (PrintObject *obj : m_objects) + { + if (need_slicing_objects.count(obj) != 0) + { + obj->ironing(); + } + else + { + if (obj->set_started(posIroning)) + obj->set_done(posIroning); } } - ); - if (slice_time) { - end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); - (*slice_time)[TIME_GENERATE_SUPPORT] = (*slice_time)[TIME_GENERATE_SUPPORT] + end_time - start_time; - } + if (slice_time) + { + start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); + } - for (PrintObject* obj : m_objects) { - if (need_slicing_objects.count(obj) != 0) { - obj->detect_overhangs_for_lift(); + tbb::parallel_for(tbb::blocked_range(0, int(m_objects.size())), + [this, need_slicing_objects](const tbb::blocked_range &range) + { + for (int i = range.begin(); i < range.end(); i++) + { + PrintObject *obj = m_objects[i]; + if (need_slicing_objects.count(obj) != 0) + { + obj->generate_support_material(); + } + else + { + if (obj->set_started(posSupportMaterial)) + obj->set_done(posSupportMaterial); + } + } + }); + + if (slice_time) + { + end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); + (*slice_time)[TIME_GENERATE_SUPPORT] = (*slice_time)[TIME_GENERATE_SUPPORT] + end_time - start_time; } - else { - if (obj->set_started(posDetectOverhangsForLift)) - obj->set_done(posDetectOverhangsForLift); + + for (PrintObject *obj : m_objects) + { + if (need_slicing_objects.count(obj) != 0) + { + obj->detect_overhangs_for_lift(); + } + else + { + if (obj->set_started(posDetectOverhangsForLift)) + obj->set_done(posDetectOverhangsForLift); + } } } - } - else { - for (PrintObject *obj : m_objects) { - if (m_reslicing_objects.count(obj) == 0) { - if (obj->set_started(posSlice)) - obj->set_done(posSlice); - if (obj->set_started(posPerimeters)) - obj->set_done(posPerimeters); - if (obj->set_started(posPrepareInfill)) - obj->set_done(posPrepareInfill); - if (obj->set_started(posInfill)) - obj->set_done(posInfill); - if (obj->set_started(posIroning)) - obj->set_done(posIroning); - if (obj->set_started(posSupportMaterial)) - obj->set_done(posSupportMaterial); - if (obj->set_started(posDetectOverhangsForLift)) - obj->set_done(posDetectOverhangsForLift); - } - else { - obj->set_auto_circle_compenstaion_params(auto_contour_holes_compensation_params); - obj->make_perimeters(); - obj->infill(); - obj->ironing(); - obj->generate_support_material(); - obj->detect_overhangs_for_lift(); + else + { + for (PrintObject *obj : m_objects) + { + if (m_reslicing_objects.count(obj) == 0) + { + if (obj->set_started(posSlice)) + obj->set_done(posSlice); + if (obj->set_started(posPerimeters)) + obj->set_done(posPerimeters); + if (obj->set_started(posPrepareInfill)) + obj->set_done(posPrepareInfill); + if (obj->set_started(posInfill)) + obj->set_done(posInfill); + if (obj->set_started(posIroning)) + obj->set_done(posIroning); + if (obj->set_started(posSupportMaterial)) + obj->set_done(posSupportMaterial); + if (obj->set_started(posDetectOverhangsForLift)) + obj->set_done(posDetectOverhangsForLift); + } + else + { + obj->set_auto_circle_compenstaion_params(auto_contour_holes_compensation_params); + obj->make_perimeters(); + obj->infill(); + obj->ironing(); + obj->generate_support_material(); + obj->detect_overhangs_for_lift(); + } } } - } - for (PrintObject *obj : m_objects) - { - if (need_slicing_objects.count(obj) == 0) { - obj->copy_layers_from_shared_object(); - obj->copy_layers_overhang_from_shared_object(); + for (PrintObject *obj : m_objects) + { + if (need_slicing_objects.count(obj) == 0) + { + obj->copy_layers_from_shared_object(); + obj->copy_layers_overhang_from_shared_object(); + } } - } - - - if (this->set_started(psWipeTower)) { + if (this->set_started(psWipeTower)) { - std::vector> geometric_unprintables(m_config.nozzle_diameter.size()); - for (PrintObject* obj : m_objects) { - std::vector> obj_geometric_unprintables = obj->detect_extruder_geometric_unprintables(); - for (size_t idx = 0; idx < obj_geometric_unprintables.size(); ++idx) { - if (idx < geometric_unprintables.size()) { - geometric_unprintables[idx].insert(obj_geometric_unprintables[idx].begin(), obj_geometric_unprintables[idx].end()); + { + std::vector> geometric_unprintables(m_config.nozzle_diameter.size()); + for (PrintObject *obj : m_objects) + { + std::vector> obj_geometric_unprintables = obj->detect_extruder_geometric_unprintables(); + for (size_t idx = 0; idx < obj_geometric_unprintables.size(); ++idx) + { + if (idx < geometric_unprintables.size()) + { + geometric_unprintables[idx].insert(obj_geometric_unprintables[idx].begin(), obj_geometric_unprintables[idx].end()); + } } } + this->set_geometric_unprintable_filaments(geometric_unprintables); } - this->set_geometric_unprintable_filaments(geometric_unprintables); - } - { - std::unordered_map> filament_print_time; - for(PrintObject* obj : m_objects){ - auto obj_filament_print_time = obj->calc_estimated_filament_print_time(); - for(auto [filament_idx,extruder_time] : obj_filament_print_time) { - for (auto [extruder_idx, time] : extruder_time) { - filament_print_time[filament_idx][extruder_idx] += time; + { + std::unordered_map> filament_print_time; + for (PrintObject *obj : m_objects) + { + auto obj_filament_print_time = obj->calc_estimated_filament_print_time(); + for (auto [filament_idx, extruder_time] : obj_filament_print_time) + { + for (auto [extruder_idx, time] : extruder_time) + { + filament_print_time[filament_idx][extruder_idx] += time; + } } } + this->set_filament_print_time(filament_print_time); } - this->set_filament_print_time(filament_print_time); + + m_nozzle_group_result.reset(); + m_wipe_tower_data.clear(); + m_tool_ordering.clear(); + if (this->has_wipe_tower()) + { + this->_make_wipe_tower(); + } + else if (this->config().print_sequence != PrintSequence::ByObject || (this->config().print_sequence == PrintSequence::ByObject && m_objects.size() == 1)) + { + // Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches. + m_tool_ordering = ToolOrdering(*this, -1, false); + m_tool_ordering.sort_and_build_data(*this, -1, false); + if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1)) + throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings."); + } + this->set_done(psWipeTower); } - m_nozzle_group_result.reset(); - m_wipe_tower_data.clear(); - m_tool_ordering.clear(); - if (this->has_wipe_tower()) { - this->_make_wipe_tower(); - } else if (this->config().print_sequence != PrintSequence::ByObject - || (this->config().print_sequence == PrintSequence::ByObject && m_objects.size() == 1)) { - // Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches. - m_tool_ordering = ToolOrdering(*this, -1, false); - m_tool_ordering.sort_and_build_data(*this, -1, false); - if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1)) - throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings."); - - } - this->set_done(psWipeTower); - } + if (this->has_wipe_tower()) + { + m_fake_wipe_tower.set_pos({m_config.wipe_tower_x.get_at(m_plate_index), m_config.wipe_tower_y.get_at(m_plate_index)}); + } - if (this->has_wipe_tower()) { - m_fake_wipe_tower.set_pos({ m_config.wipe_tower_x.get_at(m_plate_index), m_config.wipe_tower_y.get_at(m_plate_index) }); - } + if (this->set_started(psSkirtBrim)) + { + this->set_status(70, L("Generating skirt & brim")); - if (this->set_started(psSkirtBrim)) { - this->set_status(70, L("Generating skirt & brim")); - - if (slice_time) { - start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); - } - - m_skirt.clear(); - m_skirt_convex_hull.clear(); - m_first_layer_convex_hull.points.clear(); - for (PrintObject *object : m_objects) object->m_skirt.clear(); - - const bool draft_shield = config().draft_shield != dsDisabled; - - if (this->has_skirt() && draft_shield) { - // In case that draft shield is active, generate skirt first so brim - // can be trimmed to make room for it. - _make_skirt(); - } - - //BBS: get the objects' indices when GCodes are generated - ToolOrdering tool_ordering; - unsigned int initial_extruder_id = (unsigned int)-1; - unsigned int final_extruder_id = (unsigned int)-1; - bool has_wipe_tower = false; - std::vector print_object_instances_ordering; - std::vector::const_iterator print_object_instance_sequential_active; - std::vector>> layers_to_print = GCode::collect_layers_to_print(*this); - std::vector printExtruders; - if (this->config().print_sequence == PrintSequence::ByObject && m_objects.size() > 1) { - // Order object instances for sequential print. - print_object_instances_ordering = sort_object_instances_by_model_order(*this); - std::vector first_layer_used_filaments; - std::vector> all_filaments; - for (print_object_instance_sequential_active = print_object_instances_ordering.begin(); print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { - tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); - for (size_t idx = 0; idx < tool_ordering.layer_tools().size(); ++idx) { - auto& layer_filament = tool_ordering.layer_tools()[idx].extruders; - all_filaments.emplace_back(layer_filament); - if (idx == 0) - first_layer_used_filaments.insert(first_layer_used_filaments.end(), layer_filament.begin(), layer_filament.end()); - } + if (slice_time) + { + start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); } - sort_remove_duplicates(first_layer_used_filaments); - auto used_filaments = collect_sorted_used_filaments(all_filaments); - this->set_slice_used_filaments(first_layer_used_filaments,used_filaments); - auto physical_unprintables = this->get_physical_unprintable_filaments(used_filaments); - auto geometric_unprintables = this->get_geometric_unprintable_filaments(); - // get recommended filament map + m_skirt.clear(); + m_skirt_convex_hull.clear(); + m_first_layer_convex_hull.points.clear(); + for (PrintObject *object : m_objects) + object->m_skirt.clear(); + + const bool draft_shield = config().draft_shield != dsDisabled; + + if (this->has_skirt() && draft_shield) { - if (!get_nozzle_group_result().has_value()) { - auto map_mode = get_filament_map_mode(); - auto group_result = ToolOrdering::get_recommended_filament_maps(this, all_filaments, map_mode, physical_unprintables, geometric_unprintables); - set_nozzle_group_result(group_result); + // In case that draft shield is active, generate skirt first so brim + // can be trimmed to make room for it. + _make_skirt(); + } + + // BBS: get the objects' indices when GCodes are generated + ToolOrdering tool_ordering; + unsigned int initial_extruder_id = (unsigned int)-1; + unsigned int final_extruder_id = (unsigned int)-1; + bool has_wipe_tower = false; + std::vector print_object_instances_ordering; + std::vector::const_iterator print_object_instance_sequential_active; + std::vector>> layers_to_print = GCode::collect_layers_to_print(*this); + std::vector printExtruders; + if (this->config().print_sequence == PrintSequence::ByObject && m_objects.size() > 1) + { + // Order object instances for sequential print. + print_object_instances_ordering = sort_object_instances_by_model_order(*this); + std::vector first_layer_used_filaments; + std::vector> all_filaments; + for (print_object_instance_sequential_active = print_object_instances_ordering.begin(); print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) + { + tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); + for (size_t idx = 0; idx < tool_ordering.layer_tools().size(); ++idx) + { + auto &layer_filament = tool_ordering.layer_tools()[idx].extruders; + all_filaments.emplace_back(layer_filament); + if (idx == 0) + first_layer_used_filaments.insert(first_layer_used_filaments.end(), layer_filament.begin(), layer_filament.end()); + } + } + sort_remove_duplicates(first_layer_used_filaments); + auto used_filaments = collect_sorted_used_filaments(all_filaments); + this->set_slice_used_filaments(first_layer_used_filaments, used_filaments); + + auto physical_unprintables = this->get_physical_unprintable_filaments(used_filaments); + auto geometric_unprintables = this->get_geometric_unprintable_filaments(); + // get recommended filament map + { + if (!get_nozzle_group_result().has_value()) + { + auto map_mode = get_filament_map_mode(); + auto group_result = ToolOrdering::get_recommended_filament_maps(this, all_filaments, map_mode, physical_unprintables, geometric_unprintables); + set_nozzle_group_result(group_result); + } + auto group_result = get_nozzle_group_result(); + update_filament_maps_to_config( + FilamentGroupUtils::update_used_filament_values(this->config().filament_map.values, group_result->get_extruder_map(false), used_filaments), + FilamentGroupUtils::update_used_filament_values(this->config().filament_volume_map.values, group_result->get_volume_map(), used_filaments), + group_result->get_nozzle_map()); } - auto group_result = get_nozzle_group_result(); - update_filament_maps_to_config( - FilamentGroupUtils::update_used_filament_values(this->config().filament_map.values, group_result->get_extruder_map(false), used_filaments), - FilamentGroupUtils::update_used_filament_values(this->config().filament_volume_map.values, group_result->get_volume_map(), used_filaments), - group_result->get_nozzle_map() - ); - } - - // print_object_instances_ordering = sort_object_instances_by_max_z(print); - print_object_instance_sequential_active = print_object_instances_ordering.begin(); - for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { - tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); - tool_ordering.sort_and_build_data(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); - if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast(-1)) { - append(printExtruders, tool_ordering.tools_for_layer(layers_to_print.front().first).extruders); + + // print_object_instances_ordering = sort_object_instances_by_max_z(print); + print_object_instance_sequential_active = print_object_instances_ordering.begin(); + for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) + { + tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); + tool_ordering.sort_and_build_data(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); + if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast(-1)) + { + append(printExtruders, tool_ordering.tools_for_layer(layers_to_print.front().first).extruders); + } } } - } - else { - tool_ordering = this->tool_ordering(); - tool_ordering.assign_custom_gcodes(*this); + else + { + tool_ordering = this->tool_ordering(); + tool_ordering.assign_custom_gcodes(*this); - std::vector first_layer_used_filaments; - if (!tool_ordering.layer_tools().empty()) - first_layer_used_filaments = tool_ordering.layer_tools().front().extruders; + std::vector first_layer_used_filaments; + if (!tool_ordering.layer_tools().empty()) + first_layer_used_filaments = tool_ordering.layer_tools().front().extruders; - this->set_slice_used_filaments(first_layer_used_filaments, tool_ordering.all_extruders()); - has_wipe_tower = this->has_wipe_tower() && tool_ordering.has_wipe_tower(); - //BBS: have no single_extruder_multi_material_priming + this->set_slice_used_filaments(first_layer_used_filaments, tool_ordering.all_extruders()); + has_wipe_tower = this->has_wipe_tower() && tool_ordering.has_wipe_tower(); + // BBS: have no single_extruder_multi_material_priming #if 0 initial_extruder_id = (has_wipe_tower && !this->config().single_extruder_multi_material_priming) ? // The priming towers will be skipped. @@ -2156,2406 +2254,2608 @@ void Print::process(std::unordered_map* slice_time, bool // Don't skip the priming towers. tool_ordering.first_extruder(); #endif - initial_extruder_id = tool_ordering.first_extruder(); - print_object_instances_ordering = chain_print_object_instances(*this); - append(printExtruders, tool_ordering.tools_for_layer(layers_to_print.front().first).extruders); - } - - auto objectExtruderMap = getObjectExtruderMap(*this); - std::vector> objPrintVec; - for (const PrintInstance* instance : print_object_instances_ordering) { - const ObjectID& print_object_ID = instance->print_object->id(); - bool existObject = false; - for (auto& objIDPair : objPrintVec) { - if (print_object_ID == objIDPair.first) existObject = true; - } - if (!existObject && objectExtruderMap.find(print_object_ID) != objectExtruderMap.end()) - objPrintVec.push_back(std::make_pair(print_object_ID, objectExtruderMap.at(print_object_ID))); - } - // BBS: m_brimMap and m_supportBrimMap are used instead of m_brim to generate brim of objs and supports seperately - m_brimMap.clear(); - m_supportBrimMap.clear(); - m_first_layer_convex_hull.points.clear(); // BBS: plate offset is contained in this convexhull - if (this->has_brim()) { - Polygons islands_area; - make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap, - m_supportBrimMap, objPrintVec, printExtruders); - for (Polygon& poly_ex : islands_area) - poly_ex.douglas_peucker(SCALED_RESOLUTION); - for (Polygon &poly : union_(this->first_layer_islands(), islands_area)) - append(m_first_layer_convex_hull.points, std::move(poly.points)); - } + initial_extruder_id = tool_ordering.first_extruder(); + print_object_instances_ordering = chain_print_object_instances(*this); + append(printExtruders, tool_ordering.tools_for_layer(layers_to_print.front().first).extruders); + } + auto objectExtruderMap = getObjectExtruderMap(*this); + std::vector> objPrintVec; + for (const PrintInstance *instance : print_object_instances_ordering) + { + const ObjectID &print_object_ID = instance->print_object->id(); + bool existObject = false; + for (auto &objIDPair : objPrintVec) + { + if (print_object_ID == objIDPair.first) + existObject = true; + } + if (!existObject && objectExtruderMap.find(print_object_ID) != objectExtruderMap.end()) + objPrintVec.push_back(std::make_pair(print_object_ID, objectExtruderMap.at(print_object_ID))); + } + // BBS: m_brimMap and m_supportBrimMap are used instead of m_brim to generate brim of objs and supports seperately + m_brimMap.clear(); + m_supportBrimMap.clear(); + m_first_layer_convex_hull.points.clear(); // BBS: plate offset is contained in this convexhull + if (this->has_brim()) + { + Polygons islands_area; + make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap, + m_supportBrimMap, objPrintVec, printExtruders); + for (Polygon &poly_ex : islands_area) + poly_ex.douglas_peucker(SCALED_RESOLUTION); + for (Polygon &poly : union_(this->first_layer_islands(), islands_area)) + append(m_first_layer_convex_hull.points, std::move(poly.points)); + } - if (has_skirt() && ! draft_shield) { - // In case that draft shield is NOT active, generate skirt now. - // It will be placed around the brim, so brim has to be ready. - assert(m_skirt.empty()); - _make_skirt(); - } + if (has_skirt() && !draft_shield) + { + // In case that draft shield is NOT active, generate skirt now. + // It will be placed around the brim, so brim has to be ready. + assert(m_skirt.empty()); + _make_skirt(); + } - this->finalize_first_layer_convex_hull(); - this->set_done(psSkirtBrim); + this->finalize_first_layer_convex_hull(); + this->set_done(psSkirtBrim); - if (slice_time) { - end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); - (*slice_time)[TIME_USING_CACHE] = (*slice_time)[TIME_USING_CACHE] + end_time - start_time; + if (slice_time) + { + end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); + (*slice_time)[TIME_USING_CACHE] = (*slice_time)[TIME_USING_CACHE] + end_time - start_time; + } } - } - //BBS - for (PrintObject *obj : m_objects) { - if (((!use_cache)&&(need_slicing_objects.count(obj) != 0)) - || (use_cache &&(m_reslicing_objects.count(obj) != 0))){ - obj->simplify_extrusion_path(); - } - else { - if (obj->set_started(posSimplifyWall)) - obj->set_done(posSimplifyWall); - if (obj->set_started(posSimplifyInfill)) - obj->set_done(posSimplifyInfill); - if (obj->set_started(posSimplifySupportPath)) - obj->set_done(posSimplifySupportPath); + // BBS + for (PrintObject *obj : m_objects) + { + if (((!use_cache) && (need_slicing_objects.count(obj) != 0)) || (use_cache && (m_reslicing_objects.count(obj) != 0))) + { + obj->simplify_extrusion_path(); + } + else + { + if (obj->set_started(posSimplifyWall)) + obj->set_done(posSimplifyWall); + if (obj->set_started(posSimplifyInfill)) + obj->set_done(posSimplifyInfill); + if (obj->set_started(posSimplifySupportPath)) + obj->set_done(posSimplifySupportPath); + } } - } - // BBS - bool has_adaptive_layer_height = false; - for (PrintObject* obj : m_objects) { - if (obj->model_object()->layer_height_profile.empty() == false) { - has_adaptive_layer_height = true; - break; - } - } - if(!m_no_check /*&& !has_adaptive_layer_height*/) - { - using Clock = std::chrono::high_resolution_clock; - auto startTime = Clock::now(); - std::optional wipe_tower_opt = {}; - if (this->has_wipe_tower()) { - m_fake_wipe_tower.set_pos({m_config.wipe_tower_x.get_at(m_plate_index), m_config.wipe_tower_y.get_at(m_plate_index)}); - wipe_tower_opt = std::make_optional(&m_fake_wipe_tower); + // BBS + bool has_adaptive_layer_height = false; + for (PrintObject *obj : m_objects) + { + if (obj->model_object()->layer_height_profile.empty() == false) + { + has_adaptive_layer_height = true; + break; + } } - auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(m_objects, wipe_tower_opt); - auto endTime = Clock::now(); - volatile double seconds = std::chrono::duration_cast(endTime - startTime).count() / (double) 1000; - BOOST_LOG_TRIVIAL(info) << "gcode path conflicts check takes " << seconds << " secs."; + if (!m_no_check /*&& !has_adaptive_layer_height*/) + { + using Clock = std::chrono::high_resolution_clock; + auto startTime = Clock::now(); + std::optional wipe_tower_opt = {}; + if (this->has_wipe_tower()) + { + m_fake_wipe_tower.set_pos({m_config.wipe_tower_x.get_at(m_plate_index), m_config.wipe_tower_y.get_at(m_plate_index)}); + wipe_tower_opt = std::make_optional(&m_fake_wipe_tower); + } + auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(m_objects, wipe_tower_opt); + auto endTime = Clock::now(); + volatile double seconds = std::chrono::duration_cast(endTime - startTime).count() / (double)1000; + BOOST_LOG_TRIVIAL(info) << "gcode path conflicts check takes " << seconds << " secs."; - m_conflict_result = conflictRes; - if (conflictRes.has_value()) { - BOOST_LOG_TRIVIAL(error) << boost::format("gcode path conflicts found between %1% and %2%")%conflictRes.value()._objName1 %conflictRes.value()._objName2; + m_conflict_result = conflictRes; + if (conflictRes.has_value()) + { + BOOST_LOG_TRIVIAL(error) << boost::format("gcode path conflicts found between %1% and %2%") % conflictRes.value()._objName1 % conflictRes.value()._objName2; + } } - } - BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); -} - -// G-code export process, running at a background thread. -// The export_gcode may die for various reasons (fails to process filename_format, -// write error into the G-code, cannot execute post-processing scripts). -// It is up to the caller to show an error message. -std::string Print::export_gcode(const std::string& path_template, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) -{ - // output everything to a G-code file - // The following call may die if the filename_format template substitution fails. - std::string path = this->output_filepath(path_template); - std::string message; - if (!path.empty() && result == nullptr) { - // Only show the path if preview_data is not set -> running from command line. - message = L("Exporting G-code"); - message += " to "; - message += path; - } else - message = L("Generating G-code"); - this->set_status(80, message); - - // The following line may die for multiple reasons. - GCode gcode; - //BBS: compute plate offset for gcode-generator - const Vec3d origin = this->get_plate_origin(); - gcode.set_gcode_offset(origin(0), origin(1)); - gcode.do_export(this, path.c_str(), result, thumbnail_cb); - gcode.export_layer_filaments(result); - //BBS - if (result != nullptr){ - result->conflict_result = m_conflict_result; - result->nozzle_group_result = this->get_nozzle_group_result(); + BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); } - return path.c_str(); -} -void Print::_make_skirt() -{ - // First off we need to decide how tall the skirt must be. - // The skirt_height option from config is expressed in layers, but our - // object might have different layer heights, so we need to find the print_z - // of the highest layer involved. - // Note that unless has_infinite_skirt() == true - // the actual skirt might not reach this $skirt_height_z value since the print - // order of objects on each layer is not guaranteed and will not generally - // include the thickest object first. It is just guaranteed that a skirt is - // prepended to the first 'n' layers (with 'n' = skirt_height). - // $skirt_height_z in this case is the highest possible skirt height for safety. - coordf_t skirt_height_z = 0.; - for (const PrintObject *object : m_objects) { - size_t skirt_layers = this->has_infinite_skirt() ? - object->layer_count() : - 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); + // G-code export process, running at a background thread. + // The export_gcode may die for various reasons (fails to process filename_format, + // write error into the G-code, cannot execute post-processing scripts). + // It is up to the caller to show an error message. + std::string Print::export_gcode(const std::string &path_template, GCodeProcessorResult *result, ThumbnailsGeneratorCallback thumbnail_cb) + { + // output everything to a G-code file + // The following call may die if the filename_format template substitution fails. + std::string path = this->output_filepath(path_template); + std::string message; + if (!path.empty() && result == nullptr) + { + // Only show the path if preview_data is not set -> running from command line. + message = L("Exporting G-code"); + message += " to "; + message += path; + } + else + message = L("Generating G-code"); + this->set_status(80, message); + + // The following line may die for multiple reasons. + GCode gcode; + // BBS: compute plate offset for gcode-generator + const Vec3d origin = this->get_plate_origin(); + gcode.set_gcode_offset(origin(0), origin(1)); + gcode.do_export(this, path.c_str(), result, thumbnail_cb); + gcode.export_layer_filaments(result); + // BBS + if (result != nullptr) + { + result->conflict_result = m_conflict_result; + result->nozzle_group_result = this->get_nozzle_group_result(); + } + return path.c_str(); } - // Collect points from all layers contained in skirt height. - Points points; - - // BBS - std::map object_convex_hulls; - for (PrintObject *object : m_objects) { - Points object_points; - // Get object layers up to skirt_height_z. - for (const Layer *layer : object->m_layers) { - if (layer->print_z > skirt_height_z) - break; - for (const ExPolygon &expoly : layer->lslices) - // Collect the outer contour points only, ignore holes for the calculation of the convex hull. - append(object_points, expoly.contour.points); - } - // Get support layers up to skirt_height_z. - for (const SupportLayer *layer : object->support_layers()) { - if (layer->print_z > skirt_height_z) - break; - layer->support_fills.collect_points(object_points); + void Print::_make_skirt() + { + // First off we need to decide how tall the skirt must be. + // The skirt_height option from config is expressed in layers, but our + // object might have different layer heights, so we need to find the print_z + // of the highest layer involved. + // Note that unless has_infinite_skirt() == true + // the actual skirt might not reach this $skirt_height_z value since the print + // order of objects on each layer is not guaranteed and will not generally + // include the thickest object first. It is just guaranteed that a skirt is + // prepended to the first 'n' layers (with 'n' = skirt_height). + // $skirt_height_z in this case is the highest possible skirt height for safety. + coordf_t skirt_height_z = 0.; + for (const PrintObject *object : m_objects) + { + size_t skirt_layers = this->has_infinite_skirt() ? object->layer_count() : 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); } - object_convex_hulls.insert({ object, Slic3r::Geometry::convex_hull(object_points) }); + // Collect points from all layers contained in skirt height. + Points points; - // Repeat points for each object copy. - for (const PrintInstance &instance : object->instances()) { - Points copy_points = object_points; - for (Point &pt : copy_points) - pt += instance.shift; - append(points, copy_points); - } - } + // BBS + std::map object_convex_hulls; + for (PrintObject *object : m_objects) + { + Points object_points; + // Get object layers up to skirt_height_z. + for (const Layer *layer : object->m_layers) + { + if (layer->print_z > skirt_height_z) + break; + for (const ExPolygon &expoly : layer->lslices) + // Collect the outer contour points only, ignore holes for the calculation of the convex hull. + append(object_points, expoly.contour.points); + } + // Get support layers up to skirt_height_z. + for (const SupportLayer *layer : object->support_layers()) + { + if (layer->print_z > skirt_height_z) + break; + layer->support_fills.collect_points(object_points); + } - // Include the wipe tower. - append(points, this->first_layer_wipe_tower_corners()); + object_convex_hulls.insert({object, Slic3r::Geometry::convex_hull(object_points)}); - // Unless draft shield is enabled, include all brims as well. - if (config().draft_shield == dsDisabled) - append(points, m_first_layer_convex_hull.points); + // Repeat points for each object copy. + for (const PrintInstance &instance : object->instances()) + { + Points copy_points = object_points; + for (Point &pt : copy_points) + pt += instance.shift; + append(points, copy_points); + } + } - if (points.size() < 3) - // At least three points required for a convex hull. - return; + // Include the wipe tower. + append(points, this->first_layer_wipe_tower_corners()); - this->throw_if_canceled(); - Polygon convex_hull = Slic3r::Geometry::convex_hull(points); + // Unless draft shield is enabled, include all brims as well. + if (config().draft_shield == dsDisabled) + append(points, m_first_layer_convex_hull.points); - // Skirt may be printed on several layers, having distinct layer heights, - // but loops must be aligned so can't vary width/spacing - // TODO: use each extruder's own flow - double initial_layer_print_height = this->skirt_first_layer_height(); - Flow flow = this->skirt_flow(); - float spacing = flow.spacing(); - double mm3_per_mm = flow.mm3_per_mm(); + if (points.size() < 3) + // At least three points required for a convex hull. + return; - std::vector extruders; - std::vector extruders_e_per_mm; - { - auto set_extruders = this->extruders(); - extruders.reserve(set_extruders.size()); - extruders_e_per_mm.reserve(set_extruders.size()); - for (auto &extruder_id : set_extruders) { - extruders.push_back(extruder_id); - extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, &m_config, m_config.single_extruder_multi_material).e_per_mm(mm3_per_mm)); - } - } - - // Number of skirt loops per skirt layer. - size_t n_skirts = m_config.skirt_loops.value; - if (this->has_infinite_skirt() && n_skirts == 0) - n_skirts = 1; - - // Initial offset of the brim inner edge from the object (possible with a support & raft). - // The skirt will touch the brim if the brim is extruded. - auto distance = float(scale_(m_config.skirt_distance.value) - spacing/2.); - // Draw outlines from outside to inside. - // Loop while we have less skirts than required or any extruder hasn't reached the min length if any. - std::vector extruded_length(extruders.size(), 0.); - for (size_t i = n_skirts, extruder_idx = 0; i > 0; -- i) { this->throw_if_canceled(); - // Offset the skirt outside. - distance += float(scale_(spacing)); - // Generate the skirt centerline. - Polygon loop; - { - // BBS. skirt_distance is defined as the gap between skirt and outer most brim, so no need to add max_brim_width - Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1))); - Geometry::simplify_polygons(loops, scale_(0.05), &loops); - if (loops.empty()) - break; - loop = loops.front(); - } - // Extrude the skirt loop. - ExtrusionLoop eloop(elrSkirt); - eloop.paths.emplace_back(ExtrusionPath( - ExtrusionPath( - erSkirt, - (float)mm3_per_mm, // this will be overridden at G-code export time - flow.width(), - (float)initial_layer_print_height // this will be overridden at G-code export time - ))); - eloop.paths.back().polyline = loop.split_at_first_point(); - m_skirt.append(eloop); - if (Print::min_skirt_length > 0) { - // The skirt length is limited. Sum the total amount of filament length extruded, in mm. - extruded_length[extruder_idx] += unscale(loop.length()) * extruders_e_per_mm[extruder_idx]; - if (extruded_length[extruder_idx] < Print::min_skirt_length) { - // Not extruded enough yet with the current extruder. Add another loop. - if (i == 1) - ++ i; - } else { - assert(extruded_length[extruder_idx] >= Print::min_skirt_length); - // Enough extruded with the current extruder. Extrude with the next one, - // until the prescribed number of skirt loops is extruded. - if (extruder_idx + 1 < extruders.size()) - ++ extruder_idx; - } - } else { - // The skirt lenght is not limited, extrude the skirt with the 1st extruder only. + Polygon convex_hull = Slic3r::Geometry::convex_hull(points); + + // Skirt may be printed on several layers, having distinct layer heights, + // but loops must be aligned so can't vary width/spacing + // TODO: use each extruder's own flow + double initial_layer_print_height = this->skirt_first_layer_height(); + Flow flow = this->skirt_flow(); + float spacing = flow.spacing(); + double mm3_per_mm = flow.mm3_per_mm(); + + std::vector extruders; + std::vector extruders_e_per_mm; + { + auto set_extruders = this->extruders(); + extruders.reserve(set_extruders.size()); + extruders_e_per_mm.reserve(set_extruders.size()); + for (auto &extruder_id : set_extruders) + { + extruders.push_back(extruder_id); + extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, &m_config, m_config.single_extruder_multi_material).e_per_mm(mm3_per_mm)); + } } - } - // Brims were generated inside out, reverse to print the outmost contour first. - m_skirt.reverse(); - // Remember the outer edge of the last skirt line extruded as m_skirt_convex_hull. - for (Polygon &poly : offset(convex_hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1)))) - append(m_skirt_convex_hull, std::move(poly.points)); + // Number of skirt loops per skirt layer. + size_t n_skirts = m_config.skirt_loops.value; + if (this->has_infinite_skirt() && n_skirts == 0) + n_skirts = 1; - // BBS - const int n_object_skirts = 1; - const double object_skirt_distance = scale_(1.0); - for (auto obj_cvx_hull : object_convex_hulls) { - PrintObject* object = obj_cvx_hull.first; - for (int i = 0; i < n_object_skirts; i++) { + // Initial offset of the brim inner edge from the object (possible with a support & raft). + // The skirt will touch the brim if the brim is extruded. + auto distance = float(scale_(m_config.skirt_distance.value) - spacing / 2.); + // Draw outlines from outside to inside. + // Loop while we have less skirts than required or any extruder hasn't reached the min length if any. + std::vector extruded_length(extruders.size(), 0.); + for (size_t i = n_skirts, extruder_idx = 0; i > 0; --i) + { + this->throw_if_canceled(); + // Offset the skirt outside. distance += float(scale_(spacing)); + // Generate the skirt centerline. Polygon loop; { // BBS. skirt_distance is defined as the gap between skirt and outer most brim, so no need to add max_brim_width - Polygons loops = offset(obj_cvx_hull.second, object_skirt_distance, ClipperLib::jtRound, float(scale_(0.1))); + Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1))); Geometry::simplify_polygons(loops, scale_(0.05), &loops); if (loops.empty()) break; loop = loops.front(); } - // Extrude the skirt loop. ExtrusionLoop eloop(elrSkirt); eloop.paths.emplace_back(ExtrusionPath( ExtrusionPath( erSkirt, - (float)mm3_per_mm, // this will be overridden at G-code export time + (float)mm3_per_mm, // this will be overridden at G-code export time flow.width(), - (float)initial_layer_print_height // this will be overridden at G-code export time - ))); + (float)initial_layer_print_height // this will be overridden at G-code export time + ))); eloop.paths.back().polyline = loop.split_at_first_point(); - object->m_skirt.append(std::move(eloop)); - } - } -} - -Polygons Print::first_layer_islands() const -{ - Polygons islands; - for (PrintObject *object : m_objects) { - Polygons object_islands; - for (ExPolygon &expoly : object->m_layers.front()->lslices) - object_islands.push_back(expoly.contour); - if (!object->support_layers().empty()) { - ExPolygons &expolys_first_layer = object->m_support_layers.front()->support_islands; - for (ExPolygon &expoly : expolys_first_layer) { object_islands.push_back(expoly.contour); } - } - islands.reserve(islands.size() + object_islands.size() * object->instances().size()); - for (const PrintInstance &instance : object->instances()) - for (Polygon &poly : object_islands) { - islands.push_back(poly); - islands.back().translate(instance.shift); + m_skirt.append(eloop); + if (Print::min_skirt_length > 0) + { + // The skirt length is limited. Sum the total amount of filament length extruded, in mm. + extruded_length[extruder_idx] += unscale(loop.length()) * extruders_e_per_mm[extruder_idx]; + if (extruded_length[extruder_idx] < Print::min_skirt_length) + { + // Not extruded enough yet with the current extruder. Add another loop. + if (i == 1) + ++i; + } + else + { + assert(extruded_length[extruder_idx] >= Print::min_skirt_length); + // Enough extruded with the current extruder. Extrude with the next one, + // until the prescribed number of skirt loops is extruded. + if (extruder_idx + 1 < extruders.size()) + ++extruder_idx; + } + } + else + { + // The skirt lenght is not limited, extrude the skirt with the 1st extruder only. } - } - return islands; -} - -std::vector Print::first_layer_wipe_tower_corners(bool check_wipe_tower_existance) const -{ - std::vector corners; - if (check_wipe_tower_existance && (!has_wipe_tower() || m_wipe_tower_data.tool_changes.empty())) - return corners; - { - double width = m_wipe_tower_data.bbx.max.x() - m_wipe_tower_data.bbx.min.x(); - double depth = m_wipe_tower_data.bbx.max.y() -m_wipe_tower_data.bbx.min.y(); - Vec2d pt0 = m_wipe_tower_data.bbx.min + m_wipe_tower_data.rib_offset.cast(); - for (Vec2d pt : { - pt0, - Vec2d(pt0.x()+width, pt0.y() ), - Vec2d(pt0.x()+width, pt0.y()+depth), - Vec2d(pt0.x(), pt0.y()+depth) - }) { - pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; - // BBS: add partplate logic - pt += Vec2d(m_config.wipe_tower_x.get_at(m_plate_index) + m_origin(0), m_config.wipe_tower_y.get_at(m_plate_index) + m_origin(1)); - corners.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); } - } - return corners; -} + // Brims were generated inside out, reverse to print the outmost contour first. + m_skirt.reverse(); -//OrcaSlicer -Vec2d Print::translate_to_print_space(const Vec2d& point) const { - //const BoundingBoxf bed_bbox(config().printable_area.values); - return Vec2d(point(0) - m_origin(0), point(1) - m_origin(1)); -} + // Remember the outer edge of the last skirt line extruded as m_skirt_convex_hull. + for (Polygon &poly : offset(convex_hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1)))) + append(m_skirt_convex_hull, std::move(poly.points)); -Vec2d Print::translate_to_print_space(const Point& point) const { - return Vec2d(unscaled(point.x()) - m_origin(0), unscaled(point.y()) - m_origin(1)); -} + // BBS + const int n_object_skirts = 1; + const double object_skirt_distance = scale_(1.0); + for (auto obj_cvx_hull : object_convex_hulls) + { + PrintObject *object = obj_cvx_hull.first; + for (int i = 0; i < n_object_skirts; i++) + { + distance += float(scale_(spacing)); + Polygon loop; + { + // BBS. skirt_distance is defined as the gap between skirt and outer most brim, so no need to add max_brim_width + Polygons loops = offset(obj_cvx_hull.second, object_skirt_distance, ClipperLib::jtRound, float(scale_(0.1))); + Geometry::simplify_polygons(loops, scale_(0.05), &loops); + if (loops.empty()) + break; + loop = loops.front(); + } -FilamentTempType Print::get_filament_temp_type(const std::string& filament_type) -{ - const static std::string HighTempFilamentStr = "high_temp_filament"; - const static std::string LowTempFilamentStr = "low_temp_filament"; - const static std::string HighLowCompatibleFilamentStr = "high_low_compatible_filament"; - static std::unordered_map>filament_temp_type_map; - - if (filament_temp_type_map.empty()) { - fs::path file_path = fs::path(resources_dir()) / "info" / "filament_info.json"; - std::ifstream in(file_path.string()); - json j; - try{ - j = json::parse(in); - in.close(); - auto&& high_temp_filament_arr =j[HighTempFilamentStr].get < std::vector>(); - filament_temp_type_map[HighTempFilamentStr] = std::unordered_set(high_temp_filament_arr.begin(), high_temp_filament_arr.end()); - auto&& low_temp_filament_arr = j[LowTempFilamentStr].get < std::vector>(); - filament_temp_type_map[LowTempFilamentStr] = std::unordered_set(low_temp_filament_arr.begin(), low_temp_filament_arr.end()); - auto&& high_low_compatible_filament_arr = j[HighLowCompatibleFilamentStr].get < std::vector>(); - filament_temp_type_map[HighLowCompatibleFilamentStr] = std::unordered_set(high_low_compatible_filament_arr.begin(), high_low_compatible_filament_arr.end()); - } - catch (const json::parse_error& err){ - in.close(); - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what(); - filament_temp_type_map[HighTempFilamentStr] = {"ABS","ASA","PC","PA","PA-CF","PA-GF","PA6-CF","PET-CF","PPS","PPS-CF","PPA-GF","PPA-CF","ABS-Aero","ABS-GF"}; - filament_temp_type_map[LowTempFilamentStr] = {"PLA","TPU","PLA-CF","PLA-AERO","PVA","BVOH"}; - filament_temp_type_map[HighLowCompatibleFilamentStr] = { "HIPS","PETG","PCTG","PE","PP","EVA","PE-CF","PP-CF","PP-GF","PHA"}; + // Extrude the skirt loop. + ExtrusionLoop eloop(elrSkirt); + eloop.paths.emplace_back(ExtrusionPath( + ExtrusionPath( + erSkirt, + (float)mm3_per_mm, // this will be overridden at G-code export time + flow.width(), + (float)initial_layer_print_height // this will be overridden at G-code export time + ))); + eloop.paths.back().polyline = loop.split_at_first_point(); + object->m_skirt.append(std::move(eloop)); + } } } - if (filament_temp_type_map[HighLowCompatibleFilamentStr].find(filament_type) != filament_temp_type_map[HighLowCompatibleFilamentStr].end()) - return HighLowCompatible; - if (filament_temp_type_map[HighTempFilamentStr].find(filament_type) != filament_temp_type_map[HighTempFilamentStr].end()) - return HighTemp; - if (filament_temp_type_map[LowTempFilamentStr].find(filament_type) != filament_temp_type_map[LowTempFilamentStr].end()) - return LowTemp; - return Undefine; -} - -int Print::get_hrc_by_nozzle_type(const NozzleType&type) -{ - static std::mapnozzle_type_to_hrc; - if (nozzle_type_to_hrc.empty()) { - fs::path file_path = fs::path(resources_dir()) / "info" / "nozzle_info.json"; - boost::nowide::ifstream in(file_path.string()); - //std::ifstream in(file_path.string()); - json j; - try { - j = json::parse(in); - in.close(); - for (const auto& elem : j["nozzle_hrc"].items()) - nozzle_type_to_hrc[elem.key()] = elem.value(); - } - catch (const json::parse_error& err) { - in.close(); - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what(); - nozzle_type_to_hrc = { - {"hardened_steel",55}, - {"stainless_steel",20}, - {"tungsten_carbide", 85}, - {"brass",2}, - {"undefine",0} - }; + Polygons Print::first_layer_islands() const + { + Polygons islands; + for (PrintObject *object : m_objects) + { + Polygons object_islands; + for (ExPolygon &expoly : object->m_layers.front()->lslices) + object_islands.push_back(expoly.contour); + if (!object->support_layers().empty()) + { + ExPolygons &expolys_first_layer = object->m_support_layers.front()->support_islands; + for (ExPolygon &expoly : expolys_first_layer) + { + object_islands.push_back(expoly.contour); + } + } + islands.reserve(islands.size() + object_islands.size() * object->instances().size()); + for (const PrintInstance &instance : object->instances()) + for (Polygon &poly : object_islands) + { + islands.push_back(poly); + islands.back().translate(instance.shift); + } } + return islands; } - auto iter = nozzle_type_to_hrc.find(NozzleTypeEumnToStr[type]); - if (iter != nozzle_type_to_hrc.end()) - return iter->second; - //0 represents undefine - return 0; -} -std::vector Print::get_incompatible_filaments_by_nozzle(const float nozzle_diameter, const std::optional nozzle_volume_type) -{ - static std::map>> incompatible_filaments; - if(incompatible_filaments.empty()){ - fs::path file_path = fs::path(resources_dir()) / "info" / "nozzle_incompatibles.json"; - boost::nowide::ifstream in(file_path.string()); - json j; - try { - j = json::parse(in); - for(auto& [volume_type, diameter_list] : j["incompatible_nozzles"].items()) { - for(auto& [diameter, filaments]: diameter_list.items()){ - incompatible_filaments[volume_type][diameter] = filaments.get>(); - } + std::vector Print::first_layer_wipe_tower_corners(bool check_wipe_tower_existance) const + { + std::vector corners; + if (check_wipe_tower_existance && (!has_wipe_tower() || m_wipe_tower_data.tool_changes.empty())) + return corners; + { + double width = m_wipe_tower_data.bbx.max.x() - m_wipe_tower_data.bbx.min.x(); + double depth = m_wipe_tower_data.bbx.max.y() - m_wipe_tower_data.bbx.min.y(); + Vec2d pt0 = m_wipe_tower_data.bbx.min + m_wipe_tower_data.rib_offset.cast(); + for (Vec2d pt : { + pt0, + Vec2d(pt0.x() + width, pt0.y()), + Vec2d(pt0.x() + width, pt0.y() + depth), + Vec2d(pt0.x(), pt0.y() + depth)}) + { + pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; + // BBS: add partplate logic + pt += Vec2d(m_config.wipe_tower_x.get_at(m_plate_index) + m_origin(0), m_config.wipe_tower_y.get_at(m_plate_index) + m_origin(1)); + corners.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); } } - catch(const json::parse_error& err){ - in.close(); - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what(); + return corners; + } - incompatible_filaments[get_nozzle_volume_type_string(NozzleVolumeType::nvtHighFlow)] = {}; - incompatible_filaments[get_nozzle_volume_type_string(NozzleVolumeType::nvtStandard)] = {}; - } + // OrcaSlicer + Vec2d Print::translate_to_print_space(const Vec2d &point) const + { + // const BoundingBoxf bed_bbox(config().printable_area.values); + return Vec2d(point(0) - m_origin(0), point(1) - m_origin(1)); } - std::ostringstream oss; - oss << std::fixed << std::setprecision(1) << nozzle_diameter; - std::string diameter_str = oss.str(); - if(nozzle_volume_type.has_value()){ - return incompatible_filaments[get_nozzle_volume_type_string(nozzle_volume_type.value())][diameter_str]; + Vec2d Print::translate_to_print_space(const Point &point) const + { + return Vec2d(unscaled(point.x()) - m_origin(0), unscaled(point.y()) - m_origin(1)); } - std::vector incompatible_filaments_list; - for(auto& [volume_type, diameter_list] : incompatible_filaments){ - auto iter = diameter_list.find(diameter_str); - if(iter != diameter_list.end()){ - append(incompatible_filaments_list, iter->second); + FilamentTempType Print::get_filament_temp_type(const std::string &filament_type) + { + const static std::string HighTempFilamentStr = "high_temp_filament"; + const static std::string LowTempFilamentStr = "low_temp_filament"; + const static std::string HighLowCompatibleFilamentStr = "high_low_compatible_filament"; + static std::unordered_map> filament_temp_type_map; + + if (filament_temp_type_map.empty()) + { + fs::path file_path = fs::path(resources_dir()) / "info" / "filament_info.json"; + std::ifstream in(file_path.string()); + json j; + try + { + j = json::parse(in); + in.close(); + auto &&high_temp_filament_arr = j[HighTempFilamentStr].get>(); + filament_temp_type_map[HighTempFilamentStr] = std::unordered_set(high_temp_filament_arr.begin(), high_temp_filament_arr.end()); + auto &&low_temp_filament_arr = j[LowTempFilamentStr].get>(); + filament_temp_type_map[LowTempFilamentStr] = std::unordered_set(low_temp_filament_arr.begin(), low_temp_filament_arr.end()); + auto &&high_low_compatible_filament_arr = j[HighLowCompatibleFilamentStr].get>(); + filament_temp_type_map[HighLowCompatibleFilamentStr] = std::unordered_set(high_low_compatible_filament_arr.begin(), high_low_compatible_filament_arr.end()); + } + catch (const json::parse_error &err) + { + in.close(); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what(); + filament_temp_type_map[HighTempFilamentStr] = {"ABS", "ASA", "PC", "PA", "PA-CF", "PA-GF", "PA6-CF", "PET-CF", "PPS", "PPS-CF", "PPA-GF", "PPA-CF", "ABS-Aero", "ABS-GF"}; + filament_temp_type_map[LowTempFilamentStr] = {"PLA", "TPU", "PLA-CF", "PLA-AERO", "PVA", "BVOH"}; + filament_temp_type_map[HighLowCompatibleFilamentStr] = {"HIPS", "PETG", "PCTG", "PE", "PP", "EVA", "PE-CF", "PP-CF", "PP-GF", "PHA"}; + } } + + if (filament_temp_type_map[HighLowCompatibleFilamentStr].find(filament_type) != filament_temp_type_map[HighLowCompatibleFilamentStr].end()) + return HighLowCompatible; + if (filament_temp_type_map[HighTempFilamentStr].find(filament_type) != filament_temp_type_map[HighTempFilamentStr].end()) + return HighTemp; + if (filament_temp_type_map[LowTempFilamentStr].find(filament_type) != filament_temp_type_map[LowTempFilamentStr].end()) + return LowTemp; + return Undefine; } - return incompatible_filaments_list; -} -void Print::finalize_first_layer_convex_hull() -{ - append(m_first_layer_convex_hull.points, m_skirt_convex_hull); - if (m_first_layer_convex_hull.empty()) { - // Neither skirt nor brim was extruded. Collect points of printed objects from 1st layer. - for (Polygon &poly : this->first_layer_islands()) - append(m_first_layer_convex_hull.points, std::move(poly.points)); + int Print::get_hrc_by_nozzle_type(const NozzleType &type) + { + static std::map nozzle_type_to_hrc; + if (nozzle_type_to_hrc.empty()) + { + fs::path file_path = fs::path(resources_dir()) / "info" / "nozzle_info.json"; + boost::nowide::ifstream in(file_path.string()); + // std::ifstream in(file_path.string()); + json j; + try + { + j = json::parse(in); + in.close(); + for (const auto &elem : j["nozzle_hrc"].items()) + nozzle_type_to_hrc[elem.key()] = elem.value(); + } + catch (const json::parse_error &err) + { + in.close(); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what(); + nozzle_type_to_hrc = { + {"hardened_steel", 55}, + {"stainless_steel", 20}, + {"tungsten_carbide", 85}, + {"brass", 2}, + {"undefine", 0}}; + } + } + auto iter = nozzle_type_to_hrc.find(NozzleTypeEumnToStr[type]); + if (iter != nozzle_type_to_hrc.end()) + return iter->second; + // 0 represents undefine + return 0; } - append(m_first_layer_convex_hull.points, this->first_layer_wipe_tower_corners()); - m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points); -} -void Print::update_filament_maps_to_config(std::vector f_maps, std::vector f_volume_maps, std::vector f_nozzle_maps) -{ - if ((m_config.filament_map.values != f_maps) || (m_config.filament_volume_map.values != f_volume_maps) || (m_config.filament_nozzle_map.values != f_nozzle_maps)) + std::vector Print::get_incompatible_filaments_by_nozzle(const float nozzle_diameter, const std::optional nozzle_volume_type) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament maps changed after pre-slicing."); - m_ori_full_print_config.option("filament_map", true)->values = f_maps; - m_config.filament_map.values = f_maps; + static std::map>> incompatible_filaments; + if (incompatible_filaments.empty()) + { + fs::path file_path = fs::path(resources_dir()) / "info" / "nozzle_incompatibles.json"; + boost::nowide::ifstream in(file_path.string()); + json j; + try + { + j = json::parse(in); + for (auto &[volume_type, diameter_list] : j["incompatible_nozzles"].items()) + { + for (auto &[diameter, filaments] : diameter_list.items()) + { + incompatible_filaments[volume_type][diameter] = filaments.get>(); + } + } + } + catch (const json::parse_error &err) + { + in.close(); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what(); - if (!f_volume_maps.empty()) { - m_ori_full_print_config.option("filament_volume_map", true)->values = f_volume_maps; - m_config.filament_volume_map.values = f_volume_maps; - } - else { - m_ori_full_print_config.option("filament_volume_map", true)->values.resize(f_maps.size(), nvtStandard); - m_config.filament_volume_map.values.resize(f_maps.size(), nvtStandard); + incompatible_filaments[get_nozzle_volume_type_string(NozzleVolumeType::nvtHighFlow)] = {}; + incompatible_filaments[get_nozzle_volume_type_string(NozzleVolumeType::nvtStandard)] = {}; + } } + std::ostringstream oss; + oss << std::fixed << std::setprecision(1) << nozzle_diameter; + std::string diameter_str = oss.str(); - if (!f_nozzle_maps.empty()) { - m_ori_full_print_config.option("filament_nozzle_map", true)->values = f_nozzle_maps; - m_config.filament_nozzle_map.values = f_nozzle_maps; + if (nozzle_volume_type.has_value()) + { + return incompatible_filaments[get_nozzle_volume_type_string(nozzle_volume_type.value())][diameter_str]; } - int extruder_count, extruder_volume_type_count; - bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count); - std::vector> nozzle_volume_types; - extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); - - //filament_map_2 - m_config.filament_map_2.values = f_maps; - auto opt_extruder_type = dynamic_cast(m_ori_full_print_config.option("extruder_type")); - auto opt_nozzle_volume_type = dynamic_cast(m_ori_full_print_config.option("nozzle_volume_type")); - for (int index = 0; index < f_maps.size(); index++) + std::vector incompatible_filaments_list; + for (auto &[volume_type, diameter_list] : incompatible_filaments) { - ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(f_maps[index] - 1)); - NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(f_maps[index] - 1)); - if (f_volume_maps.empty()) { - m_config.filament_volume_map.values[index] = nozzle_volume_type; - m_ori_full_print_config.option("filament_volume_map")->values[index] = nozzle_volume_type; + auto iter = diameter_list.find(diameter_str); + if (iter != diameter_list.end()) + { + append(incompatible_filaments_list, iter->second); } - else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index)) - nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]); - m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant"); } - m_full_print_config = m_ori_full_print_config; - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_volume_type_count %3%")%__LINE__ %extruder_count %extruder_volume_type_count; - m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, extruder_count, extruder_volume_type_count, filament_options_with_variant, "filament_self_index", "filament_extruder_variant"); + return incompatible_filaments_list; + } - const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); - const std::string filament_prefix = "filament_"; - t_config_option_keys print_diff; - DynamicPrintConfig filament_overrides; - for (auto& opt_key: extruder_retract_keys) + void Print::finalize_first_layer_convex_hull() + { + append(m_first_layer_convex_hull.points, m_skirt_convex_hull); + if (m_first_layer_convex_hull.empty()) { - const ConfigOption *opt_new_filament = m_full_print_config.option(filament_prefix + opt_key); - const ConfigOption *opt_new_machine = m_full_print_config.option(opt_key); - const ConfigOption *opt_old_machine = m_config.option(opt_key); - - if (opt_new_filament) - compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, m_config.filament_map_2.values); - } - - t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end()); - m_config.apply_only(m_full_print_config, keys, true); - if (!print_diff.empty()) { - m_placeholder_parser.apply_config(filament_overrides); - m_config.apply(filament_overrides); + // Neither skirt nor brim was extruded. Collect points of printed objects from 1st layer. + for (Polygon &poly : this->first_layer_islands()) + append(m_first_layer_convex_hull.points, std::move(poly.points)); } + append(m_first_layer_convex_hull.points, this->first_layer_wipe_tower_corners()); + m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points); } - m_has_auto_filament_map_result = true; -} -void Print::apply_config_for_render(const DynamicConfig &config) -{ - m_config.apply(config); -} + void Print::update_filament_maps_to_config(std::vector f_maps, std::vector f_volume_maps, std::vector f_nozzle_maps) + { + if ((m_config.filament_map.values != f_maps) || (m_config.filament_volume_map.values != f_volume_maps) || (m_config.filament_nozzle_map.values != f_nozzle_maps)) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament maps changed after pre-slicing."); + m_ori_full_print_config.option("filament_map", true)->values = f_maps; + m_config.filament_map.values = f_maps; -std::vector Print::get_filament_maps() const -{ - return m_config.filament_map.values; -} + if (!f_volume_maps.empty()) + { + m_ori_full_print_config.option("filament_volume_map", true)->values = f_volume_maps; + m_config.filament_volume_map.values = f_volume_maps; + } + else + { + m_ori_full_print_config.option("filament_volume_map", true)->values.resize(f_maps.size(), nvtStandard); + m_config.filament_volume_map.values.resize(f_maps.size(), nvtStandard); + } -std::vector Print::get_filament_nozzle_maps() const -{ - return m_config.filament_nozzle_map.values; -} + if (!f_nozzle_maps.empty()) + { + m_ori_full_print_config.option("filament_nozzle_map", true)->values = f_nozzle_maps; + m_config.filament_nozzle_map.values = f_nozzle_maps; + } -std::vector Print::get_filament_volume_maps() const -{ - return m_config.filament_volume_map.values; -} + int extruder_count, extruder_volume_type_count; + bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count); + std::vector> nozzle_volume_types; + extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); -FilamentMapMode Print::get_filament_map_mode() const -{ - return m_config.filament_map_mode; -} + // filament_map_2 + m_config.filament_map_2.values = f_maps; + auto opt_extruder_type = dynamic_cast(m_ori_full_print_config.option("extruder_type")); + auto opt_nozzle_volume_type = dynamic_cast(m_ori_full_print_config.option("nozzle_volume_type")); + for (int index = 0; index < f_maps.size(); index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(f_maps[index] - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(f_maps[index] - 1)); + if (f_volume_maps.empty()) + { + m_config.filament_volume_map.values[index] = nozzle_volume_type; + m_ori_full_print_config.option("filament_volume_map")->values[index] = nozzle_volume_type; + } + else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index)) + nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]); + m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant"); + } + m_full_print_config = m_ori_full_print_config; -std::vector> Print::get_physical_unprintable_filaments(const std::vector& used_filaments) const -{ - int extruder_num = m_config.nozzle_diameter.size(); - std::vector>physical_unprintables(extruder_num); - if (extruder_num < 2) - return physical_unprintables; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_volume_type_count %3%") % __LINE__ % extruder_count % extruder_volume_type_count; + m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, extruder_count, extruder_volume_type_count, filament_options_with_variant, "filament_self_index", "filament_extruder_variant"); + + const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); + const std::string filament_prefix = "filament_"; + t_config_option_keys print_diff; + DynamicPrintConfig filament_overrides; + for (auto &opt_key : extruder_retract_keys) + { + const ConfigOption *opt_new_filament = m_full_print_config.option(filament_prefix + opt_key); + const ConfigOption *opt_new_machine = m_full_print_config.option(opt_key); + const ConfigOption *opt_old_machine = m_config.option(opt_key); - auto get_unprintable_extruder_id = [&](unsigned int filament_idx) -> int { - int status = m_config.filament_printable.values[filament_idx]; - for (int i = 0; i < extruder_num; ++i) { - if (!(status >> i & 1)) { - return i; + if (opt_new_filament) + compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, m_config.filament_map_2.values); + } + + t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end()); + m_config.apply_only(m_full_print_config, keys, true); + if (!print_diff.empty()) + { + m_placeholder_parser.apply_config(filament_overrides); + m_config.apply(filament_overrides); } } - return -1; - }; + m_has_auto_filament_map_result = true; + } + void Print::apply_config_for_render(const DynamicConfig &config) + { + m_config.apply(config); + } - std::set tpu_filaments; - for (auto f : used_filaments) { - if (m_config.filament_type.get_at(f) == "TPU") - tpu_filaments.insert(f); + std::vector Print::get_filament_maps() const + { + return m_config.filament_map.values; } - for (auto f : used_filaments) { - int extruder_id = get_unprintable_extruder_id(f); - if (extruder_id == -1) - continue; - physical_unprintables[extruder_id].insert(f); + std::vector Print::get_filament_nozzle_maps() const + { + return m_config.filament_nozzle_map.values; } - return physical_unprintables; -} + std::vector Print::get_filament_volume_maps() const + { + return m_config.filament_volume_map.values; + } + FilamentMapMode Print::get_filament_map_mode() const + { + return m_config.filament_map_mode; + } -std::vector Print::get_extruder_printable_height() const -{ - return m_config.extruder_printable_height.values; -} + std::vector> Print::get_physical_unprintable_filaments(const std::vector &used_filaments) const + { + int extruder_num = m_config.nozzle_diameter.size(); + std::vector> physical_unprintables(extruder_num); + if (extruder_num < 2) + return physical_unprintables; -std::vector Print::get_extruder_printable_polygons() const -{ - std::vector extruder_printable_polys; - std::vector> extruder_printable_areas = m_config.extruder_printable_area.values; - for (const auto &e_printable_area : extruder_printable_areas) { - Polygons ploys = {Polygon::new_scale(e_printable_area)}; - extruder_printable_polys.emplace_back(ploys); - } - return std::move(extruder_printable_polys); -} + auto get_unprintable_extruder_id = [&](unsigned int filament_idx) -> int + { + int status = m_config.filament_printable.values[filament_idx]; + for (int i = 0; i < extruder_num; ++i) + { + if (!(status >> i & 1)) + { + return i; + } + } + return -1; + }; -std::vector Print::get_extruder_unprintable_polygons() const -{ - std::vector printable_area = m_config.printable_area.values; - Polygon printable_poly = Polygon::new_scale(printable_area); - std::vector> extruder_printable_areas = m_config.extruder_printable_area.values; - std::vector extruder_unprintable_polys; - for (const auto &e_printable_area : extruder_printable_areas) { - Polygons ploys = diff(printable_poly, Polygon::new_scale(e_printable_area)); - extruder_unprintable_polys.emplace_back(ploys); - } - return std::move(extruder_unprintable_polys); -} + std::set tpu_filaments; + for (auto f : used_filaments) + { + if (m_config.filament_type.get_at(f) == "TPU") + tpu_filaments.insert(f); + } -Polygons Print::get_extruder_shared_printable_polygon() const -{ - if (m_config.nozzle_diameter.size() < 2) return {Polygon::new_scale(m_config.printable_area.values)}; - std::vector> extruder_printable_areas = m_config.extruder_printable_area.values; - Polygons shared_printable_polys = {Polygon::new_scale(extruder_printable_areas.front())}; - for (int i = 1; i < extruder_printable_areas.size();i++) { - Polygons polys = {Polygon::new_scale(extruder_printable_areas[i])}; - shared_printable_polys = intersection(shared_printable_polys, polys); + for (auto f : used_filaments) + { + int extruder_id = get_unprintable_extruder_id(f); + if (extruder_id == -1) + continue; + physical_unprintables[extruder_id].insert(f); + } + + return physical_unprintables; } - return shared_printable_polys; -} -size_t Print::get_extruder_id(unsigned int filament_id) const -{ - std::vector filament_map = get_filament_maps(); - if (filament_id < filament_map.size()) { - return filament_map[filament_id] - 1; + std::vector Print::get_extruder_printable_height() const + { + return m_config.extruder_printable_height.values; } - return 0; -} -size_t Print::get_config_idx_for_filament(unsigned int filament_id) const -{ - std::vector filament_map_2 = m_config.filament_map_2.values; - if (filament_id < filament_map_2.size()) { - return filament_map_2[filament_id]; + std::vector Print::get_extruder_printable_polygons() const + { + std::vector extruder_printable_polys; + std::vector> extruder_printable_areas = m_config.extruder_printable_area.values; + for (const auto &e_printable_area : extruder_printable_areas) + { + Polygons ploys = {Polygon::new_scale(e_printable_area)}; + extruder_printable_polys.emplace_back(ploys); + } + return std::move(extruder_printable_polys); } - return 0; -} -// Wipe tower support. -bool Print::has_wipe_tower() const -{ - if (m_config.enable_prime_tower.value == true) { - if (m_config.enable_wrapping_detection.value && m_config.wrapping_exclude_area.values.size() > 2) - return true; + std::vector Print::get_extruder_unprintable_polygons() const + { + std::vector printable_area = m_config.printable_area.values; + Polygon printable_poly = Polygon::new_scale(printable_area); + std::vector> extruder_printable_areas = m_config.extruder_printable_area.values; + std::vector extruder_unprintable_polys; + for (const auto &e_printable_area : extruder_printable_areas) + { + Polygons ploys = diff(printable_poly, Polygon::new_scale(e_printable_area)); + extruder_unprintable_polys.emplace_back(ploys); + } + return std::move(extruder_unprintable_polys); + } - if (enable_timelapse_print()) - return true; + Polygons Print::get_extruder_shared_printable_polygon() const + { + if (m_config.nozzle_diameter.size() < 2) + return {Polygon::new_scale(m_config.printable_area.values)}; + std::vector> extruder_printable_areas = m_config.extruder_printable_area.values; + Polygons shared_printable_polys = {Polygon::new_scale(extruder_printable_areas.front())}; + for (int i = 1; i < extruder_printable_areas.size(); i++) + { + Polygons polys = {Polygon::new_scale(extruder_printable_areas[i])}; + shared_printable_polys = intersection(shared_printable_polys, polys); + } + return shared_printable_polys; + } - return !m_config.spiral_mode.value && m_config.filament_diameter.values.size() > 1; + size_t Print::get_extruder_id(unsigned int filament_id) const + { + std::vector filament_map = get_filament_maps(); + if (filament_id < filament_map.size()) + { + return filament_map[filament_id] - 1; + } + return 0; } - return false; -} -const WipeTowerData& Print::wipe_tower_data(size_t filaments_cnt) const -{ - // If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default. - double max_height = 0; - for (size_t obj_idx = 0; obj_idx < m_objects.size(); obj_idx++) { - double object_z = (double) m_objects[obj_idx]->size().z(); - max_height = std::max(unscale_(object_z), max_height); + size_t Print::get_config_idx_for_filament(unsigned int filament_id) const + { + std::vector filament_map_2 = m_config.filament_map_2.values; + if (filament_id < filament_map_2.size()) + { + return filament_map_2[filament_id]; + } + return 0; } - if (max_height < EPSILON) return m_wipe_tower_data; - double layer_height = 0.08f; // hard code layer height - layer_height = m_objects.front()->config().layer_height.value; + // Wipe tower support. + bool Print::has_wipe_tower() const + { + if (m_config.enable_prime_tower.value == true) + { + if (m_config.enable_wrapping_detection.value && m_config.wrapping_exclude_area.values.size() > 2) + return true; - auto timelapse_type = config().option>("timelapse_type"); - bool need_wipe_tower = (timelapse_type ? (timelapse_type->value == TimelapseType::tlSmooth) : false) | m_config.prime_tower_rib_wall.value; - double extra_spacing = config().option("prime_tower_infill_gap")->getFloat() / 100.; - double rib_width = config().option("prime_tower_rib_width")->getFloat(); + if (enable_timelapse_print()) + return true; - double filament_change_volume = 0.; - { - std::vector filament_change_lengths; - auto filament_change_lengths_opt = config().option("filament_change_length"); - if (filament_change_lengths_opt) filament_change_lengths = filament_change_lengths_opt->values; - double length = filament_change_lengths.empty() ? 0 : *std::max_element(filament_change_lengths.begin(), filament_change_lengths.end()); - double diameter = 1.75; - std::vector diameters; - auto filament_diameter_opt = config().option("filament_diameter"); - if (filament_diameter_opt) diameters = filament_diameter_opt->values; - diameter = diameters.empty() ? diameter : *std::max_element(diameters.begin(), diameters.end()); - filament_change_volume = length * PI * diameter * diameter / 4.; + return !m_config.spiral_mode.value && m_config.filament_diameter.values.size() > 1; + } + return false; } + const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const + { + // If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default. + double max_height = 0; + for (size_t obj_idx = 0; obj_idx < m_objects.size(); obj_idx++) + { + double object_z = (double)m_objects[obj_idx]->size().z(); + max_height = std::max(unscale_(object_z), max_height); + } + if (max_height < EPSILON) + return m_wipe_tower_data; + + double layer_height = 0.08f; // hard code layer height + layer_height = m_objects.front()->config().layer_height.value; + + auto timelapse_type = config().option>("timelapse_type"); + bool need_wipe_tower = (timelapse_type ? (timelapse_type->value == TimelapseType::tlSmooth) : false) | m_config.prime_tower_rib_wall.value; + double extra_spacing = config().option("prime_tower_infill_gap")->getFloat() / 100.; + double rib_width = config().option("prime_tower_rib_width")->getFloat(); - if (! is_step_done(psWipeTower) && filaments_cnt !=0) { - std::vector filament_wipe_volume = m_config.filament_prime_volume.values; - if (m_config.prime_volume_mode == pvmSaving) { - for (auto& v : filament_wipe_volume) - v = 15.f; - } - double wipe_volume = get_max_element(filament_wipe_volume); - int filament_depth_count = m_config.nozzle_diameter.values.size() == 2 ? filaments_cnt : filaments_cnt - 1; - if (filaments_cnt == 1 && enable_timelapse_print()) filament_depth_count = 1; - double volume = wipe_volume * filament_depth_count; - if (m_config.nozzle_diameter.values.size() == 2) volume += filament_change_volume * (int) (filaments_cnt / 2); - - if (m_config.prime_tower_rib_wall.value) { - double depth = std::sqrt(volume / layer_height * extra_spacing); - if (need_wipe_tower || filaments_cnt > 1) { - float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height); - depth = std::max((double) min_wipe_tower_depth, depth); - depth += rib_width / std::sqrt(2) + config().prime_tower_extra_rib_length.value; + double filament_change_volume = 0.; + { + std::vector filament_change_lengths; + auto filament_change_lengths_opt = config().option("filament_change_length"); + if (filament_change_lengths_opt) + filament_change_lengths = filament_change_lengths_opt->values; + double length = filament_change_lengths.empty() ? 0 : *std::max_element(filament_change_lengths.begin(), filament_change_lengths.end()); + double diameter = 1.75; + std::vector diameters; + auto filament_diameter_opt = config().option("filament_diameter"); + if (filament_diameter_opt) + diameters = filament_diameter_opt->values; + diameter = diameters.empty() ? diameter : *std::max_element(diameters.begin(), diameters.end()); + filament_change_volume = length * PI * diameter * diameter / 4.; + } + + if (!is_step_done(psWipeTower) && filaments_cnt != 0) + { + std::vector filament_wipe_volume = m_config.filament_prime_volume.values; + if (m_config.prime_volume_mode == pvmSaving) + { + for (auto &v : filament_wipe_volume) + v = 15.f; + } + double wipe_volume = get_max_element(filament_wipe_volume); + int filament_depth_count = m_config.nozzle_diameter.values.size() == 2 ? filaments_cnt : filaments_cnt - 1; + if (filaments_cnt == 1 && enable_timelapse_print()) + filament_depth_count = 1; + double volume = wipe_volume * filament_depth_count; + if (m_config.nozzle_diameter.values.size() == 2) + volume += filament_change_volume * (int)(filaments_cnt / 2); + + if (m_config.prime_tower_rib_wall.value) + { + double depth = std::sqrt(volume / layer_height * extra_spacing); + if (need_wipe_tower || filaments_cnt > 1) + { + float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height); + depth = std::max((double)min_wipe_tower_depth, depth); + depth += rib_width / std::sqrt(2) + config().prime_tower_extra_rib_length.value; + const_cast(this)->m_wipe_tower_data.depth = depth; + const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; + } + } + else + { + // BBS + double width = m_config.prime_tower_width; + double depth = volume / (layer_height * width) * extra_spacing; + if (need_wipe_tower || m_wipe_tower_data.depth > EPSILON) + { + float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height); + depth = std::max((double)min_wipe_tower_depth, depth); + } const_cast(this)->m_wipe_tower_data.depth = depth; const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; } + if (m_config.prime_tower_brim_width < 0) + const_cast(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height); } - else { - // BBS - double width = m_config.prime_tower_width; - double depth = volume / (layer_height * width) * extra_spacing; - if (need_wipe_tower || m_wipe_tower_data.depth > EPSILON) { - float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height); - depth = std::max((double) min_wipe_tower_depth, depth); - } - const_cast(this)->m_wipe_tower_data.depth = depth; - const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; - } - if (m_config.prime_tower_brim_width < 0) const_cast(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height); + return m_wipe_tower_data; } - return m_wipe_tower_data; -} - -bool Print::enable_timelapse_print() const -{ - return m_config.timelapse_type.value == TimelapseType::tlSmooth; -} -void Print::_make_wipe_tower() -{ - m_wipe_tower_data.clear(); + bool Print::enable_timelapse_print() const + { + return m_config.timelapse_type.value == TimelapseType::tlSmooth; + } - // BBS - const unsigned int number_of_extruders = (unsigned int)(m_config.filament_colour.values.size()); + void Print::_make_wipe_tower() + { + m_wipe_tower_data.clear(); - // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print. - // BBS: priming logic is removed, so don't consider it in tool ordering - m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, false); - m_wipe_tower_data.tool_ordering.sort_and_build_data(*this, (unsigned int)-1, false); + // BBS + const unsigned int number_of_extruders = (unsigned int)(m_config.filament_colour.values.size()); - if (!m_wipe_tower_data.tool_ordering.has_wipe_tower()) - // Don't generate any wipe tower. - return; + // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print. + // BBS: priming logic is removed, so don't consider it in tool ordering + m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, false); + m_wipe_tower_data.tool_ordering.sort_and_build_data(*this, (unsigned int)-1, false); - // Check whether there are any layers in m_tool_ordering, which are marked with has_wipe_tower, - // they print neither object, nor support. These layers are above the raft and below the object, and they - // shall be added to the support layers to be printed. - // see https://github.com/prusa3d/PrusaSlicer/issues/607 - { - size_t idx_begin = size_t(-1); - size_t idx_end = m_wipe_tower_data.tool_ordering.layer_tools().size(); - // Find the first wipe tower layer, which does not have a counterpart in an object or a support layer. - for (size_t i = 0; i < idx_end; ++ i) { - const LayerTools < = m_wipe_tower_data.tool_ordering.layer_tools()[i]; - if (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support) { - idx_begin = i; - break; - } - } - if (idx_begin != size_t(-1)) { - // Find the position in m_objects.first()->support_layers to insert these new support layers. - double wipe_tower_new_layer_print_z_first = m_wipe_tower_data.tool_ordering.layer_tools()[idx_begin].print_z; - auto it_layer = m_objects.front()->support_layers().begin(); - auto it_end = m_objects.front()->support_layers().end(); - for (; it_layer != it_end && (*it_layer)->print_z - EPSILON < wipe_tower_new_layer_print_z_first; ++ it_layer); - // Find the stopper of the sequence of wipe tower layers, which do not have a counterpart in an object or a support layer. - for (size_t i = idx_begin; i < idx_end; ++ i) { - LayerTools < = const_cast(m_wipe_tower_data.tool_ordering.layer_tools()[i]); - if (! (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support)) + if (!m_wipe_tower_data.tool_ordering.has_wipe_tower()) + // Don't generate any wipe tower. + return; + + // Check whether there are any layers in m_tool_ordering, which are marked with has_wipe_tower, + // they print neither object, nor support. These layers are above the raft and below the object, and they + // shall be added to the support layers to be printed. + // see https://github.com/prusa3d/PrusaSlicer/issues/607 + { + size_t idx_begin = size_t(-1); + size_t idx_end = m_wipe_tower_data.tool_ordering.layer_tools().size(); + // Find the first wipe tower layer, which does not have a counterpart in an object or a support layer. + for (size_t i = 0; i < idx_end; ++i) + { + const LayerTools < = m_wipe_tower_data.tool_ordering.layer_tools()[i]; + if (lt.has_wipe_tower && !lt.has_object && !lt.has_support) + { + idx_begin = i; break; - lt.has_support = true; - // Insert the new support layer. - double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z); - //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. - it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height); - ++ it_layer; + } + } + if (idx_begin != size_t(-1)) + { + // Find the position in m_objects.first()->support_layers to insert these new support layers. + double wipe_tower_new_layer_print_z_first = m_wipe_tower_data.tool_ordering.layer_tools()[idx_begin].print_z; + auto it_layer = m_objects.front()->support_layers().begin(); + auto it_end = m_objects.front()->support_layers().end(); + for (; it_layer != it_end && (*it_layer)->print_z - EPSILON < wipe_tower_new_layer_print_z_first; ++it_layer) + ; + // Find the stopper of the sequence of wipe tower layers, which do not have a counterpart in an object or a support layer. + for (size_t i = idx_begin; i < idx_end; ++i) + { + LayerTools < = const_cast(m_wipe_tower_data.tool_ordering.layer_tools()[i]); + if (!(lt.has_wipe_tower && !lt.has_object && !lt.has_support)) + break; + lt.has_support = true; + // Insert the new support layer. + double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i - 1].print_z); + // FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. + it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height); + ++it_layer; + } } } - } - this->throw_if_canceled(); - - // Initialize the wipe tower. - // BBS: in BBL machine, wipe tower is only use to prime extruder. So just use a global wipe volume. - WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_wipe_tower_data.tool_ordering.first_extruder(), - m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders()); - wipe_tower.set_first_layer_flow_ratio(m_default_region_config.initial_layer_flow_ratio); - wipe_tower.set_has_tpu_filament(this->has_tpu_filament()); - wipe_tower.set_filament_map(this->get_filament_maps()); - wipe_tower.set_nozzle_group_result(m_nozzle_group_result.value()); - // Set the extruder & material properties at the wipe tower object. - for (size_t i = 0; i < number_of_extruders; ++ i) - wipe_tower.set_extruder(i, m_config); - // BBS: remove priming logic - //m_wipe_tower_data.priming = Slic3r::make_unique>( - // wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); - - std::set used_filament_ids; - - // Lets go through the wipe tower layers and determine pairs of extruder changes for each - // to pass to wipe_tower (so that it can use it for planning the layout of the tower) - { - // Get wiping matrix to get number of extruders and convert vector to vector: - bool is_mutli_extruder = m_config.nozzle_diameter.values.size() > 1; - size_t nozzle_nums = m_config.nozzle_diameter.values.size(); - using FlushMatrix = std::vector>; - std::vector multi_extruder_flush; - for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { - std::vector flush_matrix(cast(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, nozzle_id, nozzle_nums))); - std::vector> wipe_volumes; - for (unsigned int i = 0; i < number_of_extruders; ++i) - wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); - - multi_extruder_flush.emplace_back(wipe_volumes); - } + this->throw_if_canceled(); - std::vectorfilament_maps = get_filament_maps(); - MultiNozzleUtils::NozzleStatusRecorder nozzle_recorder; + // Initialize the wipe tower. + // BBS: in BBL machine, wipe tower is only use to prime extruder. So just use a global wipe volume. + WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_wipe_tower_data.tool_ordering.first_extruder(), + m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders()); + wipe_tower.set_first_layer_flow_ratio(m_default_region_config.initial_layer_flow_ratio); + wipe_tower.set_has_tpu_filament(this->has_tpu_filament()); + wipe_tower.set_filament_map(this->get_filament_maps()); + wipe_tower.set_nozzle_group_result(m_nozzle_group_result.value()); + // Set the extruder & material properties at the wipe tower object. + for (size_t i = 0; i < number_of_extruders; ++i) + wipe_tower.set_extruder(i, m_config); + // BBS: remove priming logic + // m_wipe_tower_data.priming = Slic3r::make_unique>( + // wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); + + std::set used_filament_ids; + + // Lets go through the wipe tower layers and determine pairs of extruder changes for each + // to pass to wipe_tower (so that it can use it for planning the layout of the tower) + { + // Get wiping matrix to get number of extruders and convert vector to vector: + bool is_mutli_extruder = m_config.nozzle_diameter.values.size() > 1; + size_t nozzle_nums = m_config.nozzle_diameter.values.size(); + using FlushMatrix = std::vector>; + std::vector multi_extruder_flush; + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) + { + std::vector flush_matrix(cast(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, nozzle_id, nozzle_nums))); + std::vector> wipe_volumes; + for (unsigned int i = 0; i < number_of_extruders; ++i) + wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); - assert(m_nozzle_group_result.has_value()); - unsigned int old_filament_id = m_wipe_tower_data.tool_ordering.first_extruder(); - nozzle_recorder.set_nozzle_status(m_nozzle_group_result->get_nozzle_for_filament(old_filament_id)->group_id, old_filament_id); + multi_extruder_flush.emplace_back(wipe_volumes); + } - for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers - if (!layer_tools.has_wipe_tower) continue; - bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); - wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, old_filament_id, old_filament_id); + std::vector filament_maps = get_filament_maps(); + MultiNozzleUtils::NozzleStatusRecorder nozzle_recorder; - used_filament_ids.insert(layer_tools.extruders.begin(), layer_tools.extruders.end()); + assert(m_nozzle_group_result.has_value()); + unsigned int old_filament_id = m_wipe_tower_data.tool_ordering.first_extruder(); + nozzle_recorder.set_nozzle_status(m_nozzle_group_result->get_nozzle_for_filament(old_filament_id)->group_id, old_filament_id); - for (const auto filament_id : layer_tools.extruders) { - if (filament_id == old_filament_id) + for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) + { // for all layers + if (!layer_tools.has_wipe_tower) continue; + bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, old_filament_id, old_filament_id); + + used_filament_ids.insert(layer_tools.extruders.begin(), layer_tools.extruders.end()); + + for (const auto filament_id : layer_tools.extruders) + { + if (filament_id == old_filament_id) + continue; - int extruder_id = filament_maps[filament_id] - 1; - int nozzle_id = m_nozzle_group_result->get_nozzle_for_filament(filament_id)->group_id; - int prev_nozzle_filament = nozzle_recorder.get_filament_in_nozzle(nozzle_id); + int extruder_id = filament_maps[filament_id] - 1; + int nozzle_id = m_nozzle_group_result->get_nozzle_for_filament(filament_id)->group_id; + int prev_nozzle_filament = nozzle_recorder.get_filament_in_nozzle(nozzle_id); - float volume_to_purge = 0; + float volume_to_purge = 0; - if(!nozzle_recorder.is_nozzle_empty(nozzle_id) && filament_id != prev_nozzle_filament){ - volume_to_purge = multi_extruder_flush[extruder_id][prev_nozzle_filament][filament_id]; - volume_to_purge *= m_config.flush_multiplier.get_at(extruder_id); - volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, old_filament_id, filament_id, volume_to_purge); - } + if (!nozzle_recorder.is_nozzle_empty(nozzle_id) && filament_id != prev_nozzle_filament) + { + volume_to_purge = multi_extruder_flush[extruder_id][prev_nozzle_filament][filament_id]; + volume_to_purge *= m_config.flush_multiplier.get_at(extruder_id); + volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, old_filament_id, filament_id, volume_to_purge); + } - //During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length. - float grab_purge_volume = m_config.grab_length.get_at(extruder_id) * 2.4; //(diameter/2)^2*PI=2.4 - volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume); + // During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length. + float grab_purge_volume = m_config.grab_length.get_at(extruder_id) * 2.4; //(diameter/2)^2*PI=2.4 + volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume); + + float wipe_volume_ec = m_config.filament_prime_volume.values[filament_id]; + float wipe_volume_nc = m_config.filament_prime_volume_nc.values[filament_id]; + // special primte volume settings for H2C + if (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) + { + wipe_volume_ec = 15.f; + wipe_volume_nc = 15.f; + } + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, old_filament_id, filament_id, + wipe_volume_ec, wipe_volume_nc, volume_to_purge); + old_filament_id = filament_id; - float wipe_volume_ec = m_config.filament_prime_volume.values[filament_id]; - float wipe_volume_nc = m_config.filament_prime_volume_nc.values[filament_id]; - // special primte volume settings for H2C - if(m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving){ - wipe_volume_ec = 15.f; - wipe_volume_nc = 15.f; + nozzle_recorder.set_nozzle_status(nozzle_id, filament_id); } - wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, old_filament_id, filament_id, - wipe_volume_ec, wipe_volume_nc, volume_to_purge); - old_filament_id = filament_id; + layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); - nozzle_recorder.set_nozzle_status(nozzle_id, filament_id); - } - layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); + // if enable timelapse, slice all layer + if (m_config.enable_wrapping_detection || enable_timelapse_print()) + { + if (layer_tools.wipe_tower_partitions == 0) + wipe_tower.set_last_layer_extruder_fill(false); + continue; + } - // if enable timelapse, slice all layer - if (m_config.enable_wrapping_detection || enable_timelapse_print()) { - if (layer_tools.wipe_tower_partitions == 0) wipe_tower.set_last_layer_extruder_fill(false); - continue; + if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) + break; } + } + wipe_tower.set_used_filament_ids(std::vector(used_filament_ids.begin(), used_filament_ids.end())); - if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) - break; + std::vector categories; + for (size_t i = 0; i < m_config.filament_adhesiveness_category.values.size(); ++i) + { + categories.push_back(m_config.filament_adhesiveness_category.get_at(i)); + } + wipe_tower.set_filament_categories(categories); + // Generate the wipe tower layers. + m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size()); + wipe_tower.generate_new(m_wipe_tower_data.tool_changes); + m_wipe_tower_data.depth = wipe_tower.get_depth(); + m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); + m_wipe_tower_data.bbx = wipe_tower.get_bbx(); + m_wipe_tower_data.rib_offset = wipe_tower.get_rib_offset(); + + // Unload the current filament over the purge tower. + coordf_t layer_height = m_objects.front()->config().layer_height.value; + if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) + { + // The wipe tower goes up to the last layer of the print. + if (wipe_tower.layer_finished()) + { + // The wipe tower is printed to the top of the print and it has no space left for the final extruder purge. + // Lift Z to the next layer. + wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z + layer_height), float(layer_height), 0, false, true); + } + else + { + // There is yet enough space at this layer of the wipe tower for the final purge. + } } - } - wipe_tower.set_used_filament_ids(std::vector(used_filament_ids.begin(), used_filament_ids.end())); + else + { + // The wipe tower does not reach the last print layer, perform the pruge at the last print layer. + assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0); + wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true); + } + m_wipe_tower_data.final_purge = Slic3r::make_unique( + wipe_tower.tool_change((unsigned int)(-1))); - std::vector categories; - for (size_t i = 0; i < m_config.filament_adhesiveness_category.values.size(); ++i) { - categories.push_back(m_config.filament_adhesiveness_category.get_at(i)); - } - wipe_tower.set_filament_categories(categories); - // Generate the wipe tower layers. - m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size()); - wipe_tower.generate_new(m_wipe_tower_data.tool_changes); - m_wipe_tower_data.depth = wipe_tower.get_depth(); - m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); - m_wipe_tower_data.bbx = wipe_tower.get_bbx(); - m_wipe_tower_data.rib_offset = wipe_tower.get_rib_offset(); - - // Unload the current filament over the purge tower. - coordf_t layer_height = m_objects.front()->config().layer_height.value; - if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) { - // The wipe tower goes up to the last layer of the print. - if (wipe_tower.layer_finished()) { - // The wipe tower is printed to the top of the print and it has no space left for the final extruder purge. - // Lift Z to the next layer. - wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z + layer_height), float(layer_height), 0, false, true); - } else { - // There is yet enough space at this layer of the wipe tower for the final purge. - } - } else { - // The wipe tower does not reach the last print layer, perform the pruge at the last print layer. - assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0); - wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true); + m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); + m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); + m_wipe_tower_data.construct_mesh(wipe_tower.width(), wipe_tower.get_depth(), wipe_tower.get_height(), wipe_tower.get_brim_width(), config().prime_tower_rib_wall.value, + wipe_tower.get_rib_width(), wipe_tower.get_rib_length(), config().prime_tower_fillet_wall.value); + const Vec3d origin = this->get_plate_origin(); + m_fake_wipe_tower.rib_offset = wipe_tower.get_rib_offset(); + m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position() + m_fake_wipe_tower.rib_offset, wipe_tower.width(), wipe_tower.get_height(), wipe_tower.get_layer_height(), + m_wipe_tower_data.depth, + m_wipe_tower_data.brim_width, {scale_(origin.x()), scale_(origin.y())}); + m_fake_wipe_tower.outer_wall = wipe_tower.get_outer_wall(); } - m_wipe_tower_data.final_purge = Slic3r::make_unique( - wipe_tower.tool_change((unsigned int)(-1))); - - m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); - m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); - m_wipe_tower_data.construct_mesh(wipe_tower.width(), wipe_tower.get_depth(), wipe_tower.get_height(), wipe_tower.get_brim_width(), config().prime_tower_rib_wall.value, - wipe_tower.get_rib_width(), wipe_tower.get_rib_length(), config().prime_tower_fillet_wall.value); - const Vec3d origin = this->get_plate_origin(); - m_fake_wipe_tower.rib_offset = wipe_tower.get_rib_offset(); - m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position() + m_fake_wipe_tower.rib_offset, wipe_tower.width(), wipe_tower.get_height(), wipe_tower.get_layer_height(), - m_wipe_tower_data.depth, - m_wipe_tower_data.brim_width, {scale_(origin.x()), scale_(origin.y())}); - m_fake_wipe_tower.outer_wall = wipe_tower.get_outer_wall(); -} - -// Generate a recommended G-code output file name based on the format template, default extension, and template parameters -// (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics. -// Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before G-code is finalized). -std::string Print::output_filename(const std::string &filename_base) const -{ - // Set the placeholders for the data know first after the G-code export is finished. - // These values will be just propagated into the output file name. - DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); - config.set_key_value("num_filaments", new ConfigOptionInt((int)m_config.nozzle_diameter.size())); - config.set_key_value("plate_name", new ConfigOptionString(get_plate_name())); - return this->PrintBase::output_filename(m_config.filename_format.value, ".gcode", filename_base, &config); -} + // Generate a recommended G-code output file name based on the format template, default extension, and template parameters + // (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics. + // Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before G-code is finalized). + std::string Print::output_filename(const std::string &filename_base) const + { + // Set the placeholders for the data know first after the G-code export is finished. + // These values will be just propagated into the output file name. + DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); + config.set_key_value("num_filaments", new ConfigOptionInt((int)m_config.nozzle_diameter.size())); + config.set_key_value("plate_name", new ConfigOptionString(get_plate_name())); -//BBS: add gcode file preload logic -void Print::set_gcode_file_ready() -{ - this->set_started(psGCodeExport); - this->set_done(psGCodeExport); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": done"); -} -//BBS: add gcode file preload logic -void Print::set_gcode_file_invalidated() -{ - this->invalidate_step(psGCodeExport); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": done"); -} + return this->PrintBase::output_filename(m_config.filename_format.value, ".gcode", filename_base, &config); + } -//BBS: add gcode file preload logic -void Print::export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) -{ - try { - GCodeProcessor processor; - if (result && result->nozzle_group_result) - processor.initialize_from_context(*result->nozzle_group_result); - const Vec3d origin = this->get_plate_origin(); - processor.set_xy_offset(origin(0), origin(1)); - //processor.enable_producers(true); - processor.process_file(file); - - // filament seq is loaded from file, processor result will override the value - auto seq_loaded = result->filament_change_sequence; - *result = std::move(processor.extract_result()); - result->filament_change_sequence = seq_loaded; - } catch (std::exception & /* ex */) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str(); - throw Slic3r::RuntimeError( - std::string("Failed to process the G-code file ") + file + " from previous 3mf\n"); + // BBS: add gcode file preload logic + void Print::set_gcode_file_ready() + { + this->set_started(psGCodeExport); + this->set_done(psGCodeExport); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": done"); + } + // BBS: add gcode file preload logic + void Print::set_gcode_file_invalidated() + { + this->invalidate_step(psGCodeExport); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": done"); } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": process the G-code file %1% successfully")%file.c_str(); -} + // BBS: add gcode file preload logic + void Print::export_gcode_from_previous_file(const std::string &file, GCodeProcessorResult *result, ThumbnailsGeneratorCallback thumbnail_cb) + { + try + { + GCodeProcessor processor; + if (result && result->nozzle_group_result) + processor.initialize_from_context(*result->nozzle_group_result); + const Vec3d origin = this->get_plate_origin(); + processor.set_xy_offset(origin(0), origin(1)); + // processor.enable_producers(true); + processor.process_file(file); + + // filament seq is loaded from file, processor result will override the value + auto seq_loaded = result->filament_change_sequence; + *result = std::move(processor.extract_result()); + result->filament_change_sequence = seq_loaded; + } + catch (std::exception & /* ex */) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") % file.c_str(); + throw Slic3r::RuntimeError( + std::string("Failed to process the G-code file ") + file + " from previous 3mf\n"); + } -DynamicConfig PrintStatistics::config() const -{ - DynamicConfig config; - std::string normal_print_time = short_time(this->estimated_normal_print_time); - std::string silent_print_time = short_time(this->estimated_silent_print_time); - config.set_key_value("print_time", new ConfigOptionString(normal_print_time)); - config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time)); - config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time)); - config.set_key_value("used_filament", new ConfigOptionFloat(this->total_used_filament / 1000.)); - config.set_key_value("extruded_volume", new ConfigOptionFloat(this->total_extruded_volume)); - config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost)); - config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges)); - config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight)); - config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost)); - config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament)); - config.set_key_value("initial_tool", new ConfigOptionInt(static_cast(this->initial_tool))); - return config; -} + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": process the G-code file %1% successfully") % file.c_str(); + } -DynamicConfig PrintStatistics::placeholders() -{ - DynamicConfig config; - for (const std::string &key : { - "print_time", "normal_print_time", "silent_print_time", - "used_filament", "extruded_volume", "total_cost", "total_weight", - "intial_tool", "total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"}) - config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); - return config; -} + DynamicConfig PrintStatistics::config() const + { + DynamicConfig config; + std::string normal_print_time = short_time(this->estimated_normal_print_time); + std::string silent_print_time = short_time(this->estimated_silent_print_time); + config.set_key_value("print_time", new ConfigOptionString(normal_print_time)); + config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time)); + config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time)); + config.set_key_value("used_filament", new ConfigOptionFloat(this->total_used_filament / 1000.)); + config.set_key_value("extruded_volume", new ConfigOptionFloat(this->total_extruded_volume)); + config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost)); + config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges)); + config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight)); + config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost)); + config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament)); + config.set_key_value("initial_tool", new ConfigOptionInt(static_cast(this->initial_tool))); + return config; + } + + DynamicConfig PrintStatistics::placeholders() + { + DynamicConfig config; + for (const std::string &key : { + "print_time", "normal_print_time", "silent_print_time", + "used_filament", "extruded_volume", "total_cost", "total_weight", + "intial_tool", "total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"}) + config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); + return config; + } -std::string PrintStatistics::finalize_output_path(const std::string &path_in) const -{ - std::string final_path; - try { - boost::filesystem::path path(path_in); - DynamicConfig cfg = this->config(); - PlaceholderParser pp; - std::string new_stem = pp.process(path.stem().string(), 0, &cfg); - final_path = (path.parent_path() / (new_stem + path.extension().string())).string(); - } catch (const std::exception &ex) { - BOOST_LOG_TRIVIAL(error) << "Failed to apply the print statistics to the export file name: " << ex.what(); - final_path = path_in; + std::string PrintStatistics::finalize_output_path(const std::string &path_in) const + { + std::string final_path; + try + { + boost::filesystem::path path(path_in); + DynamicConfig cfg = this->config(); + PlaceholderParser pp; + std::string new_stem = pp.process(path.stem().string(), 0, &cfg); + final_path = (path.parent_path() / (new_stem + path.extension().string())).string(); + } + catch (const std::exception &ex) + { + BOOST_LOG_TRIVIAL(error) << "Failed to apply the print statistics to the export file name: " << ex.what(); + final_path = path_in; + } + return final_path; } - return final_path; -} /*add json export/import related functions */ -#define JSON_POLYGON_CONTOUR "contour" -#define JSON_POLYGON_HOLES "holes" -#define JSON_POINTS "points" -#define JSON_EXPOLYGON "expolygon" -#define JSON_ARC_FITTING "arc_fitting" -#define JSON_OBJECT_NAME "name" -#define JSON_IDENTIFY_ID "identify_id" - - -#define JSON_LAYERS "layers" -#define JSON_SUPPORT_LAYERS "support_layers" -#define JSON_TREE_SUPPORT_LAYERS "tree_support_layers" -#define JSON_LAYER_REGIONS "layer_regions" -#define JSON_FIRSTLAYER_GROUPS "first_layer_groups" - -#define JSON_FIRSTLAYER_GROUP_ID "group_id" -#define JSON_FIRSTLAYER_GROUP_VOLUME_IDS "volume_ids" -#define JSON_FIRSTLAYER_GROUP_SLICES "slices" - -#define JSON_LAYER_PRINT_Z "print_z" -#define JSON_LAYER_SLICE_Z "slice_z" -#define JSON_LAYER_HEIGHT "height" -#define JSON_LAYER_ID "layer_id" -#define JSON_LAYER_SLICED_POLYGONS "sliced_polygons" -#define JSON_LAYER_SLLICED_BBOXES "sliced_bboxes" -#define JSON_LAYER_OVERHANG_POLYGONS "overhang_polygons" -#define JSON_LAYER_OVERHANG_BBOX "overhang_bbox" - -#define JSON_SUPPORT_LAYER_ISLANDS "support_islands" -#define JSON_SUPPORT_LAYER_FILLS "support_fills" -#define JSON_SUPPORT_LAYER_INTERFACE_ID "interface_id" - -#define JSON_LAYER_REGION_CONFIG_HASH "config_hash" -#define JSON_LAYER_REGION_SLICES "slices" -#define JSON_LAYER_REGION_RAW_SLICES "raw_slices" -//#define JSON_LAYER_REGION_ENTITIES "entities" -#define JSON_LAYER_REGION_THIN_FILLS "thin_fills" -#define JSON_LAYER_REGION_FILL_EXPOLYGONS "fill_expolygons" -#define JSON_LAYER_REGION_FILL_SURFACES "fill_surfaces" -#define JSON_LAYER_REGION_FILL_NO_OVERLAP "fill_no_overlap_expolygons" -#define JSON_LAYER_REGION_UNSUPPORTED_BRIDGE_EDGES "unsupported_bridge_edges" -#define JSON_LAYER_REGION_PERIMETERS "perimeters" -#define JSON_LAYER_REGION_FILLS "fills" - - - -#define JSON_SURF_TYPE "surface_type" -#define JSON_SURF_THICKNESS "thickness" -#define JSON_SURF_THICKNESS_LAYER "thickness_layers" -#define JSON_SURF_BRIDGE_ANGLE "bridge_angle" -#define JSON_SURF_EXTRA_PERIMETERS "extra_perimeters" - -#define JSON_ARC_DATA "arc_data" -#define JSON_ARC_START_INDEX "start_index" -#define JSON_ARC_END_INDEX "end_index" -#define JSON_ARC_PATH_TYPE "path_type" - -#define JSON_IS_ARC "is_arc" -#define JSON_ARC_LENGTH "length" -#define JSON_ARC_ANGLE_RADIUS "angle_radians" -#define JSON_ARC_POLAY_START_THETA "polar_start_theta" -#define JSON_ARC_POLAY_END_THETA "polar_end_theta" -#define JSON_ARC_START_POINT "start_point" -#define JSON_ARC_END_POINT "end_point" -#define JSON_ARC_DIRECTION "direction" -#define JSON_ARC_RADIUS "radius" -#define JSON_ARC_CENTER "center" - -//extrusions -#define JSON_EXTRUSION_ENTITY_TYPE "entity_type" -#define JSON_EXTRUSION_NO_SORT "no_sort" -#define JSON_EXTRUSION_PATHS "paths" -#define JSON_EXTRUSION_ENTITIES "entities" -#define JSON_EXTRUSION_TYPE_PATH "path" -#define JSON_EXTRUSION_TYPE_MULTIPATH "multipath" -#define JSON_EXTRUSION_TYPE_LOOP "loop" -#define JSON_EXTRUSION_TYPE_COLLECTION "collection" -#define JSON_EXTRUSION_POLYLINE "polyline" -#define JSON_EXTRUSION_OVERHANG_DEGREE "overhang_degree" -#define JSON_EXTRUSION_CURVE_DEGREE "curve_degree" -#define JSON_EXTRUSION_MM3_PER_MM "mm3_per_mm" -#define JSON_EXTRUSION_WIDTH "width" -#define JSON_EXTRUSION_HEIGHT "height" -#define JSON_EXTRUSION_ROLE "role" -#define JSON_EXTRUSION_NO_EXTRUSION "no_extrusion" -#define JSON_EXTRUSION_LOOP_ROLE "loop_role" - - -static void to_json(json& j, const Points& p_s) { - for (const Point& p : p_s) +#define JSON_POLYGON_CONTOUR "contour" +#define JSON_POLYGON_HOLES "holes" +#define JSON_POINTS "points" +#define JSON_EXPOLYGON "expolygon" +#define JSON_ARC_FITTING "arc_fitting" +#define JSON_OBJECT_NAME "name" +#define JSON_IDENTIFY_ID "identify_id" + +#define JSON_LAYERS "layers" +#define JSON_SUPPORT_LAYERS "support_layers" +#define JSON_TREE_SUPPORT_LAYERS "tree_support_layers" +#define JSON_LAYER_REGIONS "layer_regions" +#define JSON_FIRSTLAYER_GROUPS "first_layer_groups" + +#define JSON_FIRSTLAYER_GROUP_ID "group_id" +#define JSON_FIRSTLAYER_GROUP_VOLUME_IDS "volume_ids" +#define JSON_FIRSTLAYER_GROUP_SLICES "slices" + +#define JSON_LAYER_PRINT_Z "print_z" +#define JSON_LAYER_SLICE_Z "slice_z" +#define JSON_LAYER_HEIGHT "height" +#define JSON_LAYER_ID "layer_id" +#define JSON_LAYER_SLICED_POLYGONS "sliced_polygons" +#define JSON_LAYER_SLLICED_BBOXES "sliced_bboxes" +#define JSON_LAYER_OVERHANG_POLYGONS "overhang_polygons" +#define JSON_LAYER_OVERHANG_BBOX "overhang_bbox" + +#define JSON_SUPPORT_LAYER_ISLANDS "support_islands" +#define JSON_SUPPORT_LAYER_FILLS "support_fills" +#define JSON_SUPPORT_LAYER_INTERFACE_ID "interface_id" + +#define JSON_LAYER_REGION_CONFIG_HASH "config_hash" +#define JSON_LAYER_REGION_SLICES "slices" +#define JSON_LAYER_REGION_RAW_SLICES "raw_slices" +// #define JSON_LAYER_REGION_ENTITIES "entities" +#define JSON_LAYER_REGION_THIN_FILLS "thin_fills" +#define JSON_LAYER_REGION_FILL_EXPOLYGONS "fill_expolygons" +#define JSON_LAYER_REGION_FILL_SURFACES "fill_surfaces" +#define JSON_LAYER_REGION_FILL_NO_OVERLAP "fill_no_overlap_expolygons" +#define JSON_LAYER_REGION_UNSUPPORTED_BRIDGE_EDGES "unsupported_bridge_edges" +#define JSON_LAYER_REGION_PERIMETERS "perimeters" +#define JSON_LAYER_REGION_FILLS "fills" + +#define JSON_SURF_TYPE "surface_type" +#define JSON_SURF_THICKNESS "thickness" +#define JSON_SURF_THICKNESS_LAYER "thickness_layers" +#define JSON_SURF_BRIDGE_ANGLE "bridge_angle" +#define JSON_SURF_EXTRA_PERIMETERS "extra_perimeters" + +#define JSON_ARC_DATA "arc_data" +#define JSON_ARC_START_INDEX "start_index" +#define JSON_ARC_END_INDEX "end_index" +#define JSON_ARC_PATH_TYPE "path_type" + +#define JSON_IS_ARC "is_arc" +#define JSON_ARC_LENGTH "length" +#define JSON_ARC_ANGLE_RADIUS "angle_radians" +#define JSON_ARC_POLAY_START_THETA "polar_start_theta" +#define JSON_ARC_POLAY_END_THETA "polar_end_theta" +#define JSON_ARC_START_POINT "start_point" +#define JSON_ARC_END_POINT "end_point" +#define JSON_ARC_DIRECTION "direction" +#define JSON_ARC_RADIUS "radius" +#define JSON_ARC_CENTER "center" + +// extrusions +#define JSON_EXTRUSION_ENTITY_TYPE "entity_type" +#define JSON_EXTRUSION_NO_SORT "no_sort" +#define JSON_EXTRUSION_PATHS "paths" +#define JSON_EXTRUSION_ENTITIES "entities" +#define JSON_EXTRUSION_TYPE_PATH "path" +#define JSON_EXTRUSION_TYPE_MULTIPATH "multipath" +#define JSON_EXTRUSION_TYPE_LOOP "loop" +#define JSON_EXTRUSION_TYPE_COLLECTION "collection" +#define JSON_EXTRUSION_POLYLINE "polyline" +#define JSON_EXTRUSION_OVERHANG_DEGREE "overhang_degree" +#define JSON_EXTRUSION_CURVE_DEGREE "curve_degree" +#define JSON_EXTRUSION_MM3_PER_MM "mm3_per_mm" +#define JSON_EXTRUSION_WIDTH "width" +#define JSON_EXTRUSION_HEIGHT "height" +#define JSON_EXTRUSION_ROLE "role" +#define JSON_EXTRUSION_NO_EXTRUSION "no_extrusion" +#define JSON_EXTRUSION_LOOP_ROLE "loop_role" + + static void to_json(json &j, const Points &p_s) { - j.push_back(p.x()); - j.push_back(p.y()); + for (const Point &p : p_s) + { + j.push_back(p.x()); + j.push_back(p.y()); + } } -} -static void to_json(json& j, const BoundingBox& bb) { - j.push_back(bb.min.x()); - j.push_back(bb.min.y()); - j.push_back(bb.max.x()); - j.push_back(bb.max.y()); -} + static void to_json(json &j, const BoundingBox &bb) + { + j.push_back(bb.min.x()); + j.push_back(bb.min.y()); + j.push_back(bb.max.x()); + j.push_back(bb.max.y()); + } -static void to_json(json& j, const ExPolygon& polygon) { - json contour_json = json::array(), holes_json = json::array(); + static void to_json(json &j, const ExPolygon &polygon) + { + json contour_json = json::array(), holes_json = json::array(); - //contour - const Polygon& slice_contour = polygon.contour; - contour_json = slice_contour.points; - j[JSON_POLYGON_CONTOUR] = std::move(contour_json); + // contour + const Polygon &slice_contour = polygon.contour; + contour_json = slice_contour.points; + j[JSON_POLYGON_CONTOUR] = std::move(contour_json); - //holes - const Polygons& slice_holes = polygon.holes; - for (const Polygon& hole_polyon : slice_holes) - { - json hole_json = json::array(); - hole_json = hole_polyon.points; - holes_json.push_back(std::move(hole_json)); + // holes + const Polygons &slice_holes = polygon.holes; + for (const Polygon &hole_polyon : slice_holes) + { + json hole_json = json::array(); + hole_json = hole_polyon.points; + holes_json.push_back(std::move(hole_json)); + } + j[JSON_POLYGON_HOLES] = std::move(holes_json); } - j[JSON_POLYGON_HOLES] = std::move(holes_json); -} -static void to_json(json& j, const Surface& surf) { - j[JSON_EXPOLYGON] = surf.expolygon; - j[JSON_SURF_TYPE] = surf.surface_type; - j[JSON_SURF_THICKNESS] = surf.thickness; - j[JSON_SURF_THICKNESS_LAYER] = surf.thickness_layers; - j[JSON_SURF_BRIDGE_ANGLE] = surf.bridge_angle; - j[JSON_SURF_EXTRA_PERIMETERS] = surf.extra_perimeters; -} + static void to_json(json &j, const Surface &surf) + { + j[JSON_EXPOLYGON] = surf.expolygon; + j[JSON_SURF_TYPE] = surf.surface_type; + j[JSON_SURF_THICKNESS] = surf.thickness; + j[JSON_SURF_THICKNESS_LAYER] = surf.thickness_layers; + j[JSON_SURF_BRIDGE_ANGLE] = surf.bridge_angle; + j[JSON_SURF_EXTRA_PERIMETERS] = surf.extra_perimeters; + } -static void to_json(json& j, const ArcSegment& arc_seg) { - json start_point_json = json::array(), end_point_json = json::array(), center_point_json = json::array(); - j[JSON_IS_ARC] = arc_seg.is_arc; - j[JSON_ARC_LENGTH] = arc_seg.length; - j[JSON_ARC_ANGLE_RADIUS] = arc_seg.angle_radians; - j[JSON_ARC_POLAY_START_THETA] = arc_seg.polar_start_theta; - j[JSON_ARC_POLAY_END_THETA] = arc_seg.polar_end_theta; - start_point_json.push_back(arc_seg.start_point.x()); - start_point_json.push_back(arc_seg.start_point.y()); - j[JSON_ARC_START_POINT] = std::move(start_point_json); - end_point_json.push_back(arc_seg.end_point.x()); - end_point_json.push_back(arc_seg.end_point.y()); - j[JSON_ARC_END_POINT] = std::move(end_point_json); - j[JSON_ARC_DIRECTION] = arc_seg.direction; - j[JSON_ARC_RADIUS] = arc_seg.radius; - center_point_json.push_back(arc_seg.center.x()); - center_point_json.push_back(arc_seg.center.y()); - j[JSON_ARC_CENTER] = std::move(center_point_json); -} + static void to_json(json &j, const ArcSegment &arc_seg) + { + json start_point_json = json::array(), end_point_json = json::array(), center_point_json = json::array(); + j[JSON_IS_ARC] = arc_seg.is_arc; + j[JSON_ARC_LENGTH] = arc_seg.length; + j[JSON_ARC_ANGLE_RADIUS] = arc_seg.angle_radians; + j[JSON_ARC_POLAY_START_THETA] = arc_seg.polar_start_theta; + j[JSON_ARC_POLAY_END_THETA] = arc_seg.polar_end_theta; + start_point_json.push_back(arc_seg.start_point.x()); + start_point_json.push_back(arc_seg.start_point.y()); + j[JSON_ARC_START_POINT] = std::move(start_point_json); + end_point_json.push_back(arc_seg.end_point.x()); + end_point_json.push_back(arc_seg.end_point.y()); + j[JSON_ARC_END_POINT] = std::move(end_point_json); + j[JSON_ARC_DIRECTION] = arc_seg.direction; + j[JSON_ARC_RADIUS] = arc_seg.radius; + center_point_json.push_back(arc_seg.center.x()); + center_point_json.push_back(arc_seg.center.y()); + j[JSON_ARC_CENTER] = std::move(center_point_json); + } + + static void to_json(json &j, const Polyline &poly_line) + { + json points_json = json::array(), fittings_json = json::array(); + points_json = poly_line.points; + j[JSON_POINTS] = std::move(points_json); + for (const PathFittingData &path_fitting : poly_line.fitting_result) + { + json fitting_json; + fitting_json[JSON_ARC_START_INDEX] = path_fitting.start_point_index; + fitting_json[JSON_ARC_END_INDEX] = path_fitting.end_point_index; + fitting_json[JSON_ARC_PATH_TYPE] = path_fitting.path_type; + if (path_fitting.arc_data.is_arc) + fitting_json[JSON_ARC_DATA] = path_fitting.arc_data; -static void to_json(json& j, const Polyline& poly_line) { - json points_json = json::array(), fittings_json = json::array(); - points_json = poly_line.points; + fittings_json.push_back(std::move(fitting_json)); + } + j[JSON_ARC_FITTING] = fittings_json; + } - j[JSON_POINTS] = std::move(points_json); - for (const PathFittingData& path_fitting : poly_line.fitting_result) + static void to_json(json &j, const ExtrusionPath &extrusion_path) { - json fitting_json; - fitting_json[JSON_ARC_START_INDEX] = path_fitting.start_point_index; - fitting_json[JSON_ARC_END_INDEX] = path_fitting.end_point_index; - fitting_json[JSON_ARC_PATH_TYPE] = path_fitting.path_type; - if (path_fitting.arc_data.is_arc) - fitting_json[JSON_ARC_DATA] = path_fitting.arc_data; - - fittings_json.push_back(std::move(fitting_json)); + j[JSON_EXTRUSION_POLYLINE] = extrusion_path.polyline; + j[JSON_EXTRUSION_OVERHANG_DEGREE] = extrusion_path.overhang_degree; + j[JSON_EXTRUSION_CURVE_DEGREE] = extrusion_path.curve_degree; + j[JSON_EXTRUSION_MM3_PER_MM] = extrusion_path.mm3_per_mm; + j[JSON_EXTRUSION_WIDTH] = extrusion_path.width; + j[JSON_EXTRUSION_HEIGHT] = extrusion_path.height; + j[JSON_EXTRUSION_ROLE] = extrusion_path.role(); + j[JSON_EXTRUSION_NO_EXTRUSION] = extrusion_path.is_force_no_extrusion(); } - j[JSON_ARC_FITTING] = fittings_json; -} - -static void to_json(json& j, const ExtrusionPath& extrusion_path) { - j[JSON_EXTRUSION_POLYLINE] = extrusion_path.polyline; - j[JSON_EXTRUSION_OVERHANG_DEGREE] = extrusion_path.overhang_degree; - j[JSON_EXTRUSION_CURVE_DEGREE] = extrusion_path.curve_degree; - j[JSON_EXTRUSION_MM3_PER_MM] = extrusion_path.mm3_per_mm; - j[JSON_EXTRUSION_WIDTH] = extrusion_path.width; - j[JSON_EXTRUSION_HEIGHT] = extrusion_path.height; - j[JSON_EXTRUSION_ROLE] = extrusion_path.role(); - j[JSON_EXTRUSION_NO_EXTRUSION] = extrusion_path.is_force_no_extrusion(); -} -static bool convert_extrusion_to_json(json& entity_json, json& entity_paths_json, const ExtrusionEntity* extrusion_entity) { - std::string path_type; - const ExtrusionPath* path = NULL; - const ExtrusionMultiPath* multipath = NULL; - const ExtrusionLoop* loop = NULL; - const ExtrusionEntityCollection* collection = dynamic_cast(extrusion_entity); + static bool convert_extrusion_to_json(json &entity_json, json &entity_paths_json, const ExtrusionEntity *extrusion_entity) + { + std::string path_type; + const ExtrusionPath *path = NULL; + const ExtrusionMultiPath *multipath = NULL; + const ExtrusionLoop *loop = NULL; + const ExtrusionEntityCollection *collection = dynamic_cast(extrusion_entity); - if (!collection) - path = dynamic_cast(extrusion_entity); + if (!collection) + path = dynamic_cast(extrusion_entity); - if (!collection && !path) - multipath = dynamic_cast(extrusion_entity); + if (!collection && !path) + multipath = dynamic_cast(extrusion_entity); - if (!collection && !path && !multipath) - loop = dynamic_cast(extrusion_entity); + if (!collection && !path && !multipath) + loop = dynamic_cast(extrusion_entity); - path_type = path?JSON_EXTRUSION_TYPE_PATH:(multipath?JSON_EXTRUSION_TYPE_MULTIPATH:(loop?JSON_EXTRUSION_TYPE_LOOP:JSON_EXTRUSION_TYPE_COLLECTION)); - if (path_type.empty()) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":invalid extrusion path type Found"); - return false; - } + path_type = path ? JSON_EXTRUSION_TYPE_PATH : (multipath ? JSON_EXTRUSION_TYPE_MULTIPATH : (loop ? JSON_EXTRUSION_TYPE_LOOP : JSON_EXTRUSION_TYPE_COLLECTION)); + if (path_type.empty()) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":invalid extrusion path type Found"); + return false; + } - entity_json[JSON_EXTRUSION_ENTITY_TYPE] = path_type; + entity_json[JSON_EXTRUSION_ENTITY_TYPE] = path_type; - if (path) { - json entity_path_json = *path; - entity_paths_json.push_back(std::move(entity_path_json)); - } - else if (multipath) { - for (const ExtrusionPath& extrusion_path : multipath->paths) + if (path) { - json entity_path_json = extrusion_path; + json entity_path_json = *path; entity_paths_json.push_back(std::move(entity_path_json)); } - } - else if (loop) { - entity_json[JSON_EXTRUSION_LOOP_ROLE] = loop->loop_role(); - for (const ExtrusionPath& extrusion_path : loop->paths) + else if (multipath) { - json entity_path_json = extrusion_path; - entity_paths_json.push_back(std::move(entity_path_json)); + for (const ExtrusionPath &extrusion_path : multipath->paths) + { + json entity_path_json = extrusion_path; + entity_paths_json.push_back(std::move(entity_path_json)); + } } - } - else { - //recursive collections - entity_json[JSON_EXTRUSION_NO_SORT] = collection->no_sort; - for (const ExtrusionEntity* recursive_extrusion_entity : collection->entities) { - json recursive_entity_json, recursive_entity_paths_json = json::array(); - bool ret = convert_extrusion_to_json(recursive_entity_json, recursive_entity_paths_json, recursive_extrusion_entity); - if (!ret) { - continue; + else if (loop) + { + entity_json[JSON_EXTRUSION_LOOP_ROLE] = loop->loop_role(); + for (const ExtrusionPath &extrusion_path : loop->paths) + { + json entity_path_json = extrusion_path; + entity_paths_json.push_back(std::move(entity_path_json)); + } + } + else + { + // recursive collections + entity_json[JSON_EXTRUSION_NO_SORT] = collection->no_sort; + for (const ExtrusionEntity *recursive_extrusion_entity : collection->entities) + { + json recursive_entity_json, recursive_entity_paths_json = json::array(); + bool ret = convert_extrusion_to_json(recursive_entity_json, recursive_entity_paths_json, recursive_extrusion_entity); + if (!ret) + { + continue; + } + entity_paths_json.push_back(std::move(recursive_entity_json)); } - entity_paths_json.push_back(std::move(recursive_entity_json)); } + + if (collection) + entity_json[JSON_EXTRUSION_ENTITIES] = std::move(entity_paths_json); + else + entity_json[JSON_EXTRUSION_PATHS] = std::move(entity_paths_json); + return true; } - if (collection) - entity_json[JSON_EXTRUSION_ENTITIES] = std::move(entity_paths_json); - else - entity_json[JSON_EXTRUSION_PATHS] = std::move(entity_paths_json); - return true; -} + static void to_json(json &j, const LayerRegion &layer_region) + { + json unsupported_bridge_edges_json = json::array(), slices_surfaces_json = json::array(), raw_slices_json = json::array(), thin_fills_json, thin_fill_entities_json = json::array(); + json fill_expolygons_json = json::array(), fill_no_overlap_expolygons_json = json::array(), fill_surfaces_json = json::array(), perimeters_json, perimeter_entities_json = json::array(), fills_json, fill_entities_json = json::array(); -static void to_json(json& j, const LayerRegion& layer_region) { - json unsupported_bridge_edges_json = json::array(), slices_surfaces_json = json::array(), raw_slices_json = json::array(), thin_fills_json, thin_fill_entities_json = json::array(); - json fill_expolygons_json = json::array(), fill_no_overlap_expolygons_json = json::array(), fill_surfaces_json = json::array(), perimeters_json, perimeter_entities_json = json::array(), fills_json, fill_entities_json = json::array(); + j[JSON_LAYER_REGION_CONFIG_HASH] = layer_region.region().config_hash(); + // slices + for (const Surface &slice_surface : layer_region.slices.surfaces) + { + json surface_json = slice_surface; + slices_surfaces_json.push_back(std::move(surface_json)); + } + j.push_back({JSON_LAYER_REGION_SLICES, std::move(slices_surfaces_json)}); - j[JSON_LAYER_REGION_CONFIG_HASH] = layer_region.region().config_hash(); - //slices - for (const Surface& slice_surface : layer_region.slices.surfaces) { - json surface_json = slice_surface; - slices_surfaces_json.push_back(std::move(surface_json)); - } - j.push_back({JSON_LAYER_REGION_SLICES, std::move(slices_surfaces_json)}); + // raw_slices + for (const ExPolygon &raw_slice_explogyon : layer_region.raw_slices) + { + json raw_polygon_json = raw_slice_explogyon; - //raw_slices - for (const ExPolygon& raw_slice_explogyon : layer_region.raw_slices) { - json raw_polygon_json = raw_slice_explogyon; + raw_slices_json.push_back(std::move(raw_polygon_json)); + } + j.push_back({JSON_LAYER_REGION_RAW_SLICES, std::move(raw_slices_json)}); - raw_slices_json.push_back(std::move(raw_polygon_json)); - } - j.push_back({JSON_LAYER_REGION_RAW_SLICES, std::move(raw_slices_json)}); + // thin fills + thin_fills_json[JSON_EXTRUSION_NO_SORT] = layer_region.thin_fills.no_sort; + thin_fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; + for (const ExtrusionEntity *extrusion_entity : layer_region.thin_fills.entities) + { + json thinfills_entity_json, thinfill_entity_paths_json = json::array(); + bool ret = convert_extrusion_to_json(thinfills_entity_json, thinfill_entity_paths_json, extrusion_entity); + if (!ret) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":error found at print_z %1%") % layer_region.layer()->print_z; + continue; + } - //thin fills - thin_fills_json[JSON_EXTRUSION_NO_SORT] = layer_region.thin_fills.no_sort; - thin_fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; - for (const ExtrusionEntity* extrusion_entity : layer_region.thin_fills.entities) { - json thinfills_entity_json, thinfill_entity_paths_json = json::array(); - bool ret = convert_extrusion_to_json(thinfills_entity_json, thinfill_entity_paths_json, extrusion_entity); - if (!ret) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":error found at print_z %1%") % layer_region.layer()->print_z; - continue; + thin_fill_entities_json.push_back(std::move(thinfills_entity_json)); } + thin_fills_json[JSON_EXTRUSION_ENTITIES] = std::move(thin_fill_entities_json); + j.push_back({JSON_LAYER_REGION_THIN_FILLS, std::move(thin_fills_json)}); - thin_fill_entities_json.push_back(std::move(thinfills_entity_json)); - } - thin_fills_json[JSON_EXTRUSION_ENTITIES] = std::move(thin_fill_entities_json); - j.push_back({JSON_LAYER_REGION_THIN_FILLS, std::move(thin_fills_json)}); + // fill_expolygons + for (const ExPolygon &fill_expolygon : layer_region.fill_expolygons) + { + json fill_expolygon_json = fill_expolygon; - //fill_expolygons - for (const ExPolygon& fill_expolygon : layer_region.fill_expolygons) { - json fill_expolygon_json = fill_expolygon; + fill_expolygons_json.push_back(std::move(fill_expolygon_json)); + } + j.push_back({JSON_LAYER_REGION_FILL_EXPOLYGONS, std::move(fill_expolygons_json)}); - fill_expolygons_json.push_back(std::move(fill_expolygon_json)); - } - j.push_back({JSON_LAYER_REGION_FILL_EXPOLYGONS, std::move(fill_expolygons_json)}); + // fill_surfaces + for (const Surface &fill_surface : layer_region.fill_surfaces.surfaces) + { + json surface_json = fill_surface; + fill_surfaces_json.push_back(std::move(surface_json)); + } + j.push_back({JSON_LAYER_REGION_FILL_SURFACES, std::move(fill_surfaces_json)}); - //fill_surfaces - for (const Surface& fill_surface : layer_region.fill_surfaces.surfaces) { - json surface_json = fill_surface; - fill_surfaces_json.push_back(std::move(surface_json)); - } - j.push_back({JSON_LAYER_REGION_FILL_SURFACES, std::move(fill_surfaces_json)}); + // fill_no_overlap_expolygons + for (const ExPolygon &fill_no_overlap_expolygon : layer_region.fill_no_overlap_expolygons) + { + json fill_no_overlap_expolygon_json = fill_no_overlap_expolygon; + + fill_no_overlap_expolygons_json.push_back(std::move(fill_no_overlap_expolygon_json)); + } + j.push_back({JSON_LAYER_REGION_FILL_NO_OVERLAP, std::move(fill_no_overlap_expolygons_json)}); + + // unsupported_bridge_edges + for (const Polyline &poly_line : layer_region.unsupported_bridge_edges) + { + json polyline_json = poly_line; + + unsupported_bridge_edges_json.push_back(std::move(polyline_json)); + } + j.push_back({JSON_LAYER_REGION_UNSUPPORTED_BRIDGE_EDGES, std::move(unsupported_bridge_edges_json)}); + + // perimeters + perimeters_json[JSON_EXTRUSION_NO_SORT] = layer_region.perimeters.no_sort; + perimeters_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; + for (const ExtrusionEntity *extrusion_entity : layer_region.perimeters.entities) + { + json perimeters_entity_json, perimeters_entity_paths_json = json::array(); + bool ret = convert_extrusion_to_json(perimeters_entity_json, perimeters_entity_paths_json, extrusion_entity); + if (!ret) + continue; + + perimeter_entities_json.push_back(std::move(perimeters_entity_json)); + } + perimeters_json[JSON_EXTRUSION_ENTITIES] = std::move(perimeter_entities_json); + j.push_back({JSON_LAYER_REGION_PERIMETERS, std::move(perimeters_json)}); + + // fills + fills_json[JSON_EXTRUSION_NO_SORT] = layer_region.fills.no_sort; + fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; + for (const ExtrusionEntity *extrusion_entity : layer_region.fills.entities) + { + json fill_entity_json, fill_entity_paths_json = json::array(); + bool ret = convert_extrusion_to_json(fill_entity_json, fill_entity_paths_json, extrusion_entity); + if (!ret) + continue; - //fill_no_overlap_expolygons - for (const ExPolygon& fill_no_overlap_expolygon : layer_region.fill_no_overlap_expolygons) { - json fill_no_overlap_expolygon_json = fill_no_overlap_expolygon; + fill_entities_json.push_back(std::move(fill_entity_json)); + } + fills_json[JSON_EXTRUSION_ENTITIES] = std::move(fill_entities_json); + j.push_back({JSON_LAYER_REGION_FILLS, std::move(fills_json)}); - fill_no_overlap_expolygons_json.push_back(std::move(fill_no_overlap_expolygon_json)); + return; } - j.push_back({JSON_LAYER_REGION_FILL_NO_OVERLAP, std::move(fill_no_overlap_expolygons_json)}); - //unsupported_bridge_edges - for (const Polyline& poly_line : layer_region.unsupported_bridge_edges) + static void to_json(json &j, const groupedVolumeSlices &first_layer_group) { - json polyline_json = poly_line; + json volumes_json = json::array(), slices_json = json::array(); + j[JSON_FIRSTLAYER_GROUP_ID] = first_layer_group.groupId; - unsupported_bridge_edges_json.push_back(std::move(polyline_json)); - } - j.push_back({JSON_LAYER_REGION_UNSUPPORTED_BRIDGE_EDGES, std::move(unsupported_bridge_edges_json)}); - - //perimeters - perimeters_json[JSON_EXTRUSION_NO_SORT] = layer_region.perimeters.no_sort; - perimeters_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; - for (const ExtrusionEntity* extrusion_entity : layer_region.perimeters.entities) { - json perimeters_entity_json, perimeters_entity_paths_json = json::array(); - bool ret = convert_extrusion_to_json(perimeters_entity_json, perimeters_entity_paths_json, extrusion_entity); - if (!ret) - continue; - - perimeter_entities_json.push_back(std::move(perimeters_entity_json)); - } - perimeters_json[JSON_EXTRUSION_ENTITIES] = std::move(perimeter_entities_json); - j.push_back({JSON_LAYER_REGION_PERIMETERS, std::move(perimeters_json)}); - - //fills - fills_json[JSON_EXTRUSION_NO_SORT] = layer_region.fills.no_sort; - fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; - for (const ExtrusionEntity* extrusion_entity : layer_region.fills.entities) { - json fill_entity_json, fill_entity_paths_json = json::array(); - bool ret = convert_extrusion_to_json(fill_entity_json, fill_entity_paths_json, extrusion_entity); - if (!ret) - continue; - - fill_entities_json.push_back(std::move(fill_entity_json)); - } - fills_json[JSON_EXTRUSION_ENTITIES] = std::move(fill_entities_json); - j.push_back({JSON_LAYER_REGION_FILLS, std::move(fills_json)}); + for (const ObjectID &obj_id : first_layer_group.volume_ids) + { + volumes_json.push_back(obj_id.id); + } + j[JSON_FIRSTLAYER_GROUP_VOLUME_IDS] = std::move(volumes_json); - return; -} + for (const ExPolygon &slice_expolygon : first_layer_group.slices) + { + json slice_expolygon_json = slice_expolygon; -static void to_json(json& j, const groupedVolumeSlices& first_layer_group) { - json volumes_json = json::array(), slices_json = json::array(); - j[JSON_FIRSTLAYER_GROUP_ID] = first_layer_group.groupId; + slices_json.push_back(std::move(slice_expolygon_json)); + } + j[JSON_FIRSTLAYER_GROUP_SLICES] = std::move(slices_json); + } - for (const ObjectID& obj_id : first_layer_group.volume_ids) + // load apis from json + static void from_json(const json &j, Points &p_s) { - volumes_json.push_back(obj_id.id); + int array_size = j.size(); + for (int index = 0; index < array_size / 2; index++) + { + coord_t x = j[2 * index], y = j[2 * index + 1]; + Point p(x, y); + p_s.push_back(std::move(p)); + } + return; } - j[JSON_FIRSTLAYER_GROUP_VOLUME_IDS] = std::move(volumes_json); - for (const ExPolygon& slice_expolygon : first_layer_group.slices) { - json slice_expolygon_json = slice_expolygon; + static void from_json(const json &j, BoundingBox &bbox) + { + bbox.min[0] = j[0]; + bbox.min[1] = j[1]; + bbox.max[0] = j[2]; + bbox.max[1] = j[3]; + bbox.defined = true; - slices_json.push_back(std::move(slice_expolygon_json)); + return; } - j[JSON_FIRSTLAYER_GROUP_SLICES] = std::move(slices_json); -} -//load apis from json -static void from_json(const json& j, Points& p_s) { - int array_size = j.size(); - for (int index = 0; index < array_size/2; index++) + static void from_json(const json &j, ExPolygon &polygon) { - coord_t x = j[2*index], y = j[2*index+1]; - Point p(x, y); - p_s.push_back(std::move(p)); - } - return; -} + polygon.contour.points = j[JSON_POLYGON_CONTOUR]; -static void from_json(const json& j, BoundingBox& bbox) { - bbox.min[0] = j[0]; - bbox.min[1] = j[1]; - bbox.max[0] = j[2]; - bbox.max[1] = j[3]; - bbox.defined = true; - - return; -} + int holes_count = j[JSON_POLYGON_HOLES].size(); + for (int holes_index = 0; holes_index < holes_count; holes_index++) + { + Polygon poly; -static void from_json(const json& j, ExPolygon& polygon) { - polygon.contour.points = j[JSON_POLYGON_CONTOUR]; + poly.points = j[JSON_POLYGON_HOLES][holes_index]; + polygon.holes.push_back(std::move(poly)); + } + return; + } - int holes_count = j[JSON_POLYGON_HOLES].size(); - for (int holes_index = 0; holes_index < holes_count; holes_index++) + static void from_json(const json &j, Surface &surf) { - Polygon poly; + surf.expolygon = j[JSON_EXPOLYGON]; + surf.surface_type = j[JSON_SURF_TYPE]; + surf.thickness = j[JSON_SURF_THICKNESS]; + surf.thickness_layers = j[JSON_SURF_THICKNESS_LAYER]; + surf.bridge_angle = j[JSON_SURF_BRIDGE_ANGLE]; + surf.extra_perimeters = j[JSON_SURF_EXTRA_PERIMETERS]; - poly.points = j[JSON_POLYGON_HOLES][holes_index]; - polygon.holes.push_back(std::move(poly)); + return; } - return; -} -static void from_json(const json& j, Surface& surf) { - surf.expolygon = j[JSON_EXPOLYGON]; - surf.surface_type = j[JSON_SURF_TYPE]; - surf.thickness = j[JSON_SURF_THICKNESS]; - surf.thickness_layers = j[JSON_SURF_THICKNESS_LAYER]; - surf.bridge_angle = j[JSON_SURF_BRIDGE_ANGLE]; - surf.extra_perimeters = j[JSON_SURF_EXTRA_PERIMETERS]; - - return; -} + static void from_json(const json &j, ArcSegment &arc_seg) + { + arc_seg.is_arc = j[JSON_IS_ARC]; + arc_seg.length = j[JSON_ARC_LENGTH]; + arc_seg.angle_radians = j[JSON_ARC_ANGLE_RADIUS]; + arc_seg.polar_start_theta = j[JSON_ARC_POLAY_START_THETA]; + arc_seg.polar_end_theta = j[JSON_ARC_POLAY_END_THETA]; + arc_seg.start_point.x() = j[JSON_ARC_START_POINT][0]; + arc_seg.start_point.y() = j[JSON_ARC_START_POINT][1]; + arc_seg.end_point.x() = j[JSON_ARC_END_POINT][0]; + arc_seg.end_point.y() = j[JSON_ARC_END_POINT][1]; + arc_seg.direction = j[JSON_ARC_DIRECTION]; + arc_seg.radius = j[JSON_ARC_RADIUS]; + arc_seg.center.x() = j[JSON_ARC_CENTER][0]; + arc_seg.center.y() = j[JSON_ARC_CENTER][1]; -static void from_json(const json& j, ArcSegment& arc_seg) { - arc_seg.is_arc = j[JSON_IS_ARC]; - arc_seg.length = j[JSON_ARC_LENGTH]; - arc_seg.angle_radians = j[JSON_ARC_ANGLE_RADIUS]; - arc_seg.polar_start_theta = j[JSON_ARC_POLAY_START_THETA]; - arc_seg.polar_end_theta = j[JSON_ARC_POLAY_END_THETA]; - arc_seg.start_point.x() = j[JSON_ARC_START_POINT][0]; - arc_seg.start_point.y() = j[JSON_ARC_START_POINT][1]; - arc_seg.end_point.x() = j[JSON_ARC_END_POINT][0]; - arc_seg.end_point.y() = j[JSON_ARC_END_POINT][1]; - arc_seg.direction = j[JSON_ARC_DIRECTION]; - arc_seg.radius = j[JSON_ARC_RADIUS]; - arc_seg.center.x() = j[JSON_ARC_CENTER][0]; - arc_seg.center.y() = j[JSON_ARC_CENTER][1]; - - return; -} + return; + } + static void from_json(const json &j, Polyline &poly_line) + { + poly_line.points = j[JSON_POINTS]; -static void from_json(const json& j, Polyline& poly_line) { - poly_line.points = j[JSON_POINTS]; + int arc_fitting_count = j[JSON_ARC_FITTING].size(); + for (int arc_fitting_index = 0; arc_fitting_index < arc_fitting_count; arc_fitting_index++) + { + const json &fitting_json = j[JSON_ARC_FITTING][arc_fitting_index]; + PathFittingData path_fitting; + path_fitting.start_point_index = fitting_json[JSON_ARC_START_INDEX]; + path_fitting.end_point_index = fitting_json[JSON_ARC_END_INDEX]; + path_fitting.path_type = fitting_json[JSON_ARC_PATH_TYPE]; - int arc_fitting_count = j[JSON_ARC_FITTING].size(); - for (int arc_fitting_index = 0; arc_fitting_index < arc_fitting_count; arc_fitting_index++) - { - const json& fitting_json = j[JSON_ARC_FITTING][arc_fitting_index]; - PathFittingData path_fitting; - path_fitting.start_point_index = fitting_json[JSON_ARC_START_INDEX]; - path_fitting.end_point_index = fitting_json[JSON_ARC_END_INDEX]; - path_fitting.path_type = fitting_json[JSON_ARC_PATH_TYPE]; + if (fitting_json.contains(JSON_ARC_DATA)) + { + path_fitting.arc_data = fitting_json[JSON_ARC_DATA]; + } - if (fitting_json.contains(JSON_ARC_DATA)) { - path_fitting.arc_data = fitting_json[JSON_ARC_DATA]; + poly_line.fitting_result.push_back(std::move(path_fitting)); } - - poly_line.fitting_result.push_back(std::move(path_fitting)); + return; } - return; -} -static void from_json(const json& j, ExtrusionPath& extrusion_path) { - extrusion_path.polyline = j[JSON_EXTRUSION_POLYLINE]; - extrusion_path.overhang_degree = j[JSON_EXTRUSION_OVERHANG_DEGREE]; - extrusion_path.curve_degree = j[JSON_EXTRUSION_CURVE_DEGREE]; - extrusion_path.mm3_per_mm = j[JSON_EXTRUSION_MM3_PER_MM]; - extrusion_path.width = j[JSON_EXTRUSION_WIDTH]; - extrusion_path.height = j[JSON_EXTRUSION_HEIGHT]; - extrusion_path.set_extrusion_role(j[JSON_EXTRUSION_ROLE]); - extrusion_path.set_force_no_extrusion(j[JSON_EXTRUSION_NO_EXTRUSION]); -} + static void from_json(const json &j, ExtrusionPath &extrusion_path) + { + extrusion_path.polyline = j[JSON_EXTRUSION_POLYLINE]; + extrusion_path.overhang_degree = j[JSON_EXTRUSION_OVERHANG_DEGREE]; + extrusion_path.curve_degree = j[JSON_EXTRUSION_CURVE_DEGREE]; + extrusion_path.mm3_per_mm = j[JSON_EXTRUSION_MM3_PER_MM]; + extrusion_path.width = j[JSON_EXTRUSION_WIDTH]; + extrusion_path.height = j[JSON_EXTRUSION_HEIGHT]; + extrusion_path.set_extrusion_role(j[JSON_EXTRUSION_ROLE]); + extrusion_path.set_force_no_extrusion(j[JSON_EXTRUSION_NO_EXTRUSION]); + } -static bool convert_extrusion_from_json(const json& entity_json, ExtrusionEntityCollection& entity_collection) { - std::string path_type = entity_json[JSON_EXTRUSION_ENTITY_TYPE]; - bool ret = false; + static bool convert_extrusion_from_json(const json &entity_json, ExtrusionEntityCollection &entity_collection) + { + std::string path_type = entity_json[JSON_EXTRUSION_ENTITY_TYPE]; + bool ret = false; - if (path_type == JSON_EXTRUSION_TYPE_PATH) { - ExtrusionPath* path = new ExtrusionPath(); - if (!path) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": oom when new ExtrusionPath"); - return false; - } - *path = entity_json[JSON_EXTRUSION_PATHS][0]; - entity_collection.entities.push_back(path); - } - else if (path_type == JSON_EXTRUSION_TYPE_MULTIPATH) { - ExtrusionMultiPath* multipath = new ExtrusionMultiPath(); - if (!multipath) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": oom when new ExtrusionMultiPath"); - return false; - } - int paths_count = entity_json[JSON_EXTRUSION_PATHS].size(); - for (int path_index = 0; path_index < paths_count; path_index++) + if (path_type == JSON_EXTRUSION_TYPE_PATH) { - ExtrusionPath path; - path = entity_json[JSON_EXTRUSION_PATHS][path_index]; - multipath->paths.push_back(std::move(path)); - } - entity_collection.entities.push_back(multipath); - } - else if (path_type == JSON_EXTRUSION_TYPE_LOOP) { - ExtrusionLoop* loop = new ExtrusionLoop(); - if (!loop) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": oom when new ExtrusionLoop"); - return false; + ExtrusionPath *path = new ExtrusionPath(); + if (!path) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": oom when new ExtrusionPath"); + return false; + } + *path = entity_json[JSON_EXTRUSION_PATHS][0]; + entity_collection.entities.push_back(path); } - loop->set_loop_role(entity_json[JSON_EXTRUSION_LOOP_ROLE]); - int paths_count = entity_json[JSON_EXTRUSION_PATHS].size(); - for (int path_index = 0; path_index < paths_count; path_index++) + else if (path_type == JSON_EXTRUSION_TYPE_MULTIPATH) { - ExtrusionPath path; - path = entity_json[JSON_EXTRUSION_PATHS][path_index]; - loop->paths.push_back(std::move(path)); - } - entity_collection.entities.push_back(loop); - } - else if (path_type == JSON_EXTRUSION_TYPE_COLLECTION) { - ExtrusionEntityCollection* collection = new ExtrusionEntityCollection(); - if (!collection) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": oom when new ExtrusionEntityCollection"); - return false; + ExtrusionMultiPath *multipath = new ExtrusionMultiPath(); + if (!multipath) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": oom when new ExtrusionMultiPath"); + return false; + } + int paths_count = entity_json[JSON_EXTRUSION_PATHS].size(); + for (int path_index = 0; path_index < paths_count; path_index++) + { + ExtrusionPath path; + path = entity_json[JSON_EXTRUSION_PATHS][path_index]; + multipath->paths.push_back(std::move(path)); + } + entity_collection.entities.push_back(multipath); } - collection->no_sort = entity_json[JSON_EXTRUSION_NO_SORT]; - int entities_count = entity_json[JSON_EXTRUSION_ENTITIES].size(); - for (int entity_index = 0; entity_index < entities_count; entity_index++) + else if (path_type == JSON_EXTRUSION_TYPE_LOOP) { - const json& entity_item_json = entity_json[JSON_EXTRUSION_ENTITIES][entity_index]; - ret = convert_extrusion_from_json(entity_item_json, *collection); - if (!ret) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": convert_extrusion_from_json failed"); + ExtrusionLoop *loop = new ExtrusionLoop(); + if (!loop) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": oom when new ExtrusionLoop"); return false; } + loop->set_loop_role(entity_json[JSON_EXTRUSION_LOOP_ROLE]); + int paths_count = entity_json[JSON_EXTRUSION_PATHS].size(); + for (int path_index = 0; path_index < paths_count; path_index++) + { + ExtrusionPath path; + path = entity_json[JSON_EXTRUSION_PATHS][path_index]; + loop->paths.push_back(std::move(path)); + } + entity_collection.entities.push_back(loop); } - entity_collection.entities.push_back(collection); - } - else { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": unknown path type %1%")%path_type; - return false; - } - - return true; -} - -static void convert_layer_region_from_json(const json& j, LayerRegion& layer_region) { - //slices - int slices_count = j[JSON_LAYER_REGION_SLICES].size(); - for (int slices_index = 0; slices_index < slices_count; slices_index++) - { - Surface surface; - - surface = j[JSON_LAYER_REGION_SLICES][slices_index]; - layer_region.slices.surfaces.push_back(std::move(surface)); - } - - //raw_slices - int raw_slices_count = j[JSON_LAYER_REGION_RAW_SLICES].size(); - for (int raw_slices_index = 0; raw_slices_index < raw_slices_count; raw_slices_index++) - { - ExPolygon polygon; - - polygon = j[JSON_LAYER_REGION_RAW_SLICES][raw_slices_index]; - layer_region.raw_slices.push_back(std::move(polygon)); - } - - //thin fills - layer_region.thin_fills.no_sort = j[JSON_LAYER_REGION_THIN_FILLS][JSON_EXTRUSION_NO_SORT]; - int thinfills_entities_count = j[JSON_LAYER_REGION_THIN_FILLS][JSON_EXTRUSION_ENTITIES].size(); - for (int thinfills_entities_index = 0; thinfills_entities_index < thinfills_entities_count; thinfills_entities_index++) - { - const json& extrusion_entity_json = j[JSON_LAYER_REGION_THIN_FILLS][JSON_EXTRUSION_ENTITIES][thinfills_entities_index]; - bool ret = convert_extrusion_from_json(extrusion_entity_json, layer_region.thin_fills); - if (!ret) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":error parsing thin_fills found at layer %1%, print_z %2%") %layer_region.layer()->id() %layer_region.layer()->print_z; - char error_buf[1024]; - ::sprintf(error_buf, "Error while parsing thin_fills at layer %zu, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); - throw Slic3r::FileIOError(error_buf); + else if (path_type == JSON_EXTRUSION_TYPE_COLLECTION) + { + ExtrusionEntityCollection *collection = new ExtrusionEntityCollection(); + if (!collection) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": oom when new ExtrusionEntityCollection"); + return false; + } + collection->no_sort = entity_json[JSON_EXTRUSION_NO_SORT]; + int entities_count = entity_json[JSON_EXTRUSION_ENTITIES].size(); + for (int entity_index = 0; entity_index < entities_count; entity_index++) + { + const json &entity_item_json = entity_json[JSON_EXTRUSION_ENTITIES][entity_index]; + ret = convert_extrusion_from_json(entity_item_json, *collection); + if (!ret) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": convert_extrusion_from_json failed"); + return false; + } + } + entity_collection.entities.push_back(collection); + } + else + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": unknown path type %1%") % path_type; + return false; } - } - - //fill_expolygons - int fill_expolygons_count = j[JSON_LAYER_REGION_FILL_EXPOLYGONS].size(); - for (int fill_expolygons_index = 0; fill_expolygons_index < fill_expolygons_count; fill_expolygons_index++) - { - ExPolygon polygon; - polygon = j[JSON_LAYER_REGION_FILL_EXPOLYGONS][fill_expolygons_index]; - layer_region.fill_expolygons.push_back(std::move(polygon)); + return true; } - //fill_surfaces - int fill_surfaces_count = j[JSON_LAYER_REGION_FILL_SURFACES].size(); - for (int fill_surfaces_index = 0; fill_surfaces_index < fill_surfaces_count; fill_surfaces_index++) + static void convert_layer_region_from_json(const json &j, LayerRegion &layer_region) { - Surface surface; + // slices + int slices_count = j[JSON_LAYER_REGION_SLICES].size(); + for (int slices_index = 0; slices_index < slices_count; slices_index++) + { + Surface surface; - surface = j[JSON_LAYER_REGION_FILL_SURFACES][fill_surfaces_index]; - layer_region.fill_surfaces.surfaces.push_back(std::move(surface)); - } + surface = j[JSON_LAYER_REGION_SLICES][slices_index]; + layer_region.slices.surfaces.push_back(std::move(surface)); + } - //fill_no_overlap_expolygons - int fill_no_overlap_expolygons_count = j[JSON_LAYER_REGION_FILL_NO_OVERLAP].size(); - for (int fill_no_overlap_expolygons_index = 0; fill_no_overlap_expolygons_index < fill_no_overlap_expolygons_count; fill_no_overlap_expolygons_index++) - { - ExPolygon polygon; + // raw_slices + int raw_slices_count = j[JSON_LAYER_REGION_RAW_SLICES].size(); + for (int raw_slices_index = 0; raw_slices_index < raw_slices_count; raw_slices_index++) + { + ExPolygon polygon; - polygon = j[JSON_LAYER_REGION_FILL_NO_OVERLAP][fill_no_overlap_expolygons_index]; - layer_region.fill_no_overlap_expolygons.push_back(std::move(polygon)); - } + polygon = j[JSON_LAYER_REGION_RAW_SLICES][raw_slices_index]; + layer_region.raw_slices.push_back(std::move(polygon)); + } - //unsupported_bridge_edges - int unsupported_bridge_edges_count = j[JSON_LAYER_REGION_UNSUPPORTED_BRIDGE_EDGES].size(); - for (int unsupported_bridge_edges_index = 0; unsupported_bridge_edges_index < unsupported_bridge_edges_count; unsupported_bridge_edges_index++) - { - Polyline polyline; + // thin fills + layer_region.thin_fills.no_sort = j[JSON_LAYER_REGION_THIN_FILLS][JSON_EXTRUSION_NO_SORT]; + int thinfills_entities_count = j[JSON_LAYER_REGION_THIN_FILLS][JSON_EXTRUSION_ENTITIES].size(); + for (int thinfills_entities_index = 0; thinfills_entities_index < thinfills_entities_count; thinfills_entities_index++) + { + const json &extrusion_entity_json = j[JSON_LAYER_REGION_THIN_FILLS][JSON_EXTRUSION_ENTITIES][thinfills_entities_index]; + bool ret = convert_extrusion_from_json(extrusion_entity_json, layer_region.thin_fills); + if (!ret) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":error parsing thin_fills found at layer %1%, print_z %2%") % layer_region.layer()->id() % layer_region.layer()->print_z; + char error_buf[1024]; + ::sprintf(error_buf, "Error while parsing thin_fills at layer %zu, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); + throw Slic3r::FileIOError(error_buf); + } + } - polyline = j[JSON_LAYER_REGION_UNSUPPORTED_BRIDGE_EDGES][unsupported_bridge_edges_index]; - layer_region.unsupported_bridge_edges.push_back(std::move(polyline)); - } + // fill_expolygons + int fill_expolygons_count = j[JSON_LAYER_REGION_FILL_EXPOLYGONS].size(); + for (int fill_expolygons_index = 0; fill_expolygons_index < fill_expolygons_count; fill_expolygons_index++) + { + ExPolygon polygon; - //perimeters - layer_region.perimeters.no_sort = j[JSON_LAYER_REGION_PERIMETERS][JSON_EXTRUSION_NO_SORT]; - int perimeters_entities_count = j[JSON_LAYER_REGION_PERIMETERS][JSON_EXTRUSION_ENTITIES].size(); - for (int perimeters_entities_index = 0; perimeters_entities_index < perimeters_entities_count; perimeters_entities_index++) - { - const json& extrusion_entity_json = j[JSON_LAYER_REGION_PERIMETERS][JSON_EXTRUSION_ENTITIES][perimeters_entities_index]; - bool ret = convert_extrusion_from_json(extrusion_entity_json, layer_region.perimeters); - if (!ret) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing perimeters found at layer %1%, print_z %2%") %layer_region.layer()->id() %layer_region.layer()->print_z; - char error_buf[1024]; - ::sprintf(error_buf, "Error while parsing perimeters at layer %zu, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); - throw Slic3r::FileIOError(error_buf); + polygon = j[JSON_LAYER_REGION_FILL_EXPOLYGONS][fill_expolygons_index]; + layer_region.fill_expolygons.push_back(std::move(polygon)); } - } - //fills - layer_region.fills.no_sort = j[JSON_LAYER_REGION_FILLS][JSON_EXTRUSION_NO_SORT]; - int fills_entities_count = j[JSON_LAYER_REGION_FILLS][JSON_EXTRUSION_ENTITIES].size(); - for (int fills_entities_index = 0; fills_entities_index < fills_entities_count; fills_entities_index++) - { - const json& extrusion_entity_json = j[JSON_LAYER_REGION_FILLS][JSON_EXTRUSION_ENTITIES][fills_entities_index]; - bool ret = convert_extrusion_from_json(extrusion_entity_json, layer_region.fills); - if (!ret) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing fills found at layer %1%, print_z %2%") %layer_region.layer()->id() %layer_region.layer()->print_z; - char error_buf[1024]; - ::sprintf(error_buf, "Error while parsing fills at layer %zu, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); - throw Slic3r::FileIOError(error_buf); - } - } + // fill_surfaces + int fill_surfaces_count = j[JSON_LAYER_REGION_FILL_SURFACES].size(); + for (int fill_surfaces_index = 0; fill_surfaces_index < fill_surfaces_count; fill_surfaces_index++) + { + Surface surface; - return; -} + surface = j[JSON_LAYER_REGION_FILL_SURFACES][fill_surfaces_index]; + layer_region.fill_surfaces.surfaces.push_back(std::move(surface)); + } + // fill_no_overlap_expolygons + int fill_no_overlap_expolygons_count = j[JSON_LAYER_REGION_FILL_NO_OVERLAP].size(); + for (int fill_no_overlap_expolygons_index = 0; fill_no_overlap_expolygons_index < fill_no_overlap_expolygons_count; fill_no_overlap_expolygons_index++) + { + ExPolygon polygon; -void extract_layer(const json& layer_json, Layer& layer) { - //slice_polygons - int slice_polygons_count = layer_json[JSON_LAYER_SLICED_POLYGONS].size(); - for (int polygon_index = 0; polygon_index < slice_polygons_count; polygon_index++) - { - ExPolygon polygon; + polygon = j[JSON_LAYER_REGION_FILL_NO_OVERLAP][fill_no_overlap_expolygons_index]; + layer_region.fill_no_overlap_expolygons.push_back(std::move(polygon)); + } - polygon = layer_json[JSON_LAYER_SLICED_POLYGONS][polygon_index]; - layer.lslices.push_back(std::move(polygon)); - } + // unsupported_bridge_edges + int unsupported_bridge_edges_count = j[JSON_LAYER_REGION_UNSUPPORTED_BRIDGE_EDGES].size(); + for (int unsupported_bridge_edges_index = 0; unsupported_bridge_edges_index < unsupported_bridge_edges_count; unsupported_bridge_edges_index++) + { + Polyline polyline; - //slice_bboxes - int sliced_bboxes_count = layer_json[JSON_LAYER_SLLICED_BBOXES].size(); - for (int bbox_index = 0; bbox_index < sliced_bboxes_count; bbox_index++) - { - BoundingBox bbox; + polyline = j[JSON_LAYER_REGION_UNSUPPORTED_BRIDGE_EDGES][unsupported_bridge_edges_index]; + layer_region.unsupported_bridge_edges.push_back(std::move(polyline)); + } - bbox = layer_json[JSON_LAYER_SLLICED_BBOXES][bbox_index]; - layer.lslices_bboxes.push_back(std::move(bbox)); - } + // perimeters + layer_region.perimeters.no_sort = j[JSON_LAYER_REGION_PERIMETERS][JSON_EXTRUSION_NO_SORT]; + int perimeters_entities_count = j[JSON_LAYER_REGION_PERIMETERS][JSON_EXTRUSION_ENTITIES].size(); + for (int perimeters_entities_index = 0; perimeters_entities_index < perimeters_entities_count; perimeters_entities_index++) + { + const json &extrusion_entity_json = j[JSON_LAYER_REGION_PERIMETERS][JSON_EXTRUSION_ENTITIES][perimeters_entities_index]; + bool ret = convert_extrusion_from_json(extrusion_entity_json, layer_region.perimeters); + if (!ret) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing perimeters found at layer %1%, print_z %2%") % layer_region.layer()->id() % layer_region.layer()->print_z; + char error_buf[1024]; + ::sprintf(error_buf, "Error while parsing perimeters at layer %zu, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); + throw Slic3r::FileIOError(error_buf); + } + } - //overhang_polygons - int overhang_polygons_count = layer_json[JSON_LAYER_OVERHANG_POLYGONS].size(); - for (int polygon_index = 0; polygon_index < overhang_polygons_count; polygon_index++) - { - ExPolygon polygon; + // fills + layer_region.fills.no_sort = j[JSON_LAYER_REGION_FILLS][JSON_EXTRUSION_NO_SORT]; + int fills_entities_count = j[JSON_LAYER_REGION_FILLS][JSON_EXTRUSION_ENTITIES].size(); + for (int fills_entities_index = 0; fills_entities_index < fills_entities_count; fills_entities_index++) + { + const json &extrusion_entity_json = j[JSON_LAYER_REGION_FILLS][JSON_EXTRUSION_ENTITIES][fills_entities_index]; + bool ret = convert_extrusion_from_json(extrusion_entity_json, layer_region.fills); + if (!ret) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing fills found at layer %1%, print_z %2%") % layer_region.layer()->id() % layer_region.layer()->print_z; + char error_buf[1024]; + ::sprintf(error_buf, "Error while parsing fills at layer %zu, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); + throw Slic3r::FileIOError(error_buf); + } + } - polygon = layer_json[JSON_LAYER_OVERHANG_POLYGONS][polygon_index]; - layer.loverhangs.push_back(std::move(polygon)); + return; } - //overhang_box - layer.loverhangs_bbox = layer_json[JSON_LAYER_OVERHANG_BBOX]; - - //layer_regions - int layer_region_count = layer.region_count(); - for (int layer_region_index = 0; layer_region_index < layer_region_count; layer_region_index++) + void extract_layer(const json &layer_json, Layer &layer) { - LayerRegion* layer_region = layer.get_region(layer_region_index); - const json& layer_region_json = layer_json[JSON_LAYER_REGIONS][layer_region_index]; - convert_layer_region_from_json(layer_region_json, *layer_region); - - //LayerRegion layer_region = layer_json[JSON_LAYER_REGIONS][layer_region_index]; - } + // slice_polygons + int slice_polygons_count = layer_json[JSON_LAYER_SLICED_POLYGONS].size(); + for (int polygon_index = 0; polygon_index < slice_polygons_count; polygon_index++) + { + ExPolygon polygon; - return; -} + polygon = layer_json[JSON_LAYER_SLICED_POLYGONS][polygon_index]; + layer.lslices.push_back(std::move(polygon)); + } -void extract_support_layer(const json& support_layer_json, SupportLayer& support_layer) { - extract_layer(support_layer_json, support_layer); + // slice_bboxes + int sliced_bboxes_count = layer_json[JSON_LAYER_SLLICED_BBOXES].size(); + for (int bbox_index = 0; bbox_index < sliced_bboxes_count; bbox_index++) + { + BoundingBox bbox; - //support_islands - int islands_count = support_layer_json[JSON_SUPPORT_LAYER_ISLANDS].size(); - for (int islands_index = 0; islands_index < islands_count; islands_index++) - { - ExPolygon polygon; + bbox = layer_json[JSON_LAYER_SLLICED_BBOXES][bbox_index]; + layer.lslices_bboxes.push_back(std::move(bbox)); + } - polygon = support_layer_json[JSON_SUPPORT_LAYER_ISLANDS][islands_index]; - support_layer.support_islands.push_back(std::move(polygon)); - } + // overhang_polygons + int overhang_polygons_count = layer_json[JSON_LAYER_OVERHANG_POLYGONS].size(); + for (int polygon_index = 0; polygon_index < overhang_polygons_count; polygon_index++) + { + ExPolygon polygon; - //support_fills - support_layer.support_fills.no_sort = support_layer_json[JSON_SUPPORT_LAYER_FILLS][JSON_EXTRUSION_NO_SORT]; - int support_fills_entities_count = support_layer_json[JSON_SUPPORT_LAYER_FILLS][JSON_EXTRUSION_ENTITIES].size(); - for (int support_fills_entities_index = 0; support_fills_entities_index < support_fills_entities_count; support_fills_entities_index++) - { - const json& extrusion_entity_json = support_layer_json[JSON_SUPPORT_LAYER_FILLS][JSON_EXTRUSION_ENTITIES][support_fills_entities_index]; - bool ret = convert_extrusion_from_json(extrusion_entity_json, support_layer.support_fills); - if (!ret) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing fills found at support_layer %1%, print_z %2%")%support_layer.id() %support_layer.print_z; - char error_buf[1024]; - ::sprintf(error_buf, "Error while parsing fills at support_layer %zu, print_z %f", support_layer.id(), support_layer.print_z); - throw Slic3r::FileIOError(error_buf); + polygon = layer_json[JSON_LAYER_OVERHANG_POLYGONS][polygon_index]; + layer.loverhangs.push_back(std::move(polygon)); } - } - return; -} + // overhang_box + layer.loverhangs_bbox = layer_json[JSON_LAYER_OVERHANG_BBOX]; -static void from_json(const json& j, groupedVolumeSlices& firstlayer_group) -{ - firstlayer_group.groupId = j[JSON_FIRSTLAYER_GROUP_ID]; + // layer_regions + int layer_region_count = layer.region_count(); + for (int layer_region_index = 0; layer_region_index < layer_region_count; layer_region_index++) + { + LayerRegion *layer_region = layer.get_region(layer_region_index); + const json &layer_region_json = layer_json[JSON_LAYER_REGIONS][layer_region_index]; + convert_layer_region_from_json(layer_region_json, *layer_region); - int volume_count = j[JSON_FIRSTLAYER_GROUP_VOLUME_IDS].size(); - for (int volume_index = 0; volume_index < volume_count; volume_index++) - { - ObjectID obj_id; + // LayerRegion layer_region = layer_json[JSON_LAYER_REGIONS][layer_region_index]; + } - obj_id.id = j[JSON_FIRSTLAYER_GROUP_VOLUME_IDS][volume_index]; - firstlayer_group.volume_ids.push_back(std::move(obj_id)); + return; } - int slices_count = j[JSON_FIRSTLAYER_GROUP_SLICES].size(); - for (int slice_index = 0; slice_index < slices_count; slice_index++) + void extract_support_layer(const json &support_layer_json, SupportLayer &support_layer) { - ExPolygon polygon; - - polygon = j[JSON_FIRSTLAYER_GROUP_SLICES][slice_index]; - firstlayer_group.slices.push_back(std::move(polygon)); - } -} - -int Print::export_cached_data(const std::string& directory, int& obj_cnt_exported, bool with_space) -{ - int ret = 0; - boost::filesystem::path directory_path(directory); - obj_cnt_exported = 0; + extract_layer(support_layer_json, support_layer); - auto convert_layer_to_json = [](json& layer_json, const Layer* layer) { - json slice_polygons_json = json::array(), slice_bboxs_json = json::array(), overhang_polygons_json = json::array(), layer_regions_json = json::array(); - layer_json[JSON_LAYER_PRINT_Z] = layer->print_z; - layer_json[JSON_LAYER_HEIGHT] = layer->height; - layer_json[JSON_LAYER_SLICE_Z] = layer->slice_z; - layer_json[JSON_LAYER_ID] = layer->id(); - //layer_json["slicing_errors"] = layer->slicing_errors; + // support_islands + int islands_count = support_layer_json[JSON_SUPPORT_LAYER_ISLANDS].size(); + for (int islands_index = 0; islands_index < islands_count; islands_index++) + { + ExPolygon polygon; - //sliced_polygons - for (const ExPolygon& slice_polygon : layer->lslices) { - json slice_polygon_json = slice_polygon; - slice_polygons_json.push_back(std::move(slice_polygon_json)); + polygon = support_layer_json[JSON_SUPPORT_LAYER_ISLANDS][islands_index]; + support_layer.support_islands.push_back(std::move(polygon)); } - layer_json[JSON_LAYER_SLICED_POLYGONS] = std::move(slice_polygons_json); - //sliced_bbox - for (const BoundingBox& slice_bbox : layer->lslices_bboxes) { - json bbox_json = json::array(); - - bbox_json = slice_bbox; - slice_bboxs_json.push_back(std::move(bbox_json)); + // support_fills + support_layer.support_fills.no_sort = support_layer_json[JSON_SUPPORT_LAYER_FILLS][JSON_EXTRUSION_NO_SORT]; + int support_fills_entities_count = support_layer_json[JSON_SUPPORT_LAYER_FILLS][JSON_EXTRUSION_ENTITIES].size(); + for (int support_fills_entities_index = 0; support_fills_entities_index < support_fills_entities_count; support_fills_entities_index++) + { + const json &extrusion_entity_json = support_layer_json[JSON_SUPPORT_LAYER_FILLS][JSON_EXTRUSION_ENTITIES][support_fills_entities_index]; + bool ret = convert_extrusion_from_json(extrusion_entity_json, support_layer.support_fills); + if (!ret) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing fills found at support_layer %1%, print_z %2%") % support_layer.id() % support_layer.print_z; + char error_buf[1024]; + ::sprintf(error_buf, "Error while parsing fills at support_layer %zu, print_z %f", support_layer.id(), support_layer.print_z); + throw Slic3r::FileIOError(error_buf); + } } - layer_json[JSON_LAYER_SLLICED_BBOXES] = std::move(slice_bboxs_json); - //overhang_polygons - for (const ExPolygon& overhang_polygon : layer->loverhangs) { - json overhang_polygon_json = overhang_polygon; - overhang_polygons_json.push_back(std::move(overhang_polygon_json)); - } - layer_json[JSON_LAYER_OVERHANG_POLYGONS] = std::move(overhang_polygons_json); + return; + } - //overhang_box - layer_json[JSON_LAYER_OVERHANG_BBOX] = layer->loverhangs_bbox; + static void from_json(const json &j, groupedVolumeSlices &firstlayer_group) + { + firstlayer_group.groupId = j[JSON_FIRSTLAYER_GROUP_ID]; - for (const LayerRegion *layer_region : layer->regions()) { - json region_json = *layer_region; + int volume_count = j[JSON_FIRSTLAYER_GROUP_VOLUME_IDS].size(); + for (int volume_index = 0; volume_index < volume_count; volume_index++) + { + ObjectID obj_id; - layer_regions_json.push_back(std::move(region_json)); + obj_id.id = j[JSON_FIRSTLAYER_GROUP_VOLUME_IDS][volume_index]; + firstlayer_group.volume_ids.push_back(std::move(obj_id)); } - layer_json[JSON_LAYER_REGIONS] = std::move(layer_regions_json); - return; - }; + int slices_count = j[JSON_FIRSTLAYER_GROUP_SLICES].size(); + for (int slice_index = 0; slice_index < slices_count; slice_index++) + { + ExPolygon polygon; - //firstly clear this directory - /*if (fs::exists(directory_path)) { - fs::remove_all(directory_path); - }*/ - try { - if (!fs::exists(directory_path)) { - if (!fs::create_directory(directory_path)) { - BOOST_LOG_TRIVIAL(error) << boost::format("create directory %1% failed")%directory; - return CLI_EXPORT_CACHE_DIRECTORY_CREATE_FAILED; - } + polygon = j[JSON_FIRSTLAYER_GROUP_SLICES][slice_index]; + firstlayer_group.slices.push_back(std::move(polygon)); } } - catch (...) - { - BOOST_LOG_TRIVIAL(error) << boost::format("create directory %1% failed")%directory; - return CLI_EXPORT_CACHE_DIRECTORY_CREATE_FAILED; - } - int count = 0; - std::vector filename_vector; - std::vector json_vector; - size_t region_cnt = this->num_print_regions(); - size_t hash_values = 0; - for (size_t region_idx = 0; region_idx < region_cnt; region_idx++) + int Print::export_cached_data(const std::string &directory, int &obj_cnt_exported, bool with_space) { - boost::hash_combine(hash_values, this->get_print_region(region_idx).config_hash()); - } - for (PrintObject *obj : m_objects) { - const ModelObject* model_obj = obj->model_object(); - /*if (obj->get_shared_object()) { - BOOST_LOG_TRIVIAL(info) << boost::format("shared object %1%, skip directly")%model_obj->name; - continue; - }*/ - if (m_reslicing_objects.count(obj) == 0) { - BOOST_LOG_TRIVIAL(info) << boost::format("shared object or already cached before: %1%, skip directly")%model_obj->name; - continue; - } - obj_cnt_exported++; - - const PrintInstance &print_instance = obj->instances()[0]; - const ModelInstance *model_instance = print_instance.model_instance; - size_t identify_id = (model_instance->loaded_id > 0)?model_instance->loaded_id: model_instance->id().id; - std::string file_name = directory + "/obj_" + std::to_string(identify_id) + "_" + std::to_string(region_cnt) + "_" + std::to_string(hash_values) + ".json"; - - BOOST_LOG_TRIVIAL(warning) << boost::format("begin to dump object %1%, identify_id %2%, hash %3% to %4%, region count %5%")%model_obj->name %identify_id %hash_values %file_name %region_cnt; - - try { - json root_json, layers_json = json::array(), support_layers_json = json::array(), first_layer_groups = json::array(); - - root_json[JSON_OBJECT_NAME] = model_obj->name; - root_json[JSON_IDENTIFY_ID] = identify_id; - - //export the layers - std::vector layers_json_vector(obj->layer_count()); - tbb::parallel_for( - tbb::blocked_range(0, obj->layer_count()), - [&layers_json_vector, obj, convert_layer_to_json](const tbb::blocked_range& layer_range) { - for (size_t layer_index = layer_range.begin(); layer_index < layer_range.end(); ++ layer_index) { - const Layer *layer = obj->get_layer(layer_index); - json layer_json; - convert_layer_to_json(layer_json, layer); - layers_json_vector[layer_index] = std::move(layer_json); - } - } - ); - for (int l_index = 0; l_index < layers_json_vector.size(); l_index++) { - layers_json.push_back(std::move(layers_json_vector[l_index])); + int ret = 0; + boost::filesystem::path directory_path(directory); + obj_cnt_exported = 0; + + auto convert_layer_to_json = [](json &layer_json, const Layer *layer) + { + json slice_polygons_json = json::array(), slice_bboxs_json = json::array(), overhang_polygons_json = json::array(), layer_regions_json = json::array(); + layer_json[JSON_LAYER_PRINT_Z] = layer->print_z; + layer_json[JSON_LAYER_HEIGHT] = layer->height; + layer_json[JSON_LAYER_SLICE_Z] = layer->slice_z; + layer_json[JSON_LAYER_ID] = layer->id(); + // layer_json["slicing_errors"] = layer->slicing_errors; + + // sliced_polygons + for (const ExPolygon &slice_polygon : layer->lslices) + { + json slice_polygon_json = slice_polygon; + slice_polygons_json.push_back(std::move(slice_polygon_json)); } - layers_json_vector.clear(); - /*for (const Layer *layer : obj->layers()) { - // for each layer - json layer_json; + layer_json[JSON_LAYER_SLICED_POLYGONS] = std::move(slice_polygons_json); - convert_layer_to_json(layer_json, layer); + // sliced_bbox + for (const BoundingBox &slice_bbox : layer->lslices_bboxes) + { + json bbox_json = json::array(); - layers_json.push_back(std::move(layer_json)); - }*/ + bbox_json = slice_bbox; + slice_bboxs_json.push_back(std::move(bbox_json)); + } + layer_json[JSON_LAYER_SLLICED_BBOXES] = std::move(slice_bboxs_json); - root_json[JSON_LAYERS] = std::move(layers_json); + // overhang_polygons + for (const ExPolygon &overhang_polygon : layer->loverhangs) + { + json overhang_polygon_json = overhang_polygon; + overhang_polygons_json.push_back(std::move(overhang_polygon_json)); + } + layer_json[JSON_LAYER_OVERHANG_POLYGONS] = std::move(overhang_polygons_json); - //export the support layers - std::vector support_layers_json_vector(obj->support_layer_count()); - tbb::parallel_for( - tbb::blocked_range(0, obj->support_layer_count()), - [&support_layers_json_vector, obj, convert_layer_to_json](const tbb::blocked_range& support_layer_range) { - for (size_t s_layer_index = support_layer_range.begin(); s_layer_index < support_layer_range.end(); ++ s_layer_index) { - const SupportLayer *support_layer = obj->get_support_layer(s_layer_index); - json support_layer_json, support_islands_json = json::array(), support_fills_json, supportfills_entities_json = json::array(); + // overhang_box + layer_json[JSON_LAYER_OVERHANG_BBOX] = layer->loverhangs_bbox; - convert_layer_to_json(support_layer_json, support_layer); + for (const LayerRegion *layer_region : layer->regions()) + { + json region_json = *layer_region; - support_layer_json[JSON_SUPPORT_LAYER_INTERFACE_ID] = support_layer->interface_id(); + layer_regions_json.push_back(std::move(region_json)); + } + layer_json[JSON_LAYER_REGIONS] = std::move(layer_regions_json); - //support_islands - for (const ExPolygon& support_island : support_layer->support_islands) { - json support_island_json = support_island; - support_islands_json.push_back(std::move(support_island_json)); - } - support_layer_json[JSON_SUPPORT_LAYER_ISLANDS] = std::move(support_islands_json); - - //support_fills - support_fills_json[JSON_EXTRUSION_NO_SORT] = support_layer->support_fills.no_sort; - support_fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; - for (const ExtrusionEntity* extrusion_entity : support_layer->support_fills.entities) { - json supportfill_entity_json, supportfill_entity_paths_json = json::array(); - bool ret = convert_extrusion_to_json(supportfill_entity_json, supportfill_entity_paths_json, extrusion_entity); - if (!ret) - continue; - - supportfills_entities_json.push_back(std::move(supportfill_entity_json)); - } - support_fills_json[JSON_EXTRUSION_ENTITIES] = std::move(supportfills_entities_json); - support_layer_json[JSON_SUPPORT_LAYER_FILLS] = std::move(support_fills_json); + return; + }; - support_layers_json_vector[s_layer_index] = std::move(support_layer_json); - } + // firstly clear this directory + /*if (fs::exists(directory_path)) { + fs::remove_all(directory_path); + }*/ + try + { + if (!fs::exists(directory_path)) + { + if (!fs::create_directory(directory_path)) + { + BOOST_LOG_TRIVIAL(error) << boost::format("create directory %1% failed") % directory; + return CLI_EXPORT_CACHE_DIRECTORY_CREATE_FAILED; } - ); - for (int s_index = 0; s_index < support_layers_json_vector.size(); s_index++) { - support_layers_json.push_back(std::move(support_layers_json_vector[s_index])); } - support_layers_json_vector.clear(); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << boost::format("create directory %1% failed") % directory; + return CLI_EXPORT_CACHE_DIRECTORY_CREATE_FAILED; + } - /*for (const SupportLayer *support_layer : obj->support_layers()) { - json support_layer_json, support_islands_json = json::array(), support_fills_json, supportfills_entities_json = json::array(); + int count = 0; + std::vector filename_vector; + std::vector json_vector; + size_t region_cnt = this->num_print_regions(); + size_t hash_values = 0; + for (size_t region_idx = 0; region_idx < region_cnt; region_idx++) + { + boost::hash_combine(hash_values, this->get_print_region(region_idx).config_hash()); + } + for (PrintObject *obj : m_objects) + { + const ModelObject *model_obj = obj->model_object(); + /*if (obj->get_shared_object()) { + BOOST_LOG_TRIVIAL(info) << boost::format("shared object %1%, skip directly")%model_obj->name; + continue; + }*/ + if (m_reslicing_objects.count(obj) == 0) + { + BOOST_LOG_TRIVIAL(info) << boost::format("shared object or already cached before: %1%, skip directly") % model_obj->name; + continue; + } + obj_cnt_exported++; - convert_layer_to_json(support_layer_json, support_layer); + const PrintInstance &print_instance = obj->instances()[0]; + const ModelInstance *model_instance = print_instance.model_instance; + size_t identify_id = (model_instance->loaded_id > 0) ? model_instance->loaded_id : model_instance->id().id; + std::string file_name = directory + "/obj_" + std::to_string(identify_id) + "_" + std::to_string(region_cnt) + "_" + std::to_string(hash_values) + ".json"; - support_layer_json[JSON_SUPPORT_LAYER_INTERFACE_ID] = support_layer->interface_id(); + BOOST_LOG_TRIVIAL(warning) << boost::format("begin to dump object %1%, identify_id %2%, hash %3% to %4%, region count %5%") % model_obj->name % identify_id % hash_values % file_name % region_cnt; - //support_islands - for (const ExPolygon& support_island : support_layer->support_islands.expolygons) { - json support_island_json = support_island; - support_islands_json.push_back(std::move(support_island_json)); + try + { + json root_json, layers_json = json::array(), support_layers_json = json::array(), first_layer_groups = json::array(); + + root_json[JSON_OBJECT_NAME] = model_obj->name; + root_json[JSON_IDENTIFY_ID] = identify_id; + + // export the layers + std::vector layers_json_vector(obj->layer_count()); + tbb::parallel_for( + tbb::blocked_range(0, obj->layer_count()), + [&layers_json_vector, obj, convert_layer_to_json](const tbb::blocked_range &layer_range) + { + for (size_t layer_index = layer_range.begin(); layer_index < layer_range.end(); ++layer_index) + { + const Layer *layer = obj->get_layer(layer_index); + json layer_json; + convert_layer_to_json(layer_json, layer); + layers_json_vector[layer_index] = std::move(layer_json); + } + }); + for (int l_index = 0; l_index < layers_json_vector.size(); l_index++) + { + layers_json.push_back(std::move(layers_json_vector[l_index])); } - support_layer_json[JSON_SUPPORT_LAYER_ISLANDS] = std::move(support_islands_json); - - //support_fills - support_fills_json[JSON_EXTRUSION_NO_SORT] = support_layer->support_fills.no_sort; - support_fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; - for (const ExtrusionEntity* extrusion_entity : support_layer->support_fills.entities) { - json supportfill_entity_json, supportfill_entity_paths_json = json::array(); - bool ret = convert_extrusion_to_json(supportfill_entity_json, supportfill_entity_paths_json, extrusion_entity); - if (!ret) - continue; - - supportfills_entities_json.push_back(std::move(supportfill_entity_json)); + layers_json_vector.clear(); + /*for (const Layer *layer : obj->layers()) { + // for each layer + json layer_json; + + convert_layer_to_json(layer_json, layer); + + layers_json.push_back(std::move(layer_json)); + }*/ + + root_json[JSON_LAYERS] = std::move(layers_json); + + // export the support layers + std::vector support_layers_json_vector(obj->support_layer_count()); + tbb::parallel_for( + tbb::blocked_range(0, obj->support_layer_count()), + [&support_layers_json_vector, obj, convert_layer_to_json](const tbb::blocked_range &support_layer_range) + { + for (size_t s_layer_index = support_layer_range.begin(); s_layer_index < support_layer_range.end(); ++s_layer_index) + { + const SupportLayer *support_layer = obj->get_support_layer(s_layer_index); + json support_layer_json, support_islands_json = json::array(), support_fills_json, supportfills_entities_json = json::array(); + + convert_layer_to_json(support_layer_json, support_layer); + + support_layer_json[JSON_SUPPORT_LAYER_INTERFACE_ID] = support_layer->interface_id(); + + // support_islands + for (const ExPolygon &support_island : support_layer->support_islands) + { + json support_island_json = support_island; + support_islands_json.push_back(std::move(support_island_json)); + } + support_layer_json[JSON_SUPPORT_LAYER_ISLANDS] = std::move(support_islands_json); + + // support_fills + support_fills_json[JSON_EXTRUSION_NO_SORT] = support_layer->support_fills.no_sort; + support_fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; + for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) + { + json supportfill_entity_json, supportfill_entity_paths_json = json::array(); + bool ret = convert_extrusion_to_json(supportfill_entity_json, supportfill_entity_paths_json, extrusion_entity); + if (!ret) + continue; + + supportfills_entities_json.push_back(std::move(supportfill_entity_json)); + } + support_fills_json[JSON_EXTRUSION_ENTITIES] = std::move(supportfills_entities_json); + support_layer_json[JSON_SUPPORT_LAYER_FILLS] = std::move(support_fills_json); + + support_layers_json_vector[s_layer_index] = std::move(support_layer_json); + } + }); + for (int s_index = 0; s_index < support_layers_json_vector.size(); s_index++) + { + support_layers_json.push_back(std::move(support_layers_json_vector[s_index])); } - support_fills_json[JSON_EXTRUSION_ENTITIES] = std::move(supportfills_entities_json); - support_layer_json[JSON_SUPPORT_LAYER_FILLS] = std::move(support_fills_json); + support_layers_json_vector.clear(); - support_layers_json.push_back(std::move(support_layer_json)); - } // for each layer*/ - root_json[JSON_SUPPORT_LAYERS] = std::move(support_layers_json); + /*for (const SupportLayer *support_layer : obj->support_layers()) { + json support_layer_json, support_islands_json = json::array(), support_fills_json, supportfills_entities_json = json::array(); - const std::vector &first_layer_obj_groups = obj->firstLayerObjGroups(); - for (size_t s_group_index = 0; s_group_index < first_layer_obj_groups.size(); ++ s_group_index) { - groupedVolumeSlices group = first_layer_obj_groups[s_group_index]; + convert_layer_to_json(support_layer_json, support_layer); - //convert the id - for (ObjectID& obj_id : group.volume_ids) - { - const ModelVolume* currentModelVolumePtr = nullptr; - //BBS: support shared object logic - const PrintObject* shared_object = obj->get_shared_object(); - if (!shared_object) - shared_object = obj; - const ModelVolumePtrs& volumes_ptr = shared_object->model_object()->volumes; - size_t volume_count = volumes_ptr.size(); - for (size_t index = 0; index < volume_count; index ++) { - currentModelVolumePtr = volumes_ptr[index]; - if (currentModelVolumePtr->id() == obj_id) { - obj_id.id = index; - break; - } + support_layer_json[JSON_SUPPORT_LAYER_INTERFACE_ID] = support_layer->interface_id(); + + //support_islands + for (const ExPolygon& support_island : support_layer->support_islands.expolygons) { + json support_island_json = support_island; + support_islands_json.push_back(std::move(support_island_json)); } - } + support_layer_json[JSON_SUPPORT_LAYER_ISLANDS] = std::move(support_islands_json); + + //support_fills + support_fills_json[JSON_EXTRUSION_NO_SORT] = support_layer->support_fills.no_sort; + support_fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION; + for (const ExtrusionEntity* extrusion_entity : support_layer->support_fills.entities) { + json supportfill_entity_json, supportfill_entity_paths_json = json::array(); + bool ret = convert_extrusion_to_json(supportfill_entity_json, supportfill_entity_paths_json, extrusion_entity); + if (!ret) + continue; + + supportfills_entities_json.push_back(std::move(supportfill_entity_json)); + } + support_fills_json[JSON_EXTRUSION_ENTITIES] = std::move(supportfills_entities_json); + support_layer_json[JSON_SUPPORT_LAYER_FILLS] = std::move(support_fills_json); - json first_layer_group_json; + support_layers_json.push_back(std::move(support_layer_json)); + } // for each layer*/ + root_json[JSON_SUPPORT_LAYERS] = std::move(support_layers_json); - first_layer_group_json = group; - first_layer_groups.push_back(std::move(first_layer_group_json)); - } - root_json[JSON_FIRSTLAYER_GROUPS] = std::move(first_layer_groups); + const std::vector &first_layer_obj_groups = obj->firstLayerObjGroups(); + for (size_t s_group_index = 0; s_group_index < first_layer_obj_groups.size(); ++s_group_index) + { + groupedVolumeSlices group = first_layer_obj_groups[s_group_index]; + + // convert the id + for (ObjectID &obj_id : group.volume_ids) + { + const ModelVolume *currentModelVolumePtr = nullptr; + // BBS: support shared object logic + const PrintObject *shared_object = obj->get_shared_object(); + if (!shared_object) + shared_object = obj; + const ModelVolumePtrs &volumes_ptr = shared_object->model_object()->volumes; + size_t volume_count = volumes_ptr.size(); + for (size_t index = 0; index < volume_count; index++) + { + currentModelVolumePtr = volumes_ptr[index]; + if (currentModelVolumePtr->id() == obj_id) + { + obj_id.id = index; + break; + } + } + } - filename_vector.push_back(file_name); - json_vector.push_back(std::move(root_json)); - /*boost::nowide::ofstream c; - c.open(file_name, std::ios::out | std::ios::trunc); - if (with_space) - c << std::setw(4) << root_json << std::endl; - else - c << root_json.dump(0) << std::endl; - c.close();*/ - count ++; - BOOST_LOG_TRIVIAL(info) << boost::format("will dump object %1%'s json to %2%.")%model_obj->name%file_name; - } - catch(std::exception &err) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": save to "<name % file_name; + } + catch (std::exception &err) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": save to " << file_name << " got a generic exception, reason = " << err.what(); + ret = CLI_EXPORT_CACHE_WRITE_FAILED; } } - ); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": total printobject count %1%, saved %2%, ret=%3%")%m_objects.size() %count %ret; - return ret; -} + boost::mutex mutex; + tbb::parallel_for( + tbb::blocked_range(0, filename_vector.size()), + [filename_vector, &json_vector, with_space, &ret, &mutex](const tbb::blocked_range &output_range) + { + for (size_t object_index = output_range.begin(); object_index < output_range.end(); ++object_index) + { + try + { + boost::nowide::ofstream c; + c.open(filename_vector[object_index], std::ios::out | std::ios::trunc); + if (with_space) + c << std::setw(4) << json_vector[object_index] << std::endl; + else + c << json_vector[object_index].dump(0) << std::endl; + c.close(); + } + catch (std::exception &err) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": save to " << filename_vector[object_index] << " got a generic exception, reason = " << err.what(); + boost::unique_lock l(mutex); + ret = CLI_EXPORT_CACHE_WRITE_FAILED; + } + } + }); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": total printobject count %1%, saved %2%, ret=%3%") % m_objects.size() % count % ret; + return ret; + } -int Print::load_cached_data(const std::string& directory) -{ - int ret = 0; - boost::filesystem::path directory_path(directory); + int Print::load_cached_data(const std::string &directory) + { + int ret = 0; + boost::filesystem::path directory_path(directory); - if (!fs::exists(directory_path)) { - BOOST_LOG_TRIVIAL(info) << boost::format("directory %1% not exist.")%directory; - return CLI_IMPORT_CACHE_NOT_FOUND; - } + if (!fs::exists(directory_path)) + { + BOOST_LOG_TRIVIAL(info) << boost::format("directory %1% not exist.") % directory; + return CLI_IMPORT_CACHE_NOT_FOUND; + } - auto find_region = [this](PrintObject* object, size_t config_hash) -> const PrintRegion* { - int regions_count = object->num_printing_regions(); - for (int index = 0; index < regions_count; index++ ) + auto find_region = [this](PrintObject *object, size_t config_hash) -> const PrintRegion * { - const PrintRegion& print_region = object->printing_region(index); - if (print_region.config_hash() == config_hash ) { - return &print_region; + int regions_count = object->num_printing_regions(); + for (int index = 0; index < regions_count; index++) + { + const PrintRegion &print_region = object->printing_region(index); + if (print_region.config_hash() == config_hash) + { + return &print_region; + } } + return NULL; + }; + + int count = 0; + std::vector> object_filenames; + size_t region_cnt = this->num_print_regions(); + size_t hash_values = 0; + for (size_t region_idx = 0; region_idx < region_cnt; region_idx++) + { + boost::hash_combine(hash_values, this->get_print_region(region_idx).config_hash()); } - return NULL; - }; - - int count = 0; - std::vector> object_filenames; - size_t region_cnt = this->num_print_regions(); - size_t hash_values = 0; - for (size_t region_idx = 0; region_idx < region_cnt; region_idx++) - { - boost::hash_combine(hash_values, this->get_print_region(region_idx).config_hash()); - } - for (PrintObject *obj : m_objects) { - const ModelObject* model_obj = obj->model_object(); - const PrintInstance &print_instance = obj->instances()[0]; - const ModelInstance *model_instance = print_instance.model_instance; - - obj->clear_layers(); - obj->clear_support_layers(); + for (PrintObject *obj : m_objects) + { + const ModelObject *model_obj = obj->model_object(); + const PrintInstance &print_instance = obj->instances()[0]; + const ModelInstance *model_instance = print_instance.model_instance; - int identify_id = model_instance->loaded_id; - if (identify_id <= 0) { - //for old 3mf - identify_id = model_instance->id().id; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": object %1%'s loaded_id is 0, need to use the instance_id %2%")%model_obj->name %identify_id; - //continue; - } - std::string file_name = directory + "/obj_" + std::to_string(identify_id) + "_" + std::to_string(region_cnt) + "_" + std::to_string(hash_values) + ".json"; + obj->clear_layers(); + obj->clear_support_layers(); - if (!fs::exists(file_name)) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__<loaded_id; + if (identify_id <= 0) + { + // for old 3mf + identify_id = model_instance->id().id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": object %1%'s loaded_id is 0, need to use the instance_id %2%") % model_obj->name % identify_id; + // continue; + } + std::string file_name = directory + "/obj_" + std::to_string(identify_id) + "_" + std::to_string(region_cnt) + "_" + std::to_string(hash_values) + ".json"; - boost::mutex mutex; - std::vector object_jsons(object_filenames.size()); - tbb::parallel_for( - tbb::blocked_range(0, object_filenames.size()), - [object_filenames, &ret, &object_jsons, &mutex](const tbb::blocked_range& filename_range) { - for (size_t filename_index = filename_range.begin(); filename_index < filename_range.end(); ++ filename_index) { - try { - json root_json; - boost::nowide::ifstream ifs(object_filenames[filename_index].first); - ifs >> root_json; - object_jsons[filename_index] = std::move(root_json); - } - catch(std::exception &err) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": load from "< object_jsons(object_filenames.size()); + tbb::parallel_for( + tbb::blocked_range(0, object_filenames.size()), + [object_filenames, &ret, &object_jsons, &mutex](const tbb::blocked_range &filename_range) + { + for (size_t filename_index = filename_range.begin(); filename_index < filename_range.end(); ++filename_index) + { + try + { + json root_json; + boost::nowide::ifstream ifs(object_filenames[filename_index].first); + ifs >> root_json; + object_jsons[filename_index] = std::move(root_json); + } + catch (std::exception &err) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": load from " << object_filenames[filename_index].first << " got a generic exception, reason = " << err.what(); + boost::unique_lock l(mutex); + ret = CLI_IMPORT_CACHE_LOAD_FAILED; + } + } + }); - for (int obj_index = 0; obj_index < object_jsons.size(); obj_index++) { - json& root_json = object_jsons[obj_index]; - PrintObject *obj = object_filenames[obj_index].second; + if (ret) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": load json failed."); + return ret; + } - try { - //boost::nowide::ifstream ifs(file_name); - //ifs >> root_json; + for (int obj_index = 0; obj_index < object_jsons.size(); obj_index++) + { + json &root_json = object_jsons[obj_index]; + PrintObject *obj = object_filenames[obj_index].second; - std::string name = root_json.at(JSON_OBJECT_NAME); - int identify_id = root_json.at(JSON_IDENTIFY_ID); - int layer_count = 0, support_layer_count = 0, firstlayer_group_count = 0; + try + { + // boost::nowide::ifstream ifs(file_name); + // ifs >> root_json; - layer_count = root_json[JSON_LAYERS].size(); - support_layer_count = root_json[JSON_SUPPORT_LAYERS].size(); - firstlayer_group_count = root_json[JSON_FIRSTLAYER_GROUPS].size(); + std::string name = root_json.at(JSON_OBJECT_NAME); + int identify_id = root_json.at(JSON_IDENTIFY_ID); + int layer_count = 0, support_layer_count = 0, firstlayer_group_count = 0; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<add_layer(layer_json[JSON_LAYER_ID], layer_json[JSON_LAYER_HEIGHT], layer_json[JSON_LAYER_PRINT_Z], layer_json[JSON_LAYER_SLICE_Z]); - if (!new_layer) { - BOOST_LOG_TRIVIAL(error) <<__FUNCTION__<< boost::format(":create_layer failed, out of memory"); - return CLI_OUT_OF_MEMORY; - } - if (previous_layer) { - previous_layer->upper_layer = new_layer; - new_layer->lower_layer = previous_layer; - } - previous_layer = new_layer; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":will load %1%, identify_id %2%, layer_count %3%, support_layer_count %4%, firstlayer_group_count %5%") % name % identify_id % layer_count % support_layer_count % firstlayer_group_count; - //layer regions - int layer_regions_count = layer_json[JSON_LAYER_REGIONS].size(); - for (int region_index = 0; region_index < layer_regions_count; region_index++) + Layer *previous_layer = NULL; + // create layer and layer regions + for (int index = 0; index < layer_count; index++) { - json& region_json = layer_json[JSON_LAYER_REGIONS][region_index]; - size_t config_hash = region_json[JSON_LAYER_REGION_CONFIG_HASH]; - const PrintRegion *print_region = find_region(obj, config_hash); - - if (!print_region){ - BOOST_LOG_TRIVIAL(error) <<__FUNCTION__<< boost::format(":can not find print region of object %1%, layer %2%, print_z %3%, layer_region %4%") - %name % index %new_layer->print_z %region_index; - //delete new_layer; - return CLI_IMPORT_CACHE_DATA_CAN_NOT_USE; + json &layer_json = root_json[JSON_LAYERS][index]; + Layer *new_layer = obj->add_layer(layer_json[JSON_LAYER_ID], layer_json[JSON_LAYER_HEIGHT], layer_json[JSON_LAYER_PRINT_Z], layer_json[JSON_LAYER_SLICE_Z]); + if (!new_layer) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":create_layer failed, out of memory"); + return CLI_OUT_OF_MEMORY; + } + if (previous_layer) + { + previous_layer->upper_layer = new_layer; + new_layer->lower_layer = previous_layer; } + previous_layer = new_layer; + + // layer regions + int layer_regions_count = layer_json[JSON_LAYER_REGIONS].size(); + for (int region_index = 0; region_index < layer_regions_count; region_index++) + { + json ®ion_json = layer_json[JSON_LAYER_REGIONS][region_index]; + size_t config_hash = region_json[JSON_LAYER_REGION_CONFIG_HASH]; + const PrintRegion *print_region = find_region(obj, config_hash); + + if (!print_region) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":can not find print region of object %1%, layer %2%, print_z %3%, layer_region %4%") % name % index % new_layer->print_z % region_index; + // delete new_layer; + return CLI_IMPORT_CACHE_DATA_CAN_NOT_USE; + } - new_layer->add_region(print_region); + new_layer->add_region(print_region); + } } - } + // load the layer data parallel + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": load the layers in parallel"); + tbb::parallel_for( + tbb::blocked_range(0, obj->layer_count()), + [&root_json, &obj](const tbb::blocked_range &layer_range) + { + for (size_t layer_index = layer_range.begin(); layer_index < layer_range.end(); ++layer_index) + { + const json &layer_json = root_json[JSON_LAYERS][layer_index]; + Layer *layer = obj->get_layer(layer_index); + extract_layer(layer_json, *layer); + } + }); - //load the layer data parallel - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<(0, obj->layer_count()), - [&root_json, &obj](const tbb::blocked_range& layer_range) { - for (size_t layer_index = layer_range.begin(); layer_index < layer_range.end(); ++ layer_index) { - const json& layer_json = root_json[JSON_LAYERS][layer_index]; - Layer* layer = obj->get_layer(layer_index); - extract_layer(layer_json, *layer); + // support layers + Layer *previous_support_layer = NULL; + // create support_layers + for (int index = 0; index < support_layer_count; index++) + { + json &layer_json = root_json[JSON_SUPPORT_LAYERS][index]; + SupportLayer *new_support_layer = obj->add_support_layer(layer_json[JSON_LAYER_ID], layer_json[JSON_SUPPORT_LAYER_INTERFACE_ID], layer_json[JSON_LAYER_HEIGHT], layer_json[JSON_LAYER_PRINT_Z]); + if (!new_support_layer) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":add_support_layer failed, out of memory"); + return CLI_OUT_OF_MEMORY; } - } - ); - - //support layers - Layer* previous_support_layer = NULL; - //create support_layers - for (int index = 0; index < support_layer_count; index++) - { - json& layer_json = root_json[JSON_SUPPORT_LAYERS][index]; - SupportLayer* new_support_layer = obj->add_support_layer(layer_json[JSON_LAYER_ID], layer_json[JSON_SUPPORT_LAYER_INTERFACE_ID], layer_json[JSON_LAYER_HEIGHT], layer_json[JSON_LAYER_PRINT_Z]); - if (!new_support_layer) { - BOOST_LOG_TRIVIAL(error) <<__FUNCTION__<< boost::format(":add_support_layer failed, out of memory"); - return CLI_OUT_OF_MEMORY; - } - if (previous_support_layer) { - previous_support_layer->upper_layer = new_support_layer; - new_support_layer->lower_layer = previous_support_layer; - } - previous_support_layer = new_support_layer; - } - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": finished load layers, start to load support_layers."); - tbb::parallel_for( - tbb::blocked_range(0, obj->support_layer_count()), - [&root_json, &obj](const tbb::blocked_range& support_layer_range) { - for (size_t layer_index = support_layer_range.begin(); layer_index < support_layer_range.end(); ++ layer_index) { - const json& layer_json = root_json[JSON_SUPPORT_LAYERS][layer_index]; - SupportLayer* support_layer = obj->get_support_layer(layer_index); - extract_support_layer(layer_json, *support_layer); + if (previous_support_layer) + { + previous_support_layer->upper_layer = new_support_layer; + new_support_layer->lower_layer = previous_support_layer; } + previous_support_layer = new_support_layer; } - ); - //load first group volumes - std::vector& firstlayer_objgroups = obj->firstLayerObjGroupsMod(); - for (int index = 0; index < firstlayer_group_count; index++) - { - json& firstlayer_group_json = root_json[JSON_FIRSTLAYER_GROUPS][index]; - groupedVolumeSlices firstlayer_group = firstlayer_group_json; - //convert the id - for (ObjectID& obj_id : firstlayer_group.volume_ids) + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished load layers, start to load support_layers."); + tbb::parallel_for( + tbb::blocked_range(0, obj->support_layer_count()), + [&root_json, &obj](const tbb::blocked_range &support_layer_range) + { + for (size_t layer_index = support_layer_range.begin(); layer_index < support_layer_range.end(); ++layer_index) + { + const json &layer_json = root_json[JSON_SUPPORT_LAYERS][layer_index]; + SupportLayer *support_layer = obj->get_support_layer(layer_index); + extract_support_layer(layer_json, *support_layer); + } + }); + + // load first group volumes + std::vector &firstlayer_objgroups = obj->firstLayerObjGroupsMod(); + for (int index = 0; index < firstlayer_group_count; index++) { - ModelVolume* currentModelVolumePtr = nullptr; - ModelVolumePtrs& volumes_ptr = obj->model_object()->volumes; - size_t volume_count = volumes_ptr.size(); - if (obj_id.id < volume_count) { - currentModelVolumePtr = volumes_ptr[obj_id.id]; - obj_id = currentModelVolumePtr->id(); - } - else { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< boost::format(": can not find volume_id %1% from object file %2% in firstlayer groups, volume_count %3%!") - %obj_id.id %object_filenames[obj_index].first %volume_count; - return CLI_IMPORT_CACHE_LOAD_FAILED; + json &firstlayer_group_json = root_json[JSON_FIRSTLAYER_GROUPS][index]; + groupedVolumeSlices firstlayer_group = firstlayer_group_json; + // convert the id + for (ObjectID &obj_id : firstlayer_group.volume_ids) + { + ModelVolume *currentModelVolumePtr = nullptr; + ModelVolumePtrs &volumes_ptr = obj->model_object()->volumes; + size_t volume_count = volumes_ptr.size(); + if (obj_id.id < volume_count) + { + currentModelVolumePtr = volumes_ptr[obj_id.id]; + obj_id = currentModelVolumePtr->id(); + } + else + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": can not find volume_id %1% from object file %2% in firstlayer groups, volume_count %3%!") % obj_id.id % object_filenames[obj_index].first % volume_count; + return CLI_IMPORT_CACHE_LOAD_FAILED; + } } + firstlayer_objgroups.push_back(std::move(firstlayer_group)); } - firstlayer_objgroups.push_back(std::move(firstlayer_group)); - } - count ++; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": load object %1% from %2% successfully.")%count%object_filenames[obj_index].first; - } - catch(nlohmann::detail::parse_error &err) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<model_object()->instance_bounding_box(*model_instance, false); -} + BoundingBoxf3 PrintInstance::get_bounding_box() const + { + return print_object->model_object()->instance_bounding_box(*model_instance, false); + } -Polygon PrintInstance::get_convex_hull_2d() { - Polygon poly = print_object->model_object()->convex_hull_2d(model_instance->get_matrix()); - poly.douglas_peucker(0.1); - return poly; -} + Polygon PrintInstance::get_convex_hull_2d() + { + Polygon poly = print_object->model_object()->convex_hull_2d(model_instance->get_matrix()); + poly.douglas_peucker(0.1); + return poly; + } -//BBS: instance_shift is too large because of multi-plate, apply without plate offset. -Point PrintInstance::shift_without_plate_offset() const -{ - const Print* print = print_object->print(); - const Vec3d plate_offset = print->get_plate_origin(); - return shift - Point(scaled(plate_offset.x()), scaled(plate_offset.y())); -} + // BBS: instance_shift is too large because of multi-plate, apply without plate offset. + Point PrintInstance::shift_without_plate_offset() const + { + const Print *print = print_object->print(); + const Vec3d plate_offset = print->get_plate_origin(); + return shift - Point(scaled(plate_offset.x()), scaled(plate_offset.y())); + } -ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const -{ - ExtrusionLayers wtels; - wtels.type = ExtrusionLayersType::WIPE_TOWER; - std::vector layer_heights; - layer_heights.reserve(outer_wall.size()); - auto pre = outer_wall.begin(); - for (auto it = outer_wall.begin(); it != outer_wall.end(); ++it) { - if (it == outer_wall.begin()) - layer_heights.push_back(it->first); - else { - layer_heights.push_back(it->first - pre->first); - ++pre; + ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const + { + ExtrusionLayers wtels; + wtels.type = ExtrusionLayersType::WIPE_TOWER; + std::vector layer_heights; + layer_heights.reserve(outer_wall.size()); + auto pre = outer_wall.begin(); + for (auto it = outer_wall.begin(); it != outer_wall.end(); ++it) + { + if (it == outer_wall.begin()) + layer_heights.push_back(it->first); + else + { + layer_heights.push_back(it->first - pre->first); + ++pre; + } } + Point trans = {scale_(pos.x()), scale_(pos.y())}; + for (auto it = outer_wall.begin(); it != outer_wall.end(); ++it) + { + int index = std::distance(outer_wall.begin(), it); + ExtrusionLayer el; + ExtrusionPaths paths; + paths.reserve(it->second.size()); + for (auto &polyline : it->second) + { + ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_heights[index]); + path.polyline = polyline; + for (auto &p : path.polyline.points) + p += trans; + paths.push_back(path); + } + el.paths = std::move(paths); + el.bottom_z = it->first - layer_heights[index]; + el.layer = nullptr; + wtels.push_back(el); + } + return wtels; } - Point trans = {scale_(pos.x()), scale_(pos.y())}; - for (auto it = outer_wall.begin(); it != outer_wall.end(); ++it) { - int index = std::distance(outer_wall.begin(), it); - ExtrusionLayer el; - ExtrusionPaths paths; - paths.reserve(it->second.size()); - for (auto &polyline : it->second) { - ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_heights[index]); - path.polyline = polyline; - for (auto &p : path.polyline.points) p += trans; - paths.push_back(path); - } - el.paths = std::move(paths); - el.bottom_z = it->first - layer_heights[index]; - el.layer = nullptr; - wtels.push_back(el); - } - return wtels; -} -void WipeTowerData::construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length,bool fillet_wall) -{ - wipe_tower_mesh_data = WipeTowerMeshData{}; - float first_layer_height=0.08; //brim height - if (width < EPSILON || depth < EPSILON || height < EPSILON) return; - if (!is_rib_wipe_tower || rib_length < EPSILON) { - wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height); - wipe_tower_mesh_data->real_brim_mesh = make_cube(width + 2 * brim_width, depth + 2 * brim_width, first_layer_height); - wipe_tower_mesh_data->real_brim_mesh.translate({-brim_width, -brim_width, 0}); - wipe_tower_mesh_data->bottom = {scaled(Vec2f{-brim_width, -brim_width}), scaled(Vec2f{width + brim_width, 0}), scaled(Vec2f{width + brim_width, depth + brim_width}), - scaled(Vec2f{0, depth})}; - } else { - wipe_tower_mesh_data->real_wipe_tower_mesh = WipeTower::its_make_rib_tower(width, depth, height, rib_length, rib_width, fillet_wall); - wipe_tower_mesh_data->bottom = WipeTower::rib_section(width, depth, rib_length, rib_width, fillet_wall); - auto brim_bottom = offset(wipe_tower_mesh_data->bottom, scaled(brim_width)); - if (!brim_bottom.empty()) - wipe_tower_mesh_data->bottom = brim_bottom.front(); - wipe_tower_mesh_data->real_brim_mesh = WipeTower::its_make_rib_brim(wipe_tower_mesh_data->bottom, first_layer_height); - wipe_tower_mesh_data->real_wipe_tower_mesh.translate(Vec3f(rib_offset[0], rib_offset[1],0)); - wipe_tower_mesh_data->real_brim_mesh.translate(Vec3f(rib_offset[0], rib_offset[1], 0)); - wipe_tower_mesh_data->bottom.translate(scaled(Vec2f(rib_offset[0], rib_offset[1]))); + void WipeTowerData::construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length, bool fillet_wall) + { + wipe_tower_mesh_data = WipeTowerMeshData{}; + float first_layer_height = 0.08; // brim height + if (width < EPSILON || depth < EPSILON || height < EPSILON) + return; + if (!is_rib_wipe_tower || rib_length < EPSILON) + { + wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height); + wipe_tower_mesh_data->real_brim_mesh = make_cube(width + 2 * brim_width, depth + 2 * brim_width, first_layer_height); + wipe_tower_mesh_data->real_brim_mesh.translate({-brim_width, -brim_width, 0}); + wipe_tower_mesh_data->bottom = {scaled(Vec2f{-brim_width, -brim_width}), scaled(Vec2f{width + brim_width, 0}), scaled(Vec2f{width + brim_width, depth + brim_width}), + scaled(Vec2f{0, depth})}; + } + else + { + wipe_tower_mesh_data->real_wipe_tower_mesh = WipeTower::its_make_rib_tower(width, depth, height, rib_length, rib_width, fillet_wall); + wipe_tower_mesh_data->bottom = WipeTower::rib_section(width, depth, rib_length, rib_width, fillet_wall); + auto brim_bottom = offset(wipe_tower_mesh_data->bottom, scaled(brim_width)); + if (!brim_bottom.empty()) + wipe_tower_mesh_data->bottom = brim_bottom.front(); + wipe_tower_mesh_data->real_brim_mesh = WipeTower::its_make_rib_brim(wipe_tower_mesh_data->bottom, first_layer_height); + wipe_tower_mesh_data->real_wipe_tower_mesh.translate(Vec3f(rib_offset[0], rib_offset[1], 0)); + wipe_tower_mesh_data->real_brim_mesh.translate(Vec3f(rib_offset[0], rib_offset[1], 0)); + wipe_tower_mesh_data->bottom.translate(scaled(Vec2f(rib_offset[0], rib_offset[1]))); + } + // wipe_tower_mesh_data->real_wipe_tower_mesh.write_ascii("../wipe_tower_mesh.obj"); + // wipe_tower_mesh_data->real_brim_mesh.write_ascii("../wipe_tower_brim_mesh.obj"); } - //wipe_tower_mesh_data->real_wipe_tower_mesh.write_ascii("../wipe_tower_mesh.obj"); - //wipe_tower_mesh_data->real_brim_mesh.write_ascii("../wipe_tower_brim_mesh.obj"); -} -PrintRegion *PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region(const LayerRangeRegions &layer_range) const -{ - using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType; + PrintRegion *PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region(const LayerRangeRegions &layer_range) const + { + using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType; - if (this->parent_type == FuzzySkinParentType::PaintedRegion) { - return layer_range.painted_regions[this->parent].region; - } + if (this->parent_type == FuzzySkinParentType::PaintedRegion) + { + return layer_range.painted_regions[this->parent].region; + } - assert(this->parent_type == FuzzySkinParentType::VolumeRegion); - return layer_range.volume_regions[this->parent].region; -} + assert(this->parent_type == FuzzySkinParentType::VolumeRegion); + return layer_range.volume_regions[this->parent].region; + } -int PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region_id(const LayerRangeRegions &layer_range) const -{ - return this->parent_print_object_region(layer_range)->print_object_region_id(); -} + int PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region_id(const LayerRangeRegions &layer_range) const + { + return this->parent_print_object_region(layer_range)->print_object_region_id(); + } } // namespace Slic3r diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 157fa0d471..6cbe37a55b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -14,4553 +14,4541 @@ #include -namespace { -std::set SplitStringAndRemoveDuplicateElement(const std::string &str, const std::string &separator) +namespace { - std::set result; - if (str.empty()) return result; + std::set SplitStringAndRemoveDuplicateElement(const std::string &str, const std::string &separator) + { + std::set result; + if (str.empty()) + return result; - std::string strs = str + separator; - size_t pos; - size_t size = strs.size(); + std::string strs = str + separator; + size_t pos; + size_t size = strs.size(); - for (int i = 0; i < size; ++i) { - pos = strs.find(separator, i); - if (pos < size) { - std::string sub_str = strs.substr(i, pos - i); - result.insert(sub_str); - i = pos + separator.size() - 1; + for (int i = 0; i < size; ++i) + { + pos = strs.find(separator, i); + if (pos < size) + { + std::string sub_str = strs.substr(i, pos - i); + result.insert(sub_str); + i = pos + separator.size() - 1; + } } - } - return result; -} + return result; + } -void ReplaceString(std::string &resource_str, const std::string &old_str, const std::string &new_str) -{ - std::string::size_type pos = 0; - size_t new_size = 0; - while ((pos = resource_str.find(old_str, pos + new_size)) != std::string::npos) + void ReplaceString(std::string &resource_str, const std::string &old_str, const std::string &new_str) { - resource_str.replace(pos, old_str.length(), new_str); - new_size = new_str.size(); + std::string::size_type pos = 0; + size_t new_size = 0; + while ((pos = resource_str.find(old_str, pos + new_size)) != std::string::npos) + { + resource_str.replace(pos, old_str.length(), new_str); + new_size = new_str.size(); + } } } -} -namespace Slic3r { +namespace Slic3r +{ //! macro used to mark string used at localization, //! return same string #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) + const std::vector filament_extruder_override_keys = { + // floats + "filament_retraction_length", + "filament_z_hop", + "filament_z_hop_types", + "filament_retract_lift_above", // not in filament_options_with_variant, not used? + "filament_retract_lift_below", // not in filament_options_with_variant, not used? + "filament_retraction_speed", + "filament_deretraction_speed", + "filament_retract_restart_extra", // not in filament_options_with_variant, added on 20250816 + "filament_retraction_minimum_travel", + // BBS: floats + "filament_wipe_distance", + // bools + "filament_retract_when_changing_layer", + "filament_wipe", + // percents + "filament_retract_before_wipe", + "filament_long_retractions_when_cut", + "filament_retraction_distances_when_cut"}; + + size_t get_extruder_index(const GCodeConfig &config, unsigned int filament_id) + { + if (filament_id < config.filament_map.size()) + { + return config.filament_map.get_at(filament_id) - 1; + } + return 0; + } -const std::vector filament_extruder_override_keys = { - // floats - "filament_retraction_length", - "filament_z_hop", - "filament_z_hop_types", - "filament_retract_lift_above", //not in filament_options_with_variant, not used? - "filament_retract_lift_below", //not in filament_options_with_variant, not used? - "filament_retraction_speed", - "filament_deretraction_speed", - "filament_retract_restart_extra", //not in filament_options_with_variant, added on 20250816 - "filament_retraction_minimum_travel", - // BBS: floats - "filament_wipe_distance", - // bools - "filament_retract_when_changing_layer", - "filament_wipe", - // percents - "filament_retract_before_wipe", - "filament_long_retractions_when_cut", - "filament_retraction_distances_when_cut" -}; - -size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id) -{ - if (filament_id < config.filament_map.size()) { - return config.filament_map.get_at(filament_id)-1; + size_t get_config_idx_for_filament(const GCodeConfig &config, unsigned int filament_id) + { + if (filament_id < config.filament_map_2.size()) + { + return config.filament_map_2.get_at(filament_id); + } + return 0; } - return 0; -} -size_t get_config_idx_for_filament(const GCodeConfig& config, unsigned int filament_id) -{ - if (filament_id < config.filament_map_2.size()) { - return config.filament_map_2.get_at(filament_id); + static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values &enum_keys_map) + { + t_config_enum_names names; + int cnt = 0; + for (const auto &kvp : enum_keys_map) + cnt = std::max(cnt, kvp.second); + cnt += 1; + names.assign(cnt, ""); + for (const auto &kvp : enum_keys_map) + names[kvp.second] = kvp.first; + return names; } - return 0; -} -static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values &enum_keys_map) -{ - t_config_enum_names names; - int cnt = 0; - for (const auto& kvp : enum_keys_map) - cnt = std::max(cnt, kvp.second); - cnt += 1; - names.assign(cnt, ""); - for (const auto& kvp : enum_keys_map) - names[kvp.second] = kvp.first; - return names; -} +#define CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NAME) \ + static t_config_enum_names s_keys_names_##NAME = enum_names_from_keys_map(s_keys_map_##NAME); \ + template <> \ + const t_config_enum_values &ConfigOptionEnum::get_enum_values() { return s_keys_map_##NAME; } \ + template <> \ + const t_config_enum_names &ConfigOptionEnum::get_enum_names() { return s_keys_names_##NAME; } + + static t_config_enum_values s_keys_map_PrinterTechnology{ + {"FFF", ptFFF}, + {"SLA", ptSLA}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology) + + static t_config_enum_values s_keys_map_PrintHostType{ + {"prusalink", htPrusaLink}, + {"octoprint", htOctoPrint}, + {"duet", htDuet}, + {"flashair", htFlashAir}, + {"astrobox", htAstroBox}, + {"repetier", htRepetier}, + {"mks", htMKS}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) + + static t_config_enum_values s_keys_map_AuthorizationType{ + {"key", atKeyPassword}, + {"user", atUserPassword}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(AuthorizationType) + + static t_config_enum_values s_keys_map_GCodeFlavor{ + {"marlin", gcfMarlinLegacy}, + {"klipper", gcfKlipper}, + {"reprap", gcfRepRapSprinter}, + {"reprapfirmware", gcfRepRapFirmware}, + {"repetier", gcfRepetier}, + {"teacup", gcfTeacup}, + {"makerware", gcfMakerWare}, + {"marlin2", gcfMarlinFirmware}, + {"sailfish", gcfSailfish}, + {"smoothie", gcfSmoothie}, + {"mach3", gcfMach3}, + {"machinekit", gcfMachinekit}, + {"no-extrusion", gcfNoExtrusion}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeFlavor) + + static t_config_enum_values s_keys_map_BedTempFormula{ + {"by_first_filament", int(BedTempFormula::btfFirstFilament)}, + {"by_highest_temp", int(BedTempFormula::btfHighestTemp)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BedTempFormula) + + static t_config_enum_values s_keys_map_FuzzySkinType{ + {"none", int(FuzzySkinType::None)}, + {"external", int(FuzzySkinType::External)}, + {"all", int(FuzzySkinType::All)}, + {"allwalls", int(FuzzySkinType::AllWalls)}, + {"disabled_fuzzy", int(FuzzySkinType::Disabled_fuzzy)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType) + + static t_config_enum_values s_keys_map_InfillPattern{ + {"concentric", ipConcentric}, + {"zig-zag", ipRectilinear}, + {"grid", ipGrid}, + {"line", ipLine}, + {"cubic", ipCubic}, + {"triangles", ipTriangles}, + {"tri-hexagon", ipStars}, + {"gyroid", ipGyroid}, + {"honeycomb", ipHoneycomb}, + {"adaptivecubic", ipAdaptiveCubic}, + {"monotonic", ipMonotonic}, + {"monotonicline", ipMonotonicLine}, + {"alignedrectilinear", ipAlignedRectilinear}, + {"3dhoneycomb", ip3DHoneycomb}, + {"hilbertcurve", ipHilbertCurve}, + {"archimedeanchords", ipArchimedeanChords}, + {"octagramspiral", ipOctagramSpiral}, + {"supportcubic", ipSupportCubic}, + {"lightning", ipLightning}, + {"crosshatch", ipCrossHatch}, + {"zigzag", ipZigZag}, + {"crosszag", ipCrossZag}, + {"lockedzag", ipLockedZag}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) + + static t_config_enum_values s_keys_map_IroningType{ + {"no ironing", int(IroningType::NoIroning)}, + {"top", int(IroningType::TopSurfaces)}, + {"topmost", int(IroningType::TopmostOnly)}, + {"solid", int(IroningType::AllSolid)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(IroningType) + + // BBS: + static t_config_enum_values s_keys_map_TopOneWallType{ + {"not apply", int(TopOneWallType::None)}, + {"all top", int(TopOneWallType::Alltop)}, + {"topmost", int(TopOneWallType::Topmost)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TopOneWallType) -#define CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NAME) \ - static t_config_enum_names s_keys_names_##NAME = enum_names_from_keys_map(s_keys_map_##NAME); \ - template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values() { return s_keys_map_##NAME; } \ - template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names() { return s_keys_names_##NAME; } - -static t_config_enum_values s_keys_map_PrinterTechnology { - { "FFF", ptFFF }, - { "SLA", ptSLA } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology) - -static t_config_enum_values s_keys_map_PrintHostType{ - { "prusalink", htPrusaLink }, - { "octoprint", htOctoPrint }, - { "duet", htDuet }, - { "flashair", htFlashAir }, - { "astrobox", htAstroBox }, - { "repetier", htRepetier }, - { "mks", htMKS } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) - -static t_config_enum_values s_keys_map_AuthorizationType{ - { "key", atKeyPassword }, - { "user", atUserPassword } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(AuthorizationType) - -static t_config_enum_values s_keys_map_GCodeFlavor { - { "marlin", gcfMarlinLegacy }, - { "klipper", gcfKlipper }, - { "reprap", gcfRepRapSprinter }, - { "reprapfirmware", gcfRepRapFirmware }, - { "repetier", gcfRepetier }, - { "teacup", gcfTeacup }, - { "makerware", gcfMakerWare }, - { "marlin2", gcfMarlinFirmware }, - { "sailfish", gcfSailfish }, - { "smoothie", gcfSmoothie }, - { "mach3", gcfMach3 }, - { "machinekit", gcfMachinekit }, - { "no-extrusion", gcfNoExtrusion } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeFlavor) - -static t_config_enum_values s_keys_map_BedTempFormula { - { "by_first_filament",int(BedTempFormula::btfFirstFilament) }, - { "by_highest_temp", int(BedTempFormula::btfHighestTemp)} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BedTempFormula) - -static t_config_enum_values s_keys_map_FuzzySkinType { - { "none", int(FuzzySkinType::None) }, - { "external", int(FuzzySkinType::External) }, - { "all", int(FuzzySkinType::All) }, - { "allwalls", int(FuzzySkinType::AllWalls)}, - { "disabled_fuzzy", int(FuzzySkinType::Disabled_fuzzy)} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType) - -static t_config_enum_values s_keys_map_InfillPattern { - { "concentric", ipConcentric }, - { "zig-zag", ipRectilinear }, - { "grid", ipGrid }, - { "line", ipLine }, - { "cubic", ipCubic }, - { "triangles", ipTriangles }, - { "tri-hexagon", ipStars }, - { "gyroid", ipGyroid }, - { "honeycomb", ipHoneycomb }, - { "adaptivecubic", ipAdaptiveCubic }, - { "monotonic", ipMonotonic }, - { "monotonicline", ipMonotonicLine }, - { "alignedrectilinear", ipAlignedRectilinear }, - { "3dhoneycomb", ip3DHoneycomb }, - { "hilbertcurve", ipHilbertCurve }, - { "archimedeanchords", ipArchimedeanChords }, - { "octagramspiral", ipOctagramSpiral }, - { "supportcubic", ipSupportCubic }, - { "lightning", ipLightning }, - { "crosshatch", ipCrossHatch}, - { "zigzag", ipZigZag }, - { "crosszag", ipCrossZag }, - { "lockedzag", ipLockedZag } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) - -static t_config_enum_values s_keys_map_IroningType { - { "no ironing", int(IroningType::NoIroning) }, - { "top", int(IroningType::TopSurfaces) }, - { "topmost", int(IroningType::TopmostOnly) }, - { "solid", int(IroningType::AllSolid) } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(IroningType) - -//BBS: -static t_config_enum_values s_keys_map_TopOneWallType { - {"not apply", int(TopOneWallType::None)}, - {"all top", int(TopOneWallType::Alltop)}, - {"topmost", int(TopOneWallType::Topmost)} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TopOneWallType) - -//BBS -static t_config_enum_values s_keys_map_WallInfillOrder { - { "inner wall/outer wall/infill", int(WallInfillOrder::InnerOuterInfill) }, - { "outer wall/inner wall/infill", int(WallInfillOrder::OuterInnerInfill) }, - { "infill/inner wall/outer wall", int(WallInfillOrder::InfillInnerOuter) }, - { "infill/outer wall/inner wall", int(WallInfillOrder::InfillOuterInner) }, - { "inner-outer-inner wall/infill", int(WallInfillOrder::InnerOuterInnerInfill)} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallInfillOrder) - -//BBS -static t_config_enum_values s_keys_map_WallSequence { - { "inner wall/outer wall", int(WallSequence::InnerOuter) }, - { "outer wall/inner wall", int(WallSequence::OuterInner) }, - { "inner-outer-inner wall", int(WallSequence::InnerOuterInner)} - -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallSequence) - -static t_config_enum_values s_keys_map_EnsureVerticalThicknessLevel { - { "disabled", int(EnsureVerticalThicknessLevel::evtDisabled) }, - { "partial", int(EnsureVerticalThicknessLevel::evtPartial) }, - { "enabled", int(EnsureVerticalThicknessLevel::evtEnabled)} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnsureVerticalThicknessLevel) - -//BBS -static t_config_enum_values s_keys_map_PrintSequence { - { "by layer", int(PrintSequence::ByLayer) }, - { "by object", int(PrintSequence::ByObject) } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintSequence) - -static t_config_enum_values s_keys_map_SlicingMode { - { "regular", int(SlicingMode::Regular) }, - { "even_odd", int(SlicingMode::EvenOdd) }, - { "close_holes", int(SlicingMode::CloseHoles) } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode) - -static t_config_enum_values s_keys_map_SupportMaterialPattern { - { "rectilinear", smpRectilinear }, - { "rectilinear-grid", smpRectilinearGrid }, - { "honeycomb", smpHoneycomb }, - { "lightning", smpLightning }, - { "default", smpDefault}, - { "hollow", smpNone}, -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern) - -static t_config_enum_values s_keys_map_SupportMaterialStyle { - { "default", smsDefault }, - { "grid", smsGrid }, - { "snug", smsSnug }, - { "tree_slim", smsTreeSlim }, - { "tree_strong", smsTreeStrong }, - { "tree_hybrid", smsTreeHybrid }, - { "tree_organic", smsTreeOrganic } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle) - -static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern { - { "auto", smipAuto }, - { "rectilinear", smipRectilinear }, - { "concentric", smipConcentric }, - { "rectilinear_interlaced", smipRectilinearInterlaced}, - { "grid", smipGrid } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialInterfacePattern) - -static t_config_enum_values s_keys_map_SupportType{ - { "normal(auto)", stNormalAuto }, - { "tree(auto)", stTreeAuto }, - { "normal(manual)", stNormal }, - { "tree(manual)", stTree } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportType) - -static t_config_enum_values s_keys_map_SeamPosition { - { "nearest", spNearest }, - { "aligned", spAligned }, - { "back", spRear }, - { "random", spRandom }, -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SeamPosition) - - -// Orca -static t_config_enum_values s_keys_map_SeamScarfType{ - {"none", int(SeamScarfType::None)}, - {"external", int(SeamScarfType::External)}, - {"all", int(SeamScarfType::All)}, -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SeamScarfType) - -static const t_config_enum_values s_keys_map_SLADisplayOrientation = { - { "landscape", sladoLandscape}, - { "portrait", sladoPortrait} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLADisplayOrientation) - -static const t_config_enum_values s_keys_map_SLAPillarConnectionMode = { - {"zigzag", slapcmZigZag}, - {"cross", slapcmCross}, - {"dynamic", slapcmDynamic} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAPillarConnectionMode) - -static const t_config_enum_values s_keys_map_SLAMaterialSpeed = { - {"slow", slamsSlow}, - {"fast", slamsFast} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAMaterialSpeed); - -static const t_config_enum_values s_keys_map_BrimType = { - {"no_brim", btNoBrim}, - {"outer_only", btOuterOnly}, - {"inner_only", btInnerOnly}, - {"outer_and_inner", btOuterAndInner}, - {"auto_brim", btAutoBrim}, // BBS - {"brim_ears", btBrimEars} // BBS -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType) - -// using 0,1 to compatible with old files -static const t_config_enum_values s_keys_map_TimelapseType = { - {"0", tlTraditional}, - {"1", tlSmooth} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TimelapseType) - -static const t_config_enum_values s_keys_map_DraftShield = { - { "disabled", dsDisabled }, - { "limited", dsLimited }, - { "enabled", dsEnabled } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) - -static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = { - { "disable", ForwardCompatibilitySubstitutionRule::Disable }, - { "enable", ForwardCompatibilitySubstitutionRule::Enable }, - { "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) - -static const t_config_enum_values s_keys_map_OverhangFanThreshold = { - { "0%", Overhang_threshold_none }, - { "10%", Overhang_threshold_1_4 }, - { "25%", Overhang_threshold_2_4 }, - { "50%", Overhang_threshold_3_4 }, - { "75%", Overhang_threshold_4_4 }, - { "95%", Overhang_threshold_bridge } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(OverhangFanThreshold) - -//BBS -static const t_config_enum_values s_keys_map_OverhangThresholdParticipatingCooling = { - { "0%", Overhang_threshold_participating_cooling_none }, - { "10%", Overhang_threshold_participating_cooling_1_4 }, - { "25%", Overhang_threshold_participating_cooling_2_4 }, - { "50%", Overhang_threshold_participating_cooling_3_4 }, - { "75%", Overhang_threshold_participating_cooling_4_4 }, - { "95%", Overhang_threshold_participating_cooling_bridge } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(OverhangThresholdParticipatingCooling) - -// BBS -static const t_config_enum_values s_keys_map_BedType = { - { "Default Plate", btDefault }, - { "Cool Plate", btPC }, - { "Engineering Plate", btEP }, - { "High Temp Plate", btPEI }, - { "Textured PEI Plate", btPTE }, - {"Supertack Plate", btSuperTack} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BedType) - -// BBS -static const t_config_enum_values s_keys_map_LayerSeq = { - { "Auto", flsAuto }, - { "Customize", flsCutomize }, -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(LayerSeq) - -static t_config_enum_values s_keys_map_NozzleType { - { "undefine", int(NozzleType::ntUndefine) }, - { "hardened_steel", int(NozzleType::ntHardenedSteel) }, - { "stainless_steel", int(NozzleType::ntStainlessSteel)}, - { "tungsten_carbide", int(NozzleType::ntTungstenCarbide)}, - { "brass", int(NozzleType::ntBrass) } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleType) - -static t_config_enum_values s_keys_map_FanDirection { - { "undefine", int(FanDirection::fdUndefine) }, - { "left", int(FanDirection::fdLeft) }, - { "right", int(FanDirection::fdRight) }, - { "both", int(FanDirection::fdBoth)} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FanDirection) - -static t_config_enum_values s_keys_map_PrinterStructure { - {"undefine", int(PrinterStructure::psUndefine)}, - {"corexy", int(PrinterStructure::psCoreXY)}, - {"i3", int(PrinterStructure::psI3)}, - {"hbot", int(PrinterStructure::psHbot)}, - {"delta", int(PrinterStructure::psDelta)} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterStructure) - -static t_config_enum_values s_keys_map_PerimeterGeneratorType{ - { "classic", int(PerimeterGeneratorType::Classic) }, - { "arachne", int(PerimeterGeneratorType::Arachne) } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PerimeterGeneratorType) - -static const t_config_enum_values s_keys_map_ZHopType = { - { "Auto Lift", zhtAuto }, - { "Normal Lift", zhtNormal }, - { "Slope Lift", zhtSlope }, - { "Spiral Lift", zhtSpiral } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ZHopType) - -static const t_config_enum_values s_keys_map_ExtruderType = { - { "Direct Drive", etDirectDrive }, - { "Bowden", etBowden } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ExtruderType) - -static const t_config_enum_values s_keys_map_NozzleVolumeType = { - { "Standard", nvtStandard }, - { "High Flow", nvtHighFlow }, - { "Hybrid", nvtHybrid} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleVolumeType) - -static const t_config_enum_values s_keys_map_FilamentMapMode = { - { "Auto For Flush", fmmAutoForFlush }, - { "Auto For Match", fmmAutoForMatch }, - { "Manual", fmmManual }, - { "Nozzle Manual", fmmNozzleManual} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FilamentMapMode) - -static const t_config_enum_values s_keys_map_PrimeVolumeMode = { - { "Default", pvmDefault}, - { "Saving", pvmSaving} -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrimeVolumeMode) - -//BBS -std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type) -{ - std::string variant_string; + // BBS + static t_config_enum_values s_keys_map_WallInfillOrder{ + {"inner wall/outer wall/infill", int(WallInfillOrder::InnerOuterInfill)}, + {"outer wall/inner wall/infill", int(WallInfillOrder::OuterInnerInfill)}, + {"infill/inner wall/outer wall", int(WallInfillOrder::InfillInnerOuter)}, + {"infill/outer wall/inner wall", int(WallInfillOrder::InfillOuterInner)}, + {"inner-outer-inner wall/infill", int(WallInfillOrder::InnerOuterInnerInfill)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallInfillOrder) - if (extruder_type > etMaxExtruderType) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported ExtruderType=%1%")%extruder_type; - //extruder_type = etDirectDrive; - return variant_string; - } - if (nozzle_volume_type >= nvtMaxNozzleVolumeType) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported NozzleVolumeType=%1%")%nozzle_volume_type; - //extruder_type = etDirectDrive; + // BBS + static t_config_enum_values s_keys_map_WallSequence{ + {"inner wall/outer wall", int(WallSequence::InnerOuter)}, + {"outer wall/inner wall", int(WallSequence::OuterInner)}, + {"inner-outer-inner wall", int(WallSequence::InnerOuterInner)} + + }; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallSequence) + + static t_config_enum_values s_keys_map_EnsureVerticalThicknessLevel{ + {"disabled", int(EnsureVerticalThicknessLevel::evtDisabled)}, + {"partial", int(EnsureVerticalThicknessLevel::evtPartial)}, + {"enabled", int(EnsureVerticalThicknessLevel::evtEnabled)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnsureVerticalThicknessLevel) + + // BBS + static t_config_enum_values s_keys_map_PrintSequence{ + {"by layer", int(PrintSequence::ByLayer)}, + {"by object", int(PrintSequence::ByObject)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintSequence) + + static t_config_enum_values s_keys_map_SlicingMode{ + {"regular", int(SlicingMode::Regular)}, + {"even_odd", int(SlicingMode::EvenOdd)}, + {"close_holes", int(SlicingMode::CloseHoles)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode) + + static t_config_enum_values s_keys_map_SupportMaterialPattern{ + {"rectilinear", smpRectilinear}, + {"rectilinear-grid", smpRectilinearGrid}, + {"honeycomb", smpHoneycomb}, + {"lightning", smpLightning}, + {"default", smpDefault}, + {"hollow", smpNone}, + }; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern) + + static t_config_enum_values s_keys_map_SupportMaterialStyle{ + {"default", smsDefault}, + {"grid", smsGrid}, + {"snug", smsSnug}, + {"tree_slim", smsTreeSlim}, + {"tree_strong", smsTreeStrong}, + {"tree_hybrid", smsTreeHybrid}, + {"tree_organic", smsTreeOrganic}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle) + + static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern{ + {"auto", smipAuto}, + {"rectilinear", smipRectilinear}, + {"concentric", smipConcentric}, + {"rectilinear_interlaced", smipRectilinearInterlaced}, + {"grid", smipGrid}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialInterfacePattern) + + static t_config_enum_values s_keys_map_SupportType{ + {"normal(auto)", stNormalAuto}, + {"tree(auto)", stTreeAuto}, + {"normal(manual)", stNormal}, + {"tree(manual)", stTree}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportType) + + static t_config_enum_values s_keys_map_SeamPosition{ + {"nearest", spNearest}, + {"aligned", spAligned}, + {"back", spRear}, + {"random", spRandom}, + }; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SeamPosition) + + // Orca + static t_config_enum_values s_keys_map_SeamScarfType{ + {"none", int(SeamScarfType::None)}, + {"external", int(SeamScarfType::External)}, + {"all", int(SeamScarfType::All)}, + }; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SeamScarfType) + + static const t_config_enum_values s_keys_map_SLADisplayOrientation = { + {"landscape", sladoLandscape}, + {"portrait", sladoPortrait}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLADisplayOrientation) + + static const t_config_enum_values s_keys_map_SLAPillarConnectionMode = { + {"zigzag", slapcmZigZag}, + {"cross", slapcmCross}, + {"dynamic", slapcmDynamic}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAPillarConnectionMode) + + static const t_config_enum_values s_keys_map_SLAMaterialSpeed = { + {"slow", slamsSlow}, + {"fast", slamsFast}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAMaterialSpeed); + + static const t_config_enum_values s_keys_map_BrimType = { + {"no_brim", btNoBrim}, + {"outer_only", btOuterOnly}, + {"inner_only", btInnerOnly}, + {"outer_and_inner", btOuterAndInner}, + {"auto_brim", btAutoBrim}, // BBS + {"brim_ears", btBrimEars} // BBS + }; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType) + + // using 0,1 to compatible with old files + static const t_config_enum_values s_keys_map_TimelapseType = { + {"0", tlTraditional}, + {"1", tlSmooth}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TimelapseType) + + static const t_config_enum_values s_keys_map_DraftShield = { + {"disabled", dsDisabled}, + {"limited", dsLimited}, + {"enabled", dsEnabled}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) + + static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = { + {"disable", ForwardCompatibilitySubstitutionRule::Disable}, + {"enable", ForwardCompatibilitySubstitutionRule::Enable}, + {"enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) + + static const t_config_enum_values s_keys_map_OverhangFanThreshold = { + {"0%", Overhang_threshold_none}, + {"10%", Overhang_threshold_1_4}, + {"25%", Overhang_threshold_2_4}, + {"50%", Overhang_threshold_3_4}, + {"75%", Overhang_threshold_4_4}, + {"95%", Overhang_threshold_bridge}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(OverhangFanThreshold) + + // BBS + static const t_config_enum_values s_keys_map_OverhangThresholdParticipatingCooling = { + {"0%", Overhang_threshold_participating_cooling_none}, + {"10%", Overhang_threshold_participating_cooling_1_4}, + {"25%", Overhang_threshold_participating_cooling_2_4}, + {"50%", Overhang_threshold_participating_cooling_3_4}, + {"75%", Overhang_threshold_participating_cooling_4_4}, + {"95%", Overhang_threshold_participating_cooling_bridge}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(OverhangThresholdParticipatingCooling) + + // BBS + static const t_config_enum_values s_keys_map_BedType = { + {"Default Plate", btDefault}, + {"Cool Plate", btPC}, + {"Engineering Plate", btEP}, + {"High Temp Plate", btPEI}, + {"Textured PEI Plate", btPTE}, + {"Supertack Plate", btSuperTack}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BedType) + + // BBS + static const t_config_enum_values s_keys_map_LayerSeq = { + {"Auto", flsAuto}, + {"Customize", flsCutomize}, + }; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(LayerSeq) + + static t_config_enum_values s_keys_map_NozzleType{ + {"undefine", int(NozzleType::ntUndefine)}, + {"hardened_steel", int(NozzleType::ntHardenedSteel)}, + {"stainless_steel", int(NozzleType::ntStainlessSteel)}, + {"tungsten_carbide", int(NozzleType::ntTungstenCarbide)}, + {"brass", int(NozzleType::ntBrass)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleType) + + static t_config_enum_values s_keys_map_FanDirection{ + {"undefine", int(FanDirection::fdUndefine)}, + {"left", int(FanDirection::fdLeft)}, + {"right", int(FanDirection::fdRight)}, + {"both", int(FanDirection::fdBoth)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FanDirection) + + static t_config_enum_values s_keys_map_PrinterStructure{ + {"undefine", int(PrinterStructure::psUndefine)}, + {"corexy", int(PrinterStructure::psCoreXY)}, + {"i3", int(PrinterStructure::psI3)}, + {"hbot", int(PrinterStructure::psHbot)}, + {"delta", int(PrinterStructure::psDelta)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterStructure) + + static t_config_enum_values s_keys_map_PerimeterGeneratorType{ + {"classic", int(PerimeterGeneratorType::Classic)}, + {"arachne", int(PerimeterGeneratorType::Arachne)}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PerimeterGeneratorType) + + static const t_config_enum_values s_keys_map_ZHopType = { + {"Auto Lift", zhtAuto}, + {"Normal Lift", zhtNormal}, + {"Slope Lift", zhtSlope}, + {"Spiral Lift", zhtSpiral}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ZHopType) + + static const t_config_enum_values s_keys_map_ExtruderType = { + {"Direct Drive", etDirectDrive}, + {"Bowden", etBowden}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ExtruderType) + + static const t_config_enum_values s_keys_map_NozzleVolumeType = { + {"Standard", nvtStandard}, + {"High Flow", nvtHighFlow}, + {"Hybrid", nvtHybrid}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleVolumeType) + + static const t_config_enum_values s_keys_map_FilamentMapMode = { + {"Auto For Flush", fmmAutoForFlush}, + {"Auto For Match", fmmAutoForMatch}, + {"Manual", fmmManual}, + {"Nozzle Manual", fmmNozzleManual}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FilamentMapMode) + + static const t_config_enum_values s_keys_map_PrimeVolumeMode = { + {"Default", pvmDefault}, + {"Saving", pvmSaving}}; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrimeVolumeMode) + + // BBS + std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type) + { + std::string variant_string; + + if (extruder_type > etMaxExtruderType) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported ExtruderType=%1%") % extruder_type; + // extruder_type = etDirectDrive; + return variant_string; + } + if (nozzle_volume_type >= nvtMaxNozzleVolumeType) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported NozzleVolumeType=%1%") % nozzle_volume_type; + // extruder_type = etDirectDrive; + return variant_string; + } + variant_string = s_keys_names_ExtruderType[extruder_type]; + variant_string += " "; + variant_string += s_keys_names_NozzleVolumeType[nozzle_volume_type]; return variant_string; } - variant_string = s_keys_names_ExtruderType[extruder_type]; - variant_string+= " "; - variant_string+= s_keys_names_NozzleVolumeType[nozzle_volume_type]; - return variant_string; -} -std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type) -{ - if (nozzle_volume_type > nvtMaxNozzleVolumeType) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported NozzleVolumeType=%1%") % nozzle_volume_type; - return ""; + std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type) + { + if (nozzle_volume_type > nvtMaxNozzleVolumeType) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported NozzleVolumeType=%1%") % nozzle_volume_type; + return ""; + } + return s_keys_names_NozzleVolumeType[nozzle_volume_type]; } - return s_keys_names_NozzleVolumeType[nozzle_volume_type]; -} -std::vector> get_extruder_ams_count(const std::vector& strs) -{ - std::vector> extruder_ams_counts; - for (const std::string& str : strs) { - std::map ams_count_info; - if (str.empty()) { + std::vector> get_extruder_ams_count(const std::vector &strs) + { + std::vector> extruder_ams_counts; + for (const std::string &str : strs) + { + std::map ams_count_info; + if (str.empty()) + { + extruder_ams_counts.emplace_back(ams_count_info); + continue; + } + std::vector ams_infos; + boost::algorithm::split(ams_infos, str, boost::algorithm::is_any_of("|")); + for (const std::string &ams_info : ams_infos) + { + std::vector numbers; + boost::algorithm::split(numbers, ams_info, boost::algorithm::is_any_of("#")); + assert(numbers.size() == 2); + ams_count_info.insert(std::make_pair(stoi(numbers[0]), stoi(numbers[1]))); + } extruder_ams_counts.emplace_back(ams_count_info); - continue; - } - std::vector ams_infos; - boost::algorithm::split(ams_infos, str, boost::algorithm::is_any_of("|")); - for (const std::string& ams_info : ams_infos) { - std::vector numbers; - boost::algorithm::split(numbers, ams_info, boost::algorithm::is_any_of("#")); - assert(numbers.size() == 2); - ams_count_info.insert(std::make_pair(stoi(numbers[0]), stoi(numbers[1]))); } - extruder_ams_counts.emplace_back(ams_count_info); + return extruder_ams_counts; } - return extruder_ams_counts; -} -std::vector> get_extruder_nozzle_stats(const std::vector& strs) -{ - std::vector> extruder_nozzle_counts; - for (const std::string& str : strs) { - std::map nozzle_count_map; - if(str.empty()){ + std::vector> get_extruder_nozzle_stats(const std::vector &strs) + { + std::vector> extruder_nozzle_counts; + for (const std::string &str : strs) + { + std::map nozzle_count_map; + if (str.empty()) + { + extruder_nozzle_counts.emplace_back(nozzle_count_map); + continue; + } + std::vector nozzle_infos; + boost::algorithm::split(nozzle_infos, str, boost::is_any_of("|")); + for (auto &nozzle_info : nozzle_infos) + { + std::vector attr; + boost::algorithm::split(attr, nozzle_info, boost::is_any_of("#")); + NozzleVolumeType volume_type = NozzleVolumeType(s_keys_map_NozzleVolumeType.at(attr[0])); + int nozzle_count = std::atoi(attr[1].c_str()); + nozzle_count_map[volume_type] = nozzle_count; + } extruder_nozzle_counts.emplace_back(nozzle_count_map); - continue; - } - std::vector nozzle_infos; - boost::algorithm::split(nozzle_infos, str, boost::is_any_of("|")); - for (auto& nozzle_info : nozzle_infos) { - std::vector attr; - boost::algorithm::split(attr, nozzle_info, boost::is_any_of("#")); - NozzleVolumeType volume_type = NozzleVolumeType(s_keys_map_NozzleVolumeType.at(attr[0])); - int nozzle_count = std::atoi(attr[1].c_str()); - nozzle_count_map[volume_type] = nozzle_count; - } - extruder_nozzle_counts.emplace_back(nozzle_count_map); + } + return extruder_nozzle_counts; } - return extruder_nozzle_counts; -} - -std::vector save_extruder_ams_count_to_string(const std::vector> &extruder_ams_count) -{ - std::vector extruder_ams_count_str; - for (size_t i = 0; i < extruder_ams_count.size(); ++i) { - std::ostringstream oss; - const auto &item = extruder_ams_count[i]; - for (auto it = item.begin(); it != item.end(); ++it) { - oss << it->first << "#" << it->second; - if (std::next(it) != item.end()) { - oss << "|"; + std::vector save_extruder_ams_count_to_string(const std::vector> &extruder_ams_count) + { + std::vector extruder_ams_count_str; + for (size_t i = 0; i < extruder_ams_count.size(); ++i) + { + std::ostringstream oss; + const auto &item = extruder_ams_count[i]; + for (auto it = item.begin(); it != item.end(); ++it) + { + oss << it->first << "#" << it->second; + if (std::next(it) != item.end()) + { + oss << "|"; + } } + extruder_ams_count_str.push_back(oss.str()); } - extruder_ams_count_str.push_back(oss.str()); + return extruder_ams_count_str; } - return extruder_ams_count_str; -} -std::vector save_extruder_nozzle_stats_to_string(const std::vector>& extruder_nozzle_stats) -{ - std::vector extruder_nozzle_count_str; - for (size_t idx = 0; idx < extruder_nozzle_stats.size(); ++idx) { - std::ostringstream oss; - const auto& item = extruder_nozzle_stats[idx]; - for (auto it = item.begin(); it != item.end(); ++it) { - oss << get_nozzle_volume_type_string(it->first) << "#" << it->second; - if (std::next(it) != item.end()) - oss << "|"; - } - extruder_nozzle_count_str.emplace_back(oss.str()); + std::vector save_extruder_nozzle_stats_to_string(const std::vector> &extruder_nozzle_stats) + { + std::vector extruder_nozzle_count_str; + for (size_t idx = 0; idx < extruder_nozzle_stats.size(); ++idx) + { + std::ostringstream oss; + const auto &item = extruder_nozzle_stats[idx]; + for (auto it = item.begin(); it != item.end(); ++it) + { + oss << get_nozzle_volume_type_string(it->first) << "#" << it->second; + if (std::next(it) != item.end()) + oss << "|"; + } + extruder_nozzle_count_str.emplace_back(oss.str()); + } + return extruder_nozzle_count_str; } - return extruder_nozzle_count_str; -} + static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) + { + for (std::pair &kvp : options) + if (kvp.second.printer_technology == ptUnknown) + kvp.second.printer_technology = printer_technology; + } -static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) -{ - for (std::pair &kvp : options) - if (kvp.second.printer_technology == ptUnknown) - kvp.second.printer_technology = printer_technology; -} - -PrintConfigDef::PrintConfigDef() -{ - this->init_common_params(); - assign_printer_technology_to_unknown(this->options, ptAny); - this->init_fff_params(); - this->init_extruder_option_keys(); - assign_printer_technology_to_unknown(this->options, ptFFF); - this->init_sla_params(); - assign_printer_technology_to_unknown(this->options, ptSLA); -} + PrintConfigDef::PrintConfigDef() + { + this->init_common_params(); + assign_printer_technology_to_unknown(this->options, ptAny); + this->init_fff_params(); + this->init_extruder_option_keys(); + assign_printer_technology_to_unknown(this->options, ptFFF); + this->init_sla_params(); + assign_printer_technology_to_unknown(this->options, ptSLA); + } -void PrintConfigDef::init_common_params() -{ - ConfigOptionDef* def; - - def = this->add("printer_technology", coEnum); - //def->label = L("Printer technology"); - def->label = "Printer technology"; - //def->tooltip = L("Printer technology"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("FFF"); - def->enum_values.push_back("SLA"); - def->set_default_value(new ConfigOptionEnum(ptFFF)); - - def = this->add("printable_area", coPoints); - def->label = L("Printable area"); - //BBS - def->mode = comAdvanced; - def->gui_type = ConfigOptionDef::GUIType::one_string; - def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }); - - def = this->add("extruder_printable_area", coPointsGroups); - def->label = L("Extruder printable area"); - def->mode = comAdvanced; - def->gui_type = ConfigOptionDef::GUIType::one_string; - def->set_default_value(new ConfigOptionPointsGroups{}); - - //BBS: add "bed_exclude_area" - def = this->add("bed_exclude_area", coPoints); - def->label = L("Bed exclude area"); - def->tooltip = L("Unprintable area in XY plane. For example, X1 Series printers use the front left corner to cut filament during filament change. " - "The area is expressed as polygon by points in following format: \"XxY, XxY, ...\""); - def->mode = comAdvanced; - def->gui_type = ConfigOptionDef::GUIType::one_string; - def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); - - def = this->add("bed_custom_texture", coString); - def->label = L("Bed custom texture"); - def->mode = comAdvanced; - def->gui_type = ConfigOptionDef::GUIType::one_string; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("bed_custom_model", coString); - def->label = L("Bed custom model"); - def->mode = comAdvanced; - def->gui_type = ConfigOptionDef::GUIType::one_string; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("elefant_foot_compensation", coFloat); - def->label = L("Elephant foot compensation"); - def->category = L("Quality"); - def->tooltip = L("Shrink the initial layer on build plate to compensate for elephant foot effect"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.)); - - def = this->add("layer_height", coFloat); - def->label = L("Layer height"); - def->category = L("Quality"); - def->tooltip = L("Slicing height for each layer. Smaller layer height means more accurate and more printing time"); - def->sidetext = L("mm"); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(0.2)); - - def = this->add("printable_height", coFloat); - def->label = L("Printable height"); - def->tooltip = L("Maximum printable height which is limited by mechanism of printer"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 1000; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(100.0)); - - def = this->add("extruder_printable_height", coFloats); - def->label = L("Extruder printable height"); - def->tooltip = L("Maximum printable height of this extruder which is limited by mechanism of printer"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 1000; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0}); - - // Options used by physical printers - - def = this->add("preset_names", coStrings); - def->label = L("Printer preset names"); - //def->tooltip = L("Names of presets related to the physical printer"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("print_host", coString); - def->label = L("Hostname, IP or URL"); - def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " - "the hostname, IP address or URL of the printer host instance. " - "Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL " - "in the following format: https://username:password@your-octopi-address/"); - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("print_host_webui", coString); - def->label = L("Device UI"); - def->tooltip = L("Specify the URL of your device user interface if it's not same as print_host"); - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionString("")); - - - def = this->add("printhost_apikey", coString); - def->label = L("API Key / Password"); - def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " - "the API Key or the password required for authentication."); - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("printhost_port", coString); - def->label = L("Printer"); - def->tooltip = L("Name of the printer"); - def->gui_type = ConfigOptionDef::GUIType::select_open; - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("printhost_cafile", coString); - def->label = L("HTTPS CA File"); - def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " - "If left blank, the default OS CA certificate repository is used."); - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionString("")); - - // Options used by physical printers - - def = this->add("printhost_user", coString); - def->label = L("User"); - // def->tooltip = ""; - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("printhost_password", coString); - def->label = L("Password"); - // def->tooltip = ""; - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionString("")); - - // Only available on Windows. - def = this->add("printhost_ssl_ignore_revoke", coBool); - def->label = L("Ignore HTTPS certificate revocation checks"); - def->tooltip = L("Ignore HTTPS certificate revocation checks in case of missing or offline distribution points. " - "One may want to enable this option for self signed certificates if connection fails."); - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("preset_names", coStrings); - def->label = L("Printer preset names"); - def->tooltip = L("Names of presets related to the physical printer"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("printhost_authorization_type", coEnum); - def->label = L("Authorization Type"); - // def->tooltip = ""; - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("key"); - def->enum_values.push_back("user"); - def->enum_labels.push_back(L("API key")); - def->enum_labels.push_back(L("HTTP digest")); - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionEnum(atKeyPassword)); - - // temporary workaround for compatibility with older Slicer + void PrintConfigDef::init_common_params() { - def = this->add("preset_name", coString); - def->set_default_value(new ConfigOptionString()); + ConfigOptionDef *def; + + def = this->add("printer_technology", coEnum); + // def->label = L("Printer technology"); + def->label = "Printer technology"; + // def->tooltip = L("Printer technology"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("FFF"); + def->enum_values.push_back("SLA"); + def->set_default_value(new ConfigOptionEnum(ptFFF)); + + def = this->add("printable_area", coPoints); + def->label = L("Printable area"); + // BBS + def->mode = comAdvanced; + def->gui_type = ConfigOptionDef::GUIType::one_string; + def->set_default_value(new ConfigOptionPoints{Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200)}); + + def = this->add("extruder_printable_area", coPointsGroups); + def->label = L("Extruder printable area"); + def->mode = comAdvanced; + def->gui_type = ConfigOptionDef::GUIType::one_string; + def->set_default_value(new ConfigOptionPointsGroups{}); + + // BBS: add "bed_exclude_area" + def = this->add("bed_exclude_area", coPoints); + def->label = L("Bed exclude area"); + def->tooltip = L("Unprintable area in XY plane. For example, X1 Series printers use the front left corner to cut filament during filament change. " + "The area is expressed as polygon by points in following format: \"XxY, XxY, ...\""); + def->mode = comAdvanced; + def->gui_type = ConfigOptionDef::GUIType::one_string; + def->set_default_value(new ConfigOptionPoints{Vec2d(0, 0)}); + + def = this->add("bed_custom_texture", coString); + def->label = L("Bed custom texture"); + def->mode = comAdvanced; + def->gui_type = ConfigOptionDef::GUIType::one_string; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("bed_custom_model", coString); + def->label = L("Bed custom model"); + def->mode = comAdvanced; + def->gui_type = ConfigOptionDef::GUIType::one_string; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("elefant_foot_compensation", coFloat); + def->label = L("Elephant foot compensation"); + def->category = L("Quality"); + def->tooltip = L("Shrink the initial layer on build plate to compensate for elephant foot effect"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("layer_height", coFloat); + def->label = L("Layer height"); + def->category = L("Quality"); + def->tooltip = L("Slicing height for each layer. Smaller layer height means more accurate and more printing time"); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.2)); + + def = this->add("printable_height", coFloat); + def->label = L("Printable height"); + def->tooltip = L("Maximum printable height which is limited by mechanism of printer"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 1000; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(100.0)); + + def = this->add("extruder_printable_height", coFloats); + def->label = L("Extruder printable height"); + def->tooltip = L("Maximum printable height of this extruder which is limited by mechanism of printer"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 1000; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + // Options used by physical printers + + def = this->add("preset_names", coStrings); + def->label = L("Printer preset names"); + // def->tooltip = L("Names of presets related to the physical printer"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("print_host", coString); + def->label = L("Hostname, IP or URL"); + def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " + "the hostname, IP address or URL of the printer host instance. " + "Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL " + "in the following format: https://username:password@your-octopi-address/"); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("print_host_webui", coString); + def->label = L("Device UI"); + def->tooltip = L("Specify the URL of your device user interface if it's not same as print_host"); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("printhost_apikey", coString); + def->label = L("API Key / Password"); + def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " + "the API Key or the password required for authentication."); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("printhost_port", coString); + def->label = L("Printer"); + def->tooltip = L("Name of the printer"); + def->gui_type = ConfigOptionDef::GUIType::select_open; + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("printhost_cafile", coString); + def->label = L("HTTPS CA File"); + def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " + "If left blank, the default OS CA certificate repository is used."); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("")); + + // Options used by physical printers + + def = this->add("printhost_user", coString); + def->label = L("User"); + // def->tooltip = ""; + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("printhost_password", coString); + def->label = L("Password"); + // def->tooltip = ""; + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("")); + + // Only available on Windows. + def = this->add("printhost_ssl_ignore_revoke", coBool); + def->label = L("Ignore HTTPS certificate revocation checks"); + def->tooltip = L("Ignore HTTPS certificate revocation checks in case of missing or offline distribution points. " + "One may want to enable this option for self signed certificates if connection fails."); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("preset_names", coStrings); + def->label = L("Printer preset names"); + def->tooltip = L("Names of presets related to the physical printer"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("printhost_authorization_type", coEnum); + def->label = L("Authorization Type"); + // def->tooltip = ""; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("key"); + def->enum_values.push_back("user"); + def->enum_labels.push_back(L("API key")); + def->enum_labels.push_back(L("HTTP digest")); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionEnum(atKeyPassword)); + + // temporary workaround for compatibility with older Slicer + { + def = this->add("preset_name", coString); + def->set_default_value(new ConfigOptionString()); + } } -} -void PrintConfigDef::init_fff_params() -{ - ConfigOptionDef* def; - - // Maximum extruder temperature, bumped to 1500 to support printing of glass. - const int max_temp = 1500; - - def = this->add("reduce_crossing_wall", coBool); - def->label = L("Avoid crossing wall"); - def->category = L("Quality"); - def->tooltip = L("Detour and avoid traveling across wall which may cause blob on surface"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("z_direction_outwall_speed_continuous", coBool); - def->label = L("Smoothing wall speed along Z(experimental)"); - def->category = L("Quality"); - def->tooltip = L("Smoothing outwall speed in z direction to get better surface quality. Print time will increases. This does not work on spiral vase mode."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("max_travel_detour_distance", coFloatOrPercent); - def->label = L("Avoid crossing wall - Max detour length"); - def->category = L("Quality"); - def->tooltip = L("Maximum detour distance for avoiding crossing wall. " - "Don't detour if the detour distance is larger than this value. " - "Detour length could be specified either as an absolute value or as percentage (for example 50%) of a direct travel path. Zero to disable"); - def->sidetext = L("mm or %"); - def->min = 0; - def->max_literal = 1000; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(0., false)); - - def = this->add("avoid_crossing_wall_includes_support", coBool); - def->label = L("Avoid crossing wall - Includes support"); - def->category = L("Quality"); - def->tooltip = L("Including support while avoiding crossing wall."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); + void PrintConfigDef::init_fff_params() + { + ConfigOptionDef *def; + + // Maximum extruder temperature, bumped to 1500 to support printing of glass. + const int max_temp = 1500; + + def = this->add("reduce_crossing_wall", coBool); + def->label = L("Avoid crossing wall"); + def->category = L("Quality"); + def->tooltip = L("Detour and avoid traveling across wall which may cause blob on surface"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("z_direction_outwall_speed_continuous", coBool); + def->label = L("Smoothing wall speed along Z(experimental)"); + def->category = L("Quality"); + def->tooltip = L("Smoothing outwall speed in z direction to get better surface quality. Print time will increases. This does not work on spiral vase mode."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("max_travel_detour_distance", coFloatOrPercent); + def->label = L("Avoid crossing wall - Max detour length"); + def->category = L("Quality"); + def->tooltip = L("Maximum detour distance for avoiding crossing wall. " + "Don't detour if the detour distance is larger than this value. " + "Detour length could be specified either as an absolute value or as percentage (for example 50%) of a direct travel path. Zero to disable"); + def->sidetext = L("mm or %"); + def->min = 0; + def->max_literal = 1000; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(0., false)); + + def = this->add("avoid_crossing_wall_includes_support", coBool); + def->label = L("Avoid crossing wall - Includes support"); + def->category = L("Quality"); + def->tooltip = L("Including support while avoiding crossing wall."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); - // BBS - def = this->add("supertack_plate_temp", coInts); - def->label = L("Other layers"); - def->tooltip = L("Bed temperature for layers except the initial one. " - "Value 0 means the filament does not support to print on the Cool Plate"); - def->sidetext = "°C"; - def->full_label = L("Bed temperature"); - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{35}); - - def = this->add("cool_plate_temp", coInts); - def->label = L("Other layers"); - def->tooltip = L("Bed temperature for layers except the initial one. " - "Value 0 means the filament does not support to print on the Cool Plate"); - def->sidetext = "°C"; - def->full_label = L("Bed temperature"); - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{ 35 }); - - def = this->add("eng_plate_temp", coInts); - def->label = L("Other layers"); - def->tooltip = L("Bed temperature for layers except the initial one. " - "Value 0 means the filament does not support to print on the Engineering Plate"); - def->sidetext = "°C"; - def->full_label = L("Bed temperature"); - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{ 45 }); - - def = this->add("hot_plate_temp", coInts); - def->label = L("Other layers"); - def->tooltip = L("Bed temperature for layers except the initial one. " - "Value 0 means the filament does not support to print on the High Temp Plate"); - def->sidetext = "°C"; - def->full_label = L("Bed temperature"); - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{ 45 }); - - def = this->add("textured_plate_temp", coInts); - def->label = L("Other layers"); - def->tooltip = L("Bed temperature for layers except the initial one. " - "Value 0 means the filament does not support to print on the Textured PEI Plate"); - def->sidetext = "°C"; - def->full_label = L("Bed temperature"); - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{45}); - - def = this->add("supertack_plate_temp_initial_layer", coInts); - def->label = L("Initial layer"); - def->full_label = L("Initial layer bed temperature"); - def->tooltip = L("Bed temperature of the initial layer. " - "Value 0 means the filament does not support to print on the Bambu Cool Plate SuperTack"); - def->sidetext = "°C"; - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{ 35 }); - - def = this->add("cool_plate_temp_initial_layer", coInts); - def->label = L("Initial layer"); - def->full_label = L("Initial layer bed temperature"); - def->tooltip = L("Bed temperature of the initial layer. " - "Value 0 means the filament does not support to print on the Cool Plate"); - def->sidetext = "°C"; - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{ 35 }); - - def = this->add("eng_plate_temp_initial_layer", coInts); - def->label = L("Initial layer"); - def->full_label = L("Initial layer bed temperature"); - def->tooltip = L("Bed temperature of the initial layer. " - "Value 0 means the filament does not support to print on the Engineering Plate"); - def->sidetext = "°C"; - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{ 45 }); - - def = this->add("hot_plate_temp_initial_layer", coInts); - def->label = L("Initial layer"); - def->full_label = L("Initial layer bed temperature"); - def->tooltip = L("Bed temperature of the initial layer. " - "Value 0 means the filament does not support to print on the High Temp Plate"); - def->sidetext = "°C"; - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{ 45 }); - - def = this->add("textured_plate_temp_initial_layer", coInts); - def->label = L("Initial layer"); - def->full_label = L("Initial layer bed temperature"); - def->tooltip = L("Bed temperature of the initial layer. " - "Value 0 means the filament does not support to print on the Textured PEI Plate"); - def->sidetext = "°C"; - def->min = 0; - def->max = 120; - def->set_default_value(new ConfigOptionInts{45}); - - def = this->add("curr_bed_type", coEnum); - def->label = L("Bed type"); - def->tooltip = L("Bed types supported by the printer"); - def->mode = comSimple; - def->enum_keys_map = &s_keys_map_BedType; - def->enum_values.emplace_back("Cool Plate"); - def->enum_values.emplace_back("Engineering Plate"); - def->enum_values.emplace_back("High Temp Plate"); - def->enum_values.emplace_back("Textured PEI Plate"); - def->enum_values.emplace_back("Supertack Plate"); - def->enum_labels.emplace_back(L("Cool Plate")); - def->enum_labels.emplace_back(L("Engineering Plate")); - def->enum_labels.emplace_back(L("Smooth PEI Plate / High Temp Plate")); - def->enum_labels.emplace_back(L("Textured PEI Plate")); - def->enum_labels.emplace_back(L("Bambu Cool Plate SuperTack")); - def->set_default_value(new ConfigOptionEnum(btPC)); + // BBS + def = this->add("supertack_plate_temp", coInts); + def->label = L("Other layers"); + def->tooltip = L("Bed temperature for layers except the initial one. " + "Value 0 means the filament does not support to print on the Cool Plate"); + def->sidetext = "°C"; + def->full_label = L("Bed temperature"); + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{35}); + + def = this->add("cool_plate_temp", coInts); + def->label = L("Other layers"); + def->tooltip = L("Bed temperature for layers except the initial one. " + "Value 0 means the filament does not support to print on the Cool Plate"); + def->sidetext = "°C"; + def->full_label = L("Bed temperature"); + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{35}); + + def = this->add("eng_plate_temp", coInts); + def->label = L("Other layers"); + def->tooltip = L("Bed temperature for layers except the initial one. " + "Value 0 means the filament does not support to print on the Engineering Plate"); + def->sidetext = "°C"; + def->full_label = L("Bed temperature"); + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{45}); + + def = this->add("hot_plate_temp", coInts); + def->label = L("Other layers"); + def->tooltip = L("Bed temperature for layers except the initial one. " + "Value 0 means the filament does not support to print on the High Temp Plate"); + def->sidetext = "°C"; + def->full_label = L("Bed temperature"); + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{45}); + + def = this->add("textured_plate_temp", coInts); + def->label = L("Other layers"); + def->tooltip = L("Bed temperature for layers except the initial one. " + "Value 0 means the filament does not support to print on the Textured PEI Plate"); + def->sidetext = "°C"; + def->full_label = L("Bed temperature"); + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{45}); + + def = this->add("supertack_plate_temp_initial_layer", coInts); + def->label = L("Initial layer"); + def->full_label = L("Initial layer bed temperature"); + def->tooltip = L("Bed temperature of the initial layer. " + "Value 0 means the filament does not support to print on the Bambu Cool Plate SuperTack"); + def->sidetext = "°C"; + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{35}); + + def = this->add("cool_plate_temp_initial_layer", coInts); + def->label = L("Initial layer"); + def->full_label = L("Initial layer bed temperature"); + def->tooltip = L("Bed temperature of the initial layer. " + "Value 0 means the filament does not support to print on the Cool Plate"); + def->sidetext = "°C"; + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{35}); + + def = this->add("eng_plate_temp_initial_layer", coInts); + def->label = L("Initial layer"); + def->full_label = L("Initial layer bed temperature"); + def->tooltip = L("Bed temperature of the initial layer. " + "Value 0 means the filament does not support to print on the Engineering Plate"); + def->sidetext = "°C"; + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{45}); + + def = this->add("hot_plate_temp_initial_layer", coInts); + def->label = L("Initial layer"); + def->full_label = L("Initial layer bed temperature"); + def->tooltip = L("Bed temperature of the initial layer. " + "Value 0 means the filament does not support to print on the High Temp Plate"); + def->sidetext = "°C"; + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{45}); + + def = this->add("textured_plate_temp_initial_layer", coInts); + def->label = L("Initial layer"); + def->full_label = L("Initial layer bed temperature"); + def->tooltip = L("Bed temperature of the initial layer. " + "Value 0 means the filament does not support to print on the Textured PEI Plate"); + def->sidetext = "°C"; + def->min = 0; + def->max = 120; + def->set_default_value(new ConfigOptionInts{45}); + + def = this->add("curr_bed_type", coEnum); + def->label = L("Bed type"); + def->tooltip = L("Bed types supported by the printer"); + def->mode = comSimple; + def->enum_keys_map = &s_keys_map_BedType; + def->enum_values.emplace_back("Cool Plate"); + def->enum_values.emplace_back("Engineering Plate"); + def->enum_values.emplace_back("High Temp Plate"); + def->enum_values.emplace_back("Textured PEI Plate"); + def->enum_values.emplace_back("Supertack Plate"); + def->enum_labels.emplace_back(L("Cool Plate")); + def->enum_labels.emplace_back(L("Engineering Plate")); + def->enum_labels.emplace_back(L("Smooth PEI Plate / High Temp Plate")); + def->enum_labels.emplace_back(L("Textured PEI Plate")); + def->enum_labels.emplace_back(L("Bambu Cool Plate SuperTack")); + def->set_default_value(new ConfigOptionEnum(btPC)); - // BBS - def = this->add("first_layer_print_sequence", coInts); - def->label = L("First layer print sequence"); - def->min = 0; - def->max = 16; - def->set_default_value(new ConfigOptionInts{0}); - - def = this->add("other_layers_print_sequence", coInts); - def->label = L("Other layers print sequence"); - def->min = 0; - def->max = 16; - def->set_default_value(new ConfigOptionInts{0}); - - def = this->add("other_layers_print_sequence_nums", coInt); - def->label = L("The number of other layers print sequence"); - def->set_default_value(new ConfigOptionInt{0}); - - def = this->add("first_layer_sequence_choice", coEnum); - def->category = L("Quality"); - def->label = L("First layer filament sequence"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("Auto"); - def->enum_values.push_back("Customize"); - def->enum_labels.push_back(L("Auto")); - def->enum_labels.push_back(L("Customize")); - def->mode = comSimple; - def->set_default_value(new ConfigOptionEnum(flsAuto)); - - def = this->add("other_layers_sequence_choice", coEnum); - def->category = L("Quality"); - def->label = L("Other layers filament sequence"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("Auto"); - def->enum_values.push_back("Customize"); - def->enum_labels.push_back(L("Auto")); - def->enum_labels.push_back(L("Customize")); - def->mode = comSimple; - def->set_default_value(new ConfigOptionEnum(flsAuto)); - - def = this->add("before_layer_change_gcode", coString); - def->label = L("Before layer change G-code"); - def->tooltip = L("This G-code is inserted at every layer change before lifting z"); - def->multiline = true; - def->full_width = true; - def->height = 5; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("bottom_shell_layers", coInt); - def->label = L("Bottom shell layers"); - def->category = L("Strength"); - def->tooltip = L("This is the number of solid layers of bottom shell, including the bottom " - "surface layer. When the thickness calculated by this value is thinner " - "than bottom shell thickness, the bottom shell layers will be increased"); - def->full_label = L("Bottom shell layers"); - def->min = 0; - def->set_default_value(new ConfigOptionInt(3)); - - def = this->add("bottom_shell_thickness", coFloat); - def->label = L("Bottom shell thickness"); - def->category = L("Strength"); - def->tooltip = L("The number of bottom solid layers is increased when slicing if the thickness calculated by bottom shells layers is " - "thinner than this value. This can avoid having too thin shell when layer height is small. 0 means that " - "this setting is disabled and thickness of bottom shell is absolutely determained by bottom shell layers"); - def->full_label = L("Bottom shell thickness"); - def->sidetext = L("mm"); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(0.)); - - def = this->add("enable_overhang_bridge_fan", coBools); - def->label = L("Force cooling for overhang and bridge"); - def->tooltip = L("Enable this option to optimize part cooling fan speed for overhang and bridge to get better cooling"); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBools{ true }); - - def = this->add("overhang_fan_speed", coInts); - def->label = L("Fan speed for overhang"); - def->tooltip = L("Force part cooling fan to be at this speed when printing bridge or overhang wall which has large overhang degree. " - "Forcing cooling for overhang and bridge can get better quality for these part"); - def->sidetext = "%"; - def->min = 0; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInts { 100 }); - - def = this->add("pre_start_fan_time", coFloats); - def->label = L("Pre start fan time"); - def->tooltip = L("Force fan start early(0-5 second) when encountering overhangs. " - "This is because the fan needs time to physically increase its speed."); - def->sidetext = L("s"); - def->min = 0.; - def->max = 5.; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{0.0}); - - def = this->add("overhang_fan_threshold", coEnums); - def->label = L("Cooling overhang threshold"); - def->tooltip = L("Force cooling fan to be specific speed when overhang degree of printed part exceeds this value. " - "Expressed as percentage which indicides how much width of the line without support from lower layer. " - "0% means forcing cooling for all outer wall no matter how much overhang degree"); - def->sidetext = ""; - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->mode = comAdvanced; - def->enum_values.emplace_back("0%"); - def->enum_values.emplace_back("10%"); - def->enum_values.emplace_back("25%"); - def->enum_values.emplace_back("50%"); - def->enum_values.emplace_back("75%"); - def->enum_values.emplace_back("95%"); - def->enum_labels.emplace_back("0%"); - def->enum_labels.emplace_back("10%"); - def->enum_labels.emplace_back("25%"); - def->enum_labels.emplace_back("50%"); - def->enum_labels.emplace_back("75%"); - def->enum_labels.emplace_back("95%"); - def->set_default_value(new ConfigOptionEnumsGeneric{ (int)Overhang_threshold_bridge }); - - def = this->add("overhang_threshold_participating_cooling", coEnums); - def->label = L("Overhang threshold for participating cooling"); - def->tooltip = L("Decide which overhang part join the cooling function to slow down the speed." - "Expressed as percentage which indicides how much width of the line without support from lower layer. " - "100% means forcing cooling for all outer wall no matter how much overhang degree"); - def->sidetext = ""; - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->mode = comAdvanced; - def->enum_values.emplace_back("0%"); - def->enum_values.emplace_back("10%"); - def->enum_values.emplace_back("25%"); - def->enum_values.emplace_back("50%"); - def->enum_values.emplace_back("75%"); - def->enum_values.emplace_back("100%"); - def->enum_labels.emplace_back("0%"); - def->enum_labels.emplace_back("10%"); - def->enum_labels.emplace_back("25%"); - def->enum_labels.emplace_back("50%"); - def->enum_labels.emplace_back("75%"); - def->enum_labels.emplace_back("100%"); - def->set_default_value(new ConfigOptionEnumsGeneric{(int) Overhang_threshold_participating_cooling_bridge}); - - def = this->add("bridge_angle", coFloat); - def->label = L("Bridge direction"); - def->category = L("Strength"); - def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated " - "automatically. Otherwise the provided angle will be used for external bridges. " - "Use 180°for zero angle."); - def->sidetext = L("°"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.)); - - def = this->add("bridge_flow", coFloat); - def->label = L("Bridge flow"); - def->category = L("Quality"); - def->tooltip = L("Decrease this value slightly(for example 0.9) to reduce the amount of material for bridge, " - "to improve sag"); - def->min = 0; - def->max = 2.0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1)); - - def = this->add("top_solid_infill_flow_ratio", coFloat); - def->label = L("Top surface flow ratio"); - def->tooltip = L("This factor affects the amount of material for top solid infill. " - "You can decrease it slightly to have smooth surface finish"); - def->min = 0; - def->max = 2; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(1)); - - def = this->add("initial_layer_flow_ratio", coFloat); - def->label = L("Initial layer flow ratio"); - def->tooltip = L("This factor affects the amount of material for the initial layer"); - def->min = 0; - def->max = 2; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(1)); - - def = this->add("top_one_wall_type", coEnum); - def->label = L("Only one wall on top surfaces"); - def->category = L("Quality"); - def->tooltip = L("Use only one wall on flat top surface, to give more space to the top infill pattern. Could be applied on topmost surface or all top surface."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("not apply"); - def->enum_values.push_back("all top"); - def->enum_values.push_back("topmost"); - def->enum_labels.push_back(L("Not apply")); - def->enum_labels.push_back(L("Top surfaces")); - def->enum_labels.push_back(L("Topmost surface")); - def->set_default_value(new ConfigOptionEnum(TopOneWallType::Alltop)); - - def = this->add("top_area_threshold", coPercent); - def->label = L("Top area threshold"); - def->tooltip = L("The min width of top areas in percentage of perimeter line width."); - def->sidetext = "%"; - def->min = 0; - def->max = 500; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionPercent(200)); - - def = this->add("only_one_wall_first_layer", coBool); - def->label = L("Only one wall on first layer"); - def->category = L("Quality"); - def->tooltip = L("Use only one wall on the first layer of model"); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("enable_overhang_speed", coBools); - def->label = L("Slow down for overhang"); - def->category = L("Speed"); - def->tooltip = L("Enable this option to slow printing down for different overhang degree"); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionBoolsNullable{ true }); - - def = this->add("overhang_1_4_speed", coFloats); - def->label = "10%"; - def->category = L("Speed"); - def->full_label = "10%"; - //def->tooltip = L("Speed for line of wall which has degree of overhang between 10% and 25% line width. " - // "0 means using original wall speed"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0}); - - def = this->add("overhang_2_4_speed", coFloats); - def->label = "25%"; - def->category = L("Speed"); - def->full_label = "25%"; - //def->tooltip = L("Speed for line of wall which has degree of overhang between 25% and 50% line width. " - // "0 means using original wall speed"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0}); - - def = this->add("overhang_3_4_speed", coFloats); - def->label = "50%"; - def->category = L("Speed"); - def->full_label = "50%"; - //def->tooltip = L("Speed for line of wall which has degree of overhang between 50% and 75% line width. 0 means using original wall speed"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0}); - - def = this->add("overhang_4_4_speed", coFloats); - def->label = "75%"; - def->category = L("Speed"); - def->full_label = "75%"; - // def->tooltip = L("Speed for line of wall which has degree of overhang between 75% and 100% line width. 0 means using original wall speed"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0}); - - def = this->add("overhang_totally_speed", coFloats); - def->label = L("100%"); - def->category = L("Speed"); - def->full_label = "100%"; - def->tooltip = L("Speed of 100%% overhang wall which has 0 overlap with the lower layer."); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 10 }); - - def = this->add("enable_height_slowdown", coBools); - def->label = L("Slow down by height"); - def->category = L("Speed"); - def->tooltip = L("Enable this option to slow printing down by height"); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionBoolsNullable{ false }); - - def = this->add("slowdown_start_height", coFloats); - def->label = L("Starting height"); - def->category = L("Speed"); - def->tooltip = L("The height starts to slow down"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 0 }); - - def = this->add("slowdown_start_speed", coFloats); - def->label = L("Speed at starting height"); - def->category = L("Speed"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 1000. }); - - def = this->add("slowdown_start_acc", coFloats); - def->label = L("Acceleration at starting height"); - def->category = L("Speed"); - def->sidetext = L("mm/s²"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 100000. }); - - def = this->add("slowdown_end_height", coFloats); - def->label = L("Ending height"); - def->category = L("Speed"); - def->tooltip = L("The height finishes slowing down, " - "Ending height should be larger than Starting height, or the slowing down will not work!"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 400 }); - - def = this->add("slowdown_end_speed", coFloats); - def->label = L("Speed at ending height"); - def->category = L("Speed"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 1000. }); - - def = this->add("slowdown_end_acc", coFloats); - def->label = L("Acceleration at ending height"); - def->category = L("Speed"); - def->sidetext = L("mm/s²"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 100000. }); - - def = this->add("bridge_speed", coFloats); - def->label = L("Bridge"); - def->category = L("Speed"); - def->tooltip = L("Speed of bridge and completely overhang wall"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{25}); - - def = this->add("brim_width", coFloat); - def->label = L("Brim width"); - def->category = L("Support"); - def->tooltip = L("Distance from model to the outermost brim line"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 100; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(0.)); - - def = this->add("brim_type", coEnum); - def->label = L("Brim type"); - def->category = L("Support"); - def->tooltip = L("This controls the generation of the brim at outer and/or inner side of models. " - "Auto means the brim width is analysed and calculated automatically."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.emplace_back("auto_brim"); - def->enum_values.emplace_back("brim_ears"); - def->enum_values.emplace_back("outer_only"); -#if 1 //!BBL_RELEASE_TO_PUBLIC - // BBS: The following two types are disabled - def->enum_values.emplace_back("inner_only"); - def->enum_values.emplace_back("outer_and_inner"); + // BBS + def = this->add("first_layer_print_sequence", coInts); + def->label = L("First layer print sequence"); + def->min = 0; + def->max = 16; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("other_layers_print_sequence", coInts); + def->label = L("Other layers print sequence"); + def->min = 0; + def->max = 16; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("other_layers_print_sequence_nums", coInt); + def->label = L("The number of other layers print sequence"); + def->set_default_value(new ConfigOptionInt{0}); + + def = this->add("first_layer_sequence_choice", coEnum); + def->category = L("Quality"); + def->label = L("First layer filament sequence"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("Auto"); + def->enum_values.push_back("Customize"); + def->enum_labels.push_back(L("Auto")); + def->enum_labels.push_back(L("Customize")); + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnum(flsAuto)); + + def = this->add("other_layers_sequence_choice", coEnum); + def->category = L("Quality"); + def->label = L("Other layers filament sequence"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("Auto"); + def->enum_values.push_back("Customize"); + def->enum_labels.push_back(L("Auto")); + def->enum_labels.push_back(L("Customize")); + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnum(flsAuto)); + + def = this->add("before_layer_change_gcode", coString); + def->label = L("Before layer change G-code"); + def->tooltip = L("This G-code is inserted at every layer change before lifting z"); + def->multiline = true; + def->full_width = true; + def->height = 5; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("bottom_shell_layers", coInt); + def->label = L("Bottom shell layers"); + def->category = L("Strength"); + def->tooltip = L("This is the number of solid layers of bottom shell, including the bottom " + "surface layer. When the thickness calculated by this value is thinner " + "than bottom shell thickness, the bottom shell layers will be increased"); + def->full_label = L("Bottom shell layers"); + def->min = 0; + def->set_default_value(new ConfigOptionInt(3)); + + def = this->add("bottom_shell_thickness", coFloat); + def->label = L("Bottom shell thickness"); + def->category = L("Strength"); + def->tooltip = L("The number of bottom solid layers is increased when slicing if the thickness calculated by bottom shells layers is " + "thinner than this value. This can avoid having too thin shell when layer height is small. 0 means that " + "this setting is disabled and thickness of bottom shell is absolutely determained by bottom shell layers"); + def->full_label = L("Bottom shell thickness"); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("enable_overhang_bridge_fan", coBools); + def->label = L("Force cooling for overhang and bridge"); + def->tooltip = L("Enable this option to optimize part cooling fan speed for overhang and bridge to get better cooling"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBools{true}); + + def = this->add("overhang_fan_speed", coInts); + def->label = L("Fan speed for overhang"); + def->tooltip = L("Force part cooling fan to be at this speed when printing bridge or overhang wall which has large overhang degree. " + "Forcing cooling for overhang and bridge can get better quality for these part"); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInts{100}); + + def = this->add("pre_start_fan_time", coFloats); + def->label = L("Pre start fan time"); + def->tooltip = L("Force fan start early(0-5 second) when encountering overhangs. " + "This is because the fan needs time to physically increase its speed."); + def->sidetext = L("s"); + def->min = 0.; + def->max = 5.; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{0.0}); + + def = this->add("overhang_fan_threshold", coEnums); + def->label = L("Cooling overhang threshold"); + def->tooltip = L("Force cooling fan to be specific speed when overhang degree of printed part exceeds this value. " + "Expressed as percentage which indicides how much width of the line without support from lower layer. " + "0% means forcing cooling for all outer wall no matter how much overhang degree"); + def->sidetext = ""; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->mode = comAdvanced; + def->enum_values.emplace_back("0%"); + def->enum_values.emplace_back("10%"); + def->enum_values.emplace_back("25%"); + def->enum_values.emplace_back("50%"); + def->enum_values.emplace_back("75%"); + def->enum_values.emplace_back("95%"); + def->enum_labels.emplace_back("0%"); + def->enum_labels.emplace_back("10%"); + def->enum_labels.emplace_back("25%"); + def->enum_labels.emplace_back("50%"); + def->enum_labels.emplace_back("75%"); + def->enum_labels.emplace_back("95%"); + def->set_default_value(new ConfigOptionEnumsGeneric{(int)Overhang_threshold_bridge}); + + def = this->add("overhang_threshold_participating_cooling", coEnums); + def->label = L("Overhang threshold for participating cooling"); + def->tooltip = L("Decide which overhang part join the cooling function to slow down the speed." + "Expressed as percentage which indicides how much width of the line without support from lower layer. " + "100% means forcing cooling for all outer wall no matter how much overhang degree"); + def->sidetext = ""; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->mode = comAdvanced; + def->enum_values.emplace_back("0%"); + def->enum_values.emplace_back("10%"); + def->enum_values.emplace_back("25%"); + def->enum_values.emplace_back("50%"); + def->enum_values.emplace_back("75%"); + def->enum_values.emplace_back("100%"); + def->enum_labels.emplace_back("0%"); + def->enum_labels.emplace_back("10%"); + def->enum_labels.emplace_back("25%"); + def->enum_labels.emplace_back("50%"); + def->enum_labels.emplace_back("75%"); + def->enum_labels.emplace_back("100%"); + def->set_default_value(new ConfigOptionEnumsGeneric{(int)Overhang_threshold_participating_cooling_bridge}); + + def = this->add("bridge_angle", coFloat); + def->label = L("Bridge direction"); + def->category = L("Strength"); + def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated " + "automatically. Otherwise the provided angle will be used for external bridges. " + "Use 180°for zero angle."); + def->sidetext = L("°"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("bridge_flow", coFloat); + def->label = L("Bridge flow"); + def->category = L("Quality"); + def->tooltip = L("Decrease this value slightly(for example 0.9) to reduce the amount of material for bridge, " + "to improve sag"); + def->min = 0; + def->max = 2.0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add("top_solid_infill_flow_ratio", coFloat); + def->label = L("Top surface flow ratio"); + def->tooltip = L("This factor affects the amount of material for top solid infill. " + "You can decrease it slightly to have smooth surface finish"); + def->min = 0; + def->max = 2; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add("initial_layer_flow_ratio", coFloat); + def->label = L("Initial layer flow ratio"); + def->tooltip = L("This factor affects the amount of material for the initial layer"); + def->min = 0; + def->max = 2; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add("top_one_wall_type", coEnum); + def->label = L("Only one wall on top surfaces"); + def->category = L("Quality"); + def->tooltip = L("Use only one wall on flat top surface, to give more space to the top infill pattern. Could be applied on topmost surface or all top surface."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("not apply"); + def->enum_values.push_back("all top"); + def->enum_values.push_back("topmost"); + def->enum_labels.push_back(L("Not apply")); + def->enum_labels.push_back(L("Top surfaces")); + def->enum_labels.push_back(L("Topmost surface")); + def->set_default_value(new ConfigOptionEnum(TopOneWallType::Alltop)); + + def = this->add("top_area_threshold", coPercent); + def->label = L("Top area threshold"); + def->tooltip = L("The min width of top areas in percentage of perimeter line width."); + def->sidetext = "%"; + def->min = 0; + def->max = 500; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionPercent(200)); + + def = this->add("only_one_wall_first_layer", coBool); + def->label = L("Only one wall on first layer"); + def->category = L("Quality"); + def->tooltip = L("Use only one wall on the first layer of model"); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("enable_overhang_speed", coBools); + def->label = L("Slow down for overhang"); + def->category = L("Speed"); + def->tooltip = L("Enable this option to slow printing down for different overhang degree"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable{true}); + + def = this->add("overhang_1_4_speed", coFloats); + def->label = "10%"; + def->category = L("Speed"); + def->full_label = "10%"; + // def->tooltip = L("Speed for line of wall which has degree of overhang between 10% and 25% line width. " + // "0 means using original wall speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("overhang_2_4_speed", coFloats); + def->label = "25%"; + def->category = L("Speed"); + def->full_label = "25%"; + // def->tooltip = L("Speed for line of wall which has degree of overhang between 25% and 50% line width. " + // "0 means using original wall speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("overhang_3_4_speed", coFloats); + def->label = "50%"; + def->category = L("Speed"); + def->full_label = "50%"; + // def->tooltip = L("Speed for line of wall which has degree of overhang between 50% and 75% line width. 0 means using original wall speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("overhang_4_4_speed", coFloats); + def->label = "75%"; + def->category = L("Speed"); + def->full_label = "75%"; + // def->tooltip = L("Speed for line of wall which has degree of overhang between 75% and 100% line width. 0 means using original wall speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("overhang_totally_speed", coFloats); + def->label = L("100%"); + def->category = L("Speed"); + def->full_label = "100%"; + def->tooltip = L("Speed of 100%% overhang wall which has 0 overlap with the lower layer."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{10}); + + def = this->add("enable_height_slowdown", coBools); + def->label = L("Slow down by height"); + def->category = L("Speed"); + def->tooltip = L("Enable this option to slow printing down by height"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable{false}); + + def = this->add("slowdown_start_height", coFloats); + def->label = L("Starting height"); + def->category = L("Speed"); + def->tooltip = L("The height starts to slow down"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("slowdown_start_speed", coFloats); + def->label = L("Speed at starting height"); + def->category = L("Speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{1000.}); + + def = this->add("slowdown_start_acc", coFloats); + def->label = L("Acceleration at starting height"); + def->category = L("Speed"); + def->sidetext = L("mm/s²"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{100000.}); + + def = this->add("slowdown_end_height", coFloats); + def->label = L("Ending height"); + def->category = L("Speed"); + def->tooltip = L("The height finishes slowing down, " + "Ending height should be larger than Starting height, or the slowing down will not work!"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{400}); + + def = this->add("slowdown_end_speed", coFloats); + def->label = L("Speed at ending height"); + def->category = L("Speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{1000.}); + + def = this->add("slowdown_end_acc", coFloats); + def->label = L("Acceleration at ending height"); + def->category = L("Speed"); + def->sidetext = L("mm/s²"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{100000.}); + + def = this->add("bridge_speed", coFloats); + def->label = L("Bridge"); + def->category = L("Speed"); + def->tooltip = L("Speed of bridge and completely overhang wall"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{25}); + + def = this->add("brim_width", coFloat); + def->label = L("Brim width"); + def->category = L("Support"); + def->tooltip = L("Distance from model to the outermost brim line"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("brim_type", coEnum); + def->label = L("Brim type"); + def->category = L("Support"); + def->tooltip = L("This controls the generation of the brim at outer and/or inner side of models. " + "Auto means the brim width is analysed and calculated automatically."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.emplace_back("auto_brim"); + def->enum_values.emplace_back("brim_ears"); + def->enum_values.emplace_back("outer_only"); +#if 1 //! BBL_RELEASE_TO_PUBLIC + // BBS: The following two types are disabled + def->enum_values.emplace_back("inner_only"); + def->enum_values.emplace_back("outer_and_inner"); #endif - def->enum_values.emplace_back("no_brim"); - - def->enum_labels.emplace_back(L("Auto")); - def->enum_labels.emplace_back(L("Painted")); - def->enum_labels.emplace_back(L("Outer brim only")); -#if 1 //!BBL_RELEASE_TO_PUBLIC - // BBS: The following two types are disabled - def->enum_labels.emplace_back(L("Inner brim only")); - def->enum_labels.emplace_back(L("Outer and inner brim")); + def->enum_values.emplace_back("no_brim"); + + def->enum_labels.emplace_back(L("Auto")); + def->enum_labels.emplace_back(L("Painted")); + def->enum_labels.emplace_back(L("Outer brim only")); +#if 1 //! BBL_RELEASE_TO_PUBLIC + // BBS: The following two types are disabled + def->enum_labels.emplace_back(L("Inner brim only")); + def->enum_labels.emplace_back(L("Outer and inner brim")); #endif - def->enum_labels.emplace_back(L("No-brim")); - - def->mode = comSimple; - def->set_default_value(new ConfigOptionEnum(btAutoBrim)); - - def = this->add("brim_object_gap", coFloat); - def->label = L("Brim-object gap"); - def->category = L("Support"); - def->tooltip = L("A gap between innermost brim line and object can make brim be removed more easily"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 2; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.)); - - - def = this->add("compatible_printers", coStrings); - def->label = L("Compatible machine"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - - //BBS. - def = this->add("upward_compatible_machine", coStrings); - def->label = L("upward compatible machine"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("compatible_printers_condition", coString); - def->label = L("Compatible machine condition"); - //def->tooltip = L("A boolean expression using the configuration values of an active printer profile. " - // "If this expression evaluates to true, this profile is considered compatible " - // "with the active printer profile."); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionString()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("compatible_prints", coStrings); - def->label = L("Compatible process profiles"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("compatible_prints_condition", coString); - def->label = L("Compatible process profiles condition"); - //def->tooltip = L("A boolean expression using the configuration values of an active print profile. " - // "If this expression evaluates to true, this profile is considered compatible " - // "with the active print profile."); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionString()); - def->cli = ConfigOptionDef::nocli; - - // The following value is to be stored into the project file (AMF, 3MF, Config ...) - // and it contains a sum of "compatible_printers_condition" values over the print and filament profiles. - def = this->add("compatible_machine_expression_group", coStrings); - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - def = this->add("compatible_process_expression_group", coStrings); - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - - //BBS: add logic for checking between different system presets - def = this->add("different_settings_to_system", coStrings); - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("print_compatible_printers", coStrings); - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("print_sequence", coEnum); - def->label = L("Print sequence"); - def->tooltip = L("Print sequence, layer by layer or object by object"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("by layer"); - def->enum_values.push_back("by object"); - def->enum_labels.push_back(L("By layer")); - def->enum_labels.push_back(L("By object")); - def->mode = comSimple; - def->set_default_value(new ConfigOptionEnum(PrintSequence::ByLayer)); - - def = this->add("slow_down_for_layer_cooling", coBools); - def->label = L("Slow printing down for better layer cooling"); - def->tooltip = L("Enable this option to slow printing speed down to make the final layer time not shorter than " - "the layer time threshold in \"Max fan speed threshold\", so that layer can be cooled for a longer time. " - "This can improve the cooling quality for needle and small details"); - def->set_default_value(new ConfigOptionBools { true }); - - def = this->add("no_slow_down_for_cooling_on_outwalls", coBools); - def->label = L("Don't slow down outer walls"); - def->tooltip = L("If enabled, this setting will ensure external perimeters are not slowed down to meet the minimum layer time. " - "This is particularly helpful in the below scenarios:\n" - "1. To avoid changes in shine when printing glossy filaments\n" - "2. To avoid changes in external wall speed which may create slight wall artifacts that appear like Z banding\n" - "3. To avoid printing at speeds which cause VFAs (fine artifacts) on the external walls"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBools{false}); - - def = this->add("default_acceleration", coFloats); - def->label = L("Normal printing"); - def->tooltip = L("The default acceleration of both normal printing and travel except initial layer"); - def->sidetext = "mm/s²"; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{500.0}); - - def = this->add("travel_acceleration", coFloats); - def->label = L("Travel"); - def->tooltip = L("The acceleration of travel except initial layer"); - def->sidetext = "mm/s²"; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{500.0}); - - def = this->add("initial_layer_travel_acceleration", coFloats); - def->label = L("Initial layer travel"); - def->tooltip = L("The acceleration of travel of initial layer"); - def->sidetext = "mm/s²"; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{500.0}); - - def = this->add("default_filament_profile", coStrings); - def->label = L("Default filament profile"); - def->tooltip = L("Default filament profile when switch to this machine profile"); - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("default_print_profile", coString); - def->label = L("Default process profile"); - def->tooltip = L("Default process profile when switch to this machine profile"); - def->set_default_value(new ConfigOptionString()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("activate_air_filtration",coBools); - def->label = L("Activate air filtration"); - def->tooltip = L("Activate for better air filtration"); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBools{false}); - - def = this->add("during_print_exhaust_fan_speed", coInts); - def->label = L("Fan speed"); - def->tooltip=L("Speed of exhaust fan during printing.This speed will overwrite the speed in filament custom gcode"); - def->sidetext = "%"; - def->min=0; - def->max=100; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInts{60}); - - def = this->add("complete_print_exhaust_fan_speed", coInts); - def->label = L("Fan speed"); - def->sidetext = "%"; - def->tooltip=L("Speed of exhuast fan after printing completes"); - def->min=0; - def->max=100; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInts{80}); - - def = this->add("close_fan_the_first_x_layers", coInts); - def->label = L("For the first"); - def->tooltip = L("Set special cooling fan for the first certain layers. Cooling fan of the first layer used to be closed " - "to get better build plate adhesion and used for auto cooling function"); - def->sidetext = L("layers"); - def->min = 0; - def->max = 1000; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInts { 1 }); - - def = this->add("first_x_layer_fan_speed", coFloats); - def->label = L("Fan speed"); - def->tooltip = L("Special cooling fan speed for the first certain layers"); - def->sidetext = "%"; - def->min = 0; - def->max = 100; - def->set_default_value(new ConfigOptionFloats{0}); - - def = this->add("bridge_no_support", coBool); - def->label = L("Don't support bridges"); - def->category = L("Support"); - def->tooltip = L("Don't support the whole bridge area which makes support very large. " - "Bridge usually can be printing directly without support if not very long"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("thick_bridges", coBool); - def->label = L("Thick bridges"); - def->category = L("Quality"); - def->tooltip = L("If enabled, bridges are more reliable, can bridge longer distances, but may look worse. " - "If disabled, bridges look better but are reliable just for shorter bridged distances."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("max_bridge_length", coFloat); - def->label = L("Max bridge length"); - def->category = L("Support"); - def->tooltip = L("Max length of bridges that don't need support. Set it to 0 if you want all bridges to be supported, and set it to a very large value if you don't want any bridges to be supported."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(10)); - - def = this->add("machine_end_gcode", coString); - def->label = L("End G-code"); - def->tooltip = L("End G-code when finish the whole printing"); - def->multiline = true; - def->full_width = true; - def->height = 12; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n")); - - def = this->add("printing_by_object_gcode", coString); - def->label = L("Between Object Gcode"); - def->tooltip = L("Insert Gcode between objects. This parameter will only come into effect when you print your models object by object"); - def->multiline = true; - def->full_width = true; - def->height = 12; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("filament_end_gcode", coStrings); - def->label = L("End G-code"); - def->tooltip = L("End G-code when finish the printing of this filament"); - def->multiline = true; - def->full_width = true; - def->height = 120; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionStrings { " " }); - - def = this->add("ensure_vertical_shell_thickness", coEnum); - def->label = L("Ensure vertical shell thickness"); - def->category = L("Strength"); - def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness " - "(top+bottom solid layers)"); - def->mode = comAdvanced; - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("disabled"); - def->enum_values.push_back("partial"); - def->enum_values.push_back("enabled"); - def->enum_labels.push_back(L("Disabled")); - def->enum_labels.push_back(L("Partial")); - def->enum_labels.push_back(L("Enabled")); - def->set_default_value(new ConfigOptionEnum(EnsureVerticalThicknessLevel::evtEnabled)); - - def = this->add("vertical_shell_speed",coFloatsOrPercents); - def->label = L("Vertical shell speed"); - def->tooltip = L("Speed for vertical shells with overhang regions. If expressed as percentage (for example: 80%) it will be calculated on" - "the internal solid infill speed above"); - def->category = L("Speed"); - def->sidetext = L("mm/s or %"); - def->ratio_over = "internal_solid_infill_speed"; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(80, true)}); - - def = this->add("detect_floating_vertical_shell", coBool); - def->label = L("Detect floating vertical shells"); - def->tooltip = L("Detect overhang paths in vertical shells and slow them by bridge speed."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool{true}); - - def = this->add("internal_bridge_support_thickness", coFloat); - def->label = L("Internal bridge support thickness"); - def->category = L("Strength"); - def->tooltip = L("When sparse infill density is low, the internal solid infill or internal bridge may have no archor at the end of line. " - "This causes falling and bad quality when printing internal solid infill. " - "When enable this feature, loop paths will be added to the sparse fill of the lower layers for specific thickness, so that better archor can be provided for internal bridge. " - "0 means disable this feature"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 2; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - auto def_top_fill_pattern = def = this->add("top_surface_pattern", coEnum); - def->label = L("Top surface pattern"); - def->category = L("Strength"); - def->tooltip = L("Line pattern of top surface infill"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("concentric"); - def->enum_values.push_back("zig-zag"); - def->enum_values.push_back("monotonic"); - def->enum_values.push_back("monotonicline"); - def->enum_values.push_back("alignedrectilinear"); - def->enum_values.push_back("hilbertcurve"); - def->enum_values.push_back("archimedeanchords"); - def->enum_values.push_back("octagramspiral"); - def->enum_labels.push_back(L("Concentric")); - def->enum_labels.push_back(L("Rectilinear")); - def->enum_labels.push_back(L("Monotonic")); - def->enum_labels.push_back(L("Monotonic line")); - def->enum_labels.push_back(L("Aligned Rectilinear")); - def->enum_labels.push_back(L("Hilbert Curve")); - def->enum_labels.push_back(L("Archimedean Chords")); - def->enum_labels.push_back(L("Octagram Spiral")); - def->set_default_value(new ConfigOptionEnum(ipRectilinear)); - - def = this->add("bottom_surface_pattern", coEnum); - def->label = L("Bottom surface pattern"); - def->category = L("Strength"); - def->tooltip = L("Line pattern of bottom surface infill, not bridge infill"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values = def_top_fill_pattern->enum_values; - def->enum_labels = def_top_fill_pattern->enum_labels; - def->set_default_value(new ConfigOptionEnum(ipRectilinear)); - - def = this->add("internal_solid_infill_pattern", coEnum); - def->label = L("Internal solid infill pattern"); - def->category = L("Strength"); - def->tooltip = L("Line pattern of internal solid infill. if the detect narrow internal solid infill be enabled, the concentric pattern will be used for the small area."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values = def_top_fill_pattern->enum_values; - def->enum_labels = def_top_fill_pattern->enum_labels; - def->set_default_value(new ConfigOptionEnum(ipRectilinear)); - - def = this->add("outer_wall_line_width", coFloat); - def->label = L("Outer wall"); - def->category = L("Quality"); - def->tooltip = L("Line width of outer wall"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("outer_wall_speed", coFloats); - def->label = L("Outer wall"); - def->category = L("Speed"); - def->tooltip = L("Speed of outer wall which is outermost and visible. " - "It's used to be slower than inner wall speed to get better quality."); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{60}); - - - def = this->add("small_perimeter_speed", coFloatsOrPercents); - def->label = L("Small perimeters"); - def->category = L("Speed"); - def->tooltip = L("This setting will affect the speed of perimeters having radius <= small perimeter threshold" - "(usually holes). If expressed as percentage (for example: 80%) it will be calculated on" - "the outer wall speed setting above. Set to zero for auto."); - def->sidetext = L("mm/s or %"); - def->ratio_over = "outer_wall_speed"; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(50, true)}); - - def = this->add("small_perimeter_threshold", coFloats); - def->label = L("Small perimeter threshold"); - def->category = L("Speed"); - def->tooltip = L("This sets the threshold for small perimeter length. Default threshold is 0mm"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0}); - - def = this->add("wall_sequence", coEnum); - def->label = L("Order of walls"); - def->category = L("Quality"); - def->tooltip = L("Print sequence of inner wall and outer wall. "); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("inner wall/outer wall"); - def->enum_values.push_back("outer wall/inner wall"); - def->enum_values.push_back("inner-outer-inner wall"); - def->enum_labels.push_back(L("inner/outer")); - def->enum_labels.push_back(L("outer/inner")); - def->enum_labels.push_back(L("inner wall/outer wall/inner wall")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(WallSequence::InnerOuter)); - - def = this->add("is_infill_first",coBool); - def->label = L("Print infill first"); - def->tooltip = L("Order of wall/infill. false means print wall first. "); - def->category = L("Quality"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool{false}); - - def = this->add("extruder", coInt); - def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Extruder"); - def->category = L("Extruders"); - //def->tooltip = L("The extruder to use (unless more specific extruder settings are specified). " - // "This value overrides perimeter and infill extruders, but not the support extruders."); - def->min = 0; // 0 = inherit defaults - def->enum_labels.push_back(L("default")); // override label for item 0 - def->enum_labels.push_back("1"); - def->enum_labels.push_back("2"); - def->enum_labels.push_back("3"); - def->enum_labels.push_back("4"); - def->enum_labels.push_back("5"); - def->mode = comDevelop; - - def = this->add("extruder_clearance_height_to_rod", coFloat); - def->label = L("Height to rod"); - def->tooltip = L("Distance of the nozzle tip to the lower rod. " - "Used for collision avoidance in by-object printing."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(40)); + def->enum_labels.emplace_back(L("No-brim")); + + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnum(btAutoBrim)); + + def = this->add("brim_object_gap", coFloat); + def->label = L("Brim-object gap"); + def->category = L("Support"); + def->tooltip = L("A gap between innermost brim line and object can make brim be removed more easily"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 2; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("compatible_printers", coStrings); + def->label = L("Compatible machine"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + + // BBS. + def = this->add("upward_compatible_machine", coStrings); + def->label = L("upward compatible machine"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("compatible_printers_condition", coString); + def->label = L("Compatible machine condition"); + // def->tooltip = L("A boolean expression using the configuration values of an active printer profile. " + // "If this expression evaluates to true, this profile is considered compatible " + // "with the active printer profile."); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionString()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("compatible_prints", coStrings); + def->label = L("Compatible process profiles"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("compatible_prints_condition", coString); + def->label = L("Compatible process profiles condition"); + // def->tooltip = L("A boolean expression using the configuration values of an active print profile. " + // "If this expression evaluates to true, this profile is considered compatible " + // "with the active print profile."); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionString()); + def->cli = ConfigOptionDef::nocli; + + // The following value is to be stored into the project file (AMF, 3MF, Config ...) + // and it contains a sum of "compatible_printers_condition" values over the print and filament profiles. + def = this->add("compatible_machine_expression_group", coStrings); + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + def = this->add("compatible_process_expression_group", coStrings); + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + + // BBS: add logic for checking between different system presets + def = this->add("different_settings_to_system", coStrings); + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("print_compatible_printers", coStrings); + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("print_sequence", coEnum); + def->label = L("Print sequence"); + def->tooltip = L("Print sequence, layer by layer or object by object"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("by layer"); + def->enum_values.push_back("by object"); + def->enum_labels.push_back(L("By layer")); + def->enum_labels.push_back(L("By object")); + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnum(PrintSequence::ByLayer)); + + def = this->add("slow_down_for_layer_cooling", coBools); + def->label = L("Slow printing down for better layer cooling"); + def->tooltip = L("Enable this option to slow printing speed down to make the final layer time not shorter than " + "the layer time threshold in \"Max fan speed threshold\", so that layer can be cooled for a longer time. " + "This can improve the cooling quality for needle and small details"); + def->set_default_value(new ConfigOptionBools{true}); + + def = this->add("no_slow_down_for_cooling_on_outwalls", coBools); + def->label = L("Don't slow down outer walls"); + def->tooltip = L("If enabled, this setting will ensure external perimeters are not slowed down to meet the minimum layer time. " + "This is particularly helpful in the below scenarios:\n" + "1. To avoid changes in shine when printing glossy filaments\n" + "2. To avoid changes in external wall speed which may create slight wall artifacts that appear like Z banding\n" + "3. To avoid printing at speeds which cause VFAs (fine artifacts) on the external walls"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBools{false}); + + def = this->add("default_acceleration", coFloats); + def->label = L("Normal printing"); + def->tooltip = L("The default acceleration of both normal printing and travel except initial layer"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500.0}); + + def = this->add("travel_acceleration", coFloats); + def->label = L("Travel"); + def->tooltip = L("The acceleration of travel except initial layer"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500.0}); + + def = this->add("initial_layer_travel_acceleration", coFloats); + def->label = L("Initial layer travel"); + def->tooltip = L("The acceleration of travel of initial layer"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500.0}); + + def = this->add("default_filament_profile", coStrings); + def->label = L("Default filament profile"); + def->tooltip = L("Default filament profile when switch to this machine profile"); + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("default_print_profile", coString); + def->label = L("Default process profile"); + def->tooltip = L("Default process profile when switch to this machine profile"); + def->set_default_value(new ConfigOptionString()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("activate_air_filtration", coBools); + def->label = L("Activate air filtration"); + def->tooltip = L("Activate for better air filtration"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBools{false}); + + def = this->add("during_print_exhaust_fan_speed", coInts); + def->label = L("Fan speed"); + def->tooltip = L("Speed of exhaust fan during printing.This speed will overwrite the speed in filament custom gcode"); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInts{60}); + + def = this->add("complete_print_exhaust_fan_speed", coInts); + def->label = L("Fan speed"); + def->sidetext = "%"; + def->tooltip = L("Speed of exhuast fan after printing completes"); + def->min = 0; + def->max = 100; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInts{80}); + + def = this->add("close_fan_the_first_x_layers", coInts); + def->label = L("For the first"); + def->tooltip = L("Set special cooling fan for the first certain layers. Cooling fan of the first layer used to be closed " + "to get better build plate adhesion and used for auto cooling function"); + def->sidetext = L("layers"); + def->min = 0; + def->max = 1000; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInts{1}); + + def = this->add("first_x_layer_fan_speed", coFloats); + def->label = L("Fan speed"); + def->tooltip = L("Special cooling fan speed for the first certain layers"); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("bridge_no_support", coBool); + def->label = L("Don't support bridges"); + def->category = L("Support"); + def->tooltip = L("Don't support the whole bridge area which makes support very large. " + "Bridge usually can be printing directly without support if not very long"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("thick_bridges", coBool); + def->label = L("Thick bridges"); + def->category = L("Quality"); + def->tooltip = L("If enabled, bridges are more reliable, can bridge longer distances, but may look worse. " + "If disabled, bridges look better but are reliable just for shorter bridged distances."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("max_bridge_length", coFloat); + def->label = L("Max bridge length"); + def->category = L("Support"); + def->tooltip = L("Max length of bridges that don't need support. Set it to 0 if you want all bridges to be supported, and set it to a very large value if you don't want any bridges to be supported."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(10)); + + def = this->add("machine_end_gcode", coString); + def->label = L("End G-code"); + def->tooltip = L("End G-code when finish the whole printing"); + def->multiline = true; + def->full_width = true; + def->height = 12; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n")); + + def = this->add("printing_by_object_gcode", coString); + def->label = L("Between Object Gcode"); + def->tooltip = L("Insert Gcode between objects. This parameter will only come into effect when you print your models object by object"); + def->multiline = true; + def->full_width = true; + def->height = 12; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("filament_end_gcode", coStrings); + def->label = L("End G-code"); + def->tooltip = L("End G-code when finish the printing of this filament"); + def->multiline = true; + def->full_width = true; + def->height = 120; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionStrings{" "}); + + def = this->add("ensure_vertical_shell_thickness", coEnum); + def->label = L("Ensure vertical shell thickness"); + def->category = L("Strength"); + def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness " + "(top+bottom solid layers)"); + def->mode = comAdvanced; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("disabled"); + def->enum_values.push_back("partial"); + def->enum_values.push_back("enabled"); + def->enum_labels.push_back(L("Disabled")); + def->enum_labels.push_back(L("Partial")); + def->enum_labels.push_back(L("Enabled")); + def->set_default_value(new ConfigOptionEnum(EnsureVerticalThicknessLevel::evtEnabled)); + + def = this->add("vertical_shell_speed", coFloatsOrPercents); + def->label = L("Vertical shell speed"); + def->tooltip = L("Speed for vertical shells with overhang regions. If expressed as percentage (for example: 80%) it will be calculated on" + "the internal solid infill speed above"); + def->category = L("Speed"); + def->sidetext = L("mm/s or %"); + def->ratio_over = "internal_solid_infill_speed"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(80, true)}); + + def = this->add("detect_floating_vertical_shell", coBool); + def->label = L("Detect floating vertical shells"); + def->tooltip = L("Detect overhang paths in vertical shells and slow them by bridge speed."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool{true}); + + def = this->add("internal_bridge_support_thickness", coFloat); + def->label = L("Internal bridge support thickness"); + def->category = L("Strength"); + def->tooltip = L("When sparse infill density is low, the internal solid infill or internal bridge may have no archor at the end of line. " + "This causes falling and bad quality when printing internal solid infill. " + "When enable this feature, loop paths will be added to the sparse fill of the lower layers for specific thickness, so that better archor can be provided for internal bridge. " + "0 means disable this feature"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 2; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + auto def_top_fill_pattern = def = this->add("top_surface_pattern", coEnum); + def->label = L("Top surface pattern"); + def->category = L("Strength"); + def->tooltip = L("Line pattern of top surface infill"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("concentric"); + def->enum_values.push_back("zig-zag"); + def->enum_values.push_back("monotonic"); + def->enum_values.push_back("monotonicline"); + def->enum_values.push_back("alignedrectilinear"); + def->enum_values.push_back("hilbertcurve"); + def->enum_values.push_back("archimedeanchords"); + def->enum_values.push_back("octagramspiral"); + def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Monotonic")); + def->enum_labels.push_back(L("Monotonic line")); + def->enum_labels.push_back(L("Aligned Rectilinear")); + def->enum_labels.push_back(L("Hilbert Curve")); + def->enum_labels.push_back(L("Archimedean Chords")); + def->enum_labels.push_back(L("Octagram Spiral")); + def->set_default_value(new ConfigOptionEnum(ipRectilinear)); + + def = this->add("bottom_surface_pattern", coEnum); + def->label = L("Bottom surface pattern"); + def->category = L("Strength"); + def->tooltip = L("Line pattern of bottom surface infill, not bridge infill"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = def_top_fill_pattern->enum_values; + def->enum_labels = def_top_fill_pattern->enum_labels; + def->set_default_value(new ConfigOptionEnum(ipRectilinear)); + + def = this->add("internal_solid_infill_pattern", coEnum); + def->label = L("Internal solid infill pattern"); + def->category = L("Strength"); + def->tooltip = L("Line pattern of internal solid infill. if the detect narrow internal solid infill be enabled, the concentric pattern will be used for the small area."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = def_top_fill_pattern->enum_values; + def->enum_labels = def_top_fill_pattern->enum_labels; + def->set_default_value(new ConfigOptionEnum(ipRectilinear)); + + def = this->add("outer_wall_line_width", coFloat); + def->label = L("Outer wall"); + def->category = L("Quality"); + def->tooltip = L("Line width of outer wall"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("outer_wall_speed", coFloats); + def->label = L("Outer wall"); + def->category = L("Speed"); + def->tooltip = L("Speed of outer wall which is outermost and visible. " + "It's used to be slower than inner wall speed to get better quality."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{60}); + + def = this->add("small_perimeter_speed", coFloatsOrPercents); + def->label = L("Small perimeters"); + def->category = L("Speed"); + def->tooltip = L("This setting will affect the speed of perimeters having radius <= small perimeter threshold" + "(usually holes). If expressed as percentage (for example: 80%) it will be calculated on" + "the outer wall speed setting above. Set to zero for auto."); + def->sidetext = L("mm/s or %"); + def->ratio_over = "outer_wall_speed"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(50, true)}); + + def = this->add("small_perimeter_threshold", coFloats); + def->label = L("Small perimeter threshold"); + def->category = L("Speed"); + def->tooltip = L("This sets the threshold for small perimeter length. Default threshold is 0mm"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("wall_sequence", coEnum); + def->label = L("Order of walls"); + def->category = L("Quality"); + def->tooltip = L("Print sequence of inner wall and outer wall. "); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("inner wall/outer wall"); + def->enum_values.push_back("outer wall/inner wall"); + def->enum_values.push_back("inner-outer-inner wall"); + def->enum_values.push_back("inner-outer-inner-inner wall"); + def->enum_labels.push_back(L("inner/outer")); + def->enum_labels.push_back(L("outer/inner")); + def->enum_labels.push_back(L("inner wall/outer wall/inner wall")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(WallSequence::InnerOuter)); + + def = this->add("is_outer_second", coBool); + def->label = L("Print outer wall second"); + def->tooltip = L("Order of wall. true means the outer wall will be printed after the first inner wall, false means outer wall will be second last printed wall. "); + def->category = L("Quality"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool{false}); + + def = this->add("is_infill_first", coBool); + def->label = L("Print infill first"); + def->tooltip = L("Order of wall/infill. false means print wall first. "); + def->category = L("Quality"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool{false}); + + def = this->add("extruder", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Extruder"); + def->category = L("Extruders"); + // def->tooltip = L("The extruder to use (unless more specific extruder settings are specified). " + // "This value overrides perimeter and infill extruders, but not the support extruders."); + def->min = 0; // 0 = inherit defaults + def->enum_labels.push_back(L("default")); // override label for item 0 + def->enum_labels.push_back("1"); + def->enum_labels.push_back("2"); + def->enum_labels.push_back("3"); + def->enum_labels.push_back("4"); + def->enum_labels.push_back("5"); + def->mode = comDevelop; + + def = this->add("extruder_clearance_height_to_rod", coFloat); + def->label = L("Height to rod"); + def->tooltip = L("Distance of the nozzle tip to the lower rod. " + "Used for collision avoidance in by-object printing."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(40)); - // BBS - def = this->add("extruder_clearance_height_to_lid", coFloat); - def->label = L("Height to lid"); - def->tooltip = L("Distance of the nozzle tip to the lid. " - "Used for collision avoidance in by-object printing."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(120)); - - def = this->add("extruder_clearance_dist_to_rod", coFloat); - def->label = L("Distance to rod"); - def->tooltip = L("Horizontal distance of the nozzle tip to the rod's farther edge. Used for collision avoidance in by-object printing."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(40)); - - def = this->add("nozzle_height", coFloat); - def->label = L("Nozzle height"); - def->tooltip = L("The height of nozzle tip."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(4)); - - def = this->add("extruder_clearance_max_radius", coFloat); - def->label = L("Max Radius"); - def->tooltip = L("Max clearance radius around extruder. Used for collision avoidance in by-object printing."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(68)); - - def = this->add("grab_length",coFloats); - def->label = L("Grab length"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloats({0})); - - def = this->add("extruder_colour", coStrings); - def->label = L("Extruder Color"); - def->tooltip = L("Only used as a visual help on UI"); - def->gui_type = ConfigOptionDef::GUIType::color; - // Empty string means no color assigned yet. - def->mode = comDevelop; - def->set_default_value(new ConfigOptionStrings { "" }); - - def = this->add("extruder_offset", coPoints); - def->label = L("Extruder offset"); - //def->tooltip = L("If your firmware doesn't handle the extruder displacement you need the G-code " - // "to take it into account. This option lets you specify the displacement of each extruder " - // "with respect to the first one. It expects positive coordinates (they will be subtracted " - // "from the XY coordinate)."); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->min = -5; - def->max = 5; - def->set_default_value(new ConfigOptionPoints { Vec2d(0,0) }); - - def = this->add("filament_flow_ratio", coFloats); - def->label = L("Flow ratio"); - def->tooltip = L("The material may have volumetric change after switching between molten state and crystalline state. " - "This setting changes all extrusion flow of this filament in gcode proportionally. " - "Recommended value range is between 0.95 and 1.05. " - "Maybe you can tune this value to get nice flat surface when there has slight overflow or underflow"); - def->max = 2; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 1. }); - - def = this->add("print_flow_ratio", coFloat); - def->label = L("Object flow ratio"); - def->tooltip = L("The flow ratio set by object, the meaning is the same as flow ratio."); - def->mode = comDevelop; - def->max = 2; - def->min = 0.01; - def->set_default_value(new ConfigOptionFloat(1)); - - def = this->add("enable_pressure_advance", coBools); - def->label = L("Enable pressure advance"); - def->tooltip = L("Enable pressure advance, auto calibration result will be overwriten once enabled. Useless for Bambu Printer"); - def->set_default_value(new ConfigOptionBools{ false }); - - def = this->add("pressure_advance", coFloats); - def->label = L("Pressure advance"); - def->tooltip = L("Pressure advance(Klipper) AKA Linear advance factor(Marlin). Useless for Bambu Printer"); - def->max = 2; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{ 0.02 }); - - - def = this->add("filament_notes",coString); - def->label= L("Filament notes"); - def->tooltip = L("You can put your notes regarding the filament here."); - def->multiline = true; - def->full_width = true; - def->height = 13; - def->mode =comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("process_notes",coString); - def->label= L("Process notes"); - def->tooltip = L("You can put your notes regarding the process here."); - def->multiline =true; - def->full_width = true; - def->height = 13; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("printer_notes",coString); - def->label = L("Printer notes"); - def->tooltip = L("You can put your notes regarding the printer here."); - def->multiline = true; - def->full_width=true; - def->height = 13; - def->mode=comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("line_width", coFloat); - def->label = L("Default"); - def->category = L("Quality"); - def->tooltip = L("Default line width if some line width is set to be zero"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("reduce_fan_stop_start_freq", coBools); - def->label = L("Keep fan always on"); - def->tooltip = L("If enable this setting, part cooling fan will never be stopped and will run at least " - "at minimum speed to reduce the frequency of starting and stopping"); - def->set_default_value(new ConfigOptionBools { false }); - - def = this->add("fan_cooling_layer_time", coInts); - def->label = L("Layer time"); - def->tooltip = L("Part cooling fan will be enabled for layers of which estimated time is shorter than this value. " - "Fan speed is interpolated between the minimum and maximum fan speeds according to layer printing time"); - def->sidetext = L("s"); - def->min = 0; - def->max = 1000; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInts { 60 }); - - def = this->add("default_filament_colour", coStrings); - def->label = L("Default color"); - def->tooltip = L("Default filament color"); - def->gui_type = ConfigOptionDef::GUIType::color; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionStrings{""}); - - def = this->add("filament_colour", coStrings); - def->label = L("Color"); - def->tooltip = L("Only used as a visual help on UI"); - def->gui_type = ConfigOptionDef::GUIType::color; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionStrings{ "#00AE42" }); - - - def = this->add("filament_multi_colour", coStrings); - def->set_default_value(new ConfigOptionStrings{""}); - - // 0: gradient color, 1: default color(single or multi color) - def = this->add("filament_colour_type", coStrings); - def->set_default_value(new ConfigOptionStrings{"1"}); // Init as default color - - //bbs - def = this->add("required_nozzle_HRC", coInts); - def->label = L("Required nozzle HRC"); - def->tooltip = L("Minimum HRC of nozzle required to print the filament. Zero means no checking of nozzle's HRC."); - def->min = 0; - def->max = 500; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts{0}); - - def = this->add("filament_map", coInts); - def->label = L("Filament map to extruder"); - def->tooltip = L("Filament map to extruder"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts{1}); - - def = this->add("filament_map_2", coInts); - def->label = "Filament map plus for multi nozzle"; - def->tooltip = "Filament map to the index identified by extruder and nozzle_volume_type"; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts{1}); - - def = this->add("filament_volume_map", coInts); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts{(int)(NozzleVolumeType::nvtStandard)}); - - - def = this->add("physical_extruder_map",coInts); - def->label = "Map the logical extruder to physical extruder"; - def->tooltip = "Map the logical extruder to physical extruder"; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts{0}); - - def = this->add("filament_map_mode", coEnum); - def->label = L("filament mapping mode"); - def->tooltip = ("filament mapping mode used as plate param"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("Auto For Flush"); - def->enum_values.push_back("Auto For Match"); - def->enum_values.push_back("Manual"); - def->enum_values.push_back("Nozzle Manual"); - def->enum_values.push_back("Default"); - def->enum_labels.push_back(L("Auto For Flush")); - def->enum_labels.push_back(L("Auto For Match")); - def->enum_labels.push_back(L("Manual")); - def->enum_labels.push_back(L("Default")); - def->enum_labels.push_back(L("Nozzle Manual")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(fmmAutoForFlush)); - - def = this->add("filament_nozzle_map",coInts); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts{1}); - - def = this->add("filament_flush_temp", coInts); - def->label = L("Flush temperature"); - def->tooltip = L("temperature when flushing filament. 0 indicates the upper bound of the recommended nozzle temperature range"); - def->mode = comAdvanced; - def->nullable = true; - def->min = 0; - def->max = max_temp; - def->sidetext = "°C"; - def->set_default_value(new ConfigOptionIntsNullable{0}); - - def = this->add("filament_flush_volumetric_speed", coFloats); - def->label = L("Flush volumetric speed"); - def->tooltip = L("Volumetric speed when flushing filament. 0 indicates the max volumetric speed"); - def->mode = comAdvanced; - def->nullable = true; - def->min = 0; - def->max = 200; - def->sidetext = L("mm³/s"); - def->set_default_value(new ConfigOptionFloatsNullable{ 0 }); - - def = this->add("filament_max_volumetric_speed", coFloats); - def->label = L("Max volumetric speed"); - def->tooltip = L("This setting stands for how much volume of filament can be melted and extruded per second. " - "Printing speed is limited by max volumetric speed, in case of too high and unreasonable speed setting. " - "Can't be zero"); - def->sidetext = L("mm³/s"); - def->min = 0; - def->max = 200; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 2. }); - - def = this->add("filament_ramming_volumetric_speed", coFloats); - def->label = L("Extruder change"); - def->tooltip = L("The maximum volumetric speed for ramming before extruder change, where -1 means using the maximum volumetric speed."); - def->sidetext = L("mm³/s"); - def->min = -1; - def->max = 200; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{-1}); - - def = this->add("filament_ramming_volumetric_speed_nc", coFloats); - def->label = L("Hotend change"); - def->tooltip = L("The maximum volumetric speed for ramming before a hotend change, where -1 means using the maximum volumetric speed."); - def->sidetext = L("mm³/s"); - def->min = -1; - def->max = 200; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{-1}); - - - def = this->add("filament_minimal_purge_on_wipe_tower", coFloats); - def->label = L("Minimal purge on wipe tower"); - //def->tooltip = L("After a tool change, the exact position of the newly loaded filament inside " - // "the nozzle may not be known, and the filament pressure is likely not yet stable. " - // "Before purging the print head into an infill or a sacrificial object, Slic3r will always prime " - // "this amount of material into the wipe tower to produce successive infill or sacrificial object extrusions reliably."); - def->sidetext = L("mm³"); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloats { 15. }); - - def = this->add("machine_load_filament_time", coFloat); - def->label = L("Filament load time"); - def->tooltip = L("Time to load new filament when switch filament. For statistics only"); - def->sidetext = L("s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.0)); - - def = this->add("machine_unload_filament_time", coFloat); - def->label = L("Filament unload time"); - def->tooltip = L("Time to unload old filament when switch filament. For statistics only"); - def->sidetext = L("s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.0)); - - def = this->add("machine_switch_extruder_time", coFloat); - def->label = L("Extruder switch time"); - def->tooltip = L("Time to switch extruder. For statistics only"); - def->min = 0; - def->sidetext = L("s"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(5)); - - def = this->add("machine_hotend_change_time", coFloat); - def->label = L("Hotend change time"); - def->tooltip = L("Time to change hotend."); - def->sidetext = L("s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.0)); - - def = this->add("group_algo_with_time", coBool); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("machine_prepare_compensation_time", coFloat); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(260)); - - def = this->add("hotend_cooling_rate", coFloats); - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{2}); - - def = this->add("hotend_heating_rate", coFloats); - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{2}); - - def = this->add("enable_pre_heating", coBool); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("support_object_skip_flush", coBool); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("bed_temperature_formula", coEnum); - def->label = L("Bed temperature type"); - def->tooltip = L("This option determines how the bed temperature is set during slicing: based on the temperature of the first filament or the highest temperature of the printed filaments."); - def->mode = comDevelop; - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("by_first_filament"); - def->enum_values.push_back("by_highest_temp"); - def->enum_labels.push_back(L("By First filament")); - def->enum_labels.push_back(L("By Highest Temp")); - def->set_default_value(new ConfigOptionEnum(BedTempFormula::btfFirstFilament)); - - def = this->add("nozzle_flush_dataset", coInts); - def->nullable = true; - def->set_default_value(new ConfigOptionIntsNullable{0}); - - def = this->add("filament_diameter", coFloats); - def->label = L("Diameter"); - def->tooltip = L("Filament diameter is used to calculate extrusion in gcode, so it's important and should be accurate"); - def->sidetext = L("mm"); - def->min = 0; - def->set_default_value(new ConfigOptionFloats { 1.75 }); - - def = this->add("filament_adaptive_volumetric_speed", coBools); - def->label = L("Adaptive volumetric speed"); - def->tooltip = L("When enabled, the extrusion flow is limited by the smaller of " - "the fitted value (calculated from line width and layer height) and the user-defined maximum flow." - " When disabled, only the user-defined maximum flow is applied."); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionBoolsNullable {false}); - - def = this->add("volumetric_speed_coefficients", coStrings); - def->label = L("Max volumetric speed multinomial coefficients"); - def->set_default_value(new ConfigOptionStrings{""}); - - def = this->add("filament_shrink", coPercents); - def->label = L("Shrinkage"); - // xgettext:no-c-format, no-boost-format - def->tooltip = L("Enter the shrinkage percentage that the filament will get after cooling (94% if you measure 94mm instead of 100mm)." - " The part will be scaled in xy to compensate." - " Only the filament used for the perimeter is taken into account." - "\nBe sure to allow enough space between objects, as this compensation is done after the checks."); - def->sidetext = L("%"); - def->ratio_over = ""; - def->min = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercents{ 100 }); - - def = this->add("filament_velocity_adaptation_factor", coFloats); - def->label = L("Velocity Adaptation Factor"); - def->min = 0; - def->tooltip = L("This parameter reflects the speed at which a material transitions from one state to another. " - "It, along with the smooth coefficient, determines the final length of the transition zone. " - "A larger value: requires a shorter transition zone. " - "A smaller value: requires a longer transition zone to avoid flow instability."); - def->set_default_value(new ConfigOptionFloats{1.0}); - - def = this->add("filament_adhesiveness_category", coInts); - def->label = L("Adhesiveness Category"); - def->tooltip = L("Filament category"); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts{0}); - - def = this->add("filament_density", coFloats); - def->label = L("Density"); - def->tooltip = L("Filament density. For statistics only"); - def->sidetext = L("g/cm³"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 0. }); - - def = this->add("filament_type", coStrings); - def->label = L("Type"); - def->tooltip = L("The material type of filament"); - def->gui_type = ConfigOptionDef::GUIType::f_enum_open; - def->gui_flags = "show_value"; - def->enum_values.push_back("PLA"); - def->enum_values.push_back("ABS"); - def->enum_values.push_back("ASA"); - def->enum_values.push_back("ASA-CF"); - def->enum_values.push_back("PETG"); - def->enum_values.push_back("PCTG"); - def->enum_values.push_back("TPU"); - def->enum_values.push_back("TPU-AMS"); - def->enum_values.push_back("PC"); - def->enum_values.push_back("PA"); - def->enum_values.push_back("PA-CF"); - def->enum_values.push_back("PA-GF"); - def->enum_values.push_back("PA6-CF"); - def->enum_values.push_back("PLA-CF"); - def->enum_values.push_back("PET-CF"); - def->enum_values.push_back("PETG-CF"); - def->enum_values.push_back("PVA"); - def->enum_values.push_back("HIPS"); - def->enum_values.push_back("PLA-AERO"); - def->enum_values.push_back("PPS"); - def->enum_values.push_back("PPS-CF"); - def->enum_values.push_back("PPA-CF"); - def->enum_values.push_back("PPA-GF"); - def->enum_values.push_back("ABS-GF"); - def->enum_values.push_back("ASA-AERO"); - def->enum_values.push_back("PE"); - def->enum_values.push_back("PP"); - def->enum_values.push_back("EVA"); - def->enum_values.push_back("PHA"); - def->enum_values.push_back("BVOH"); - def->enum_values.push_back("PE-CF"); - def->enum_values.push_back("PP-CF"); - def->enum_values.push_back("PP-GF"); - - def->mode = comSimple; - def->set_default_value(new ConfigOptionStrings { "PLA" }); - - def = this->add("filament_soluble", coBools); - def->label = L("Soluble material"); - def->tooltip = L("Soluble material is commonly used to print support and support interface"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBools { false }); - - def = this->add("filament_scarf_seam_type", coEnums); - def->label = L("Scarf seam type"); - def->tooltip = L("Set scarf seam type for this filament. This setting could minimize seam visibiliy."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("none"); - def->enum_values.push_back("external"); - def->enum_values.push_back("all"); - def->enum_labels.push_back(L("None")); - def->enum_labels.push_back(L("Contour")); - def->enum_labels.push_back(L("Contour and hole")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnumsGeneric{0}); - - def = this->add("filament_scarf_height", coFloatsOrPercents); - def->label = L("Scarf start height"); - def->tooltip = L("This amount can be specified in millimeters or as a percentage of the current layer height."); - def->min = 0; - def->ratio_over = "layer_height"; - def->sidetext = L("mm/%"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatsOrPercents{FloatOrPercent(10, true)}); - - def = this->add("filament_scarf_gap", coFloatsOrPercents); - def->label = L("Scarf slope gap"); - def->tooltip = L("In order to reduce the visiblity of the seam in closed loop, the inner wall and outer wall are shortened by a specified amount."); - def->min = 0; - def->ratio_over = "nozzle_diameter"; - def->sidetext = L("mm/%"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatsOrPercents{FloatOrPercent(0, 0)}); - - def = this->add("filament_scarf_length", coFloats); - def->label = L("Scarf length"); - def->tooltip = L("Length of the scarf. Setting this parameter to zero effectively disables the scarf."); - def->min = 0; - def->sidetext = "mm"; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{10}); - - def = this->add("filament_change_length", coFloats); - def->label = L("Extruder change"); - def->tooltip = L("When changing the extruder, it is recommended to extrude a certain length of filament from the original extruder. This helps minimize nozzle oozing."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{10}); - - def = this->add("filament_change_length_nc", coFloats); - def->label = L("Hotend change"); - def->tooltip = L("When changing the hotend, it is recommended to extrude a certain length of filament from the original nozzle. This helps minimize nozzle oozing."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{10}); - - def = this->add("filament_is_support", coBools); - def->label = L("Support material"); - def->tooltip = L("Support material is commonly used to print support and support interface"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBools{false}); - - // defined in bits - // 0 means cannot support, 1 means support - // 0 bit: can support in left extruder - // 1 bit: can support in right extruder - def = this->add("filament_printable", coInts); - def->label = L("Filament printable"); - def->tooltip = L("The filament is printable in extruder"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts{3}); + // BBS + def = this->add("extruder_clearance_height_to_lid", coFloat); + def->label = L("Height to lid"); + def->tooltip = L("Distance of the nozzle tip to the lid. " + "Used for collision avoidance in by-object printing."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(120)); + + def = this->add("extruder_clearance_dist_to_rod", coFloat); + def->label = L("Distance to rod"); + def->tooltip = L("Horizontal distance of the nozzle tip to the rod's farther edge. Used for collision avoidance in by-object printing."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(40)); + + def = this->add("nozzle_height", coFloat); + def->label = L("Nozzle height"); + def->tooltip = L("The height of nozzle tip."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(4)); + + def = this->add("extruder_clearance_max_radius", coFloat); + def->label = L("Max Radius"); + def->tooltip = L("Max clearance radius around extruder. Used for collision avoidance in by-object printing."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(68)); + + def = this->add("grab_length", coFloats); + def->label = L("Grab length"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloats({0})); + + def = this->add("extruder_colour", coStrings); + def->label = L("Extruder Color"); + def->tooltip = L("Only used as a visual help on UI"); + def->gui_type = ConfigOptionDef::GUIType::color; + // Empty string means no color assigned yet. + def->mode = comDevelop; + def->set_default_value(new ConfigOptionStrings{""}); + + def = this->add("extruder_offset", coPoints); + def->label = L("Extruder offset"); + // def->tooltip = L("If your firmware doesn't handle the extruder displacement you need the G-code " + // "to take it into account. This option lets you specify the displacement of each extruder " + // "with respect to the first one. It expects positive coordinates (they will be subtracted " + // "from the XY coordinate)."); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = -5; + def->max = 5; + def->set_default_value(new ConfigOptionPoints{Vec2d(0, 0)}); + + def = this->add("filament_flow_ratio", coFloats); + def->label = L("Flow ratio"); + def->tooltip = L("The material may have volumetric change after switching between molten state and crystalline state. " + "This setting changes all extrusion flow of this filament in gcode proportionally. " + "Recommended value range is between 0.95 and 1.05. " + "Maybe you can tune this value to get nice flat surface when there has slight overflow or underflow"); + def->max = 2; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{1.}); + + def = this->add("print_flow_ratio", coFloat); + def->label = L("Object flow ratio"); + def->tooltip = L("The flow ratio set by object, the meaning is the same as flow ratio."); + def->mode = comDevelop; + def->max = 2; + def->min = 0.01; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add("enable_pressure_advance", coBools); + def->label = L("Enable pressure advance"); + def->tooltip = L("Enable pressure advance, auto calibration result will be overwriten once enabled. Useless for Bambu Printer"); + def->set_default_value(new ConfigOptionBools{false}); + + def = this->add("pressure_advance", coFloats); + def->label = L("Pressure advance"); + def->tooltip = L("Pressure advance(Klipper) AKA Linear advance factor(Marlin). Useless for Bambu Printer"); + def->max = 2; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{0.02}); + + def = this->add("filament_notes", coString); + def->label = L("Filament notes"); + def->tooltip = L("You can put your notes regarding the filament here."); + def->multiline = true; + def->full_width = true; + def->height = 13; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("process_notes", coString); + def->label = L("Process notes"); + def->tooltip = L("You can put your notes regarding the process here."); + def->multiline = true; + def->full_width = true; + def->height = 13; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("printer_notes", coString); + def->label = L("Printer notes"); + def->tooltip = L("You can put your notes regarding the printer here."); + def->multiline = true; + def->full_width = true; + def->height = 13; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("line_width", coFloat); + def->label = L("Default"); + def->category = L("Quality"); + def->tooltip = L("Default line width if some line width is set to be zero"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("reduce_fan_stop_start_freq", coBools); + def->label = L("Keep fan always on"); + def->tooltip = L("If enable this setting, part cooling fan will never be stopped and will run at least " + "at minimum speed to reduce the frequency of starting and stopping"); + def->set_default_value(new ConfigOptionBools{false}); + + def = this->add("fan_cooling_layer_time", coInts); + def->label = L("Layer time"); + def->tooltip = L("Part cooling fan will be enabled for layers of which estimated time is shorter than this value. " + "Fan speed is interpolated between the minimum and maximum fan speeds according to layer printing time"); + def->sidetext = L("s"); + def->min = 0; + def->max = 1000; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInts{60}); + + def = this->add("default_filament_colour", coStrings); + def->label = L("Default color"); + def->tooltip = L("Default filament color"); + def->gui_type = ConfigOptionDef::GUIType::color; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionStrings{""}); + + def = this->add("filament_colour", coStrings); + def->label = L("Color"); + def->tooltip = L("Only used as a visual help on UI"); + def->gui_type = ConfigOptionDef::GUIType::color; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionStrings{"#00AE42"}); + + def = this->add("filament_multi_colour", coStrings); + def->set_default_value(new ConfigOptionStrings{""}); + + // 0: gradient color, 1: default color(single or multi color) + def = this->add("filament_colour_type", coStrings); + def->set_default_value(new ConfigOptionStrings{"1"}); // Init as default color + + // bbs + def = this->add("required_nozzle_HRC", coInts); + def->label = L("Required nozzle HRC"); + def->tooltip = L("Minimum HRC of nozzle required to print the filament. Zero means no checking of nozzle's HRC."); + def->min = 0; + def->max = 500; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("filament_map", coInts); + def->label = L("Filament map to extruder"); + def->tooltip = L("Filament map to extruder"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{1}); + + def = this->add("filament_map_2", coInts); + def->label = "Filament map plus for multi nozzle"; + def->tooltip = "Filament map to the index identified by extruder and nozzle_volume_type"; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{1}); + + def = this->add("filament_volume_map", coInts); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{(int)(NozzleVolumeType::nvtStandard)}); + + def = this->add("physical_extruder_map", coInts); + def->label = "Map the logical extruder to physical extruder"; + def->tooltip = "Map the logical extruder to physical extruder"; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("filament_map_mode", coEnum); + def->label = L("filament mapping mode"); + def->tooltip = ("filament mapping mode used as plate param"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("Auto For Flush"); + def->enum_values.push_back("Auto For Match"); + def->enum_values.push_back("Manual"); + def->enum_values.push_back("Nozzle Manual"); + def->enum_values.push_back("Default"); + def->enum_labels.push_back(L("Auto For Flush")); + def->enum_labels.push_back(L("Auto For Match")); + def->enum_labels.push_back(L("Manual")); + def->enum_labels.push_back(L("Default")); + def->enum_labels.push_back(L("Nozzle Manual")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(fmmAutoForFlush)); + + def = this->add("filament_nozzle_map", coInts); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{1}); + + def = this->add("filament_flush_temp", coInts); + def->label = L("Flush temperature"); + def->tooltip = L("temperature when flushing filament. 0 indicates the upper bound of the recommended nozzle temperature range"); + def->mode = comAdvanced; + def->nullable = true; + def->min = 0; + def->max = max_temp; + def->sidetext = "°C"; + def->set_default_value(new ConfigOptionIntsNullable{0}); + + def = this->add("filament_flush_volumetric_speed", coFloats); + def->label = L("Flush volumetric speed"); + def->tooltip = L("Volumetric speed when flushing filament. 0 indicates the max volumetric speed"); + def->mode = comAdvanced; + def->nullable = true; + def->min = 0; + def->max = 200; + def->sidetext = L("mm³/s"); + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("filament_max_volumetric_speed", coFloats); + def->label = L("Max volumetric speed"); + def->tooltip = L("This setting stands for how much volume of filament can be melted and extruded per second. " + "Printing speed is limited by max volumetric speed, in case of too high and unreasonable speed setting. " + "Can't be zero"); + def->sidetext = L("mm³/s"); + def->min = 0; + def->max = 200; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{2.}); + + def = this->add("filament_ramming_volumetric_speed", coFloats); + def->label = L("Extruder change"); + def->tooltip = L("The maximum volumetric speed for ramming before extruder change, where -1 means using the maximum volumetric speed."); + def->sidetext = L("mm³/s"); + def->min = -1; + def->max = 200; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{-1}); + + def = this->add("filament_ramming_volumetric_speed_nc", coFloats); + def->label = L("Hotend change"); + def->tooltip = L("The maximum volumetric speed for ramming before a hotend change, where -1 means using the maximum volumetric speed."); + def->sidetext = L("mm³/s"); + def->min = -1; + def->max = 200; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{-1}); + + def = this->add("filament_minimal_purge_on_wipe_tower", coFloats); + def->label = L("Minimal purge on wipe tower"); + // def->tooltip = L("After a tool change, the exact position of the newly loaded filament inside " + // "the nozzle may not be known, and the filament pressure is likely not yet stable. " + // "Before purging the print head into an infill or a sacrificial object, Slic3r will always prime " + // "this amount of material into the wipe tower to produce successive infill or sacrificial object extrusions reliably."); + def->sidetext = L("mm³"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloats{15.}); + + def = this->add("machine_load_filament_time", coFloat); + def->label = L("Filament load time"); + def->tooltip = L("Time to load new filament when switch filament. For statistics only"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("machine_unload_filament_time", coFloat); + def->label = L("Filament unload time"); + def->tooltip = L("Time to unload old filament when switch filament. For statistics only"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("machine_switch_extruder_time", coFloat); + def->label = L("Extruder switch time"); + def->tooltip = L("Time to switch extruder. For statistics only"); + def->min = 0; + def->sidetext = L("s"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5)); + + def = this->add("machine_hotend_change_time", coFloat); + def->label = L("Hotend change time"); + def->tooltip = L("Time to change hotend."); + def->sidetext = L("s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("group_algo_with_time", coBool); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("machine_prepare_compensation_time", coFloat); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(260)); + + def = this->add("hotend_cooling_rate", coFloats); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{2}); + + def = this->add("hotend_heating_rate", coFloats); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{2}); + + def = this->add("enable_pre_heating", coBool); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("support_object_skip_flush", coBool); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("bed_temperature_formula", coEnum); + def->label = L("Bed temperature type"); + def->tooltip = L("This option determines how the bed temperature is set during slicing: based on the temperature of the first filament or the highest temperature of the printed filaments."); + def->mode = comDevelop; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("by_first_filament"); + def->enum_values.push_back("by_highest_temp"); + def->enum_labels.push_back(L("By First filament")); + def->enum_labels.push_back(L("By Highest Temp")); + def->set_default_value(new ConfigOptionEnum(BedTempFormula::btfFirstFilament)); + + def = this->add("nozzle_flush_dataset", coInts); + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable{0}); + + def = this->add("filament_diameter", coFloats); + def->label = L("Diameter"); + def->tooltip = L("Filament diameter is used to calculate extrusion in gcode, so it's important and should be accurate"); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloats{1.75}); + + def = this->add("filament_adaptive_volumetric_speed", coBools); + def->label = L("Adaptive volumetric speed"); + def->tooltip = L("When enabled, the extrusion flow is limited by the smaller of " + "the fitted value (calculated from line width and layer height) and the user-defined maximum flow." + " When disabled, only the user-defined maximum flow is applied."); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable{false}); + + def = this->add("volumetric_speed_coefficients", coStrings); + def->label = L("Max volumetric speed multinomial coefficients"); + def->set_default_value(new ConfigOptionStrings{""}); + + def = this->add("filament_shrink", coPercents); + def->label = L("Shrinkage"); + // xgettext:no-c-format, no-boost-format + def->tooltip = L("Enter the shrinkage percentage that the filament will get after cooling (94% if you measure 94mm instead of 100mm)." + " The part will be scaled in xy to compensate." + " Only the filament used for the perimeter is taken into account." + "\nBe sure to allow enough space between objects, as this compensation is done after the checks."); + def->sidetext = L("%"); + def->ratio_over = ""; + def->min = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercents{100}); + + def = this->add("filament_velocity_adaptation_factor", coFloats); + def->label = L("Velocity Adaptation Factor"); + def->min = 0; + def->tooltip = L("This parameter reflects the speed at which a material transitions from one state to another. " + "It, along with the smooth coefficient, determines the final length of the transition zone. " + "A larger value: requires a shorter transition zone. " + "A smaller value: requires a longer transition zone to avoid flow instability."); + def->set_default_value(new ConfigOptionFloats{1.0}); + + def = this->add("filament_adhesiveness_category", coInts); + def->label = L("Adhesiveness Category"); + def->tooltip = L("Filament category"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("filament_density", coFloats); + def->label = L("Density"); + def->tooltip = L("Filament density. For statistics only"); + def->sidetext = L("g/cm³"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{0.}); + + def = this->add("filament_type", coStrings); + def->label = L("Type"); + def->tooltip = L("The material type of filament"); + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; + def->gui_flags = "show_value"; + def->enum_values.push_back("PLA"); + def->enum_values.push_back("ABS"); + def->enum_values.push_back("ASA"); + def->enum_values.push_back("ASA-CF"); + def->enum_values.push_back("PETG"); + def->enum_values.push_back("PCTG"); + def->enum_values.push_back("TPU"); + def->enum_values.push_back("TPU-AMS"); + def->enum_values.push_back("PC"); + def->enum_values.push_back("PA"); + def->enum_values.push_back("PA-CF"); + def->enum_values.push_back("PA-GF"); + def->enum_values.push_back("PA6-CF"); + def->enum_values.push_back("PLA-CF"); + def->enum_values.push_back("PET-CF"); + def->enum_values.push_back("PETG-CF"); + def->enum_values.push_back("PVA"); + def->enum_values.push_back("HIPS"); + def->enum_values.push_back("PLA-AERO"); + def->enum_values.push_back("PPS"); + def->enum_values.push_back("PPS-CF"); + def->enum_values.push_back("PPA-CF"); + def->enum_values.push_back("PPA-GF"); + def->enum_values.push_back("ABS-GF"); + def->enum_values.push_back("ASA-AERO"); + def->enum_values.push_back("PE"); + def->enum_values.push_back("PP"); + def->enum_values.push_back("EVA"); + def->enum_values.push_back("PHA"); + def->enum_values.push_back("BVOH"); + def->enum_values.push_back("PE-CF"); + def->enum_values.push_back("PP-CF"); + def->enum_values.push_back("PP-GF"); + + def->mode = comSimple; + def->set_default_value(new ConfigOptionStrings{"PLA"}); + + def = this->add("filament_soluble", coBools); + def->label = L("Soluble material"); + def->tooltip = L("Soluble material is commonly used to print support and support interface"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBools{false}); + + def = this->add("filament_scarf_seam_type", coEnums); + def->label = L("Scarf seam type"); + def->tooltip = L("Set scarf seam type for this filament. This setting could minimize seam visibiliy."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("none"); + def->enum_values.push_back("external"); + def->enum_values.push_back("all"); + def->enum_labels.push_back(L("None")); + def->enum_labels.push_back(L("Contour")); + def->enum_labels.push_back(L("Contour and hole")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnumsGeneric{0}); + + def = this->add("filament_scarf_height", coFloatsOrPercents); + def->label = L("Scarf start height"); + def->tooltip = L("This amount can be specified in millimeters or as a percentage of the current layer height."); + def->min = 0; + def->ratio_over = "layer_height"; + def->sidetext = L("mm/%"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatsOrPercents{FloatOrPercent(10, true)}); + + def = this->add("filament_scarf_gap", coFloatsOrPercents); + def->label = L("Scarf slope gap"); + def->tooltip = L("In order to reduce the visiblity of the seam in closed loop, the inner wall and outer wall are shortened by a specified amount."); + def->min = 0; + def->ratio_over = "nozzle_diameter"; + def->sidetext = L("mm/%"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatsOrPercents{FloatOrPercent(0, 0)}); + + def = this->add("filament_scarf_length", coFloats); + def->label = L("Scarf length"); + def->tooltip = L("Length of the scarf. Setting this parameter to zero effectively disables the scarf."); + def->min = 0; + def->sidetext = "mm"; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{10}); + + def = this->add("filament_change_length", coFloats); + def->label = L("Extruder change"); + def->tooltip = L("When changing the extruder, it is recommended to extrude a certain length of filament from the original extruder. This helps minimize nozzle oozing."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{10}); + + def = this->add("filament_change_length_nc", coFloats); + def->label = L("Hotend change"); + def->tooltip = L("When changing the hotend, it is recommended to extrude a certain length of filament from the original nozzle. This helps minimize nozzle oozing."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{10}); + + def = this->add("filament_is_support", coBools); + def->label = L("Support material"); + def->tooltip = L("Support material is commonly used to print support and support interface"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBools{false}); + + // defined in bits + // 0 means cannot support, 1 means support + // 0 bit: can support in left extruder + // 1 bit: can support in right extruder + def = this->add("filament_printable", coInts); + def->label = L("Filament printable"); + def->tooltip = L("The filament is printable in extruder"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{3}); - // BBS - def = this->add("filament_prime_volume", coFloats); - def->label = L("Filament change"); - //def->tooltip = L("The volume of material to prime extruder on tower."); - def->tooltip = L("The volume of material required to prime the extruder on the tower, excluding a hotend change."); - def->sidetext = L("mm³"); - def->min = 1.0; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloats{45.}); + // BBS + def = this->add("filament_prime_volume", coFloats); + def->label = L("Filament change"); + // def->tooltip = L("The volume of material to prime extruder on tower."); + def->tooltip = L("The volume of material required to prime the extruder on the tower, excluding a hotend change."); + def->sidetext = L("mm³"); + def->min = 1.0; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloats{45.}); - // BBS - def = this->add("filament_prime_volume_nc", coFloats); - def->label = L("Hotend change"); - def->tooltip = L("The volume of material required to prime the extruder for a hotend change on the tower."); - def->sidetext = L("mm³"); - def->min = 1.0; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloats{60.}); - - def = this->add("filament_cooling_before_tower", coFloats); - def->label = L("Wipe tower cooling"); - def->tooltip = L("Temperature drop before entering filament tower"); - def->sidetext = "°C"; - def->mode = comDevelop; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{10}); + // BBS + def = this->add("filament_prime_volume_nc", coFloats); + def->label = L("Hotend change"); + def->tooltip = L("The volume of material required to prime the extruder for a hotend change on the tower."); + def->sidetext = L("mm³"); + def->min = 1.0; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloats{60.}); + + def = this->add("filament_cooling_before_tower", coFloats); + def->label = L("Wipe tower cooling"); + def->tooltip = L("Temperature drop before entering filament tower"); + def->sidetext = "°C"; + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{10}); - // BBS - def = this->add("temperature_vitrification", coInts); - def->label = L("Softening temperature"); - def->tooltip = L("The material softens at this temperature, so when the bed temperature is equal to or greater than it, it's highly recommended to open the front door and/or remove the upper glass to avoid cloggings."); - def->mode = comSimple; - def->set_default_value(new ConfigOptionInts{ 100 }); - - def = this->add("filament_ramming_travel_time", coFloats); - def->label = L("Extruder change"); - def->tooltip = L("To prevent oozing, the nozzle will perform a reverse travel movement for a certain period after " - "the ramming is complete. The setting define the travel time."); - def->mode = comAdvanced; - def->sidetext = "s"; - def->min = 0; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0}); - - def = this->add("filament_ramming_travel_time_nc", coFloats); - def->label = L("Hotend change"); - def->tooltip = L("To prevent oozing, the nozzle will perform a reverse travel movement for a certain period after " - "the ramming is complete. The setting define the travel time."); - def->mode = comAdvanced; - def->sidetext = "s"; - def->min = 0; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0}); - - def = this->add("filament_pre_cooling_temperature", coInts); - def->label = L("Extruder change"); - def->tooltip = L("To prevent oozing, the nozzle temperature will be cooled during ramming. Therefore, the ramming time must be greater than the cooldown time. 0 means disabled."); - //def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->mode = comAdvanced; - def->sidetext = "°C"; - def->min = 0; - //def->enum_values.push_back("-1"); - //def->enum_labels.push_back(L("None")); - def->nullable = true; - def->set_default_value(new ConfigOptionIntsNullable{0}); - - def = this->add("filament_pre_cooling_temperature_nc", coInts); - def->label = L("Hotend change"); - def->tooltip = L( - "To prevent oozing, the nozzle temperature will be cooled during ramming. Note: only a cooldown command and fan activation are triggered, reaching the target temperature is not guaranteed. 0 means disabled."); - def->mode = comAdvanced; - def->sidetext = "°C"; - def->min = 0; - def->nullable = true; - def->set_default_value(new ConfigOptionIntsNullable{0}); - - def = this->add("filament_cost", coFloats); - def->label = L("Price"); - def->tooltip = L("Filament price. For statistics only"); - def->sidetext = L("money/kg"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 0. }); - - def = this->add("filament_settings_id", coStrings); - def->set_default_value(new ConfigOptionStrings { "" }); - //BBS: open this option to command line - //def->cli = ConfigOptionDef::nocli; - - def = this->add("filament_ids", coStrings); - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("filament_vendor", coStrings); - def->label = L("Vendor"); - def->tooltip = L("Vendor of filament. For show only"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionStrings{L("(Undefined)")}); - def->cli = ConfigOptionDef::nocli; - - def = this->add("infill_direction", coFloat); - def->label = L("Infill direction"); - def->category = L("Strength"); - def->tooltip = L("Angle for sparse infill pattern, which controls the start or main direction of line"); - def->sidetext = L("°"); - def->min = 0; - def->max = 360; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(45)); - - def = this->add("sparse_infill_density", coPercent); - def->label = L("Sparse infill density"); - def->category = L("Strength"); - def->tooltip = L("Density of internal sparse infill, 100% means solid throughout"); - def->sidetext = "%"; - def->min = 0; - def->max = 100; - def->set_default_value(new ConfigOptionPercent(20)); - - def = this->add("fill_multiline", coInt); - def->label = L("Fill multiline"); - def->category = L("Strength"); - def->tooltip = L("Using multiple lines for the infill pattern, if supported by infill pattern."); - def->min = 1; - def->max = 5; - def->set_default_value(new ConfigOptionInt(1)); - - def = this->add("sparse_infill_pattern", coEnum); - def->label = L("Sparse infill pattern"); - def->category = L("Strength"); - def->tooltip = L("Line pattern for internal sparse infill"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("concentric"); - def->enum_values.push_back("zig-zag"); - def->enum_values.push_back("grid"); - def->enum_values.push_back("line"); - def->enum_values.push_back("cubic"); - def->enum_values.push_back("triangles"); - def->enum_values.push_back("tri-hexagon"); - def->enum_values.push_back("gyroid"); - def->enum_values.push_back("honeycomb"); - def->enum_values.push_back("adaptivecubic"); - def->enum_values.push_back("alignedrectilinear"); - def->enum_values.push_back("3dhoneycomb"); - def->enum_values.push_back("hilbertcurve"); - def->enum_values.push_back("archimedeanchords"); - def->enum_values.push_back("octagramspiral"); - def->enum_values.push_back("supportcubic"); - def->enum_values.push_back("lightning"); - def->enum_values.push_back("crosshatch"); - def->enum_values.push_back("zigzag"); - def->enum_values.push_back("crosszag"); - def->enum_values.push_back("lockedzag"); - def->enum_labels.push_back(L("Concentric")); - def->enum_labels.push_back(L("Rectilinear")); - def->enum_labels.push_back(L("Grid")); - def->enum_labels.push_back(L("Line")); - def->enum_labels.push_back(L("Cubic")); - def->enum_labels.push_back(L("Triangles")); - def->enum_labels.push_back(L("Tri-hexagon")); - def->enum_labels.push_back(L("Gyroid")); - def->enum_labels.push_back(L("Honeycomb")); - def->enum_labels.push_back(L("Adaptive Cubic")); - def->enum_labels.push_back(L("Aligned Rectilinear")); - def->enum_labels.push_back(L("3D Honeycomb")); - def->enum_labels.push_back(L("Hilbert Curve")); - def->enum_labels.push_back(L("Archimedean Chords")); - def->enum_labels.push_back(L("Octagram Spiral")); - def->enum_labels.push_back(L("Support Cubic")); - def->enum_labels.push_back(L("Lightning")); - def->enum_labels.push_back(L("Cross Hatch")); - def->enum_labels.push_back(L("Zig Zag")); - def->enum_labels.push_back(L("Cross Zag")); - def->enum_labels.push_back(L("Locked Zag")); - def->set_default_value(new ConfigOptionEnum(ipCubic)); - - def = this->add("locked_skin_infill_pattern", coEnum); - def->label = L("Skin infill pattern"); - def->category = L("Strength"); - def->tooltip = L("Line pattern for skin"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("concentric"); - def->enum_values.push_back("zig-zag"); - def->enum_values.push_back("grid"); - def->enum_values.push_back("line"); - def->enum_values.push_back("cubic"); - def->enum_values.push_back("triangles"); - def->enum_values.push_back("tri-hexagon"); - def->enum_values.push_back("gyroid"); - def->enum_values.push_back("honeycomb"); - def->enum_values.push_back("alignedrectilinear"); - def->enum_values.push_back("3dhoneycomb"); - def->enum_values.push_back("hilbertcurve"); - def->enum_values.push_back("archimedeanchords"); - def->enum_values.push_back("octagramspiral"); - def->enum_values.push_back("crosshatch"); - def->enum_values.push_back("zigzag"); - def->enum_values.push_back("crosszag"); - def->enum_labels.push_back(L("Concentric")); - def->enum_labels.push_back(L("Rectilinear")); - def->enum_labels.push_back(L("Grid")); - def->enum_labels.push_back(L("Line")); - def->enum_labels.push_back(L("Cubic")); - def->enum_labels.push_back(L("Triangles")); - def->enum_labels.push_back(L("Tri-hexagon")); - def->enum_labels.push_back(L("Gyroid")); - def->enum_labels.push_back(L("Honeycomb")); - def->enum_labels.push_back(L("Aligned Rectilinear")); - def->enum_labels.push_back(L("3D Honeycomb")); - def->enum_labels.push_back(L("Hilbert Curve")); - def->enum_labels.push_back(L("Archimedean Chords")); - def->enum_labels.push_back(L("Octagram Spiral")); - def->enum_labels.push_back(L("Cross Hatch")); - def->enum_labels.push_back(L("Zig Zag")); - def->enum_labels.push_back(L("Cross Zag")); - def->set_default_value(new ConfigOptionEnum(ipCrossZag)); - - def = this->add("locked_skeleton_infill_pattern", coEnum); - def->label = L("Skeleton infill pattern"); - def->category = L("Strength"); - def->tooltip = L("Line pattern for skeleton"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("concentric"); - def->enum_values.push_back("zig-zag"); - def->enum_values.push_back("grid"); - def->enum_values.push_back("line"); - def->enum_values.push_back("cubic"); - def->enum_values.push_back("triangles"); - def->enum_values.push_back("tri-hexagon"); - def->enum_values.push_back("gyroid"); - def->enum_values.push_back("honeycomb"); - def->enum_values.push_back("alignedrectilinear"); - def->enum_values.push_back("3dhoneycomb"); - def->enum_values.push_back("hilbertcurve"); - def->enum_values.push_back("archimedeanchords"); - def->enum_values.push_back("octagramspiral"); - def->enum_values.push_back("crosshatch"); - def->enum_values.push_back("zigzag"); - def->enum_values.push_back("crosszag"); - def->enum_labels.push_back(L("Concentric")); - def->enum_labels.push_back(L("Rectilinear")); - def->enum_labels.push_back(L("Grid")); - def->enum_labels.push_back(L("Line")); - def->enum_labels.push_back(L("Cubic")); - def->enum_labels.push_back(L("Triangles")); - def->enum_labels.push_back(L("Tri-hexagon")); - def->enum_labels.push_back(L("Gyroid")); - def->enum_labels.push_back(L("Honeycomb")); - def->enum_labels.push_back(L("Aligned Rectilinear")); - def->enum_labels.push_back(L("3D Honeycomb")); - def->enum_labels.push_back(L("Hilbert Curve")); - def->enum_labels.push_back(L("Archimedean Chords")); - def->enum_labels.push_back(L("Octagram Spiral")); - def->enum_labels.push_back(L("Cross Hatch")); - def->enum_labels.push_back(L("Zig Zag")); - def->enum_labels.push_back(L("Cross Zag")); - def->set_default_value(new ConfigOptionEnum(ipZigZag)); - - def = this->add("top_surface_acceleration", coFloats); - def->label = L("Top surface"); - def->tooltip = L("Acceleration of top surface infill. Using a lower value may improve top surface quality"); - def->sidetext = "mm/s²"; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{500}); - - def = this->add("outer_wall_acceleration", coFloats); - def->label = L("Outer wall"); - def->tooltip = L("Acceleration of outer wall. Using a lower value can improve quality"); - def->sidetext = "mm/s²"; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{500}); - - def = this->add("inner_wall_acceleration", coFloats); - def->label = L("Inner wall"); - def->tooltip = L("Acceleration of inner walls. 0 means using normal printing acceleration"); - def->sidetext = "mm/s²"; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0}); - - def = this->add("sparse_infill_acceleration", coFloatsOrPercents); - def->label = L("Sparse infill"); - def->tooltip = L("Acceleration of sparse infill. If the value is expressed as a percentage (e.g. 100%), it will be calculated based on the default acceleration."); - def->sidetext = L("mm/s² or %"); - def->min = 0; - def->mode = comAdvanced; - def->ratio_over = "default_acceleration"; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(100, true)}); - - def = this->add("initial_layer_acceleration", coFloats); - def->label = L("Initial layer"); - def->tooltip = L("Acceleration of initial layer. Using a lower value can improve build plate adhensive"); - def->sidetext = "mm/s²"; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{300}); - - def = this->add("accel_to_decel_enable", coBool); - def->label = L("Enable accel_to_decel"); - def->tooltip = L("Klipper's max_accel_to_decel will be adjusted automatically"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("accel_to_decel_factor", coPercent); - def->label = L("accel_to_decel"); - def->tooltip = L("Klipper's max_accel_to_decel will be adjusted to this percent of acceleration"); - def->sidetext = "%"; - def->min = 1; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(50)); - - def = this->add("default_jerk", coFloat); - def->label = L("Default"); - def->tooltip = L("Default jerk"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("outer_wall_jerk", coFloat); - def->label = L("Outer wall"); - def->tooltip = L("Jerk of outer walls"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(9)); - - def = this->add("inner_wall_jerk", coFloat); - def->label = L("Inner wall"); - def->tooltip = L("Jerk of inner walls"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(9)); - - def = this->add("infill_jerk", coFloat); - def->label = L("Infill"); - def->tooltip = L("Jerk of infill"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(9)); - - def = this->add("top_surface_jerk", coFloat); - def->label = L("Top surface"); - def->tooltip = L("Jerk of top surface"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(9)); - - def = this->add("initial_layer_jerk", coFloat); - def->label = L("First layer"); - def->tooltip = L("Jerk of first layer"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(9)); - - def = this->add("travel_jerk", coFloat); - def->label = L("Travel"); - def->tooltip = L("Jerk of travel"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(9)); - - def = this->add("initial_layer_line_width", coFloat); - def->label = L("Initial layer"); - def->category = L("Quality"); - def->tooltip = L("Line width of initial layer"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("initial_layer_print_height", coFloat); - def->label = L("Initial layer height"); - def->category = L("Quality"); - def->tooltip = L("Height of initial layer. Making initial layer height thick slightly can improve build plate adhension"); - def->sidetext = L("mm"); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(0.2)); - - //def = this->add("adaptive_layer_height", coBool); - //def->label = L("Adaptive layer height"); - //def->category = L("Quality"); - //def->tooltip = L("Enabling this option means the height of every layer except the first will be automatically calculated " - // "during slicing according to the slope of the model’s surface.\n" - // "Note that this option only takes effect if no prime tower is generated in current plate."); - //def->set_default_value(new ConfigOptionBool(0)); - - def = this->add("initial_layer_speed", coFloats); - def->label = L("Initial layer"); - def->tooltip = L("Speed of initial layer except the solid infill part"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{30}); - - def = this->add("initial_layer_infill_speed", coFloats); - def->label = L("Initial layer infill"); - def->tooltip = L("Speed of solid infill part of initial layer"); - def->sidetext = L("mm/s"); - def->min = 1; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{60.0}); - - def = this->add("nozzle_temperature_initial_layer", coInts); - def->label = L("Initial layer"); - def->full_label = L("Initial layer nozzle temperature"); - def->tooltip = L("Nozzle temperature to print initial layer when using this filament"); - def->sidetext = "°C"; - def->min = 0; - def->max = max_temp; - def->nullable = true; - def->set_default_value(new ConfigOptionIntsNullable { 200 }); - - def = this->add("full_fan_speed_layer", coInts); - def->label = L("Full fan speed at layer"); - //def->tooltip = L("Fan speed will be ramped up linearly from zero at layer \"close_fan_the_first_x_layers\" " - // "to maximum at layer \"full_fan_speed_layer\". " - // "\"full_fan_speed_layer\" will be ignored if lower than \"close_fan_the_first_x_layers\", in which case " - // "the fan will be running at maximum allowed speed at layer \"close_fan_the_first_x_layers\" + 1."); - def->min = 0; - def->max = 1000; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts { 0 }); - - def = this->add("fuzzy_skin", coEnum); - def->label = L("Fuzzy Skin"); - def->category = L("Others"); - def->tooltip = L("Randomly jitter while printing the wall, so that the surface has a rough look. This setting controls " - "the fuzzy position"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("none"); - def->enum_values.push_back("external"); - def->enum_values.push_back("all"); - def->enum_values.push_back("allwalls"); - def->enum_values.push_back("disabled_fuzzy"); - def->enum_labels.push_back(L("None(allow paint)")); - def->enum_labels.push_back(L("Contour")); - def->enum_labels.push_back(L("Contour and hole")); - def->enum_labels.push_back(L("All walls")); - def->enum_labels.push_back(L("Disabled")); - def->mode = comSimple; - def->set_default_value(new ConfigOptionEnum(FuzzySkinType::None)); - - def = this->add("fuzzy_skin_thickness", coFloat); - def->label = L("Fuzzy skin thickness"); - def->category = L("Others"); - def->tooltip = L("The width within which to jitter. It's adversed to be below outer wall line width"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 1; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(0.3)); - - def = this->add("fuzzy_skin_point_distance", coFloat); - def->label = L("Fuzzy skin point distance"); - def->category = L("Others"); - def->tooltip = L("The average distance between the random points introduced on each line segment"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 5; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(0.8)); - - def = this->add("filter_out_gap_fill", coFloat); - def->label = L("Filter out tiny gaps"); - def->sidetext = L("mm"); - def->tooltip = L("Filter out gaps smaller than the threshold specified. Gaps smaller than this threshold will be ignored"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("precise_outer_wall", coBool); - def->label = L("Precise wall"); - def->category = L("Quality"); - def->tooltip = L("Improve shell precision by adjusting outer wall spacing. This also improves layer consistency."); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool{false}); - - def = this->add("gap_infill_speed", coFloats); - def->label = L("Gap infill"); - def->category = L("Speed"); - def->tooltip = L("Speed of gap infill. Gap usually has irregular line width and should be printed more slowly"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{30}); + // BBS + def = this->add("temperature_vitrification", coInts); + def->label = L("Softening temperature"); + def->tooltip = L("The material softens at this temperature, so when the bed temperature is equal to or greater than it, it's highly recommended to open the front door and/or remove the upper glass to avoid cloggings."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionInts{100}); + + def = this->add("filament_ramming_travel_time", coFloats); + def->label = L("Extruder change"); + def->tooltip = L("To prevent oozing, the nozzle will perform a reverse travel movement for a certain period after " + "the ramming is complete. The setting define the travel time."); + def->mode = comAdvanced; + def->sidetext = "s"; + def->min = 0; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("filament_ramming_travel_time_nc", coFloats); + def->label = L("Hotend change"); + def->tooltip = L("To prevent oozing, the nozzle will perform a reverse travel movement for a certain period after " + "the ramming is complete. The setting define the travel time."); + def->mode = comAdvanced; + def->sidetext = "s"; + def->min = 0; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("filament_pre_cooling_temperature", coInts); + def->label = L("Extruder change"); + def->tooltip = L("To prevent oozing, the nozzle temperature will be cooled during ramming. Therefore, the ramming time must be greater than the cooldown time. 0 means disabled."); + // def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->mode = comAdvanced; + def->sidetext = "°C"; + def->min = 0; + // def->enum_values.push_back("-1"); + // def->enum_labels.push_back(L("None")); + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable{0}); + + def = this->add("filament_pre_cooling_temperature_nc", coInts); + def->label = L("Hotend change"); + def->tooltip = L( + "To prevent oozing, the nozzle temperature will be cooled during ramming. Note: only a cooldown command and fan activation are triggered, reaching the target temperature is not guaranteed. 0 means disabled."); + def->mode = comAdvanced; + def->sidetext = "°C"; + def->min = 0; + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable{0}); + + def = this->add("filament_cost", coFloats); + def->label = L("Price"); + def->tooltip = L("Filament price. For statistics only"); + def->sidetext = L("money/kg"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{0.}); + + def = this->add("filament_settings_id", coStrings); + def->set_default_value(new ConfigOptionStrings{""}); + // BBS: open this option to command line + // def->cli = ConfigOptionDef::nocli; + + def = this->add("filament_ids", coStrings); + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("filament_vendor", coStrings); + def->label = L("Vendor"); + def->tooltip = L("Vendor of filament. For show only"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionStrings{L("(Undefined)")}); + def->cli = ConfigOptionDef::nocli; + + def = this->add("infill_direction", coFloat); + def->label = L("Infill direction"); + def->category = L("Strength"); + def->tooltip = L("Angle for sparse infill pattern, which controls the start or main direction of line"); + def->sidetext = L("°"); + def->min = 0; + def->max = 360; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(45)); + + def = this->add("sparse_infill_density", coPercent); + def->label = L("Sparse infill density"); + def->category = L("Strength"); + def->tooltip = L("Density of internal sparse infill, 100% means solid throughout"); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->set_default_value(new ConfigOptionPercent(20)); + + def = this->add("fill_multiline", coInt); + def->label = L("Fill multiline"); + def->category = L("Strength"); + def->tooltip = L("Using multiple lines for the infill pattern, if supported by infill pattern."); + def->min = 1; + def->max = 5; + def->set_default_value(new ConfigOptionInt(1)); + + def = this->add("sparse_infill_pattern", coEnum); + def->label = L("Sparse infill pattern"); + def->category = L("Strength"); + def->tooltip = L("Line pattern for internal sparse infill"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("concentric"); + def->enum_values.push_back("zig-zag"); + def->enum_values.push_back("grid"); + def->enum_values.push_back("line"); + def->enum_values.push_back("cubic"); + def->enum_values.push_back("triangles"); + def->enum_values.push_back("tri-hexagon"); + def->enum_values.push_back("gyroid"); + def->enum_values.push_back("honeycomb"); + def->enum_values.push_back("adaptivecubic"); + def->enum_values.push_back("alignedrectilinear"); + def->enum_values.push_back("3dhoneycomb"); + def->enum_values.push_back("hilbertcurve"); + def->enum_values.push_back("archimedeanchords"); + def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("supportcubic"); + def->enum_values.push_back("lightning"); + def->enum_values.push_back("crosshatch"); + def->enum_values.push_back("zigzag"); + def->enum_values.push_back("crosszag"); + def->enum_values.push_back("lockedzag"); + def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Grid")); + def->enum_labels.push_back(L("Line")); + def->enum_labels.push_back(L("Cubic")); + def->enum_labels.push_back(L("Triangles")); + def->enum_labels.push_back(L("Tri-hexagon")); + def->enum_labels.push_back(L("Gyroid")); + def->enum_labels.push_back(L("Honeycomb")); + def->enum_labels.push_back(L("Adaptive Cubic")); + def->enum_labels.push_back(L("Aligned Rectilinear")); + def->enum_labels.push_back(L("3D Honeycomb")); + def->enum_labels.push_back(L("Hilbert Curve")); + def->enum_labels.push_back(L("Archimedean Chords")); + def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Support Cubic")); + def->enum_labels.push_back(L("Lightning")); + def->enum_labels.push_back(L("Cross Hatch")); + def->enum_labels.push_back(L("Zig Zag")); + def->enum_labels.push_back(L("Cross Zag")); + def->enum_labels.push_back(L("Locked Zag")); + def->set_default_value(new ConfigOptionEnum(ipCubic)); + + def = this->add("locked_skin_infill_pattern", coEnum); + def->label = L("Skin infill pattern"); + def->category = L("Strength"); + def->tooltip = L("Line pattern for skin"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("concentric"); + def->enum_values.push_back("zig-zag"); + def->enum_values.push_back("grid"); + def->enum_values.push_back("line"); + def->enum_values.push_back("cubic"); + def->enum_values.push_back("triangles"); + def->enum_values.push_back("tri-hexagon"); + def->enum_values.push_back("gyroid"); + def->enum_values.push_back("honeycomb"); + def->enum_values.push_back("alignedrectilinear"); + def->enum_values.push_back("3dhoneycomb"); + def->enum_values.push_back("hilbertcurve"); + def->enum_values.push_back("archimedeanchords"); + def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("crosshatch"); + def->enum_values.push_back("zigzag"); + def->enum_values.push_back("crosszag"); + def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Grid")); + def->enum_labels.push_back(L("Line")); + def->enum_labels.push_back(L("Cubic")); + def->enum_labels.push_back(L("Triangles")); + def->enum_labels.push_back(L("Tri-hexagon")); + def->enum_labels.push_back(L("Gyroid")); + def->enum_labels.push_back(L("Honeycomb")); + def->enum_labels.push_back(L("Aligned Rectilinear")); + def->enum_labels.push_back(L("3D Honeycomb")); + def->enum_labels.push_back(L("Hilbert Curve")); + def->enum_labels.push_back(L("Archimedean Chords")); + def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Cross Hatch")); + def->enum_labels.push_back(L("Zig Zag")); + def->enum_labels.push_back(L("Cross Zag")); + def->set_default_value(new ConfigOptionEnum(ipCrossZag)); + + def = this->add("locked_skeleton_infill_pattern", coEnum); + def->label = L("Skeleton infill pattern"); + def->category = L("Strength"); + def->tooltip = L("Line pattern for skeleton"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("concentric"); + def->enum_values.push_back("zig-zag"); + def->enum_values.push_back("grid"); + def->enum_values.push_back("line"); + def->enum_values.push_back("cubic"); + def->enum_values.push_back("triangles"); + def->enum_values.push_back("tri-hexagon"); + def->enum_values.push_back("gyroid"); + def->enum_values.push_back("honeycomb"); + def->enum_values.push_back("alignedrectilinear"); + def->enum_values.push_back("3dhoneycomb"); + def->enum_values.push_back("hilbertcurve"); + def->enum_values.push_back("archimedeanchords"); + def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("crosshatch"); + def->enum_values.push_back("zigzag"); + def->enum_values.push_back("crosszag"); + def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Grid")); + def->enum_labels.push_back(L("Line")); + def->enum_labels.push_back(L("Cubic")); + def->enum_labels.push_back(L("Triangles")); + def->enum_labels.push_back(L("Tri-hexagon")); + def->enum_labels.push_back(L("Gyroid")); + def->enum_labels.push_back(L("Honeycomb")); + def->enum_labels.push_back(L("Aligned Rectilinear")); + def->enum_labels.push_back(L("3D Honeycomb")); + def->enum_labels.push_back(L("Hilbert Curve")); + def->enum_labels.push_back(L("Archimedean Chords")); + def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Cross Hatch")); + def->enum_labels.push_back(L("Zig Zag")); + def->enum_labels.push_back(L("Cross Zag")); + def->set_default_value(new ConfigOptionEnum(ipZigZag)); + + def = this->add("top_surface_acceleration", coFloats); + def->label = L("Top surface"); + def->tooltip = L("Acceleration of top surface infill. Using a lower value may improve top surface quality"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500}); + + def = this->add("outer_wall_acceleration", coFloats); + def->label = L("Outer wall"); + def->tooltip = L("Acceleration of outer wall. Using a lower value can improve quality"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500}); + + def = this->add("inner_wall_acceleration", coFloats); + def->label = L("Inner wall"); + def->tooltip = L("Acceleration of inner walls. 0 means using normal printing acceleration"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("sparse_infill_acceleration", coFloatsOrPercents); + def->label = L("Sparse infill"); + def->tooltip = L("Acceleration of sparse infill. If the value is expressed as a percentage (e.g. 100%), it will be calculated based on the default acceleration."); + def->sidetext = L("mm/s² or %"); + def->min = 0; + def->mode = comAdvanced; + def->ratio_over = "default_acceleration"; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(100, true)}); + + def = this->add("initial_layer_acceleration", coFloats); + def->label = L("Initial layer"); + def->tooltip = L("Acceleration of initial layer. Using a lower value can improve build plate adhensive"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{300}); + + def = this->add("accel_to_decel_enable", coBool); + def->label = L("Enable accel_to_decel"); + def->tooltip = L("Klipper's max_accel_to_decel will be adjusted automatically"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("accel_to_decel_factor", coPercent); + def->label = L("accel_to_decel"); + def->tooltip = L("Klipper's max_accel_to_decel will be adjusted to this percent of acceleration"); + def->sidetext = "%"; + def->min = 1; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(50)); + + def = this->add("default_jerk", coFloat); + def->label = L("Default"); + def->tooltip = L("Default jerk"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("outer_wall_jerk", coFloat); + def->label = L("Outer wall"); + def->tooltip = L("Jerk of outer walls"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(9)); + + def = this->add("inner_wall_jerk", coFloat); + def->label = L("Inner wall"); + def->tooltip = L("Jerk of inner walls"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(9)); + + def = this->add("infill_jerk", coFloat); + def->label = L("Infill"); + def->tooltip = L("Jerk of infill"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(9)); + + def = this->add("top_surface_jerk", coFloat); + def->label = L("Top surface"); + def->tooltip = L("Jerk of top surface"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(9)); + + def = this->add("initial_layer_jerk", coFloat); + def->label = L("First layer"); + def->tooltip = L("Jerk of first layer"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(9)); + + def = this->add("travel_jerk", coFloat); + def->label = L("Travel"); + def->tooltip = L("Jerk of travel"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(9)); + + def = this->add("initial_layer_line_width", coFloat); + def->label = L("Initial layer"); + def->category = L("Quality"); + def->tooltip = L("Line width of initial layer"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("initial_layer_print_height", coFloat); + def->label = L("Initial layer height"); + def->category = L("Quality"); + def->tooltip = L("Height of initial layer. Making initial layer height thick slightly can improve build plate adhension"); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.2)); + + // def = this->add("adaptive_layer_height", coBool); + // def->label = L("Adaptive layer height"); + // def->category = L("Quality"); + // def->tooltip = L("Enabling this option means the height of every layer except the first will be automatically calculated " + // "during slicing according to the slope of the model’s surface.\n" + // "Note that this option only takes effect if no prime tower is generated in current plate."); + // def->set_default_value(new ConfigOptionBool(0)); + + def = this->add("initial_layer_speed", coFloats); + def->label = L("Initial layer"); + def->tooltip = L("Speed of initial layer except the solid infill part"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{30}); + + def = this->add("initial_layer_infill_speed", coFloats); + def->label = L("Initial layer infill"); + def->tooltip = L("Speed of solid infill part of initial layer"); + def->sidetext = L("mm/s"); + def->min = 1; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{60.0}); + + def = this->add("nozzle_temperature_initial_layer", coInts); + def->label = L("Initial layer"); + def->full_label = L("Initial layer nozzle temperature"); + def->tooltip = L("Nozzle temperature to print initial layer when using this filament"); + def->sidetext = "°C"; + def->min = 0; + def->max = max_temp; + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable{200}); + + def = this->add("full_fan_speed_layer", coInts); + def->label = L("Full fan speed at layer"); + // def->tooltip = L("Fan speed will be ramped up linearly from zero at layer \"close_fan_the_first_x_layers\" " + // "to maximum at layer \"full_fan_speed_layer\". " + // "\"full_fan_speed_layer\" will be ignored if lower than \"close_fan_the_first_x_layers\", in which case " + // "the fan will be running at maximum allowed speed at layer \"close_fan_the_first_x_layers\" + 1."); + def->min = 0; + def->max = 1000; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("fuzzy_skin", coEnum); + def->label = L("Fuzzy Skin"); + def->category = L("Others"); + def->tooltip = L("Randomly jitter while printing the wall, so that the surface has a rough look. This setting controls " + "the fuzzy position"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("none"); + def->enum_values.push_back("external"); + def->enum_values.push_back("all"); + def->enum_values.push_back("allwalls"); + def->enum_values.push_back("disabled_fuzzy"); + def->enum_labels.push_back(L("None(allow paint)")); + def->enum_labels.push_back(L("Contour")); + def->enum_labels.push_back(L("Contour and hole")); + def->enum_labels.push_back(L("All walls")); + def->enum_labels.push_back(L("Disabled")); + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnum(FuzzySkinType::None)); + + def = this->add("fuzzy_skin_thickness", coFloat); + def->label = L("Fuzzy skin thickness"); + def->category = L("Others"); + def->tooltip = L("The width within which to jitter. It's adversed to be below outer wall line width"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 1; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(0.3)); + + def = this->add("fuzzy_skin_point_distance", coFloat); + def->label = L("Fuzzy skin point distance"); + def->category = L("Others"); + def->tooltip = L("The average distance between the random points introduced on each line segment"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 5; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(0.8)); + + def = this->add("filter_out_gap_fill", coFloat); + def->label = L("Filter out tiny gaps"); + def->sidetext = L("mm"); + def->tooltip = L("Filter out gaps smaller than the threshold specified. Gaps smaller than this threshold will be ignored"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("precise_outer_wall", coBool); + def->label = L("Precise wall"); + def->category = L("Quality"); + def->tooltip = L("Improve shell precision by adjusting outer wall spacing. This also improves layer consistency."); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool{false}); + + def = this->add("gap_infill_speed", coFloats); + def->label = L("Gap infill"); + def->category = L("Speed"); + def->tooltip = L("Speed of gap infill. Gap usually has irregular line width and should be printed more slowly"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{30}); - // BBS - def = this->add("precise_z_height", coBool); - def->label = L("Precise Z height"); - def->tooltip = L("Enable this to get precise z height of object after slicing. " - "It will get the precise object height by fine-tuning the layer heights of the last few layers. " - "Note that this is an experimental parameter."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(0)); + // BBS + def = this->add("precise_z_height", coBool); + def->label = L("Precise Z height"); + def->tooltip = L("Enable this to get precise z height of object after slicing. " + "It will get the precise object height by fine-tuning the layer heights of the last few layers. " + "Note that this is an experimental parameter."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(0)); - // BBS - def = this->add("enable_arc_fitting", coBool); - def->label = L("Arc fitting"); - def->tooltip = L("Enable this to get a G-code file which has G2 and G3 moves. " - "And the fitting tolerance is the same as resolution"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(0)); - // BBS - def = this->add("gcode_add_line_number", coBool); - def->label = L("Add line number"); - def->tooltip = L("Enable this to add line number(Nx) at the beginning of each G-Code line"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(0)); + // BBS + def = this->add("enable_arc_fitting", coBool); + def->label = L("Arc fitting"); + def->tooltip = L("Enable this to get a G-code file which has G2 and G3 moves. " + "And the fitting tolerance is the same as resolution"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(0)); + // BBS + def = this->add("gcode_add_line_number", coBool); + def->label = L("Add line number"); + def->tooltip = L("Enable this to add line number(Nx) at the beginning of each G-Code line"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(0)); - // BBS - def = this->add("scan_first_layer", coBool); - def->label = L("Scan first layer"); - def->tooltip = L("Enable this to enable the camera on printer to check the quality of first layer"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(false)); + // BBS + def = this->add("scan_first_layer", coBool); + def->label = L("Scan first layer"); + def->tooltip = L("Enable this to enable the camera on printer to check the quality of first layer"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); - // BBS - def = this->add("enable_wrapping_detection", coBool); - def->label = L("Enable clumping detection"); - def->tooltip = L("Enable clumping detection"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("wrapping_detection_layers", coInt); - def->label = L("Clumping detection layers"); - def->tooltip = L("Clumping detection layers."); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInt(20)); - - def = this->add("wrapping_exclude_area", coPoints); - def->label = L("Probing exclude area of clumping"); - def->tooltip = L("Probing exclude area of clumping."); - def->mode = comAdvanced; - def->gui_type = ConfigOptionDef::GUIType::one_string; - def->set_default_value(new ConfigOptionPoints()); + // BBS + def = this->add("enable_wrapping_detection", coBool); + def->label = L("Enable clumping detection"); + def->tooltip = L("Enable clumping detection"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("wrapping_detection_layers", coInt); + def->label = L("Clumping detection layers"); + def->tooltip = L("Clumping detection layers."); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInt(20)); + + def = this->add("wrapping_exclude_area", coPoints); + def->label = L("Probing exclude area of clumping"); + def->tooltip = L("Probing exclude area of clumping."); + def->mode = comAdvanced; + def->gui_type = ConfigOptionDef::GUIType::one_string; + def->set_default_value(new ConfigOptionPoints()); - // BBS - def = this->add("thumbnail_size", coPoints); - def->label = L("Thumbnail size"); - def->tooltip = L("Decides the size of thumbnail stored in gcode files"); - def->mode = comDevelop; - def->gui_type = ConfigOptionDef::GUIType::one_string; - def->set_default_value(new ConfigOptionPoints{ Vec2d(50,50) }); - - //BBS - // def = this->add("spaghetti_detector", coBool); - // def->label = L("Enable spaghetti detector"); - // def->tooltip = L("Enable the camera on printer to check spaghetti"); - // def->mode = comSimple; - // def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("nozzle_type", coEnums); - def->label = L("Nozzle type"); - def->tooltip = L("The metallic material of nozzle. This determines the abrasive resistance of nozzle, and " - "what kind of filament can be printed"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("undefine"); - def->enum_values.push_back("hardened_steel"); - def->enum_values.push_back("stainless_steel"); - def->enum_values.push_back("tungsten_carbide"); - def->enum_values.push_back("brass"); - def->enum_labels.push_back(L("Undefine")); - def->enum_labels.push_back(L("Hardened steel")); - def->enum_labels.push_back(L("Stainless steel")); - def->enum_labels.push_back(L("Tungsten carbide")); - def->enum_labels.push_back(L("Brass")); - def->mode = comDevelop; - def->nullable = true; - def->set_default_value(new ConfigOptionEnumsGenericNullable({ ntUndefine })); - - def = this->add("printer_structure", coEnum); - def->label = L("Printer structure"); - def->tooltip = L("The physical arrangement and components of a printing device"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("undefine"); - def->enum_values.push_back("corexy"); - def->enum_values.push_back("i3"); - def->enum_values.push_back("hbot"); - def->enum_values.push_back("delta"); - def->enum_labels.push_back(L("Undefine")); - def->enum_labels.push_back("CoreXY"); - def->enum_labels.push_back("I3"); - def->enum_labels.push_back("Hbot"); - def->enum_labels.push_back("Delta"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionEnum(psUndefine)); - - def = this->add("best_object_pos", coPoint); - def->label = L("Best object position"); - def->tooltip = L("Best auto arranging position in range [0,1] w.r.t. bed shape."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPoint(Vec2d(0.5, 0.5))); - - def = this->add("auxiliary_fan", coBool); - def->label = L("Auxiliary part cooling fan"); - def->tooltip = L("Enable this option if machine has auxiliary part cooling fan"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("fan_direction", coEnum); - def->label = L("Fan direction"); - def->tooltip = L("Cooling fan direction of the printer"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("undefine"); - def->enum_values.push_back("left"); - def->enum_values.push_back("right"); - def->enum_values.push_back("both"); - def->enum_labels.push_back(L("Undefine")); - def->enum_labels.push_back(L("Left")); - def->enum_labels.push_back(L("Right")); - def->enum_labels.push_back(L("Both")); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionEnum(fdUndefine)); - - def =this->add("support_chamber_temp_control",coBool); - def->label=L("Support control chamber temperature"); - def->tooltip=L("This option is enabled if machine support controlling chamber temperature"); - def->mode=comDevelop; - def->set_default_value(new ConfigOptionBool(false)); - def->readonly=false; - - def = this->add("apply_top_surface_compensation", coBool); - def->label = L("Apply top surface compensation"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(false)); - - def =this->add("support_air_filtration",coBool); - def->label=L("Air filtration enhancement"); - def->tooltip=L("Enable this if printer support air filtration enhancement."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("support_cooling_filter", coBool); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("cooling_filter_enabled", coBool); - def->label = L("Use cooling filter"); - def->tooltip = L("Enable this if printer support cooling filter"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("auto_disable_filter_on_overheat", coBool); - def->label = L("Auto turn off filter on overheat"); - def->tooltip = L("Enable this if printer support cooling filter"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("gcode_flavor", coEnum); - def->label = L("G-code flavor"); - def->tooltip = L("What kind of gcode the printer is compatible with"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("marlin"); - def->enum_values.push_back("klipper"); - //def->enum_values.push_back("reprap"); - //def->enum_values.push_back("reprapfirmware"); - //def->enum_values.push_back("repetier"); - //def->enum_values.push_back("teacup"); - //def->enum_values.push_back("makerware"); - //def->enum_values.push_back("marlin2"); - //def->enum_values.push_back("sailfish"); - //def->enum_values.push_back("mach3"); - //def->enum_values.push_back("machinekit"); - //def->enum_values.push_back("smoothie"); - //def->enum_values.push_back("no-extrusion"); - def->enum_labels.push_back("Marlin(legacy)"); - def->enum_labels.push_back("Klipper"); - //def->enum_labels.push_back("RepRap/Sprinter"); - //def->enum_labels.push_back("RepRapFirmware"); - //def->enum_labels.push_back("Repetier"); - //def->enum_labels.push_back("Teacup"); - //def->enum_labels.push_back("MakerWare (MakerBot)"); - //def->enum_labels.push_back("Marlin 2"); - //def->enum_labels.push_back("Sailfish (MakerBot)"); - //def->enum_labels.push_back("Mach3/LinuxCNC"); - //def->enum_labels.push_back("Machinekit"); - //def->enum_labels.push_back("Smoothie"); - //def->enum_labels.push_back(L("No extrusion")); - def->mode = comAdvanced; - def->readonly = false; - def->set_default_value(new ConfigOptionEnum(gcfMarlinLegacy)); - - //OrcaSlicer - def = this->add("exclude_object", coBool); - def->label = L("Exclude objects"); - def->tooltip = L("Enable this option to add EXCLUDE OBJECT command in g-code for klipper firmware printer"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(1)); - - //BBS - def = this->add("infill_combination", coBool); - def->label = L("Infill combination"); - def->category = L("Strength"); - def->tooltip = L("Automatically Combine sparse infill of several layers to print together to reduce time. Wall is still printed " - "with original layer height."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("infill_shift_step", coFloat); - def->label = L("Infill shift step"); - def->category = L("Strength"); - def->tooltip = L("This parameter adds a slight displacement to each layer of infill to create a cross texture."); - def->sidetext = L("mm"); - def->min = 0; - def->max = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("infill_rotate_step", coFloat); - def->label = L("Infill rotate step"); - def->category = L("Strength"); - def->tooltip = L("This parameter adds a slight rotation to each layer of infill to create a cross texture."); - def->sidetext = L("°"); - def->min = 0; - def->max = 360; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("skeleton_infill_density", coPercent); - def->label = L("Skeleton infill density"); - def->category = L("Strength"); - def->tooltip = L("The remaining part of the model contour after removing a certain depth from the surface is called the skeleton. This parameter is used to adjust the density of this section." - "When two regions have the same sparse infill settings but different skeleton densities, their skeleton areas will develop overlapping sections." - "default is as same as infill density."); - def->sidetext = "%"; - def->min = 0; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(15)); - - def = this->add("skin_infill_density", coPercent); - def->label = L("Skin infill density"); - def->category = L("Strength"); - def->tooltip = L("The portion of the model's outer surface within a certain depth range is called the skin. This parameter is used to adjust the density of this section." - "When two regions have the same sparse infill settings but different skin densities, This area will not be split into two separate regions." - "default is as same as infill density."); - def->sidetext = "%"; - def->min = 0; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(15)); - - def = this->add("skin_infill_depth", coFloat); - def->label = L("Skin infill depth"); - def->category = L("Strength"); - def->tooltip = L("The parameter sets the depth of skin."); - def->sidetext = L("mm"); - def->min = 0; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(2.0)); - - def = this->add("infill_lock_depth", coFloat); - def->label = L("Infill lock depth"); - def->category = L("Strength"); - def->tooltip = L("The parameter sets the overlapping depth between the interior and skin."); - def->sidetext = L("mm"); - def->min = 0; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("skin_infill_line_width", coFloat); - def->label = L("Skin line width"); - def->category = L("Strength"); - def->tooltip = L("Adjust the line width of the selected skin paths."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("skeleton_infill_line_width", coFloat); - def->label = L("Skeleton line width"); - def->category = L("Strength"); - def->tooltip = L("Adjust the line width of the selected skeleton paths."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("symmetric_infill_y_axis", coBool); - def->label = L("Symmetric infill y axis"); - def->category = L("Strength"); - def->tooltip = L("If the model has two parts that are symmetric about the y-axis," - " and you want these parts to have symmetric textures, please click this option on one of the parts."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - auto def_infill_anchor_min = def = this->add("sparse_infill_anchor", coFloatOrPercent); - def->label = L("Length of sparse infill anchor"); - def->category = L("Strength"); - def->tooltip = L("Connect a sparse infill line to an internal perimeter with a short segment of an additional perimeter. " - "If expressed as percentage (example: 15%) it is calculated over sparse infill line width. " - "Slicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment " - "shorter than infill_anchor_max is found, the infill line is connected to a perimeter segment at just one side " - "and the length of the perimeter segment taken is limited to this parameter, but no longer than anchor_length_max. " - "Set this parameter to zero to disable anchoring perimeters connected to a single infill line."); - def->sidetext = L("mm or %"); - def->ratio_over = "sparse_infill_line_width"; - def->max_literal = 1000; - def->gui_type = ConfigOptionDef::GUIType::f_enum_open; - def->enum_values.push_back("0"); - def->enum_values.push_back("1"); - def->enum_values.push_back("2"); - def->enum_values.push_back("5"); - def->enum_values.push_back("10"); - def->enum_values.push_back("1000"); - def->enum_labels.push_back(L("0 (no open anchors)")); - def->enum_labels.push_back("1 mm"); - def->enum_labels.push_back("2 mm"); - def->enum_labels.push_back("5 mm"); - def->enum_labels.push_back("10 mm"); - def->enum_labels.push_back(L("1000 (unlimited)")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(400, true)); - - def = this->add("sparse_infill_anchor_max", coFloatOrPercent); - def->label = L("Maximum length of sparse infill anchor"); - def->category = def_infill_anchor_min->category; - def->tooltip = L("Connect a sparse infill line to an internal perimeter with a short segment of an additional perimeter. " - "If expressed as percentage (example: 15%) it is calculated over sparse infill line width. " - "Slicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment " - "shorter than this parameter is found, the infill line is connected to a perimeter segment at just one side " - "and the length of the perimeter segment taken is limited to infill_anchor, but no longer than this parameter. " - "Set this parameter to zero to disable anchoring."); - def->sidetext = def_infill_anchor_min->sidetext; - def->ratio_over = def_infill_anchor_min->ratio_over; - def->max_literal = def_infill_anchor_min->max_literal; - def->gui_type = def_infill_anchor_min->gui_type; - def->enum_values.push_back("0"); - def->enum_values.push_back("1"); - def->enum_values.push_back("2"); - def->enum_values.push_back("5"); - def->enum_values.push_back("10"); - def->enum_values.push_back("1000"); - def->enum_labels.push_back(L("0 (not anchored)")); - def->enum_labels.push_back("1 mm"); - def->enum_labels.push_back("2 mm"); - def->enum_labels.push_back("5 mm"); - def->enum_labels.push_back("10 mm"); - def->enum_labels.push_back(L("1000 (unlimited)")); - def->mode = def_infill_anchor_min->mode; - def->set_default_value(new ConfigOptionFloatOrPercent(20, false)); - - def = this->add("sparse_infill_filament", coInt); - def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Sparse infill filament"); - def->category = L("Extruders"); - def->tooltip = L("Filament to print internal sparse infill."); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInt(0)); - - def = this->add("sparse_infill_line_width", coFloat); - def->label = L("Sparse infill"); - def->category = L("Quality"); - def->tooltip = L("Line width of internal sparse infill"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("infill_wall_overlap", coPercent); - def->label = L("Infill/Wall overlap"); - def->category = L("Strength"); - def->tooltip = L("Infill area is enlarged slightly to overlap with wall for better bonding. The percentage value is relative to line width of sparse infill"); - def->sidetext = "%"; - def->ratio_over = "inner_wall_line_width"; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(15)); - - def = this->add("sparse_infill_speed", coFloats); - def->label = L("Sparse infill"); - def->category = L("Speed"); - def->tooltip = L("Speed of internal sparse infill"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{100}); - - def = this->add("inherits", coString); - //def->label = L("Inherits profile"); - def->label = "Inherits profile"; - //def->tooltip = L("Name of parent profile"); - def->tooltip = "Name of parent profile"; - def->full_width = true; - def->height = 5; - def->set_default_value(new ConfigOptionString()); - def->cli = ConfigOptionDef::nocli; - - // The following value is to be stored into the project file (AMF, 3MF, Config ...) - // and it contains a sum of "inherits" values over the print and filament profiles. - def = this->add("inherits_group", coStrings); - def->set_default_value(new ConfigOptionStrings()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("interface_shells", coBool); - //def->label = L("Interface shells"); - def->label = L("Interface shells"); - def->tooltip = L("Force the generation of solid shells between adjacent materials/volumes. " - "Useful for multi-extruder prints with translucent materials or manual soluble " - "support material"); - def->category = L("Quality"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("mmu_segmented_region_max_width", coFloat); - def->label = L("Maximum width of a segmented region"); - def->tooltip = L("Maximum width of a segmented region. Zero disables this feature."); - def->sidetext = L("mm"); - def->min = 0; - def->category = L("Advanced"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.)); - - def = this->add("mmu_segmented_region_interlocking_depth", coFloat); - def->label = L("Interlocking depth of a segmented region"); - //def->tooltip = L("Interlocking depth of a segmented region. It will be ignored if " - // "\"mmu_segmented_region_max_width\" is zero or if \"mmu_segmented_region_interlocking_depth\"" - // "is bigger then \"mmu_segmented_region_max_width\". Zero disables this feature."); - def->tooltip = L("Interlocking depth of a segmented region. Zero disables this feature."); - def->sidetext = L("mm"); //(zero to disable) - def->min = 0; - def->category = L("Advanced"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.)); - - def = this->add("interlocking_beam", coBool); - def->label = L("Use beam interlocking"); - def->tooltip = L("Generate interlocking beam structure at the locations where different filaments touch. This improves the adhesion between filaments, especially models printed in different materials."); - def->category = L("Advanced"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("interlocking_beam_width", coFloat); - def->label = L("Interlocking beam width"); - def->tooltip = L("The width of the interlocking structure beams."); - def->sidetext = L("mm"); - def->min = 0.01; - def->category = L("Advanced"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.8)); - - def = this->add("interlocking_orientation", coFloat); - def->label = L("Interlocking direction"); - def->tooltip = L("Orientation of interlock beams."); - def->sidetext = L("°"); - def->min = 0; - def->max = 360; - def->category = L("Advanced"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(22.5)); - - def = this->add("interlocking_beam_layer_count", coInt); - def->label = L("Interlocking beam layers"); - def->tooltip = L("The height of the beams of the interlocking structure, measured in number of layers. Less layers is stronger, but more prone to defects."); - def->min = 1; - def->category = L("Advanced"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(2)); - - def = this->add("interlocking_depth", coInt); - def->label = L("Interlocking depth"); - def->tooltip = L("The distance from the boundary between filaments to generate interlocking structure, measured in cells. Too few cells will result in poor adhesion."); - def->min = 1; - def->category = L("Advanced"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(2)); - - def = this->add("interlocking_boundary_avoidance", coInt); - def->label = L("Interlocking boundary avoidance"); - def->tooltip = L("The distance from the outside of a model where interlocking structures will not be generated, measured in cells."); - def->min = 0; - def->category = L("Advanced"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(2)); - - def = this->add("ironing_type", coEnum); - def->label = L("Ironing Type"); - def->category = L("Quality"); - def->tooltip = L("Ironing is using small flow to print on same height of surface again to make flat surface more smooth. " - "This setting controls which layer being ironed"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("no ironing"); - def->enum_values.push_back("top"); - def->enum_values.push_back("topmost"); - def->enum_values.push_back("solid"); - def->enum_labels.push_back(L("No ironing")); - def->enum_labels.push_back(L("Top surfaces")); - def->enum_labels.push_back(L("Topmost surface")); - def->enum_labels.push_back(L("All solid layer")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(IroningType::NoIroning)); - - def = this->add("ironing_pattern", coEnum); - def->label = L("Ironing Pattern"); - def->category = L("Quality"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("concentric"); - def->enum_values.push_back("zig-zag"); - def->enum_labels.push_back(L("Concentric")); - def->enum_labels.push_back(L("Rectilinear")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(ipRectilinear)); - - def = this->add("ironing_flow", coPercent); - def->label = L("Ironing flow"); - def->category = L("Quality"); - def->tooltip = L("The amount of material to extrude during ironing. Relative to flow of normal layer height. " - "Too high value results in overextrusion on the surface"); - def->sidetext = "%"; - def->ratio_over = "layer_height"; - def->min = 0; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(10)); - - def = this->add("ironing_spacing", coFloat); - def->label = L("Ironing line spacing"); - def->category = L("Quality"); - def->tooltip = L("The distance between the lines of ironing"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 1; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.1)); - - def = this->add("ironing_inset", coFloat); - def->label = L("Ironing inset"); - def->category = L("Quality"); - def->tooltip = L("The distance to keep the from the edges of ironing line. 0 means not apply."); - def->sidetext = L("mm"); - def->min = 0; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("ironing_speed", coFloat); - def->label = L("Ironing speed"); - def->category = L("Quality"); - def->tooltip = L("Print speed of ironing lines"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(20)); - - def = this->add("ironing_direction", coFloat); - def->label = L("ironing direction"); - def->category = L("Quality"); - def->tooltip = L("Angle for ironing, which controls the relative angle between the top surface and ironing"); - def->sidetext = L("°"); - def->min = 0; - def->max = 360; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(45)); - - def = this->add("layer_change_gcode", coString); - def->label = L("Layer change G-code"); - def->tooltip = L("This gcode part is inserted at every layer change after lift z"); - def->multiline = true; - def->full_width = true; - def->height = 5; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("time_lapse_gcode",coString); - def->label = L("Time lapse G-code"); - def->multiline = true; - def->full_width = true; - def->height =5; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("wrapping_detection_gcode", coString); - def->label = L("Clumping detection G-code"); - def->multiline = true; - def->full_width = true; - def->height = 5; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("silent_mode", coBool); - def->label = L("Supports silent mode"); - def->tooltip = L("Whether the machine supports silent mode in which machine use lower acceleration to print"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("machine_pause_gcode", coString); - def->label = L("Pause G-code"); - def->tooltip = L("This G-code will be used as a code for the pause print. User can insert pause G-code in gcode viewer"); - def->multiline = true; - def->full_width = true; - def->height = 12; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("template_custom_gcode", coString); - def->label = L("Custom G-code"); - def->tooltip = L("This G-code will be used as a custom code"); - def->multiline = true; - def->full_width = true; - def->height = 12; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("extruder_max_nozzle_count", coInts); - def->mode = comDevelop; - def->nullable = true; - def->set_default_value(new ConfigOptionIntsNullable{ 1 }); - - def = this->add("has_scarf_joint_seam", coBool); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); + // BBS + def = this->add("thumbnail_size", coPoints); + def->label = L("Thumbnail size"); + def->tooltip = L("Decides the size of thumbnail stored in gcode files"); + def->mode = comDevelop; + def->gui_type = ConfigOptionDef::GUIType::one_string; + def->set_default_value(new ConfigOptionPoints{Vec2d(50, 50)}); - { - struct AxisDefault { - std::string name; - std::vector max_feedrate; - std::vector max_acceleration; - std::vector max_jerk; - }; - std::vector axes { - // name, max_feedrate, max_acceleration, max_jerk - { "x", { 500., 200. }, { 1000., 1000. }, { 10. , 10. } }, - { "y", { 500., 200. }, { 1000., 1000. }, { 10. , 10. } }, - { "z", { 12., 12. }, { 500., 200. }, { 0.2, 0.4 } }, - { "e", { 120., 120. }, { 5000., 5000. }, { 2.5, 2.5 } } - }; - for (const AxisDefault &axis : axes) { - std::string axis_upper = boost::to_upper_copy(axis.name); - // Add the machine feedrate limits for XYZE axes. (M203) - def = this->add("machine_max_speed_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum speed %1%") % axis_upper).str(); - (void)L("Maximum speed X"); - (void)L("Maximum speed Y"); - (void)L("Maximum speed Z"); - (void)L("Maximum speed E"); - def->category = L("Machine limits"); - def->readonly = false; - def->tooltip = (boost::format("Maximum speed of %1% axis") % axis_upper).str(); - (void)L("Maximum X speed"); - (void)L("Maximum Y speed"); - (void)L("Maximum Z speed"); - (void)L("Maximum E speed"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comSimple; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable(axis.max_feedrate)); - // Add the machine acceleration limits for XYZE axes (M201) - def = this->add("machine_max_acceleration_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); - (void)L("Maximum acceleration X"); - (void)L("Maximum acceleration Y"); - (void)L("Maximum acceleration Z"); - (void)L("Maximum acceleration E"); - def->category = L("Machine limits"); - def->readonly = false; - def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); - (void)L("Maximum acceleration of the X axis"); - (void)L("Maximum acceleration of the Y axis"); - (void)L("Maximum acceleration of the Z axis"); - (void)L("Maximum acceleration of the E axis"); - def->sidetext = "mm/s²"; - def->min = 0; - def->mode = comSimple; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable(axis.max_acceleration)); - // Add the machine jerk limits for XYZE axes (M205) - def = this->add("machine_max_jerk_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); - (void)L("Maximum jerk X"); - (void)L("Maximum jerk Y"); - (void)L("Maximum jerk Z"); - (void)L("Maximum jerk E"); - def->category = L("Machine limits"); - def->readonly = false; - def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); - (void)L("Maximum jerk of the X axis"); - (void)L("Maximum jerk of the Y axis"); - (void)L("Maximum jerk of the Z axis"); - (void)L("Maximum jerk of the E axis"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comSimple; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable(axis.max_jerk)); + // BBS + // def = this->add("spaghetti_detector", coBool); + // def->label = L("Enable spaghetti detector"); + // def->tooltip = L("Enable the camera on printer to check spaghetti"); + // def->mode = comSimple; + // def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("nozzle_type", coEnums); + def->label = L("Nozzle type"); + def->tooltip = L("The metallic material of nozzle. This determines the abrasive resistance of nozzle, and " + "what kind of filament can be printed"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("undefine"); + def->enum_values.push_back("hardened_steel"); + def->enum_values.push_back("stainless_steel"); + def->enum_values.push_back("tungsten_carbide"); + def->enum_values.push_back("brass"); + def->enum_labels.push_back(L("Undefine")); + def->enum_labels.push_back(L("Hardened steel")); + def->enum_labels.push_back(L("Stainless steel")); + def->enum_labels.push_back(L("Tungsten carbide")); + def->enum_labels.push_back(L("Brass")); + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionEnumsGenericNullable({ntUndefine})); + + def = this->add("printer_structure", coEnum); + def->label = L("Printer structure"); + def->tooltip = L("The physical arrangement and components of a printing device"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("undefine"); + def->enum_values.push_back("corexy"); + def->enum_values.push_back("i3"); + def->enum_values.push_back("hbot"); + def->enum_values.push_back("delta"); + def->enum_labels.push_back(L("Undefine")); + def->enum_labels.push_back("CoreXY"); + def->enum_labels.push_back("I3"); + def->enum_labels.push_back("Hbot"); + def->enum_labels.push_back("Delta"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionEnum(psUndefine)); + + def = this->add("best_object_pos", coPoint); + def->label = L("Best object position"); + def->tooltip = L("Best auto arranging position in range [0,1] w.r.t. bed shape."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPoint(Vec2d(0.5, 0.5))); + + def = this->add("auxiliary_fan", coBool); + def->label = L("Auxiliary part cooling fan"); + def->tooltip = L("Enable this option if machine has auxiliary part cooling fan"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("fan_direction", coEnum); + def->label = L("Fan direction"); + def->tooltip = L("Cooling fan direction of the printer"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("undefine"); + def->enum_values.push_back("left"); + def->enum_values.push_back("right"); + def->enum_values.push_back("both"); + def->enum_labels.push_back(L("Undefine")); + def->enum_labels.push_back(L("Left")); + def->enum_labels.push_back(L("Right")); + def->enum_labels.push_back(L("Both")); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionEnum(fdUndefine)); + + def = this->add("support_chamber_temp_control", coBool); + def->label = L("Support control chamber temperature"); + def->tooltip = L("This option is enabled if machine support controlling chamber temperature"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + def->readonly = false; + + def = this->add("apply_top_surface_compensation", coBool); + def->label = L("Apply top surface compensation"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("support_air_filtration", coBool); + def->label = L("Air filtration enhancement"); + def->tooltip = L("Enable this if printer support air filtration enhancement."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("support_cooling_filter", coBool); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("cooling_filter_enabled", coBool); + def->label = L("Use cooling filter"); + def->tooltip = L("Enable this if printer support cooling filter"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("auto_disable_filter_on_overheat", coBool); + def->label = L("Auto turn off filter on overheat"); + def->tooltip = L("Enable this if printer support cooling filter"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("gcode_flavor", coEnum); + def->label = L("G-code flavor"); + def->tooltip = L("What kind of gcode the printer is compatible with"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("marlin"); + def->enum_values.push_back("klipper"); + // def->enum_values.push_back("reprap"); + // def->enum_values.push_back("reprapfirmware"); + // def->enum_values.push_back("repetier"); + // def->enum_values.push_back("teacup"); + // def->enum_values.push_back("makerware"); + // def->enum_values.push_back("marlin2"); + // def->enum_values.push_back("sailfish"); + // def->enum_values.push_back("mach3"); + // def->enum_values.push_back("machinekit"); + // def->enum_values.push_back("smoothie"); + // def->enum_values.push_back("no-extrusion"); + def->enum_labels.push_back("Marlin(legacy)"); + def->enum_labels.push_back("Klipper"); + // def->enum_labels.push_back("RepRap/Sprinter"); + // def->enum_labels.push_back("RepRapFirmware"); + // def->enum_labels.push_back("Repetier"); + // def->enum_labels.push_back("Teacup"); + // def->enum_labels.push_back("MakerWare (MakerBot)"); + // def->enum_labels.push_back("Marlin 2"); + // def->enum_labels.push_back("Sailfish (MakerBot)"); + // def->enum_labels.push_back("Mach3/LinuxCNC"); + // def->enum_labels.push_back("Machinekit"); + // def->enum_labels.push_back("Smoothie"); + // def->enum_labels.push_back(L("No extrusion")); + def->mode = comAdvanced; + def->readonly = false; + def->set_default_value(new ConfigOptionEnum(gcfMarlinLegacy)); + + // OrcaSlicer + def = this->add("exclude_object", coBool); + def->label = L("Exclude objects"); + def->tooltip = L("Enable this option to add EXCLUDE OBJECT command in g-code for klipper firmware printer"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(1)); + + // BBS + def = this->add("infill_combination", coBool); + def->label = L("Infill combination"); + def->category = L("Strength"); + def->tooltip = L("Automatically Combine sparse infill of several layers to print together to reduce time. Wall is still printed " + "with original layer height."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("infill_shift_step", coFloat); + def->label = L("Infill shift step"); + def->category = L("Strength"); + def->tooltip = L("This parameter adds a slight displacement to each layer of infill to create a cross texture."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("infill_rotate_step", coFloat); + def->label = L("Infill rotate step"); + def->category = L("Strength"); + def->tooltip = L("This parameter adds a slight rotation to each layer of infill to create a cross texture."); + def->sidetext = L("°"); + def->min = 0; + def->max = 360; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("skeleton_infill_density", coPercent); + def->label = L("Skeleton infill density"); + def->category = L("Strength"); + def->tooltip = L("The remaining part of the model contour after removing a certain depth from the surface is called the skeleton. This parameter is used to adjust the density of this section." + "When two regions have the same sparse infill settings but different skeleton densities, their skeleton areas will develop overlapping sections." + "default is as same as infill density."); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(15)); + + def = this->add("skin_infill_density", coPercent); + def->label = L("Skin infill density"); + def->category = L("Strength"); + def->tooltip = L("The portion of the model's outer surface within a certain depth range is called the skin. This parameter is used to adjust the density of this section." + "When two regions have the same sparse infill settings but different skin densities, This area will not be split into two separate regions." + "default is as same as infill density."); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(15)); + + def = this->add("skin_infill_depth", coFloat); + def->label = L("Skin infill depth"); + def->category = L("Strength"); + def->tooltip = L("The parameter sets the depth of skin."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(2.0)); + + def = this->add("infill_lock_depth", coFloat); + def->label = L("Infill lock depth"); + def->category = L("Strength"); + def->tooltip = L("The parameter sets the overlapping depth between the interior and skin."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("skin_infill_line_width", coFloat); + def->label = L("Skin line width"); + def->category = L("Strength"); + def->tooltip = L("Adjust the line width of the selected skin paths."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("skeleton_infill_line_width", coFloat); + def->label = L("Skeleton line width"); + def->category = L("Strength"); + def->tooltip = L("Adjust the line width of the selected skeleton paths."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("symmetric_infill_y_axis", coBool); + def->label = L("Symmetric infill y axis"); + def->category = L("Strength"); + def->tooltip = L("If the model has two parts that are symmetric about the y-axis," + " and you want these parts to have symmetric textures, please click this option on one of the parts."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + auto def_infill_anchor_min = def = this->add("sparse_infill_anchor", coFloatOrPercent); + def->label = L("Length of sparse infill anchor"); + def->category = L("Strength"); + def->tooltip = L("Connect a sparse infill line to an internal perimeter with a short segment of an additional perimeter. " + "If expressed as percentage (example: 15%) it is calculated over sparse infill line width. " + "Slicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment " + "shorter than infill_anchor_max is found, the infill line is connected to a perimeter segment at just one side " + "and the length of the perimeter segment taken is limited to this parameter, but no longer than anchor_length_max. " + "Set this parameter to zero to disable anchoring perimeters connected to a single infill line."); + def->sidetext = L("mm or %"); + def->ratio_over = "sparse_infill_line_width"; + def->max_literal = 1000; + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; + def->enum_values.push_back("0"); + def->enum_values.push_back("1"); + def->enum_values.push_back("2"); + def->enum_values.push_back("5"); + def->enum_values.push_back("10"); + def->enum_values.push_back("1000"); + def->enum_labels.push_back(L("0 (no open anchors)")); + def->enum_labels.push_back("1 mm"); + def->enum_labels.push_back("2 mm"); + def->enum_labels.push_back("5 mm"); + def->enum_labels.push_back("10 mm"); + def->enum_labels.push_back(L("1000 (unlimited)")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(400, true)); + + def = this->add("sparse_infill_anchor_max", coFloatOrPercent); + def->label = L("Maximum length of sparse infill anchor"); + def->category = def_infill_anchor_min->category; + def->tooltip = L("Connect a sparse infill line to an internal perimeter with a short segment of an additional perimeter. " + "If expressed as percentage (example: 15%) it is calculated over sparse infill line width. " + "Slicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment " + "shorter than this parameter is found, the infill line is connected to a perimeter segment at just one side " + "and the length of the perimeter segment taken is limited to infill_anchor, but no longer than this parameter. " + "Set this parameter to zero to disable anchoring."); + def->sidetext = def_infill_anchor_min->sidetext; + def->ratio_over = def_infill_anchor_min->ratio_over; + def->max_literal = def_infill_anchor_min->max_literal; + def->gui_type = def_infill_anchor_min->gui_type; + def->enum_values.push_back("0"); + def->enum_values.push_back("1"); + def->enum_values.push_back("2"); + def->enum_values.push_back("5"); + def->enum_values.push_back("10"); + def->enum_values.push_back("1000"); + def->enum_labels.push_back(L("0 (not anchored)")); + def->enum_labels.push_back("1 mm"); + def->enum_labels.push_back("2 mm"); + def->enum_labels.push_back("5 mm"); + def->enum_labels.push_back("10 mm"); + def->enum_labels.push_back(L("1000 (unlimited)")); + def->mode = def_infill_anchor_min->mode; + def->set_default_value(new ConfigOptionFloatOrPercent(20, false)); + + def = this->add("sparse_infill_filament", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Sparse infill filament"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print internal sparse infill."); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("sparse_infill_line_width", coFloat); + def->label = L("Sparse infill"); + def->category = L("Quality"); + def->tooltip = L("Line width of internal sparse infill"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("infill_wall_overlap", coPercent); + def->label = L("Infill/Wall overlap"); + def->category = L("Strength"); + def->tooltip = L("Infill area is enlarged slightly to overlap with wall for better bonding. The percentage value is relative to line width of sparse infill"); + def->sidetext = "%"; + def->ratio_over = "inner_wall_line_width"; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(15)); + + def = this->add("sparse_infill_speed", coFloats); + def->label = L("Sparse infill"); + def->category = L("Speed"); + def->tooltip = L("Speed of internal sparse infill"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{100}); + + def = this->add("inherits", coString); + // def->label = L("Inherits profile"); + def->label = "Inherits profile"; + // def->tooltip = L("Name of parent profile"); + def->tooltip = "Name of parent profile"; + def->full_width = true; + def->height = 5; + def->set_default_value(new ConfigOptionString()); + def->cli = ConfigOptionDef::nocli; + + // The following value is to be stored into the project file (AMF, 3MF, Config ...) + // and it contains a sum of "inherits" values over the print and filament profiles. + def = this->add("inherits_group", coStrings); + def->set_default_value(new ConfigOptionStrings()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("interface_shells", coBool); + // def->label = L("Interface shells"); + def->label = L("Interface shells"); + def->tooltip = L("Force the generation of solid shells between adjacent materials/volumes. " + "Useful for multi-extruder prints with translucent materials or manual soluble " + "support material"); + def->category = L("Quality"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("mmu_segmented_region_max_width", coFloat); + def->label = L("Maximum width of a segmented region"); + def->tooltip = L("Maximum width of a segmented region. Zero disables this feature."); + def->sidetext = L("mm"); + def->min = 0; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("mmu_segmented_region_interlocking_depth", coFloat); + def->label = L("Interlocking depth of a segmented region"); + // def->tooltip = L("Interlocking depth of a segmented region. It will be ignored if " + // "\"mmu_segmented_region_max_width\" is zero or if \"mmu_segmented_region_interlocking_depth\"" + // "is bigger then \"mmu_segmented_region_max_width\". Zero disables this feature."); + def->tooltip = L("Interlocking depth of a segmented region. Zero disables this feature."); + def->sidetext = L("mm"); //(zero to disable) + def->min = 0; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("interlocking_beam", coBool); + def->label = L("Use beam interlocking"); + def->tooltip = L("Generate interlocking beam structure at the locations where different filaments touch. This improves the adhesion between filaments, especially models printed in different materials."); + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("interlocking_beam_width", coFloat); + def->label = L("Interlocking beam width"); + def->tooltip = L("The width of the interlocking structure beams."); + def->sidetext = L("mm"); + def->min = 0.01; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.8)); + + def = this->add("interlocking_orientation", coFloat); + def->label = L("Interlocking direction"); + def->tooltip = L("Orientation of interlock beams."); + def->sidetext = L("°"); + def->min = 0; + def->max = 360; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(22.5)); + + def = this->add("interlocking_beam_layer_count", coInt); + def->label = L("Interlocking beam layers"); + def->tooltip = L("The height of the beams of the interlocking structure, measured in number of layers. Less layers is stronger, but more prone to defects."); + def->min = 1; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(2)); + + def = this->add("interlocking_depth", coInt); + def->label = L("Interlocking depth"); + def->tooltip = L("The distance from the boundary between filaments to generate interlocking structure, measured in cells. Too few cells will result in poor adhesion."); + def->min = 1; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(2)); + + def = this->add("interlocking_boundary_avoidance", coInt); + def->label = L("Interlocking boundary avoidance"); + def->tooltip = L("The distance from the outside of a model where interlocking structures will not be generated, measured in cells."); + def->min = 0; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(2)); + + def = this->add("ironing_type", coEnum); + def->label = L("Ironing Type"); + def->category = L("Quality"); + def->tooltip = L("Ironing is using small flow to print on same height of surface again to make flat surface more smooth. " + "This setting controls which layer being ironed"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("no ironing"); + def->enum_values.push_back("top"); + def->enum_values.push_back("topmost"); + def->enum_values.push_back("solid"); + def->enum_labels.push_back(L("No ironing")); + def->enum_labels.push_back(L("Top surfaces")); + def->enum_labels.push_back(L("Topmost surface")); + def->enum_labels.push_back(L("All solid layer")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(IroningType::NoIroning)); + + def = this->add("ironing_pattern", coEnum); + def->label = L("Ironing Pattern"); + def->category = L("Quality"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("concentric"); + def->enum_values.push_back("zig-zag"); + def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Rectilinear")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(ipRectilinear)); + + def = this->add("ironing_flow", coPercent); + def->label = L("Ironing flow"); + def->category = L("Quality"); + def->tooltip = L("The amount of material to extrude during ironing. Relative to flow of normal layer height. " + "Too high value results in overextrusion on the surface"); + def->sidetext = "%"; + def->ratio_over = "layer_height"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(10)); + + def = this->add("ironing_spacing", coFloat); + def->label = L("Ironing line spacing"); + def->category = L("Quality"); + def->tooltip = L("The distance between the lines of ironing"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 1; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.1)); + + def = this->add("ironing_inset", coFloat); + def->label = L("Ironing inset"); + def->category = L("Quality"); + def->tooltip = L("The distance to keep the from the edges of ironing line. 0 means not apply."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("ironing_speed", coFloat); + def->label = L("Ironing speed"); + def->category = L("Quality"); + def->tooltip = L("Print speed of ironing lines"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(20)); + + def = this->add("ironing_direction", coFloat); + def->label = L("ironing direction"); + def->category = L("Quality"); + def->tooltip = L("Angle for ironing, which controls the relative angle between the top surface and ironing"); + def->sidetext = L("°"); + def->min = 0; + def->max = 360; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(45)); + + def = this->add("layer_change_gcode", coString); + def->label = L("Layer change G-code"); + def->tooltip = L("This gcode part is inserted at every layer change after lift z"); + def->multiline = true; + def->full_width = true; + def->height = 5; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("time_lapse_gcode", coString); + def->label = L("Time lapse G-code"); + def->multiline = true; + def->full_width = true; + def->height = 5; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("wrapping_detection_gcode", coString); + def->label = L("Clumping detection G-code"); + def->multiline = true; + def->full_width = true; + def->height = 5; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("silent_mode", coBool); + def->label = L("Supports silent mode"); + def->tooltip = L("Whether the machine supports silent mode in which machine use lower acceleration to print"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("machine_pause_gcode", coString); + def->label = L("Pause G-code"); + def->tooltip = L("This G-code will be used as a code for the pause print. User can insert pause G-code in gcode viewer"); + def->multiline = true; + def->full_width = true; + def->height = 12; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("template_custom_gcode", coString); + def->label = L("Custom G-code"); + def->tooltip = L("This G-code will be used as a custom code"); + def->multiline = true; + def->full_width = true; + def->height = 12; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("extruder_max_nozzle_count", coInts); + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable{1}); + + def = this->add("has_scarf_joint_seam", coBool); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + { + struct AxisDefault + { + std::string name; + std::vector max_feedrate; + std::vector max_acceleration; + std::vector max_jerk; + }; + std::vector axes{ + // name, max_feedrate, max_acceleration, max_jerk + {"x", {500., 200.}, {1000., 1000.}, {10., 10.}}, + {"y", {500., 200.}, {1000., 1000.}, {10., 10.}}, + {"z", {12., 12.}, {500., 200.}, {0.2, 0.4}}, + {"e", {120., 120.}, {5000., 5000.}, {2.5, 2.5}}}; + for (const AxisDefault &axis : axes) + { + std::string axis_upper = boost::to_upper_copy(axis.name); + // Add the machine feedrate limits for XYZE axes. (M203) + def = this->add("machine_max_speed_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum speed %1%") % axis_upper).str(); + (void)L("Maximum speed X"); + (void)L("Maximum speed Y"); + (void)L("Maximum speed Z"); + (void)L("Maximum speed E"); + def->category = L("Machine limits"); + def->readonly = false; + def->tooltip = (boost::format("Maximum speed of %1% axis") % axis_upper).str(); + (void)L("Maximum X speed"); + (void)L("Maximum Y speed"); + (void)L("Maximum Z speed"); + (void)L("Maximum E speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comSimple; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable(axis.max_feedrate)); + // Add the machine acceleration limits for XYZE axes (M201) + def = this->add("machine_max_acceleration_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); + (void)L("Maximum acceleration X"); + (void)L("Maximum acceleration Y"); + (void)L("Maximum acceleration Z"); + (void)L("Maximum acceleration E"); + def->category = L("Machine limits"); + def->readonly = false; + def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); + (void)L("Maximum acceleration of the X axis"); + (void)L("Maximum acceleration of the Y axis"); + (void)L("Maximum acceleration of the Z axis"); + (void)L("Maximum acceleration of the E axis"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comSimple; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable(axis.max_acceleration)); + // Add the machine jerk limits for XYZE axes (M205) + def = this->add("machine_max_jerk_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); + (void)L("Maximum jerk X"); + (void)L("Maximum jerk Y"); + (void)L("Maximum jerk Z"); + (void)L("Maximum jerk E"); + def->category = L("Machine limits"); + def->readonly = false; + def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); + (void)L("Maximum jerk of the X axis"); + (void)L("Maximum jerk of the Y axis"); + (void)L("Maximum jerk of the Z axis"); + (void)L("Maximum jerk of the E axis"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comSimple; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable(axis.max_jerk)); + } } - } - // M205 S... [mm/sec] - def = this->add("machine_min_extruding_rate", coFloats); - def->full_label = L("Minimum speed for extruding"); - def->category = L("Machine limits"); - def->tooltip = L("Minimum speed for extruding (M205 S)"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloatsNullable{ 0., 0. }); - - // M205 T... [mm/sec] - def = this->add("machine_min_travel_rate", coFloats); - def->full_label = L("Minimum travel speed"); - def->category = L("Machine limits"); - def->tooltip = L("Minimum travel speed (M205 T)"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloatsNullable{ 0., 0. }); - - // M204 P... [mm/sec^2] - def = this->add("machine_max_acceleration_extruding", coFloats); - def->full_label = L("Maximum acceleration for extruding"); - def->category = L("Machine limits"); - def->tooltip = L("Maximum acceleration for extruding (M204 P)"); - // "Marlin (legacy) firmware flavor will use this also " - // "as travel acceleration (M204 T)."); - def->sidetext = "mm/s²"; - def->min = 0; - def->readonly = false; - def->mode = comSimple; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 1500., 1250. }); - - - // M204 R... [mm/sec^2] - def = this->add("machine_max_acceleration_retracting", coFloats); - def->full_label = L("Maximum acceleration for retracting"); - def->category = L("Machine limits"); - def->tooltip = L("Maximum acceleration for retracting (M204 R)"); - def->sidetext = "mm/s²"; - def->min = 0; - def->readonly = false; - def->mode = comSimple; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 1500., 1250. }); - - // M204 T... [mm/sec^2] - def = this->add("machine_max_acceleration_travel", coFloats); - def->full_label = L("Maximum acceleration for travel"); - def->category = L("Machine limits"); - def->tooltip = L("Maximum acceleration for travel (M204 T)"); - def->sidetext = "mm/s²"; - def->min = 0; - def->readonly = true; - def->mode = comDevelop; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 1500., 1250. }); - - def = this->add("fan_max_speed", coInts); - def->label = L("Fan speed"); - def->tooltip = L("Part cooling fan speed may be increased when auto cooling is enabled. " - "This is the maximum speed limitation of part cooling fan"); - def->sidetext = "%"; - def->min = 0; - def->max = 100; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInts { 100 }); - - def = this->add("max_layer_height", coFloats); - def->label = L("Max"); - def->tooltip = L("The largest printable layer height for extruder. Used to limit " - "the maximum layer height when enable adaptive layer height"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 0. }); + // M205 S... [mm/sec] + def = this->add("machine_min_extruding_rate", coFloats); + def->full_label = L("Minimum speed for extruding"); + def->category = L("Machine limits"); + def->tooltip = L("Minimum speed for extruding (M205 S)"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloatsNullable{0., 0.}); + + // M205 T... [mm/sec] + def = this->add("machine_min_travel_rate", coFloats); + def->full_label = L("Minimum travel speed"); + def->category = L("Machine limits"); + def->tooltip = L("Minimum travel speed (M205 T)"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloatsNullable{0., 0.}); + + // M204 P... [mm/sec^2] + def = this->add("machine_max_acceleration_extruding", coFloats); + def->full_label = L("Maximum acceleration for extruding"); + def->category = L("Machine limits"); + def->tooltip = L("Maximum acceleration for extruding (M204 P)"); + // "Marlin (legacy) firmware flavor will use this also " + // "as travel acceleration (M204 T)."); + def->sidetext = "mm/s²"; + def->min = 0; + def->readonly = false; + def->mode = comSimple; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{1500., 1250.}); + + // M204 R... [mm/sec^2] + def = this->add("machine_max_acceleration_retracting", coFloats); + def->full_label = L("Maximum acceleration for retracting"); + def->category = L("Machine limits"); + def->tooltip = L("Maximum acceleration for retracting (M204 R)"); + def->sidetext = "mm/s²"; + def->min = 0; + def->readonly = false; + def->mode = comSimple; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{1500., 1250.}); + + // M204 T... [mm/sec^2] + def = this->add("machine_max_acceleration_travel", coFloats); + def->full_label = L("Maximum acceleration for travel"); + def->category = L("Machine limits"); + def->tooltip = L("Maximum acceleration for travel (M204 T)"); + def->sidetext = "mm/s²"; + def->min = 0; + def->readonly = true; + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{1500., 1250.}); + + def = this->add("fan_max_speed", coInts); + def->label = L("Fan speed"); + def->tooltip = L("Part cooling fan speed may be increased when auto cooling is enabled. " + "This is the maximum speed limitation of part cooling fan"); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInts{100}); + + def = this->add("max_layer_height", coFloats); + def->label = L("Max"); + def->tooltip = L("The largest printable layer height for extruder. Used to limit " + "the maximum layer height when enable adaptive layer height"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); #ifdef HAS_PRESSURE_EQUALIZER - //def = this->add("max_volumetric_extrusion_rate_slope_positive", coFloat); - //def->label = L("Max volumetric slope positive"); - //def->tooltip = L("This experimental setting is used to limit the speed of change in extrusion rate. " - // "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " - // "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " - // "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."); - //def->sidetext = L("mm³/s²"); - //def->min = 0; - //def->mode = comAdvanced; - //def->set_default_value(new ConfigOptionFloat(0)); - - //def = this->add("max_volumetric_extrusion_rate_slope_negative", coFloat); - //def->label = L("Max volumetric slope negative"); - //def->tooltip = L("This experimental setting is used to limit the speed of change in extrusion rate. " - // "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " - // "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " - // "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."); - //def->sidetext = L("mm³/s²"); - //def->min = 0; - //def->mode = comAdvanced; - //def->set_default_value(new ConfigOptionFloat(0)); + // def = this->add("max_volumetric_extrusion_rate_slope_positive", coFloat); + // def->label = L("Max volumetric slope positive"); + // def->tooltip = L("This experimental setting is used to limit the speed of change in extrusion rate. " + // "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " + // "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " + // "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."); + // def->sidetext = L("mm³/s²"); + // def->min = 0; + // def->mode = comAdvanced; + // def->set_default_value(new ConfigOptionFloat(0)); + + // def = this->add("max_volumetric_extrusion_rate_slope_negative", coFloat); + // def->label = L("Max volumetric slope negative"); + // def->tooltip = L("This experimental setting is used to limit the speed of change in extrusion rate. " + // "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " + // "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " + // "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."); + // def->sidetext = L("mm³/s²"); + // def->min = 0; + // def->mode = comAdvanced; + // def->set_default_value(new ConfigOptionFloat(0)); #endif /* HAS_PRESSURE_EQUALIZER */ - def = this->add("fan_min_speed", coInts); - def->label = L("Fan speed"); - def->tooltip = L("Minimum speed for part cooling fan"); - def->sidetext = "%"; - def->min = 0; - def->max = 100; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInts { 20 }); - - def = this->add("additional_cooling_fan_speed", coInts); - def->label = L("Fan speed"); - def->tooltip = L("Speed of auxiliary part cooling fan. Auxiliary fan will run at this speed during printing except the first several layers " - "which are defined by no cooling layers"); - def->sidetext = "%"; - def->min = 0; - def->max = 100; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInts { 0 }); - - def = this->add("min_layer_height", coFloats); - def->label = L("Min"); - def->tooltip = L("The lowest printable layer height for extruder. Used to limit " - "the minimum layer height when enable adaptive layer height"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 0.07 }); - - def = this->add("slow_down_min_speed", coFloats); - def->label = L("Min print speed"); - def->tooltip = L("The minimum printing speed when slow down for cooling"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 10. }); - - def = this->add("nozzle_diameter", coFloats); - def->label = L("Nozzle diameter"); - def->tooltip = L("Diameter of nozzle"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->max = 1.0; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 0.4 }); - - def = this->add("host_type", coEnum); - def->label = L("Host Type"); - def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain " - "the kind of the host."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("prusalink"); - def->enum_values.push_back("octoprint"); - def->enum_values.push_back("duet"); - def->enum_values.push_back("flashair"); - def->enum_values.push_back("astrobox"); - def->enum_values.push_back("repetier"); - def->enum_values.push_back("mks"); - def->enum_labels.push_back("PrusaLink"); - def->enum_labels.push_back("OctoPrint"); - def->enum_labels.push_back("Duet"); - def->enum_labels.push_back("FlashAir"); - def->enum_labels.push_back("AstroBox"); - def->enum_labels.push_back("Repetier"); - def->enum_labels.push_back("MKS"); - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionEnum(htOctoPrint)); - - def = this->add("nozzle_volume", coFloats); - def->label = L("Nozzle volume"); - def->tooltip = L("Volume of nozzle between the cutter and the end of nozzle"); - def->sidetext = L("mm³"); - def->mode = comAdvanced; - def->readonly = true; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { {0.0} }); - - def = this->add("start_end_points", coPoints); - def->label = L("Start end points"); - def->tooltip = L("The start and end points which are from cutter area to garbage can."); - def->mode = comDevelop; - def->readonly = true; - // start and end point is from the change_filament_gcode - def->set_default_value(new ConfigOptionPoints{Vec2d(30, -3), Vec2d(54, 245)}); - - def = this->add("reduce_infill_retraction", coBool); - def->label = L("Reduce infill retraction"); - def->tooltip = L("Don't retract when the travel is in infill area absolutely. That means the oozing can't been seen. " - "This can reduce times of retraction for complex model and save printing time, but make slicing and " - "G-code generating slower"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("ooze_prevention", coBool); - def->label = L("Enable"); - //def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. " - // "It will enable a tall skirt automatically and move extruders outside such " - // "skirt when changing temperatures."); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("filename_format", coString); - def->label = L("Filename format"); - def->tooltip = L("User can self-define the project file name when export"); - def->full_width = true; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionString("[input_filename_base].gcode")); - - def = this->add("detect_overhang_wall", coBool); - def->label = L("Detect overhang wall"); - def->category = L("Quality"); - def->tooltip = L("Detect the overhang percentage relative to line width and use different speed to print. " - "For 100 percent overhang, bridge speed is used."); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("smooth_speed_discontinuity_area", coBool); - def->label = L("Smooth speed discontinuity area"); - def->category = L("Quality"); - def->tooltip = L("Add the speed transition between discontinuity area."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("smooth_coefficient", coFloat); - def->label = L("Smooth coefficient"); - def->category = L("Quality"); - def->tooltip = L("The smaller the number, the longer the speed transition path. 0 means not apply."); - def->mode = comAdvanced; - def->min = 0; - def->set_default_value(new ConfigOptionFloat(80)); - - def = this->add("wall_filament", coInt); - //def->label = L("Walls"); - //def->category = L("Extruders"); - //def->tooltip = L("Filament to print walls"); - def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Walls filament"); - def->category = L("Extruders"); - def->tooltip = L("Filament to print walls"); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInt(0)); - - def = this->add("inner_wall_line_width", coFloat); - def->label = L("Inner wall"); - def->category = L("Quality"); - def->tooltip = L("Line width of inner wall"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("inner_wall_speed", coFloats); - def->label = L("Inner wall"); - def->category = L("Speed"); - def->tooltip = L("Speed of inner wall"); - def->sidetext = L("mm/s"); - def->aliases = { "perimeter_feed_rate" }; - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{60}); - - def = this->add("wall_loops", coInt); - def->label = L("Wall loops"); - def->category = L("Strength"); - def->tooltip = L("Number of walls of every layer"); - def->min = 0; - def->max = 1000; - def->set_default_value(new ConfigOptionInt(2)); - - def = this->add("embedding_wall_into_infill", coBool); - def->label = L("Embedding the wall into the infill"); - def->category = L("Strength"); - def->tooltip = L("Embedding the wall into parts where the wall loops are absent ensures that the wall connects seamlessly to the infill."); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("post_process", coStrings); - def->label = L("Post-processing Scripts"); - def->tooltip = L("If you want to process the output G-code through custom scripts, " - "just list their absolute paths here. Separate multiple scripts with a semicolon. " - "Scripts will be passed the absolute path to the G-code file as the first argument, " - "and variables of settings also can be read"); - def->gui_flags = "serialized"; - def->multiline = true; - def->full_width = true; - def->height = 6; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("printer_model", coString); - //def->label = L("Printer type"); - //def->tooltip = L("Type of the printer"); - def->label = "Printer type"; - def->tooltip = "Type of the printer"; - def->set_default_value(new ConfigOptionString()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("printer_variant", coString); - //def->label = L("Printer variant"); - def->label = "Printer variant"; - //def->tooltip = L("Name of the printer variant. For example, the printer variants may be differentiated by a nozzle diameter."); - def->set_default_value(new ConfigOptionString()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("print_settings_id", coString); - def->set_default_value(new ConfigOptionString("")); - //BBS: open this option to command line - //def->cli = ConfigOptionDef::nocli; - - def = this->add("printer_settings_id", coString); - def->set_default_value(new ConfigOptionString("")); - //BBS: open this option to command line - //def->cli = ConfigOptionDef::nocli; - - def = this->add("raft_contact_distance", coFloat); - def->label = L("Raft contact Z distance"); - def->category = L("Support"); - def->tooltip = L("Z gap between object and raft. Ignored for soluble interface"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.1)); - - def = this->add("raft_expansion", coFloat); - def->label = L("Raft expansion"); - def->category = L("Support"); - def->tooltip = L("Expand all raft layers in XY plane"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.5)); - - def = this->add("raft_first_layer_density", coPercent); - def->label = L("Initial layer density"); - def->category = L("Support"); - def->tooltip = L("Density of the first raft or support layer"); - def->sidetext = "%"; - def->min = 10; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(90)); - - def = this->add("raft_first_layer_expansion", coFloat); - def->label = L("Initial layer expansion"); - def->category = L("Support"); - def->tooltip = L("Expand the first raft or support layer to improve bed plate adhesion, -1 means auto"); - def->sidetext = L("mm"); - def->min = -1; - def->mode = comAdvanced; - //BBS: change from 3.0 to 2.0 - def->set_default_value(new ConfigOptionFloat(-1)); - - def = this->add("raft_layers", coInt); - def->label = L("Raft layers"); - def->category = L("Support"); - def->tooltip = L("Object will be raised by this number of support layers. " - "Use this function to avoid warping when print ABS"); - def->sidetext = L("layers"); - def->min = 0; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(0)); - - def = this->add("resolution", coFloat); - def->label = L("Resolution"); - def->tooltip = L("G-code path is generated after simplifying the contour of model to avoid too many points and gcode lines " - "in the gcode file. Smaller value means higher resolution and more time to slice"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.01)); - - def = this->add("retraction_minimum_travel", coFloats); - def->label = L("Travel distance threshold"); - def->tooltip = L("Only trigger retraction when the travel distance is longer than this threshold"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 2. }); - - def = this->add("retract_before_wipe", coPercents); - def->label = L("Retract amount before wipe"); - def->tooltip = L("The length of fast retraction before wipe, relative to retraction length"); - def->sidetext = "%"; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionPercentsNullable { 100 }); - - def = this->add("retract_when_changing_layer", coBools); - def->label = L("Retract when change layer"); - def->tooltip = L("Force a retraction when changes layer"); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionBoolsNullable { false }); - - def = this->add("retraction_length", coFloats); - def->label = L("Length"); - def->full_label = L("Retraction Length"); - def->tooltip = L("Some amount of material in extruder is pulled back to avoid ooze during long travel. " - "Set zero to disable retraction"); - def->sidetext = L("mm"); - def->mode = comSimple; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 0.8 }); - - def = this->add("enable_long_retraction_when_cut",coInt); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInt {0}); - - def = this->add("long_retractions_when_cut", coBools); - def->label = L("Long retraction when cut(experimental)"); - def->tooltip = L("Experimental feature.Retracting and cutting off the filament at a longer distance during changes to minimize purge." - "While this reduces flush significantly, it may also raise the risk of nozzle clogs or other printing problems."); - def->mode = comDevelop; - def->nullable = true; - def->set_default_value(new ConfigOptionBoolsNullable {false}); - - def = this->add("retraction_distances_when_cut",coFloats); - def->label = L("Retraction distance when cut"); - def->tooltip = L("Experimental feature.Retraction length before cutting off during filament change"); - def->mode = comDevelop; - def->min = 10; - def->max = 18; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable {18}); - - def = this->add("long_retractions_when_ec", coBools); - def->label = L("Long retraction when extruder change"); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionBoolsNullable {false}); - - def = this->add("retraction_distances_when_ec", coFloats); - def->label = L("Retraction distance when extruder change"); - def->mode = comAdvanced; - def->nullable = true; - def->min = 0; - def->max = 10; - def->sidetext = L("mm"); - def->set_default_value(new ConfigOptionFloatsNullable{10}); - - def = this->add("retract_length_toolchange", coFloats); - def->label = L("Length"); - //def->full_label = L("Retraction Length (Toolchange)"); - def->full_label = "Retraction Length (Toolchange)"; - //def->tooltip = L("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)."); - def->sidetext = L("mm"); - def->mode = comDevelop; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 10. }); - - def = this->add("z_hop", coFloats); - def->label = L("Z hop when retract"); - def->tooltip = L("Whenever the retraction is done, the nozzle is lifted a little to create clearance between nozzle and the print. " - "It prevents nozzle from hitting the print when travel moves. " - "Using spiral line to lift z can prevent stringing"); - def->sidetext = L("mm"); - def->mode = comSimple; - def->min = 0; - def->max = 5; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 0.4 }); - - def = this->add("retract_lift_above", coFloats); - def->label = L("Z hop lower boundary"); - def->tooltip = L("Z hop will only come into effect when Z is above this value and is below the parameter: \"Z hop upper boundary\""); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->min = 0; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0.}); - - def = this->add("retract_lift_below", coFloats); - def->label = L("Z hop upper boundary"); - def->tooltip = L("If this value is positive, Z hop will only come into effect when Z is above the parameter: \"Z hop lower boundary\" and is below this value"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->min = 0; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0.}); - - - def = this->add("z_hop_types", coEnums); - def->label = L("Z Hop Type"); - def->tooltip = L(""); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("Auto Lift"); - def->enum_values.push_back("Normal Lift"); - def->enum_values.push_back("Slope Lift"); - def->enum_values.push_back("Spiral Lift"); - def->enum_labels.push_back(L("Auto")); - def->enum_labels.push_back(L("Normal")); - def->enum_labels.push_back(L("Slope")); - def->enum_labels.push_back(L("Spiral")); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionEnumsGenericNullable{ ZHopType::zhtSpiral }); - - def = this->add("extruder_type", coEnums); - def->label = L("Type"); - def->tooltip = ("This setting is only used for initial value of manual calibration of pressure advance. Bowden extruder usually has larger pa value. This setting doesn't influence normal slicing"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("Direct Drive"); - def->enum_values.push_back("Bowden"); - def->enum_labels.push_back(L("Direct Drive")); - def->enum_labels.push_back(L("Bowden")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnumsGeneric{ ExtruderType::etDirectDrive }); - - //BBS - def = this->add("nozzle_volume_type", coEnums); - def->label = L("Nozzle Volume Type"); - def->tooltip = ("Nozzle volume type"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back(L("Standard")); - def->enum_values.push_back(L("High Flow")); - def->enum_values.push_back("Hybrid"); - def->enum_labels.push_back(L("Standard")); - def->enum_labels.push_back(L("High Flow")); - def->enum_labels.push_back(L("Hybrid")); - def->mode = comSimple; - def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); - - def = this->add("default_nozzle_volume_type", coEnums); - def->label = L("Default Nozzle Volume Type"); - def->tooltip = ("Default Nozzle volume type for extruders in this printer"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back(L("Standard")); - def->enum_values.push_back(L("High Flow")); - def->enum_values.push_back(L("Hybrid")); - def->enum_labels.push_back(L("Standard")); - def->enum_labels.push_back(L("High Flow")); - def->enum_labels.push_back(L("Hybrid")); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); - - def = this->add("extruder_variant_list", coStrings); - def->label = "Extruder variant list"; - def->tooltip = "Extruder variant list"; - def->set_default_value(new ConfigOptionStrings { "Direct Drive Standard" }); - def->cli = ConfigOptionDef::nocli; - - def = this->add("extruder_ams_count", coStrings); - def->label = "Extruder ams count"; - def->tooltip = "Ams counts of per extruder"; - def->set_default_value(new ConfigOptionStrings { }); - - def = this->add("extruder_nozzle_stats", coStrings); - def->set_default_value(new ConfigOptionStrings { }); - - def = this->add("prime_volume_mode", coEnum); - def->enum_values.push_back("Default"); - def->enum_values.push_back("Saving"); - def->enum_labels.push_back(L("Default")); - def->enum_labels.push_back(L("Saving")); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->set_default_value(new ConfigOptionEnum{ PrimeVolumeMode::pvmDefault }); - - - def = this->add("extruder_nozzle_count", coInts); - def->label = "extruder nozzle count"; - def->tooltip = "extruder nozzle count"; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInts{1}); - - def = this->add("extruder_nozzle_volume_type", coEnums); - def->label = "extruder nozzle volume type"; - def->tooltip = "extruder nozzle volume type"; - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back(L("Standard")); - def->enum_values.push_back(L("High Flow")); - def->enum_labels.push_back(L("Standard")); - def->enum_labels.push_back(L("High Flow")); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); - - def = this->add("printer_extruder_id", coInts); - def->label = "Printer extruder id"; - def->tooltip = "Printer extruder id"; - def->set_default_value(new ConfigOptionInts { 1 }); - def->cli = ConfigOptionDef::nocli; - - def = this->add("printer_extruder_variant", coStrings); - def->label = "Printer's extruder variant"; - def->tooltip = "Printer's extruder variant"; - def->set_default_value(new ConfigOptionStrings { "Direct Drive Standard" }); - def->cli = ConfigOptionDef::nocli; - - def = this->add("master_extruder_id", coInt); - def->label = "Master extruder id"; - def->tooltip = "Default extruder id to place filament"; - def->set_default_value(new ConfigOptionInt{ 1 }); - - def = this->add("print_extruder_id", coInts); - def->label = "Print extruder id"; - def->tooltip = "Print extruder id"; - def->set_default_value(new ConfigOptionInts { 1 }); - def->cli = ConfigOptionDef::nocli; - - def = this->add("print_extruder_variant", coStrings); - def->label = "Print's extruder variant"; - def->tooltip = "Print's extruder variant"; - def->set_default_value(new ConfigOptionStrings { "Direct Drive Standard" }); - def->cli = ConfigOptionDef::nocli; - - /*def = this->add("filament_extruder_id", coInts); - def->label = "Filament extruder id"; - def->tooltip = "Filament extruder id"; - def->set_default_value(new ConfigOptionInts { 1 }); - def->cli = ConfigOptionDef::nocli;*/ - - def = this->add("filament_extruder_variant", coStrings); - def->label = "Filament's extruder variant"; - def->tooltip = "Filament's extruder variant"; - def->set_default_value(new ConfigOptionStrings { "Direct Drive Standard" }); - def->cli = ConfigOptionDef::nocli; - - def = this->add("filament_self_index", coInts); - def->label = "Filament self index"; - def->tooltip = "Filament self index"; - def->set_default_value(new ConfigOptionInts { 1 }); - def->cli = ConfigOptionDef::nocli; - - def = this->add("filament_retract_length_nc", coFloats); - def->label = L("length when change hotend"); - def->tooltip = L("When this retraction value is modified, it will be used as the amount of filament retracted " - "inside the hotend before changing hotends."); - def->sidetext = L("mm"); - def->mode = comDevelop; - def->nullable = true; - def->min = 0; - def->max = 18; - def->set_default_value(new ConfigOptionFloatsNullable { 10. }); - - def = this->add("retract_restart_extra", coFloats); - def->label = L("Extra length on restart"); - //def->label = "Extra length on restart"; - def->tooltip = L("When the retraction is compensated after the travel move, the extruder will push " - "this additional amount of filament. This setting is rarely needed."); - def->sidetext = L("mm"); - def->mode = comDevelop; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 0. }); - - def = this->add("retract_restart_extra_toolchange", coFloats); - def->label = L("Extra length on restart"); - //def->label = "Extra length on restart"; - def->tooltip = L("When the retraction is compensated after changing tool, the extruder will push " - "this additional amount of filament."); - def->sidetext = L("mm"); - def->mode = comDevelop; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 0. }); - - def = this->add("retraction_speed", coFloats); - def->label = L("Retraction Speed"); - def->full_label = L("Retraction Speed"); - def->tooltip = L("Speed of retractions"); - def->sidetext = L("mm/s"); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 30. }); - - def = this->add("deretraction_speed", coFloats); - def->label = L("Deretraction Speed"); - def->full_label = L("Deretraction Speed"); - def->tooltip = L("Speed for reloading filament into extruder. Zero means the same speed as retraction"); - def->sidetext = L("mm/s"); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 0. }); - - def = this->add("seam_position", coEnum); - def->label = L("Seam position"); - def->category = L("Quality"); - def->tooltip = L("The start position to print each part of outer wall"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("nearest"); - def->enum_values.push_back("aligned"); - def->enum_values.push_back("back"); - def->enum_values.push_back("random"); - def->enum_labels.push_back(L("Nearest")); - def->enum_labels.push_back(L("Aligned")); - def->enum_labels.push_back(L("Back")); - def->enum_labels.push_back(L("Random")); - def->mode = comSimple; - def->set_default_value(new ConfigOptionEnum(spAligned)); - - def = this->add("seam_placement_away_from_overhangs", coBool); - def->label = L("Seam placement away from overhangs(experimental)"); - def->category = L("Quality"); - def->tooltip = L("Ensure seam placement away from overhangs for alignment and backing modes."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("seam_gap", coPercent); - def->label = L("Seam gap"); - def->category = L("Quality"); - def->tooltip = L("In order to reduce the visibility of the seam in a closed loop extrusion, the loop is interrupted and shortened by a specified amount.\n" "This amount as a percentage of the current extruder diameter. The default value for this parameter is 15"); - def->sidetext = "%"; - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionPercent(15)); - - def = this->add("seam_slope_conditional", coBool); - def->label = L("Smart scarf seam application"); - def->category = L("Quality"); - def->tooltip = L("Apply scarf joints only to smooth perimeters where traditional seams do not conceal the seams at sharp corners effectively."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("scarf_angle_threshold", coInt); - def->label = L("Scarf application angle threshold"); - def->category = L("Quality"); - def->tooltip = L("This option sets the threshold angle for applying a conditional scarf joint seam.\nIf the seam angle within the perimeter loop " "exceeds this value (indicating the absence of sharp corners), a scarf joint seam will be used. The default value is 155°."); - def->mode = comAdvanced; - def->sidetext = L("°"); - def->min = 0; - def->max = 180; - def->set_default_value(new ConfigOptionInt(155)); - - def = this->add("seam_slope_entire_loop", coBool); - def->label = L("Scarf around entire wall"); - def->category = L("Quality"); - def->tooltip = L("The scarf extends to the entire length of the wall."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("seam_slope_steps", coInt); - def->label = L("Scarf steps"); - def->category = L("Quality"); - def->tooltip = L("Minimum number of segments of each scarf."); - def->min = 1; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(10)); - - def = this->add("seam_slope_inner_walls", coBool); - def->label = L("Scarf joint for inner walls"); - def->category = L("Quality"); - def->tooltip = L("Use scarf joint for inner walls as well."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("override_filament_scarf_seam_setting", coBool); - def->label = L("Override filament scarf seam setting"); - def->category = L("Quality"); - def->tooltip = L("Overrider filament scarf seam setting and could control settings by modifier."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("seam_slope_type", coEnum); - def->label = L("Scarf seam type"); - def->category = L("Quality"); - def->tooltip = L("Set scarf seam type for this filament. This setting could minimize seam visibiliy."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("none"); - def->enum_values.push_back("external"); - def->enum_values.push_back("all"); - def->enum_labels.push_back(L("None")); - def->enum_labels.push_back(L("Contour")); - def->enum_labels.push_back(L("Contour and hole")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(SeamScarfType::None)); - - def = this->add("seam_slope_start_height", coFloatOrPercent); - def->label = L("Scarf start height"); - def->category = L("Quality"); - def->tooltip = L("This amount can be specified in millimeters or as a percentage of the current layer height."); - def->min = 0; - def->ratio_over = "layer_height"; - def->sidetext = L("mm/%"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent{10, true}); - - def = this->add("seam_slope_gap", coFloatOrPercent); - def->label = L("Scarf slope gap"); - def->category = L("Quality"); - def->tooltip = L("In order to reduce the visiblity of the seam in closed loop, the inner wall and outer wall are shortened by a specified amount."); - def->min = 0; - def->ratio_over = "nozzle_diameter"; - def->sidetext = L("mm/%"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent{0, 0}); - - def = this->add("seam_slope_min_length", coFloat); - def->label = L("Scarf length"); - def->category = L("Quality"); - def->tooltip = L("Length of the scarf. Setting this parameter to zero effectively disables the scarf."); - def->min = 0; - def->sidetext = "mm"; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat{10}); - - def = this->add("wipe_speed", coPercent); - def->label = L("Wipe speed"); - def->category = L("Quality"); - def->tooltip = L("The wipe speed is determined by the speed setting specified in this configuration." "If the value is expressed as a percentage (e.g. 80%), it will be calculated based on the travel speed setting above." "The default value for this parameter is 80%"); - def->sidetext = "%"; - def->min = 0.01; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionPercent(80)); - - def = this->add("role_base_wipe_speed", coBool); - def->label = L("Role-based wipe speed"); - def->category = L("Quality"); - def->tooltip = L("The wipe speed is determined by speed of current extrusion role. " "e.g if a wipe action is executed immediately following an outer wall extrusion, the speed of the outer wall extrusion will be utilized for the wipe action."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("skirt_distance", coFloat); - def->label = L("Skirt distance"); - def->tooltip = L("Distance from skirt to brim or object"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 55; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(2)); - - def = this->add("skirt_height", coInt); - def->label = L("Skirt height"); - //def->label = "Skirt height"; - def->tooltip = L("How many layers of skirt. Usually only one layer"); - def->sidetext = L("layers"); - def->mode = comSimple; - def->max = 10000; - def->set_default_value(new ConfigOptionInt(1)); - - def = this->add("draft_shield", coEnum); - //def->label = L("Draft shield"); - def->label = "Draft shield"; - //def->tooltip = L("With draft shield active, the skirt will be printed skirt_distance from the object, possibly intersecting brim.\n" - // "Enabled = skirt is as tall as the highest printed object.\n" - // "Limited = skirt is as tall as specified by skirt_height.\n" - // "This is useful to protect an ABS or ASA print from warping and detaching from print bed due to wind draft."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("disabled"); - def->enum_values.push_back("limited"); - def->enum_values.push_back("enabled"); - def->enum_labels.push_back("Disabled"); - def->enum_labels.push_back("Limited"); - def->enum_labels.push_back("Enabled"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionEnum(dsDisabled)); - - def = this->add("skirt_loops", coInt); - def->label = L("Skirt loops"); - def->full_label = L("Skirt loops"); - def->tooltip = L("Number of loops for the skirt. Zero means disabling skirt"); - def->min = 0; - def->max = 10; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInt(1)); - - def = this->add("slow_down_layer_time", coInts); - def->label = L("Layer time"); - def->tooltip = L("The printing speed in exported gcode will be slowed down, when the estimated layer time is shorter than this value, to " - "get better cooling for these layers"); - def->sidetext = L("s"); - def->min = 0; - def->max = 1000; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInts { 5 }); - - def = this->add("minimum_sparse_infill_area", coFloat); - def->label = L("Minimum sparse infill threshold"); - def->category = L("Strength"); - def->tooltip = L("Sparse infill area which is smaller than threshold value is replaced by internal solid infill"); - def->sidetext = L("mm²"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(15)); - - def = this->add("solid_infill_filament", coInt); - //def->label = L("Solid infill"); - //def->category = L("Extruders"); - //def->tooltip = L("Filament to print solid infill"); - def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Solid infill filament"); - def->category = L("Extruders"); - def->tooltip = L("Filament to print solid infill"); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInt(0)); - - def = this->add("internal_solid_infill_line_width", coFloat); - def->label = L("Internal solid infill"); - def->category = L("Quality"); - def->tooltip = L("Line width of internal solid infill"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("internal_solid_infill_speed", coFloats); - def->label = L("Internal solid infill"); - def->category = L("Speed"); - def->tooltip = L("Speed of internal solid infill, not the top and bottom surface"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{100}); - - def = this->add("spiral_mode", coBool); - def->label = L("Spiral vase"); - def->tooltip = L("Spiralize smooths out the z moves of the outer contour. " - "And turns a solid model into a single walled print with solid bottom layers. " - "The final generated model has no seam"); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("spiral_mode_smooth", coBool); - def->label = L("Smooth Spiral"); - def->tooltip = L("Smooth Spiral smoothes out X and Y moves as well" - "resulting in no visible seam at all, even in the XY directions on walls that are not vertical"); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("spiral_mode_max_xy_smoothing", coFloatOrPercent); - def->label = L("Max XY Smoothing"); - def->tooltip = L("Maximum distance to move points in XY to try to achieve a smooth spiral" - "If expressed as a %, it will be computed over nozzle diameter"); - def->sidetext = L("mm or %"); - def->ratio_over = "nozzle_diameter"; - def->min = 0; - def->max = 1000; - def->max_literal = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(200, true)); - - def = this->add("timelapse_type", coEnum); - def->label = L("Timelapse"); - def->tooltip = L("If smooth or traditional mode is selected, a timelapse video will be generated for each print. " - "After each layer is printed, a snapshot is taken with the chamber camera. " - "All of these snapshots are composed into a timelapse video when printing completes. " - "If smooth mode is selected, the toolhead will move to the excess chute after each layer is printed " - "and then take a snapshot. " - "Since the melt filament may leak from the nozzle during the process of taking a snapshot, " - "prime tower is required for smooth mode to wipe nozzle."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.emplace_back("0"); - def->enum_values.emplace_back("1"); - def->enum_labels.emplace_back(L("Traditional")); - def->enum_labels.emplace_back(L("Smooth")); - def->mode = comSimple; - def->set_default_value(new ConfigOptionEnum(tlTraditional)); - - def = this->add("standby_temperature_delta", coInt); - def->label = L("Temperature variation"); - //def->tooltip = L("Temperature difference to be applied when an extruder is not active. " - // "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."); - def->sidetext = "∆°C"; - def->min = -max_temp; - def->max = max_temp; - //BBS - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInt(-5)); - - def = this->add("machine_start_gcode", coString); - def->label = L("Start G-code"); - def->tooltip = L("Start G-code when start the whole printing"); - def->multiline = true; - def->full_width = true; - def->height = 12; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n")); - - def = this->add("filament_start_gcode", coStrings); - def->label = L("Start G-code"); - def->tooltip = L("Start G-code when start the printing of this filament"); - def->multiline = true; - def->full_width = true; - def->height = 12; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionStrings { " " }); - - def = this->add("single_extruder_multi_material", coBool); - //def->label = L("Single Extruder Multi Material"); - //def->tooltip = L("Use single nozzle to print multi filament"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("wipe_tower_no_sparse_layers", coBool); - //def->label = L("No sparse layers (EXPERIMENTAL)"); - //def->tooltip = L("If enabled, the wipe tower will not be printed on layers with no toolchanges. " - // "On layers with a toolchange, extruder will travel downward to print the wipe tower. " - // "User is responsible for ensuring there is no collision with the print."); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("slice_closing_radius", coFloat); - def->label = L("Slice gap closing radius"); - def->category = L("Quality"); - def->tooltip = L("Cracks smaller than 2x gap closing radius are being filled during the triangle mesh slicing. " - "The gap closing operation may reduce the final print resolution, therefore it is advisable to keep the value reasonably low."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.049)); - - def = this->add("slicing_mode", coEnum); - def->label = L("Slicing Mode"); - def->category = L("Other"); - def->tooltip = L("Use \"Even-odd\" for 3DLabPrint airplane models. Use \"Close holes\" to close all holes in the model."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("regular"); - def->enum_values.push_back("even_odd"); - def->enum_values.push_back("close_holes"); - def->enum_labels.push_back(L("Regular")); - def->enum_labels.push_back(L("Even-odd")); - def->enum_labels.push_back(L("Close holes")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(SlicingMode::Regular)); - - def = this->add("enable_support", coBool); - //BBS: remove material behind support - def->label = L("Enable support"); - def->category = L("Support"); - def->tooltip = L("Enable support generation."); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("support_type", coEnum); - def->label = L("Type"); - def->category = L("Support"); - def->tooltip = L("normal(auto) and tree(auto) is used to generate support automatically. " - "If normal(manual) or tree(manual) is selected, only support enforcers are generated"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("normal(auto)"); - def->enum_values.push_back("tree(auto)"); - def->enum_values.push_back("normal(manual)"); - def->enum_values.push_back("tree(manual)"); - def->enum_labels.push_back(L("normal(auto)")); - def->enum_labels.push_back(L("tree(auto)")); - def->enum_labels.push_back(L("normal(manual)")); - def->enum_labels.push_back(L("tree(manual)")); - def->mode = comSimple; - def->set_default_value(new ConfigOptionEnum(stNormalAuto)); - - def = this->add("support_object_xy_distance", coFloat); - def->label = L("Support/object xy distance"); - def->category = L("Support"); - def->tooltip = L("XY separation between an object and its support"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 10; - def->mode = comAdvanced; - //Support with too small spacing may touch the object and difficult to remove. - def->set_default_value(new ConfigOptionFloat(0.35)); - - def = this->add("support_object_first_layer_gap", coFloat); - def->label = L("Support/object first layer gap"); - def->category = L("Support"); - def->tooltip = L("XY separation between an object and its support at the first layer."); - def->sidetext = L("mm"); - def->min = 0; - def->max = 10; - def->mode = comAdvanced; - //Support with too small spacing may touch the object and difficult to remove. - def->set_default_value(new ConfigOptionFloat(0.2)); - - def = this->add("support_angle", coFloat); - def->label = L("Pattern angle"); - def->category = L("Support"); - def->tooltip = L("Use this setting to rotate the support pattern on the horizontal plane."); - def->sidetext = L("°"); - def->min = 0; - def->max = 359; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("support_on_build_plate_only", coBool); - def->label = L("On build plate only"); - def->category = L("Support"); - def->tooltip = L("Don't create support on model surface, only on build plate"); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); + def = this->add("fan_min_speed", coInts); + def->label = L("Fan speed"); + def->tooltip = L("Minimum speed for part cooling fan"); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInts{20}); + + def = this->add("additional_cooling_fan_speed", coInts); + def->label = L("Fan speed"); + def->tooltip = L("Speed of auxiliary part cooling fan. Auxiliary fan will run at this speed during printing except the first several layers " + "which are defined by no cooling layers"); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("min_layer_height", coFloats); + def->label = L("Min"); + def->tooltip = L("The lowest printable layer height for extruder. Used to limit " + "the minimum layer height when enable adaptive layer height"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.07}); + + def = this->add("slow_down_min_speed", coFloats); + def->label = L("Min print speed"); + def->tooltip = L("The minimum printing speed when slow down for cooling"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{10.}); + + def = this->add("nozzle_diameter", coFloats); + def->label = L("Nozzle diameter"); + def->tooltip = L("Diameter of nozzle"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->max = 1.0; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.4}); + + def = this->add("host_type", coEnum); + def->label = L("Host Type"); + def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain " + "the kind of the host."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("prusalink"); + def->enum_values.push_back("octoprint"); + def->enum_values.push_back("duet"); + def->enum_values.push_back("flashair"); + def->enum_values.push_back("astrobox"); + def->enum_values.push_back("repetier"); + def->enum_values.push_back("mks"); + def->enum_labels.push_back("PrusaLink"); + def->enum_labels.push_back("OctoPrint"); + def->enum_labels.push_back("Duet"); + def->enum_labels.push_back("FlashAir"); + def->enum_labels.push_back("AstroBox"); + def->enum_labels.push_back("Repetier"); + def->enum_labels.push_back("MKS"); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionEnum(htOctoPrint)); + + def = this->add("nozzle_volume", coFloats); + def->label = L("Nozzle volume"); + def->tooltip = L("Volume of nozzle between the cutter and the end of nozzle"); + def->sidetext = L("mm³"); + def->mode = comAdvanced; + def->readonly = true; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{{0.0}}); + + def = this->add("start_end_points", coPoints); + def->label = L("Start end points"); + def->tooltip = L("The start and end points which are from cutter area to garbage can."); + def->mode = comDevelop; + def->readonly = true; + // start and end point is from the change_filament_gcode + def->set_default_value(new ConfigOptionPoints{Vec2d(30, -3), Vec2d(54, 245)}); + + def = this->add("reduce_infill_retraction", coBool); + def->label = L("Reduce infill retraction"); + def->tooltip = L("Don't retract when the travel is in infill area absolutely. That means the oozing can't been seen. " + "This can reduce times of retraction for complex model and save printing time, but make slicing and " + "G-code generating slower"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("ooze_prevention", coBool); + def->label = L("Enable"); + // def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. " + // "It will enable a tall skirt automatically and move extruders outside such " + // "skirt when changing temperatures."); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("filename_format", coString); + def->label = L("Filename format"); + def->tooltip = L("User can self-define the project file name when export"); + def->full_width = true; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionString("[input_filename_base].gcode")); + + def = this->add("detect_overhang_wall", coBool); + def->label = L("Detect overhang wall"); + def->category = L("Quality"); + def->tooltip = L("Detect the overhang percentage relative to line width and use different speed to print. " + "For 100 percent overhang, bridge speed is used."); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("smooth_speed_discontinuity_area", coBool); + def->label = L("Smooth speed discontinuity area"); + def->category = L("Quality"); + def->tooltip = L("Add the speed transition between discontinuity area."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("smooth_coefficient", coFloat); + def->label = L("Smooth coefficient"); + def->category = L("Quality"); + def->tooltip = L("The smaller the number, the longer the speed transition path. 0 means not apply."); + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionFloat(80)); + + def = this->add("wall_filament", coInt); + // def->label = L("Walls"); + // def->category = L("Extruders"); + // def->tooltip = L("Filament to print walls"); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Walls filament"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print walls"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("inner_wall_line_width", coFloat); + def->label = L("Inner wall"); + def->category = L("Quality"); + def->tooltip = L("Line width of inner wall"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("inner_wall_speed", coFloats); + def->label = L("Inner wall"); + def->category = L("Speed"); + def->tooltip = L("Speed of inner wall"); + def->sidetext = L("mm/s"); + def->aliases = {"perimeter_feed_rate"}; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{60}); + + def = this->add("wall_loops", coInt); + def->label = L("Wall loops"); + def->category = L("Strength"); + def->tooltip = L("Number of walls of every layer"); + def->min = 0; + def->max = 1000; + def->set_default_value(new ConfigOptionInt(2)); + + def = this->add("embedding_wall_into_infill", coBool); + def->label = L("Embedding the wall into the infill"); + def->category = L("Strength"); + def->tooltip = L("Embedding the wall into parts where the wall loops are absent ensures that the wall connects seamlessly to the infill."); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("post_process", coStrings); + def->label = L("Post-processing Scripts"); + def->tooltip = L("If you want to process the output G-code through custom scripts, " + "just list their absolute paths here. Separate multiple scripts with a semicolon. " + "Scripts will be passed the absolute path to the G-code file as the first argument, " + "and variables of settings also can be read"); + def->gui_flags = "serialized"; + def->multiline = true; + def->full_width = true; + def->height = 6; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("printer_model", coString); + // def->label = L("Printer type"); + // def->tooltip = L("Type of the printer"); + def->label = "Printer type"; + def->tooltip = "Type of the printer"; + def->set_default_value(new ConfigOptionString()); + def->cli = ConfigOptionDef::nocli; - // BBS - def = this->add("support_critical_regions_only", coBool); - def->label = L("Support critical regions only"); - def->category = L("Support"); - def->tooltip = L("Only create support for critical regions including sharp tail, cantilever, etc."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("support_remove_small_overhang", coBool); - def->label = L("Remove small overhangs"); - def->category = L("Support"); - def->tooltip = L("Remove small overhangs that possibly need no supports."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - // BBS: change type to common float. - // It may be rounded to mulitple layer height when independent_support_layer_height is false. - def = this->add("support_top_z_distance", coFloat); - //def->gui_type = ConfigOptionDef::GUIType::f_enum_open; - def->label = L("Top Z distance"); - def->category = L("Support"); - def->tooltip = L("The z gap between the top support interface and object"); - def->sidetext = L("mm"); + def = this->add("printer_variant", coString); + // def->label = L("Printer variant"); + def->label = "Printer variant"; + // def->tooltip = L("Name of the printer variant. For example, the printer variants may be differentiated by a nozzle diameter."); + def->set_default_value(new ConfigOptionString()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("print_settings_id", coString); + def->set_default_value(new ConfigOptionString("")); + // BBS: open this option to command line + // def->cli = ConfigOptionDef::nocli; + + def = this->add("printer_settings_id", coString); + def->set_default_value(new ConfigOptionString("")); + // BBS: open this option to command line + // def->cli = ConfigOptionDef::nocli; + + def = this->add("raft_contact_distance", coFloat); + def->label = L("Raft contact Z distance"); + def->category = L("Support"); + def->tooltip = L("Z gap between object and raft. Ignored for soluble interface"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.1)); + + def = this->add("raft_expansion", coFloat); + def->label = L("Raft expansion"); + def->category = L("Support"); + def->tooltip = L("Expand all raft layers in XY plane"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.5)); + + def = this->add("raft_first_layer_density", coPercent); + def->label = L("Initial layer density"); + def->category = L("Support"); + def->tooltip = L("Density of the first raft or support layer"); + def->sidetext = "%"; + def->min = 10; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(90)); + + def = this->add("raft_first_layer_expansion", coFloat); + def->label = L("Initial layer expansion"); + def->category = L("Support"); + def->tooltip = L("Expand the first raft or support layer to improve bed plate adhesion, -1 means auto"); + def->sidetext = L("mm"); + def->min = -1; + def->mode = comAdvanced; + // BBS: change from 3.0 to 2.0 + def->set_default_value(new ConfigOptionFloat(-1)); + + def = this->add("raft_layers", coInt); + def->label = L("Raft layers"); + def->category = L("Support"); + def->tooltip = L("Object will be raised by this number of support layers. " + "Use this function to avoid warping when print ABS"); + def->sidetext = L("layers"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("resolution", coFloat); + def->label = L("Resolution"); + def->tooltip = L("G-code path is generated after simplifying the contour of model to avoid too many points and gcode lines " + "in the gcode file. Smaller value means higher resolution and more time to slice"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.01)); + + def = this->add("retraction_minimum_travel", coFloats); + def->label = L("Travel distance threshold"); + def->tooltip = L("Only trigger retraction when the travel distance is longer than this threshold"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{2.}); + + def = this->add("retract_before_wipe", coPercents); + def->label = L("Retract amount before wipe"); + def->tooltip = L("The length of fast retraction before wipe, relative to retraction length"); + def->sidetext = "%"; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionPercentsNullable{100}); + + def = this->add("retract_when_changing_layer", coBools); + def->label = L("Retract when change layer"); + def->tooltip = L("Force a retraction when changes layer"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable{false}); + + def = this->add("retraction_length", coFloats); + def->label = L("Length"); + def->full_label = L("Retraction Length"); + def->tooltip = L("Some amount of material in extruder is pulled back to avoid ooze during long travel. " + "Set zero to disable retraction"); + def->sidetext = L("mm"); + def->mode = comSimple; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.8}); + + def = this->add("enable_long_retraction_when_cut", coInt); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInt{0}); + + def = this->add("long_retractions_when_cut", coBools); + def->label = L("Long retraction when cut(experimental)"); + def->tooltip = L("Experimental feature.Retracting and cutting off the filament at a longer distance during changes to minimize purge." + "While this reduces flush significantly, it may also raise the risk of nozzle clogs or other printing problems."); + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable{false}); + + def = this->add("retraction_distances_when_cut", coFloats); + def->label = L("Retraction distance when cut"); + def->tooltip = L("Experimental feature.Retraction length before cutting off during filament change"); + def->mode = comDevelop; + def->min = 10; + def->max = 18; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{18}); + + def = this->add("long_retractions_when_ec", coBools); + def->label = L("Long retraction when extruder change"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable{false}); + + def = this->add("retraction_distances_when_ec", coFloats); + def->label = L("Retraction distance when extruder change"); + def->mode = comAdvanced; + def->nullable = true; + def->min = 0; + def->max = 10; + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloatsNullable{10}); + + def = this->add("retract_length_toolchange", coFloats); + def->label = L("Length"); + // def->full_label = L("Retraction Length (Toolchange)"); + def->full_label = "Retraction Length (Toolchange)"; + // def->tooltip = L("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)."); + def->sidetext = L("mm"); + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{10.}); + + def = this->add("z_hop", coFloats); + def->label = L("Z hop when retract"); + def->tooltip = L("Whenever the retraction is done, the nozzle is lifted a little to create clearance between nozzle and the print. " + "It prevents nozzle from hitting the print when travel moves. " + "Using spiral line to lift z can prevent stringing"); + def->sidetext = L("mm"); + def->mode = comSimple; + def->min = 0; + def->max = 5; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.4}); + + def = this->add("retract_lift_above", coFloats); + def->label = L("Z hop lower boundary"); + def->tooltip = L("Z hop will only come into effect when Z is above this value and is below the parameter: \"Z hop upper boundary\""); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = 0; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); + + def = this->add("retract_lift_below", coFloats); + def->label = L("Z hop upper boundary"); + def->tooltip = L("If this value is positive, Z hop will only come into effect when Z is above the parameter: \"Z hop lower boundary\" and is below this value"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = 0; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); + + def = this->add("z_hop_types", coEnums); + def->label = L("Z Hop Type"); + def->tooltip = L(""); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("Auto Lift"); + def->enum_values.push_back("Normal Lift"); + def->enum_values.push_back("Slope Lift"); + def->enum_values.push_back("Spiral Lift"); + def->enum_labels.push_back(L("Auto")); + def->enum_labels.push_back(L("Normal")); + def->enum_labels.push_back(L("Slope")); + def->enum_labels.push_back(L("Spiral")); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionEnumsGenericNullable{ZHopType::zhtSpiral}); + + def = this->add("extruder_type", coEnums); + def->label = L("Type"); + def->tooltip = ("This setting is only used for initial value of manual calibration of pressure advance. Bowden extruder usually has larger pa value. This setting doesn't influence normal slicing"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("Direct Drive"); + def->enum_values.push_back("Bowden"); + def->enum_labels.push_back(L("Direct Drive")); + def->enum_labels.push_back(L("Bowden")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnumsGeneric{ExtruderType::etDirectDrive}); + + // BBS + def = this->add("nozzle_volume_type", coEnums); + def->label = L("Nozzle Volume Type"); + def->tooltip = ("Nozzle volume type"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back(L("Standard")); + def->enum_values.push_back(L("High Flow")); + def->enum_values.push_back("Hybrid"); + def->enum_labels.push_back(L("Standard")); + def->enum_labels.push_back(L("High Flow")); + def->enum_labels.push_back(L("Hybrid")); + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnumsGeneric{NozzleVolumeType::nvtStandard}); + + def = this->add("default_nozzle_volume_type", coEnums); + def->label = L("Default Nozzle Volume Type"); + def->tooltip = ("Default Nozzle volume type for extruders in this printer"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back(L("Standard")); + def->enum_values.push_back(L("High Flow")); + def->enum_values.push_back(L("Hybrid")); + def->enum_labels.push_back(L("Standard")); + def->enum_labels.push_back(L("High Flow")); + def->enum_labels.push_back(L("Hybrid")); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionEnumsGeneric{NozzleVolumeType::nvtStandard}); + + def = this->add("extruder_variant_list", coStrings); + def->label = "Extruder variant list"; + def->tooltip = "Extruder variant list"; + def->set_default_value(new ConfigOptionStrings{"Direct Drive Standard"}); + def->cli = ConfigOptionDef::nocli; + + def = this->add("extruder_ams_count", coStrings); + def->label = "Extruder ams count"; + def->tooltip = "Ams counts of per extruder"; + def->set_default_value(new ConfigOptionStrings{}); + + def = this->add("extruder_nozzle_stats", coStrings); + def->set_default_value(new ConfigOptionStrings{}); + + def = this->add("prime_volume_mode", coEnum); + def->enum_values.push_back("Default"); + def->enum_values.push_back("Saving"); + def->enum_labels.push_back(L("Default")); + def->enum_labels.push_back(L("Saving")); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->set_default_value(new ConfigOptionEnum{PrimeVolumeMode::pvmDefault}); + + def = this->add("extruder_nozzle_count", coInts); + def->label = "extruder nozzle count"; + def->tooltip = "extruder nozzle count"; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{1}); + + def = this->add("extruder_nozzle_volume_type", coEnums); + def->label = "extruder nozzle volume type"; + def->tooltip = "extruder nozzle volume type"; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back(L("Standard")); + def->enum_values.push_back(L("High Flow")); + def->enum_labels.push_back(L("Standard")); + def->enum_labels.push_back(L("High Flow")); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionEnumsGeneric{NozzleVolumeType::nvtStandard}); + + def = this->add("printer_extruder_id", coInts); + def->label = "Printer extruder id"; + def->tooltip = "Printer extruder id"; + def->set_default_value(new ConfigOptionInts{1}); + def->cli = ConfigOptionDef::nocli; + + def = this->add("printer_extruder_variant", coStrings); + def->label = "Printer's extruder variant"; + def->tooltip = "Printer's extruder variant"; + def->set_default_value(new ConfigOptionStrings{"Direct Drive Standard"}); + def->cli = ConfigOptionDef::nocli; + + def = this->add("master_extruder_id", coInt); + def->label = "Master extruder id"; + def->tooltip = "Default extruder id to place filament"; + def->set_default_value(new ConfigOptionInt{1}); + + def = this->add("print_extruder_id", coInts); + def->label = "Print extruder id"; + def->tooltip = "Print extruder id"; + def->set_default_value(new ConfigOptionInts{1}); + def->cli = ConfigOptionDef::nocli; + + def = this->add("print_extruder_variant", coStrings); + def->label = "Print's extruder variant"; + def->tooltip = "Print's extruder variant"; + def->set_default_value(new ConfigOptionStrings{"Direct Drive Standard"}); + def->cli = ConfigOptionDef::nocli; + + /*def = this->add("filament_extruder_id", coInts); + def->label = "Filament extruder id"; + def->tooltip = "Filament extruder id"; + def->set_default_value(new ConfigOptionInts { 1 }); + def->cli = ConfigOptionDef::nocli;*/ + + def = this->add("filament_extruder_variant", coStrings); + def->label = "Filament's extruder variant"; + def->tooltip = "Filament's extruder variant"; + def->set_default_value(new ConfigOptionStrings{"Direct Drive Standard"}); + def->cli = ConfigOptionDef::nocli; + + def = this->add("filament_self_index", coInts); + def->label = "Filament self index"; + def->tooltip = "Filament self index"; + def->set_default_value(new ConfigOptionInts{1}); + def->cli = ConfigOptionDef::nocli; + + def = this->add("filament_retract_length_nc", coFloats); + def->label = L("length when change hotend"); + def->tooltip = L("When this retraction value is modified, it will be used as the amount of filament retracted " + "inside the hotend before changing hotends."); + def->sidetext = L("mm"); + def->mode = comDevelop; + def->nullable = true; + def->min = 0; + def->max = 18; + def->set_default_value(new ConfigOptionFloatsNullable{10.}); + + def = this->add("retract_restart_extra", coFloats); + def->label = L("Extra length on restart"); + // def->label = "Extra length on restart"; + def->tooltip = L("When the retraction is compensated after the travel move, the extruder will push " + "this additional amount of filament. This setting is rarely needed."); + def->sidetext = L("mm"); + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); + + def = this->add("retract_restart_extra_toolchange", coFloats); + def->label = L("Extra length on restart"); + // def->label = "Extra length on restart"; + def->tooltip = L("When the retraction is compensated after changing tool, the extruder will push " + "this additional amount of filament."); + def->sidetext = L("mm"); + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); + + def = this->add("retraction_speed", coFloats); + def->label = L("Retraction Speed"); + def->full_label = L("Retraction Speed"); + def->tooltip = L("Speed of retractions"); + def->sidetext = L("mm/s"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{30.}); + + def = this->add("deretraction_speed", coFloats); + def->label = L("Deretraction Speed"); + def->full_label = L("Deretraction Speed"); + def->tooltip = L("Speed for reloading filament into extruder. Zero means the same speed as retraction"); + def->sidetext = L("mm/s"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); + + def = this->add("seam_position", coEnum); + def->label = L("Seam position"); + def->category = L("Quality"); + def->tooltip = L("The start position to print each part of outer wall"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("nearest"); + def->enum_values.push_back("aligned"); + def->enum_values.push_back("back"); + def->enum_values.push_back("random"); + def->enum_labels.push_back(L("Nearest")); + def->enum_labels.push_back(L("Aligned")); + def->enum_labels.push_back(L("Back")); + def->enum_labels.push_back(L("Random")); + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnum(spAligned)); + + def = this->add("seam_placement_away_from_overhangs", coBool); + def->label = L("Seam placement away from overhangs(experimental)"); + def->category = L("Quality"); + def->tooltip = L("Ensure seam placement away from overhangs for alignment and backing modes."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("seam_gap", coPercent); + def->label = L("Seam gap"); + def->category = L("Quality"); + def->tooltip = L("In order to reduce the visibility of the seam in a closed loop extrusion, the loop is interrupted and shortened by a specified amount.\n" + "This amount as a percentage of the current extruder diameter. The default value for this parameter is 15"); + def->sidetext = "%"; + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionPercent(15)); + + def = this->add("seam_slope_conditional", coBool); + def->label = L("Smart scarf seam application"); + def->category = L("Quality"); + def->tooltip = L("Apply scarf joints only to smooth perimeters where traditional seams do not conceal the seams at sharp corners effectively."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("scarf_angle_threshold", coInt); + def->label = L("Scarf application angle threshold"); + def->category = L("Quality"); + def->tooltip = L("This option sets the threshold angle for applying a conditional scarf joint seam.\nIf the seam angle within the perimeter loop " + "exceeds this value (indicating the absence of sharp corners), a scarf joint seam will be used. The default value is 155°."); + def->mode = comAdvanced; + def->sidetext = L("°"); + def->min = 0; + def->max = 180; + def->set_default_value(new ConfigOptionInt(155)); + + def = this->add("seam_slope_entire_loop", coBool); + def->label = L("Scarf around entire wall"); + def->category = L("Quality"); + def->tooltip = L("The scarf extends to the entire length of the wall."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("seam_slope_steps", coInt); + def->label = L("Scarf steps"); + def->category = L("Quality"); + def->tooltip = L("Minimum number of segments of each scarf."); + def->min = 1; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(10)); + + def = this->add("seam_slope_inner_walls", coBool); + def->label = L("Scarf joint for inner walls"); + def->category = L("Quality"); + def->tooltip = L("Use scarf joint for inner walls as well."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("override_filament_scarf_seam_setting", coBool); + def->label = L("Override filament scarf seam setting"); + def->category = L("Quality"); + def->tooltip = L("Overrider filament scarf seam setting and could control settings by modifier."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("seam_slope_type", coEnum); + def->label = L("Scarf seam type"); + def->category = L("Quality"); + def->tooltip = L("Set scarf seam type for this filament. This setting could minimize seam visibiliy."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("none"); + def->enum_values.push_back("external"); + def->enum_values.push_back("all"); + def->enum_labels.push_back(L("None")); + def->enum_labels.push_back(L("Contour")); + def->enum_labels.push_back(L("Contour and hole")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(SeamScarfType::None)); + + def = this->add("seam_slope_start_height", coFloatOrPercent); + def->label = L("Scarf start height"); + def->category = L("Quality"); + def->tooltip = L("This amount can be specified in millimeters or as a percentage of the current layer height."); + def->min = 0; + def->ratio_over = "layer_height"; + def->sidetext = L("mm/%"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent{10, true}); + + def = this->add("seam_slope_gap", coFloatOrPercent); + def->label = L("Scarf slope gap"); + def->category = L("Quality"); + def->tooltip = L("In order to reduce the visiblity of the seam in closed loop, the inner wall and outer wall are shortened by a specified amount."); + def->min = 0; + def->ratio_over = "nozzle_diameter"; + def->sidetext = L("mm/%"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent{0, 0}); + + def = this->add("seam_slope_min_length", coFloat); + def->label = L("Scarf length"); + def->category = L("Quality"); + def->tooltip = L("Length of the scarf. Setting this parameter to zero effectively disables the scarf."); + def->min = 0; + def->sidetext = "mm"; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat{10}); + + def = this->add("wipe_speed", coPercent); + def->label = L("Wipe speed"); + def->category = L("Quality"); + def->tooltip = L("The wipe speed is determined by the speed setting specified in this configuration." + "If the value is expressed as a percentage (e.g. 80%), it will be calculated based on the travel speed setting above." + "The default value for this parameter is 80%"); + def->sidetext = "%"; + def->min = 0.01; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionPercent(80)); + + def = this->add("role_base_wipe_speed", coBool); + def->label = L("Role-based wipe speed"); + def->category = L("Quality"); + def->tooltip = L("The wipe speed is determined by speed of current extrusion role. " + "e.g if a wipe action is executed immediately following an outer wall extrusion, the speed of the outer wall extrusion will be utilized for the wipe action."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("skirt_distance", coFloat); + def->label = L("Skirt distance"); + def->tooltip = L("Distance from skirt to brim or object"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 55; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(2)); + + def = this->add("skirt_height", coInt); + def->label = L("Skirt height"); + // def->label = "Skirt height"; + def->tooltip = L("How many layers of skirt. Usually only one layer"); + def->sidetext = L("layers"); + def->mode = comSimple; + def->max = 10000; + def->set_default_value(new ConfigOptionInt(1)); + + def = this->add("draft_shield", coEnum); + // def->label = L("Draft shield"); + def->label = "Draft shield"; + // def->tooltip = L("With draft shield active, the skirt will be printed skirt_distance from the object, possibly intersecting brim.\n" + // "Enabled = skirt is as tall as the highest printed object.\n" + // "Limited = skirt is as tall as specified by skirt_height.\n" + // "This is useful to protect an ABS or ASA print from warping and detaching from print bed due to wind draft."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("disabled"); + def->enum_values.push_back("limited"); + def->enum_values.push_back("enabled"); + def->enum_labels.push_back("Disabled"); + def->enum_labels.push_back("Limited"); + def->enum_labels.push_back("Enabled"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionEnum(dsDisabled)); + + def = this->add("skirt_loops", coInt); + def->label = L("Skirt loops"); + def->full_label = L("Skirt loops"); + def->tooltip = L("Number of loops for the skirt. Zero means disabling skirt"); + def->min = 0; + def->max = 10; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInt(1)); + + def = this->add("slow_down_layer_time", coInts); + def->label = L("Layer time"); + def->tooltip = L("The printing speed in exported gcode will be slowed down, when the estimated layer time is shorter than this value, to " + "get better cooling for these layers"); + def->sidetext = L("s"); + def->min = 0; + def->max = 1000; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInts{5}); + + def = this->add("minimum_sparse_infill_area", coFloat); + def->label = L("Minimum sparse infill threshold"); + def->category = L("Strength"); + def->tooltip = L("Sparse infill area which is smaller than threshold value is replaced by internal solid infill"); + def->sidetext = L("mm²"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(15)); + + def = this->add("solid_infill_filament", coInt); + // def->label = L("Solid infill"); + // def->category = L("Extruders"); + // def->tooltip = L("Filament to print solid infill"); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Solid infill filament"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print solid infill"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("internal_solid_infill_line_width", coFloat); + def->label = L("Internal solid infill"); + def->category = L("Quality"); + def->tooltip = L("Line width of internal solid infill"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("internal_solid_infill_speed", coFloats); + def->label = L("Internal solid infill"); + def->category = L("Speed"); + def->tooltip = L("Speed of internal solid infill, not the top and bottom surface"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{100}); + + def = this->add("spiral_mode", coBool); + def->label = L("Spiral vase"); + def->tooltip = L("Spiralize smooths out the z moves of the outer contour. " + "And turns a solid model into a single walled print with solid bottom layers. " + "The final generated model has no seam"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("spiral_mode_smooth", coBool); + def->label = L("Smooth Spiral"); + def->tooltip = L("Smooth Spiral smoothes out X and Y moves as well" + "resulting in no visible seam at all, even in the XY directions on walls that are not vertical"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("spiral_mode_max_xy_smoothing", coFloatOrPercent); + def->label = L("Max XY Smoothing"); + def->tooltip = L("Maximum distance to move points in XY to try to achieve a smooth spiral" + "If expressed as a %, it will be computed over nozzle diameter"); + def->sidetext = L("mm or %"); + def->ratio_over = "nozzle_diameter"; + def->min = 0; + def->max = 1000; + def->max_literal = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(200, true)); + + def = this->add("timelapse_type", coEnum); + def->label = L("Timelapse"); + def->tooltip = L("If smooth or traditional mode is selected, a timelapse video will be generated for each print. " + "After each layer is printed, a snapshot is taken with the chamber camera. " + "All of these snapshots are composed into a timelapse video when printing completes. " + "If smooth mode is selected, the toolhead will move to the excess chute after each layer is printed " + "and then take a snapshot. " + "Since the melt filament may leak from the nozzle during the process of taking a snapshot, " + "prime tower is required for smooth mode to wipe nozzle."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.emplace_back("0"); + def->enum_values.emplace_back("1"); + def->enum_labels.emplace_back(L("Traditional")); + def->enum_labels.emplace_back(L("Smooth")); + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnum(tlTraditional)); + + def = this->add("standby_temperature_delta", coInt); + def->label = L("Temperature variation"); + // def->tooltip = L("Temperature difference to be applied when an extruder is not active. " + // "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."); + def->sidetext = "∆°C"; + def->min = -max_temp; + def->max = max_temp; + // BBS + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInt(-5)); + + def = this->add("machine_start_gcode", coString); + def->label = L("Start G-code"); + def->tooltip = L("Start G-code when start the whole printing"); + def->multiline = true; + def->full_width = true; + def->height = 12; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n")); + + def = this->add("filament_start_gcode", coStrings); + def->label = L("Start G-code"); + def->tooltip = L("Start G-code when start the printing of this filament"); + def->multiline = true; + def->full_width = true; + def->height = 12; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionStrings{" "}); + + def = this->add("single_extruder_multi_material", coBool); + // def->label = L("Single Extruder Multi Material"); + // def->tooltip = L("Use single nozzle to print multi filament"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("wipe_tower_no_sparse_layers", coBool); + // def->label = L("No sparse layers (EXPERIMENTAL)"); + // def->tooltip = L("If enabled, the wipe tower will not be printed on layers with no toolchanges. " + // "On layers with a toolchange, extruder will travel downward to print the wipe tower. " + // "User is responsible for ensuring there is no collision with the print."); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("slice_closing_radius", coFloat); + def->label = L("Slice gap closing radius"); + def->category = L("Quality"); + def->tooltip = L("Cracks smaller than 2x gap closing radius are being filled during the triangle mesh slicing. " + "The gap closing operation may reduce the final print resolution, therefore it is advisable to keep the value reasonably low."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.049)); + + def = this->add("slicing_mode", coEnum); + def->label = L("Slicing Mode"); + def->category = L("Other"); + def->tooltip = L("Use \"Even-odd\" for 3DLabPrint airplane models. Use \"Close holes\" to close all holes in the model."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("regular"); + def->enum_values.push_back("even_odd"); + def->enum_values.push_back("close_holes"); + def->enum_labels.push_back(L("Regular")); + def->enum_labels.push_back(L("Even-odd")); + def->enum_labels.push_back(L("Close holes")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(SlicingMode::Regular)); + + def = this->add("enable_support", coBool); + // BBS: remove material behind support + def->label = L("Enable support"); + def->category = L("Support"); + def->tooltip = L("Enable support generation."); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("support_type", coEnum); + def->label = L("Type"); + def->category = L("Support"); + def->tooltip = L("normal(auto) and tree(auto) is used to generate support automatically. " + "If normal(manual) or tree(manual) is selected, only support enforcers are generated"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("normal(auto)"); + def->enum_values.push_back("tree(auto)"); + def->enum_values.push_back("normal(manual)"); + def->enum_values.push_back("tree(manual)"); + def->enum_labels.push_back(L("normal(auto)")); + def->enum_labels.push_back(L("tree(auto)")); + def->enum_labels.push_back(L("normal(manual)")); + def->enum_labels.push_back(L("tree(manual)")); + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnum(stNormalAuto)); + + def = this->add("support_object_xy_distance", coFloat); + def->label = L("Support/object xy distance"); + def->category = L("Support"); + def->tooltip = L("XY separation between an object and its support"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + // Support with too small spacing may touch the object and difficult to remove. + def->set_default_value(new ConfigOptionFloat(0.35)); + + def = this->add("support_object_first_layer_gap", coFloat); + def->label = L("Support/object first layer gap"); + def->category = L("Support"); + def->tooltip = L("XY separation between an object and its support at the first layer."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + // Support with too small spacing may touch the object and difficult to remove. + def->set_default_value(new ConfigOptionFloat(0.2)); + + def = this->add("support_angle", coFloat); + def->label = L("Pattern angle"); + def->category = L("Support"); + def->tooltip = L("Use this setting to rotate the support pattern on the horizontal plane."); + def->sidetext = L("°"); + def->min = 0; + def->max = 359; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("support_on_build_plate_only", coBool); + def->label = L("On build plate only"); + def->category = L("Support"); + def->tooltip = L("Don't create support on model surface, only on build plate"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + // BBS + def = this->add("support_critical_regions_only", coBool); + def->label = L("Support critical regions only"); + def->category = L("Support"); + def->tooltip = L("Only create support for critical regions including sharp tail, cantilever, etc."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("support_remove_small_overhang", coBool); + def->label = L("Remove small overhangs"); + def->category = L("Support"); + def->tooltip = L("Remove small overhangs that possibly need no supports."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + // BBS: change type to common float. + // It may be rounded to mulitple layer height when independent_support_layer_height is false. + def = this->add("support_top_z_distance", coFloat); + // def->gui_type = ConfigOptionDef::GUIType::f_enum_open; + def->label = L("Top Z distance"); + def->category = L("Support"); + def->tooltip = L("The z gap between the top support interface and object"); + def->sidetext = L("mm"); // def->min = 0; #if 0 //def->enum_values.push_back("0"); @@ -4570,2404 +4558,2549 @@ void PrintConfigDef::init_fff_params() //def->enum_labels.push_back(L("0.1 (semi-detachable)")); //def->enum_labels.push_back(L("0.2 (detachable)")); #endif - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.2)); - - // BBS:MusangKing - def = this->add("support_bottom_z_distance", coFloat); - def->label = L("Bottom Z distance"); - def->category = L("Support"); - def->tooltip = L("The z gap between the bottom support interface and object"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.2)); - - def = this->add("enforce_support_layers", coInt); - //def->label = L("Enforce support for the first"); - def->category = L("Support"); - //def->tooltip = L("Generate support material for the specified number of layers counting from bottom, " - // "regardless of whether normal support material is enabled or not and regardless " - // "of any angle threshold. This is useful for getting more adhesion of objects " - // "having a very thin or poor footprint on the build plate."); - def->sidetext = L("layers"); - //def->full_label = L("Enforce support for the first n layers"); - def->min = 0; - def->max = 5000; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionInt(0)); - - def = this->add("support_filament", coInt); - def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Support/raft base"); - def->category = L("Support"); - def->tooltip = L("Filament to print support base and raft. \"Default\" means no specific filament for support and current filament is used"); - def->min = 0; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInt(0)); - - def = this->add("support_interface_not_for_body",coBool); - def->label = L("Avoid interface filament for base"); - def->category = L("Support"); - def->tooltip = L("Avoid using support interface filament to print support base if possible."); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("support_line_width", coFloat); - def->label = L("Support"); - def->category = L("Quality"); - def->tooltip = L("Line width of support"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("support_interface_loop_pattern", coBool); - def->label = L("Interface use loop pattern"); - def->category = L("Support"); - def->tooltip = L("Cover the top contact layer of the supports with loops. Disabled by default."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("support_interface_filament", coInt); - def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Support/raft interface"); - def->category = L("Support"); - def->tooltip = L("Filament to print support interface. \"Default\" means no specific filament for support interface and current filament is used"); - def->min = 0; - // BBS - def->mode = comSimple; - def->set_default_value(new ConfigOptionInt(0)); - - auto support_interface_top_layers = def = this->add("support_interface_top_layers", coInt); - def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Top interface layers"); - def->category = L("Support"); - def->tooltip = L("Number of top interface layers"); - def->sidetext = L("layers"); - def->min = 0; - def->enum_values.push_back("0"); - def->enum_values.push_back("1"); - def->enum_values.push_back("2"); - def->enum_values.push_back("3"); - def->enum_labels.push_back("0"); - def->enum_labels.push_back("1"); - def->enum_labels.push_back("2"); - def->enum_labels.push_back("3"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(3)); - - def = this->add("support_interface_bottom_layers", coInt); - def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Bottom interface layers"); - def->category = L("Support"); - def->tooltip = L("Number of bottom interface layers"); - def->sidetext = L("layers"); - def->min = -1; - def->enum_values.push_back("-1"); - append(def->enum_values, support_interface_top_layers->enum_values); - def->enum_labels.push_back(L("Same as top")); - append(def->enum_labels, support_interface_top_layers->enum_labels); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(0)); - - def = this->add("support_interface_spacing", coFloat); - def->label = L("Top interface spacing"); - def->category = L("Support"); - def->tooltip = L("Spacing of interface lines. Zero means solid interface"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.5)); - - //BBS - def = this->add("support_bottom_interface_spacing", coFloat); - def->label = L("Bottom interface spacing"); - def->category = L("Support"); - def->tooltip = L("Spacing of bottom interface lines. Zero means solid interface"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(0.5)); - - def = this->add("support_interface_speed", coFloats); - def->label = L("Support interface"); - def->category = L("Speed"); - def->tooltip = L("Speed of support interface"); - def->sidetext = L("mm/s"); - def->min = 1; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{80}); - - def = this->add("support_base_pattern", coEnum); - def->label = L("Base pattern"); - def->category = L("Support"); - def->tooltip = L("Line pattern of support"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("default"); - def->enum_values.push_back("rectilinear"); - def->enum_values.push_back("rectilinear-grid"); - def->enum_values.push_back("honeycomb"); - def->enum_values.push_back("lightning"); - def->enum_values.push_back("hollow"); - def->enum_labels.push_back(L("Default")); - def->enum_labels.push_back(L("Rectilinear")); - def->enum_labels.push_back(L("Rectilinear grid")); - def->enum_labels.push_back(L("Honeycomb")); - def->enum_labels.push_back(L("Lightning")); - def->enum_labels.push_back(L("Hollow")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(smpDefault)); - - def = this->add("support_interface_pattern", coEnum); - def->label = L("Interface pattern"); - def->category = L("Support"); - def->tooltip = L("Line pattern of support interface. " - "Default pattern for support interface is Rectilinear Interlaced"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("auto"); - def->enum_values.push_back("rectilinear"); - def->enum_values.push_back("concentric"); - def->enum_values.push_back("rectilinear_interlaced"); - def->enum_values.push_back("grid"); - def->enum_labels.push_back(L("Default")); - def->enum_labels.push_back(L("Rectilinear")); - def->enum_labels.push_back(L("Concentric")); - def->enum_labels.push_back(L("Rectilinear Interlaced")); - def->enum_labels.push_back(L("Grid")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(smipAuto)); - - def = this->add("support_base_pattern_spacing", coFloat); - def->label = L("Base pattern spacing"); - def->category = L("Support"); - def->tooltip = L("Spacing between support lines"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(2.5)); - - def = this->add("support_expansion", coFloat); - def->label = L("Normal Support expansion"); - def->category = L("Support"); - def->tooltip = L("Expand (+) or shrink (-) the horizontal span of normal support"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("support_speed", coFloats); - def->label = L("Support"); - def->category = L("Speed"); - def->tooltip = L("Speed of support"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{ 80 }); - - def = this->add("support_style", coEnum); - def->label = L("Style"); - def->category = L("Support"); - def->tooltip = L("Style and shape of the support. For normal support, projecting the supports into a regular grid " - "will create more stable supports (default), while snug support towers will save material and reduce " - "object scarring.\n" - "For tree support, slim style will merge branches more aggressively and save " - "a lot of material, strong style will make larger and stronger support structure and use more materials, " - "while hybrid style is the combination of slim tree and normal support with normal nodes " - "under large flat overhangs. Organic style will produce more organic shaped tree structure and less interfaces which makes it easer to be removed. " - "The default style is organic tree for most cases, and hybrid tree if adaptive layer height or soluble interface is enabled."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("default"); - def->enum_values.push_back("grid"); - def->enum_values.push_back("snug"); - def->enum_values.push_back("tree_slim"); - def->enum_values.push_back("tree_strong"); - def->enum_values.push_back("tree_hybrid"); - def->enum_values.push_back("tree_organic"); - def->enum_labels.push_back(L("Default")); - def->enum_labels.push_back(L("Grid")); - def->enum_labels.push_back(L("Snug")); - def->enum_labels.push_back(L("Tree Slim")); - def->enum_labels.push_back(L("Tree Strong")); - def->enum_labels.push_back(L("Tree Hybrid")); - def->enum_labels.push_back(L("Tree Organic")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(smsDefault)); - - def = this->add("top_z_overrides_xy_distance", coBool); - def->label = L("Z overrides X/Y"); - def->category = L("Support"); - def->tooltip = L("When top z distance overrides support/object xy distance, give priority to ensuring that supports are generated beneath overhangs, " - "and a gap of the same size as top z distance is leaved with the model. Whereas in the opposite case, the gap between supports " - "and the model follows support/object xy distance all the time. Only recommended to enable when using HybridTree."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("independent_support_layer_height", coBool); - def->label = L("Independent support layer height"); - def->category = L("Support"); - def->tooltip = L("Support layer uses layer height independent with object layer. This is to support customizing z-gap and save print time." - "This option will be invalid when the prime tower is enabled."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("support_threshold_angle", coInt); - def->label = L("Threshold angle"); - def->category = L("Support"); - def->tooltip = L("Support will be generated for overhangs whose slope angle is below the threshold."); - def->sidetext = L("°"); - def->min = 1; - def->max = 90; - def->mode = comSimple; - def->set_default_value(new ConfigOptionInt(30)); - - def = this->add("tree_support_branch_angle", coFloat); - def->label = L("Branch angle"); - def->category = L("Support"); - def->tooltip = L("This setting determines the maximum overhang angle that t he branches of tree support allowed to make." - "If the angle is increased, the branches can be printed more horizontally, allowing them to reach farther."); - def->sidetext = L("°"); - def->min = 0; - def->max = 60; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(40.)); - - def = this->add("tree_support_branch_distance", coFloat); - def->label = L("Branch distance"); - def->category = L("Support"); - def->tooltip = L("This setting determines the distance between neighboring tree support nodes."); - def->sidetext = L("mm"); - def->min = 1.0; - def->max = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(5.)); - - def = this->add("tree_support_branch_diameter", coFloat); - def->label = L("Branch diameter"); - def->category = L("Support"); - def->tooltip = L("This setting determines the initial diameter of support nodes."); - def->sidetext = L("mm"); - def->min = 1.0; - def->max = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(5.)); - - def = this->add("tree_support_branch_diameter_angle", coFloat); - def->label = L("Branch diameter angle"); - def->category = L("Support"); - def->tooltip = L("The angle of the branches' diameter as they gradually become thicker towards the bottom. " - "An angle of 0 will cause the branches to have uniform thickness over their length. " - "A bit of an angle can increase stability of the tree support."); - def->sidetext = L("°"); - def->min = 0.0; - def->max = 15; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(5.)); - - def = this->add("tree_support_wall_count", coInt); - def->label = L("Support wall loops"); - def->category = L("Support"); - def->tooltip = L("This setting specifies the count of support walls in the range of [-1,2]. -1 means auto, " - "and 0 means allowing infill-only mode where support is thick enough."); - def->min = -1; - def->max = 2; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(-1)); - - def = this->add("chamber_temperatures", coInts); - def->label = L("Chamber temperature"); - def->tooltip = L("Higher chamber temperature can help suppress or reduce warping and potentially lead to higher interlayer bonding strength for high temperature materials like ABS, ASA, PC, PA and so on." - "At the same time, the air filtration of ABS and ASA will get worse.While for PLA, PETG, TPU, PVA and other low temperature materials," - "the actual chamber temperature should not be high to avoid cloggings, so 0 which stands for turning off is highly recommended" - ); - def->sidetext = "°C"; - def->full_label = L("Chamber temperature"); - def->min = 0; - def->max = 80; - def->set_default_value(new ConfigOptionInts{0}); - - def = this->add("nozzle_temperature", coInts); - def->label = L("Other layers"); - def->tooltip = L("Nozzle temperature for layers after the initial one"); - def->sidetext = "°C"; - def->full_label = L("Nozzle temperature"); - def->min = 0; - def->max = max_temp; - def->nullable = true; - def->set_default_value(new ConfigOptionIntsNullable { 200 }); - - def = this->add("nozzle_temperature_range_low", coInts); - def->label = L("Min"); - //def->tooltip = ""; - def->sidetext = "°C"; - def->min = 0; - def->max = max_temp; - def->set_default_value(new ConfigOptionInts { 190 }); - - def = this->add("nozzle_temperature_range_high", coInts); - def->label = L("Max"); - //def->tooltip = ""; - def->sidetext = "°C"; - def->min = 0; - def->max = max_temp; - def->set_default_value(new ConfigOptionInts { 240 }); - - def = this->add("head_wrap_detect_zone", coPoints); - def->label ="Head wrap detect zone"; //do not need translation - def->mode = comDevelop; - def->set_default_value(new ConfigOptionPoints{}); - - def = this->add("impact_strength_z", coFloats); - def->label = L("Impact Strength Z"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloats{0}); - - def = this->add("detect_thin_wall", coBool); - def->label = L("Detect thin wall"); - def->category = L("Strength"); - def->tooltip = L("Detect thin wall which can't contain two line width. And use single line to print. " - "Maybe printed not very well, because it's not closed loop"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("change_filament_gcode", coString); - def->label = L("Change filament G-code"); - def->tooltip = L("This gcode is inserted when change filament, including T command to trigger tool change"); - def->multiline = true; - def->full_width = true; - def->height = 5; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("top_surface_line_width", coFloat); - def->label = L("Top surface"); - def->category = L("Quality"); - def->tooltip = L("Line width for top surfaces"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("top_surface_speed", coFloats); - def->label = L("Top surface"); - def->category = L("Speed"); - def->tooltip = L("Speed of top surface infill which is solid"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{100}); - - def = this->add("top_shell_layers", coInt); - def->label = L("Top shell layers"); - def->category = L("Strength"); - def->tooltip = L("This is the number of solid layers of top shell, including the top " - "surface layer. When the thickness calculated by this value is thinner " - "than top shell thickness, the top shell layers will be increased"); - def->full_label = L("Top solid layers"); - def->min = 0; - def->set_default_value(new ConfigOptionInt(4)); - - def = this->add("top_shell_thickness", coFloat); - def->label = L("Top shell thickness"); - def->category = L("Strength"); - def->tooltip = L("The number of top solid layers is increased when slicing if the thickness calculated by top shell layers is " - "thinner than this value. This can avoid having too thin shell when layer height is small. 0 means that " - "this setting is disabled and thickness of top shell is absolutely determained by top shell layers"); - def->full_label = L("Top shell thickness"); - def->sidetext = L("mm"); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(0.6)); - - def = this->add("top_color_penetration_layers", coInt); - def->label = L("Top paint penetration layers"); - def->category = L("Strength"); - def->tooltip = L("This is the number of layers of top paint penetration."); - def->min = 1; - def->set_default_value(new ConfigOptionInt(4)); - - def = this->add("bottom_color_penetration_layers", coInt); - def->label = L("Bottom paint penetration layers"); - def->category = L("Strength"); - def->tooltip = L("This is the number of layers of top bottom penetration."); - def->min = 1; - def->set_default_value(new ConfigOptionInt(3)); - - def = this->add("infill_instead_top_bottom_surfaces", coBool); - def->label = L("Use infill instead of top and bottom surfaces"); - def->category = L("Strength"); - def->tooltip = L("Using infill instead of top and bottom surfaces."); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("travel_speed", coFloats); - def->label = L("Travel"); - def->tooltip = L("Speed of travel which is faster and without extrusion"); - def->sidetext = L("mm/s"); - def->min = 1; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{120}); - - def = this->add("travel_speed_z", coFloats); - //def->label = L("Z travel"); - //def->tooltip = L("Speed of vertical travel along z axis. " - // "This is typically lower because build plate or gantry is hard to be moved. " - // "Zero means using travel speed directly in gcode, but will be limited by printer's ability when run gcode"); - def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comDevelop; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable{0.}); - - def = this->add("use_relative_e_distances", coBool); - def->label = L("Use relative E distances"); - def->tooltip = L("If your firmware requires relative E values, check this, " - "otherwise leave it unchecked. Must use relative e distance for Bambu printer"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("use_firmware_retraction",coBool); - def->label = L("Use firmware retraction"); - def->tooltip = L("Convert the retraction moves to G10 and G11 gcode"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("wipe", coBools); - def->label = L("Wipe while retracting"); - def->tooltip = L("Move nozzle along the last extrusion path when retracting to clean leaked material on nozzle. " - "This can minimize blob when printing new part after travel"); - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionBoolsNullable { false }); - - def = this->add("wipe_distance", coFloats); - def->label = L("Wipe Distance"); - def->tooltip = L("Describe how long the nozzle will move along the last path when retracting"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { 2. }); - - def = this->add("enable_prime_tower", coBool); - def->label = L("Enable"); - def->tooltip = L("The wiping tower can be used to clean up the residue on the nozzle and stabilize the chamber pressure inside the nozzle, " - "in order to avoid appearance defects when printing objects."); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("prime_tower_enable_framework", coBool); - def->label = L("Internal ribs"); - def->tooltip = L("Enable internal ribs to increase the stability of the prime tower."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("enable_circle_compensation", coBool); - def->label = L("Auto circle contour-hole compensation"); - def->tooltip = L("Expirment feature to compensate the circle holes and circle contour. " - "This feature is used to improve the accuracy of the circle holes and contour within the diameter below 50mm. " - "Only support PLA Basic, PLA CF, PET CF, PETG CF and PETG HF."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("circle_compensation_manual_offset", coFloat); - def->label = L("User Customized Offset"); - def->sidetext = L("mm"); - def->tooltip = L("If you want to have tighter or looser assemble, you can set this value. When it is positive, it indicates tightening, otherwise, it indicates loosening"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.0)); - - def = this->add("apply_scarf_seam_on_circles", coBool); - def->label = L("Scarf Seam On Compensation Circles"); - def->tooltip = L("Scarf seam will be applied on circles for better dimensional accuracy."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("circle_compensation_speed", coFloats); - def->label = L("Circle Compensation Speed"); - def->tooltip = L("circle_compensation_speed"); - def->sidetext = L("mm/s"); - def->min = 0; - def->set_default_value(new ConfigOptionFloats{200}); - - def = this->add("counter_coef_1", coFloats); - def->label = L("Counter Coef 1"); - def->tooltip = L("counter_coef_1"); - def->set_default_value(new ConfigOptionFloats{0}); - - def = this->add("counter_coef_2", coFloats); - def->label = L("Contour Coef 2"); - def->tooltip = L("counter_coef_2"); - def->set_default_value(new ConfigOptionFloats{0.025}); - - def = this->add("counter_coef_3", coFloats); - def->label = L("Contour Coef 3"); - def->tooltip = L("counter_coef_3"); - def->sidetext = L("mm"); - def->set_default_value(new ConfigOptionFloats{-0.11}); - - def = this->add("hole_coef_1", coFloats); - def->label = L("Hole Coef 1"); - def->tooltip = L("hole_coef_1"); - def->set_default_value(new ConfigOptionFloats{0}); - - def = this->add("hole_coef_2", coFloats); - def->label = L("Hole Coef 2"); - def->tooltip = L("hole_coef_2"); - def->set_default_value(new ConfigOptionFloats{-0.025}); - - def = this->add("hole_coef_3", coFloats); - def->label = L("Hole Coef 3"); - def->tooltip = L("hole_coef_3"); - def->sidetext = L("mm"); - def->set_default_value(new ConfigOptionFloats{0.28}); - - def = this->add("counter_limit_min", coFloats); - def->label = L("Contour limit min"); - def->tooltip = L("counter_limit_min"); - def->sidetext = L("mm"); - def->set_default_value(new ConfigOptionFloats{-0.04}); - - def = this->add("counter_limit_max", coFloats); - def->label = L("Contour limit max"); - def->tooltip = L("counter_limit_max"); - def->sidetext = L("mm"); - def->set_default_value(new ConfigOptionFloats{0.05}); - - def = this->add("hole_limit_min", coFloats); - def->label = L("Hole limit min"); - def->tooltip = L("hole_limit_min"); - def->sidetext = L("mm"); - def->set_default_value(new ConfigOptionFloats{0.08}); - - def = this->add("hole_limit_max", coFloats); - def->label = L("Hole limit max"); - def->tooltip = L("hole_limit_max"); - def->sidetext = L("mm"); - def->set_default_value(new ConfigOptionFloats{0.25}); - - def = this->add("diameter_limit", coFloats); - def->label = L("Diameter limit"); - def->tooltip = L("diameter_limit"); - def->sidetext = L("mm"); - def->set_default_value(new ConfigOptionFloats{50}); - - def = this->add("flush_volumes_vector", coFloats); - // BBS: remove _L()w - def->label = ("Purging volumes - load/unload volumes"); - //def->tooltip = L("This vector saves required volumes to change from/to each tool used on the " - // "wipe tower. These values are used to simplify creation of the full purging " - // "volumes below."); - - // BBS: change 70.f => 140.f - def->set_default_value(new ConfigOptionFloats { 140.f, 140.f, 140.f, 140.f, 140.f, 140.f, 140.f, 140.f }); - - def = this->add("flush_volumes_matrix", coFloats); - def->label = L("Purging volumes"); - //def->tooltip = L("This matrix describes volumes (in cubic milimetres) required to purge the" - // " new filament on the wipe tower for any given pair of tools."); - // BBS: change 140.f => 280.f - def->set_default_value(new ConfigOptionFloats { 0.f, 280.f, 280.f, 280.f, - 280.f, 0.f, 280.f, 280.f, - 280.f, 280.f, 0.f, 280.f, - 280.f, 280.f, 280.f, 0.f }); - - def = this->add("flush_multiplier", coFloats); - def->label = L("Flush multiplier"); - def->tooltip = L("The actual flushing volumes is equal to the flush multiplier multiplied by the flushing volumes in the table."); - def->sidetext = ""; - def->set_default_value(new ConfigOptionFloats{1.0}); - - // // BBS - // def = this->add("prime_volume", coFloat); - // def->label = L("Prime volume"); - // def->tooltip = L("The volume of material to prime extruder on tower."); - // def->sidetext = L("mm³"); - // def->min = 1.0; - // def->mode = comSimple; - // def->set_default_value(new ConfigOptionFloat(45.)); - - def = this->add("wipe_tower_x", coFloats); - //def->label = L("Position X"); - //def->tooltip = L("X coordinate of the left front corner of a wipe tower"); - //def->sidetext = L("mm"); - def->mode = comDevelop; - // BBS: change data type to floats to add partplate logic - def->set_default_value(new ConfigOptionFloats{ 15. }); - - def = this->add("wipe_tower_y", coFloats); - //def->label = L("Position Y"); - //def->tooltip = L("Y coordinate of the left front corner of a wipe tower"); - //def->sidetext = L("mm"); - def->mode = comDevelop; - // BBS: change data type to floats to add partplate logic - def->set_default_value(new ConfigOptionFloats{ 220. }); - - def = this->add("prime_tower_width", coFloat); - def->label = L("Width"); - def->tooltip = L("Width of prime tower"); - def->sidetext = L("mm"); - def->min = 2.0; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(35.)); - - def = this->add("wipe_tower_rotation_angle", coFloat); - //def->label = L("Wipe tower rotation angle"); - //def->tooltip = L("Wipe tower rotation angle with respect to x-axis."); - //def->sidetext = L("°"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(0.)); - - def = this->add("prime_tower_max_speed", coFloat); - def->label = L("Max speed"); - def->tooltip = L("The maximum printing speed on the prime tower excluding ramming."); - def->sidetext = L("mm/s"); - def->mode = comAdvanced; - def->min = 10; - def->set_default_value(new ConfigOptionFloat(90.)); - - def = this->add("prime_tower_lift_speed", coFloat); - def->set_default_value(new ConfigOptionFloat(90.)); - - def = this->add("prime_tower_lift_height", coFloat); - def->set_default_value(new ConfigOptionFloat(-1)); - - def = this->add("prime_tower_brim_width", coFloat); - def->gui_type = ConfigOptionDef::GUIType::f_enum_open; - def->label = L("Brim width"); - def->tooltip = L("Brim width of prime tower, negative number means auto calculated width based on the height of prime tower."); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->min = -1; - def->enum_values.push_back("-1"); - def->enum_labels.push_back(L("Auto")); - def->set_default_value(new ConfigOptionFloat(3.)); - - def = this->add("prime_tower_extra_rib_length", coFloat); - def->label = L("Extra rib length"); - def->tooltip = L("Positive values can increase the size of the rib wall, while negative values can reduce the size." - "However, the size of the rib wall can not be smaller than that determined by the cleaning volume."); - def->sidetext = L("mm"); - def->max = 300; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("prime_tower_rib_width", coFloat); - def->label = L("Rib width"); - def->tooltip = L("Rib width is always less than half the prime tower side length."); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->min = 0; - def->max = 300; - def->set_default_value(new ConfigOptionFloat(8)); - - def = this->add("prime_tower_skip_points", coBool); - def->label = L("Skip points"); - def->tooltip = L("The wall of prime tower will skip the start points of wipe path"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("prime_tower_flat_ironing", coBool); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("prime_tower_rib_wall", coBool); - def->label = L("Rib wall"); - def->tooltip = L("The wall of prime tower will add four ribs and make its " - "cross-section as close to a square as possible, so the width will be fixed."); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("prime_tower_fillet_wall", coBool); - def->label = L("Fillet wall"); - def->tooltip = L("The wall of prime tower will fillet"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("prime_tower_infill_gap", coPercent); - def->label = L("Infill gap"); - def->tooltip = L("Infill gap"); - def->sidetext = L("%"); - def->mode = comAdvanced; - def->min = 100; - def->set_default_value(new ConfigOptionPercent(150)); - - def = this->add("flush_into_infill", coBool); - def->category = L("Flush options"); - def->label = L("Flush into objects' infill"); - def->tooltip = L("Purging after filament change will be done inside objects' infills. " - "This may lower the amount of waste and decrease the print time. " - "If the walls are printed with transparent filament, the mixed color infill will be seen outside. " - "It will not take effect, unless the prime tower is enabled."); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("flush_into_support", coBool); - def->category = L("Flush options"); - def->label = L("Flush into objects' support"); - def->tooltip = L("Purging after filament change will be done inside objects' support. " - "This may lower the amount of waste and decrease the print time. " - "It will not take effect, unless the prime tower is enabled."); - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("flush_into_objects", coBool); - def->category = L("Flush options"); - def->label = L("Flush into this object"); - def->tooltip = L("This object will be used to purge the nozzle after a filament change to save filament and decrease the print time. " - "Colours of the objects will be mixed as a result. " - "It will not take effect, unless the prime tower is enabled."); - def->set_default_value(new ConfigOptionBool(false)); - - //BBS - //def = this->add("wipe_tower_bridging", coFloat); - //def->label = L("Maximal bridging distance"); - //def->tooltip = L("Maximal distance between supports on sparse infill sections."); - //def->sidetext = L("mm"); - //def->mode = comAdvanced; - //def->set_default_value(new ConfigOptionFloat(10.)); - - def = this->add("xy_hole_compensation", coFloat); - def->label = L("X-Y hole compensation"); - def->category = L("Quality"); - def->tooltip = L("Holes of object will be grown or shrunk in XY plane by the configured value. " - "Positive value makes holes bigger. Negative value makes holes smaller. " - "This function is used to adjust size slightly when the object has assembling issue"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("xy_contour_compensation", coFloat); - def->label = L("X-Y contour compensation"); - def->category = L("Quality"); - def->tooltip = L("Contour of object will be grown or shrunk in XY plane by the configured value. " - "Positive value makes contour bigger. Negative value makes contour smaller. " - "This function is used to adjust size slightly when the object has assembling issue"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("wall_generator", coEnum); - def->label = L("Wall generator"); - def->category = L("Quality"); - def->tooltip = L("Classic wall generator produces walls with constant extrusion width and for " - "very thin areas is used gap-fill. " - "Arachne engine produces walls with variable extrusion width"); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("classic"); - def->enum_values.push_back("arachne"); - def->enum_labels.push_back(L("Classic")); - def->enum_labels.push_back(L("Arachne")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(PerimeterGeneratorType::Arachne)); - - def = this->add("wall_transition_length", coPercent); - def->label = L("Wall transition length"); - def->category = L("Quality"); - def->tooltip = L("When transitioning between different numbers of walls as the part becomes " - "thinner, a certain amount of space is allotted to split or join the wall segments. " - "It's expressed as a percentage over nozzle diameter"); - def->sidetext = "%"; - def->mode = comAdvanced; - def->min = 0; - def->set_default_value(new ConfigOptionPercent(100)); - - def = this->add("wall_transition_filter_deviation", coPercent); - def->label = L("Wall transitioning filter margin"); - def->category = L("Quality"); - def->tooltip = L("Prevent transitioning back and forth between one extra wall and one less. This " - "margin extends the range of extrusion widths which follow to [Minimum wall width " - "- margin, 2 * Minimum wall width + margin]. Increasing this margin " - "reduces the number of transitions, which reduces the number of extrusion " - "starts/stops and travel time. However, large extrusion width variation can lead to " - "under- or overextrusion problems. " - "It's expressed as a percentage over nozzle diameter"); - def->sidetext = "%"; - def->mode = comAdvanced; - def->min = 0; - def->set_default_value(new ConfigOptionPercent(25)); - - def = this->add("wall_transition_angle", coFloat); - def->label = L("Wall transitioning threshold angle"); - def->category = L("Quality"); - def->tooltip = L("When to create transitions between even and odd numbers of walls. A wedge shape with" - " an angle greater than this setting will not have transitions and no walls will be " - "printed in the center to fill the remaining space. Reducing this setting reduces " - "the number and length of these center walls, but may leave gaps or overextrude"); - def->sidetext = L("°"); - def->mode = comAdvanced; - def->min = 1.; - def->max = 59.; - def->set_default_value(new ConfigOptionFloat(10.)); - - def = this->add("wall_distribution_count", coInt); - def->label = L("Wall distribution count"); - def->category = L("Quality"); - def->tooltip = L("The number of walls, counted from the center, over which the variation needs to be " - "spread. Lower values mean that the outer walls don't change in width"); - def->mode = comAdvanced; - def->min = 1; - def->set_default_value(new ConfigOptionInt(1)); - - def = this->add("min_feature_size", coPercent); - def->label = L("Minimum feature size"); - def->category = L("Quality"); - def->tooltip = L("Minimum thickness of thin features. Model features that are thinner than this value will " - "not be printed, while features thicker than the Minimum feature size will be widened to " - "the Minimum wall width. " - "It's expressed as a percentage over nozzle diameter"); - def->sidetext = "%"; - def->mode = comAdvanced; - def->min = 0; - def->set_default_value(new ConfigOptionPercent(25)); - - def = this->add("min_bead_width", coPercent); - def->label = L("Minimum wall width"); - def->category = L("Quality"); - def->tooltip = L("Width of the wall that will replace thin features (according to the Minimum feature size) " - "of the model. If the Minimum wall width is thinner than the thickness of the feature," - " the wall will become as thick as the feature itself. " - "It's expressed as a percentage over nozzle diameter"); - def->sidetext = "%"; - def->mode = comAdvanced; - def->min = 0; - def->set_default_value(new ConfigOptionPercent(85)); - - // Declare retract values for filament profile, overriding the printer's extruder profile. - for (auto& opt_key : filament_extruder_override_keys) { - const std::string filament_prefix = "filament_"; - std::string extruder_raw_key = opt_key.substr(opt_key.find(filament_prefix) + filament_prefix.length()); - auto it_opt = options.find(extruder_raw_key); - assert(it_opt != options.end()); - def = this->add_nullable(opt_key, it_opt->second.type); - def->label = it_opt->second.label; - def->full_label = it_opt->second.full_label; - def->tooltip = it_opt->second.tooltip; - def->sidetext = it_opt->second.sidetext; - def->enum_keys_map = it_opt->second.enum_keys_map; - def->enum_labels = it_opt->second.enum_labels; - def->enum_values = it_opt->second.enum_values; - def->min = it_opt->second.min; - def->max = it_opt->second.max; - //BBS: shown specific filament retract config because we hide the machine retract into comDevelop mode - if (opt_key =="filament_retraction_length"|| - opt_key=="filament_z_hop" || - opt_key== "filament_long_retractions_when_cut" || - opt_key=="filament_retraction_distances_when_cut") - def->mode = comSimple; - else - def->mode = comAdvanced; - switch (def->type) { - case coFloats: def->set_default_value(new ConfigOptionFloatsNullable(static_cast(it_opt->second.default_value.get())->values)); break; - case coPercents: def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); break; - case coBools: def->set_default_value(new ConfigOptionBoolsNullable(static_cast(it_opt->second.default_value.get())->values)); break; - case coEnums: def->set_default_value(new ConfigOptionEnumsGenericNullable(static_cast(it_opt->second.default_value.get())->values)); break; - default: assert(false); - } - } + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.2)); + + // BBS:MusangKing + def = this->add("support_bottom_z_distance", coFloat); + def->label = L("Bottom Z distance"); + def->category = L("Support"); + def->tooltip = L("The z gap between the bottom support interface and object"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.2)); + + def = this->add("enforce_support_layers", coInt); + // def->label = L("Enforce support for the first"); + def->category = L("Support"); + // def->tooltip = L("Generate support material for the specified number of layers counting from bottom, " + // "regardless of whether normal support material is enabled or not and regardless " + // "of any angle threshold. This is useful for getting more adhesion of objects " + // "having a very thin or poor footprint on the build plate."); + def->sidetext = L("layers"); + // def->full_label = L("Enforce support for the first n layers"); + def->min = 0; + def->max = 5000; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("support_filament", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Support/raft base"); + def->category = L("Support"); + def->tooltip = L("Filament to print support base and raft. \"Default\" means no specific filament for support and current filament is used"); + def->min = 0; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("support_interface_not_for_body", coBool); + def->label = L("Avoid interface filament for base"); + def->category = L("Support"); + def->tooltip = L("Avoid using support interface filament to print support base if possible."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("support_line_width", coFloat); + def->label = L("Support"); + def->category = L("Quality"); + def->tooltip = L("Line width of support"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("support_interface_loop_pattern", coBool); + def->label = L("Interface use loop pattern"); + def->category = L("Support"); + def->tooltip = L("Cover the top contact layer of the supports with loops. Disabled by default."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("support_interface_filament", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Support/raft interface"); + def->category = L("Support"); + def->tooltip = L("Filament to print support interface. \"Default\" means no specific filament for support interface and current filament is used"); + def->min = 0; + // BBS + def->mode = comSimple; + def->set_default_value(new ConfigOptionInt(0)); + + auto support_interface_top_layers = def = this->add("support_interface_top_layers", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Top interface layers"); + def->category = L("Support"); + def->tooltip = L("Number of top interface layers"); + def->sidetext = L("layers"); + def->min = 0; + def->enum_values.push_back("0"); + def->enum_values.push_back("1"); + def->enum_values.push_back("2"); + def->enum_values.push_back("3"); + def->enum_labels.push_back("0"); + def->enum_labels.push_back("1"); + def->enum_labels.push_back("2"); + def->enum_labels.push_back("3"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(3)); + + def = this->add("support_interface_bottom_layers", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Bottom interface layers"); + def->category = L("Support"); + def->tooltip = L("Number of bottom interface layers"); + def->sidetext = L("layers"); + def->min = -1; + def->enum_values.push_back("-1"); + append(def->enum_values, support_interface_top_layers->enum_values); + def->enum_labels.push_back(L("Same as top")); + append(def->enum_labels, support_interface_top_layers->enum_labels); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("support_interface_spacing", coFloat); + def->label = L("Top interface spacing"); + def->category = L("Support"); + def->tooltip = L("Spacing of interface lines. Zero means solid interface"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.5)); - def = this->add("detect_narrow_internal_solid_infill", coBool); - def->label = L("Detect narrow internal solid infill"); - def->category = L("Strength"); - def->tooltip = L("This option will auto detect narrow internal solid infill area." - " If enabled, concentric pattern will be used for the area to speed printing up." - " Otherwise, rectilinear pattern is used defaultly."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); -} + // BBS + def = this->add("support_bottom_interface_spacing", coFloat); + def->label = L("Bottom interface spacing"); + def->category = L("Support"); + def->tooltip = L("Spacing of bottom interface lines. Zero means solid interface"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(0.5)); + + def = this->add("support_interface_speed", coFloats); + def->label = L("Support interface"); + def->category = L("Speed"); + def->tooltip = L("Speed of support interface"); + def->sidetext = L("mm/s"); + def->min = 1; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{80}); + + def = this->add("support_base_pattern", coEnum); + def->label = L("Base pattern"); + def->category = L("Support"); + def->tooltip = L("Line pattern of support"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("default"); + def->enum_values.push_back("rectilinear"); + def->enum_values.push_back("rectilinear-grid"); + def->enum_values.push_back("honeycomb"); + def->enum_values.push_back("lightning"); + def->enum_values.push_back("hollow"); + def->enum_labels.push_back(L("Default")); + def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Rectilinear grid")); + def->enum_labels.push_back(L("Honeycomb")); + def->enum_labels.push_back(L("Lightning")); + def->enum_labels.push_back(L("Hollow")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(smpDefault)); + + def = this->add("support_interface_pattern", coEnum); + def->label = L("Interface pattern"); + def->category = L("Support"); + def->tooltip = L("Line pattern of support interface. " + "Default pattern for support interface is Rectilinear Interlaced"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("auto"); + def->enum_values.push_back("rectilinear"); + def->enum_values.push_back("concentric"); + def->enum_values.push_back("rectilinear_interlaced"); + def->enum_values.push_back("grid"); + def->enum_labels.push_back(L("Default")); + def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Rectilinear Interlaced")); + def->enum_labels.push_back(L("Grid")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(smipAuto)); + + def = this->add("support_base_pattern_spacing", coFloat); + def->label = L("Base pattern spacing"); + def->category = L("Support"); + def->tooltip = L("Spacing between support lines"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(2.5)); + + def = this->add("support_expansion", coFloat); + def->label = L("Normal Support expansion"); + def->category = L("Support"); + def->tooltip = L("Expand (+) or shrink (-) the horizontal span of normal support"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("support_speed", coFloats); + def->label = L("Support"); + def->category = L("Speed"); + def->tooltip = L("Speed of support"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{80}); + + def = this->add("support_style", coEnum); + def->label = L("Style"); + def->category = L("Support"); + def->tooltip = L("Style and shape of the support. For normal support, projecting the supports into a regular grid " + "will create more stable supports (default), while snug support towers will save material and reduce " + "object scarring.\n" + "For tree support, slim style will merge branches more aggressively and save " + "a lot of material, strong style will make larger and stronger support structure and use more materials, " + "while hybrid style is the combination of slim tree and normal support with normal nodes " + "under large flat overhangs. Organic style will produce more organic shaped tree structure and less interfaces which makes it easer to be removed. " + "The default style is organic tree for most cases, and hybrid tree if adaptive layer height or soluble interface is enabled."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("default"); + def->enum_values.push_back("grid"); + def->enum_values.push_back("snug"); + def->enum_values.push_back("tree_slim"); + def->enum_values.push_back("tree_strong"); + def->enum_values.push_back("tree_hybrid"); + def->enum_values.push_back("tree_organic"); + def->enum_labels.push_back(L("Default")); + def->enum_labels.push_back(L("Grid")); + def->enum_labels.push_back(L("Snug")); + def->enum_labels.push_back(L("Tree Slim")); + def->enum_labels.push_back(L("Tree Strong")); + def->enum_labels.push_back(L("Tree Hybrid")); + def->enum_labels.push_back(L("Tree Organic")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(smsDefault)); + + def = this->add("top_z_overrides_xy_distance", coBool); + def->label = L("Z overrides X/Y"); + def->category = L("Support"); + def->tooltip = L("When top z distance overrides support/object xy distance, give priority to ensuring that supports are generated beneath overhangs, " + "and a gap of the same size as top z distance is leaved with the model. Whereas in the opposite case, the gap between supports " + "and the model follows support/object xy distance all the time. Only recommended to enable when using HybridTree."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("independent_support_layer_height", coBool); + def->label = L("Independent support layer height"); + def->category = L("Support"); + def->tooltip = L("Support layer uses layer height independent with object layer. This is to support customizing z-gap and save print time." + "This option will be invalid when the prime tower is enabled."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("support_threshold_angle", coInt); + def->label = L("Threshold angle"); + def->category = L("Support"); + def->tooltip = L("Support will be generated for overhangs whose slope angle is below the threshold."); + def->sidetext = L("°"); + def->min = 1; + def->max = 90; + def->mode = comSimple; + def->set_default_value(new ConfigOptionInt(30)); + + def = this->add("tree_support_branch_angle", coFloat); + def->label = L("Branch angle"); + def->category = L("Support"); + def->tooltip = L("This setting determines the maximum overhang angle that t he branches of tree support allowed to make." + "If the angle is increased, the branches can be printed more horizontally, allowing them to reach farther."); + def->sidetext = L("°"); + def->min = 0; + def->max = 60; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(40.)); + + def = this->add("tree_support_branch_distance", coFloat); + def->label = L("Branch distance"); + def->category = L("Support"); + def->tooltip = L("This setting determines the distance between neighboring tree support nodes."); + def->sidetext = L("mm"); + def->min = 1.0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5.)); + + def = this->add("tree_support_branch_diameter", coFloat); + def->label = L("Branch diameter"); + def->category = L("Support"); + def->tooltip = L("This setting determines the initial diameter of support nodes."); + def->sidetext = L("mm"); + def->min = 1.0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5.)); + + def = this->add("tree_support_branch_diameter_angle", coFloat); + def->label = L("Branch diameter angle"); + def->category = L("Support"); + def->tooltip = L("The angle of the branches' diameter as they gradually become thicker towards the bottom. " + "An angle of 0 will cause the branches to have uniform thickness over their length. " + "A bit of an angle can increase stability of the tree support."); + def->sidetext = L("°"); + def->min = 0.0; + def->max = 15; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5.)); + + def = this->add("tree_support_wall_count", coInt); + def->label = L("Support wall loops"); + def->category = L("Support"); + def->tooltip = L("This setting specifies the count of support walls in the range of [-1,2]. -1 means auto, " + "and 0 means allowing infill-only mode where support is thick enough."); + def->min = -1; + def->max = 2; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(-1)); + + def = this->add("chamber_temperatures", coInts); + def->label = L("Chamber temperature"); + def->tooltip = L("Higher chamber temperature can help suppress or reduce warping and potentially lead to higher interlayer bonding strength for high temperature materials like ABS, ASA, PC, PA and so on." + "At the same time, the air filtration of ABS and ASA will get worse.While for PLA, PETG, TPU, PVA and other low temperature materials," + "the actual chamber temperature should not be high to avoid cloggings, so 0 which stands for turning off is highly recommended"); + def->sidetext = "°C"; + def->full_label = L("Chamber temperature"); + def->min = 0; + def->max = 80; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("nozzle_temperature", coInts); + def->label = L("Other layers"); + def->tooltip = L("Nozzle temperature for layers after the initial one"); + def->sidetext = "°C"; + def->full_label = L("Nozzle temperature"); + def->min = 0; + def->max = max_temp; + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable{200}); + + def = this->add("nozzle_temperature_range_low", coInts); + def->label = L("Min"); + // def->tooltip = ""; + def->sidetext = "°C"; + def->min = 0; + def->max = max_temp; + def->set_default_value(new ConfigOptionInts{190}); + + def = this->add("nozzle_temperature_range_high", coInts); + def->label = L("Max"); + // def->tooltip = ""; + def->sidetext = "°C"; + def->min = 0; + def->max = max_temp; + def->set_default_value(new ConfigOptionInts{240}); + + def = this->add("head_wrap_detect_zone", coPoints); + def->label = "Head wrap detect zone"; // do not need translation + def->mode = comDevelop; + def->set_default_value(new ConfigOptionPoints{}); + + def = this->add("impact_strength_z", coFloats); + def->label = L("Impact Strength Z"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("detect_thin_wall", coBool); + def->label = L("Detect thin wall"); + def->category = L("Strength"); + def->tooltip = L("Detect thin wall which can't contain two line width. And use single line to print. " + "Maybe printed not very well, because it's not closed loop"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("change_filament_gcode", coString); + def->label = L("Change filament G-code"); + def->tooltip = L("This gcode is inserted when change filament, including T command to trigger tool change"); + def->multiline = true; + def->full_width = true; + def->height = 5; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("top_surface_line_width", coFloat); + def->label = L("Top surface"); + def->category = L("Quality"); + def->tooltip = L("Line width for top surfaces"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("top_surface_speed", coFloats); + def->label = L("Top surface"); + def->category = L("Speed"); + def->tooltip = L("Speed of top surface infill which is solid"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{100}); + + def = this->add("top_shell_layers", coInt); + def->label = L("Top shell layers"); + def->category = L("Strength"); + def->tooltip = L("This is the number of solid layers of top shell, including the top " + "surface layer. When the thickness calculated by this value is thinner " + "than top shell thickness, the top shell layers will be increased"); + def->full_label = L("Top solid layers"); + def->min = 0; + def->set_default_value(new ConfigOptionInt(4)); + + def = this->add("top_shell_thickness", coFloat); + def->label = L("Top shell thickness"); + def->category = L("Strength"); + def->tooltip = L("The number of top solid layers is increased when slicing if the thickness calculated by top shell layers is " + "thinner than this value. This can avoid having too thin shell when layer height is small. 0 means that " + "this setting is disabled and thickness of top shell is absolutely determained by top shell layers"); + def->full_label = L("Top shell thickness"); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.6)); + + def = this->add("top_color_penetration_layers", coInt); + def->label = L("Top paint penetration layers"); + def->category = L("Strength"); + def->tooltip = L("This is the number of layers of top paint penetration."); + def->min = 1; + def->set_default_value(new ConfigOptionInt(4)); + + def = this->add("bottom_color_penetration_layers", coInt); + def->label = L("Bottom paint penetration layers"); + def->category = L("Strength"); + def->tooltip = L("This is the number of layers of top bottom penetration."); + def->min = 1; + def->set_default_value(new ConfigOptionInt(3)); + + def = this->add("infill_instead_top_bottom_surfaces", coBool); + def->label = L("Use infill instead of top and bottom surfaces"); + def->category = L("Strength"); + def->tooltip = L("Using infill instead of top and bottom surfaces."); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("travel_speed", coFloats); + def->label = L("Travel"); + def->tooltip = L("Speed of travel which is faster and without extrusion"); + def->sidetext = L("mm/s"); + def->min = 1; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{120}); + + def = this->add("travel_speed_z", coFloats); + // def->label = L("Z travel"); + // def->tooltip = L("Speed of vertical travel along z axis. " + // "This is typically lower because build plate or gantry is hard to be moved. " + // "Zero means using travel speed directly in gcode, but will be limited by printer's ability when run gcode"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); + + def = this->add("use_relative_e_distances", coBool); + def->label = L("Use relative E distances"); + def->tooltip = L("If your firmware requires relative E values, check this, " + "otherwise leave it unchecked. Must use relative e distance for Bambu printer"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("use_firmware_retraction", coBool); + def->label = L("Use firmware retraction"); + def->tooltip = L("Convert the retraction moves to G10 and G11 gcode"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("wipe", coBools); + def->label = L("Wipe while retracting"); + def->tooltip = L("Move nozzle along the last extrusion path when retracting to clean leaked material on nozzle. " + "This can minimize blob when printing new part after travel"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable{false}); + + def = this->add("wipe_distance", coFloats); + def->label = L("Wipe Distance"); + def->tooltip = L("Describe how long the nozzle will move along the last path when retracting"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{2.}); + + def = this->add("enable_prime_tower", coBool); + def->label = L("Enable"); + def->tooltip = L("The wiping tower can be used to clean up the residue on the nozzle and stabilize the chamber pressure inside the nozzle, " + "in order to avoid appearance defects when printing objects."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("prime_tower_enable_framework", coBool); + def->label = L("Internal ribs"); + def->tooltip = L("Enable internal ribs to increase the stability of the prime tower."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("enable_circle_compensation", coBool); + def->label = L("Auto circle contour-hole compensation"); + def->tooltip = L("Expirment feature to compensate the circle holes and circle contour. " + "This feature is used to improve the accuracy of the circle holes and contour within the diameter below 50mm. " + "Only support PLA Basic, PLA CF, PET CF, PETG CF and PETG HF."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("circle_compensation_manual_offset", coFloat); + def->label = L("User Customized Offset"); + def->sidetext = L("mm"); + def->tooltip = L("If you want to have tighter or looser assemble, you can set this value. When it is positive, it indicates tightening, otherwise, it indicates loosening"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("apply_scarf_seam_on_circles", coBool); + def->label = L("Scarf Seam On Compensation Circles"); + def->tooltip = L("Scarf seam will be applied on circles for better dimensional accuracy."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("circle_compensation_speed", coFloats); + def->label = L("Circle Compensation Speed"); + def->tooltip = L("circle_compensation_speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->set_default_value(new ConfigOptionFloats{200}); + + def = this->add("counter_coef_1", coFloats); + def->label = L("Counter Coef 1"); + def->tooltip = L("counter_coef_1"); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("counter_coef_2", coFloats); + def->label = L("Contour Coef 2"); + def->tooltip = L("counter_coef_2"); + def->set_default_value(new ConfigOptionFloats{0.025}); + + def = this->add("counter_coef_3", coFloats); + def->label = L("Contour Coef 3"); + def->tooltip = L("counter_coef_3"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{-0.11}); + + def = this->add("hole_coef_1", coFloats); + def->label = L("Hole Coef 1"); + def->tooltip = L("hole_coef_1"); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("hole_coef_2", coFloats); + def->label = L("Hole Coef 2"); + def->tooltip = L("hole_coef_2"); + def->set_default_value(new ConfigOptionFloats{-0.025}); + + def = this->add("hole_coef_3", coFloats); + def->label = L("Hole Coef 3"); + def->tooltip = L("hole_coef_3"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{0.28}); + + def = this->add("counter_limit_min", coFloats); + def->label = L("Contour limit min"); + def->tooltip = L("counter_limit_min"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{-0.04}); + + def = this->add("counter_limit_max", coFloats); + def->label = L("Contour limit max"); + def->tooltip = L("counter_limit_max"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{0.05}); + + def = this->add("hole_limit_min", coFloats); + def->label = L("Hole limit min"); + def->tooltip = L("hole_limit_min"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{0.08}); + + def = this->add("hole_limit_max", coFloats); + def->label = L("Hole limit max"); + def->tooltip = L("hole_limit_max"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{0.25}); + + def = this->add("diameter_limit", coFloats); + def->label = L("Diameter limit"); + def->tooltip = L("diameter_limit"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{50}); + + def = this->add("flush_volumes_vector", coFloats); + // BBS: remove _L()w + def->label = ("Purging volumes - load/unload volumes"); + // def->tooltip = L("This vector saves required volumes to change from/to each tool used on the " + // "wipe tower. These values are used to simplify creation of the full purging " + // "volumes below."); + + // BBS: change 70.f => 140.f + def->set_default_value(new ConfigOptionFloats{140.f, 140.f, 140.f, 140.f, 140.f, 140.f, 140.f, 140.f}); + + def = this->add("flush_volumes_matrix", coFloats); + def->label = L("Purging volumes"); + // def->tooltip = L("This matrix describes volumes (in cubic milimetres) required to purge the" + // " new filament on the wipe tower for any given pair of tools."); + // BBS: change 140.f => 280.f + def->set_default_value(new ConfigOptionFloats{0.f, 280.f, 280.f, 280.f, + 280.f, 0.f, 280.f, 280.f, + 280.f, 280.f, 0.f, 280.f, + 280.f, 280.f, 280.f, 0.f}); + + def = this->add("flush_multiplier", coFloats); + def->label = L("Flush multiplier"); + def->tooltip = L("The actual flushing volumes is equal to the flush multiplier multiplied by the flushing volumes in the table."); + def->sidetext = ""; + def->set_default_value(new ConfigOptionFloats{1.0}); + + // // BBS + // def = this->add("prime_volume", coFloat); + // def->label = L("Prime volume"); + // def->tooltip = L("The volume of material to prime extruder on tower."); + // def->sidetext = L("mm³"); + // def->min = 1.0; + // def->mode = comSimple; + // def->set_default_value(new ConfigOptionFloat(45.)); + + def = this->add("wipe_tower_x", coFloats); + // def->label = L("Position X"); + // def->tooltip = L("X coordinate of the left front corner of a wipe tower"); + // def->sidetext = L("mm"); + def->mode = comDevelop; + // BBS: change data type to floats to add partplate logic + def->set_default_value(new ConfigOptionFloats{15.}); + + def = this->add("wipe_tower_y", coFloats); + // def->label = L("Position Y"); + // def->tooltip = L("Y coordinate of the left front corner of a wipe tower"); + // def->sidetext = L("mm"); + def->mode = comDevelop; + // BBS: change data type to floats to add partplate logic + def->set_default_value(new ConfigOptionFloats{220.}); + + def = this->add("prime_tower_width", coFloat); + def->label = L("Width"); + def->tooltip = L("Width of prime tower"); + def->sidetext = L("mm"); + def->min = 2.0; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(35.)); + + def = this->add("wipe_tower_rotation_angle", coFloat); + // def->label = L("Wipe tower rotation angle"); + // def->tooltip = L("Wipe tower rotation angle with respect to x-axis."); + // def->sidetext = L("°"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("prime_tower_max_speed", coFloat); + def->label = L("Max speed"); + def->tooltip = L("The maximum printing speed on the prime tower excluding ramming."); + def->sidetext = L("mm/s"); + def->mode = comAdvanced; + def->min = 10; + def->set_default_value(new ConfigOptionFloat(90.)); + + def = this->add("prime_tower_lift_speed", coFloat); + def->set_default_value(new ConfigOptionFloat(90.)); + + def = this->add("prime_tower_lift_height", coFloat); + def->set_default_value(new ConfigOptionFloat(-1)); + + def = this->add("prime_tower_brim_width", coFloat); + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; + def->label = L("Brim width"); + def->tooltip = L("Brim width of prime tower, negative number means auto calculated width based on the height of prime tower."); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = -1; + def->enum_values.push_back("-1"); + def->enum_labels.push_back(L("Auto")); + def->set_default_value(new ConfigOptionFloat(3.)); + + def = this->add("prime_tower_extra_rib_length", coFloat); + def->label = L("Extra rib length"); + def->tooltip = L("Positive values can increase the size of the rib wall, while negative values can reduce the size." + "However, the size of the rib wall can not be smaller than that determined by the cleaning volume."); + def->sidetext = L("mm"); + def->max = 300; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("prime_tower_rib_width", coFloat); + def->label = L("Rib width"); + def->tooltip = L("Rib width is always less than half the prime tower side length."); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = 0; + def->max = 300; + def->set_default_value(new ConfigOptionFloat(8)); + + def = this->add("prime_tower_skip_points", coBool); + def->label = L("Skip points"); + def->tooltip = L("The wall of prime tower will skip the start points of wipe path"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("prime_tower_flat_ironing", coBool); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("prime_tower_rib_wall", coBool); + def->label = L("Rib wall"); + def->tooltip = L("The wall of prime tower will add four ribs and make its " + "cross-section as close to a square as possible, so the width will be fixed."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("prime_tower_fillet_wall", coBool); + def->label = L("Fillet wall"); + def->tooltip = L("The wall of prime tower will fillet"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("prime_tower_infill_gap", coPercent); + def->label = L("Infill gap"); + def->tooltip = L("Infill gap"); + def->sidetext = L("%"); + def->mode = comAdvanced; + def->min = 100; + def->set_default_value(new ConfigOptionPercent(150)); + + def = this->add("flush_into_infill", coBool); + def->category = L("Flush options"); + def->label = L("Flush into objects' infill"); + def->tooltip = L("Purging after filament change will be done inside objects' infills. " + "This may lower the amount of waste and decrease the print time. " + "If the walls are printed with transparent filament, the mixed color infill will be seen outside. " + "It will not take effect, unless the prime tower is enabled."); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("flush_into_support", coBool); + def->category = L("Flush options"); + def->label = L("Flush into objects' support"); + def->tooltip = L("Purging after filament change will be done inside objects' support. " + "This may lower the amount of waste and decrease the print time. " + "It will not take effect, unless the prime tower is enabled."); + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("flush_into_objects", coBool); + def->category = L("Flush options"); + def->label = L("Flush into this object"); + def->tooltip = L("This object will be used to purge the nozzle after a filament change to save filament and decrease the print time. " + "Colours of the objects will be mixed as a result. " + "It will not take effect, unless the prime tower is enabled."); + def->set_default_value(new ConfigOptionBool(false)); -void PrintConfigDef::init_extruder_option_keys() -{ - // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings - m_extruder_option_keys = { - "extruder_type", "nozzle_diameter", "default_nozzle_volume_type", "min_layer_height", "max_layer_height", "extruder_offset", - "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "retract_lift_above", "retract_lift_below","deretraction_speed", - "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", - "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", - "default_filament_profile","retraction_distances_when_cut","long_retractions_when_cut" - }; + // BBS + // def = this->add("wipe_tower_bridging", coFloat); + // def->label = L("Maximal bridging distance"); + // def->tooltip = L("Maximal distance between supports on sparse infill sections."); + // def->sidetext = L("mm"); + // def->mode = comAdvanced; + // def->set_default_value(new ConfigOptionFloat(10.)); + + def = this->add("xy_hole_compensation", coFloat); + def->label = L("X-Y hole compensation"); + def->category = L("Quality"); + def->tooltip = L("Holes of object will be grown or shrunk in XY plane by the configured value. " + "Positive value makes holes bigger. Negative value makes holes smaller. " + "This function is used to adjust size slightly when the object has assembling issue"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("xy_contour_compensation", coFloat); + def->label = L("X-Y contour compensation"); + def->category = L("Quality"); + def->tooltip = L("Contour of object will be grown or shrunk in XY plane by the configured value. " + "Positive value makes contour bigger. Negative value makes contour smaller. " + "This function is used to adjust size slightly when the object has assembling issue"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("wall_generator", coEnum); + def->label = L("Wall generator"); + def->category = L("Quality"); + def->tooltip = L("Classic wall generator produces walls with constant extrusion width and for " + "very thin areas is used gap-fill. " + "Arachne engine produces walls with variable extrusion width"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("classic"); + def->enum_values.push_back("arachne"); + def->enum_labels.push_back(L("Classic")); + def->enum_labels.push_back(L("Arachne")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(PerimeterGeneratorType::Arachne)); + + def = this->add("wall_transition_length", coPercent); + def->label = L("Wall transition length"); + def->category = L("Quality"); + def->tooltip = L("When transitioning between different numbers of walls as the part becomes " + "thinner, a certain amount of space is allotted to split or join the wall segments. " + "It's expressed as a percentage over nozzle diameter"); + def->sidetext = "%"; + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionPercent(100)); + + def = this->add("wall_transition_filter_deviation", coPercent); + def->label = L("Wall transitioning filter margin"); + def->category = L("Quality"); + def->tooltip = L("Prevent transitioning back and forth between one extra wall and one less. This " + "margin extends the range of extrusion widths which follow to [Minimum wall width " + "- margin, 2 * Minimum wall width + margin]. Increasing this margin " + "reduces the number of transitions, which reduces the number of extrusion " + "starts/stops and travel time. However, large extrusion width variation can lead to " + "under- or overextrusion problems. " + "It's expressed as a percentage over nozzle diameter"); + def->sidetext = "%"; + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionPercent(25)); + + def = this->add("wall_transition_angle", coFloat); + def->label = L("Wall transitioning threshold angle"); + def->category = L("Quality"); + def->tooltip = L("When to create transitions between even and odd numbers of walls. A wedge shape with" + " an angle greater than this setting will not have transitions and no walls will be " + "printed in the center to fill the remaining space. Reducing this setting reduces " + "the number and length of these center walls, but may leave gaps or overextrude"); + def->sidetext = L("°"); + def->mode = comAdvanced; + def->min = 1.; + def->max = 59.; + def->set_default_value(new ConfigOptionFloat(10.)); + + def = this->add("wall_distribution_count", coInt); + def->label = L("Wall distribution count"); + def->category = L("Quality"); + def->tooltip = L("The number of walls, counted from the center, over which the variation needs to be " + "spread. Lower values mean that the outer walls don't change in width"); + def->mode = comAdvanced; + def->min = 1; + def->set_default_value(new ConfigOptionInt(1)); + + def = this->add("min_feature_size", coPercent); + def->label = L("Minimum feature size"); + def->category = L("Quality"); + def->tooltip = L("Minimum thickness of thin features. Model features that are thinner than this value will " + "not be printed, while features thicker than the Minimum feature size will be widened to " + "the Minimum wall width. " + "It's expressed as a percentage over nozzle diameter"); + def->sidetext = "%"; + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionPercent(25)); + + def = this->add("min_bead_width", coPercent); + def->label = L("Minimum wall width"); + def->category = L("Quality"); + def->tooltip = L("Width of the wall that will replace thin features (according to the Minimum feature size) " + "of the model. If the Minimum wall width is thinner than the thickness of the feature," + " the wall will become as thick as the feature itself. " + "It's expressed as a percentage over nozzle diameter"); + def->sidetext = "%"; + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionPercent(85)); + + // Declare retract values for filament profile, overriding the printer's extruder profile. + for (auto &opt_key : filament_extruder_override_keys) + { + const std::string filament_prefix = "filament_"; + std::string extruder_raw_key = opt_key.substr(opt_key.find(filament_prefix) + filament_prefix.length()); + auto it_opt = options.find(extruder_raw_key); + assert(it_opt != options.end()); + def = this->add_nullable(opt_key, it_opt->second.type); + def->label = it_opt->second.label; + def->full_label = it_opt->second.full_label; + def->tooltip = it_opt->second.tooltip; + def->sidetext = it_opt->second.sidetext; + def->enum_keys_map = it_opt->second.enum_keys_map; + def->enum_labels = it_opt->second.enum_labels; + def->enum_values = it_opt->second.enum_values; + def->min = it_opt->second.min; + def->max = it_opt->second.max; + // BBS: shown specific filament retract config because we hide the machine retract into comDevelop mode + if (opt_key == "filament_retraction_length" || + opt_key == "filament_z_hop" || + opt_key == "filament_long_retractions_when_cut" || + opt_key == "filament_retraction_distances_when_cut") + def->mode = comSimple; + else + def->mode = comAdvanced; + switch (def->type) + { + case coFloats: + def->set_default_value(new ConfigOptionFloatsNullable(static_cast(it_opt->second.default_value.get())->values)); + break; + case coPercents: + def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); + break; + case coBools: + def->set_default_value(new ConfigOptionBoolsNullable(static_cast(it_opt->second.default_value.get())->values)); + break; + case coEnums: + def->set_default_value(new ConfigOptionEnumsGenericNullable(static_cast(it_opt->second.default_value.get())->values)); + break; + default: + assert(false); + } + } - m_extruder_retract_keys = { - "deretraction_speed", - "long_retractions_when_cut", - "retract_before_wipe", - "retract_lift_above", - "retract_lift_below", - "retract_restart_extra", - "retract_when_changing_layer", - "retraction_distances_when_cut", - "retraction_length", - "retraction_minimum_travel", - "retraction_speed", - "wipe", - "wipe_distance", - "z_hop", - "z_hop_types" - }; - assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); -} + def = this->add("detect_narrow_internal_solid_infill", coBool); + def->label = L("Detect narrow internal solid infill"); + def->category = L("Strength"); + def->tooltip = L("This option will auto detect narrow internal solid infill area." + " If enabled, concentric pattern will be used for the area to speed printing up." + " Otherwise, rectilinear pattern is used defaultly."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + } -void PrintConfigDef::init_filament_option_keys() -{ - m_filament_option_keys = { - "filament_diameter", "min_layer_height", "max_layer_height","volumetric_speed_coefficients", - "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "deretraction_speed", - "retract_before_wipe", "filament_retract_length_nc","retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", - "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "filament_colour", - "default_filament_profile","retraction_distances_when_cut","long_retractions_when_cut" - }; + void PrintConfigDef::init_extruder_option_keys() + { + // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings + m_extruder_option_keys = { + "extruder_type", "nozzle_diameter", "default_nozzle_volume_type", "min_layer_height", "max_layer_height", "extruder_offset", + "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "retract_lift_above", "retract_lift_below", "deretraction_speed", + "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", + "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", + "default_filament_profile", "retraction_distances_when_cut", "long_retractions_when_cut"}; + + m_extruder_retract_keys = { + "deretraction_speed", + "long_retractions_when_cut", + "retract_before_wipe", + "retract_lift_above", + "retract_lift_below", + "retract_restart_extra", + "retract_when_changing_layer", + "retraction_distances_when_cut", + "retraction_length", + "retraction_minimum_travel", + "retraction_speed", + "wipe", + "wipe_distance", + "z_hop", + "z_hop_types"}; + assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); + } - m_filament_retract_keys = { - "deretraction_speed", - "long_retractions_when_cut", - "retract_before_wipe", - "filament_retract_length_nc", - "retract_restart_extra", - "retract_when_changing_layer", - "retraction_distances_when_cut", - "retraction_length", - "retraction_minimum_travel", - "retraction_speed", - "wipe", - "wipe_distance", - "z_hop", - "z_hop_types" - }; - assert(std::is_sorted(m_filament_retract_keys.begin(), m_filament_retract_keys.end())); -} + void PrintConfigDef::init_filament_option_keys() + { + m_filament_option_keys = { + "filament_diameter", "min_layer_height", "max_layer_height", "volumetric_speed_coefficients", + "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "deretraction_speed", + "retract_before_wipe", "filament_retract_length_nc", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", + "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "filament_colour", + "default_filament_profile", "retraction_distances_when_cut", "long_retractions_when_cut"}; + + m_filament_retract_keys = { + "deretraction_speed", + "long_retractions_when_cut", + "retract_before_wipe", + "filament_retract_length_nc", + "retract_restart_extra", + "retract_when_changing_layer", + "retraction_distances_when_cut", + "retraction_length", + "retraction_minimum_travel", + "retraction_speed", + "wipe", + "wipe_distance", + "z_hop", + "z_hop_types"}; + assert(std::is_sorted(m_filament_retract_keys.begin(), m_filament_retract_keys.end())); + } -void PrintConfigDef::init_sla_params() -{ - ConfigOptionDef* def; - - // SLA Printer settings - - def = this->add("display_width", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->min = 1; - def->set_default_value(new ConfigOptionFloat(120.)); - - def = this->add("display_height", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->min = 1; - def->set_default_value(new ConfigOptionFloat(68.)); - - def = this->add("display_pixels_x", coInt); - def->full_label = L(" "); - def->label = ("X"); - def->tooltip = L(" "); - def->min = 100; - def->set_default_value(new ConfigOptionInt(2560)); - - def = this->add("display_pixels_y", coInt); - def->label = ("Y"); - def->tooltip = L(" "); - def->min = 100; - def->set_default_value(new ConfigOptionInt(1440)); - - def = this->add("display_mirror_x", coBool); - def->full_label = L(" "); - def->label = L(" "); - def->tooltip = L(" "); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("display_mirror_y", coBool); - def->full_label = L(" "); - def->label = L(" "); - def->tooltip = L(" "); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("display_orientation", coEnum); - def->label = L(" "); - def->tooltip = L(" "); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("landscape"); - def->enum_values.push_back("portrait"); - def->enum_labels.push_back(L(" ")); - def->enum_labels.push_back(L(" ")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(sladoPortrait)); - - def = this->add("fast_tilt_time", coFloat); - def->label = L(" "); - def->full_label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(5.)); - - def = this->add("slow_tilt_time", coFloat); - def->label = L(" "); - def->full_label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(8.)); - - def = this->add("area_fill", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(50.)); - - def = this->add("relative_correction", coFloats); - def->label = L(" "); - def->full_label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats( { 1., 1.} )); - - def = this->add("relative_correction_x", coFloat); - def->label = L(" "); - def->full_label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.)); - - def = this->add("relative_correction_y", coFloat); - def->label = L(" "); - def->full_label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.)); - - def = this->add("relative_correction_z", coFloat); - def->label = L(" "); - def->full_label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.)); - - def = this->add("absolute_correction", coFloat); - def->label = L(" "); - def->full_label = L(" "); - def->tooltip = L(" "); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.0)); - - def = this->add("elefant_foot_min_width", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.2)); - - def = this->add("gamma_correction", coFloat); - def->label = L(" "); - def->full_label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->max = 1; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); - - - // SLA Material settings. - - def = this->add("material_colour", coString); - def->label = L(" "); - def->tooltip = L(" "); - def->gui_type = ConfigOptionDef::GUIType::color; - def->set_default_value(new ConfigOptionString("#29B2B2")); - - def = this->add("material_type", coString); - def->label = L(" "); - def->tooltip = L(" "); - def->gui_type = ConfigOptionDef::GUIType::f_enum_open; // TODO: ??? - def->gui_flags = "show_value"; - def->enum_values.push_back("Tough"); - def->enum_values.push_back("Flexible"); - def->enum_values.push_back("Casting"); - def->enum_values.push_back("Dental"); - def->enum_values.push_back("Heat-resistant"); - def->set_default_value(new ConfigOptionString("Tough")); - - def = this->add("initial_layer_height", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(0.3)); - - def = this->add("bottle_volume", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 50; - def->set_default_value(new ConfigOptionFloat(1000.0)); - - def = this->add("bottle_weight", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("material_density", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("bottle_cost", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(0.0)); - - def = this->add("faded_layers", coInt); - def->label = L(" "); - def->tooltip = L(" "); - def->min = 3; - def->max = 20; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(10)); - - def = this->add("min_exposure_time", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("max_exposure_time", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(100)); - - def = this->add("exposure_time", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(10)); - - def = this->add("min_initial_exposure_time", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("max_initial_exposure_time", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(150)); - - def = this->add("initial_exposure_time", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(15)); - - def = this->add("material_correction", coFloats); - def->full_label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats( { 1., 1., 1. } )); - - def = this->add("material_correction_x", coFloat); - def->full_label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.)); - - def = this->add("material_correction_y", coFloat); - def->full_label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.)); - - def = this->add("material_correction_z", coFloat); - def->full_label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.)); - - def = this->add("material_vendor", coString); - def->set_default_value(new ConfigOptionString("")); - def->cli = ConfigOptionDef::nocli; - - def = this->add("default_sla_material_profile", coString); - def->label = L(" "); - def->tooltip = L(" "); - def->set_default_value(new ConfigOptionString()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("sla_material_settings_id", coString); - def->set_default_value(new ConfigOptionString("")); - def->cli = ConfigOptionDef::nocli; - - def = this->add("default_sla_print_profile", coString); - def->label = L(" "); - def->tooltip = L(" "); - def->set_default_value(new ConfigOptionString()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("sla_print_settings_id", coString); - def->set_default_value(new ConfigOptionString("")); - def->cli = ConfigOptionDef::nocli; - - def = this->add("supports_enable", coBool); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("support_head_front_diameter", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("support_head_penetration", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->mode = comAdvanced; - def->min = 0; - def->set_default_value(new ConfigOptionFloat(0.2)); - - def = this->add("support_head_width", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 20; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("support_pillar_diameter", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 15; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("support_small_pillar_diameter_percent", coPercent); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 1; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(50)); - - def = this->add("support_max_bridges_on_pillar", coInt); - def->label = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->max = 50; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(3)); - - def = this->add("support_pillar_connection_mode", coEnum); - def->label = L(" "); - def->tooltip = L(" "); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("zigzag"); - def->enum_values.push_back("cross"); - def->enum_values.push_back("dynamic"); - def->enum_labels.push_back(L(" ")); - def->enum_labels.push_back(L(" ")); - def->enum_labels.push_back(L(" ")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(slapcmDynamic)); - - def = this->add("support_buildplate_only", coBool); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("support_pillar_widening_factor", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->max = 1; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.0)); - - def = this->add("support_base_diameter", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 30; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(4.0)); - - def = this->add("support_base_height", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("support_base_safety_distance", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1)); - - def = this->add("support_critical_angle", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 90; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(45)); - - def = this->add("support_max_bridge_length", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(15.0)); - - def = this->add("support_max_pillar_link_distance", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; // 0 means no linking - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(10.0)); - - def = this->add("support_object_elevation", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 150; // This is the max height of print on SL1 - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(5.0)); - - def = this->add("support_points_density_relative", coInt); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->set_default_value(new ConfigOptionInt(100)); - - def = this->add("support_points_minimal_distance", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L("mm"); - def->min = 0; - def->set_default_value(new ConfigOptionFloat(1.)); - - def = this->add("pad_enable", coBool); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("pad_wall_thickness", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 30; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(2.0)); - - def = this->add("pad_wall_height", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->category = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 30; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.)); - - def = this->add("pad_brim_size", coFloat); - def->label = L(" "); - def->tooltip = L(" "); - def->category = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 30; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.6)); - - def = this->add("pad_max_merge_distance", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(50.0)); - - def = this->add("pad_wall_slope", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 45; - def->max = 90; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(90.0)); - - def = this->add("pad_around_object", coBool); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("pad_around_object_everywhere", coBool); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("pad_object_gap", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->max = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1)); - - def = this->add("pad_object_connector_stride", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(10)); - - def = this->add("pad_object_connector_width", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.5)); - - def = this->add("pad_object_connector_penetration", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.3)); - - def = this->add("hollowing_enable", coBool); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("hollowing_min_thickness", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L(" "); - def->min = 1; - def->max = 10; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(3.)); - - def = this->add("hollowing_quality", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->min = 0; - def->max = 1; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.5)); - - def = this->add("hollowing_closing_distance", coFloat); - def->label = L(" "); - def->category = L(" "); - def->tooltip = L(" "); - def->sidetext = L("mm"); - def->min = 0; - def->max = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(2.0)); - - def = this->add("material_print_speed", coEnum); - def->label = L(" "); - def->tooltip = L(" "); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("slow"); - def->enum_values.push_back("fast"); - def->enum_labels.push_back(L(" ")); - def->enum_labels.push_back(L(" ")); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(slamsFast)); -} + void PrintConfigDef::init_sla_params() + { + ConfigOptionDef *def; + + // SLA Printer settings + + def = this->add("display_width", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->min = 1; + def->set_default_value(new ConfigOptionFloat(120.)); + + def = this->add("display_height", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->min = 1; + def->set_default_value(new ConfigOptionFloat(68.)); + + def = this->add("display_pixels_x", coInt); + def->full_label = L(" "); + def->label = ("X"); + def->tooltip = L(" "); + def->min = 100; + def->set_default_value(new ConfigOptionInt(2560)); + + def = this->add("display_pixels_y", coInt); + def->label = ("Y"); + def->tooltip = L(" "); + def->min = 100; + def->set_default_value(new ConfigOptionInt(1440)); + + def = this->add("display_mirror_x", coBool); + def->full_label = L(" "); + def->label = L(" "); + def->tooltip = L(" "); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("display_mirror_y", coBool); + def->full_label = L(" "); + def->label = L(" "); + def->tooltip = L(" "); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("display_orientation", coEnum); + def->label = L(" "); + def->tooltip = L(" "); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("landscape"); + def->enum_values.push_back("portrait"); + def->enum_labels.push_back(L(" ")); + def->enum_labels.push_back(L(" ")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(sladoPortrait)); + + def = this->add("fast_tilt_time", coFloat); + def->label = L(" "); + def->full_label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5.)); + + def = this->add("slow_tilt_time", coFloat); + def->label = L(" "); + def->full_label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(8.)); + + def = this->add("area_fill", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(50.)); + + def = this->add("relative_correction", coFloats); + def->label = L(" "); + def->full_label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats({1., 1.})); + + def = this->add("relative_correction_x", coFloat); + def->label = L(" "); + def->full_label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.)); + + def = this->add("relative_correction_y", coFloat); + def->label = L(" "); + def->full_label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.)); + + def = this->add("relative_correction_z", coFloat); + def->label = L(" "); + def->full_label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.)); + + def = this->add("absolute_correction", coFloat); + def->label = L(" "); + def->full_label = L(" "); + def->tooltip = L(" "); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("elefant_foot_min_width", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.2)); + + def = this->add("gamma_correction", coFloat); + def->label = L(" "); + def->full_label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->max = 1; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + // SLA Material settings. + + def = this->add("material_colour", coString); + def->label = L(" "); + def->tooltip = L(" "); + def->gui_type = ConfigOptionDef::GUIType::color; + def->set_default_value(new ConfigOptionString("#29B2B2")); + + def = this->add("material_type", coString); + def->label = L(" "); + def->tooltip = L(" "); + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; // TODO: ??? + def->gui_flags = "show_value"; + def->enum_values.push_back("Tough"); + def->enum_values.push_back("Flexible"); + def->enum_values.push_back("Casting"); + def->enum_values.push_back("Dental"); + def->enum_values.push_back("Heat-resistant"); + def->set_default_value(new ConfigOptionString("Tough")); + + def = this->add("initial_layer_height", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.3)); + + def = this->add("bottle_volume", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 50; + def->set_default_value(new ConfigOptionFloat(1000.0)); + + def = this->add("bottle_weight", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("material_density", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("bottle_cost", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("faded_layers", coInt); + def->label = L(" "); + def->tooltip = L(" "); + def->min = 3; + def->max = 20; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(10)); + + def = this->add("min_exposure_time", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("max_exposure_time", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(100)); + + def = this->add("exposure_time", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(10)); + + def = this->add("min_initial_exposure_time", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("max_initial_exposure_time", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(150)); + + def = this->add("initial_exposure_time", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(15)); + + def = this->add("material_correction", coFloats); + def->full_label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats({1., 1., 1.})); + + def = this->add("material_correction_x", coFloat); + def->full_label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.)); + + def = this->add("material_correction_y", coFloat); + def->full_label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.)); + + def = this->add("material_correction_z", coFloat); + def->full_label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.)); + + def = this->add("material_vendor", coString); + def->set_default_value(new ConfigOptionString("")); + def->cli = ConfigOptionDef::nocli; + + def = this->add("default_sla_material_profile", coString); + def->label = L(" "); + def->tooltip = L(" "); + def->set_default_value(new ConfigOptionString()); + def->cli = ConfigOptionDef::nocli; -void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) -{ - //BBS: handle legacy options - if (opt_key == "enable_wipe_tower") { - opt_key = "enable_prime_tower"; - } else if (opt_key == "wipe_tower_width") { - opt_key = "prime_tower_width"; - } else if (opt_key == "bottom_solid_infill_flow_ratio") { - opt_key = "initial_layer_flow_ratio"; - } else if (opt_key == "wiping_volume") { - opt_key = "filament_prime_volume"; - } - else if (opt_key == "prime_volume") { - opt_key = "filament_prime_volume"; + def = this->add("sla_material_settings_id", coString); + def->set_default_value(new ConfigOptionString("")); + def->cli = ConfigOptionDef::nocli; + + def = this->add("default_sla_print_profile", coString); + def->label = L(" "); + def->tooltip = L(" "); + def->set_default_value(new ConfigOptionString()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("sla_print_settings_id", coString); + def->set_default_value(new ConfigOptionString("")); + def->cli = ConfigOptionDef::nocli; + + def = this->add("supports_enable", coBool); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("support_head_front_diameter", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("support_head_penetration", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.2)); + + def = this->add("support_head_width", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 20; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("support_pillar_diameter", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 15; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("support_small_pillar_diameter_percent", coPercent); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 1; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(50)); + + def = this->add("support_max_bridges_on_pillar", coInt); + def->label = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->max = 50; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(3)); + + def = this->add("support_pillar_connection_mode", coEnum); + def->label = L(" "); + def->tooltip = L(" "); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("zigzag"); + def->enum_values.push_back("cross"); + def->enum_values.push_back("dynamic"); + def->enum_labels.push_back(L(" ")); + def->enum_labels.push_back(L(" ")); + def->enum_labels.push_back(L(" ")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(slapcmDynamic)); + + def = this->add("support_buildplate_only", coBool); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("support_pillar_widening_factor", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->max = 1; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("support_base_diameter", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 30; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(4.0)); + + def = this->add("support_base_height", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("support_base_safety_distance", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add("support_critical_angle", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 90; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(45)); + + def = this->add("support_max_bridge_length", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(15.0)); + + def = this->add("support_max_pillar_link_distance", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; // 0 means no linking + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(10.0)); + + def = this->add("support_object_elevation", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 150; // This is the max height of print on SL1 + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5.0)); + + def = this->add("support_points_density_relative", coInt); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->set_default_value(new ConfigOptionInt(100)); + + def = this->add("support_points_minimal_distance", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(1.)); + + def = this->add("pad_enable", coBool); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("pad_wall_thickness", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 30; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(2.0)); + + def = this->add("pad_wall_height", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->category = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 30; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("pad_brim_size", coFloat); + def->label = L(" "); + def->tooltip = L(" "); + def->category = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 30; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.6)); + + def = this->add("pad_max_merge_distance", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(50.0)); + + def = this->add("pad_wall_slope", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 45; + def->max = 90; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(90.0)); + + def = this->add("pad_around_object", coBool); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("pad_around_object_everywhere", coBool); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("pad_object_gap", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add("pad_object_connector_stride", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(10)); + + def = this->add("pad_object_connector_width", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.5)); + + def = this->add("pad_object_connector_penetration", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.3)); + + def = this->add("hollowing_enable", coBool); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("hollowing_min_thickness", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L(" "); + def->min = 1; + def->max = 10; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(3.)); + + def = this->add("hollowing_quality", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->min = 0; + def->max = 1; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.5)); + + def = this->add("hollowing_closing_distance", coFloat); + def->label = L(" "); + def->category = L(" "); + def->tooltip = L(" "); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(2.0)); + + def = this->add("material_print_speed", coEnum); + def->label = L(" "); + def->tooltip = L(" "); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("slow"); + def->enum_values.push_back("fast"); + def->enum_labels.push_back(L(" ")); + def->enum_labels.push_back(L(" ")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(slamsFast)); } - else if (opt_key == "wipe_tower_brim_width") { - opt_key = "prime_tower_brim_width"; - } else if (opt_key == "tool_change_gcode") { - opt_key = "change_filament_gcode"; - } else if (opt_key == "bridge_fan_speed") { - opt_key = "overhang_fan_speed"; - } else if (opt_key == "infill_extruder") { - opt_key = "sparse_infill_filament"; - }else if (opt_key == "solid_infill_extruder") { - opt_key = "solid_infill_filament"; - }else if (opt_key == "perimeter_extruder") { - opt_key = "wall_filament"; - } else if (opt_key == "support_material_extruder") { - opt_key = "support_filament"; - } else if (opt_key == "support_material_interface_extruder") { - opt_key = "support_interface_filament"; - } else if (opt_key == "support_material_angle") { - opt_key = "support_angle"; - } else if (opt_key == "support_material_enforce_layers") { - opt_key = "enforce_support_layers"; - } else if ((opt_key == "initial_layer_print_height" || - opt_key == "initial_layer_speed" || - opt_key == "internal_solid_infill_speed" || - opt_key == "top_surface_speed" || - opt_key == "support_interface_speed" || - opt_key == "outer_wall_speed" || - opt_key == "support_object_xy_distance") && value.find("%") != std::string::npos) { - //BBS: this is old profile in which value is expressed as percentage. - //But now these key-value must be absolute value. - //Reset to default value by erasing these key to avoid parsing error. - opt_key = ""; - } else if (opt_key == "inherits_cummulative") { - opt_key = "inherits_group"; - } else if (opt_key == "compatible_printers_condition_cummulative") { - opt_key = "compatible_machine_expression_group"; - } else if (opt_key == "compatible_prints_condition_cummulative") { - opt_key = "compatible_process_expression_group"; - } else if (opt_key == "cooling") { - opt_key = "slow_down_for_layer_cooling"; - } else if (opt_key == "timelapse_no_toolhead") { - opt_key = "timelapse_type"; - } else if (opt_key == "timelapse_type" && value == "2") { - // old file "0" is None, "2" is Traditional - // new file "0" is Traditional, erase "2" - value = "0"; - } else if (opt_key == "support_type" && value == "normal") { - value = "normal(manual)"; - } else if (opt_key == "support_type" && value == "tree") { - value = "tree(manual)"; - } else if (opt_key == "support_type" && value == "hybrid(auto)") { - value = "tree(auto)"; - } else if (opt_key == "support_base_pattern" && value == "none") { - value = "hollow"; - } else if (opt_key == "infill_anchor") { - opt_key = "sparse_infill_anchor"; - } else if (opt_key == "infill_anchor_max") { - opt_key = "sparse_infill_anchor_max"; - } else if (opt_key == "different_settings_to_system") { - std::string copy_value = value; - copy_value.erase(std::remove(copy_value.begin(), copy_value.end(), '\"'), copy_value.end()); // remove '"' in string - std::set split_keys = SplitStringAndRemoveDuplicateElement(copy_value, ";"); - for (std::string split_key : split_keys) { - std::string copy_key = split_key, copy_value = ""; - handle_legacy(copy_key, copy_value); - if (copy_key != split_key) { - ReplaceString(value, split_key, copy_key); + + void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) + { + // BBS: handle legacy options + if (opt_key == "enable_wipe_tower") + { + opt_key = "enable_prime_tower"; + } + else if (opt_key == "wipe_tower_width") + { + opt_key = "prime_tower_width"; + } + else if (opt_key == "bottom_solid_infill_flow_ratio") + { + opt_key = "initial_layer_flow_ratio"; + } + else if (opt_key == "wiping_volume") + { + opt_key = "filament_prime_volume"; + } + else if (opt_key == "prime_volume") + { + opt_key = "filament_prime_volume"; + } + else if (opt_key == "wipe_tower_brim_width") + { + opt_key = "prime_tower_brim_width"; + } + else if (opt_key == "tool_change_gcode") + { + opt_key = "change_filament_gcode"; + } + else if (opt_key == "bridge_fan_speed") + { + opt_key = "overhang_fan_speed"; + } + else if (opt_key == "infill_extruder") + { + opt_key = "sparse_infill_filament"; + } + else if (opt_key == "solid_infill_extruder") + { + opt_key = "solid_infill_filament"; + } + else if (opt_key == "perimeter_extruder") + { + opt_key = "wall_filament"; + } + else if (opt_key == "support_material_extruder") + { + opt_key = "support_filament"; + } + else if (opt_key == "support_material_interface_extruder") + { + opt_key = "support_interface_filament"; + } + else if (opt_key == "support_material_angle") + { + opt_key = "support_angle"; + } + else if (opt_key == "support_material_enforce_layers") + { + opt_key = "enforce_support_layers"; + } + else if ((opt_key == "initial_layer_print_height" || + opt_key == "initial_layer_speed" || + opt_key == "internal_solid_infill_speed" || + opt_key == "top_surface_speed" || + opt_key == "support_interface_speed" || + opt_key == "outer_wall_speed" || + opt_key == "support_object_xy_distance") && + value.find("%") != std::string::npos) + { + // BBS: this is old profile in which value is expressed as percentage. + // But now these key-value must be absolute value. + // Reset to default value by erasing these key to avoid parsing error. + opt_key = ""; + } + else if (opt_key == "inherits_cummulative") + { + opt_key = "inherits_group"; + } + else if (opt_key == "compatible_printers_condition_cummulative") + { + opt_key = "compatible_machine_expression_group"; + } + else if (opt_key == "compatible_prints_condition_cummulative") + { + opt_key = "compatible_process_expression_group"; + } + else if (opt_key == "cooling") + { + opt_key = "slow_down_for_layer_cooling"; + } + else if (opt_key == "timelapse_no_toolhead") + { + opt_key = "timelapse_type"; + } + else if (opt_key == "timelapse_type" && value == "2") + { + // old file "0" is None, "2" is Traditional + // new file "0" is Traditional, erase "2" + value = "0"; + } + else if (opt_key == "support_type" && value == "normal") + { + value = "normal(manual)"; + } + else if (opt_key == "support_type" && value == "tree") + { + value = "tree(manual)"; + } + else if (opt_key == "support_type" && value == "hybrid(auto)") + { + value = "tree(auto)"; + } + else if (opt_key == "support_base_pattern" && value == "none") + { + value = "hollow"; + } + else if (opt_key == "infill_anchor") + { + opt_key = "sparse_infill_anchor"; + } + else if (opt_key == "infill_anchor_max") + { + opt_key = "sparse_infill_anchor_max"; + } + else if (opt_key == "different_settings_to_system") + { + std::string copy_value = value; + copy_value.erase(std::remove(copy_value.begin(), copy_value.end(), '\"'), copy_value.end()); // remove '"' in string + std::set split_keys = SplitStringAndRemoveDuplicateElement(copy_value, ";"); + for (std::string split_key : split_keys) + { + std::string copy_key = split_key, copy_value = ""; + handle_legacy(copy_key, copy_value); + if (copy_key != split_key) + { + ReplaceString(value, split_key, copy_key); + } } } - } else if (opt_key == "overhang_fan_threshold" && value == "5%") { - value = "10%"; - } else if( opt_key == "wall_infill_order" ) { - if (value == "inner wall/outer wall/infill" || value == "infill/inner wall/outer wall") { - opt_key = "wall_sequence"; - value = "inner wall/outer wall"; - } else if (value == "outer wall/inner wall/infill" || value == "infill/outer wall/inner wall") { - opt_key = "wall_sequence"; - value = "outer wall/inner wall"; - } else if (value == "inner-outer-inner wall/infill") { - opt_key = "wall_sequence"; - value = "inner-outer-inner wall"; - } else { - opt_key = "wall_sequence"; - } - } else if (opt_key == "nozzle_volume_type" - || opt_key == "default_nozzle_volume_type" - || opt_key == "printer_extruder_variant" - || opt_key == "print_extruder_variant" - || opt_key == "filament_extruder_variant" - || opt_key == "extruder_variant_list") { - ReplaceString(value, "Normal", "Standard"); - ReplaceString(value, "Big Traffic", "High Flow"); - } - else if (opt_key == "extruder_type") { - ReplaceString(value, "DirectDrive", "Direct Drive"); - } - else if (opt_key == "ensure_vertical_shell_thickness") { - auto kvmap=ConfigOptionEnum::get_enum_names(); - // handle old values - if (value == "1") - value = ConfigOptionEnum::get_enum_names()[EnsureVerticalThicknessLevel::evtEnabled]; - else if (value == "0") - value = ConfigOptionEnum::get_enum_names()[EnsureVerticalThicknessLevel::evtPartial]; - } else if (opt_key == "filament_map_mode") { - if (value == "Auto") value = "Auto For Flush"; - } - else if (opt_key == "filament_type"){ - std::vector type_list; - std::stringstream ss(value); - std::string token; - bool rebuild_value = false; - while (std::getline(ss, token, ';')) { - if (token.size() >= 2 && token.front() == '"' && token.back() == '"') - token = token.substr(1, token.size() - 2); - if (token == "ASA-Aero") { - token = "ASA-AERO"; - rebuild_value = true; + else if (opt_key == "overhang_fan_threshold" && value == "5%") + { + value = "10%"; + } + else if (opt_key == "wall_infill_order") + { + if (value == "inner wall/outer wall/infill" || value == "infill/inner wall/outer wall") + { + opt_key = "wall_sequence"; + value = "inner wall/outer wall"; + } + else if (value == "outer wall/inner wall/infill" || value == "infill/outer wall/inner wall") + { + opt_key = "wall_sequence"; + value = "outer wall/inner wall"; } - type_list.emplace_back(token); - } - if (rebuild_value) { - value.clear(); - for (size_t idx = 0; idx < type_list.size(); ++idx) { - if (idx != 0) - value += ';'; - value += "\"" + type_list[idx] + "\""; + else if (value == "inner-outer-inner wall/infill") + { + opt_key = "wall_sequence"; + value = "inner-outer-inner wall"; + } + else if (value == "inner-outer-inner-inner wall/infill") + { + opt_key = "wall_sequence"; + value = "inner-outer-inner-inner wall"; + } + else + { + opt_key = "wall_sequence"; + } + } + else if (opt_key == "nozzle_volume_type" || opt_key == "default_nozzle_volume_type" || opt_key == "printer_extruder_variant" || opt_key == "print_extruder_variant" || opt_key == "filament_extruder_variant" || opt_key == "extruder_variant_list") + { + ReplaceString(value, "Normal", "Standard"); + ReplaceString(value, "Big Traffic", "High Flow"); + } + else if (opt_key == "extruder_type") + { + ReplaceString(value, "DirectDrive", "Direct Drive"); + } + else if (opt_key == "ensure_vertical_shell_thickness") + { + auto kvmap = ConfigOptionEnum::get_enum_names(); + // handle old values + if (value == "1") + value = ConfigOptionEnum::get_enum_names()[EnsureVerticalThicknessLevel::evtEnabled]; + else if (value == "0") + value = ConfigOptionEnum::get_enum_names()[EnsureVerticalThicknessLevel::evtPartial]; + } + else if (opt_key == "filament_map_mode") + { + if (value == "Auto") + value = "Auto For Flush"; + } + else if (opt_key == "filament_type") + { + std::vector type_list; + std::stringstream ss(value); + std::string token; + bool rebuild_value = false; + while (std::getline(ss, token, ';')) + { + if (token.size() >= 2 && token.front() == '"' && token.back() == '"') + token = token.substr(1, token.size() - 2); + if (token == "ASA-Aero") + { + token = "ASA-AERO"; + rebuild_value = true; + } + type_list.emplace_back(token); + } + if (rebuild_value) + { + value.clear(); + for (size_t idx = 0; idx < type_list.size(); ++idx) + { + if (idx != 0) + value += ';'; + value += "\"" + type_list[idx] + "\""; + } } } - } - // Ignore the following obsolete configuration keys: - static std::set ignore = { - "acceleration", "scale", "rotate", "duplicate", "duplicate_grid", - "bed_size", - "print_center", "g0", "wipe_tower_per_color_wipe" + // Ignore the following obsolete configuration keys: + static std::set ignore = { + "acceleration", "scale", "rotate", "duplicate", "duplicate_grid", + "bed_size", + "print_center", "g0", "wipe_tower_per_color_wipe" #ifndef HAS_PRESSURE_EQUALIZER - , "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative" + , + "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative" #endif /* HAS_PRESSURE_EQUALIZER */ - // BBS - , "support_sharp_tails","support_remove_small_overhangs", "support_with_sheath", - "tree_support_collision_resolution", "tree_support_with_infill", - "tree_support_brim_width", - "max_volumetric_speed", "max_print_speed", - "support_closing_radius", - "remove_freq_sweep", "remove_bed_leveling", "remove_extrusion_calibration", - "support_transition_line_width", "support_transition_speed", "bed_temperature", "bed_temperature_initial_layer", - "can_switch_nozzle_type", "can_add_auxiliary_fan", "extra_flush_volume", "spaghetti_detector", "adaptive_layer_height", - "z_hop_type","nozzle_hrc","chamber_temperature","only_one_wall_top","bed_temperature_difference","long_retraction_when_cut", - "retraction_distance_when_cut", - "prime_volume" + // BBS + , + "support_sharp_tails", "support_remove_small_overhangs", "support_with_sheath", + "tree_support_collision_resolution", "tree_support_with_infill", + "tree_support_brim_width", + "max_volumetric_speed", "max_print_speed", + "support_closing_radius", + "remove_freq_sweep", "remove_bed_leveling", "remove_extrusion_calibration", + "support_transition_line_width", "support_transition_speed", "bed_temperature", "bed_temperature_initial_layer", + "can_switch_nozzle_type", "can_add_auxiliary_fan", "extra_flush_volume", "spaghetti_detector", "adaptive_layer_height", + "z_hop_type", "nozzle_hrc", "chamber_temperature", "only_one_wall_top", "bed_temperature_difference", "long_retraction_when_cut", + "retraction_distance_when_cut", + "prime_volume"}; + + if (ignore.find(opt_key) != ignore.end()) + { + opt_key = ""; + return; + } + + if (!print_config_def.has(opt_key)) + { + opt_key = ""; + return; + } + } + + const PrintConfigDef print_config_def; + + // todo + std::set print_options_with_variant = { + "initial_layer_speed", + "initial_layer_infill_speed", + "outer_wall_speed", + "inner_wall_speed", + "small_perimeter_speed", // coFloatsOrPercents + "small_perimeter_threshold", + "sparse_infill_speed", + "internal_solid_infill_speed", + "vertical_shell_speed", + "top_surface_speed", + "enable_overhang_speed", // coBools + "overhang_1_4_speed", + "overhang_2_4_speed", + "overhang_3_4_speed", + "overhang_4_4_speed", + "overhang_totally_speed", + "enable_height_slowdown", // coBools + "slowdown_start_height", // coFloats + "slowdown_start_speed", // coFloats + "slowdown_start_acc", // coFloats + "slowdown_end_height", // coFloats + "slowdown_end_speed", // coFloats + "slowdown_end_acc", // coFloats + "bridge_speed", + "gap_infill_speed", + "support_speed", + "support_interface_speed", + "travel_speed", + "travel_speed_z", + "default_acceleration", + "travel_acceleration", + "initial_layer_travel_acceleration", + "initial_layer_acceleration", + "outer_wall_acceleration", + "inner_wall_acceleration", + "sparse_infill_acceleration", // coFloatsOrPercents + "top_surface_acceleration", + "print_extruder_id", // coInts + "print_extruder_variant" // coStrings }; - if (ignore.find(opt_key) != ignore.end()) { - opt_key = ""; - return; + std::set filament_options_with_variant = { + "filament_flow_ratio", + "filament_max_volumetric_speed", + "filament_ramming_volumetric_speed", + "filament_pre_cooling_temperature", + "filament_ramming_travel_time", + "filament_ramming_volumetric_speed_nc", + "filament_pre_cooling_temperature_nc", + "filament_ramming_travel_time_nc", + //"filament_extruder_id", + "filament_extruder_variant", + "filament_retraction_length", + "filament_retract_length_nc", + "filament_z_hop", + "filament_z_hop_types", + "filament_retract_restart_extra", + "filament_retraction_speed", + "filament_deretraction_speed", + "filament_retraction_minimum_travel", + "filament_retract_when_changing_layer", + "filament_wipe", + // BBS + "filament_wipe_distance", + "filament_retract_before_wipe", + "filament_long_retractions_when_cut", + "filament_retraction_distances_when_cut", + "long_retractions_when_ec", + "retraction_distances_when_ec", + "nozzle_temperature_initial_layer", + "nozzle_temperature", + "filament_flush_volumetric_speed", + "filament_flush_temp", + "volumetric_speed_coefficients", + "filament_adaptive_volumetric_speed", + "filament_cooling_before_tower"}; + + // Parameters that are the same as the number of extruders + std::set printer_extruder_options = { + "extruder_type", + "nozzle_diameter", + "default_nozzle_volume_type", + "extruder_printable_area", + "extruder_printable_height", + "min_layer_height", + "max_layer_height", + "extruder_max_nozzle_count"}; + + std::set printer_options_with_variant_1 = { + "nozzle_volume", + "retraction_length", + "z_hop", + "retract_lift_above", + "retract_lift_below", + "z_hop_types", + "retraction_speed", + "deretraction_speed", + "retraction_minimum_travel", + "retract_when_changing_layer", + "wipe", + "wipe_distance", + "retract_before_wipe", + "retract_length_toolchange", + "retract_restart_extra", + "retract_restart_extra_toolchange", + "long_retractions_when_cut", + "retraction_distances_when_cut", + "nozzle_type", + "printer_extruder_id", + "printer_extruder_variant", + "hotend_cooling_rate", + "hotend_heating_rate", + "nozzle_flush_dataset"}; + + // options with silient mode + std::set printer_options_with_variant_2 = { + "machine_max_acceleration_x", + "machine_max_acceleration_y", + "machine_max_acceleration_z", + "machine_max_acceleration_e", + "machine_max_acceleration_extruding", + "machine_max_acceleration_retracting", + "machine_max_acceleration_travel", + "machine_max_speed_x", + "machine_max_speed_y", + "machine_max_speed_z", + "machine_max_speed_e", + "machine_max_jerk_x", + "machine_max_jerk_y", + "machine_max_jerk_z", + "machine_max_jerk_e"}; + + std::set empty_options; + + DynamicPrintConfig DynamicPrintConfig::full_print_config() + { + return DynamicPrintConfig((const PrintRegionConfig &)FullPrintConfig::defaults()); } - if (! print_config_def.has(opt_key)) { - opt_key = ""; - return; + DynamicPrintConfig::DynamicPrintConfig(const StaticPrintConfig &rhs) : DynamicConfig(rhs, rhs.keys_ref()) + { } -} -const PrintConfigDef print_config_def; - -//todo -std::set print_options_with_variant = { - "initial_layer_speed", - "initial_layer_infill_speed", - "outer_wall_speed", - "inner_wall_speed", - "small_perimeter_speed", //coFloatsOrPercents - "small_perimeter_threshold", - "sparse_infill_speed", - "internal_solid_infill_speed", - "vertical_shell_speed", - "top_surface_speed", - "enable_overhang_speed", //coBools - "overhang_1_4_speed", - "overhang_2_4_speed", - "overhang_3_4_speed", - "overhang_4_4_speed", - "overhang_totally_speed", - "enable_height_slowdown", //coBools - "slowdown_start_height", //coFloats - "slowdown_start_speed", //coFloats - "slowdown_start_acc", //coFloats - "slowdown_end_height", //coFloats - "slowdown_end_speed", //coFloats - "slowdown_end_acc", //coFloats - "bridge_speed", - "gap_infill_speed", - "support_speed", - "support_interface_speed", - "travel_speed", - "travel_speed_z", - "default_acceleration", - "travel_acceleration", - "initial_layer_travel_acceleration", - "initial_layer_acceleration", - "outer_wall_acceleration", - "inner_wall_acceleration", - "sparse_infill_acceleration", //coFloatsOrPercents - "top_surface_acceleration", - "print_extruder_id", //coInts - "print_extruder_variant" //coStrings -}; - -std::set filament_options_with_variant = { - "filament_flow_ratio", - "filament_max_volumetric_speed", - "filament_ramming_volumetric_speed", - "filament_pre_cooling_temperature", - "filament_ramming_travel_time", - "filament_ramming_volumetric_speed_nc", - "filament_pre_cooling_temperature_nc", - "filament_ramming_travel_time_nc", - //"filament_extruder_id", - "filament_extruder_variant", - "filament_retraction_length", - "filament_retract_length_nc", - "filament_z_hop", - "filament_z_hop_types", - "filament_retract_restart_extra", - "filament_retraction_speed", - "filament_deretraction_speed", - "filament_retraction_minimum_travel", - "filament_retract_when_changing_layer", - "filament_wipe", - //BBS - "filament_wipe_distance", - "filament_retract_before_wipe", - "filament_long_retractions_when_cut", - "filament_retraction_distances_when_cut", - "long_retractions_when_ec", - "retraction_distances_when_ec", - "nozzle_temperature_initial_layer", - "nozzle_temperature", - "filament_flush_volumetric_speed", - "filament_flush_temp", - "volumetric_speed_coefficients", - "filament_adaptive_volumetric_speed", - "filament_cooling_before_tower" -}; - -// Parameters that are the same as the number of extruders -std::set printer_extruder_options = { - "extruder_type", - "nozzle_diameter", - "default_nozzle_volume_type", - "extruder_printable_area", - "extruder_printable_height", - "min_layer_height", - "max_layer_height", - "extruder_max_nozzle_count" -}; - -std::set printer_options_with_variant_1 = { - "nozzle_volume", - "retraction_length", - "z_hop", - "retract_lift_above", - "retract_lift_below", - "z_hop_types", - "retraction_speed", - "deretraction_speed", - "retraction_minimum_travel", - "retract_when_changing_layer", - "wipe", - "wipe_distance", - "retract_before_wipe", - "retract_length_toolchange", - "retract_restart_extra", - "retract_restart_extra_toolchange", - "long_retractions_when_cut", - "retraction_distances_when_cut", - "nozzle_type", - "printer_extruder_id", - "printer_extruder_variant", - "hotend_cooling_rate", - "hotend_heating_rate", - "nozzle_flush_dataset" -}; - -//options with silient mode -std::set printer_options_with_variant_2 = { - "machine_max_acceleration_x", - "machine_max_acceleration_y", - "machine_max_acceleration_z", - "machine_max_acceleration_e", - "machine_max_acceleration_extruding", - "machine_max_acceleration_retracting", - "machine_max_acceleration_travel", - "machine_max_speed_x", - "machine_max_speed_y", - "machine_max_speed_z", - "machine_max_speed_e", - "machine_max_jerk_x", - "machine_max_jerk_y", - "machine_max_jerk_z", - "machine_max_jerk_e" -}; - -std::set empty_options; - -DynamicPrintConfig DynamicPrintConfig::full_print_config() -{ - return DynamicPrintConfig((const PrintRegionConfig&)FullPrintConfig::defaults()); -} + DynamicPrintConfig *DynamicPrintConfig::new_from_defaults_keys(const std::vector &keys) + { + auto *out = new DynamicPrintConfig(); + out->apply_only(FullPrintConfig::defaults(), keys); + return out; + } -DynamicPrintConfig::DynamicPrintConfig(const StaticPrintConfig& rhs) : DynamicConfig(rhs, rhs.keys_ref()) -{ -} + double min_object_distance(const ConfigBase &cfg) + { + const ConfigOptionEnum *opt_printer_technology = cfg.option>("printer_technology"); + auto printer_technology = opt_printer_technology ? opt_printer_technology->value : ptUnknown; -DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector &keys) -{ - auto *out = new DynamicPrintConfig(); - out->apply_only(FullPrintConfig::defaults(), keys); - return out; -} + double ret = 0.; -double min_object_distance(const ConfigBase &cfg) -{ - const ConfigOptionEnum *opt_printer_technology = cfg.option>("printer_technology"); - auto printer_technology = opt_printer_technology ? opt_printer_technology->value : ptUnknown; - - double ret = 0.; - - if (printer_technology == ptSLA) - ret = 6.; - else { - //BBS: duplicate_distance seam to be useless - constexpr double duplicate_distance = 6.; - auto ecr_opt = cfg.option("extruder_clearance_max_radius"); - auto co_opt = cfg.option>("print_sequence"); - - if (!ecr_opt || !co_opt) - ret = 0.; - else { - // min object distance is max(duplicate_distance, clearance_radius) - ret = ((co_opt->value == PrintSequence::ByObject) && ecr_opt->value > duplicate_distance) ? - ecr_opt->value : duplicate_distance; + if (printer_technology == ptSLA) + ret = 6.; + else + { + // BBS: duplicate_distance seam to be useless + constexpr double duplicate_distance = 6.; + auto ecr_opt = cfg.option("extruder_clearance_max_radius"); + auto co_opt = cfg.option>("print_sequence"); + + if (!ecr_opt || !co_opt) + ret = 0.; + else + { + // min object distance is max(duplicate_distance, clearance_radius) + ret = ((co_opt->value == PrintSequence::ByObject) && ecr_opt->value > duplicate_distance) ? ecr_opt->value : duplicate_distance; + } } - } - return ret; -} - -void DynamicPrintConfig::normalize_fdm() -{ - if (this->has("extruder")) { - int extruder = this->option("extruder")->getInt(); - this->erase("extruder"); - if (extruder != 0) { - if (!this->has("sparse_infill_filament")) - this->option("sparse_infill_filament", true)->setInt(extruder); - if (!this->has("wall_filament")) - this->option("wall_filament", true)->setInt(extruder); - // Don't propagate the current extruder to support. - // For non-soluble supports, the default "0" extruder means to use the active extruder, - // for soluble supports one certainly does not want to set the extruder to non-soluble. - // if (!this->has("support_filament")) - // this->option("support_filament", true)->setInt(extruder); - // if (!this->has("support_interface_filament")) - // this->option("support_interface_filament", true)->setInt(extruder); - } + return ret; } - if (!this->has("solid_infill_filament") && this->has("sparse_infill_filament")) - this->option("solid_infill_filament", true)->setInt(this->option("sparse_infill_filament")->getInt()); - - if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { - { - // this should be actually done only on the spiral layers instead of all - auto* opt = this->opt("retract_when_changing_layer", true); - opt->values.assign(opt->values.size(), false); // set all values to false - // Disable retract on layer change also for filament overrides. - auto* opt_n = this->opt("filament_retract_when_changing_layer", true); - opt_n->values.assign(opt_n->values.size(), false); // Set all values to false. - } + void DynamicPrintConfig::normalize_fdm() + { + if (this->has("extruder")) { - this->opt("wall_loops", true)->value = 1; - this->opt("top_shell_layers", true)->value = 0; - this->opt("sparse_infill_density", true)->value = 0; + int extruder = this->option("extruder")->getInt(); + this->erase("extruder"); + if (extruder != 0) + { + if (!this->has("sparse_infill_filament")) + this->option("sparse_infill_filament", true)->setInt(extruder); + if (!this->has("wall_filament")) + this->option("wall_filament", true)->setInt(extruder); + // Don't propagate the current extruder to support. + // For non-soluble supports, the default "0" extruder means to use the active extruder, + // for soluble supports one certainly does not want to set the extruder to non-soluble. + // if (!this->has("support_filament")) + // this->option("support_filament", true)->setInt(extruder); + // if (!this->has("support_interface_filament")) + // this->option("support_interface_filament", true)->setInt(extruder); + } } - } - - if (auto *opt_gcode_resolution = this->opt("resolution", false); opt_gcode_resolution) - // Resolution will be above 1um. - opt_gcode_resolution->value = std::max(opt_gcode_resolution->value, 0.001); -} + if (!this->has("solid_infill_filament") && this->has("sparse_infill_filament")) + this->option("solid_infill_filament", true)->setInt(this->option("sparse_infill_filament")->getInt()); -//BBS:divide normalize_fdm to 2 steps and call them one by one in Print::Apply -void DynamicPrintConfig::normalize_fdm_1() -{ - if (this->has("extruder")) { - int extruder = this->option("extruder")->getInt(); - this->erase("extruder"); - if (extruder != 0) { - if (!this->has("sparse_infill_filament")) - this->option("sparse_infill_filament", true)->setInt(extruder); - if (!this->has("wall_filament")) - this->option("wall_filament", true)->setInt(extruder); - // Don't propagate the current extruder to support. - // For non-soluble supports, the default "0" extruder means to use the active extruder, - // for soluble supports one certainly does not want to set the extruder to non-soluble. - // if (!this->has("support_filament")) - // this->option("support_filament", true)->setInt(extruder); - // if (!this->has("support_interface_filament")) - // this->option("support_interface_filament", true)->setInt(extruder); + if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) + { + { + // this should be actually done only on the spiral layers instead of all + auto *opt = this->opt("retract_when_changing_layer", true); + opt->values.assign(opt->values.size(), false); // set all values to false + // Disable retract on layer change also for filament overrides. + auto *opt_n = this->opt("filament_retract_when_changing_layer", true); + opt_n->values.assign(opt_n->values.size(), false); // Set all values to false. + } + { + this->opt("wall_loops", true)->value = 1; + this->opt("top_shell_layers", true)->value = 0; + this->opt("sparse_infill_density", true)->value = 0; + } } - } - if (!this->has("solid_infill_filament") && this->has("sparse_infill_filament")) - this->option("solid_infill_filament", true)->setInt(this->option("sparse_infill_filament")->getInt()); + if (auto *opt_gcode_resolution = this->opt("resolution", false); opt_gcode_resolution) + // Resolution will be above 1um. + opt_gcode_resolution->value = std::max(opt_gcode_resolution->value, 0.001); + } - if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { + // BBS:divide normalize_fdm to 2 steps and call them one by one in Print::Apply + void DynamicPrintConfig::normalize_fdm_1() + { + if (this->has("extruder")) { - // this should be actually done only on the spiral layers instead of all - auto* opt = this->opt("retract_when_changing_layer", true); - opt->values.assign(opt->values.size(), false); // set all values to false - // Disable retract on layer change also for filament overrides. - auto* opt_n = this->opt("filament_retract_when_changing_layer", true); - opt_n->values.assign(opt_n->values.size(), false); // Set all values to false. + int extruder = this->option("extruder")->getInt(); + this->erase("extruder"); + if (extruder != 0) + { + if (!this->has("sparse_infill_filament")) + this->option("sparse_infill_filament", true)->setInt(extruder); + if (!this->has("wall_filament")) + this->option("wall_filament", true)->setInt(extruder); + // Don't propagate the current extruder to support. + // For non-soluble supports, the default "0" extruder means to use the active extruder, + // for soluble supports one certainly does not want to set the extruder to non-soluble. + // if (!this->has("support_filament")) + // this->option("support_filament", true)->setInt(extruder); + // if (!this->has("support_interface_filament")) + // this->option("support_interface_filament", true)->setInt(extruder); + } } + + if (!this->has("solid_infill_filament") && this->has("sparse_infill_filament")) + this->option("solid_infill_filament", true)->setInt(this->option("sparse_infill_filament")->getInt()); + + if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { - this->opt("wall_loops", true)->value = 1; - this->opt("top_shell_layers", true)->value = 0; - this->opt("sparse_infill_density", true)->value = 0; + { + // this should be actually done only on the spiral layers instead of all + auto *opt = this->opt("retract_when_changing_layer", true); + opt->values.assign(opt->values.size(), false); // set all values to false + // Disable retract on layer change also for filament overrides. + auto *opt_n = this->opt("filament_retract_when_changing_layer", true); + opt_n->values.assign(opt_n->values.size(), false); // Set all values to false. + } + { + this->opt("wall_loops", true)->value = 1; + this->opt("top_shell_layers", true)->value = 0; + this->opt("sparse_infill_density", true)->value = 0; + } } + + if (auto *opt_gcode_resolution = this->opt("resolution", false); opt_gcode_resolution) + // Resolution will be above 1um. + opt_gcode_resolution->value = std::max(opt_gcode_resolution->value, 0.001); + + return; } - if (auto *opt_gcode_resolution = this->opt("resolution", false); opt_gcode_resolution) - // Resolution will be above 1um. - opt_gcode_resolution->value = std::max(opt_gcode_resolution->value, 0.001); + t_config_option_keys DynamicPrintConfig::normalize_fdm_2(int num_objects, int used_filaments) + { + t_config_option_keys changed_keys; + ConfigOptionBool *ept_opt = this->option("enable_prime_tower"); + if (used_filaments > 0 && ept_opt != nullptr) + { + ConfigOptionBool *islh_opt = this->option("independent_support_layer_height", true); + // ConfigOptionBool* alh_opt = this->option("adaptive_layer_height"); + ConfigOptionEnum *ps_opt = this->option>("print_sequence"); - return; -} + ConfigOptionEnum *timelapse_opt = this->option>("timelapse_type"); + bool is_smooth_timelapse = timelapse_opt != nullptr && timelapse_opt->value == TimelapseType::tlSmooth; -t_config_option_keys DynamicPrintConfig::normalize_fdm_2(int num_objects, int used_filaments) -{ - t_config_option_keys changed_keys; - ConfigOptionBool* ept_opt = this->option("enable_prime_tower"); - if (used_filaments > 0 && ept_opt != nullptr) { - ConfigOptionBool* islh_opt = this->option("independent_support_layer_height", true); - //ConfigOptionBool* alh_opt = this->option("adaptive_layer_height"); - ConfigOptionEnum* ps_opt = this->option>("print_sequence"); - - ConfigOptionEnum* timelapse_opt = this->option>("timelapse_type"); - bool is_smooth_timelapse = timelapse_opt != nullptr && timelapse_opt->value == TimelapseType::tlSmooth; - - ConfigOptionBool *enable_wrapping_opt = this->option("enable_wrapping_detection"); - bool enable_wrapping = enable_wrapping_opt != nullptr && enable_wrapping_opt->value; - - if (!is_smooth_timelapse && !enable_wrapping && (used_filaments == 1 || (ps_opt->value == PrintSequence::ByObject && num_objects > 1))) { - if (ept_opt->value) { - ept_opt->value = false; - changed_keys.push_back("enable_prime_tower"); + ConfigOptionBool *enable_wrapping_opt = this->option("enable_wrapping_detection"); + bool enable_wrapping = enable_wrapping_opt != nullptr && enable_wrapping_opt->value; + + if (!is_smooth_timelapse && !enable_wrapping && (used_filaments == 1 || (ps_opt->value == PrintSequence::ByObject && num_objects > 1))) + { + if (ept_opt->value) + { + ept_opt->value = false; + changed_keys.push_back("enable_prime_tower"); + } + // ept_opt->value = false; } - //ept_opt->value = false; - } - if (ept_opt->value) { - if (islh_opt) { - if (islh_opt->value) { - islh_opt->value = false; - changed_keys.push_back("independent_support_layer_height"); + if (ept_opt->value) + { + if (islh_opt) + { + if (islh_opt->value) + { + islh_opt->value = false; + changed_keys.push_back("independent_support_layer_height"); + } + // islh_opt->value = false; } - //islh_opt->value = false; + // if (alh_opt) { + // if (alh_opt->value) { + // alh_opt->value = false; + // changed_keys.push_back("adaptive_layer_height"); + // } + // //alh_opt->value = false; + // } } - //if (alh_opt) { - // if (alh_opt->value) { - // alh_opt->value = false; - // changed_keys.push_back("adaptive_layer_height"); - // } - // //alh_opt->value = false; - //} - } - /* BBS:MusangKing - use "global->support->Independent support layer height" widget to replace previous assignment - else { - if (islh_opt) { - if (!islh_opt->value) { - islh_opt->value = true; - changed_keys.push_back("independent_support_layer_height"); + /* BBS:MusangKing - use "global->support->Independent support layer height" widget to replace previous assignment + else { + if (islh_opt) { + if (!islh_opt->value) { + islh_opt->value = true; + changed_keys.push_back("independent_support_layer_height"); + } + //islh_opt->value = true; } - //islh_opt->value = true; } + */ } - */ + + return changed_keys; } - return changed_keys; -} + void handle_legacy_sla(DynamicPrintConfig &config) + { + for (std::string corr : {"relative_correction", "material_correction"}) + { + if (config.has(corr)) + { + if (std::string corr_x = corr + "_x"; !config.has(corr_x)) + { + auto *opt = config.opt(corr_x, true); + opt->value = config.opt(corr)->values[0]; + } -void handle_legacy_sla(DynamicPrintConfig &config) -{ - for (std::string corr : {"relative_correction", "material_correction"}) { - if (config.has(corr)) { - if (std::string corr_x = corr + "_x"; !config.has(corr_x)) { - auto* opt = config.opt(corr_x, true); - opt->value = config.opt(corr)->values[0]; - } + if (std::string corr_y = corr + "_y"; !config.has(corr_y)) + { + auto *opt = config.opt(corr_y, true); + opt->value = config.opt(corr)->values[0]; + } - if (std::string corr_y = corr + "_y"; !config.has(corr_y)) { - auto* opt = config.opt(corr_y, true); - opt->value = config.opt(corr)->values[0]; + if (std::string corr_z = corr + "_z"; !config.has(corr_z)) + { + auto *opt = config.opt(corr_z, true); + opt->value = config.opt(corr)->values[1]; + } } + } + } - if (std::string corr_z = corr + "_z"; !config.has(corr_z)) { - auto* opt = config.opt(corr_z, true); - opt->value = config.opt(corr)->values[1]; - } + size_t DynamicPrintConfig::get_parameter_size(const std::string ¶m_name, size_t extruder_nums) + { + constexpr size_t default_param_length = 1; + size_t filament_variant_length = default_param_length; + size_t process_variant_length = default_param_length; + size_t machine_variant_length = default_param_length; + + if (this->has("filament_extruder_variant")) + filament_variant_length = this->option("filament_extruder_variant")->size(); + if (this->has("print_extruder_variant")) + process_variant_length = this->option("print_extruder_variant")->size(); + if (this->has("printer_extruder_variant")) + machine_variant_length = this->option("printer_extruder_variant")->size(); + + if (printer_options_with_variant_1.count(param_name) > 0) + { + return machine_variant_length; + } + else if (printer_options_with_variant_2.count(param_name) > 0) + { + return machine_variant_length * 2; + } + else if (filament_options_with_variant.count(param_name) > 0) + { + return filament_variant_length; + } + else if (print_options_with_variant.count(param_name) > 0) + { + return process_variant_length; } + return extruder_nums; } -} - -size_t DynamicPrintConfig::get_parameter_size(const std::string& param_name, size_t extruder_nums) -{ - constexpr size_t default_param_length = 1; - size_t filament_variant_length = default_param_length; - size_t process_variant_length = default_param_length; - size_t machine_variant_length = default_param_length; - - if (this->has("filament_extruder_variant")) - filament_variant_length = this->option("filament_extruder_variant")->size(); - if (this->has("print_extruder_variant")) - process_variant_length = this->option("print_extruder_variant")->size(); - if (this->has("printer_extruder_variant")) - machine_variant_length = this->option("printer_extruder_variant")->size(); - - if (printer_options_with_variant_1.count(param_name) > 0) { - return machine_variant_length; - } - else if (printer_options_with_variant_2.count(param_name) > 0) { - return machine_variant_length * 2; - } - else if (filament_options_with_variant.count(param_name) > 0) { - return filament_variant_length; - } - else if (print_options_with_variant.count(param_name) > 0) { - return process_variant_length; - } - return extruder_nums; -} -void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders) -{ - const auto &defaults = FullPrintConfig::defaults(); - for (const std::string &key : print_config_def.extruder_option_keys()) { - if (key == "default_filament_profile") - // Don't resize this field, as it is presented to the user at the "Dependencies" page of the Printer profile and we don't want to present - // empty fields there, if not defined by the system profile. - continue; - auto *opt = this->option(key, false); - assert(opt != nullptr); - assert(opt->is_vector()); - if (opt != nullptr && opt->is_vector()) { - static_cast(opt)->resize(get_parameter_size(key, num_extruders), defaults.option(key)); + void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders) + { + const auto &defaults = FullPrintConfig::defaults(); + for (const std::string &key : print_config_def.extruder_option_keys()) + { + if (key == "default_filament_profile") + // Don't resize this field, as it is presented to the user at the "Dependencies" page of the Printer profile and we don't want to present + // empty fields there, if not defined by the system profile. + continue; + auto *opt = this->option(key, false); + assert(opt != nullptr); + assert(opt->is_vector()); + if (opt != nullptr && opt->is_vector()) + { + static_cast(opt)->resize(get_parameter_size(key, num_extruders), defaults.option(key)); + } } } -} -// BBS -void DynamicPrintConfig::set_num_filaments(unsigned int num_filaments) -{ - const auto& defaults = FullPrintConfig::defaults(); - for (const std::string& key : print_config_def.filament_option_keys()) { - if (key == "default_filament_profile") - // Don't resize this field, as it is presented to the user at the "Dependencies" page of the Printer profile and we don't want to present - // empty fields there, if not defined by the system profile. - continue; - auto* opt = this->option(key, false); - assert(opt != nullptr); - assert(opt->is_vector()); - if (opt != nullptr && opt->is_vector()) - static_cast(opt)->resize(num_filaments, defaults.option(key)); + // BBS + void DynamicPrintConfig::set_num_filaments(unsigned int num_filaments) + { + const auto &defaults = FullPrintConfig::defaults(); + for (const std::string &key : print_config_def.filament_option_keys()) + { + if (key == "default_filament_profile") + // Don't resize this field, as it is presented to the user at the "Dependencies" page of the Printer profile and we don't want to present + // empty fields there, if not defined by the system profile. + continue; + auto *opt = this->option(key, false); + assert(opt != nullptr); + assert(opt->is_vector()); + if (opt != nullptr && opt->is_vector()) + static_cast(opt)->resize(num_filaments, defaults.option(key)); + } } -} -//BBS: pass map to recording all invalid valies -std::map DynamicPrintConfig::validate(bool under_cli) -{ - // Full print config is initialized from the defaults. - const ConfigOption *opt = this->option("printer_technology", false); - auto printer_technology = (opt == nullptr) ? ptFFF : static_cast(dynamic_cast(opt)->value); - switch (printer_technology) { - case ptFFF: + // BBS: pass map to recording all invalid valies + std::map DynamicPrintConfig::validate(bool under_cli) { - FullPrintConfig fpc; - fpc.apply(*this, true); - // Verify this print options through the FullPrintConfig. - return Slic3r::validate(fpc, under_cli); - } - default: - //FIXME no validation on SLA data? - return std::map(); + // Full print config is initialized from the defaults. + const ConfigOption *opt = this->option("printer_technology", false); + auto printer_technology = (opt == nullptr) ? ptFFF : static_cast(dynamic_cast(opt)->value); + switch (printer_technology) + { + case ptFFF: + { + FullPrintConfig fpc; + fpc.apply(*this, true); + // Verify this print options through the FullPrintConfig. + return Slic3r::validate(fpc, under_cli); + } + default: + // FIXME no validation on SLA data? + return std::map(); + } } -} - -std::string DynamicPrintConfig::get_filament_type(std::string &displayed_filament_type, int id) -{ - auto* filament_id = dynamic_cast(this->option("filament_id")); - auto* filament_type = dynamic_cast(this->option("filament_type")); - auto* filament_is_support = dynamic_cast(this->option("filament_is_support")); - if (!filament_type) - return ""; + std::string DynamicPrintConfig::get_filament_type(std::string &displayed_filament_type, int id) + { + auto *filament_id = dynamic_cast(this->option("filament_id")); + auto *filament_type = dynamic_cast(this->option("filament_type")); + auto *filament_is_support = dynamic_cast(this->option("filament_is_support")); - if (!filament_is_support) { - if (filament_type) { - displayed_filament_type = filament_type->get_at(id); - return filament_type->get_at(id); - } - else { - displayed_filament_type = ""; + if (!filament_type) return ""; + + if (!filament_is_support) + { + if (filament_type) + { + displayed_filament_type = filament_type->get_at(id); + return filament_type->get_at(id); + } + else + { + displayed_filament_type = ""; + return ""; + } } - } - else { - bool is_support = filament_is_support ? filament_is_support->get_at(id) : false; - if (is_support) { - if (filament_id) { - if (filament_id->get_at(id) == "GFS00") { - displayed_filament_type = "Sup.PLA"; - return "PLA-S"; - } - else if (filament_id->get_at(id) == "GFS01") { - displayed_filament_type = "Sup.PA"; - return "PA-S"; + else + { + bool is_support = filament_is_support ? filament_is_support->get_at(id) : false; + if (is_support) + { + if (filament_id) + { + if (filament_id->get_at(id) == "GFS00") + { + displayed_filament_type = "Sup.PLA"; + return "PLA-S"; + } + else if (filament_id->get_at(id) == "GFS01") + { + displayed_filament_type = "Sup.PA"; + return "PA-S"; + } + else + { + if (filament_type->get_at(id) == "PLA") + { + displayed_filament_type = "Sup.PLA"; + return "PLA-S"; + } + else if (filament_type->get_at(id) == "PA") + { + displayed_filament_type = "Sup.PA"; + return "PA-S"; + } + else + { + displayed_filament_type = filament_type->get_at(id); + return filament_type->get_at(id); + } + } } - else { - if (filament_type->get_at(id) == "PLA") { + else + { + if (filament_type->get_at(id) == "PLA") + { displayed_filament_type = "Sup.PLA"; return "PLA-S"; } - else if (filament_type->get_at(id) == "PA") { + else if (filament_type->get_at(id) == "PA") + { displayed_filament_type = "Sup.PA"; return "PA-S"; } - else { + else if (filament_type->get_at(id) == "ABS") + { + displayed_filament_type = "Sup.ABS"; + return "ABS-S"; + } + else + { displayed_filament_type = filament_type->get_at(id); return filament_type->get_at(id); } } } - else { - if (filament_type->get_at(id) == "PLA") { - displayed_filament_type = "Sup.PLA"; - return "PLA-S"; - } else if (filament_type->get_at(id) == "PA") { - displayed_filament_type = "Sup.PA"; - return "PA-S"; - } - else if (filament_type->get_at(id) == "ABS") { - displayed_filament_type = "Sup.ABS"; - return "ABS-S"; - } - else { - displayed_filament_type = filament_type->get_at(id); - return filament_type->get_at(id); - } + else + { + displayed_filament_type = filament_type->get_at(id); + return filament_type->get_at(id); } } - else { - displayed_filament_type = filament_type->get_at(id); - return filament_type->get_at(id); - } + return "PLA"; } - return "PLA"; -} -bool DynamicPrintConfig::is_using_different_extruders() -{ - bool ret = false; - - auto nozzle_diameters_opt = dynamic_cast(this->option("nozzle_diameter")); - if (nozzle_diameters_opt != nullptr) { - int size = nozzle_diameters_opt->size(); - if (size > 1) { - auto extruder_type_opt = dynamic_cast(this->option("extruder_type")); - auto nozzle_volume_type_opt = dynamic_cast(this->option("nozzle_volume_type")); - if (extruder_type_opt && nozzle_volume_type_opt) { - ExtruderType extruder_type = (ExtruderType)(extruder_type_opt->get_at(0)); - NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(nozzle_volume_type_opt->get_at(0)); - for (int index = 1; index < size; index++) + bool DynamicPrintConfig::is_using_different_extruders() + { + bool ret = false; + + auto nozzle_diameters_opt = dynamic_cast(this->option("nozzle_diameter")); + if (nozzle_diameters_opt != nullptr) + { + int size = nozzle_diameters_opt->size(); + if (size > 1) + { + auto extruder_type_opt = dynamic_cast(this->option("extruder_type")); + auto nozzle_volume_type_opt = dynamic_cast(this->option("nozzle_volume_type")); + if (extruder_type_opt && nozzle_volume_type_opt) { - ExtruderType extruder_type_1 = (ExtruderType)(extruder_type_opt->get_at(index)); - NozzleVolumeType nozzle_volume_type_1 = (NozzleVolumeType)(nozzle_volume_type_opt->get_at(index)); - if ((extruder_type_1 != extruder_type) || (nozzle_volume_type_1 != nozzle_volume_type)) { - ret = true; - break; + ExtruderType extruder_type = (ExtruderType)(extruder_type_opt->get_at(0)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(nozzle_volume_type_opt->get_at(0)); + for (int index = 1; index < size; index++) + { + ExtruderType extruder_type_1 = (ExtruderType)(extruder_type_opt->get_at(index)); + NozzleVolumeType nozzle_volume_type_1 = (NozzleVolumeType)(nozzle_volume_type_opt->get_at(index)); + if ((extruder_type_1 != extruder_type) || (nozzle_volume_type_1 != nozzle_volume_type)) + { + ret = true; + break; + } } } } } + + return ret; } - return ret; -} + bool DynamicPrintConfig::support_different_extruders(int &extruder_count) + { + std::set variant_set; -bool DynamicPrintConfig::support_different_extruders(int& extruder_count) -{ - std::set variant_set; - - auto nozzle_diameters_opt = dynamic_cast(this->option("nozzle_diameter")); - if (nozzle_diameters_opt != nullptr) { - int size = nozzle_diameters_opt->size(); - extruder_count = size; - auto extruder_variant_opt = dynamic_cast(this->option("extruder_variant_list")); - if (extruder_variant_opt != nullptr) { - for (int index = 0; index < size; index++) { - std::string variant = extruder_variant_opt->get_at(index); - std::vector variants_list; - boost::split(variants_list, variant, boost::is_any_of(","), boost::token_compress_on); - if (!variants_list.empty()) - variant_set.insert(variants_list.begin(), variants_list.end()); + auto nozzle_diameters_opt = dynamic_cast(this->option("nozzle_diameter")); + if (nozzle_diameters_opt != nullptr) + { + int size = nozzle_diameters_opt->size(); + extruder_count = size; + auto extruder_variant_opt = dynamic_cast(this->option("extruder_variant_list")); + if (extruder_variant_opt != nullptr) + { + for (int index = 0; index < size; index++) + { + std::string variant = extruder_variant_opt->get_at(index); + std::vector variants_list; + boost::split(variants_list, variant, boost::is_any_of(","), boost::token_compress_on); + if (!variants_list.empty()) + variant_set.insert(variants_list.begin(), variants_list.end()); + } } } + + return (variant_set.size() > 1); } - return (variant_set.size() > 1); -} + int DynamicPrintConfig::get_index_for_extruder(int extruder_or_filament_id, std::string id_name, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, std::string variant_name, unsigned int stride) const + { + int ret = -1; -int DynamicPrintConfig::get_index_for_extruder(int extruder_or_filament_id, std::string id_name, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, std::string variant_name, unsigned int stride) const -{ - int ret = -1; - - auto variant_opt = dynamic_cast(this->option(variant_name)); - const ConfigOptionInts* id_opt = id_name.empty()?nullptr: dynamic_cast(this->option(id_name)); - if (variant_opt != nullptr) { - int v_size = variant_opt->values.size(); - //int i_size = id_opt->values.size(); - // nvtHybrid not supported in presets, switch to nvtStandard to match the preset values - if (nozzle_volume_type == nvtHybrid) - nozzle_volume_type = nvtStandard; - std::string extruder_variant = get_extruder_variant_string(extruder_type, nozzle_volume_type); - for (int index = 0; index < v_size; index++) - { - const std::string variant = variant_opt->get_at(index); - if (extruder_variant == variant) { - if (id_opt) { - const int id = id_opt->get_at(index); - if (id == extruder_or_filament_id) { + auto variant_opt = dynamic_cast(this->option(variant_name)); + const ConfigOptionInts *id_opt = id_name.empty() ? nullptr : dynamic_cast(this->option(id_name)); + if (variant_opt != nullptr) + { + int v_size = variant_opt->values.size(); + // int i_size = id_opt->values.size(); + // nvtHybrid not supported in presets, switch to nvtStandard to match the preset values + if (nozzle_volume_type == nvtHybrid) + nozzle_volume_type = nvtStandard; + std::string extruder_variant = get_extruder_variant_string(extruder_type, nozzle_volume_type); + for (int index = 0; index < v_size; index++) + { + const std::string variant = variant_opt->get_at(index); + if (extruder_variant == variant) + { + if (id_opt) + { + const int id = id_opt->get_at(index); + if (id == extruder_or_filament_id) + { + ret = index * stride; + break; + } + } + else + { ret = index * stride; break; } } - else { - ret = index * stride; - break; - } - } } + return ret; } - return ret; -} - -//only used for cli -//update values in single extruder process config to values in multi-extruder process -//limit the new values -int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig& multi_config, std::set& key_set, std::string id_name, std::string variant_name) -{ - auto print_variant_opt = dynamic_cast(multi_config.option(variant_name)); - if (!print_variant_opt) { - BOOST_LOG_TRIVIAL(error) << boost::format("%1%:%2%, can not get %3% from config")%__FUNCTION__ %__LINE__ % variant_name; - return -1; - } - int variant_count = print_variant_opt->size(); - const ConfigDef *config_def = this->def(); - if (!config_def) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; - return -1; - } - for (auto& key: key_set) + // only used for cli + // update values in single extruder process config to values in multi-extruder process + // limit the new values + int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig &multi_config, std::set &key_set, std::string id_name, std::string variant_name) { - const ConfigOptionDef *optdef = config_def->get(key); - if (!optdef) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; - continue; + auto print_variant_opt = dynamic_cast(multi_config.option(variant_name)); + if (!print_variant_opt) + { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%:%2%, can not get %3% from config") % __FUNCTION__ % __LINE__ % variant_name; + return -1; + } + int variant_count = print_variant_opt->size(); + + const ConfigDef *config_def = this->def(); + if (!config_def) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define") % __LINE__; + return -1; } - switch (optdef->type) { + for (auto &key : key_set) + { + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%") % __LINE__ % key; + continue; + } + switch (optdef->type) + { case coStrings: { - ConfigOptionStrings* src_opt = multi_config.option(key); - if (src_opt) { - ConfigOptionStrings* opt = this->option(key, true); + ConfigOptionStrings *src_opt = multi_config.option(key); + if (src_opt) + { + ConfigOptionStrings *opt = this->option(key, true); opt->values = src_opt->values; } @@ -6975,9 +7108,10 @@ int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig& m } case coInts: { - ConfigOptionInts* src_opt = multi_config.option(key); - if (src_opt) { - ConfigOptionInts* opt = this->option(key, true); + ConfigOptionInts *src_opt = multi_config.option(key); + if (src_opt) + { + ConfigOptionInts *opt = this->option(key, true); opt->values = src_opt->values; } @@ -6985,9 +7119,10 @@ int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig& m } case coFloats: { - ConfigOptionFloats * src_opt = multi_config.option(key); - if (src_opt) { - ConfigOptionFloats * opt = this->option(key, true); + ConfigOptionFloats *src_opt = multi_config.option(key); + if (src_opt) + { + ConfigOptionFloats *opt = this->option(key, true); assert(variant_count == src_opt->size()); opt->resize(variant_count, opt); @@ -7002,9 +7137,10 @@ int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig& m } case coFloatsOrPercents: { - ConfigOptionFloatsOrPercents * src_opt = multi_config.option(key); - if (src_opt) { - ConfigOptionFloatsOrPercents * opt = this->option(key, true); + ConfigOptionFloatsOrPercents *src_opt = multi_config.option(key); + if (src_opt) + { + ConfigOptionFloatsOrPercents *opt = this->option(key, true); assert(variant_count == src_opt->size()); opt->resize(variant_count, opt); @@ -7019,10 +7155,10 @@ int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig& m } case coBools: { - ConfigOptionBools * src_opt = multi_config.option(key); + ConfigOptionBools *src_opt = multi_config.option(key); if (src_opt) { - ConfigOptionBools * opt = this->option(key, true); + ConfigOptionBools *opt = this->option(key, true); assert(variant_count == src_opt->size()); opt->resize(variant_count, opt); @@ -7031,310 +7167,333 @@ int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig& m break; } default: - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%") % __LINE__ % key; break; + } } - } - return 0; -} - -//used for object/region config -//duplicate single to multiple -/*int DynamicPrintConfig::update_values_from_single_to_multi_2(DynamicPrintConfig& multi_config, std::set& key_set) -{ - const ConfigDef *config_def = this->def(); - if (!config_def) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; - return -1; + return 0; } - t_config_option_keys keys = this->keys(); - for (auto& key: keys) + // used for object/region config + // duplicate single to multiple + /*int DynamicPrintConfig::update_values_from_single_to_multi_2(DynamicPrintConfig& multi_config, std::set& key_set) { - if (key_set.find(key) == key_set.end()) - continue; - - const ConfigOptionDef *optdef = config_def->get(key); - if (!optdef) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; - continue; + const ConfigDef *config_def = this->def(); + if (!config_def) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; + return -1; } - switch (optdef->type) { - case coFloats: - { - ConfigOptionFloatsNullable * opt = this->option(key); - ConfigOptionFloatsNullable* src_opt = multi_config.option(key); - if (src_opt && !opt->is_nil(0)) - opt->values.resize(src_opt->size(), opt->values[0]); - break; - } - case coFloatsOrPercents: - { - ConfigOptionFloatsOrPercentsNullable* opt = this->option(key); - ConfigOptionFloatsOrPercentsNullable* src_opt = multi_config.option(key); + t_config_option_keys keys = this->keys(); + for (auto& key: keys) + { + if (key_set.find(key) == key_set.end()) + continue; - if (src_opt &&!opt->is_nil(0)) - opt->values.resize(src_opt->size(), opt->values[0]); - break; + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; + continue; } - case coBools: - { - ConfigOptionBoolsNullable* opt = this->option(key); - ConfigOptionBoolsNullable* src_opt = multi_config.option(key); + switch (optdef->type) { + case coFloats: + { + ConfigOptionFloatsNullable * opt = this->option(key); + ConfigOptionFloatsNullable* src_opt = multi_config.option(key); + + if (src_opt && !opt->is_nil(0)) + opt->values.resize(src_opt->size(), opt->values[0]); + break; + } + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercentsNullable* opt = this->option(key); + ConfigOptionFloatsOrPercentsNullable* src_opt = multi_config.option(key); + + if (src_opt &&!opt->is_nil(0)) + opt->values.resize(src_opt->size(), opt->values[0]); + break; + } + case coBools: + { + ConfigOptionBoolsNullable* opt = this->option(key); + ConfigOptionBoolsNullable* src_opt = multi_config.option(key); - if (src_opt &&!opt->is_nil(0)) - opt->values.resize(src_opt->size(), opt->values[0]); + if (src_opt &&!opt->is_nil(0)) + opt->values.resize(src_opt->size(), opt->values[0]); - break; + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + break; } - default: - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; - break; } - } - - return 0; -}*/ -//update global process config for multi variant to multi variant case -//1. skip the key-values not in key_set -//2. update the key-value to the new one, then check whether the old one with the same variant can be used or not -int DynamicPrintConfig::update_values_from_multi_to_multi(DynamicPrintConfig& new_config, std::set& key_set, std::string id_name, std::string variant_name, std::vector& new_extruder_variants) -{ - int new_extruder_count = new_extruder_variants.size(); - std::vector new_variant_indices(new_extruder_count, -1); - - auto print_variant_opt = dynamic_cast(this->option(variant_name)); - auto new_variant_opt = dynamic_cast(new_config.option(variant_name)); - auto new_print_id_opt = dynamic_cast(new_config.option(id_name)); - if (!print_variant_opt || !new_variant_opt || !new_print_id_opt) { - BOOST_LOG_TRIVIAL(error) << boost::format("%1%:%2%, can not get variant %3%, id %4% from config")%__FUNCTION__ %__LINE__ % variant_name % id_name; - return -1; - } - int variant_count = print_variant_opt->size(), new_variant_count = new_variant_opt->size(); + return 0; + }*/ - std::vector> extruder_variant_indices; - for (int i = 0; i < new_extruder_count; i++) + // update global process config for multi variant to multi variant case + // 1. skip the key-values not in key_set + // 2. update the key-value to the new one, then check whether the old one with the same variant can be used or not + int DynamicPrintConfig::update_values_from_multi_to_multi(DynamicPrintConfig &new_config, std::set &key_set, std::string id_name, std::string variant_name, std::vector &new_extruder_variants) { - std::vector variant_indices; - for (int j = 0; j < variant_count; j++) + int new_extruder_count = new_extruder_variants.size(); + std::vector new_variant_indices(new_extruder_count, -1); + + auto print_variant_opt = dynamic_cast(this->option(variant_name)); + auto new_variant_opt = dynamic_cast(new_config.option(variant_name)); + auto new_print_id_opt = dynamic_cast(new_config.option(id_name)); + if (!print_variant_opt || !new_variant_opt || !new_print_id_opt) { - if (new_extruder_variants[i] == print_variant_opt->values[j]) { - variant_indices.push_back(j); - } + BOOST_LOG_TRIVIAL(error) << boost::format("%1%:%2%, can not get variant %3%, id %4% from config") % __FUNCTION__ % __LINE__ % variant_name % id_name; + return -1; } + int variant_count = print_variant_opt->size(), new_variant_count = new_variant_opt->size(); - if (variant_indices.empty()) + std::vector> extruder_variant_indices; + for (int i = 0; i < new_extruder_count; i++) { - //can not find any - variant_indices.resize(variant_count, 0); + std::vector variant_indices; for (int j = 0; j < variant_count; j++) - variant_indices[j] = j; + { + if (new_extruder_variants[i] == print_variant_opt->values[j]) + { + variant_indices.push_back(j); + } + } + + if (variant_indices.empty()) + { + // can not find any + variant_indices.resize(variant_count, 0); + for (int j = 0; j < variant_count; j++) + variant_indices[j] = j; + } + extruder_variant_indices.emplace_back(variant_indices); } - extruder_variant_indices.emplace_back(variant_indices); - } - for (int i = 0; i < new_extruder_count; i++) - { - for (int j = 0; j < new_variant_count; j++) + for (int i = 0; i < new_extruder_count; i++) { - if ((i+1 == new_print_id_opt->values[j]) && (new_extruder_variants[i] == new_variant_opt->values[j])) { - new_variant_indices[i] = j; - break; + for (int j = 0; j < new_variant_count; j++) + { + if ((i + 1 == new_print_id_opt->values[j]) && (new_extruder_variants[i] == new_variant_opt->values[j])) + { + new_variant_indices[i] = j; + break; + } } } - } - const ConfigDef* config_def = this->def(); - if (!config_def) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define") % __LINE__; - return -1; - } - for (auto& key : key_set) - { - const ConfigOptionDef* optdef = config_def->get(key); - if (!optdef) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%") % __LINE__ % key; - continue; - } - switch (optdef->type) { - case coStrings: + const ConfigDef *config_def = this->def(); + if (!config_def) { - ConfigOptionStrings* src_opt = new_config.option(key); - if (src_opt) { - ConfigOptionStrings* opt = this->option(key, true); - - //assert(variant_count == opt->size()); - opt->values = src_opt->values; - } - break; + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define") % __LINE__; + return -1; } - case coInts: + for (auto &key : key_set) { - ConfigOptionInts* src_opt = new_config.option(key); - if (src_opt) { - ConfigOptionInts* opt = this->option(key, true); + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%") % __LINE__ % key; + continue; + } + switch (optdef->type) + { + case coStrings: + { + ConfigOptionStrings *src_opt = new_config.option(key); + if (src_opt) + { + ConfigOptionStrings *opt = this->option(key, true); - //assert(variant_count == opt->size()); - opt->values = src_opt->values; + // assert(variant_count == opt->size()); + opt->values = src_opt->values; + } + break; } - break; - } - case coFloats: - { - ConfigOptionFloats* src_opt = new_config.option(key); - if (src_opt) { - ConfigOptionFloats* opt = this->option(key, true); + case coInts: + { + ConfigOptionInts *src_opt = new_config.option(key); + if (src_opt) + { + ConfigOptionInts *opt = this->option(key, true); - std::vector old_values = opt->values; - int old_count = old_values.size(); - int new_count = src_opt->values.size(); + // assert(variant_count == opt->size()); + opt->values = src_opt->values; + } + break; + } + case coFloats: + { + ConfigOptionFloats *src_opt = new_config.option(key); + if (src_opt) + { + ConfigOptionFloats *opt = this->option(key, true); - assert(variant_count == old_count); - assert(new_variant_count == new_count); - opt->values = src_opt->values; + std::vector old_values = opt->values; + int old_count = old_values.size(); + int new_count = src_opt->values.size(); - for (int i = 0; i < new_extruder_count; i++) - { - std::vector& variant_indices = extruder_variant_indices[i]; - int new_variant_index = new_variant_indices[i]; - if ((new_variant_index == -1) || variant_indices.empty()) - continue; + assert(variant_count == old_count); + assert(new_variant_count == new_count); + opt->values = src_opt->values; - for(auto idx : variant_indices){ - assert(idx < old_count); - if (old_values[idx] < opt->values[new_variant_index]) - opt->values[new_variant_index] = old_values[idx]; + for (int i = 0; i < new_extruder_count; i++) + { + std::vector &variant_indices = extruder_variant_indices[i]; + int new_variant_index = new_variant_indices[i]; + if ((new_variant_index == -1) || variant_indices.empty()) + continue; + + for (auto idx : variant_indices) + { + assert(idx < old_count); + if (old_values[idx] < opt->values[new_variant_index]) + opt->values[new_variant_index] = old_values[idx]; + } } } + break; } - break; - } - case coFloatsOrPercents: - { - ConfigOptionFloatsOrPercents* src_opt = new_config.option(key); - if (src_opt) { - ConfigOptionFloatsOrPercents* opt = this->option(key, true); - - std::vector old_values = opt->values; - int old_count = old_values.size(); - int new_count = src_opt->values.size(); + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercents *src_opt = new_config.option(key); + if (src_opt) + { + ConfigOptionFloatsOrPercents *opt = this->option(key, true); - assert(variant_count == old_count); - assert(new_variant_count == new_count); - opt->values = src_opt->values; + std::vector old_values = opt->values; + int old_count = old_values.size(); + int new_count = src_opt->values.size(); - for (int i = 0; i < new_extruder_count; i++) - { - std::vector& variant_indices = extruder_variant_indices[i]; - int new_variant_index = new_variant_indices[i]; - if ((new_variant_index == -1) || variant_indices.empty()) - continue; + assert(variant_count == old_count); + assert(new_variant_count == new_count); + opt->values = src_opt->values; - for(auto idx : variant_indices){ - assert(idx < old_count); - if (old_values[idx] < opt->values[new_variant_index]) - opt->values[new_variant_index] = old_values[idx]; + for (int i = 0; i < new_extruder_count; i++) + { + std::vector &variant_indices = extruder_variant_indices[i]; + int new_variant_index = new_variant_indices[i]; + if ((new_variant_index == -1) || variant_indices.empty()) + continue; + + for (auto idx : variant_indices) + { + assert(idx < old_count); + if (old_values[idx] < opt->values[new_variant_index]) + opt->values[new_variant_index] = old_values[idx]; + } } } + break; } - break; - } - case coBools: - { - ConfigOptionBools* src_opt = new_config.option(key); - if (src_opt) { - ConfigOptionBools* opt = this->option(key, true); - - std::vector old_values = opt->values; - int old_count = old_values.size(); - int new_count = src_opt->values.size(); + case coBools: + { + ConfigOptionBools *src_opt = new_config.option(key); + if (src_opt) + { + ConfigOptionBools *opt = this->option(key, true); - assert(variant_count == old_count); - assert(new_variant_count == new_count); - opt->values = src_opt->values; + std::vector old_values = opt->values; + int old_count = old_values.size(); + int new_count = src_opt->values.size(); - for (int i = 0; i < new_extruder_count; i++) - { - std::vector& variant_indices = extruder_variant_indices[i]; - int new_variant_index = new_variant_indices[i]; - if ((new_variant_index == -1) || variant_indices.empty()) - continue; + assert(variant_count == old_count); + assert(new_variant_count == new_count); + opt->values = src_opt->values; - for(auto idx : variant_indices){ - assert(idx < old_count); - if (old_values[idx]) //enabled - opt->values[new_variant_index] = old_values[idx]; + for (int i = 0; i < new_extruder_count; i++) + { + std::vector &variant_indices = extruder_variant_indices[i]; + int new_variant_index = new_variant_indices[i]; + if ((new_variant_index == -1) || variant_indices.empty()) + continue; + + for (auto idx : variant_indices) + { + assert(idx < old_count); + if (old_values[idx]) // enabled + opt->values[new_variant_index] = old_values[idx]; + } } } - } - break; - } - default: - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%") % __LINE__ % key; - break; + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%") % __LINE__ % key; + break; + } } - } - - return 0; -} -int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector& src_extruder_variants, const std::vector& dst_extruder_variants, const DynamicPrintConfig& dst_config, const std::set& key_sets) -{ - const ConfigDef *config_def = this->def(); - if (!config_def) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; - return -1; + return 0; } - auto get_same_variant_indices = [](const std::vector& extruder_variants, const std::string& variant){ - std::vector indices; - for(int i=0;i &src_extruder_variants, const std::vector &dst_extruder_variants, const DynamicPrintConfig &dst_config, const std::set &key_sets) + { + const ConfigDef *config_def = this->def(); + if (!config_def) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define") % __LINE__; + return -1; + } - std::vector> same_variant_indices; - for(size_t dst_idx =0 ;dst_idx < dst_extruder_variants.size(); ++dst_idx){ - auto& dst_variant = dst_extruder_variants[dst_idx]; - auto indices =get_same_variant_indices(src_extruder_variants, dst_variant); - same_variant_indices.emplace_back(indices); - } + auto get_same_variant_indices = [](const std::vector &extruder_variants, const std::string &variant) + { + std::vector indices; + for (int i = 0; i < extruder_variants.size(); ++i) + if (extruder_variants[i] == variant) + indices.push_back(i); + return indices; + }; - t_config_option_keys keys = this->keys(); - for(auto& key : keys){ - if(key_sets.find(key) == key_sets.end()) - continue; - const ConfigOptionDef* optdef = config_def->get(key); - if(!optdef){ - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; - continue; + std::vector> same_variant_indices; + for (size_t dst_idx = 0; dst_idx < dst_extruder_variants.size(); ++dst_idx) + { + auto &dst_variant = dst_extruder_variants[dst_idx]; + auto indices = get_same_variant_indices(src_extruder_variants, dst_variant); + same_variant_indices.emplace_back(indices); } - switch (optdef->type){ + t_config_option_keys keys = this->keys(); + for (auto &key : keys) + { + if (key_sets.find(key) == key_sets.end()) + continue; + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%") % __LINE__ % key; + continue; + } + + switch (optdef->type) + { case coFloats: { - ConfigOptionFloatsNullable* opt = this->option(key); + ConfigOptionFloatsNullable *opt = this->option(key); auto src_values = opt->values; - auto dst_values = dst_config.option(key) ->values; - for(size_t dst_idx =0; dst_idx < same_variant_indices.size(); ++dst_idx){ - auto& indices = same_variant_indices[dst_idx]; - if(indices.empty()) + auto dst_values = dst_config.option(key)->values; + for (size_t dst_idx = 0; dst_idx < same_variant_indices.size(); ++dst_idx) + { + auto &indices = same_variant_indices[dst_idx]; + if (indices.empty()) continue; bool has_value = false; double target_value = std::numeric_limits::max(); - for(auto idx : indices){ - if(opt && idx < opt->values.size() && !opt->is_nil(idx)){ + for (auto idx : indices) + { + if (opt && idx < opt->values.size() && !opt->is_nil(idx)) + { has_value = true; target_value = std::min(target_value, src_values[idx]); } } - if(has_value) + if (has_value) dst_values[dst_idx] = target_value; } opt->values = dst_values; @@ -7342,23 +7501,26 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vectoroption(key); + ConfigOptionFloatsOrPercentsNullable *opt = this->option(key); auto src_values = opt->values; - auto dst_values = dst_config.option(key) ->values; - for(size_t dst_idx =0; dst_idx < same_variant_indices.size(); ++dst_idx){ - auto& indices = same_variant_indices[dst_idx]; - if(indices.empty()) + auto dst_values = dst_config.option(key)->values; + for (size_t dst_idx = 0; dst_idx < same_variant_indices.size(); ++dst_idx) + { + auto &indices = same_variant_indices[dst_idx]; + if (indices.empty()) continue; bool has_value = false; FloatOrPercent target_value(9999.f, true); - for(auto idx : indices){ - if(opt && !opt->is_nil(idx)){ + for (auto idx : indices) + { + if (opt && !opt->is_nil(idx)) + { has_value = true; target_value = src_values[idx].value < target_value.value ? src_values[idx] : target_value; } } - if(has_value) + if (has_value) dst_values[dst_idx] = target_value; } opt->values = dst_values; @@ -7366,24 +7528,27 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vectoroption(key); + ConfigOptionBoolsNullable *opt = this->option(key); auto src_values = opt->values; - auto dst_values = dst_config.option(key) ->values; - for(size_t dst_idx =0; dst_idx < same_variant_indices.size(); ++dst_idx){ + auto dst_values = dst_config.option(key)->values; + for (size_t dst_idx = 0; dst_idx < same_variant_indices.size(); ++dst_idx) + { auto indices = same_variant_indices[dst_idx]; - if(indices.empty()) + if (indices.empty()) continue; bool has_value = false; bool target_value; - for(auto idx : indices){ - if(opt && !opt->is_nil(idx)){ + for (auto idx : indices) + { + if (opt && !opt->is_nil(idx)) + { has_value = true; target_value = src_values[idx]; break; } } - if(has_value) + if (has_value) dst_values[dst_idx] = target_value; } @@ -7391,446 +7556,460 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector& key_set) -{ - const ConfigDef *config_def = this->def(); - if (!config_def) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; - return -1; - } - - t_config_option_keys keys = this->keys(); - for (auto& key: keys) + // used for object/region config + // use the smallest of multiple to single + /*int DynamicPrintConfig::update_values_from_multi_to_single_2(std::set& key_set) { - if (key_set.find(key) == key_set.end()) - continue; - - const ConfigOptionDef *optdef = config_def->get(key); - if (!optdef) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; - continue; + const ConfigDef *config_def = this->def(); + if (!config_def) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; + return -1; } - switch (optdef->type) { - case coFloats: - { - ConfigOptionFloatsNullable* opt = this->option(key); - double min = 9999.0; - bool has_value = false; - for (int index = 0; index < opt->values.size(); index++) - { - if (!opt->is_nil(index) && (opt->values[index] < min)) { - min = opt->values[index]; - has_value = true; - } - } + t_config_option_keys keys = this->keys(); + for (auto& key: keys) + { + if (key_set.find(key) == key_set.end()) + continue; - opt->values.erase(opt->values.begin() + 1, opt->values.end()); - if (has_value) - opt->values[0] = min; - break; + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; + continue; } - case coFloatsOrPercents: - { - ConfigOptionFloatsOrPercentsNullable * opt = this->option(key); - FloatOrPercent min(9999.f, true); - bool has_value = false; - - for (int index = 0; index < opt->values.size(); index++) + switch (optdef->type) { + case coFloats: { - if (!opt->is_nil(index) && (opt->values[index].value < min.value)) { - min = opt->values[index]; - has_value = true; + ConfigOptionFloatsNullable* opt = this->option(key); + double min = 9999.0; + bool has_value = false; + + for (int index = 0; index < opt->values.size(); index++) + { + if (!opt->is_nil(index) && (opt->values[index] < min)) { + min = opt->values[index]; + has_value = true; + } } + + opt->values.erase(opt->values.begin() + 1, opt->values.end()); + if (has_value) + opt->values[0] = min; + break; } + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercentsNullable * opt = this->option(key); + FloatOrPercent min(9999.f, true); + bool has_value = false; - opt->values.erase(opt->values.begin() + 1, opt->values.end()); - if (has_value) - opt->values[0] = min; - break; - } - case coBools: - { - ConfigOptionBoolsNullable* opt = this->option(key); + for (int index = 0; index < opt->values.size(); index++) + { + if (!opt->is_nil(index) && (opt->values[index].value < min.value)) { + min = opt->values[index]; + has_value = true; + } + } - bool min, has_value = false; - for (int index = 0; index < opt->values.size(); index++) + opt->values.erase(opt->values.begin() + 1, opt->values.end()); + if (has_value) + opt->values[0] = min; + break; + } + case coBools: { - if (!opt->is_nil(index)) { - min = opt->values[index]; - has_value = true; - break; + ConfigOptionBoolsNullable* opt = this->option(key); + + bool min, has_value = false; + for (int index = 0; index < opt->values.size(); index++) + { + if (!opt->is_nil(index)) { + min = opt->values[index]; + has_value = true; + break; + } } - } - opt->values.erase(opt->values.begin() + 1, opt->values.end()); - if (has_value) - opt->values[0] = min; - break; + opt->values.erase(opt->values.begin() + 1, opt->values.end()); + if (has_value) + opt->values[0] = min; + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + break; } - default: - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; - break; } - } - return 0; -}*/ + return 0; + }*/ -std::string -DynamicPrintConfig::get_filament_vendor() const -{ - const ConfigOptionStrings* opt = dynamic_cast (option("filament_vendor")); - if (opt && !opt->values.empty()) + std::string + DynamicPrintConfig::get_filament_vendor() const { - return opt->values[0]; - } - - return std::string(); -} - + const ConfigOptionStrings *opt = dynamic_cast(option("filament_vendor")); + if (opt && !opt->values.empty()) + { + return opt->values[0]; + } -std::string -DynamicPrintConfig::get_filament_type() const -{ - const ConfigOptionStrings* opt = dynamic_cast (option("filament_type")); - if (opt && !opt->values.empty()) - { - return opt->values[0]; + return std::string(); } - return std::string(); -} - -int DynamicPrintConfig::get_extruder_nozzle_volume_count(int extruder_count, std::vector>& nozzle_volume_types) const -{ - int count = extruder_count; - auto opt_extruder_nozzle_count = dynamic_cast(this->option("extruder_nozzle_stats")); - nozzle_volume_types.resize(extruder_count, std::vector{}); - if (opt_extruder_nozzle_count && (opt_extruder_nozzle_count->values.size() == extruder_count)) { - std::vector extruder_nozzle_count_strs = opt_extruder_nozzle_count->values; - std::vector> extruder_nozzle_counts; - - extruder_nozzle_counts = get_extruder_nozzle_stats(extruder_nozzle_count_strs); - count = 0; - for (int i = 0; i < extruder_count; i++) + std::string + DynamicPrintConfig::get_filament_type() const + { + const ConfigOptionStrings *opt = dynamic_cast(option("filament_type")); + if (opt && !opt->values.empty()) { - count += extruder_nozzle_counts[i].size(); - for (auto& iter: extruder_nozzle_counts[i]) - nozzle_volume_types[i].push_back(iter.first); + return opt->values[0]; } + + return std::string(); } - /*auto opt_extruder_nozzle_volume_types = dynamic_cast(this->option("extruder_nozzle_volume_type")); - if (opt_extruder_nozzle_count && opt_extruder_nozzle_volume_types - && (opt_extruder_nozzle_count->values.size() == extruder_count)) { - count = 0; - for (int i = 0; i < extruder_count; i++) - { - if (opt_extruder_nozzle_count->values[i] == 1) - count += 1; - else { - std::unordered_set unique(opt_extruder_nozzle_volume_types->values.begin() + count, - opt_extruder_nozzle_volume_types->values.begin() + count + opt_extruder_nozzle_count->values[i]); - count += unique.size(); + + int DynamicPrintConfig::get_extruder_nozzle_volume_count(int extruder_count, std::vector> &nozzle_volume_types) const + { + int count = extruder_count; + auto opt_extruder_nozzle_count = dynamic_cast(this->option("extruder_nozzle_stats")); + nozzle_volume_types.resize(extruder_count, std::vector{}); + if (opt_extruder_nozzle_count && (opt_extruder_nozzle_count->values.size() == extruder_count)) + { + std::vector extruder_nozzle_count_strs = opt_extruder_nozzle_count->values; + std::vector> extruder_nozzle_counts; + + extruder_nozzle_counts = get_extruder_nozzle_stats(extruder_nozzle_count_strs); + count = 0; + for (int i = 0; i < extruder_count; i++) + { + count += extruder_nozzle_counts[i].size(); + for (auto &iter : extruder_nozzle_counts[i]) + nozzle_volume_types[i].push_back(iter.first); } } - }*/ - return count; -} + /*auto opt_extruder_nozzle_volume_types = dynamic_cast(this->option("extruder_nozzle_volume_type")); + if (opt_extruder_nozzle_count && opt_extruder_nozzle_volume_types + && (opt_extruder_nozzle_count->values.size() == extruder_count)) { + count = 0; + for (int i = 0; i < extruder_count; i++) + { + if (opt_extruder_nozzle_count->values[i] == 1) + count += 1; + else { + std::unordered_set unique(opt_extruder_nozzle_volume_types->values.begin() + count, + opt_extruder_nozzle_volume_types->values.begin() + count + opt_extruder_nozzle_count->values[i]); + count += unique.size(); + } + } + }*/ + return count; + } + std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig &printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector> &nv_types, + std::set &key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt) + { + // int extruder_count; + // bool different_extruder = printer_config.support_different_extruders(extruder_count); + std::vector variant_index; + int variant_count = extruder_count; -std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector>& nv_types, - std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt) -{ - //int extruder_count; - //bool different_extruder = printer_config.support_different_extruders(extruder_count); - std::vector variant_index; - int variant_count = extruder_count; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%") % __LINE__ % extruder_count % extruder_nozzle_volume_count; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%")%__LINE__ %extruder_count %extruder_nozzle_volume_count; + // if (extruder_nozzle_volume_count > 1) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: different nozzle volume processing") % __LINE__; + // apply process settings + // auto opt_nozzle_diameters = this->option("nozzle_diameter"); + // int extruder_count = opt_nozzle_diameters->size(); + auto opt_extruder_type = dynamic_cast(printer_config.option("extruder_type")); + auto opt_nozzle_volume_type = dynamic_cast(printer_config.option("nozzle_volume_type")); + + if (extruder_id > 0 && extruder_id <= static_cast(extruder_count)) + { + variant_index.resize(1); + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(extruder_id - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(extruder_id - 1)); - //if (extruder_nozzle_volume_count > 1) - { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: different nozzle volume processing")%__LINE__; - //apply process settings - //auto opt_nozzle_diameters = this->option("nozzle_diameter"); - //int extruder_count = opt_nozzle_diameters->size(); - auto opt_extruder_type = dynamic_cast(printer_config.option("extruder_type")); - auto opt_nozzle_volume_type = dynamic_cast(printer_config.option("nozzle_volume_type")); - - if (extruder_id > 0 && extruder_id <= static_cast (extruder_count)) { - variant_index.resize(1); - ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(extruder_id - 1)); - NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(extruder_id - 1)); - - if (nozzle_volume_type == nvtHybrid) { - if (extruder_nozzle_volume_count > extruder_count) { - //use the one passed - nozzle_volume_type = filament_nvt; - } - else { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: nozzle_volume_type is default in unsupported machine.")%__LINE__; - assert(false); + if (nozzle_volume_type == nvtHybrid) + { + if (extruder_nozzle_volume_count > extruder_count) + { + // use the one passed + nozzle_volume_type = filament_nvt; + } + else + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: nozzle_volume_type is default in unsupported machine.") % __LINE__; + assert(false); + } } - } - else if (nozzle_volume_type != filament_nvt) { - if (extruder_nozzle_volume_count > extruder_count) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: nozzle_volume_type is %2%, not equal to filament_nvt %3%")%__LINE__ %nozzle_volume_type %filament_nvt; - //assert(false); + else if (nozzle_volume_type != filament_nvt) + { + if (extruder_nozzle_volume_count > extruder_count) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: nozzle_volume_type is %2%, not equal to filament_nvt %3%") % __LINE__ % nozzle_volume_type % filament_nvt; + // assert(false); + } } - } - //variant index - variant_index[0] = get_index_for_extruder(extruder_id, id_name, extruder_type, nozzle_volume_type, variant_name); + // variant index + variant_index[0] = get_index_for_extruder(extruder_id, id_name, extruder_type, nozzle_volume_type, variant_name); - if (variant_index[0] < 0) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, for filament") - % __LINE__ % s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type]; - assert(false); - } + if (variant_index[0] < 0) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, for filament") % __LINE__ % s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type]; + assert(false); + } - variant_count = 1; - } - else { - if (extruder_nozzle_volume_count > extruder_count){ - variant_count = extruder_nozzle_volume_count; + variant_count = 1; } - variant_index.resize(variant_count); - - int v_index = 0; - for (int e_index = 0; e_index < extruder_count; e_index++) + else { - ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(e_index)); - NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(e_index)); - - int nvt_count = 1; - if (extruder_nozzle_volume_count > extruder_count) { - nvt_count = nv_types[e_index].size(); + if (extruder_nozzle_volume_count > extruder_count) + { + variant_count = extruder_nozzle_volume_count; } - for (int nvt_index = 0; nvt_index < nvt_count; nvt_index++) + variant_index.resize(variant_count); + + int v_index = 0; + for (int e_index = 0; e_index < extruder_count; e_index++) { - if (extruder_nozzle_volume_count > extruder_count) - nozzle_volume_type = nv_types[e_index][nvt_index]; - //variant index - variant_index[v_index] = get_index_for_extruder(e_index+1, id_name, extruder_type, nozzle_volume_type, variant_name); - if (variant_index[v_index] < 0) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, extruder_index %4%, nvt_index %5%, nvt_count %6%") - %__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (e_index+1) %nvt_index %nvt_count; - assert(false); - //for some updates happens in a invalid state(caused by popup window) - //we need to avoid crash - variant_index[v_index] = 0; + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(e_index)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(e_index)); + + int nvt_count = 1; + if (extruder_nozzle_volume_count > extruder_count) + { + nvt_count = nv_types[e_index].size(); + } + for (int nvt_index = 0; nvt_index < nvt_count; nvt_index++) + { + if (extruder_nozzle_volume_count > extruder_count) + nozzle_volume_type = nv_types[e_index][nvt_index]; + // variant index + variant_index[v_index] = get_index_for_extruder(e_index + 1, id_name, extruder_type, nozzle_volume_type, variant_name); + if (variant_index[v_index] < 0) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, extruder_index %4%, nvt_index %5%, nvt_count %6%") % __LINE__ % s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (e_index + 1) % nvt_index % nvt_count; + assert(false); + // for some updates happens in a invalid state(caused by popup window) + // we need to avoid crash + variant_index[v_index] = 0; + } + v_index++; } - v_index++; } } - } - const ConfigDef *config_def = this->def(); - if (!config_def) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; - return variant_index; - } - for (auto& key: key_set) - { - const ConfigOptionDef *optdef = config_def->get(key); - if (!optdef) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; - continue; + const ConfigDef *config_def = this->def(); + if (!config_def) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define") % __LINE__; + return variant_index; } - switch (optdef->type) { + for (auto &key : key_set) + { + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%") % __LINE__ % key; + continue; + } + switch (optdef->type) + { case coStrings: { - ConfigOptionStrings * opt = this->option(key); + ConfigOptionStrings *opt = this->option(key); std::vector new_values; new_values.resize(variant_count * stride); for (int e_index = 0; e_index < variant_count; e_index++) { for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + new_values[e_index * stride + i] = opt->get_at(variant_index[e_index] * stride + i); } opt->values = new_values; break; } case coInts: { - ConfigOptionInts * opt = this->option(key); + ConfigOptionInts *opt = this->option(key); std::vector new_values; new_values.resize(variant_count * stride); for (int e_index = 0; e_index < variant_count; e_index++) { for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + new_values[e_index * stride + i] = opt->get_at(variant_index[e_index] * stride + i); } opt->values = new_values; break; } case coFloats: { - ConfigOptionFloats * opt = this->option(key); + ConfigOptionFloats *opt = this->option(key); std::vector new_values; new_values.resize(variant_count * stride); for (int e_index = 0; e_index < variant_count; e_index++) { for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + new_values[e_index * stride + i] = opt->get_at(variant_index[e_index] * stride + i); } opt->values = new_values; break; } case coPercents: { - ConfigOptionPercents * opt = this->option(key); + ConfigOptionPercents *opt = this->option(key); std::vector new_values; new_values.resize(variant_count * stride); for (int e_index = 0; e_index < variant_count; e_index++) { for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + new_values[e_index * stride + i] = opt->get_at(variant_index[e_index] * stride + i); } opt->values = new_values; break; } case coFloatsOrPercents: { - ConfigOptionFloatsOrPercents * opt = this->option(key); + ConfigOptionFloatsOrPercents *opt = this->option(key); std::vector new_values; new_values.resize(variant_count * stride); for (int e_index = 0; e_index < variant_count; e_index++) { for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + new_values[e_index * stride + i] = opt->get_at(variant_index[e_index] * stride + i); } opt->values = new_values; break; } case coBools: { - ConfigOptionBools * opt = this->option(key); + ConfigOptionBools *opt = this->option(key); std::vector new_values; new_values.resize(variant_count * stride); for (int e_index = 0; e_index < variant_count; e_index++) { for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + new_values[e_index * stride + i] = opt->get_at(variant_index[e_index] * stride + i); } opt->values = new_values; break; } case coEnums: { - ConfigOptionEnumsGeneric * opt = this->option(key); + ConfigOptionEnumsGeneric *opt = this->option(key); std::vector new_values; new_values.resize(variant_count * stride); for (int e_index = 0; e_index < variant_count; e_index++) { for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + new_values[e_index * stride + i] = opt->get_at(variant_index[e_index] * stride + i); } opt->values = new_values; break; } default: - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%") % __LINE__ % key; break; + } } } - } - - return variant_index; -} -void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set& key_set, std::string id_name, std::string variant_name) -{ - //int extruder_count, extruder_volume_type_count; - //bool different_extruder = printer_config.support_different_extruders(extruder_count); - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%")%__LINE__ %extruder_count %extruder_nozzle_volume_count; + return variant_index; + } - //if (extruder_nozzle_volume_count > 1) + void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig &printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set &key_set, std::string id_name, std::string variant_name) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: different nozzle volume processing")%__LINE__; - std::vector filament_maps = printer_config.option("filament_map")->values; - size_t filament_count = filament_maps.size(); - //apply process settings - //auto opt_nozzle_diameters = this->option("nozzle_diameter"); - //int extruder_count = opt_nozzle_diameters->size(); - auto opt_extruder_type = dynamic_cast(printer_config.option("extruder_type")); - auto opt_nozzle_volume_type = dynamic_cast(printer_config.option("nozzle_volume_type")); - - auto opt_filament_volume_maps = dynamic_cast(printer_config.option("filament_volume_map")); - std::vector filament_volume_maps; - if (opt_filament_volume_maps) - filament_volume_maps = opt_filament_volume_maps->values; - auto opt_ids = id_name.empty()? nullptr: dynamic_cast(this->option(id_name)); - std::vector variant_index; + // int extruder_count, extruder_volume_type_count; + // bool different_extruder = printer_config.support_different_extruders(extruder_count); - variant_index.resize(filament_count, -1); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%") % __LINE__ % extruder_count % extruder_nozzle_volume_count; - for (int f_index = 0; f_index < filament_count; f_index++) + // if (extruder_nozzle_volume_count > 1) { - ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[f_index] - 1)); - NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[f_index] - 1)); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: different nozzle volume processing") % __LINE__; + std::vector filament_maps = printer_config.option("filament_map")->values; + size_t filament_count = filament_maps.size(); + // apply process settings + // auto opt_nozzle_diameters = this->option("nozzle_diameter"); + // int extruder_count = opt_nozzle_diameters->size(); + auto opt_extruder_type = dynamic_cast(printer_config.option("extruder_type")); + auto opt_nozzle_volume_type = dynamic_cast(printer_config.option("nozzle_volume_type")); + + auto opt_filament_volume_maps = dynamic_cast(printer_config.option("filament_volume_map")); + std::vector filament_volume_maps; + if (opt_filament_volume_maps) + filament_volume_maps = opt_filament_volume_maps->values; + auto opt_ids = id_name.empty() ? nullptr : dynamic_cast(this->option(id_name)); + std::vector variant_index; + + variant_index.resize(filament_count, -1); + + for (int f_index = 0; f_index < filament_count; f_index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[f_index] - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[f_index] - 1)); - if ((extruder_nozzle_volume_count > extruder_count)&&(!filament_volume_maps.empty())) { - nozzle_volume_type = (NozzleVolumeType)(filament_volume_maps[f_index]); - } + if ((extruder_nozzle_volume_count > extruder_count) && (!filament_volume_maps.empty())) + { + nozzle_volume_type = (NozzleVolumeType)(filament_volume_maps[f_index]); + } - //variant index - variant_index[f_index] = get_index_for_extruder(f_index+1, id_name, extruder_type, nozzle_volume_type, variant_name); - if (variant_index[f_index] < 0) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, filament_index %4%, extruder index %5%") - %__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (f_index+1) %filament_maps[f_index]; - assert(false); - //for some updates happens in a invalid state(caused by popup window) - //we need to avoid crash - variant_index[f_index] = 0; - if (opt_ids) { - for (int i = 0; i < opt_ids->values.size(); i++) - if (opt_ids->values[i] == (f_index+1)) { - variant_index[f_index] = i; - break; - } + // variant index + variant_index[f_index] = get_index_for_extruder(f_index + 1, id_name, extruder_type, nozzle_volume_type, variant_name); + if (variant_index[f_index] < 0) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, filament_index %4%, extruder index %5%") % __LINE__ % s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (f_index + 1) % filament_maps[f_index]; + assert(false); + // for some updates happens in a invalid state(caused by popup window) + // we need to avoid crash + variant_index[f_index] = 0; + if (opt_ids) + { + for (int i = 0; i < opt_ids->values.size(); i++) + if (opt_ids->values[i] == (f_index + 1)) + { + variant_index[f_index] = i; + break; + } + } } } - } - const ConfigDef *config_def = this->def(); - if (!config_def) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; - return; - } - for (auto& key: key_set) - { - const ConfigOptionDef *optdef = config_def->get(key); - if (!optdef) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; - continue; + const ConfigDef *config_def = this->def(); + if (!config_def) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define") % __LINE__; + return; } - switch (optdef->type) { + for (auto &key : key_set) + { + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%") % __LINE__ % key; + continue; + } + switch (optdef->type) + { case coStrings: { - ConfigOptionStrings * opt = this->option(key); + ConfigOptionStrings *opt = this->option(key); std::vector new_values; new_values.resize(filament_count); @@ -7843,7 +8022,7 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen } case coInts: { - ConfigOptionInts * opt = this->option(key); + ConfigOptionInts *opt = this->option(key); std::vector new_values; new_values.resize(filament_count); @@ -7856,7 +8035,7 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen } case coFloats: { - ConfigOptionFloats * opt = this->option(key); + ConfigOptionFloats *opt = this->option(key); std::vector new_values; new_values.resize(filament_count); @@ -7869,7 +8048,7 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen } case coPercents: { - ConfigOptionPercents * opt = this->option(key); + ConfigOptionPercents *opt = this->option(key); std::vector new_values; new_values.resize(filament_count); @@ -7882,7 +8061,7 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen } case coFloatsOrPercents: { - ConfigOptionFloatsOrPercents * opt = this->option(key); + ConfigOptionFloatsOrPercents *opt = this->option(key); std::vector new_values; new_values.resize(filament_count); @@ -7895,7 +8074,7 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen } case coBools: { - ConfigOptionBools * opt = this->option(key); + ConfigOptionBools *opt = this->option(key); std::vector new_values; new_values.resize(filament_count); @@ -7908,7 +8087,7 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen } case coEnums: { - ConfigOptionEnumsGeneric * opt = this->option(key); + ConfigOptionEnumsGeneric *opt = this->option(key); std::vector new_values; new_values.resize(filament_count); @@ -7920,1097 +8099,1158 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen break; } default: - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%") % __LINE__ % key; break; + } } } } -} + void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfig &new_config, const t_config_option_keys &keys, const std::set &different_keys, + std::string extruder_id_name, std::string extruder_variant_name, std::set &key_set1, std::set &key_set2) + { + std::vector cur_extruder_ids, target_extruder_ids, variant_index; + std::vector cur_extruder_variants, target_extruder_variants; + + if (!extruder_id_name.empty()) + { + if (this->option(extruder_id_name)) + cur_extruder_ids = this->option(extruder_id_name)->values; + if (new_config.option(extruder_id_name)) + target_extruder_ids = new_config.option(extruder_id_name)->values; + } + if (this->option(extruder_variant_name)) + cur_extruder_variants = this->option(extruder_variant_name, true)->values; + if (new_config.option(extruder_variant_name)) + target_extruder_variants = new_config.option(extruder_variant_name, true)->values; + + int cur_variant_count = cur_extruder_variants.size(); + int target_variant_count = target_extruder_variants.size(); + + variant_index.resize(target_variant_count, -1); + if (cur_variant_count == 0) + { + variant_index[0] = 0; + } + else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()) + { + // should not happen + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") % extruder_variant_name % cur_variant_count % extruder_id_name % cur_extruder_ids.size(); + } + else if ((target_extruder_ids.size() > 0) && target_variant_count != target_extruder_ids.size()) + { + // should not happen + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") % extruder_variant_name % target_variant_count % extruder_id_name % target_extruder_ids.size(); + } + else + { + for (int i = 0; i < target_variant_count; i++) + { + for (int j = 0; j < cur_variant_count; j++) + { + if ((target_extruder_variants[i] == cur_extruder_variants[j]) && (target_extruder_ids.empty() || (target_extruder_ids[i] == cur_extruder_ids[j]))) + { + variant_index[i] = j; + break; + } + } + } + } + + for (auto &opt : keys) + { + ConfigOption *opt_src = this->option(opt); + const ConfigOption *opt_target = new_config.option(opt); + if (opt_src && opt_target && (*opt_src != *opt_target)) + { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from old_value %2% to inherit's value %3%") % opt % (opt_src->serialize()) % (opt_target->serialize()); + if (different_keys.find(opt) == different_keys.end()) + { + opt_src->set(opt_target); + } + else + { + if (opt_target->is_scalar() || ((key_set1.find(opt) == key_set1.end()) && (key_set2.empty() || (key_set2.find(opt) == key_set2.end())))) + { + // nothing to do, keep the original one + } + else + { + ConfigOptionVectorBase *opt_vec_src = static_cast(opt_src); + const ConfigOptionVectorBase *opt_vec_dest = static_cast(opt_target); + int stride = 1; + if (key_set2.find(opt) != key_set2.end()) + stride = 2; + opt_vec_src->set_with_restore(opt_vec_dest, variant_index, stride); + } + } + } + } + return; + } + + void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig &new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set &key_set1, std::set &key_set2) + { + std::vector cur_extruder_ids, target_extruder_ids, variant_index; + std::vector cur_extruder_variants, target_extruder_variants; -void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set& different_keys, - std::string extruder_id_name, std::string extruder_variant_name, std::set& key_set1, std::set& key_set2) -{ - std::vector cur_extruder_ids, target_extruder_ids, variant_index; - std::vector cur_extruder_variants, target_extruder_variants; - - if (!extruder_id_name.empty()) { - if (this->option(extruder_id_name)) - cur_extruder_ids = this->option(extruder_id_name)->values; - if (new_config.option(extruder_id_name)) - target_extruder_ids = new_config.option(extruder_id_name)->values; - } - if (this->option(extruder_variant_name)) - cur_extruder_variants = this->option(extruder_variant_name, true)->values; - if (new_config.option(extruder_variant_name)) - target_extruder_variants = new_config.option(extruder_variant_name, true)->values; + if (!extruder_id_name.empty()) + { + if (this->option(extruder_id_name)) + cur_extruder_ids = this->option(extruder_id_name)->values; + if (new_config.option(extruder_id_name)) + target_extruder_ids = new_config.option(extruder_id_name)->values; + } + if (this->option(extruder_variant_name)) + cur_extruder_variants = this->option(extruder_variant_name, true)->values; + if (new_config.option(extruder_variant_name)) + target_extruder_variants = new_config.option(extruder_variant_name, true)->values; - int cur_variant_count = cur_extruder_variants.size(); - int target_variant_count = target_extruder_variants.size(); + int cur_variant_count = cur_extruder_variants.size(); + int target_variant_count = target_extruder_variants.size(); - variant_index.resize(target_variant_count, -1); - if (cur_variant_count == 0) { - variant_index[0] = 0; - } - else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){ - //should not happen - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") - %extruder_variant_name %cur_variant_count %extruder_id_name %cur_extruder_ids.size(); - } - else if ((target_extruder_ids.size() > 0) && target_variant_count != target_extruder_ids.size()){ - //should not happen - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") - %extruder_variant_name %target_variant_count %extruder_id_name %target_extruder_ids.size(); - } - else { - for (int i = 0; i < target_variant_count; i++) + if (cur_variant_count > 0) + variant_index.resize(cur_variant_count, -1); + else + variant_index.resize(1, 0); + + if (target_variant_count == 0) + { + variant_index[0] = 0; + } + else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()) + { + // should not happen + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") % extruder_variant_name % cur_variant_count % extruder_id_name % cur_extruder_ids.size(); + } + else if ((target_extruder_ids.size() > 0) && target_variant_count != target_extruder_ids.size()) + { + // should not happen + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") % extruder_variant_name % target_variant_count % extruder_id_name % target_extruder_ids.size(); + } + else { - for (int j = 0; j < cur_variant_count; j++) + for (int i = 0; i < cur_variant_count; i++) { - if ((target_extruder_variants[i] == cur_extruder_variants[j]) - &&(target_extruder_ids.empty() || (target_extruder_ids[i] == cur_extruder_ids[j]))) + for (int j = 0; j < target_variant_count; j++) { - variant_index[i] = j; - break; + if ((cur_extruder_variants[i] == target_extruder_variants[j]) && (cur_extruder_ids.empty() || (cur_extruder_ids[i] == target_extruder_ids[j]))) + { + variant_index[i] = j; + break; + } } } } - } - for (auto& opt : keys) { - ConfigOption *opt_src = this->option(opt); - const ConfigOption *opt_target = new_config.option(opt); - if (opt_src && opt_target && (*opt_src != *opt_target)) { - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from old_value %2% to inherit's value %3%") - %opt %(opt_src->serialize()) %(opt_target->serialize()); - if (different_keys.find(opt) == different_keys.end()) { - opt_src->set(opt_target); - } - else { - if (opt_target->is_scalar() - || ((key_set1.find(opt) == key_set1.end()) && (key_set2.empty() || (key_set2.find(opt) == key_set2.end())))) { - //nothing to do, keep the original one + const t_config_option_keys &keys = new_config.keys(); + for (auto &opt : keys) + { + if ((opt == extruder_id_name) || (opt == extruder_variant_name)) + continue; + ConfigOption *opt_src = this->option(opt); + const ConfigOption *opt_target = new_config.option(opt); + if (opt_src && opt_target && (*opt_src != *opt_target)) + { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from base_value %2% to child's value %3%") % opt % (opt_src->serialize()) % (opt_target->serialize()); + if (opt_target->is_scalar() || ((key_set1.find(opt) == key_set1.end()) && (key_set2.empty() || (key_set2.find(opt) == key_set2.end())))) + { + // nothing to do, keep the original one + opt_src->set(opt_target); } - else { - ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); - const ConfigOptionVectorBase* opt_vec_dest = static_cast(opt_target); + else + { + ConfigOptionVectorBase *opt_vec_src = static_cast(opt_src); + const ConfigOptionVectorBase *opt_vec_dest = static_cast(opt_target); int stride = 1; if (key_set2.find(opt) != key_set2.end()) stride = 2; - opt_vec_src->set_with_restore(opt_vec_dest, variant_index, stride); + opt_vec_src->set_only_diff(opt_vec_dest, variant_index, stride); } } } + return; } - return; -} - -void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig& new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set& key_set1, std::set& key_set2) -{ - std::vector cur_extruder_ids, target_extruder_ids, variant_index; - std::vector cur_extruder_variants, target_extruder_variants; - - if (!extruder_id_name.empty()) { - if (this->option(extruder_id_name)) - cur_extruder_ids = this->option(extruder_id_name)->values; - if (new_config.option(extruder_id_name)) - target_extruder_ids = new_config.option(extruder_id_name)->values; - } - if (this->option(extruder_variant_name)) - cur_extruder_variants = this->option(extruder_variant_name, true)->values; - if (new_config.option(extruder_variant_name)) - target_extruder_variants = new_config.option(extruder_variant_name, true)->values; - - int cur_variant_count = cur_extruder_variants.size(); - int target_variant_count = target_extruder_variants.size(); - if (cur_variant_count > 0) - variant_index.resize(cur_variant_count, -1); - else - variant_index.resize(1, 0); - - if (target_variant_count == 0) { - variant_index[0] = 0; - } - else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){ - //should not happen - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") - %extruder_variant_name %cur_variant_count %extruder_id_name %cur_extruder_ids.size(); - } - else if ((target_extruder_ids.size() > 0) && target_variant_count != target_extruder_ids.size()){ - //should not happen - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") - %extruder_variant_name %target_variant_count %extruder_id_name %target_extruder_ids.size(); - } - else { - for (int i = 0; i < cur_variant_count; i++) + void update_static_print_config_from_dynamic(ConfigBase &config, const DynamicPrintConfig &dest_config, std::vector variant_index, std::set &key_set1, int stride) + { + if (variant_index.size() > 0) { - for (int j = 0; j < target_variant_count; j++) + const t_config_option_keys &keys = dest_config.keys(); + for (auto &opt : keys) { - if ((cur_extruder_variants[i] == target_extruder_variants[j]) - &&(cur_extruder_ids.empty() || (cur_extruder_ids[i] == target_extruder_ids[j]))) + ConfigOption *opt_src = config.option(opt); + const ConfigOption *opt_dest = dest_config.option(opt); + if (opt_src && opt_dest && (*opt_src != *opt_dest)) { - variant_index[i] = j; - break; + if (opt_dest->is_scalar() || (key_set1.find(opt) == key_set1.end())) + opt_src->set(opt_dest); + else + { + ConfigOptionVectorBase *opt_vec_src = static_cast(opt_src); + const ConfigOptionVectorBase *opt_vec_dest = static_cast(opt_dest); + opt_vec_src->set_to_index(opt_vec_dest, variant_index, stride); + } } } } + else + config.apply(dest_config, true); } - const t_config_option_keys &keys = new_config.keys(); - for (auto& opt : keys) { - if ((opt == extruder_id_name) || (opt == extruder_variant_name)) - continue; - ConfigOption *opt_src = this->option(opt); - const ConfigOption *opt_target = new_config.option(opt); - if (opt_src && opt_target && (*opt_src != *opt_target)) { - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from base_value %2% to child's value %3%") - %opt %(opt_src->serialize()) %(opt_target->serialize()); - if (opt_target->is_scalar() - || ((key_set1.find(opt) == key_set1.end()) && (key_set2.empty() || (key_set2.find(opt) == key_set2.end())))) { - //nothing to do, keep the original one - opt_src->set(opt_target); - } - else { - ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); - const ConfigOptionVectorBase* opt_vec_dest = static_cast(opt_target); - int stride = 1; - if (key_set2.find(opt) != key_set2.end()) - stride = 2; - opt_vec_src->set_only_diff(opt_vec_dest, variant_index, stride); - } - } - } - return; -} + void compute_filament_override_value(const std::string &opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig &new_full_config, + t_config_option_keys &diff_keys, DynamicPrintConfig &filament_overrides, std::vector &f_map_indices) + { + bool is_nil = opt_new_filament->is_nil(); -void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector variant_index, std::set& key_set1, int stride) -{ - if (variant_index.size() > 0) { - const t_config_option_keys &keys = dest_config.keys(); - for (auto& opt : keys) { - ConfigOption *opt_src = config.option(opt); - const ConfigOption *opt_dest = dest_config.option(opt); - if (opt_src && opt_dest && (*opt_src != *opt_dest)) { - if (opt_dest->is_scalar() || (key_set1.find(opt) == key_set1.end())) - opt_src->set(opt_dest); - else { - ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); - const ConfigOptionVectorBase* opt_vec_dest = static_cast(opt_dest); - opt_vec_src->set_to_index(opt_vec_dest, variant_index, stride); - } - } + // ugly code, for these params, we should ignore the value in filament params + ConfigOptionBoolsNullable opt_long_retraction_default; + if (opt_key == "long_retractions_when_cut" && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament) + { + auto ptr = dynamic_cast(opt_new_filament); + for (size_t idx = 0; idx < ptr->values.size(); ++idx) + opt_long_retraction_default.values.push_back(ptr->nil_value()); + opt_new_filament = &opt_long_retraction_default; } - } - else - config.apply(dest_config, true); -} - -void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config, - t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_map_indices) -{ - bool is_nil = opt_new_filament->is_nil(); - - // ugly code, for these params, we should ignore the value in filament params - ConfigOptionBoolsNullable opt_long_retraction_default; - if (opt_key == "long_retractions_when_cut" && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament) { - auto ptr = dynamic_cast(opt_new_filament); - for (size_t idx = 0; idx < ptr->values.size(); ++idx) - opt_long_retraction_default.values.push_back(ptr->nil_value()); - opt_new_filament = &opt_long_retraction_default; - } - - ConfigOptionFloatsNullable opt_retraction_distance_default; - if (opt_key == "retraction_distances_when_cut" && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament) { - auto ptr = dynamic_cast(opt_new_filament); - for (size_t idx = 0; idx < ptr->values.size(); ++idx) - opt_long_retraction_default.values.push_back(ptr->nil_value()); - opt_new_filament = &opt_retraction_distance_default; - } - - auto opt_copy = opt_new_machine->clone(); - opt_copy->apply_override(opt_new_filament, f_map_indices); - bool changed = *opt_old_machine != *opt_copy; - - if (changed) { - diff_keys.emplace_back(opt_key); - filament_overrides.set_key_value(opt_key, opt_copy); - } - else - delete opt_copy; -} + ConfigOptionFloatsNullable opt_retraction_distance_default; + if (opt_key == "retraction_distances_when_cut" && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament) + { + auto ptr = dynamic_cast(opt_new_filament); + for (size_t idx = 0; idx < ptr->values.size(); ++idx) + opt_long_retraction_default.values.push_back(ptr->nil_value()); + opt_new_filament = &opt_retraction_distance_default; + } -//BBS: pass map to recording all invalid valies -//FIXME localize this function. -std::map validate(const FullPrintConfig &cfg, bool under_cli) -{ - std::map error_message; - // --layer-height - if (cfg.get_abs_value("layer_height") <= 0) { - error_message.emplace("layer_height", L("invalid value ") + std::to_string(cfg.get_abs_value("layer_height"))); - } - else if (fabs(fmod(cfg.get_abs_value("layer_height"), SCALING_FACTOR)) > 1e-4) { - error_message.emplace("layer_height", L("invalid value ") + std::to_string(cfg.get_abs_value("layer_height"))); - } + auto opt_copy = opt_new_machine->clone(); + opt_copy->apply_override(opt_new_filament, f_map_indices); + bool changed = *opt_old_machine != *opt_copy; - // --first-layer-height - if (cfg.initial_layer_print_height.value <= 0) { - error_message.emplace("initial_layer_print_height", L("invalid value ") + std::to_string(cfg.initial_layer_print_height.value)); + if (changed) + { + diff_keys.emplace_back(opt_key); + filament_overrides.set_key_value(opt_key, opt_copy); + } + else + delete opt_copy; } - // --filament-diameter - for (double fd : cfg.filament_diameter.values) - if (fd < 1) { - error_message.emplace("filament_diameter", L("invalid value ") + cfg.filament_diameter.serialize()); - break; + // BBS: pass map to recording all invalid valies + // FIXME localize this function. + std::map validate(const FullPrintConfig &cfg, bool under_cli) + { + std::map error_message; + // --layer-height + if (cfg.get_abs_value("layer_height") <= 0) + { + error_message.emplace("layer_height", L("invalid value ") + std::to_string(cfg.get_abs_value("layer_height"))); } - - // --nozzle-diameter - for (double nd : cfg.nozzle_diameter.values) - if (nd < 0.005) { - error_message.emplace("nozzle_diameter", L("invalid value ") + cfg.nozzle_diameter.serialize()); - break; + else if (fabs(fmod(cfg.get_abs_value("layer_height"), SCALING_FACTOR)) > 1e-4) + { + error_message.emplace("layer_height", L("invalid value ") + std::to_string(cfg.get_abs_value("layer_height"))); } - // --perimeters - if (cfg.wall_loops.value < 0) { - error_message.emplace("wall_loops", L("invalid value ") + std::to_string(cfg.wall_loops.value)); - } - - // --solid-layers - if (cfg.top_shell_layers < 0) { - error_message.emplace("top_shell_layers", L("invalid value ") + std::to_string(cfg.top_shell_layers)); - } - if (cfg.bottom_shell_layers < 0) { - error_message.emplace("bottom_shell_layers", L("invalid value ") + std::to_string(cfg.bottom_shell_layers)); - } - - std::setwith_firmware_retraction_flavor = { - gcfSmoothie, - gcfRepRapSprinter, - gcfRepRapFirmware, - gcfMarlinLegacy, - gcfMarlinFirmware, - gcfMachinekit, - gcfRepetier, - gcfKlipper - }; - if (cfg.use_firmware_retraction.value && with_firmware_retraction_flavor.count(cfg.gcode_flavor.value)==0) - error_message.emplace("gcode_flavor",L("--use-firmware-retraction is only supported by Marlin, Klipper, Smoothie, RepRapFirmware, Repetier and Machinekit firmware")); - - if (cfg.use_firmware_retraction.value) - for (unsigned char wipe : cfg.wipe.values) - if (wipe) - error_message.emplace("wipe",L("--use-firmware-retraction is not compatible with --wipe")); - - // --gcode-flavor - if (! print_config_def.get("gcode_flavor")->has_enum_value(cfg.gcode_flavor.serialize())) { - error_message.emplace("gcode_flavor", L("invalid value ") + cfg.gcode_flavor.serialize()); - } - - // --fill-pattern - if (! print_config_def.get("sparse_infill_pattern")->has_enum_value(cfg.sparse_infill_pattern.serialize())) { - error_message.emplace("sparse_infill_pattern", L("invalid value ") + cfg.sparse_infill_pattern.serialize()); - } + // --first-layer-height + if (cfg.initial_layer_print_height.value <= 0) + { + error_message.emplace("initial_layer_print_height", L("invalid value ") + std::to_string(cfg.initial_layer_print_height.value)); + } - // --top-fill-pattern - if (! print_config_def.get("top_surface_pattern")->has_enum_value(cfg.top_surface_pattern.serialize())) { - error_message.emplace("top_surface_pattern", L("invalid value ") + cfg.top_surface_pattern.serialize()); - } + // --filament-diameter + for (double fd : cfg.filament_diameter.values) + if (fd < 1) + { + error_message.emplace("filament_diameter", L("invalid value ") + cfg.filament_diameter.serialize()); + break; + } - // --bottom-fill-pattern - if (!print_config_def.get("bottom_surface_pattern")->has_enum_value(cfg.bottom_surface_pattern.serialize())) { - error_message.emplace("bottom_surface_pattern", L("invalid value ") + cfg.bottom_surface_pattern.serialize()); - } + // --nozzle-diameter + for (double nd : cfg.nozzle_diameter.values) + if (nd < 0.005) + { + error_message.emplace("nozzle_diameter", L("invalid value ") + cfg.nozzle_diameter.serialize()); + break; + } - // --soild-fill-pattern - if (!print_config_def.get("internal_solid_infill_pattern")->has_enum_value(cfg.internal_solid_infill_pattern.serialize())) { - error_message.emplace("internal_solid_infill_pattern", L("invalid value ") + cfg.internal_solid_infill_pattern.serialize()); - } + // --perimeters + if (cfg.wall_loops.value < 0) + { + error_message.emplace("wall_loops", L("invalid value ") + std::to_string(cfg.wall_loops.value)); + } - // --fill-density - if (fabs(cfg.sparse_infill_density.value - 100.) < EPSILON && - ! print_config_def.get("top_surface_pattern")->has_enum_value(cfg.sparse_infill_pattern.serialize())) { - error_message.emplace("sparse_infill_pattern", cfg.sparse_infill_pattern.serialize() + L(" doesn't work at 100%% density ")); - } + // --solid-layers + if (cfg.top_shell_layers < 0) + { + error_message.emplace("top_shell_layers", L("invalid value ") + std::to_string(cfg.top_shell_layers)); + } + if (cfg.bottom_shell_layers < 0) + { + error_message.emplace("bottom_shell_layers", L("invalid value ") + std::to_string(cfg.bottom_shell_layers)); + } - // --skirt-height - if (cfg.skirt_height < 0) { - error_message.emplace("skirt_height", L("invalid value ") + std::to_string(cfg.skirt_height)); - } + std::set with_firmware_retraction_flavor = { + gcfSmoothie, + gcfRepRapSprinter, + gcfRepRapFirmware, + gcfMarlinLegacy, + gcfMarlinFirmware, + gcfMachinekit, + gcfRepetier, + gcfKlipper}; + if (cfg.use_firmware_retraction.value && with_firmware_retraction_flavor.count(cfg.gcode_flavor.value) == 0) + error_message.emplace("gcode_flavor", L("--use-firmware-retraction is only supported by Marlin, Klipper, Smoothie, RepRapFirmware, Repetier and Machinekit firmware")); + + if (cfg.use_firmware_retraction.value) + for (unsigned char wipe : cfg.wipe.values) + if (wipe) + error_message.emplace("wipe", L("--use-firmware-retraction is not compatible with --wipe")); + + // --gcode-flavor + if (!print_config_def.get("gcode_flavor")->has_enum_value(cfg.gcode_flavor.serialize())) + { + error_message.emplace("gcode_flavor", L("invalid value ") + cfg.gcode_flavor.serialize()); + } - // --bridge-flow-ratio - if (cfg.bridge_flow <= 0) { - error_message.emplace("bridge_flow", L("invalid value ") + std::to_string(cfg.bridge_flow)); - } + // --fill-pattern + if (!print_config_def.get("sparse_infill_pattern")->has_enum_value(cfg.sparse_infill_pattern.serialize())) + { + error_message.emplace("sparse_infill_pattern", L("invalid value ") + cfg.sparse_infill_pattern.serialize()); + } - // extruder clearance - if (cfg.extruder_clearance_max_radius <= 0) { - error_message.emplace("extruder_clearance_max_radius", L("invalid value ") + std::to_string(cfg.extruder_clearance_max_radius)); - } - if (cfg.extruder_clearance_height_to_rod <= 0) { - error_message.emplace("extruder_clearance_height_to_rod", L("invalid value ") + std::to_string(cfg.extruder_clearance_height_to_rod)); - } - if (cfg.extruder_clearance_height_to_lid <= 0) { - error_message.emplace("extruder_clearance_height_to_lid", L("invalid value ") + std::to_string(cfg.extruder_clearance_height_to_lid)); - } - if (cfg.nozzle_height <= 0) - error_message.emplace("nozzle_height", L("invalid value ") + std::to_string(cfg.nozzle_height)); + // --top-fill-pattern + if (!print_config_def.get("top_surface_pattern")->has_enum_value(cfg.top_surface_pattern.serialize())) + { + error_message.emplace("top_surface_pattern", L("invalid value ") + cfg.top_surface_pattern.serialize()); + } - // --extrusion-multiplier - for (double em : cfg.filament_flow_ratio.values) - if (em <= 0) { - error_message.emplace("filament_flow_ratio", L("invalid value ") + cfg.filament_flow_ratio.serialize()); - break; + // --bottom-fill-pattern + if (!print_config_def.get("bottom_surface_pattern")->has_enum_value(cfg.bottom_surface_pattern.serialize())) + { + error_message.emplace("bottom_surface_pattern", L("invalid value ") + cfg.bottom_surface_pattern.serialize()); } - // The following test was commented out after 482841b, see also https://github.com/prusa3d/PrusaSlicer/pull/6743. - // The backend should now handle this case correctly. I.e., zero default_acceleration behaves as if all others - // were zero too. This is now consistent with what the UI said would happen. - // The UI already grays the fields out, there is no more reason to reject it here. This function validates the - // config before exporting, leaving this check in would mean that config would be rejected before export - // (although both the UI and the backend handle it). - // --default-acceleration - //if ((cfg.outer_wall_acceleration != 0. || cfg.infill_acceleration != 0. || cfg.bridge_acceleration != 0. || cfg.initial_layer_acceleration != 0.) && - // cfg.default_acceleration == 0.) - // return "Invalid zero value for --default-acceleration when using other acceleration settings"; + // --soild-fill-pattern + if (!print_config_def.get("internal_solid_infill_pattern")->has_enum_value(cfg.internal_solid_infill_pattern.serialize())) + { + error_message.emplace("internal_solid_infill_pattern", L("invalid value ") + cfg.internal_solid_infill_pattern.serialize()); + } - // --spiral-vase - //for non-cli case, we will popup dialog for spiral mode correction - if (cfg.spiral_mode && under_cli) { - // Note that we might want to have more than one perimeter on the bottom - // solid layers. - if (cfg.wall_loops != 1) { - error_message.emplace("wall_loops", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.wall_loops)); - //return "Can't make more than one perimeter when spiral vase mode is enabled"; - //return "Can't make less than one perimeter when spiral vase mode is enabled"; + // --fill-density + if (fabs(cfg.sparse_infill_density.value - 100.) < EPSILON && + !print_config_def.get("top_surface_pattern")->has_enum_value(cfg.sparse_infill_pattern.serialize())) + { + error_message.emplace("sparse_infill_pattern", cfg.sparse_infill_pattern.serialize() + L(" doesn't work at 100%% density ")); } - if (cfg.sparse_infill_density > 0) { - error_message.emplace("sparse_infill_density", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.sparse_infill_density)); - //return "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0"; + // --skirt-height + if (cfg.skirt_height < 0) + { + error_message.emplace("skirt_height", L("invalid value ") + std::to_string(cfg.skirt_height)); } - if (cfg.top_shell_layers > 0) { - error_message.emplace("top_shell_layers", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.top_shell_layers)); - //return "Spiral vase mode is not compatible with top solid layers"; + // --bridge-flow-ratio + if (cfg.bridge_flow <= 0) + { + error_message.emplace("bridge_flow", L("invalid value ") + std::to_string(cfg.bridge_flow)); } - if (cfg.enable_support ) { - error_message.emplace("enable_support", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.enable_support)); - //return "Spiral vase mode is not compatible with support"; + // extruder clearance + if (cfg.extruder_clearance_max_radius <= 0) + { + error_message.emplace("extruder_clearance_max_radius", L("invalid value ") + std::to_string(cfg.extruder_clearance_max_radius)); + } + if (cfg.extruder_clearance_height_to_rod <= 0) + { + error_message.emplace("extruder_clearance_height_to_rod", L("invalid value ") + std::to_string(cfg.extruder_clearance_height_to_rod)); } - if (cfg.enforce_support_layers > 0) { - error_message.emplace("enforce_support_layers", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.enforce_support_layers)); - //return "Spiral vase mode is not compatible with support"; + if (cfg.extruder_clearance_height_to_lid <= 0) + { + error_message.emplace("extruder_clearance_height_to_lid", L("invalid value ") + std::to_string(cfg.extruder_clearance_height_to_lid)); } - } + if (cfg.nozzle_height <= 0) + error_message.emplace("nozzle_height", L("invalid value ") + std::to_string(cfg.nozzle_height)); - // extrusion widths - { - double max_nozzle_diameter = 0.; - for (double dmr : cfg.nozzle_diameter.values) - max_nozzle_diameter = std::max(max_nozzle_diameter, dmr); - const char *widths[] = { - "outer_wall_line_width", - "inner_wall_line_width", - "sparse_infill_line_width", - "internal_solid_infill_line_width", - "top_surface_line_width", - "support_line_width", - "initial_layer_line_width", - "skin_infill_line_width", - "skeleton_infill_line_width"}; - for (size_t i = 0; i < sizeof(widths) / sizeof(widths[i]); ++ i) { - std::string key(widths[i]); - if (cfg.get_abs_value(key) > 2.5 * max_nozzle_diameter) { - error_message.emplace(key, L("too large line width ") + std::to_string(cfg.get_abs_value(key))); - //return std::string("Too Large line width: ") + key; + // --extrusion-multiplier + for (double em : cfg.filament_flow_ratio.values) + if (em <= 0) + { + error_message.emplace("filament_flow_ratio", L("invalid value ") + cfg.filament_flow_ratio.serialize()); + break; } - } - } - // Out of range validation of numeric values. - for (const std::string &opt_key : cfg.keys()) { - const ConfigOption *opt = cfg.optptr(opt_key); - assert(opt != nullptr); - const ConfigOptionDef *optdef = print_config_def.get(opt_key); - assert(optdef != nullptr); - bool out_of_range = false; - switch (opt->type()) { - case coFloat: - case coPercent: - case coFloatOrPercent: - { - auto *fopt = static_cast(opt); - out_of_range = fopt->value < optdef->min || fopt->value > optdef->max; - break; - } - case coFloats: - case coPercents: - for (double v : static_cast*>(opt)->values) - if (v < optdef->min || v > optdef->max) { - out_of_range = true; - break; - } - break; - case coInt: + // The following test was commented out after 482841b, see also https://github.com/prusa3d/PrusaSlicer/pull/6743. + // The backend should now handle this case correctly. I.e., zero default_acceleration behaves as if all others + // were zero too. This is now consistent with what the UI said would happen. + // The UI already grays the fields out, there is no more reason to reject it here. This function validates the + // config before exporting, leaving this check in would mean that config would be rejected before export + // (although both the UI and the backend handle it). + // --default-acceleration + // if ((cfg.outer_wall_acceleration != 0. || cfg.infill_acceleration != 0. || cfg.bridge_acceleration != 0. || cfg.initial_layer_acceleration != 0.) && + // cfg.default_acceleration == 0.) + // return "Invalid zero value for --default-acceleration when using other acceleration settings"; + + // --spiral-vase + // for non-cli case, we will popup dialog for spiral mode correction + if (cfg.spiral_mode && under_cli) { - auto *iopt = static_cast(opt); - out_of_range = iopt->value < optdef->min || iopt->value > optdef->max; - break; + // Note that we might want to have more than one perimeter on the bottom + // solid layers. + if (cfg.wall_loops != 1) + { + error_message.emplace("wall_loops", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.wall_loops)); + // return "Can't make more than one perimeter when spiral vase mode is enabled"; + // return "Can't make less than one perimeter when spiral vase mode is enabled"; + } + + if (cfg.sparse_infill_density > 0) + { + error_message.emplace("sparse_infill_density", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.sparse_infill_density)); + // return "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0"; + } + + if (cfg.top_shell_layers > 0) + { + error_message.emplace("top_shell_layers", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.top_shell_layers)); + // return "Spiral vase mode is not compatible with top solid layers"; + } + + if (cfg.enable_support) + { + error_message.emplace("enable_support", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.enable_support)); + // return "Spiral vase mode is not compatible with support"; + } + if (cfg.enforce_support_layers > 0) + { + error_message.emplace("enforce_support_layers", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.enforce_support_layers)); + // return "Spiral vase mode is not compatible with support"; + } } - case coInts: - for (int v : static_cast*>(opt)->values) - if (v < optdef->min || v > optdef->max) { - out_of_range = true; - break; + + // extrusion widths + { + double max_nozzle_diameter = 0.; + for (double dmr : cfg.nozzle_diameter.values) + max_nozzle_diameter = std::max(max_nozzle_diameter, dmr); + const char *widths[] = { + "outer_wall_line_width", + "inner_wall_line_width", + "sparse_infill_line_width", + "internal_solid_infill_line_width", + "top_surface_line_width", + "support_line_width", + "initial_layer_line_width", + "skin_infill_line_width", + "skeleton_infill_line_width"}; + for (size_t i = 0; i < sizeof(widths) / sizeof(widths[i]); ++i) + { + std::string key(widths[i]); + if (cfg.get_abs_value(key) > 2.5 * max_nozzle_diameter) + { + error_message.emplace(key, L("too large line width ") + std::to_string(cfg.get_abs_value(key))); + // return std::string("Too Large line width: ") + key; } - break; - default:; + } } - if (out_of_range) { - if (error_message.find(opt_key) == error_message.end()) - error_message.emplace(opt_key, opt->serialize() + L(" not in range ") +"[" + std::to_string(optdef->min) + "," + std::to_string(optdef->max) + "]"); - //return std::string("Value out of range: " + opt_key); + + // Out of range validation of numeric values. + for (const std::string &opt_key : cfg.keys()) + { + const ConfigOption *opt = cfg.optptr(opt_key); + assert(opt != nullptr); + const ConfigOptionDef *optdef = print_config_def.get(opt_key); + assert(optdef != nullptr); + bool out_of_range = false; + switch (opt->type()) + { + case coFloat: + case coPercent: + case coFloatOrPercent: + { + auto *fopt = static_cast(opt); + out_of_range = fopt->value < optdef->min || fopt->value > optdef->max; + break; + } + case coFloats: + case coPercents: + for (double v : static_cast *>(opt)->values) + if (v < optdef->min || v > optdef->max) + { + out_of_range = true; + break; + } + break; + case coInt: + { + auto *iopt = static_cast(opt); + out_of_range = iopt->value < optdef->min || iopt->value > optdef->max; + break; + } + case coInts: + for (int v : static_cast *>(opt)->values) + if (v < optdef->min || v > optdef->max) + { + out_of_range = true; + break; + } + break; + default:; + } + if (out_of_range) + { + if (error_message.find(opt_key) == error_message.end()) + error_message.emplace(opt_key, opt->serialize() + L(" not in range ") + "[" + std::to_string(optdef->min) + "," + std::to_string(optdef->max) + "]"); + // return std::string("Value out of range: " + opt_key); + } } - } - // The configuration is valid. - return error_message; -} + // The configuration is valid. + return error_message; + } // Declare and initialize static caches of StaticPrintConfig derived classes. #define PRINT_CONFIG_CACHE_ELEMENT_DEFINITION(r, data, CLASS_NAME) StaticPrintConfig::StaticCache BOOST_PP_CAT(CLASS_NAME::s_cache_, CLASS_NAME); #define PRINT_CONFIG_CACHE_ELEMENT_INITIALIZATION(r, data, CLASS_NAME) Slic3r::CLASS_NAME::initialize_cache(); -#define PRINT_CONFIG_CACHE_INITIALIZE(CLASSES_SEQ) \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_DEFINITION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \ - int print_config_static_initializer() { \ - /* Putting a trace here to avoid the compiler to optimize out this function. */ \ - BOOST_LOG_TRIVIAL(trace) << "Initializing StaticPrintConfigs"; \ +#define PRINT_CONFIG_CACHE_INITIALIZE(CLASSES_SEQ) \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_DEFINITION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \ + int print_config_static_initializer() \ + { \ + /* Putting a trace here to avoid the compiler to optimize out this function. */ \ + BOOST_LOG_TRIVIAL(trace) << "Initializing StaticPrintConfigs"; \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_INITIALIZATION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \ - return 1; \ + return 1; \ } -PRINT_CONFIG_CACHE_INITIALIZE(( - PrintObjectConfig, PrintRegionConfig, MachineEnvelopeConfig, GCodeConfig, PrintConfig, FullPrintConfig, - SLAMaterialConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAPrinterConfig, SLAFullPrintConfig)) -static int print_config_static_initialized = print_config_static_initializer(); + PRINT_CONFIG_CACHE_INITIALIZE(( + PrintObjectConfig, PrintRegionConfig, MachineEnvelopeConfig, GCodeConfig, PrintConfig, FullPrintConfig, + SLAMaterialConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAPrinterConfig, SLAFullPrintConfig)) + static int print_config_static_initialized = print_config_static_initializer(); -//BBS: remove unused command currently -CLIActionsConfigDef::CLIActionsConfigDef() -{ - ConfigOptionDef* def; - - // Actions: - /*def = this->add("export_obj", coBool); - def->label = L("Export OBJ"); - def->tooltip = L("Export the model(s) as OBJ."); - def->set_default_value(new ConfigOptionBool(false));*/ - -/* - def = this->add("export_svg", coBool); - def->label = L("Export SVG"); - def->tooltip = L("Slice the model and export solid slices as SVG."); - def->set_default_value(new ConfigOptionBool(false)); -*/ - - /*def = this->add("export_sla", coBool); - def->label = L("Export SLA"); - def->tooltip = L("Slice the model and export SLA printing layers as PNG."); - def->cli = "export-sla|sla"; - def->set_default_value(new ConfigOptionBool(false));*/ - - def = this->add("export_3mf", coString); - def->label = "Export 3MF"; - def->tooltip = "Export project as 3MF."; - def->cli_params = "filename.3mf"; - def->set_default_value(new ConfigOptionString("output.3mf")); - - def = this->add("export_slicedata", coString); - def->label = "Export slicing data"; - def->tooltip = "Export slicing data to a folder."; - def->cli_params = "slicing_data_directory"; - def->set_default_value(new ConfigOptionString("cached_data")); - - def = this->add("load_slicedata", coStrings); - def->label = "Load slicing data"; - def->tooltip = "Load cached slicing data from directory"; - def->cli_params = "slicing_data_directory"; - def->set_default_value(new ConfigOptionString("cached_data")); - - /*def = this->add("export_amf", coBool); - def->label = L("Export AMF"); - def->tooltip = L("Export the model(s) as AMF."); - def->set_default_value(new ConfigOptionBool(false));*/ - - def = this->add("export_stl", coBool); - def->label = "Export STL"; - def->tooltip = "Export the objects as single STL."; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("export_stls", coString); - def->label = "Export multiple stls"; - def->tooltip = "Export the objects as multiple stls to directory"; - def->set_default_value(new ConfigOptionString("stl_path")); - - /*def = this->add("export_gcode", coBool); - def->label = L("Export G-code"); - def->tooltip = L("Slice the model and export toolpaths as G-code."); - def->cli = "export-gcode|gcode|g"; - def->set_default_value(new ConfigOptionBool(false));*/ - - /*def = this->add("gcodeviewer", coBool); - // BBS: remove _L() - def->label = ("G-code viewer"); - def->tooltip = ("Visualize an already sliced and saved G-code"); - def->cli = "gcodeviewer"; - def->set_default_value(new ConfigOptionBool(false));*/ - - def = this->add("slice", coInt); - def->label = "Slice"; - def->tooltip = "Slice the plates: 0-all plates, i-plate i, others-invalid"; - def->cli = "slice"; - def->cli_params = "option"; - def->set_default_value(new ConfigOptionInt(0)); - - def = this->add("export_png", coInt); - def->label = "Export png of plate"; - def->tooltip = "Export png of plate: 0-all plates, i-plate i, others-invalid"; - def->cli = "export-png"; - def->cli_params = "option"; - def->set_default_value(new ConfigOptionInt(-1)); - - def = this->add("help", coBool); - def->label = "Help"; - def->tooltip = "Show command help."; - def->cli = "help|h"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("uptodate", coBool); - def->label = "UpToDate"; - def->tooltip = "Update the configs values of 3mf to latest."; - def->cli = "uptodate"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("load_defaultfila", coBool); - def->label = "Load default filaments"; - def->tooltip = "Load first filament as default for those not loaded"; - def->cli_params = "option"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("min_save", coBool); - def->label = "Minimum save"; - def->tooltip = "export 3mf with minimum size."; - def->cli_params = "option"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("mtcpp", coInt); - def->label = "mtcpp"; - def->tooltip = "max triangle count per plate for slicing."; - def->cli = "mtcpp"; - def->cli_params = "count"; - def->set_default_value(new ConfigOptionInt(1000000)); - - def = this->add("mstpp", coInt); - def->label = "mstpp"; - def->tooltip = "max slicing time per plate in seconds."; - def->cli = "mstpp"; - def->cli_params = "time"; - def->set_default_value(new ConfigOptionInt(300)); - - // must define new params here, otherwise comamnd param check will fail - def = this->add("no_check", coBool); - def->label = L("No check"); - def->tooltip = L("Do not run any validity checks, such as gcode path conflicts check."); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("normative_check", coBool); - def->label = "Normative check"; - def->tooltip = "Check the normative items."; - def->cli_params = "option"; - def->set_default_value(new ConfigOptionBool(true)); - - /*def = this->add("help_fff", coBool); - def->label = L("Help (FFF options)"); - def->tooltip = L("Show the full list of print/G-code configuration options."); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("help_sla", coBool); - def->label = L("Help (SLA options)"); - def->tooltip = L("Show the full list of SLA print configuration options."); - def->set_default_value(new ConfigOptionBool(false));*/ - - def = this->add("info", coBool); - def->label = "Output Model Info"; - def->tooltip = "Output the model's information."; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("export_settings", coString); - def->label = "Export Settings"; - def->tooltip = "Export settings to a file."; - def->cli_params = "settings.json"; - def->set_default_value(new ConfigOptionString("output.json")); - - def = this->add("pipe", coString); - def->label = "Send progress to pipe"; - def->tooltip = "Send progress to pipe."; - def->cli_params = "pipename"; - def->set_default_value(new ConfigOptionString("")); -} + // BBS: remove unused command currently + CLIActionsConfigDef::CLIActionsConfigDef() + { + ConfigOptionDef *def; + + // Actions: + /*def = this->add("export_obj", coBool); + def->label = L("Export OBJ"); + def->tooltip = L("Export the model(s) as OBJ."); + def->set_default_value(new ConfigOptionBool(false));*/ + + /* + def = this->add("export_svg", coBool); + def->label = L("Export SVG"); + def->tooltip = L("Slice the model and export solid slices as SVG."); + def->set_default_value(new ConfigOptionBool(false)); + */ -//BBS: remove unused command currently -CLITransformConfigDef::CLITransformConfigDef() -{ - ConfigOptionDef* def; - - // Transform options: - /*def = this->add("align_xy", coPoint); - def->label = L("Align XY"); - def->tooltip = L("Align the model to the given point."); - def->set_default_value(new ConfigOptionPoint(Vec2d(100,100))); - - def = this->add("cut", coFloat); - def->label = L("Cut"); - def->tooltip = L("Cut model at the given Z."); - def->set_default_value(new ConfigOptionFloat(0));*/ - -/* - def = this->add("cut_grid", coFloat); - def->label = L("Cut"); - def->tooltip = L("Cut model in the XY plane into tiles of the specified max size."); - def->set_default_value(new ConfigOptionPoint()); - - def = this->add("cut_x", coFloat); - def->label = L("Cut"); - def->tooltip = L("Cut model at the given X."); - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("cut_y", coFloat); - def->label = L("Cut"); - def->tooltip = L("Cut model at the given Y."); - def->set_default_value(new ConfigOptionFloat(0)); -*/ - - /*def = this->add("center", coPoint); - def->label = L("Center"); - def->tooltip = L("Center the print around the given center."); - def->set_default_value(new ConfigOptionPoint(Vec2d(100,100)));*/ - - def = this->add("arrange", coInt); - def->label = "Arrange Options"; - def->tooltip = "Arrange options: 0-disable, 1-enable, others-auto"; - def->cli_params = "option"; - //def->cli = "arrange|a"; - def->set_default_value(new ConfigOptionInt(0)); - - def = this->add("repetitions", coInt); - def->label = "Repetition count"; - def->tooltip = "Repetition count of the whole model"; - def->cli_params = "count"; - def->set_default_value(new ConfigOptionInt(1)); - - def = this->add("ensure_on_bed", coBool); - def->label = "Ensure on bed"; - def->tooltip = "Lift the object above the bed when it is partially below. Disabled by default"; - def->set_default_value(new ConfigOptionBool(false)); - - /*def = this->add("copy", coInt); - def->label = L("Copy"); - def->tooltip =L("Duplicate copies of model"); - def->min = 1; - def->set_default_value(new ConfigOptionInt(1));*/ - - /*def = this->add("duplicate_grid", coPoint); - def->label = L("Duplicate by grid"); - def->tooltip = L("Multiply copies by creating a grid.");*/ - - def = this->add("assemble", coBool); - def->label = "Assemble"; - def->tooltip = "Arrange the supplied models in a plate and merge them in a single model in order to perform actions once."; - //def->cli = "merge|m"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("convert_unit", coBool); - def->label = "Convert Unit"; - def->tooltip = "Convert the units of model"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("orient", coInt); - def->label = "Orient Options"; - def->tooltip = "Orient options: 0-disable, 1-enable, others-auto"; - //def->cli = "orient|o"; - def->set_default_value(new ConfigOptionInt(0)); - - /*def = this->add("repair", coBool); - def->label = L("Repair"); - def->tooltip = L("Repair the model's meshes if it is non-manifold mesh"); - def->set_default_value(new ConfigOptionBool(false));*/ - - def = this->add("rotate", coFloat); - def->label = "Rotate"; - def->tooltip = "Rotation angle around the Z axis in degrees."; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("rotate_x", coFloat); - def->label = "Rotate around X"; - def->tooltip = "Rotation angle around the X axis in degrees."; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("rotate_y", coFloat); - def->label = "Rotate around Y"; - def->tooltip = "Rotation angle around the Y axis in degrees."; - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("scale", coFloat); - def->label = "Scale"; - def->tooltip = "Scale the model by a float factor"; - def->cli_params = "factor"; - def->set_default_value(new ConfigOptionFloat(1.f)); - - /*def = this->add("split", coBool); - def->label = L("Split"); - def->tooltip = L("Detect unconnected parts in the given model(s) and split them into separate objects."); - - def = this->add("scale_to_fit", coPoint3); - def->label = L("Scale to Fit"); - def->tooltip = L("Scale to fit the given volume."); - def->set_default_value(new ConfigOptionPoint3(Vec3d(0,0,0)));*/ -} + /*def = this->add("export_sla", coBool); + def->label = L("Export SLA"); + def->tooltip = L("Slice the model and export SLA printing layers as PNG."); + def->cli = "export-sla|sla"; + def->set_default_value(new ConfigOptionBool(false));*/ + + def = this->add("export_3mf", coString); + def->label = "Export 3MF"; + def->tooltip = "Export project as 3MF."; + def->cli_params = "filename.3mf"; + def->set_default_value(new ConfigOptionString("output.3mf")); + + def = this->add("export_slicedata", coString); + def->label = "Export slicing data"; + def->tooltip = "Export slicing data to a folder."; + def->cli_params = "slicing_data_directory"; + def->set_default_value(new ConfigOptionString("cached_data")); + + def = this->add("load_slicedata", coStrings); + def->label = "Load slicing data"; + def->tooltip = "Load cached slicing data from directory"; + def->cli_params = "slicing_data_directory"; + def->set_default_value(new ConfigOptionString("cached_data")); + + /*def = this->add("export_amf", coBool); + def->label = L("Export AMF"); + def->tooltip = L("Export the model(s) as AMF."); + def->set_default_value(new ConfigOptionBool(false));*/ + + def = this->add("export_stl", coBool); + def->label = "Export STL"; + def->tooltip = "Export the objects as single STL."; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("export_stls", coString); + def->label = "Export multiple stls"; + def->tooltip = "Export the objects as multiple stls to directory"; + def->set_default_value(new ConfigOptionString("stl_path")); + + /*def = this->add("export_gcode", coBool); + def->label = L("Export G-code"); + def->tooltip = L("Slice the model and export toolpaths as G-code."); + def->cli = "export-gcode|gcode|g"; + def->set_default_value(new ConfigOptionBool(false));*/ + + /*def = this->add("gcodeviewer", coBool); + // BBS: remove _L() + def->label = ("G-code viewer"); + def->tooltip = ("Visualize an already sliced and saved G-code"); + def->cli = "gcodeviewer"; + def->set_default_value(new ConfigOptionBool(false));*/ + + def = this->add("slice", coInt); + def->label = "Slice"; + def->tooltip = "Slice the plates: 0-all plates, i-plate i, others-invalid"; + def->cli = "slice"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("export_png", coInt); + def->label = "Export png of plate"; + def->tooltip = "Export png of plate: 0-all plates, i-plate i, others-invalid"; + def->cli = "export-png"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionInt(-1)); + + def = this->add("help", coBool); + def->label = "Help"; + def->tooltip = "Show command help."; + def->cli = "help|h"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("uptodate", coBool); + def->label = "UpToDate"; + def->tooltip = "Update the configs values of 3mf to latest."; + def->cli = "uptodate"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("load_defaultfila", coBool); + def->label = "Load default filaments"; + def->tooltip = "Load first filament as default for those not loaded"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("min_save", coBool); + def->label = "Minimum save"; + def->tooltip = "export 3mf with minimum size."; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("mtcpp", coInt); + def->label = "mtcpp"; + def->tooltip = "max triangle count per plate for slicing."; + def->cli = "mtcpp"; + def->cli_params = "count"; + def->set_default_value(new ConfigOptionInt(1000000)); + + def = this->add("mstpp", coInt); + def->label = "mstpp"; + def->tooltip = "max slicing time per plate in seconds."; + def->cli = "mstpp"; + def->cli_params = "time"; + def->set_default_value(new ConfigOptionInt(300)); + + // must define new params here, otherwise comamnd param check will fail + def = this->add("no_check", coBool); + def->label = L("No check"); + def->tooltip = L("Do not run any validity checks, such as gcode path conflicts check."); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("normative_check", coBool); + def->label = "Normative check"; + def->tooltip = "Check the normative items."; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionBool(true)); + + /*def = this->add("help_fff", coBool); + def->label = L("Help (FFF options)"); + def->tooltip = L("Show the full list of print/G-code configuration options."); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("help_sla", coBool); + def->label = L("Help (SLA options)"); + def->tooltip = L("Show the full list of SLA print configuration options."); + def->set_default_value(new ConfigOptionBool(false));*/ + + def = this->add("info", coBool); + def->label = "Output Model Info"; + def->tooltip = "Output the model's information."; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("export_settings", coString); + def->label = "Export Settings"; + def->tooltip = "Export settings to a file."; + def->cli_params = "settings.json"; + def->set_default_value(new ConfigOptionString("output.json")); + + def = this->add("pipe", coString); + def->label = "Send progress to pipe"; + def->tooltip = "Send progress to pipe."; + def->cli_params = "pipename"; + def->set_default_value(new ConfigOptionString("")); + } -CLIMiscConfigDef::CLIMiscConfigDef() -{ - ConfigOptionDef* def; - - /*def = this->add("ignore_nonexistent_config", coBool); - def->label = L("Ignore non-existent config files"); - def->tooltip = L("Do not fail if a file supplied to --load does not exist."); - - def = this->add("config_compatibility", coEnum); - def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF)."); - def->tooltip = L("This version of BambuStudio may not understand configurations produced by the newest BambuStudio versions. " - "For example, newer BambuStudio may extend the list of supported firmware flavors. One may decide to " - "bail out or to substitute an unknown value with a default silently or verbosely."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("disable"); - def->enum_values.push_back("enable"); - def->enum_values.push_back("enable_silent"); - def->enum_labels.push_back(L("Bail out on unknown configuration values")); - def->enum_labels.push_back(L("Enable reading unknown configuration values by verbosely substituting them with defaults.")); - def->enum_labels.push_back(L("Enable reading unknown configuration values by silently substituting them with defaults.")); - def->set_default_value(new ConfigOptionEnum(ForwardCompatibilitySubstitutionRule::Enable));*/ - - /*def = this->add("load", coStrings); - def->label = L("Load config file"); - def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files.");*/ - - def = this->add("load_settings", coStrings); - def->label = "Load General Settings"; - def->tooltip = "Load process/machine settings from the specified file"; - def->cli_params = "\"setting1.json;setting2.json\""; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("load_filaments", coStrings); - def->label = "Load Filament Settings"; - def->tooltip = "Load filament settings from the specified file list"; - def->cli_params = "\"filament1.json;filament2.json;...\""; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("skip_objects", coInts); - def->label = "Skip Objects"; - def->tooltip = "Skip some objects in this print"; - def->cli_params = "\"3,5,10,77\""; - def->set_default_value(new ConfigOptionInts()); - - def = this->add("clone_objects", coInts); - def->label = "Clone Objects"; - def->tooltip = "Clone objects in the load list"; - def->cli_params = "\"1,3,1,10\""; - def->set_default_value(new ConfigOptionInts()); - - def = this->add("uptodate_settings", coStrings); - def->label = "load uptodate process/machine settings when using uptodate"; - def->tooltip = "load uptodate process/machine settings from the specified file when using uptodate"; - def->cli_params = "\"setting1.json;setting2.json\""; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("uptodate_filaments", coStrings); - def->label = "load uptodate filament settings when using uptodate"; - def->tooltip = "load uptodate filament settings from the specified file when using uptodate"; - def->cli_params = "\"filament1.json;filament2.json;...\""; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("downward_check", coBool); - def->label = "downward machines check"; - def->tooltip = "if enabled, check whether current machine downward compatible with the machines in the list"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("downward_settings", coStrings); - def->label = "downward machines settings"; - def->tooltip = "the machine settings list need to do downward checking"; - def->cli_params = "\"machine1.json;machine2.json;...\""; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("load_assemble_list", coString); - def->label = "Load assemble list"; - def->tooltip = "Load assemble object list from config file"; - def->cli_params = "assemble_list.json"; - def->set_default_value(new ConfigOptionString()); - - /*def = this->add("output", coString); - def->label = L("Output File"); - def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); - def->cli = "output|o"; - - def = this->add("single_instance", coBool); - def->label = L("Single instance mode"); - def->tooltip = L("If enabled, the command line arguments are sent to an existing instance of GUI BambuStudio, " - "or an existing BambuStudio window is activated. " - "Overrides the \"single_instance\" configuration value from application preferences.");*/ - -/* - def = this->add("autosave", coString); - def->label = L("Autosave"); - def->tooltip = L("Automatically export current configuration to the specified file."); -*/ - - def = this->add("outputdir", coString); - def->label = "Output directory"; - def->tooltip = "Output directory for the exported files."; - def->cli_params = "dir"; - def->set_default_value(new ConfigOptionString()); - - def = this->add("debug", coInt); - def->label = "Debug level"; - def->tooltip = "Sets debug logging level. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\n"; - def->min = 0; - def->cli_params = "level"; - def->set_default_value(new ConfigOptionInt(1)); - - def = this->add("enable_timelapse", coBool); - def->label = "Enable timeplapse for print"; - def->tooltip = "If enabled, this slicing will be considered using timelapse"; - def->set_default_value(new ConfigOptionBool(false)); + // BBS: remove unused command currently + CLITransformConfigDef::CLITransformConfigDef() + { + ConfigOptionDef *def; + + // Transform options: + /*def = this->add("align_xy", coPoint); + def->label = L("Align XY"); + def->tooltip = L("Align the model to the given point."); + def->set_default_value(new ConfigOptionPoint(Vec2d(100,100))); + + def = this->add("cut", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given Z."); + def->set_default_value(new ConfigOptionFloat(0));*/ + + /* + def = this->add("cut_grid", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model in the XY plane into tiles of the specified max size."); + def->set_default_value(new ConfigOptionPoint()); + + def = this->add("cut_x", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given X."); + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("cut_y", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given Y."); + def->set_default_value(new ConfigOptionFloat(0)); + */ + + /*def = this->add("center", coPoint); + def->label = L("Center"); + def->tooltip = L("Center the print around the given center."); + def->set_default_value(new ConfigOptionPoint(Vec2d(100,100)));*/ + + def = this->add("arrange", coInt); + def->label = "Arrange Options"; + def->tooltip = "Arrange options: 0-disable, 1-enable, others-auto"; + def->cli_params = "option"; + // def->cli = "arrange|a"; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("repetitions", coInt); + def->label = "Repetition count"; + def->tooltip = "Repetition count of the whole model"; + def->cli_params = "count"; + def->set_default_value(new ConfigOptionInt(1)); + + def = this->add("ensure_on_bed", coBool); + def->label = "Ensure on bed"; + def->tooltip = "Lift the object above the bed when it is partially below. Disabled by default"; + def->set_default_value(new ConfigOptionBool(false)); + + /*def = this->add("copy", coInt); + def->label = L("Copy"); + def->tooltip =L("Duplicate copies of model"); + def->min = 1; + def->set_default_value(new ConfigOptionInt(1));*/ + + /*def = this->add("duplicate_grid", coPoint); + def->label = L("Duplicate by grid"); + def->tooltip = L("Multiply copies by creating a grid.");*/ + + def = this->add("assemble", coBool); + def->label = "Assemble"; + def->tooltip = "Arrange the supplied models in a plate and merge them in a single model in order to perform actions once."; + // def->cli = "merge|m"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("convert_unit", coBool); + def->label = "Convert Unit"; + def->tooltip = "Convert the units of model"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("orient", coInt); + def->label = "Orient Options"; + def->tooltip = "Orient options: 0-disable, 1-enable, others-auto"; + // def->cli = "orient|o"; + def->set_default_value(new ConfigOptionInt(0)); + + /*def = this->add("repair", coBool); + def->label = L("Repair"); + def->tooltip = L("Repair the model's meshes if it is non-manifold mesh"); + def->set_default_value(new ConfigOptionBool(false));*/ + + def = this->add("rotate", coFloat); + def->label = "Rotate"; + def->tooltip = "Rotation angle around the Z axis in degrees."; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("rotate_x", coFloat); + def->label = "Rotate around X"; + def->tooltip = "Rotation angle around the X axis in degrees."; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("rotate_y", coFloat); + def->label = "Rotate around Y"; + def->tooltip = "Rotation angle around the Y axis in degrees."; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("scale", coFloat); + def->label = "Scale"; + def->tooltip = "Scale the model by a float factor"; + def->cli_params = "factor"; + def->set_default_value(new ConfigOptionFloat(1.f)); + + /*def = this->add("split", coBool); + def->label = L("Split"); + def->tooltip = L("Detect unconnected parts in the given model(s) and split them into separate objects."); + + def = this->add("scale_to_fit", coPoint3); + def->label = L("Scale to Fit"); + def->tooltip = L("Scale to fit the given volume."); + def->set_default_value(new ConfigOptionPoint3(Vec3d(0,0,0)));*/ + } + + CLIMiscConfigDef::CLIMiscConfigDef() + { + ConfigOptionDef *def; + + /*def = this->add("ignore_nonexistent_config", coBool); + def->label = L("Ignore non-existent config files"); + def->tooltip = L("Do not fail if a file supplied to --load does not exist."); + + def = this->add("config_compatibility", coEnum); + def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF)."); + def->tooltip = L("This version of BambuStudio may not understand configurations produced by the newest BambuStudio versions. " + "For example, newer BambuStudio may extend the list of supported firmware flavors. One may decide to " + "bail out or to substitute an unknown value with a default silently or verbosely."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("disable"); + def->enum_values.push_back("enable"); + def->enum_values.push_back("enable_silent"); + def->enum_labels.push_back(L("Bail out on unknown configuration values")); + def->enum_labels.push_back(L("Enable reading unknown configuration values by verbosely substituting them with defaults.")); + def->enum_labels.push_back(L("Enable reading unknown configuration values by silently substituting them with defaults.")); + def->set_default_value(new ConfigOptionEnum(ForwardCompatibilitySubstitutionRule::Enable));*/ + + /*def = this->add("load", coStrings); + def->label = L("Load config file"); + def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files.");*/ + + def = this->add("load_settings", coStrings); + def->label = "Load General Settings"; + def->tooltip = "Load process/machine settings from the specified file"; + def->cli_params = "\"setting1.json;setting2.json\""; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("load_filaments", coStrings); + def->label = "Load Filament Settings"; + def->tooltip = "Load filament settings from the specified file list"; + def->cli_params = "\"filament1.json;filament2.json;...\""; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("skip_objects", coInts); + def->label = "Skip Objects"; + def->tooltip = "Skip some objects in this print"; + def->cli_params = "\"3,5,10,77\""; + def->set_default_value(new ConfigOptionInts()); + + def = this->add("clone_objects", coInts); + def->label = "Clone Objects"; + def->tooltip = "Clone objects in the load list"; + def->cli_params = "\"1,3,1,10\""; + def->set_default_value(new ConfigOptionInts()); + + def = this->add("uptodate_settings", coStrings); + def->label = "load uptodate process/machine settings when using uptodate"; + def->tooltip = "load uptodate process/machine settings from the specified file when using uptodate"; + def->cli_params = "\"setting1.json;setting2.json\""; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("uptodate_filaments", coStrings); + def->label = "load uptodate filament settings when using uptodate"; + def->tooltip = "load uptodate filament settings from the specified file when using uptodate"; + def->cli_params = "\"filament1.json;filament2.json;...\""; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("downward_check", coBool); + def->label = "downward machines check"; + def->tooltip = "if enabled, check whether current machine downward compatible with the machines in the list"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("downward_settings", coStrings); + def->label = "downward machines settings"; + def->tooltip = "the machine settings list need to do downward checking"; + def->cli_params = "\"machine1.json;machine2.json;...\""; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("load_assemble_list", coString); + def->label = "Load assemble list"; + def->tooltip = "Load assemble object list from config file"; + def->cli_params = "assemble_list.json"; + def->set_default_value(new ConfigOptionString()); + + /*def = this->add("output", coString); + def->label = L("Output File"); + def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); + def->cli = "output|o"; + + def = this->add("single_instance", coBool); + def->label = L("Single instance mode"); + def->tooltip = L("If enabled, the command line arguments are sent to an existing instance of GUI BambuStudio, " + "or an existing BambuStudio window is activated. " + "Overrides the \"single_instance\" configuration value from application preferences.");*/ + + /* + def = this->add("autosave", coString); + def->label = L("Autosave"); + def->tooltip = L("Automatically export current configuration to the specified file."); + */ + + def = this->add("outputdir", coString); + def->label = "Output directory"; + def->tooltip = "Output directory for the exported files."; + def->cli_params = "dir"; + def->set_default_value(new ConfigOptionString()); + + def = this->add("debug", coInt); + def->label = "Debug level"; + def->tooltip = "Sets debug logging level. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\n"; + def->min = 0; + def->cli_params = "level"; + def->set_default_value(new ConfigOptionInt(1)); + + def = this->add("enable_timelapse", coBool); + def->label = "Enable timeplapse for print"; + def->tooltip = "If enabled, this slicing will be considered using timelapse"; + def->set_default_value(new ConfigOptionBool(false)); #if (defined(_MSC_VER) || defined(__MINGW32__)) && defined(SLIC3R_GUI) - /*def = this->add("sw_renderer", coBool); - def->label = L("Render with a software renderer"); - def->tooltip = L("Render with a software renderer. The bundled MESA software renderer is loaded instead of the default OpenGL driver."); - def->min = 0;*/ + /*def = this->add("sw_renderer", coBool); + def->label = L("Render with a software renderer"); + def->tooltip = L("Render with a software renderer. The bundled MESA software renderer is loaded instead of the default OpenGL driver."); + def->min = 0;*/ #endif /* _MSC_VER */ - def = this->add("load_custom_gcodes", coString); - def->label = "Load custom gcode"; - def->tooltip = "Load custom gcode from json"; - def->cli_params = "custom_gcode_toolchange.json"; - def->set_default_value(new ConfigOptionString()); - - def = this->add("load_filament_ids", coInts); - def->label = "Load filament ids"; - def->tooltip = "Load filament ids for each object"; - def->cli_params = "\"1,2,3,1\""; - def->set_default_value(new ConfigOptionInts()); - - def = this->add("allow_multicolor_oneplate", coBool); - def->label = "Allow multiple color on one plate"; - def->tooltip = "If enabled, the arrange will allow multiple color on one plate"; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("allow_rotations", coBool); - def->label = "Allow rotatations when arrange"; - def->tooltip = "If enabled, the arrange will allow rotations when place object"; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("avoid_extrusion_cali_region", coBool); - def->label = "Avoid extrusion calibrate region when doing arrange"; - def->tooltip = "If enabled, the arrange will avoid extrusion calibrate region when place object"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("skip_modified_gcodes", coBool); - def->label = "Skip modified gcodes in 3mf"; - def->tooltip = "Skip the modified gcodes in 3mf from Printer or filament Presets"; - def->cli_params = "option"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("skip_useless_pick", coBool); - def->label = "Skip generating useless pick/top images into 3mf"; - def->tooltip = "Skip generating useless pick/top images into 3mf"; - def->cli_params = "option"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("makerlab_name", coString); - def->label = "MakerLab name"; - def->tooltip = "MakerLab name to generate this 3mf"; - def->cli_params = "name"; - def->set_default_value(new ConfigOptionString()); - - def = this->add("makerlab_version", coString); - def->label = "MakerLab version"; - def->tooltip = "MakerLab version to generate this 3mf"; - def->cli_params = "version"; - def->set_default_value(new ConfigOptionString()); - - def = this->add("metadata_name", coStrings); - def->label = "metadata name list"; - def->tooltip = "matadata name list added into 3mf"; - def->cli_params = "\"name1;name2;...\""; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("metadata_value", coStrings); - def->label = "metadata value list"; - def->tooltip = "matadata value list added into 3mf"; - def->cli_params = "\"value1;value2;...\""; - def->set_default_value(new ConfigOptionStrings()); - - def = this->add("allow_newer_file", coBool); - def->label = "Allow 3mf with newer version to be sliced"; - def->tooltip = "Allow 3mf with newer version to be sliced"; - def->cli_params = "option"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("allow_mix_temp", coBool); - def->label = "Allow filaments with high/low temperature to be printed together"; - def->tooltip = "Allow filaments with high/low temperature to be printed together"; - def->cli_params = "option"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("camera_view", coInt); - def->label = "Camera view angle for exporting png"; - def->tooltip = "Camera view angle for exporting png: 0-Iso, 1-Top_Front, 2-Left, 3-Right, 10-Iso_1, 11-Iso_2, 12-Iso_3"; - def->cli_params = "angle"; - def->set_default_value(new ConfigOptionInt(0)); -} + def = this->add("load_custom_gcodes", coString); + def->label = "Load custom gcode"; + def->tooltip = "Load custom gcode from json"; + def->cli_params = "custom_gcode_toolchange.json"; + def->set_default_value(new ConfigOptionString()); -const CLIActionsConfigDef cli_actions_config_def; -const CLITransformConfigDef cli_transform_config_def; -const CLIMiscConfigDef cli_misc_config_def; + def = this->add("load_filament_ids", coInts); + def->label = "Load filament ids"; + def->tooltip = "Load filament ids for each object"; + def->cli_params = "\"1,2,3,1\""; + def->set_default_value(new ConfigOptionInts()); + + def = this->add("allow_multicolor_oneplate", coBool); + def->label = "Allow multiple color on one plate"; + def->tooltip = "If enabled, the arrange will allow multiple color on one plate"; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("allow_rotations", coBool); + def->label = "Allow rotatations when arrange"; + def->tooltip = "If enabled, the arrange will allow rotations when place object"; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("avoid_extrusion_cali_region", coBool); + def->label = "Avoid extrusion calibrate region when doing arrange"; + def->tooltip = "If enabled, the arrange will avoid extrusion calibrate region when place object"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("skip_modified_gcodes", coBool); + def->label = "Skip modified gcodes in 3mf"; + def->tooltip = "Skip the modified gcodes in 3mf from Printer or filament Presets"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("skip_useless_pick", coBool); + def->label = "Skip generating useless pick/top images into 3mf"; + def->tooltip = "Skip generating useless pick/top images into 3mf"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("makerlab_name", coString); + def->label = "MakerLab name"; + def->tooltip = "MakerLab name to generate this 3mf"; + def->cli_params = "name"; + def->set_default_value(new ConfigOptionString()); -DynamicPrintAndCLIConfig::PrintAndCLIConfigDef DynamicPrintAndCLIConfig::s_def; + def = this->add("makerlab_version", coString); + def->label = "MakerLab version"; + def->tooltip = "MakerLab version to generate this 3mf"; + def->cli_params = "version"; + def->set_default_value(new ConfigOptionString()); -void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::string &value) const -{ - if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && - cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && - cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { - PrintConfigDef::handle_legacy(opt_key, value); + def = this->add("metadata_name", coStrings); + def->label = "metadata name list"; + def->tooltip = "matadata name list added into 3mf"; + def->cli_params = "\"name1;name2;...\""; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("metadata_value", coStrings); + def->label = "metadata value list"; + def->tooltip = "matadata value list added into 3mf"; + def->cli_params = "\"value1;value2;...\""; + def->set_default_value(new ConfigOptionStrings()); + + def = this->add("allow_newer_file", coBool); + def->label = "Allow 3mf with newer version to be sliced"; + def->tooltip = "Allow 3mf with newer version to be sliced"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("allow_mix_temp", coBool); + def->label = "Allow filaments with high/low temperature to be printed together"; + def->tooltip = "Allow filaments with high/low temperature to be printed together"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("camera_view", coInt); + def->label = "Camera view angle for exporting png"; + def->tooltip = "Camera view angle for exporting png: 0-Iso, 1-Top_Front, 2-Left, 3-Right, 10-Iso_1, 11-Iso_2, 12-Iso_3"; + def->cli_params = "angle"; + def->set_default_value(new ConfigOptionInt(0)); } -} -uint64_t ModelConfig::s_last_timestamp = 1; + const CLIActionsConfigDef cli_actions_config_def; + const CLITransformConfigDef cli_transform_config_def; + const CLIMiscConfigDef cli_misc_config_def; -static Points to_points(const std::vector &dpts) -{ - Points pts; pts.reserve(dpts.size()); - for (auto &v : dpts) - pts.emplace_back( coord_t(scale_(v.x())), coord_t(scale_(v.y())) ); - return pts; -} + DynamicPrintAndCLIConfig::PrintAndCLIConfigDef DynamicPrintAndCLIConfig::s_def; -Polygon get_shared_poly(const std::vector& extruder_polys) -{ - Polygon result; - for (int index = 0; index < extruder_polys.size(); index++) + void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::string &value) const { - const Pointfs& extruder_area = extruder_polys[index]; - if (index == 0) - result.points = to_points(extruder_area); - else { - Polygon extruer_poly; - extruer_poly.points = to_points(extruder_area); - Polygons result_polygon = intersection(extruer_poly, result); - result = result_polygon[0]; + if (cli_actions_config_def.options.find(opt_key) == cli_actions_config_def.options.end() && + cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && + cli_misc_config_def.options.find(opt_key) == cli_misc_config_def.options.end()) + { + PrintConfigDef::handle_legacy(opt_key, value); } } - return result; -} -Points get_bed_shape(const DynamicPrintConfig &config, bool use_share) -{ - const ConfigOptionPoints *bed_shape_opt = config.opt("printable_area"); - if (!bed_shape_opt) { - // Here, it is certain that the bed shape is missing, so an infinite one - // has to be used, but still, the center of bed can be queried - if (auto center_opt = config.opt("center")) - return { scaled(center_opt->value) }; + uint64_t ModelConfig::s_last_timestamp = 1; + + static Points to_points(const std::vector &dpts) + { + Points pts; + pts.reserve(dpts.size()); + for (auto &v : dpts) + pts.emplace_back(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); + return pts; + } - return {}; + Polygon get_shared_poly(const std::vector &extruder_polys) + { + Polygon result; + for (int index = 0; index < extruder_polys.size(); index++) + { + const Pointfs &extruder_area = extruder_polys[index]; + if (index == 0) + result.points = to_points(extruder_area); + else + { + Polygon extruer_poly; + extruer_poly.points = to_points(extruder_area); + Polygons result_polygon = intersection(extruer_poly, result); + result = result_polygon[0]; + } + } + return result; } + Points get_bed_shape(const DynamicPrintConfig &config, bool use_share) + { + const ConfigOptionPoints *bed_shape_opt = config.opt("printable_area"); + if (!bed_shape_opt) + { + + // Here, it is certain that the bed shape is missing, so an infinite one + // has to be used, but still, the center of bed can be queried + if (auto center_opt = config.opt("center")) + return {scaled(center_opt->value)}; + + return {}; + } - Polygon bed_poly; - if (use_share) { - const ConfigOptionPointsGroups *extruder_area_opt = config.opt("extruder_printable_area"); - if (extruder_area_opt && (extruder_area_opt->size() > 0)) { - const std::vector& extruder_areas = extruder_area_opt->values; - bed_poly = get_shared_poly(extruder_areas); + Polygon bed_poly; + if (use_share) + { + const ConfigOptionPointsGroups *extruder_area_opt = config.opt("extruder_printable_area"); + if (extruder_area_opt && (extruder_area_opt->size() > 0)) + { + const std::vector &extruder_areas = extruder_area_opt->values; + bed_poly = get_shared_poly(extruder_areas); + } + else + bed_poly.points = to_points(bed_shape_opt->values); } else bed_poly.points = to_points(bed_shape_opt->values); - } - else - bed_poly.points = to_points(bed_shape_opt->values); - return bed_poly.points; -} + return bed_poly.points; + } -Points get_bed_shape(const PrintConfig &cfg, bool use_share) -{ - Polygon bed_poly; - if (use_share) { - const std::vector& extruder_areas = cfg.extruder_printable_area.values; - if (extruder_areas.size() > 0) { - bed_poly = get_shared_poly(extruder_areas); + Points get_bed_shape(const PrintConfig &cfg, bool use_share) + { + Polygon bed_poly; + if (use_share) + { + const std::vector &extruder_areas = cfg.extruder_printable_area.values; + if (extruder_areas.size() > 0) + { + bed_poly = get_shared_poly(extruder_areas); + } + else + bed_poly.points = to_points(cfg.printable_area.values); } else bed_poly.points = to_points(cfg.printable_area.values); + + return bed_poly.points; } - else - bed_poly.points = to_points(cfg.printable_area.values); - return bed_poly.points; -} + Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.printable_area.values); } -Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.printable_area.values); } + Polygon get_bed_shape_with_excluded_area(const PrintConfig &cfg, bool use_share) + { + Polygon bed_poly; + bed_poly.points = get_bed_shape(cfg, use_share); -Polygon get_bed_shape_with_excluded_area(const PrintConfig& cfg, bool use_share) -{ - Polygon bed_poly; - bed_poly.points = get_bed_shape(cfg, use_share); - - Points excluse_area_points = to_points(cfg.bed_exclude_area.values); - Polygons exclude_polys; - Polygon exclude_poly; - for (int i = 0; i < excluse_area_points.size(); i++) { - auto pt = excluse_area_points[i]; - exclude_poly.points.emplace_back(pt); - if (i % 4 == 3) { // exclude areas are always rectangle - exclude_polys.push_back(exclude_poly); - exclude_poly.points.clear(); + Points excluse_area_points = to_points(cfg.bed_exclude_area.values); + Polygons exclude_polys; + Polygon exclude_poly; + for (int i = 0; i < excluse_area_points.size(); i++) + { + auto pt = excluse_area_points[i]; + exclude_poly.points.emplace_back(pt); + if (i % 4 == 3) + { // exclude areas are always rectangle + exclude_polys.push_back(exclude_poly); + exclude_poly.points.clear(); + } + } + if (cfg.enable_wrapping_detection.value) + { + Pointfs wrapping_detection_area = cfg.wrapping_exclude_area.values; + Polygon wrapping_poly; + for (size_t i = 0; i < wrapping_detection_area.size(); ++i) + { + auto pt = wrapping_detection_area[i]; + wrapping_poly.points.push_back(Point(scale_(pt.x()), scale_(pt.y()))); + } + exclude_polys.push_back(wrapping_poly); } + auto tmp = diff({bed_poly}, exclude_polys); + if (!tmp.empty()) + bed_poly = tmp[0]; + return bed_poly; } - if (cfg.enable_wrapping_detection.value) { - Pointfs wrapping_detection_area = cfg.wrapping_exclude_area.values; - Polygon wrapping_poly; - for (size_t i = 0; i < wrapping_detection_area.size(); ++i) { - auto pt = wrapping_detection_area[i]; - wrapping_poly.points.push_back(Point(scale_(pt.x()), scale_(pt.y()))); - } - exclude_polys.push_back(wrapping_poly); + bool has_skirt(const DynamicPrintConfig &cfg) + { + auto opt_skirt_height = cfg.option("skirt_height"); + auto opt_skirt_loops = cfg.option("skirt_loops"); + auto opt_draft_shield = cfg.option("draft_shield"); + return (opt_skirt_height && opt_skirt_height->getInt() > 0 && opt_skirt_loops && opt_skirt_loops->getInt() > 0) || (opt_draft_shield && opt_draft_shield->getInt() != dsDisabled); + } + float get_real_skirt_dist(const DynamicPrintConfig &cfg) + { + return has_skirt(cfg) ? cfg.opt_float("skirt_distance") : 0; } - auto tmp = diff({ bed_poly }, exclude_polys); - if (!tmp.empty()) bed_poly = tmp[0]; - return bed_poly; -} -bool has_skirt(const DynamicPrintConfig& cfg) -{ - auto opt_skirt_height = cfg.option("skirt_height"); - auto opt_skirt_loops = cfg.option("skirt_loops"); - auto opt_draft_shield = cfg.option("draft_shield"); - return (opt_skirt_height && opt_skirt_height->getInt() > 0 && opt_skirt_loops && opt_skirt_loops->getInt() > 0) - || (opt_draft_shield && opt_draft_shield->getInt() != dsDisabled); -} -float get_real_skirt_dist(const DynamicPrintConfig& cfg) { - return has_skirt(cfg) ? cfg.opt_float("skirt_distance") : 0; -} } // namespace Slic3r #include diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index e8c7b6cd00..ec3a1b12eb 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -30,722 +30,859 @@ // #define HAS_PRESSURE_EQUALIZER -namespace Slic3r { - -enum GCodeFlavor : unsigned char { - gcfMarlinLegacy, gcfKlipper, gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinFirmware, gcfSailfish, gcfMach3, gcfMachinekit, - gcfSmoothie, gcfNoExtrusion -}; - -enum class FuzzySkinType { - None, - External, - All, - AllWalls, - Disabled_fuzzy, -}; - -enum PrintHostType { - htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS -}; - -enum AuthorizationType { - atKeyPassword, atUserPassword -}; - -enum InfillPattern : int { - ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb, - ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricInternal, - ipLightning, ipCrossHatch, ipZigZag, ipCrossZag,ipFloatingConcentric, ipLockedZag, - ipCount, -}; - -enum EnsureVerticalThicknessLevel{ - evtDisabled, - evtPartial, - evtEnabled -}; - -enum class IroningType { - NoIroning, - TopSurfaces, - TopmostOnly, - AllSolid, - Count, -}; - -//BBS -enum class WallInfillOrder { - InnerOuterInfill, - OuterInnerInfill, - InfillInnerOuter, - InfillOuterInner, - InnerOuterInnerInfill, - Count, -}; - -enum class BedTempFormula { - btfFirstFilament, - btfHighestTemp, - count, -}; - -// BBS -enum class WallSequence { - InnerOuter, - OuterInner, - InnerOuterInner, - Count, -}; -//BBS -enum class PrintSequence { - ByLayer, - ByObject, - ByDefault, - Count, -}; - -enum class SlicingMode +namespace Slic3r { - // Regular, applying ClipperLib::pftNonZero rule when creating ExPolygons. - Regular, - // Compatible with 3DLabPrint models, applying ClipperLib::pftEvenOdd rule when creating ExPolygons. - EvenOdd, - // Orienting all contours CCW, thus closing all holes. - CloseHoles, -}; - -enum SupportMaterialPattern { - smpDefault, - smpRectilinear, smpRectilinearGrid, smpHoneycomb, - smpLightning, - smpNone, -}; - -enum SupportMaterialStyle { - smsDefault, smsGrid, smsSnug, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsTreeOrganic -}; - -enum LongRectrationLevel -{ - Disabled=0, - EnableMachine, - EnableFilament -}; - -enum SupportMaterialInterfacePattern { - smipAuto, smipRectilinear, smipConcentric, smipRectilinearInterlaced, smipGrid -}; - -// BBS -enum SupportType { - stNormalAuto, stTreeAuto, stNormal, stTree -}; -inline bool is_tree(SupportType stype) -{ - return std::set{stTreeAuto, stTree}.count(stype) != 0; -}; -inline bool is_tree_slim(SupportType type, SupportMaterialStyle style) -{ - return is_tree(type) && style==smsTreeSlim; -}; -inline bool is_auto(SupportType stype) -{ - return std::set{stNormalAuto, stTreeAuto}.count(stype) != 0; -}; - -enum SeamPosition { - spNearest, spAligned, spRear, spRandom -}; - -// Orca -enum class SeamScarfType { - None = 0, - External, - All, -}; - -enum SLAMaterial { - slamTough, - slamFlex, - slamCasting, - slamDental, - slamHeatResistant, -}; - -enum SLADisplayOrientation { - sladoLandscape, - sladoPortrait -}; - -enum SLAPillarConnectionMode { - slapcmZigZag, - slapcmCross, - slapcmDynamic -}; - -enum BrimType { - btAutoBrim, // BBS - btBrimEars, // BBS - btOuterOnly, - btInnerOnly, - btOuterAndInner, - btNoBrim, -}; - -enum TimelapseType : int { - tlTraditional = 0, - tlSmooth -}; - -enum DraftShield { - dsDisabled, dsLimited, dsEnabled -}; - -enum class PerimeterGeneratorType -{ - // Classic perimeter generator using Clipper offsets with constant extrusion width. - Classic, - // Perimeter generator with variable extrusion width based on the paper - // "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling" ported from Cura. - Arachne -}; - -enum class TopOneWallType -{ - None, - Alltop, - Topmost -}; - -// BBS -enum OverhangFanThreshold { - Overhang_threshold_none = 0, - Overhang_threshold_1_4, - Overhang_threshold_2_4, - Overhang_threshold_3_4, - Overhang_threshold_4_4, - Overhang_threshold_bridge -}; - -enum OverhangThresholdParticipatingCooling { - Overhang_threshold_participating_cooling_none = 0, - Overhang_threshold_participating_cooling_1_4, - Overhang_threshold_participating_cooling_2_4, - Overhang_threshold_participating_cooling_3_4, - Overhang_threshold_participating_cooling_4_4, - Overhang_threshold_participating_cooling_bridge -}; - -// BBS -enum BedType { - btDefault = 0, - btPC, - btEP, - btPEI, - btPTE, - btSuperTack, - btCount -}; - -enum class ExtruderOnlyAreaType:unsigned char { - btNoArea= 0, - Engilish, - Chinese, - btAreaCount -}; - -// BBS -enum LayerSeq { - flsAuto, - flsCutomize -}; - -enum FanDirection { - fdUndefine = 0, - fdLeft, - fdRight, - fdBoth -}; - -enum PrimeVolumeMode { - pvmDefault = 0, - pvmSaving -}; - -static std::unordered_mapNozzleTypeEumnToStr = { - {NozzleType::ntUndefine, "undefine"}, - {NozzleType::ntHardenedSteel, "hardened_steel"}, - {NozzleType::ntStainlessSteel, "stainless_steel"}, - {NozzleType::ntTungstenCarbide, "tungsten_carbide"}, - {NozzleType::ntBrass, "brass"}, - {NozzleType::ntE3D, "E3D"} -}; - -static std::unordered_mapNozzleTypeStrToEumn = { - {"undefine", NozzleType::ntUndefine}, - {"hardened_steel", NozzleType::ntHardenedSteel}, - {"stainless_steel", NozzleType::ntStainlessSteel}, - {"tungsten_carbide", NozzleType::ntTungstenCarbide}, - {"brass", NozzleType::ntBrass}, - {"E3D", NozzleType::ntE3D} -}; - -// BBS -enum PrinterStructure { - psUndefine=0, - psCoreXY, - psI3, - psHbot, - psDelta -}; - -// BBS -enum ZHopType { - zhtAuto = 0, - zhtNormal, - zhtSlope, - zhtSpiral, - zhtCount -}; - -// BBS -enum ExtruderType { - etDirectDrive = 0, - etBowden, - etMaxExtruderType = etBowden -}; - -enum NozzleVolumeType { - nvtStandard = 0, - nvtHighFlow, - nvtHybrid, - nvtMaxNozzleVolumeType = nvtHybrid -}; - -enum FilamentMapMode { - fmmAutoForFlush, - fmmAutoForMatch, - fmmManual, - fmmNozzleManual, - fmmDefault -}; - -extern std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type); - - -std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type); -static std::string bed_type_to_gcode_string(const BedType type) -{ - std::string type_str; - - switch (type) { - case btSuperTack: - type_str = "supertack_plate"; - break; - case btPC: - type_str = "cool_plate"; - break; - case btEP: - type_str = "eng_plate"; - break; - case btPEI: - type_str = "hot_plate"; - break; - case btPTE: - type_str = "textured_plate"; - break; - default: - type_str = "unknown"; - break; - } - return type_str; -} + enum GCodeFlavor : unsigned char + { + gcfMarlinLegacy, + gcfKlipper, + gcfRepRapSprinter, + gcfRepRapFirmware, + gcfRepetier, + gcfTeacup, + gcfMakerWare, + gcfMarlinFirmware, + gcfSailfish, + gcfMach3, + gcfMachinekit, + gcfSmoothie, + gcfNoExtrusion + }; -static std::string get_bed_temp_key(const BedType type) -{ - if (type == btSuperTack) - return "supertack_plate_temp"; + enum class FuzzySkinType + { + None, + External, + All, + AllWalls, + Disabled_fuzzy, + }; - if (type == btPC) - return "cool_plate_temp"; + enum PrintHostType + { + htPrusaLink, + htOctoPrint, + htDuet, + htFlashAir, + htAstroBox, + htRepetier, + htMKS + }; - if (type == btEP) - return "eng_plate_temp"; + enum AuthorizationType + { + atKeyPassword, + atUserPassword + }; - if (type == btPEI) - return "hot_plate_temp"; + enum InfillPattern : int + { + ipConcentric, + ipRectilinear, + ipGrid, + ipLine, + ipCubic, + ipTriangles, + ipStars, + ipGyroid, + ipHoneycomb, + ipAdaptiveCubic, + ipMonotonic, + ipMonotonicLine, + ipAlignedRectilinear, + ip3DHoneycomb, + ipHilbertCurve, + ipArchimedeanChords, + ipOctagramSpiral, + ipSupportCubic, + ipSupportBase, + ipConcentricInternal, + ipLightning, + ipCrossHatch, + ipZigZag, + ipCrossZag, + ipFloatingConcentric, + ipLockedZag, + ipCount, + }; - if (type == btPTE) - return "textured_plate_temp"; + enum EnsureVerticalThicknessLevel + { + evtDisabled, + evtPartial, + evtEnabled + }; - return ""; -} + enum class IroningType + { + NoIroning, + TopSurfaces, + TopmostOnly, + AllSolid, + Count, + }; -static std::string get_bed_temp_1st_layer_key(const BedType type) -{ - if (type == btSuperTack) - return "supertack_plate_temp_initial_layer"; + // BBS + enum class WallInfillOrder + { + InnerOuterInfill, + OuterInnerInfill, + InfillInnerOuter, + InfillOuterInner, + InnerOuterInnerInfill, + Count, + }; + + enum class BedTempFormula + { + btfFirstFilament, + btfHighestTemp, + count, + }; + + // BBS + enum class WallSequence + { + InnerOuter, + OuterInner, + InnerOuterInner, + Count, + }; + // BBS + enum class PrintSequence + { + ByLayer, + ByObject, + ByDefault, + Count, + }; - if (type == btPC) - return "cool_plate_temp_initial_layer"; + enum class SlicingMode + { + // Regular, applying ClipperLib::pftNonZero rule when creating ExPolygons. + Regular, + // Compatible with 3DLabPrint models, applying ClipperLib::pftEvenOdd rule when creating ExPolygons. + EvenOdd, + // Orienting all contours CCW, thus closing all holes. + CloseHoles, + }; - if (type == btEP) - return "eng_plate_temp_initial_layer"; + enum SupportMaterialPattern + { + smpDefault, + smpRectilinear, + smpRectilinearGrid, + smpHoneycomb, + smpLightning, + smpNone, + }; - if (type == btPEI) - return "hot_plate_temp_initial_layer"; + enum SupportMaterialStyle + { + smsDefault, + smsGrid, + smsSnug, + smsTreeSlim, + smsTreeStrong, + smsTreeHybrid, + smsTreeOrganic + }; - if (type == btPTE) - return "textured_plate_temp_initial_layer"; + enum LongRectrationLevel + { + Disabled = 0, + EnableMachine, + EnableFilament + }; - return ""; -} + enum SupportMaterialInterfacePattern + { + smipAuto, + smipRectilinear, + smipConcentric, + smipRectilinearInterlaced, + smipGrid + }; -extern const std::vector filament_extruder_override_keys; - -// for parse extruder_ams_count -extern std::vector> get_extruder_ams_count(const std::vector &strs); -extern std::vector save_extruder_ams_count_to_string(const std::vector> &extruder_ams_count); -extern std::vector> get_extruder_nozzle_stats(const std::vector & strs); -extern std::vector save_extruder_nozzle_stats_to_string(const std::vector> &extruder_nozzle_stats); - -#define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ - template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ - template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); - -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(IroningType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingMode) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern) -// BBS -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamScarfType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TimelapseType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BedType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) - -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrintHostType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(AuthorizationType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TopOneWallType) -#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS + // BBS + enum SupportType + { + stNormalAuto, + stTreeAuto, + stNormal, + stTree + }; + inline bool is_tree(SupportType stype) + { + return std::set{stTreeAuto, stTree}.count(stype) != 0; + }; + inline bool is_tree_slim(SupportType type, SupportMaterialStyle style) + { + return is_tree(type) && style == smsTreeSlim; + }; + inline bool is_auto(SupportType stype) + { + return std::set{stNormalAuto, stTreeAuto}.count(stype) != 0; + }; -// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. -// Does not store the actual values, but defines default values. -class PrintConfigDef : public ConfigDef -{ -public: - PrintConfigDef(); + enum SeamPosition + { + spNearest, + spAligned, + spRear, + spRandom + }; + + // Orca + enum class SeamScarfType + { + None = 0, + External, + All, + }; + + enum SLAMaterial + { + slamTough, + slamFlex, + slamCasting, + slamDental, + slamHeatResistant, + }; + + enum SLADisplayOrientation + { + sladoLandscape, + sladoPortrait + }; + + enum SLAPillarConnectionMode + { + slapcmZigZag, + slapcmCross, + slapcmDynamic + }; + + enum BrimType + { + btAutoBrim, // BBS + btBrimEars, // BBS + btOuterOnly, + btInnerOnly, + btOuterAndInner, + btNoBrim, + }; - static void handle_legacy(t_config_option_key &opt_key, std::string &value); + enum TimelapseType : int + { + tlTraditional = 0, + tlSmooth + }; + + enum DraftShield + { + dsDisabled, + dsLimited, + dsEnabled + }; - // Array options growing with the number of extruders - const std::vector& extruder_option_keys() const { return m_extruder_option_keys; } - // Options defining the extruder retract properties. These keys are sorted lexicographically. - // The extruder retract keys could be overidden by the same values defined at the Filament level - // (then the key is further prefixed with the "filament_" prefix). - const std::vector& extruder_retract_keys() const { return m_extruder_retract_keys; } + enum class PerimeterGeneratorType + { + // Classic perimeter generator using Clipper offsets with constant extrusion width. + Classic, + // Perimeter generator with variable extrusion width based on the paper + // "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling" ported from Cura. + Arachne + }; + + enum class TopOneWallType + { + None, + Alltop, + Topmost + }; // BBS - const std::vector& filament_option_keys() const { return m_filament_option_keys; } - const std::vector& filament_retract_keys() const { return m_filament_retract_keys; } + enum OverhangFanThreshold + { + Overhang_threshold_none = 0, + Overhang_threshold_1_4, + Overhang_threshold_2_4, + Overhang_threshold_3_4, + Overhang_threshold_4_4, + Overhang_threshold_bridge + }; + + enum OverhangThresholdParticipatingCooling + { + Overhang_threshold_participating_cooling_none = 0, + Overhang_threshold_participating_cooling_1_4, + Overhang_threshold_participating_cooling_2_4, + Overhang_threshold_participating_cooling_3_4, + Overhang_threshold_participating_cooling_4_4, + Overhang_threshold_participating_cooling_bridge + }; -private: - void init_common_params(); - void init_fff_params(); - void init_extruder_option_keys(); - void init_sla_params(); + // BBS + enum BedType + { + btDefault = 0, + btPC, + btEP, + btPEI, + btPTE, + btSuperTack, + btCount + }; - std::vector m_extruder_option_keys; - std::vector m_extruder_retract_keys; + enum class ExtruderOnlyAreaType : unsigned char + { + btNoArea = 0, + Engilish, + Chinese, + btAreaCount + }; // BBS - void init_filament_option_keys(); + enum LayerSeq + { + flsAuto, + flsCutomize + }; - std::vector m_filament_option_keys; - std::vector m_filament_retract_keys; -}; + enum FanDirection + { + fdUndefine = 0, + fdLeft, + fdRight, + fdBoth + }; -// The one and only global definition of SLic3r configuration options. -// This definition is constant. -extern const PrintConfigDef print_config_def; + enum PrimeVolumeMode + { + pvmDefault = 0, + pvmSaving + }; -class StaticPrintConfig; + static std::unordered_map NozzleTypeEumnToStr = { + {NozzleType::ntUndefine, "undefine"}, + {NozzleType::ntHardenedSteel, "hardened_steel"}, + {NozzleType::ntStainlessSteel, "stainless_steel"}, + {NozzleType::ntTungstenCarbide, "tungsten_carbide"}, + {NozzleType::ntBrass, "brass"}, + {NozzleType::ntE3D, "E3D"}}; + + static std::unordered_map NozzleTypeStrToEumn = { + {"undefine", NozzleType::ntUndefine}, + {"hardened_steel", NozzleType::ntHardenedSteel}, + {"stainless_steel", NozzleType::ntStainlessSteel}, + {"tungsten_carbide", NozzleType::ntTungstenCarbide}, + {"brass", NozzleType::ntBrass}, + {"E3D", NozzleType::ntE3D}}; -// Minimum object distance for arrangement, based on printer technology. -double min_object_distance(const ConfigBase &cfg); + // BBS + enum PrinterStructure + { + psUndefine = 0, + psCoreXY, + psI3, + psHbot, + psDelta + }; -// Slic3r dynamic configuration, used to override the configuration -// per object, per modification volume or per printing material. -// The dynamic configuration is also used to store user modifications of the print global parameters, -// so the modified configuration values may be diffed against the active configuration -// to invalidate the proper slicing resp. g-code generation processing steps. -// This object is mapped to Perl as Slic3r::Config. -class DynamicPrintConfig : public DynamicConfig -{ -public: - DynamicPrintConfig() {} - DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfig(rhs) {} - DynamicPrintConfig(DynamicPrintConfig &&rhs) noexcept : DynamicConfig(std::move(rhs)) {} - explicit DynamicPrintConfig(const StaticPrintConfig &rhs); - explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfig(rhs) {} + // BBS + enum ZHopType + { + zhtAuto = 0, + zhtNormal, + zhtSlope, + zhtSpiral, + zhtCount + }; + + // BBS + enum ExtruderType + { + etDirectDrive = 0, + etBowden, + etMaxExtruderType = etBowden + }; + + enum NozzleVolumeType + { + nvtStandard = 0, + nvtHighFlow, + nvtHybrid, + nvtMaxNozzleVolumeType = nvtHybrid + }; + + enum FilamentMapMode + { + fmmAutoForFlush, + fmmAutoForMatch, + fmmManual, + fmmNozzleManual, + fmmDefault + }; + + extern std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type); + + std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type); + static std::string bed_type_to_gcode_string(const BedType type) + { + std::string type_str; + + switch (type) + { + case btSuperTack: + type_str = "supertack_plate"; + break; + case btPC: + type_str = "cool_plate"; + break; + case btEP: + type_str = "eng_plate"; + break; + case btPEI: + type_str = "hot_plate"; + break; + case btPTE: + type_str = "textured_plate"; + break; + default: + type_str = "unknown"; + break; + } - DynamicPrintConfig& operator=(const DynamicPrintConfig &rhs) { DynamicConfig::operator=(rhs); return *this; } - DynamicPrintConfig& operator=(DynamicPrintConfig &&rhs) noexcept { DynamicConfig::operator=(std::move(rhs)); return *this; } + return type_str; + } + + static std::string get_bed_temp_key(const BedType type) + { + if (type == btSuperTack) + return "supertack_plate_temp"; + + if (type == btPC) + return "cool_plate_temp"; + + if (type == btEP) + return "eng_plate_temp"; - static DynamicPrintConfig full_print_config(); - static DynamicPrintConfig* new_from_defaults_keys(const std::vector &keys); + if (type == btPEI) + return "hot_plate_temp"; + + if (type == btPTE) + return "textured_plate_temp"; + + return ""; + } + + static std::string get_bed_temp_1st_layer_key(const BedType type) + { + if (type == btSuperTack) + return "supertack_plate_temp_initial_layer"; - // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. - const ConfigDef* def() const override { return &print_config_def; } + if (type == btPC) + return "cool_plate_temp_initial_layer"; - void normalize_fdm(); - void normalize_fdm_1(); - //return the changed param set - t_config_option_keys normalize_fdm_2(int num_objects, int used_filaments = 0); + if (type == btEP) + return "eng_plate_temp_initial_layer"; - size_t get_parameter_size(const std::string& param_name, size_t extruder_nums); - void set_num_extruders(unsigned int num_extruders); + if (type == btPEI) + return "hot_plate_temp_initial_layer"; + if (type == btPTE) + return "textured_plate_temp_initial_layer"; + + return ""; + } + + extern const std::vector filament_extruder_override_keys; + + // for parse extruder_ams_count + extern std::vector> get_extruder_ams_count(const std::vector &strs); + extern std::vector save_extruder_ams_count_to_string(const std::vector> &extruder_ams_count); + extern std::vector> get_extruder_nozzle_stats(const std::vector &strs); + extern std::vector save_extruder_nozzle_stats_to_string(const std::vector> &extruder_nozzle_stats); + +#define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ + template <> \ + const t_config_enum_names &ConfigOptionEnum::get_enum_names(); \ + template <> \ + const t_config_enum_values &ConfigOptionEnum::get_enum_values(); + + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(IroningType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingMode) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern) // BBS - void set_num_filaments(unsigned int num_filaments); - - //BBS - // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. - std::map validate(bool under_cli = false); - - // Verify whether the opt_key has not been obsoleted or renamed. - // Both opt_key and value may be modified by handle_legacy(). - // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). - // handle_legacy() is called internally by set_deserialize(). - void handle_legacy(t_config_option_key &opt_key, std::string &value) const override - { PrintConfigDef::handle_legacy(opt_key, value); } - - //BBS special case Support G/ Support W - std::string get_filament_type(std::string &displayed_filament_type, int id = 0); - - //BBS - bool is_using_different_extruders(); - bool support_different_extruders(int& extruder_count); - int get_extruder_nozzle_volume_count(int extruder_count, std::vector>& nozzle_volume_types) const; - int get_index_for_extruder(int extruder_or_filament_id, std::string id_name, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, std::string variant_name, unsigned int stride = 1) const; - std::vector update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector>& nv_types, - std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0, NozzleVolumeType filament_nvt = nvtStandard); - void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set& key_set, std::string id_name, std::string variant_name); - - void update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set& different_keys, std::string extruder_id_name, std::string extruder_variant_name, - std::set& key_set1, std::set& key_set2); - void update_diff_values_to_child_config(DynamicPrintConfig& new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set& key_set1, std::set& key_set2); - - int update_values_from_single_to_multi(DynamicPrintConfig& multi_config, std::set& key_set, std::string id_name, std::string variant_name); - int update_values_from_multi_to_multi(DynamicPrintConfig& new_config, std::set& key_set, std::string id_name, std::string variant_name, std::vector& extruder_variants); - - //int update_values_from_single_to_multi_2(DynamicPrintConfig& multi_config, std::set& key_set); - //int update_values_from_multi_to_single_2(std::set& key_set); - - int update_values_from_multi_to_multi_2(const std::vector& src_extruder_variants, const std::vector& dst_extruder_variants, const DynamicPrintConfig& dst_config, const std::set& key_sets); - -public: - // query filament - std::string get_filament_vendor() const; - std::string get_filament_type() const; -}; -extern std::set printer_extruder_options; -extern std::set print_options_with_variant; -extern std::set filament_options_with_variant; -extern std::set printer_options_with_variant_1; -extern std::set printer_options_with_variant_2; -extern std::set empty_options; - -extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector variant_index, std::set& key_set1, int stride = 1); -extern void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config, - t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_map_indices); - -void handle_legacy_sla(DynamicPrintConfig &config); - -class StaticPrintConfig : public StaticConfig -{ -public: - StaticPrintConfig() {} - - // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. - const ConfigDef* def() const override { return &print_config_def; } - // Reference to the cached list of keys. - virtual const t_config_option_keys& keys_ref() const = 0; - -protected: - // Verify whether the opt_key has not been obsoleted or renamed. - // Both opt_key and value may be modified by handle_legacy(). - // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). - // handle_legacy() is called internally by set_deserialize(). - void handle_legacy(t_config_option_key &opt_key, std::string &value) const override - { PrintConfigDef::handle_legacy(opt_key, value); } - - // Internal class for keeping a dynamic map to static options. - class StaticCacheBase + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamScarfType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TimelapseType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BedType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) + + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrintHostType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(AuthorizationType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) + CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TopOneWallType) +#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS + + // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. + // Does not store the actual values, but defines default values. + class PrintConfigDef : public ConfigDef + { + public: + PrintConfigDef(); + + static void handle_legacy(t_config_option_key &opt_key, std::string &value); + + // Array options growing with the number of extruders + const std::vector &extruder_option_keys() const { return m_extruder_option_keys; } + // Options defining the extruder retract properties. These keys are sorted lexicographically. + // The extruder retract keys could be overidden by the same values defined at the Filament level + // (then the key is further prefixed with the "filament_" prefix). + const std::vector &extruder_retract_keys() const { return m_extruder_retract_keys; } + + // BBS + const std::vector &filament_option_keys() const { return m_filament_option_keys; } + const std::vector &filament_retract_keys() const { return m_filament_retract_keys; } + + private: + void init_common_params(); + void init_fff_params(); + void init_extruder_option_keys(); + void init_sla_params(); + + std::vector m_extruder_option_keys; + std::vector m_extruder_retract_keys; + + // BBS + void init_filament_option_keys(); + + std::vector m_filament_option_keys; + std::vector m_filament_retract_keys; + }; + + // The one and only global definition of SLic3r configuration options. + // This definition is constant. + extern const PrintConfigDef print_config_def; + + class StaticPrintConfig; + + // Minimum object distance for arrangement, based on printer technology. + double min_object_distance(const ConfigBase &cfg); + + // Slic3r dynamic configuration, used to override the configuration + // per object, per modification volume or per printing material. + // The dynamic configuration is also used to store user modifications of the print global parameters, + // so the modified configuration values may be diffed against the active configuration + // to invalidate the proper slicing resp. g-code generation processing steps. + // This object is mapped to Perl as Slic3r::Config. + class DynamicPrintConfig : public DynamicConfig { public: - // To be called during the StaticCache setup. - // Add one ConfigOption into m_map_name_to_offset. - template - void opt_add(const std::string &name, const char *base_ptr, const T &opt) + DynamicPrintConfig() {} + DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfig(rhs) {} + DynamicPrintConfig(DynamicPrintConfig &&rhs) noexcept : DynamicConfig(std::move(rhs)) {} + explicit DynamicPrintConfig(const StaticPrintConfig &rhs); + explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfig(rhs) {} + + DynamicPrintConfig &operator=(const DynamicPrintConfig &rhs) + { + DynamicConfig::operator=(rhs); + return *this; + } + DynamicPrintConfig &operator=(DynamicPrintConfig &&rhs) noexcept { - assert(m_map_name_to_offset.find(name) == m_map_name_to_offset.end()); - m_map_name_to_offset[name] = (const char*)&opt - base_ptr; + DynamicConfig::operator=(std::move(rhs)); + return *this; } - protected: - std::map m_map_name_to_offset; + static DynamicPrintConfig full_print_config(); + static DynamicPrintConfig *new_from_defaults_keys(const std::vector &keys); + + // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. + const ConfigDef *def() const override { return &print_config_def; } + + void normalize_fdm(); + void normalize_fdm_1(); + // return the changed param set + t_config_option_keys normalize_fdm_2(int num_objects, int used_filaments = 0); + + size_t get_parameter_size(const std::string ¶m_name, size_t extruder_nums); + void set_num_extruders(unsigned int num_extruders); + + // BBS + void set_num_filaments(unsigned int num_filaments); + + // BBS + // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. + std::map validate(bool under_cli = false); + + // Verify whether the opt_key has not been obsoleted or renamed. + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). + // handle_legacy() is called internally by set_deserialize(). + void handle_legacy(t_config_option_key &opt_key, std::string &value) const override + { + PrintConfigDef::handle_legacy(opt_key, value); + } + + // BBS special case Support G/ Support W + std::string get_filament_type(std::string &displayed_filament_type, int id = 0); + + // BBS + bool is_using_different_extruders(); + bool support_different_extruders(int &extruder_count); + int get_extruder_nozzle_volume_count(int extruder_count, std::vector> &nozzle_volume_types) const; + int get_index_for_extruder(int extruder_or_filament_id, std::string id_name, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, std::string variant_name, unsigned int stride = 1) const; + std::vector update_values_to_printer_extruders(DynamicPrintConfig &printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector> &nv_types, + std::set &key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0, NozzleVolumeType filament_nvt = nvtStandard); + void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig &printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set &key_set, std::string id_name, std::string variant_name); + + void update_non_diff_values_to_base_config(DynamicPrintConfig &new_config, const t_config_option_keys &keys, const std::set &different_keys, std::string extruder_id_name, std::string extruder_variant_name, + std::set &key_set1, std::set &key_set2); + void update_diff_values_to_child_config(DynamicPrintConfig &new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set &key_set1, std::set &key_set2); + + int update_values_from_single_to_multi(DynamicPrintConfig &multi_config, std::set &key_set, std::string id_name, std::string variant_name); + int update_values_from_multi_to_multi(DynamicPrintConfig &new_config, std::set &key_set, std::string id_name, std::string variant_name, std::vector &extruder_variants); + + // int update_values_from_single_to_multi_2(DynamicPrintConfig& multi_config, std::set& key_set); + // int update_values_from_multi_to_single_2(std::set& key_set); + + int update_values_from_multi_to_multi_2(const std::vector &src_extruder_variants, const std::vector &dst_extruder_variants, const DynamicPrintConfig &dst_config, const std::set &key_sets); + + public: + // query filament + std::string get_filament_vendor() const; + std::string get_filament_type() const; }; + extern std::set printer_extruder_options; + extern std::set print_options_with_variant; + extern std::set filament_options_with_variant; + extern std::set printer_options_with_variant_1; + extern std::set printer_options_with_variant_2; + extern std::set empty_options; + + extern void update_static_print_config_from_dynamic(ConfigBase &config, const DynamicPrintConfig &dest_config, std::vector variant_index, std::set &key_set1, int stride = 1); + extern void compute_filament_override_value(const std::string &opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig &new_full_config, + t_config_option_keys &diff_keys, DynamicPrintConfig &filament_overrides, std::vector &f_map_indices); - // Parametrized by the type of the topmost class owning the options. - template - class StaticCache : public StaticCacheBase + void handle_legacy_sla(DynamicPrintConfig &config); + + class StaticPrintConfig : public StaticConfig { public: - // Calling the constructor of m_defaults with 0 forces m_defaults to not run the initialization. - StaticCache() : m_defaults(nullptr) {} - ~StaticCache() { delete m_defaults; m_defaults = nullptr; } + StaticPrintConfig() {} - bool initialized() const { return ! m_keys.empty(); } + // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. + const ConfigDef *def() const override { return &print_config_def; } + // Reference to the cached list of keys. + virtual const t_config_option_keys &keys_ref() const = 0; - ConfigOption* optptr(const std::string &name, T *owner) const + protected: + // Verify whether the opt_key has not been obsoleted or renamed. + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). + // handle_legacy() is called internally by set_deserialize(). + void handle_legacy(t_config_option_key &opt_key, std::string &value) const override { - const auto it = m_map_name_to_offset.find(name); - return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast((char*)owner + it->second); + PrintConfigDef::handle_legacy(opt_key, value); } - const ConfigOption* optptr(const std::string &name, const T *owner) const + // Internal class for keeping a dynamic map to static options. + class StaticCacheBase { - const auto it = m_map_name_to_offset.find(name); - return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast((const char*)owner + it->second); - } + public: + // To be called during the StaticCache setup. + // Add one ConfigOption into m_map_name_to_offset. + template + void opt_add(const std::string &name, const char *base_ptr, const T &opt) + { + assert(m_map_name_to_offset.find(name) == m_map_name_to_offset.end()); + m_map_name_to_offset[name] = (const char *)&opt - base_ptr; + } - const std::vector& keys() const { return m_keys; } - const T& defaults() const { return *m_defaults; } + protected: + std::map m_map_name_to_offset; + }; - // To be called during the StaticCache setup. - // Collect option keys from m_map_name_to_offset, - // assign default values to m_defaults. - void finalize(T *defaults, const ConfigDef *defs) + // Parametrized by the type of the topmost class owning the options. + template + class StaticCache : public StaticCacheBase { - assert(defs != nullptr); - m_defaults = defaults; - m_keys.clear(); - m_keys.reserve(m_map_name_to_offset.size()); - for (const auto &kvp : defs->options) { - // Find the option given the option name kvp.first by an offset from (char*)m_defaults. - ConfigOption *opt = this->optptr(kvp.first, m_defaults); - if (opt == nullptr) - // This option is not defined by the ConfigBase of type T. - continue; - m_keys.emplace_back(kvp.first); - const ConfigOptionDef *def = defs->get(kvp.first); - assert(def != nullptr); - if (def->default_value) - opt->set(def->default_value.get()); + public: + // Calling the constructor of m_defaults with 0 forces m_defaults to not run the initialization. + StaticCache() : m_defaults(nullptr) {} + ~StaticCache() + { + delete m_defaults; + m_defaults = nullptr; } - } - private: - T *m_defaults; - std::vector m_keys; + bool initialized() const { return !m_keys.empty(); } + + ConfigOption *optptr(const std::string &name, T *owner) const + { + const auto it = m_map_name_to_offset.find(name); + return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast((char *)owner + it->second); + } + + const ConfigOption *optptr(const std::string &name, const T *owner) const + { + const auto it = m_map_name_to_offset.find(name); + return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast((const char *)owner + it->second); + } + + const std::vector &keys() const { return m_keys; } + const T &defaults() const { return *m_defaults; } + + // To be called during the StaticCache setup. + // Collect option keys from m_map_name_to_offset, + // assign default values to m_defaults. + void finalize(T *defaults, const ConfigDef *defs) + { + assert(defs != nullptr); + m_defaults = defaults; + m_keys.clear(); + m_keys.reserve(m_map_name_to_offset.size()); + for (const auto &kvp : defs->options) + { + // Find the option given the option name kvp.first by an offset from (char*)m_defaults. + ConfigOption *opt = this->optptr(kvp.first, m_defaults); + if (opt == nullptr) + // This option is not defined by the ConfigBase of type T. + continue; + m_keys.emplace_back(kvp.first); + const ConfigOptionDef *def = defs->get(kvp.first); + assert(def != nullptr); + if (def->default_value) + opt->set(def->default_value.get()); + } + } + + private: + T *m_defaults; + std::vector m_keys; + }; }; -}; - -#define STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ -public: \ - /* Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. */ \ - const ConfigOption* optptr(const t_config_option_key &opt_key) const override \ - { return s_cache_##CLASS_NAME.optptr(opt_key, this); } \ - /* Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. */ \ - ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override \ - { return s_cache_##CLASS_NAME.optptr(opt_key, this); } \ - /* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \ - t_config_option_keys keys() const override { return s_cache_##CLASS_NAME.keys(); } \ - const t_config_option_keys& keys_ref() const override { return s_cache_##CLASS_NAME.keys(); } \ - static const CLASS_NAME& defaults() { assert(s_cache_##CLASS_NAME.initialized()); return s_cache_##CLASS_NAME.defaults(); } \ -private: \ - friend int print_config_static_initializer(); \ - static void initialize_cache() \ - { \ - assert(! s_cache_##CLASS_NAME.initialized()); \ - if (! s_cache_##CLASS_NAME.initialized()) { \ - CLASS_NAME *inst = new CLASS_NAME(1); \ - inst->initialize(s_cache_##CLASS_NAME, (const char*)inst); \ - s_cache_##CLASS_NAME.finalize(inst, inst->def()); \ - } \ - } \ + +#define STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ +public: \ + /* Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. */ \ + const ConfigOption *optptr(const t_config_option_key &opt_key) const override \ + { \ + return s_cache_##CLASS_NAME.optptr(opt_key, this); \ + } \ + /* Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. */ \ + ConfigOption *optptr(const t_config_option_key &opt_key, bool create = false) override \ + { \ + return s_cache_##CLASS_NAME.optptr(opt_key, this); \ + } \ + /* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \ + t_config_option_keys keys() const override { return s_cache_##CLASS_NAME.keys(); } \ + const t_config_option_keys &keys_ref() const override { return s_cache_##CLASS_NAME.keys(); } \ + static const CLASS_NAME &defaults() \ + { \ + assert(s_cache_##CLASS_NAME.initialized()); \ + return s_cache_##CLASS_NAME.defaults(); \ + } \ + \ +private: \ + friend int print_config_static_initializer(); \ + static void initialize_cache() \ + { \ + assert(!s_cache_##CLASS_NAME.initialized()); \ + if (!s_cache_##CLASS_NAME.initialized()) \ + { \ + CLASS_NAME *inst = new CLASS_NAME(1); \ + inst->initialize(s_cache_##CLASS_NAME, (const char *)inst); \ + s_cache_##CLASS_NAME.finalize(inst, inst->def()); \ + } \ + } \ /* Cache object holding a key/option map, a list of option keys and a copy of this static config initialized with the defaults. */ \ static StaticPrintConfig::StaticCache s_cache_##CLASS_NAME; -#define STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \ - STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ -public: \ +#define STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \ + STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ +public: \ /* Public default constructor will initialize the key/option cache and the default object copy if needed. */ \ - CLASS_NAME() { assert(s_cache_##CLASS_NAME.initialized()); *this = s_cache_##CLASS_NAME.defaults(); } \ -protected: \ - /* Protected constructor to be called when compounded. */ \ + CLASS_NAME() \ + { \ + assert(s_cache_##CLASS_NAME.initialized()); \ + *this = s_cache_##CLASS_NAME.defaults(); \ + } \ + \ +protected: \ + /* Protected constructor to be called when compounded. */ \ CLASS_NAME(int) {} -#define STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \ - STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ -public: \ +#define STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \ + STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ +public: \ /* Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. */ \ - const ConfigDef* def() const override { return &print_config_def; } \ - /* Handle legacy and obsoleted config keys */ \ - void handle_legacy(t_config_option_key &opt_key, std::string &value) const override \ - { PrintConfigDef::handle_legacy(opt_key, value); } + const ConfigDef *def() const override { return &print_config_def; } \ + /* Handle legacy and obsoleted config keys */ \ + void handle_legacy(t_config_option_key &opt_key, std::string &value) const override \ + { \ + PrintConfigDef::handle_legacy(opt_key, value); \ + } #define PRINT_CONFIG_CLASS_ELEMENT_DEFINITION(r, data, elem) BOOST_PP_TUPLE_ELEM(0, elem) BOOST_PP_TUPLE_ELEM(1, elem); #define PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2(KEY) cache.opt_add(BOOST_PP_STRINGIZE(KEY), base_ptr, this->KEY); #define PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION(r, data, elem) PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2(BOOST_PP_TUPLE_ELEM(1, elem)) #define PRINT_CONFIG_CLASS_ELEMENT_HASH(r, data, elem) boost::hash_combine(seed, BOOST_PP_TUPLE_ELEM(1, elem).hash()); -#define PRINT_CONFIG_CLASS_ELEMENT_EQUAL(r, data, elem) if (! (BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return false; -#define PRINT_CONFIG_CLASS_ELEMENT_LOWER(r, data, elem) \ - if (BOOST_PP_TUPLE_ELEM(1, elem) < rhs.BOOST_PP_TUPLE_ELEM(1, elem)) return true; \ - if (! (BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return false; - -#define PRINT_CONFIG_CLASS_DEFINE(CLASS_NAME, PARAMETER_DEFINITION_SEQ) \ -class CLASS_NAME : public StaticPrintConfig { \ - STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \ -public: \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ) \ - size_t hash() const throw() \ - { \ - size_t seed = 0; \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ) \ - return seed; \ - } \ - bool operator==(const CLASS_NAME &rhs) const throw() \ - { \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ) \ - return true; \ - } \ - bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \ - bool operator<(const CLASS_NAME &rhs) const throw() \ - { \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_LOWER, _, PARAMETER_DEFINITION_SEQ) \ - return false; \ - } \ -protected: \ - void initialize(StaticCacheBase &cache, const char *base_ptr) \ - { \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ) \ - } \ -}; +#define PRINT_CONFIG_CLASS_ELEMENT_EQUAL(r, data, elem) \ + if (!(BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) \ + return false; +#define PRINT_CONFIG_CLASS_ELEMENT_LOWER(r, data, elem) \ + if (BOOST_PP_TUPLE_ELEM(1, elem) < rhs.BOOST_PP_TUPLE_ELEM(1, elem)) \ + return true; \ + if (!(BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) \ + return false; + +#define PRINT_CONFIG_CLASS_DEFINE(CLASS_NAME, PARAMETER_DEFINITION_SEQ) \ + class CLASS_NAME : public StaticPrintConfig \ + { \ + STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \ + public: \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ) \ + size_t hash() const throw() \ + { \ + size_t seed = 0; \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ) \ + return seed; \ + } \ + bool operator==(const CLASS_NAME &rhs) const throw() \ + { \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ) \ + return true; \ + } \ + bool operator!=(const CLASS_NAME &rhs) const throw() { return !(*this == rhs); } \ + bool operator<(const CLASS_NAME &rhs) const throw() \ + { \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_LOWER, _, PARAMETER_DEFINITION_SEQ) \ + return false; \ + } \ + \ + protected: \ + void initialize(StaticCacheBase &cache, const char *base_ptr) \ + { \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ) \ + } \ + }; #define PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM(r, data, i, elem) BOOST_PP_COMMA_IF(i) public elem #define PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE) BOOST_PP_SEQ_FOR_EACH_I(PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) @@ -753,817 +890,371 @@ protected: \ #define PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, VALUE) BOOST_PP_SEQ_FOR_EACH_I(PRINT_CONFIG_CLASS_DERIVED_INITIALIZER_ITEM, VALUE, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) #define PRINT_CONFIG_CLASS_DERIVED_INITCACHE_ITEM(r, data, elem) this->elem::initialize(cache, base_ptr); #define PRINT_CONFIG_CLASS_DERIVED_INITCACHE(CLASSES_PARENTS_TUPLE) BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_INITCACHE_ITEM, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) -#define PRINT_CONFIG_CLASS_DERIVED_HASH(r, data, elem) boost::hash_combine(seed, static_cast(this)->hash()); -#define PRINT_CONFIG_CLASS_DERIVED_EQUAL(r, data, elem) \ - if (! (*static_cast(this) == static_cast(rhs))) return false; +#define PRINT_CONFIG_CLASS_DERIVED_HASH(r, data, elem) boost::hash_combine(seed, static_cast(this)->hash()); +#define PRINT_CONFIG_CLASS_DERIVED_EQUAL(r, data, elem) \ + if (!(*static_cast(this) == static_cast(rhs))) \ + return false; // Generic version, with or without new parameters. Don't use this directly. #define PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION, PARAMETER_REGISTRATION, PARAMETER_HASHES, PARAMETER_EQUALS) \ -class CLASS_NAME : PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE) { \ - STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \ - CLASS_NAME() : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 0) { assert(s_cache_##CLASS_NAME.initialized()); *this = s_cache_##CLASS_NAME.defaults(); } \ -public: \ - PARAMETER_DEFINITION \ - size_t hash() const throw() \ - { \ - size_t seed = 0; \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_HASH, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \ - PARAMETER_HASHES \ - return seed; \ - } \ - bool operator==(const CLASS_NAME &rhs) const throw() \ - { \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_EQUAL, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \ - PARAMETER_EQUALS \ - return true; \ - } \ - bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \ -protected: \ - CLASS_NAME(int) : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 1) {} \ - void initialize(StaticCacheBase &cache, const char* base_ptr) { \ - PRINT_CONFIG_CLASS_DERIVED_INITCACHE(CLASSES_PARENTS_TUPLE) \ - PARAMETER_REGISTRATION \ - } \ -}; + class CLASS_NAME : PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE) \ + { \ + STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \ + CLASS_NAME() : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 0) \ + { \ + assert(s_cache_##CLASS_NAME.initialized()); \ + *this = s_cache_##CLASS_NAME.defaults(); \ + } \ + \ + public: \ + PARAMETER_DEFINITION \ + size_t hash() const throw() \ + { \ + size_t seed = 0; \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_HASH, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \ + PARAMETER_HASHES \ + return seed; \ + } \ + bool operator==(const CLASS_NAME &rhs) const throw() \ + { \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_EQUAL, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \ + PARAMETER_EQUALS \ + return true; \ + } \ + bool operator!=(const CLASS_NAME &rhs) const throw() { return !(*this == rhs); } \ + \ + protected: \ + CLASS_NAME(int) : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 1) {} \ + void initialize(StaticCacheBase &cache, const char *base_ptr) \ + { \ + PRINT_CONFIG_CLASS_DERIVED_INITCACHE(CLASSES_PARENTS_TUPLE) \ + PARAMETER_REGISTRATION \ + } \ + }; // Variant without adding new parameters. #define PRINT_CONFIG_CLASS_DERIVED_DEFINE0(CLASS_NAME, CLASSES_PARENTS_TUPLE) \ PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), BOOST_PP_EMPTY()) // Variant with adding new parameters. -#define PRINT_CONFIG_CLASS_DERIVED_DEFINE(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION_SEQ) \ - PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ), \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ), \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ), \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ)) - -// This object is mapped to Perl as Slic3r::Config::PrintObject. -PRINT_CONFIG_CLASS_DEFINE( - PrintObjectConfig, - - ((ConfigOptionFloat, brim_object_gap)) - ((ConfigOptionEnum, brim_type)) - ((ConfigOptionFloat, brim_width)) - ((ConfigOptionBool, bridge_no_support)) - ((ConfigOptionFloat, elefant_foot_compensation)) - ((ConfigOptionFloat, max_bridge_length)) - ((ConfigOptionFloat, line_width)) - // Force the generation of solid shells between adjacent materials/volumes. - ((ConfigOptionBool, interface_shells)) - ((ConfigOptionFloat, layer_height)) - ((ConfigOptionFloat, mmu_segmented_region_max_width)) - ((ConfigOptionFloat, mmu_segmented_region_interlocking_depth)) - ((ConfigOptionFloat, raft_contact_distance)) - ((ConfigOptionFloat, raft_expansion)) - ((ConfigOptionPercent, raft_first_layer_density)) - ((ConfigOptionFloat, raft_first_layer_expansion)) - ((ConfigOptionInt, raft_layers)) - ((ConfigOptionEnum, seam_position)) - ((ConfigOptionBool, seam_placement_away_from_overhangs)) - ((ConfigOptionFloat, slice_closing_radius)) - ((ConfigOptionEnum, slicing_mode)) - ((ConfigOptionBool, enable_support)) - // Automatic supports (generated based on support_threshold_angle). - ((ConfigOptionEnum, support_type)) - // Direction of the support pattern (in XY plane).` - ((ConfigOptionFloat, support_angle)) - ((ConfigOptionBool, support_on_build_plate_only)) - ((ConfigOptionBool, support_critical_regions_only)) - ((ConfigOptionBool, support_remove_small_overhang)) - ((ConfigOptionFloat, support_top_z_distance)) - ((ConfigOptionFloat, support_bottom_z_distance)) - ((ConfigOptionInt, enforce_support_layers)) - ((ConfigOptionInt, support_filament)) - ((ConfigOptionFloat, support_line_width)) - ((ConfigOptionBool, support_interface_not_for_body)) - ((ConfigOptionBool, support_interface_loop_pattern)) - ((ConfigOptionInt, support_interface_filament)) - ((ConfigOptionInt, support_interface_top_layers)) - ((ConfigOptionInt, support_interface_bottom_layers)) - // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. - ((ConfigOptionFloat, support_interface_spacing)) - ((ConfigOptionFloatsNullable, support_interface_speed)) - ((ConfigOptionEnum, support_base_pattern)) - ((ConfigOptionEnum, support_interface_pattern)) - // Spacing between support material lines (the hatching distance). - ((ConfigOptionFloat, support_base_pattern_spacing)) - ((ConfigOptionFloat, support_expansion)) - ((ConfigOptionFloatsNullable, support_speed)) - ((ConfigOptionEnum, support_style)) - // BBS - //((ConfigOptionBool, independent_support_layer_height)) - ((ConfigOptionBool, thick_bridges)) - // Overhang angle threshold. - ((ConfigOptionInt, support_threshold_angle)) - ((ConfigOptionFloat, support_object_xy_distance)) - ((ConfigOptionFloat, support_object_first_layer_gap)) - ((ConfigOptionFloat, xy_hole_compensation)) - ((ConfigOptionFloat, xy_contour_compensation)) - //BBS auto hole contour compensation - ((ConfigOptionBool, enable_circle_compensation)) - ((ConfigOptionFloat, circle_compensation_manual_offset)) - ((ConfigOptionBool, apply_scarf_seam_on_circles)) - ((ConfigOptionBool, flush_into_objects)) - // BBS - ((ConfigOptionBool, flush_into_infill)) - ((ConfigOptionBool, flush_into_support)) - //((ConfigOptionEnum, wall_sequence)) - // BBS - ((ConfigOptionFloat, tree_support_branch_distance)) - ((ConfigOptionFloat, tree_support_branch_diameter)) - ((ConfigOptionFloat, tree_support_branch_angle)) - ((ConfigOptionFloat, tree_support_branch_diameter_angle)) - ((ConfigOptionInt, tree_support_wall_count)) - ((ConfigOptionBool, detect_narrow_internal_solid_infill)) - ((ConfigOptionBool, detect_floating_vertical_shell)) - // ((ConfigOptionBool, adaptive_layer_height)) - ((ConfigOptionFloat, support_bottom_interface_spacing)) - ((ConfigOptionFloat, internal_bridge_support_thickness)) - ((ConfigOptionEnum, wall_generator)) - ((ConfigOptionPercent, wall_transition_length)) - ((ConfigOptionPercent, wall_transition_filter_deviation)) - ((ConfigOptionFloat, wall_transition_angle)) - ((ConfigOptionInt, wall_distribution_count)) - ((ConfigOptionPercent, min_feature_size)) - ((ConfigOptionPercent, min_bead_width)) - ((ConfigOptionEnum, top_one_wall_type)) - ((ConfigOptionPercent, top_area_threshold)) - ((ConfigOptionBool, only_one_wall_first_layer)) - // OrcaSlicer - ((ConfigOptionPercent, seam_gap)) - ((ConfigOptionPercent, wipe_speed)) - ((ConfigOptionBool, role_base_wipe_speed)) - ((ConfigOptionBool, precise_z_height)) // BBS - - ((ConfigOptionBool, interlocking_beam)) - ((ConfigOptionFloat,interlocking_beam_width)) - ((ConfigOptionFloat,interlocking_orientation)) - ((ConfigOptionInt, interlocking_beam_layer_count)) - ((ConfigOptionInt, interlocking_depth)) - ((ConfigOptionInt, interlocking_boundary_avoidance)) - ((ConfigOptionInt, scarf_angle_threshold)) - -) - -// This object is mapped to Perl as Slic3r::Config::PrintRegion. -PRINT_CONFIG_CLASS_DEFINE( - PrintRegionConfig, - - ((ConfigOptionInts, print_extruder_id)) - ((ConfigOptionStrings, print_extruder_variant)) - ((ConfigOptionInt, bottom_shell_layers)) - ((ConfigOptionFloat, bottom_shell_thickness)) - ((ConfigOptionFloat, bridge_angle)) - ((ConfigOptionFloat, bridge_flow)) - ((ConfigOptionFloatsNullable, overhang_totally_speed)) - ((ConfigOptionFloatsNullable, bridge_speed)) - ((ConfigOptionEnum, ensure_vertical_shell_thickness)) - ((ConfigOptionEnum, top_surface_pattern)) - ((ConfigOptionEnum, bottom_surface_pattern)) - ((ConfigOptionEnum, internal_solid_infill_pattern)) - ((ConfigOptionFloat, outer_wall_line_width)) - ((ConfigOptionFloatsNullable, outer_wall_speed)) - ((ConfigOptionFloat, infill_direction)) - ((ConfigOptionBool, symmetric_infill_y_axis)) - ((ConfigOptionFloat, infill_shift_step)) - ((ConfigOptionFloat, infill_rotate_step)) - ((ConfigOptionPercent, skeleton_infill_density)) - ((ConfigOptionPercent, skin_infill_density)) - ((ConfigOptionPercent, sparse_infill_density)) - ((ConfigOptionInt, fill_multiline)) - ((ConfigOptionFloat, infill_lock_depth)) - ((ConfigOptionFloat, skin_infill_depth)) - ((ConfigOptionEnum, sparse_infill_pattern)) - ((ConfigOptionEnum, locked_skin_infill_pattern)) - ((ConfigOptionEnum, locked_skeleton_infill_pattern)) - ((ConfigOptionEnum, fuzzy_skin)) - ((ConfigOptionFloat, fuzzy_skin_thickness)) - ((ConfigOptionFloat, fuzzy_skin_point_distance)) - ((ConfigOptionFloatsNullable, gap_infill_speed)) - ((ConfigOptionInt, sparse_infill_filament)) - ((ConfigOptionFloat, sparse_infill_line_width)) - ((ConfigOptionFloat, skin_infill_line_width)) - ((ConfigOptionFloat, skeleton_infill_line_width)) - ((ConfigOptionPercent, infill_wall_overlap)) - ((ConfigOptionFloatsNullable, sparse_infill_speed)) - //BBS - ((ConfigOptionBool, infill_combination)) - // Ironing options - ((ConfigOptionEnum, ironing_type)) - ((ConfigOptionEnum, ironing_pattern)) - ((ConfigOptionPercent, ironing_flow)) - ((ConfigOptionFloat, ironing_spacing)) - ((ConfigOptionFloat, ironing_inset)) - ((ConfigOptionFloat, ironing_direction)) - ((ConfigOptionFloat, ironing_speed)) - // Detect bridging perimeters - ((ConfigOptionBool, detect_overhang_wall)) - ((ConfigOptionBool, smooth_speed_discontinuity_area)) - ((ConfigOptionFloat, smooth_coefficient)) - ((ConfigOptionInt, wall_filament)) - ((ConfigOptionFloat, inner_wall_line_width)) - ((ConfigOptionFloatsNullable, inner_wall_speed)) - // Total number of perimeters. - ((ConfigOptionInt, wall_loops)) - ((ConfigOptionFloat, minimum_sparse_infill_area)) - ((ConfigOptionInt, solid_infill_filament)) - ((ConfigOptionFloat, internal_solid_infill_line_width)) - ((ConfigOptionFloatsNullable, internal_solid_infill_speed)) - // Detect thin walls. - ((ConfigOptionBool, detect_thin_wall)) - ((ConfigOptionFloat, top_surface_line_width)) - ((ConfigOptionInt, top_shell_layers)) - ((ConfigOptionFloat, top_shell_thickness)) - ((ConfigOptionFloatsNullable, top_surface_speed)) - ((ConfigOptionFloatsOrPercentsNullable, small_perimeter_speed)) - ((ConfigOptionFloatsNullable, small_perimeter_threshold)) - ((ConfigOptionFloatsOrPercentsNullable, vertical_shell_speed)) - ((ConfigOptionInt, top_color_penetration_layers)) - ((ConfigOptionInt, bottom_color_penetration_layers)) - ((ConfigOptionBool, infill_instead_top_bottom_surfaces)) - ((ConfigOptionEnum, wall_sequence)) - //BBS - ((ConfigOptionBoolsNullable, enable_overhang_speed)) - ((ConfigOptionFloatsNullable, overhang_1_4_speed)) - ((ConfigOptionFloatsNullable, overhang_2_4_speed)) - ((ConfigOptionFloatsNullable, overhang_3_4_speed)) - ((ConfigOptionFloatsNullable, overhang_4_4_speed)) - ((ConfigOptionBoolsNullable, enable_height_slowdown)) - ((ConfigOptionFloatsNullable, slowdown_start_height)) - ((ConfigOptionFloatsNullable, slowdown_start_speed)) - ((ConfigOptionFloatsNullable, slowdown_start_acc)) - ((ConfigOptionFloatsNullable, slowdown_end_height)) - ((ConfigOptionFloatsNullable, slowdown_end_speed)) - ((ConfigOptionFloatsNullable, slowdown_end_acc)) - ((ConfigOptionFloatOrPercent, sparse_infill_anchor)) - ((ConfigOptionFloatOrPercent, sparse_infill_anchor_max)) - //OrcaSlicer - ((ConfigOptionFloat, top_solid_infill_flow_ratio)) - ((ConfigOptionFloat, initial_layer_flow_ratio)) - ((ConfigOptionFloat, filter_out_gap_fill)) - ((ConfigOptionBool, precise_outer_wall)) - //calib - ((ConfigOptionFloat, print_flow_ratio)) - // Orca: seam slopes - ((ConfigOptionBool, override_filament_scarf_seam_setting)) - ((ConfigOptionEnum, seam_slope_type)) - ((ConfigOptionBool, seam_slope_conditional)) - ((ConfigOptionFloatOrPercent, seam_slope_start_height)) - ((ConfigOptionFloatOrPercent, seam_slope_gap)) - ((ConfigOptionBool, seam_slope_entire_loop)) - ((ConfigOptionFloat, seam_slope_min_length)) - ((ConfigOptionInt, seam_slope_steps)) - ((ConfigOptionBool, seam_slope_inner_walls)) - ((ConfigOptionBool, embedding_wall_into_infill)) -) - -PRINT_CONFIG_CLASS_DEFINE( - MachineEnvelopeConfig, - - // M201 X... Y... Z... E... [mm/sec^2] - ((ConfigOptionFloatsNullable, machine_max_acceleration_x)) - ((ConfigOptionFloatsNullable, machine_max_acceleration_y)) - ((ConfigOptionFloatsNullable, machine_max_acceleration_z)) - ((ConfigOptionFloatsNullable, machine_max_acceleration_e)) - // M203 X... Y... Z... E... [mm/sec] - ((ConfigOptionFloatsNullable, machine_max_speed_x)) - ((ConfigOptionFloatsNullable, machine_max_speed_y)) - ((ConfigOptionFloatsNullable, machine_max_speed_z)) - ((ConfigOptionFloatsNullable, machine_max_speed_e)) - - // M204 P... R... T...[mm/sec^2] - ((ConfigOptionFloatsNullable, machine_max_acceleration_extruding)) - ((ConfigOptionFloatsNullable, machine_max_acceleration_retracting)) - ((ConfigOptionFloatsNullable, machine_max_acceleration_travel)) - - // M205 X... Y... Z... E... [mm/sec] - ((ConfigOptionFloatsNullable, machine_max_jerk_x)) - ((ConfigOptionFloatsNullable, machine_max_jerk_y)) - ((ConfigOptionFloatsNullable, machine_max_jerk_z)) - ((ConfigOptionFloatsNullable, machine_max_jerk_e)) - // M205 T... [mm/sec] - ((ConfigOptionFloatsNullable, machine_min_travel_rate)) - // M205 S... [mm/sec] - ((ConfigOptionFloatsNullable, machine_min_extruding_rate)) -) - -// This object is mapped to Perl as Slic3r::Config::GCode. -PRINT_CONFIG_CLASS_DEFINE( - GCodeConfig, - - ((ConfigOptionString, before_layer_change_gcode)) - ((ConfigOptionString, printing_by_object_gcode)) - ((ConfigOptionFloatsNullable, deretraction_speed)) - //BBS - ((ConfigOptionBool, enable_arc_fitting)) - ((ConfigOptionString, machine_end_gcode)) - ((ConfigOptionStrings, filament_end_gcode)) - ((ConfigOptionFloatsNullable, filament_flow_ratio)) - ((ConfigOptionBools, enable_pressure_advance)) - ((ConfigOptionFloats, pressure_advance)) - ((ConfigOptionFloats, filament_diameter)) - ((ConfigOptionBoolsNullable, filament_adaptive_volumetric_speed)) - ((ConfigOptionStrings, volumetric_speed_coefficients)) - ((ConfigOptionInts, filament_adhesiveness_category)) - ((ConfigOptionFloats, filament_density)) - ((ConfigOptionStrings, filament_type)) - ((ConfigOptionBools, filament_soluble)) - ((ConfigOptionStrings, filament_ids)) - ((ConfigOptionStrings, filament_colour)) - ((ConfigOptionStrings, filament_vendor)) - ((ConfigOptionBools, filament_is_support)) - ((ConfigOptionInts, filament_printable)) - ((ConfigOptionEnumsGeneric, filament_scarf_seam_type)) - ((ConfigOptionFloatsOrPercents, filament_scarf_height)) - ((ConfigOptionFloatsOrPercents, filament_scarf_gap)) - ((ConfigOptionFloats, filament_scarf_length)) - ((ConfigOptionFloats, filament_change_length)) - ((ConfigOptionFloats, filament_change_length_nc)) - ((ConfigOptionFloats, filament_cost)) - ((ConfigOptionFloats, impact_strength_z)) - ((ConfigOptionString, filament_notes)) - ((ConfigOptionStrings, default_filament_colour)) - ((ConfigOptionInts, temperature_vitrification)) //BBS - ((ConfigOptionFloatsNullable, filament_ramming_travel_time)) //BBS - ((ConfigOptionIntsNullable, filament_pre_cooling_temperature))// BBS - ((ConfigOptionFloatsNullable, filament_max_volumetric_speed)) - ((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed))//extruder change - ((ConfigOptionFloatsNullable, filament_ramming_travel_time_nc))//nc:nozzle change - ((ConfigOptionIntsNullable, filament_pre_cooling_temperature_nc)) - ((ConfigOptionFloatsNullable, filament_max_volumetric_speed_nc)) - ((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed_nc)) - ((ConfigOptionFloat, prime_tower_lift_speed)) - ((ConfigOptionFloat, prime_tower_lift_height)) - ((ConfigOptionInts, required_nozzle_HRC)) - ((ConfigOptionEnum, filament_map_mode)) - ((ConfigOptionInts, filament_map)) - ((ConfigOptionInts, filament_volume_map)) - ((ConfigOptionInts, filament_nozzle_map)) - ((ConfigOptionInts, filament_map_2)) //used for multi nozzle, map filament to the index identified by extruder+nozzle_volume_type - //((ConfigOptionInts, filament_extruder_id)) - ((ConfigOptionStrings, filament_extruder_variant)) - ((ConfigOptionFloat, machine_load_filament_time)) - ((ConfigOptionFloat, machine_unload_filament_time)) - ((ConfigOptionFloat, machine_switch_extruder_time)) - ((ConfigOptionFloat, machine_hotend_change_time)) - ((ConfigOptionBool, group_algo_with_time)) - ((ConfigOptionFloat, machine_prepare_compensation_time)) - ((ConfigOptionBool, enable_pre_heating)) - ((ConfigOptionBool, support_object_skip_flush)) - ((ConfigOptionEnum, bed_temperature_formula)) - ((ConfigOptionInts, physical_extruder_map)) - ((ConfigOptionFloatsNullable, hotend_cooling_rate)) - ((ConfigOptionFloatsNullable, hotend_heating_rate)) - ((ConfigOptionIntsNullable, nozzle_flush_dataset)) - ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) - ((ConfigOptionFloatsNullable, filament_flush_volumetric_speed)) - ((ConfigOptionIntsNullable, filament_flush_temp)) - // BBS - ((ConfigOptionBool, scan_first_layer)) - ((ConfigOptionBool, enable_wrapping_detection)) - ((ConfigOptionInt, wrapping_detection_layers)) - ((ConfigOptionPoints, wrapping_exclude_area)) - ((ConfigOptionPoints, thumbnail_size)) - // ((ConfigOptionBool, spaghetti_detector)) - ((ConfigOptionBool, gcode_add_line_number)) - ((ConfigOptionBool, bbl_bed_temperature_gcode)) - ((ConfigOptionEnum, gcode_flavor)) - ((ConfigOptionString, layer_change_gcode)) - ((ConfigOptionString, time_lapse_gcode)) - ((ConfigOptionString, wrapping_detection_gcode)) -//#ifdef HAS_PRESSURE_EQUALIZER -// ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_positive)) -// ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_negative)) -//#endif - ((ConfigOptionPercentsNullable, retract_before_wipe)) - ((ConfigOptionFloatsNullable, retraction_length)) - ((ConfigOptionFloatsNullable, retract_length_toolchange)) - ((ConfigOptionInt, enable_long_retraction_when_cut)) - ((ConfigOptionFloatsNullable, retraction_distances_when_cut)) - ((ConfigOptionBoolsNullable, long_retractions_when_cut)) - ((ConfigOptionFloatsNullable, retraction_distances_when_ec)) - ((ConfigOptionBoolsNullable, long_retractions_when_ec)) - ((ConfigOptionFloatsNullable, z_hop)) - // BBS - ((ConfigOptionEnumsGenericNullable,z_hop_types)) - ((ConfigOptionFloatsNullable, filament_retract_length_nc)) - ((ConfigOptionFloatsNullable, retract_restart_extra)) - ((ConfigOptionFloatsNullable, retract_restart_extra_toolchange)) - ((ConfigOptionFloatsNullable, retraction_speed)) - ((ConfigOptionFloatsNullable, retract_lift_above)) - ((ConfigOptionFloatsNullable, retract_lift_below)) - ((ConfigOptionString, machine_start_gcode)) - ((ConfigOptionStrings, filament_start_gcode)) - ((ConfigOptionBool, single_extruder_multi_material)) - ((ConfigOptionBool, wipe_tower_no_sparse_layers)) - ((ConfigOptionString, change_filament_gcode)) - ((ConfigOptionFloatsNullable, travel_speed)) - ((ConfigOptionFloatsNullable, travel_speed_z)) - ((ConfigOptionBool, use_relative_e_distances)) - ((ConfigOptionBool, use_firmware_retraction)) - ((ConfigOptionBool, silent_mode)) - ((ConfigOptionString, machine_pause_gcode)) - ((ConfigOptionString, template_custom_gcode)) - //BBS - ((ConfigOptionEnumsGenericNullable,nozzle_type)) - ((ConfigOptionEnum,printer_structure)) - ((ConfigOptionBool, auxiliary_fan)) - ((ConfigOptionEnum,fan_direction)) - ((ConfigOptionBool, support_chamber_temp_control)) - ((ConfigOptionBool, apply_top_surface_compensation)) - ((ConfigOptionBool, support_air_filtration)) - ((ConfigOptionBool, support_cooling_filter)) - ((ConfigOptionBool, cooling_filter_enabled)) - ((ConfigOptionBool, auto_disable_filter_on_overheat)) - ((ConfigOptionIntsNullable, extruder_max_nozzle_count)) - ((ConfigOptionBool, accel_to_decel_enable)) - ((ConfigOptionPercent, accel_to_decel_factor)) - ((ConfigOptionEnumsGeneric, extruder_type)) - ((ConfigOptionEnumsGeneric, nozzle_volume_type)) - ((ConfigOptionStrings, extruder_ams_count)) - ((ConfigOptionStrings, extruder_nozzle_stats)) - ((ConfigOptionEnum,prime_volume_mode)) - ((ConfigOptionInts, printer_extruder_id)) - ((ConfigOptionInt, master_extruder_id)) - ((ConfigOptionStrings, printer_extruder_variant)) - //Orca - ((ConfigOptionBool, has_scarf_joint_seam)) -) - -// This object is mapped to Perl as Slic3r::Config::Print. -PRINT_CONFIG_CLASS_DERIVED_DEFINE( - PrintConfig, - (MachineEnvelopeConfig, GCodeConfig), - - //BBS - ((ConfigOptionInts, additional_cooling_fan_speed)) - ((ConfigOptionBool, reduce_crossing_wall)) - ((ConfigOptionBool, z_direction_outwall_speed_continuous)) - ((ConfigOptionFloatOrPercent, max_travel_detour_distance)) - ((ConfigOptionBool, avoid_crossing_wall_includes_support)) - ((ConfigOptionPoints, printable_area)) - ((ConfigOptionPointsGroups, extruder_printable_area)) - //BBS: add bed_exclude_area - ((ConfigOptionPoints, bed_exclude_area)) - ((ConfigOptionPoints, head_wrap_detect_zone)) - // BBS - ((ConfigOptionString, bed_custom_texture)) - ((ConfigOptionString, bed_custom_model)) - ((ConfigOptionEnum, curr_bed_type)) - ((ConfigOptionInts, cool_plate_temp)) - ((ConfigOptionInts, supertack_plate_temp)) - ((ConfigOptionInts, eng_plate_temp)) - ((ConfigOptionInts, hot_plate_temp)) // hot is short for high temperature - ((ConfigOptionInts, textured_plate_temp)) - ((ConfigOptionInts, supertack_plate_temp_initial_layer)) - ((ConfigOptionInts, cool_plate_temp_initial_layer)) - ((ConfigOptionInts, eng_plate_temp_initial_layer)) - ((ConfigOptionInts, hot_plate_temp_initial_layer)) // hot is short for high temperature - ((ConfigOptionInts, textured_plate_temp_initial_layer)) - ((ConfigOptionBools, enable_overhang_bridge_fan)) - ((ConfigOptionInts, overhang_fan_speed)) - ((ConfigOptionFloats, pre_start_fan_time)) - ((ConfigOptionEnumsGeneric, overhang_fan_threshold)) - ((ConfigOptionEnumsGeneric, overhang_threshold_participating_cooling)) - ((ConfigOptionEnum,print_sequence)) - ((ConfigOptionInts, first_layer_print_sequence)) - ((ConfigOptionInts, other_layers_print_sequence)) - ((ConfigOptionInt, other_layers_print_sequence_nums)) - ((ConfigOptionBools, slow_down_for_layer_cooling)) - ((ConfigOptionBools, no_slow_down_for_cooling_on_outwalls)) - ((ConfigOptionFloatsNullable, default_acceleration)) - ((ConfigOptionFloatsNullable, travel_acceleration)) - ((ConfigOptionFloatsNullable, initial_layer_travel_acceleration)) - ((ConfigOptionFloatsNullable, inner_wall_acceleration)) - ((ConfigOptionFloatsOrPercentsNullable, sparse_infill_acceleration)) - ((ConfigOptionBools, activate_air_filtration)) - ((ConfigOptionInts, during_print_exhaust_fan_speed)) - ((ConfigOptionInts, complete_print_exhaust_fan_speed)) - ((ConfigOptionInts, close_fan_the_first_x_layers)) - ((ConfigOptionFloats, first_x_layer_fan_speed)) - ((ConfigOptionEnum, draft_shield)) - ((ConfigOptionFloat, extruder_clearance_height_to_rod))//BBs - ((ConfigOptionFloat, extruder_clearance_height_to_lid))//BBS - ((ConfigOptionFloat, extruder_clearance_dist_to_rod)) - ((ConfigOptionFloat, nozzle_height)) - ((ConfigOptionFloat, extruder_clearance_max_radius)) - ((ConfigOptionStrings, extruder_colour)) - ((ConfigOptionPoints, extruder_offset)) - ((ConfigOptionBools, reduce_fan_stop_start_freq)) - ((ConfigOptionInts, fan_cooling_layer_time)) - ((ConfigOptionFloatsNullable, top_surface_acceleration)) - ((ConfigOptionFloatsNullable, outer_wall_acceleration)) - ((ConfigOptionFloatsNullable, initial_layer_acceleration)) - ((ConfigOptionFloat, initial_layer_line_width)) - ((ConfigOptionFloat, initial_layer_print_height)) - ((ConfigOptionFloatsNullable, initial_layer_speed)) - //BBS - ((ConfigOptionFloatsNullable, initial_layer_infill_speed)) - ((ConfigOptionIntsNullable, nozzle_temperature_initial_layer)) - ((ConfigOptionInts, full_fan_speed_layer)) - ((ConfigOptionInts, fan_max_speed)) - ((ConfigOptionFloatsNullable, max_layer_height)) - ((ConfigOptionInts, fan_min_speed)) - ((ConfigOptionFloatsNullable, min_layer_height)) - ((ConfigOptionString, printer_notes)) - ((ConfigOptionFloat, printable_height)) - ((ConfigOptionFloatsNullable, extruder_printable_height)) - ((ConfigOptionPoint, best_object_pos)) - ((ConfigOptionFloats, slow_down_min_speed)) - ((ConfigOptionFloatsNullable, nozzle_diameter)) - ((ConfigOptionBool, reduce_infill_retraction)) - ((ConfigOptionBool, ooze_prevention)) - ((ConfigOptionString, filename_format)) - ((ConfigOptionStrings, post_process)) - ((ConfigOptionString, printer_model)) - ((ConfigOptionString, process_notes)) - ((ConfigOptionFloat, resolution)) - ((ConfigOptionFloatsNullable, retraction_minimum_travel)) - ((ConfigOptionBoolsNullable, retract_when_changing_layer)) - ((ConfigOptionFloat, skirt_distance)) - ((ConfigOptionInt, skirt_height)) - ((ConfigOptionInt, skirt_loops)) - ((ConfigOptionInts, slow_down_layer_time)) - ((ConfigOptionBool, spiral_mode)) - ((ConfigOptionBool, spiral_mode_smooth)) - ((ConfigOptionFloatOrPercent, spiral_mode_max_xy_smoothing)) - ((ConfigOptionInt, standby_temperature_delta)) - ((ConfigOptionIntsNullable, nozzle_temperature)) - ((ConfigOptionInts, chamber_temperatures)) - ((ConfigOptionBoolsNullable, wipe)) - // BBS - ((ConfigOptionInts, nozzle_temperature_range_low)) - ((ConfigOptionInts, nozzle_temperature_range_high)) - ((ConfigOptionFloatsNullable, wipe_distance)) - ((ConfigOptionBool, enable_prime_tower)) - ((ConfigOptionBool, prime_tower_enable_framework)) - // BBS: change wipe_tower_x and wipe_tower_y data type to floats to add partplate logic - ((ConfigOptionFloats, wipe_tower_x)) - ((ConfigOptionFloats, wipe_tower_y)) - ((ConfigOptionFloat, prime_tower_width)) - ((ConfigOptionFloat, wipe_tower_per_color_wipe)) - ((ConfigOptionFloat, wipe_tower_rotation_angle)) - ((ConfigOptionFloat, prime_tower_brim_width)) - ((ConfigOptionFloat, prime_tower_max_speed)) - ((ConfigOptionFloat, prime_tower_extra_rib_length)) - ((ConfigOptionFloat, prime_tower_rib_width)) - ((ConfigOptionPercent, prime_tower_infill_gap)) - ((ConfigOptionBool, prime_tower_skip_points)) - ((ConfigOptionBool, prime_tower_flat_ironing)) - ((ConfigOptionBool, prime_tower_rib_wall)) - ((ConfigOptionBool, prime_tower_fillet_wall)) - //((ConfigOptionFloat, wipe_tower_bridging)) - ((ConfigOptionFloats, flush_volumes_matrix)) - ((ConfigOptionFloats, flush_volumes_vector)) - // BBS: wipe tower is only used for priming - ((ConfigOptionFloats, flush_multiplier)) - //((ConfigOptionFloat, z_offset)) - // BBS: project filaments - ((ConfigOptionFloats, filament_colour_new)) - // BBS: not in any preset, calculated before slicing - ((ConfigOptionBool, has_prime_tower)) - ((ConfigOptionFloatsNullable, nozzle_volume)) - ((ConfigOptionPoints, start_end_points)) - ((ConfigOptionEnum, timelapse_type)) - ((ConfigOptionFloat, default_jerk)) - ((ConfigOptionFloat, outer_wall_jerk)) - ((ConfigOptionFloat, inner_wall_jerk)) - ((ConfigOptionFloat, infill_jerk)) - ((ConfigOptionFloat, top_surface_jerk)) - ((ConfigOptionFloat, initial_layer_jerk)) - ((ConfigOptionFloat, travel_jerk)) - ((ConfigOptionBool, is_infill_first)) - // BBS: move from PrintObjectConfig - ((ConfigOptionBool, independent_support_layer_height)) - ((ConfigOptionBool, top_z_overrides_xy_distance)) - ((ConfigOptionBool, exclude_object)) - ((ConfigOptionPercents, filament_shrink)) - ((ConfigOptionFloats, grab_length)) - ((ConfigOptionFloats, filament_velocity_adaptation_factor)) - //BBS - ((ConfigOptionFloats, circle_compensation_speed)) - ((ConfigOptionFloats, diameter_limit)) - ((ConfigOptionFloats, counter_coef_1)) - ((ConfigOptionFloats, counter_coef_2)) - ((ConfigOptionFloats, counter_coef_3)) - ((ConfigOptionFloats, hole_coef_1)) - ((ConfigOptionFloats, hole_coef_2)) - ((ConfigOptionFloats, hole_coef_3)) - ((ConfigOptionFloats, counter_limit_min)) - ((ConfigOptionFloats, counter_limit_max)) - ((ConfigOptionFloats, hole_limit_min)) - ((ConfigOptionFloats, hole_limit_max)) - ((ConfigOptionFloats, filament_prime_volume)) - ((ConfigOptionFloats, filament_prime_volume_nc)) - ((ConfigOptionFloatsNullable, filament_cooling_before_tower))) -// This object is mapped to Perl as Slic3r::Config::Full. -PRINT_CONFIG_CLASS_DERIVED_DEFINE0( - FullPrintConfig, - (PrintObjectConfig, PrintRegionConfig, PrintConfig) -) - -// Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned. -std::map validate(const FullPrintConfig &config, bool under_cli = false); - -PRINT_CONFIG_CLASS_DEFINE( - SLAPrintConfig, - ((ConfigOptionString, filename_format)) -) - -PRINT_CONFIG_CLASS_DEFINE( - SLAPrintObjectConfig, - - ((ConfigOptionFloat, layer_height)) - - //Number of the layers needed for the exposure time fade [3;20] - ((ConfigOptionInt, faded_layers))/*= 10*/ - - ((ConfigOptionFloat, slice_closing_radius)) - - // Enabling or disabling support creation - ((ConfigOptionBool, supports_enable)) - - // Diameter in mm of the pointing side of the head. - ((ConfigOptionFloat, support_head_front_diameter))/*= 0.2*/ - - // How much the pinhead has to penetrate the model surface - ((ConfigOptionFloat, support_head_penetration))/*= 0.2*/ - - // Width in mm from the back sphere center to the front sphere center. - ((ConfigOptionFloat, support_head_width))/*= 1.0*/ - - // Radius in mm of the support pillars. - ((ConfigOptionFloat, support_pillar_diameter))/*= 0.8*/ - - // The percentage of smaller pillars compared to the normal pillar diameter - // which are used in problematic areas where a normal pilla cannot fit. - ((ConfigOptionPercent, support_small_pillar_diameter_percent)) - - // How much bridge (supporting another pinhead) can be placed on a pillar. - ((ConfigOptionInt, support_max_bridges_on_pillar)) - - // How the pillars are bridged together - ((ConfigOptionEnum, support_pillar_connection_mode)) - - // Generate only ground facing supports - ((ConfigOptionBool, support_buildplate_only)) - - // TODO: unimplemented at the moment. This coefficient will have an impact - // when bridges and pillars are merged. The resulting pillar should be a bit - // thicker than the ones merging into it. How much thicker? I don't know - // but it will be derived from this value. - ((ConfigOptionFloat, support_pillar_widening_factor)) - - // Radius in mm of the pillar base. - ((ConfigOptionFloat, support_base_diameter))/*= 2.0*/ - - // The height of the pillar base cone in mm. - ((ConfigOptionFloat, support_base_height))/*= 1.0*/ - - // The minimum distance of the pillar base from the model in mm. - ((ConfigOptionFloat, support_base_safety_distance)) /*= 1.0*/ - - // The default angle for connecting support sticks and junctions. - ((ConfigOptionFloat, support_critical_angle))/*= 45*/ - - // The max length of a bridge in mm - ((ConfigOptionFloat, support_max_bridge_length))/*= 15.0*/ - - // The max distance of two pillars to get cross linked. - ((ConfigOptionFloat, support_max_pillar_link_distance)) - - // The elevation in Z direction upwards. This is the space between the pad - // and the model object's bounding box bottom. Units in mm. - ((ConfigOptionFloat, support_object_elevation))/*= 5.0*/ - - /////// Following options influence automatic support points placement: - ((ConfigOptionInt, support_points_density_relative)) - ((ConfigOptionFloat, support_points_minimal_distance)) - - // Now for the base pool (pad) ///////////////////////////////////////////// - - // Enabling or disabling support creation - ((ConfigOptionBool, pad_enable)) - - // The thickness of the pad walls - ((ConfigOptionFloat, pad_wall_thickness))/*= 2*/ - - // The height of the pad from the bottom to the top not considering the pit - ((ConfigOptionFloat, pad_wall_height))/*= 5*/ - - // How far should the pad extend around the contained geometry - ((ConfigOptionFloat, pad_brim_size)) - - // The greatest distance where two individual pads are merged into one. The - // distance is measured roughly from the centroids of the pads. - ((ConfigOptionFloat, pad_max_merge_distance))/*= 50*/ - - // The smoothing radius of the pad edges - // ((ConfigOptionFloat, pad_edge_radius))/*= 1*/; +#define PRINT_CONFIG_CLASS_DERIVED_DEFINE(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION_SEQ) \ + PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ), \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ), \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ), \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ)) + + // This object is mapped to Perl as Slic3r::Config::PrintObject. + PRINT_CONFIG_CLASS_DEFINE( + PrintObjectConfig, + + ((ConfigOptionFloat, brim_object_gap))((ConfigOptionEnum, brim_type))((ConfigOptionFloat, brim_width))((ConfigOptionBool, bridge_no_support))((ConfigOptionFloat, elefant_foot_compensation))((ConfigOptionFloat, max_bridge_length))((ConfigOptionFloat, line_width)) + // Force the generation of solid shells between adjacent materials/volumes. + ((ConfigOptionBool, interface_shells))((ConfigOptionFloat, layer_height))((ConfigOptionFloat, mmu_segmented_region_max_width))((ConfigOptionFloat, mmu_segmented_region_interlocking_depth))((ConfigOptionFloat, raft_contact_distance))((ConfigOptionFloat, raft_expansion))((ConfigOptionPercent, raft_first_layer_density))((ConfigOptionFloat, raft_first_layer_expansion))((ConfigOptionInt, raft_layers))((ConfigOptionEnum, seam_position))((ConfigOptionBool, seam_placement_away_from_overhangs))((ConfigOptionFloat, slice_closing_radius))((ConfigOptionEnum, slicing_mode))((ConfigOptionBool, enable_support)) + // Automatic supports (generated based on support_threshold_angle). + ((ConfigOptionEnum, support_type)) + // Direction of the support pattern (in XY plane).` + ((ConfigOptionFloat, support_angle))((ConfigOptionBool, support_on_build_plate_only))((ConfigOptionBool, support_critical_regions_only))((ConfigOptionBool, support_remove_small_overhang))((ConfigOptionFloat, support_top_z_distance))((ConfigOptionFloat, support_bottom_z_distance))((ConfigOptionInt, enforce_support_layers))((ConfigOptionInt, support_filament))((ConfigOptionFloat, support_line_width))((ConfigOptionBool, support_interface_not_for_body))((ConfigOptionBool, support_interface_loop_pattern))((ConfigOptionInt, support_interface_filament))((ConfigOptionInt, support_interface_top_layers))((ConfigOptionInt, support_interface_bottom_layers)) + // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. + ((ConfigOptionFloat, support_interface_spacing))((ConfigOptionFloatsNullable, support_interface_speed))((ConfigOptionEnum, support_base_pattern))((ConfigOptionEnum, support_interface_pattern)) + // Spacing between support material lines (the hatching distance). + ((ConfigOptionFloat, support_base_pattern_spacing))((ConfigOptionFloat, support_expansion))((ConfigOptionFloatsNullable, support_speed))((ConfigOptionEnum, support_style)) + // BBS + //((ConfigOptionBool, independent_support_layer_height)) + ((ConfigOptionBool, thick_bridges)) + // Overhang angle threshold. + ((ConfigOptionInt, support_threshold_angle))((ConfigOptionFloat, support_object_xy_distance))((ConfigOptionFloat, support_object_first_layer_gap))((ConfigOptionFloat, xy_hole_compensation))((ConfigOptionFloat, xy_contour_compensation)) + // BBS auto hole contour compensation + ((ConfigOptionBool, enable_circle_compensation))((ConfigOptionFloat, circle_compensation_manual_offset))((ConfigOptionBool, apply_scarf_seam_on_circles))((ConfigOptionBool, flush_into_objects)) + // BBS + ((ConfigOptionBool, flush_into_infill))((ConfigOptionBool, flush_into_support)) + //((ConfigOptionEnum, wall_sequence)) + // BBS + ((ConfigOptionFloat, tree_support_branch_distance))((ConfigOptionFloat, tree_support_branch_diameter))((ConfigOptionFloat, tree_support_branch_angle))((ConfigOptionFloat, tree_support_branch_diameter_angle))((ConfigOptionInt, tree_support_wall_count))((ConfigOptionBool, detect_narrow_internal_solid_infill))((ConfigOptionBool, detect_floating_vertical_shell)) + // ((ConfigOptionBool, adaptive_layer_height)) + ((ConfigOptionFloat, support_bottom_interface_spacing))((ConfigOptionFloat, internal_bridge_support_thickness))((ConfigOptionEnum, wall_generator))((ConfigOptionPercent, wall_transition_length))((ConfigOptionPercent, wall_transition_filter_deviation))((ConfigOptionFloat, wall_transition_angle))((ConfigOptionInt, wall_distribution_count))((ConfigOptionPercent, min_feature_size))((ConfigOptionPercent, min_bead_width))((ConfigOptionEnum, top_one_wall_type))((ConfigOptionPercent, top_area_threshold))((ConfigOptionBool, only_one_wall_first_layer)) + // OrcaSlicer + ((ConfigOptionPercent, seam_gap))((ConfigOptionPercent, wipe_speed))((ConfigOptionBool, role_base_wipe_speed))((ConfigOptionBool, precise_z_height)) // BBS + + ((ConfigOptionBool, interlocking_beam))((ConfigOptionFloat, interlocking_beam_width))((ConfigOptionFloat, interlocking_orientation))((ConfigOptionInt, interlocking_beam_layer_count))((ConfigOptionInt, interlocking_depth))((ConfigOptionInt, interlocking_boundary_avoidance))((ConfigOptionInt, scarf_angle_threshold)) + + ) + + // This object is mapped to Perl as Slic3r::Config::PrintRegion. + PRINT_CONFIG_CLASS_DEFINE( + PrintRegionConfig, + + ((ConfigOptionInts, print_extruder_id))((ConfigOptionStrings, print_extruder_variant))((ConfigOptionInt, bottom_shell_layers))((ConfigOptionFloat, bottom_shell_thickness))((ConfigOptionFloat, bridge_angle))((ConfigOptionFloat, bridge_flow))((ConfigOptionFloatsNullable, overhang_totally_speed))((ConfigOptionFloatsNullable, bridge_speed))((ConfigOptionEnum, ensure_vertical_shell_thickness))((ConfigOptionEnum, top_surface_pattern))((ConfigOptionEnum, bottom_surface_pattern))((ConfigOptionEnum, internal_solid_infill_pattern))((ConfigOptionFloat, outer_wall_line_width))((ConfigOptionFloatsNullable, outer_wall_speed))((ConfigOptionFloat, infill_direction))((ConfigOptionBool, symmetric_infill_y_axis))((ConfigOptionFloat, infill_shift_step))((ConfigOptionFloat, infill_rotate_step))((ConfigOptionPercent, skeleton_infill_density))((ConfigOptionPercent, skin_infill_density))((ConfigOptionPercent, sparse_infill_density))((ConfigOptionInt, fill_multiline))((ConfigOptionFloat, infill_lock_depth))((ConfigOptionFloat, skin_infill_depth))((ConfigOptionEnum, sparse_infill_pattern))((ConfigOptionEnum, locked_skin_infill_pattern))((ConfigOptionEnum, locked_skeleton_infill_pattern))((ConfigOptionEnum, fuzzy_skin))((ConfigOptionFloat, fuzzy_skin_thickness))((ConfigOptionFloat, fuzzy_skin_point_distance))((ConfigOptionFloatsNullable, gap_infill_speed))((ConfigOptionInt, sparse_infill_filament))((ConfigOptionFloat, sparse_infill_line_width))((ConfigOptionFloat, skin_infill_line_width))((ConfigOptionFloat, skeleton_infill_line_width))((ConfigOptionPercent, infill_wall_overlap))((ConfigOptionFloatsNullable, sparse_infill_speed)) + // BBS + ((ConfigOptionBool, infill_combination)) + // Ironing options + ((ConfigOptionEnum, ironing_type))((ConfigOptionEnum, ironing_pattern))((ConfigOptionPercent, ironing_flow))((ConfigOptionFloat, ironing_spacing))((ConfigOptionFloat, ironing_inset))((ConfigOptionFloat, ironing_direction))((ConfigOptionFloat, ironing_speed)) + // Detect bridging perimeters + ((ConfigOptionBool, detect_overhang_wall))((ConfigOptionBool, smooth_speed_discontinuity_area))((ConfigOptionFloat, smooth_coefficient))((ConfigOptionInt, wall_filament))((ConfigOptionFloat, inner_wall_line_width))((ConfigOptionFloatsNullable, inner_wall_speed)) + // Total number of perimeters. + ((ConfigOptionInt, wall_loops))((ConfigOptionFloat, minimum_sparse_infill_area))((ConfigOptionInt, solid_infill_filament))((ConfigOptionFloat, internal_solid_infill_line_width))((ConfigOptionFloatsNullable, internal_solid_infill_speed)) + // Detect thin walls. + ((ConfigOptionBool, detect_thin_wall))((ConfigOptionFloat, top_surface_line_width))((ConfigOptionInt, top_shell_layers))((ConfigOptionFloat, top_shell_thickness))((ConfigOptionFloatsNullable, top_surface_speed))((ConfigOptionFloatsOrPercentsNullable, small_perimeter_speed))((ConfigOptionFloatsNullable, small_perimeter_threshold))((ConfigOptionFloatsOrPercentsNullable, vertical_shell_speed))((ConfigOptionInt, top_color_penetration_layers))((ConfigOptionInt, bottom_color_penetration_layers))((ConfigOptionBool, infill_instead_top_bottom_surfaces))((ConfigOptionEnum, wall_sequence))((ConfigOptionBool, is_outer_second)) + // BBS + ((ConfigOptionBoolsNullable, enable_overhang_speed))((ConfigOptionFloatsNullable, overhang_1_4_speed))((ConfigOptionFloatsNullable, overhang_2_4_speed))((ConfigOptionFloatsNullable, overhang_3_4_speed))((ConfigOptionFloatsNullable, overhang_4_4_speed))((ConfigOptionBoolsNullable, enable_height_slowdown))((ConfigOptionFloatsNullable, slowdown_start_height))((ConfigOptionFloatsNullable, slowdown_start_speed))((ConfigOptionFloatsNullable, slowdown_start_acc))((ConfigOptionFloatsNullable, slowdown_end_height))((ConfigOptionFloatsNullable, slowdown_end_speed))((ConfigOptionFloatsNullable, slowdown_end_acc))((ConfigOptionFloatOrPercent, sparse_infill_anchor))((ConfigOptionFloatOrPercent, sparse_infill_anchor_max)) + // OrcaSlicer + ((ConfigOptionFloat, top_solid_infill_flow_ratio))((ConfigOptionFloat, initial_layer_flow_ratio))((ConfigOptionFloat, filter_out_gap_fill))((ConfigOptionBool, precise_outer_wall)) + // calib + ((ConfigOptionFloat, print_flow_ratio)) + // Orca: seam slopes + ((ConfigOptionBool, override_filament_scarf_seam_setting))((ConfigOptionEnum, seam_slope_type))((ConfigOptionBool, seam_slope_conditional))((ConfigOptionFloatOrPercent, seam_slope_start_height))((ConfigOptionFloatOrPercent, seam_slope_gap))((ConfigOptionBool, seam_slope_entire_loop))((ConfigOptionFloat, seam_slope_min_length))((ConfigOptionInt, seam_slope_steps))((ConfigOptionBool, seam_slope_inner_walls))((ConfigOptionBool, embedding_wall_into_infill))) + + PRINT_CONFIG_CLASS_DEFINE( + MachineEnvelopeConfig, + + // M201 X... Y... Z... E... [mm/sec^2] + ((ConfigOptionFloatsNullable, machine_max_acceleration_x))((ConfigOptionFloatsNullable, machine_max_acceleration_y))((ConfigOptionFloatsNullable, machine_max_acceleration_z))((ConfigOptionFloatsNullable, machine_max_acceleration_e)) + // M203 X... Y... Z... E... [mm/sec] + ((ConfigOptionFloatsNullable, machine_max_speed_x))((ConfigOptionFloatsNullable, machine_max_speed_y))((ConfigOptionFloatsNullable, machine_max_speed_z))((ConfigOptionFloatsNullable, machine_max_speed_e)) + + // M204 P... R... T...[mm/sec^2] + ((ConfigOptionFloatsNullable, machine_max_acceleration_extruding))((ConfigOptionFloatsNullable, machine_max_acceleration_retracting))((ConfigOptionFloatsNullable, machine_max_acceleration_travel)) + + // M205 X... Y... Z... E... [mm/sec] + ((ConfigOptionFloatsNullable, machine_max_jerk_x))((ConfigOptionFloatsNullable, machine_max_jerk_y))((ConfigOptionFloatsNullable, machine_max_jerk_z))((ConfigOptionFloatsNullable, machine_max_jerk_e)) + // M205 T... [mm/sec] + ((ConfigOptionFloatsNullable, machine_min_travel_rate)) + // M205 S... [mm/sec] + ((ConfigOptionFloatsNullable, machine_min_extruding_rate))) + + // This object is mapped to Perl as Slic3r::Config::GCode. + PRINT_CONFIG_CLASS_DEFINE( + GCodeConfig, + + ((ConfigOptionString, before_layer_change_gcode))((ConfigOptionString, printing_by_object_gcode))((ConfigOptionFloatsNullable, deretraction_speed)) + // BBS + ((ConfigOptionBool, enable_arc_fitting))((ConfigOptionString, machine_end_gcode))((ConfigOptionStrings, filament_end_gcode))((ConfigOptionFloatsNullable, filament_flow_ratio))((ConfigOptionBools, enable_pressure_advance))((ConfigOptionFloats, pressure_advance))((ConfigOptionFloats, filament_diameter))((ConfigOptionBoolsNullable, filament_adaptive_volumetric_speed))((ConfigOptionStrings, volumetric_speed_coefficients))((ConfigOptionInts, filament_adhesiveness_category))((ConfigOptionFloats, filament_density))((ConfigOptionStrings, filament_type))((ConfigOptionBools, filament_soluble))((ConfigOptionStrings, filament_ids))((ConfigOptionStrings, filament_colour))((ConfigOptionStrings, filament_vendor))((ConfigOptionBools, filament_is_support))((ConfigOptionInts, filament_printable))((ConfigOptionEnumsGeneric, filament_scarf_seam_type))((ConfigOptionFloatsOrPercents, filament_scarf_height))((ConfigOptionFloatsOrPercents, filament_scarf_gap))((ConfigOptionFloats, filament_scarf_length))((ConfigOptionFloats, filament_change_length))((ConfigOptionFloats, filament_change_length_nc))((ConfigOptionFloats, filament_cost))((ConfigOptionFloats, impact_strength_z))((ConfigOptionString, filament_notes))((ConfigOptionStrings, default_filament_colour))((ConfigOptionInts, temperature_vitrification)) // BBS + ((ConfigOptionFloatsNullable, filament_ramming_travel_time)) // BBS + ((ConfigOptionIntsNullable, filament_pre_cooling_temperature)) // BBS + ((ConfigOptionFloatsNullable, filament_max_volumetric_speed))((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed)) // extruder change + ((ConfigOptionFloatsNullable, filament_ramming_travel_time_nc)) // nc:nozzle change + ((ConfigOptionIntsNullable, filament_pre_cooling_temperature_nc))((ConfigOptionFloatsNullable, filament_max_volumetric_speed_nc))((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed_nc))((ConfigOptionFloat, prime_tower_lift_speed))((ConfigOptionFloat, prime_tower_lift_height))((ConfigOptionInts, required_nozzle_HRC))((ConfigOptionEnum, filament_map_mode))((ConfigOptionInts, filament_map))((ConfigOptionInts, filament_volume_map))((ConfigOptionInts, filament_nozzle_map))((ConfigOptionInts, filament_map_2)) // used for multi nozzle, map filament to the index identified by extruder+nozzle_volume_type + //((ConfigOptionInts, filament_extruder_id)) + ((ConfigOptionStrings, filament_extruder_variant))((ConfigOptionFloat, machine_load_filament_time))((ConfigOptionFloat, machine_unload_filament_time))((ConfigOptionFloat, machine_switch_extruder_time))((ConfigOptionFloat, machine_hotend_change_time))((ConfigOptionBool, group_algo_with_time))((ConfigOptionFloat, machine_prepare_compensation_time))((ConfigOptionBool, enable_pre_heating))((ConfigOptionBool, support_object_skip_flush))((ConfigOptionEnum, bed_temperature_formula))((ConfigOptionInts, physical_extruder_map))((ConfigOptionFloatsNullable, hotend_cooling_rate))((ConfigOptionFloatsNullable, hotend_heating_rate))((ConfigOptionIntsNullable, nozzle_flush_dataset))((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower))((ConfigOptionFloatsNullable, filament_flush_volumetric_speed))((ConfigOptionIntsNullable, filament_flush_temp)) + // BBS + ((ConfigOptionBool, scan_first_layer))((ConfigOptionBool, enable_wrapping_detection))((ConfigOptionInt, wrapping_detection_layers))((ConfigOptionPoints, wrapping_exclude_area))((ConfigOptionPoints, thumbnail_size)) + // ((ConfigOptionBool, spaghetti_detector)) + ((ConfigOptionBool, gcode_add_line_number))((ConfigOptionBool, bbl_bed_temperature_gcode))((ConfigOptionEnum, gcode_flavor))((ConfigOptionString, layer_change_gcode))((ConfigOptionString, time_lapse_gcode))((ConfigOptionString, wrapping_detection_gcode)) + // #ifdef HAS_PRESSURE_EQUALIZER + // ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_positive)) + // ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_negative)) + // #endif + ((ConfigOptionPercentsNullable, retract_before_wipe))((ConfigOptionFloatsNullable, retraction_length))((ConfigOptionFloatsNullable, retract_length_toolchange))((ConfigOptionInt, enable_long_retraction_when_cut))((ConfigOptionFloatsNullable, retraction_distances_when_cut))((ConfigOptionBoolsNullable, long_retractions_when_cut))((ConfigOptionFloatsNullable, retraction_distances_when_ec))((ConfigOptionBoolsNullable, long_retractions_when_ec))((ConfigOptionFloatsNullable, z_hop)) + // BBS + ((ConfigOptionEnumsGenericNullable, z_hop_types))((ConfigOptionFloatsNullable, filament_retract_length_nc))((ConfigOptionFloatsNullable, retract_restart_extra))((ConfigOptionFloatsNullable, retract_restart_extra_toolchange))((ConfigOptionFloatsNullable, retraction_speed))((ConfigOptionFloatsNullable, retract_lift_above))((ConfigOptionFloatsNullable, retract_lift_below))((ConfigOptionString, machine_start_gcode))((ConfigOptionStrings, filament_start_gcode))((ConfigOptionBool, single_extruder_multi_material))((ConfigOptionBool, wipe_tower_no_sparse_layers))((ConfigOptionString, change_filament_gcode))((ConfigOptionFloatsNullable, travel_speed))((ConfigOptionFloatsNullable, travel_speed_z))((ConfigOptionBool, use_relative_e_distances))((ConfigOptionBool, use_firmware_retraction))((ConfigOptionBool, silent_mode))((ConfigOptionString, machine_pause_gcode))((ConfigOptionString, template_custom_gcode)) + // BBS + ((ConfigOptionEnumsGenericNullable, nozzle_type))((ConfigOptionEnum, printer_structure))((ConfigOptionBool, auxiliary_fan))((ConfigOptionEnum, fan_direction))((ConfigOptionBool, support_chamber_temp_control))((ConfigOptionBool, apply_top_surface_compensation))((ConfigOptionBool, support_air_filtration))((ConfigOptionBool, support_cooling_filter))((ConfigOptionBool, cooling_filter_enabled))((ConfigOptionBool, auto_disable_filter_on_overheat))((ConfigOptionIntsNullable, extruder_max_nozzle_count))((ConfigOptionBool, accel_to_decel_enable))((ConfigOptionPercent, accel_to_decel_factor))((ConfigOptionEnumsGeneric, extruder_type))((ConfigOptionEnumsGeneric, nozzle_volume_type))((ConfigOptionStrings, extruder_ams_count))((ConfigOptionStrings, extruder_nozzle_stats))((ConfigOptionEnum, prime_volume_mode))((ConfigOptionInts, printer_extruder_id))((ConfigOptionInt, master_extruder_id))((ConfigOptionStrings, printer_extruder_variant)) + // Orca + ((ConfigOptionBool, has_scarf_joint_seam))) + + // This object is mapped to Perl as Slic3r::Config::Print. + PRINT_CONFIG_CLASS_DERIVED_DEFINE( + PrintConfig, + (MachineEnvelopeConfig, GCodeConfig), + + // BBS + ((ConfigOptionInts, additional_cooling_fan_speed))((ConfigOptionBool, reduce_crossing_wall))((ConfigOptionBool, z_direction_outwall_speed_continuous))((ConfigOptionFloatOrPercent, max_travel_detour_distance))((ConfigOptionBool, avoid_crossing_wall_includes_support))((ConfigOptionPoints, printable_area))((ConfigOptionPointsGroups, extruder_printable_area)) + // BBS: add bed_exclude_area + ((ConfigOptionPoints, bed_exclude_area))((ConfigOptionPoints, head_wrap_detect_zone)) + // BBS + ((ConfigOptionString, bed_custom_texture))((ConfigOptionString, bed_custom_model))((ConfigOptionEnum, curr_bed_type))((ConfigOptionInts, cool_plate_temp))((ConfigOptionInts, supertack_plate_temp))((ConfigOptionInts, eng_plate_temp))((ConfigOptionInts, hot_plate_temp)) // hot is short for high temperature + ((ConfigOptionInts, textured_plate_temp))((ConfigOptionInts, supertack_plate_temp_initial_layer))((ConfigOptionInts, cool_plate_temp_initial_layer))((ConfigOptionInts, eng_plate_temp_initial_layer))((ConfigOptionInts, hot_plate_temp_initial_layer)) // hot is short for high temperature + ((ConfigOptionInts, textured_plate_temp_initial_layer))((ConfigOptionBools, enable_overhang_bridge_fan))((ConfigOptionInts, overhang_fan_speed))((ConfigOptionFloats, pre_start_fan_time))((ConfigOptionEnumsGeneric, overhang_fan_threshold))((ConfigOptionEnumsGeneric, overhang_threshold_participating_cooling))((ConfigOptionEnum, print_sequence))((ConfigOptionInts, first_layer_print_sequence))((ConfigOptionInts, other_layers_print_sequence))((ConfigOptionInt, other_layers_print_sequence_nums))((ConfigOptionBools, slow_down_for_layer_cooling))((ConfigOptionBools, no_slow_down_for_cooling_on_outwalls))((ConfigOptionFloatsNullable, default_acceleration))((ConfigOptionFloatsNullable, travel_acceleration))((ConfigOptionFloatsNullable, initial_layer_travel_acceleration))((ConfigOptionFloatsNullable, inner_wall_acceleration))((ConfigOptionFloatsOrPercentsNullable, sparse_infill_acceleration))((ConfigOptionBools, activate_air_filtration))((ConfigOptionInts, during_print_exhaust_fan_speed))((ConfigOptionInts, complete_print_exhaust_fan_speed))((ConfigOptionInts, close_fan_the_first_x_layers))((ConfigOptionFloats, first_x_layer_fan_speed))((ConfigOptionEnum, draft_shield))((ConfigOptionFloat, extruder_clearance_height_to_rod)) // BBs + ((ConfigOptionFloat, extruder_clearance_height_to_lid)) // BBS + ((ConfigOptionFloat, extruder_clearance_dist_to_rod))((ConfigOptionFloat, nozzle_height))((ConfigOptionFloat, extruder_clearance_max_radius))((ConfigOptionStrings, extruder_colour))((ConfigOptionPoints, extruder_offset))((ConfigOptionBools, reduce_fan_stop_start_freq))((ConfigOptionInts, fan_cooling_layer_time))((ConfigOptionFloatsNullable, top_surface_acceleration))((ConfigOptionFloatsNullable, outer_wall_acceleration))((ConfigOptionFloatsNullable, initial_layer_acceleration))((ConfigOptionFloat, initial_layer_line_width))((ConfigOptionFloat, initial_layer_print_height))((ConfigOptionFloatsNullable, initial_layer_speed)) + // BBS + ((ConfigOptionFloatsNullable, initial_layer_infill_speed))((ConfigOptionIntsNullable, nozzle_temperature_initial_layer))((ConfigOptionInts, full_fan_speed_layer))((ConfigOptionInts, fan_max_speed))((ConfigOptionFloatsNullable, max_layer_height))((ConfigOptionInts, fan_min_speed))((ConfigOptionFloatsNullable, min_layer_height))((ConfigOptionString, printer_notes))((ConfigOptionFloat, printable_height))((ConfigOptionFloatsNullable, extruder_printable_height))((ConfigOptionPoint, best_object_pos))((ConfigOptionFloats, slow_down_min_speed))((ConfigOptionFloatsNullable, nozzle_diameter))((ConfigOptionBool, reduce_infill_retraction))((ConfigOptionBool, ooze_prevention))((ConfigOptionString, filename_format))((ConfigOptionStrings, post_process))((ConfigOptionString, printer_model))((ConfigOptionString, process_notes))((ConfigOptionFloat, resolution))((ConfigOptionFloatsNullable, retraction_minimum_travel))((ConfigOptionBoolsNullable, retract_when_changing_layer))((ConfigOptionFloat, skirt_distance))((ConfigOptionInt, skirt_height))((ConfigOptionInt, skirt_loops))((ConfigOptionInts, slow_down_layer_time))((ConfigOptionBool, spiral_mode))((ConfigOptionBool, spiral_mode_smooth))((ConfigOptionFloatOrPercent, spiral_mode_max_xy_smoothing))((ConfigOptionInt, standby_temperature_delta))((ConfigOptionIntsNullable, nozzle_temperature))((ConfigOptionInts, chamber_temperatures))((ConfigOptionBoolsNullable, wipe)) + // BBS + ((ConfigOptionInts, nozzle_temperature_range_low))((ConfigOptionInts, nozzle_temperature_range_high))((ConfigOptionFloatsNullable, wipe_distance))((ConfigOptionBool, enable_prime_tower))((ConfigOptionBool, prime_tower_enable_framework)) + // BBS: change wipe_tower_x and wipe_tower_y data type to floats to add partplate logic + ((ConfigOptionFloats, wipe_tower_x))((ConfigOptionFloats, wipe_tower_y))((ConfigOptionFloat, prime_tower_width))((ConfigOptionFloat, wipe_tower_per_color_wipe))((ConfigOptionFloat, wipe_tower_rotation_angle))((ConfigOptionFloat, prime_tower_brim_width))((ConfigOptionFloat, prime_tower_max_speed))((ConfigOptionFloat, prime_tower_extra_rib_length))((ConfigOptionFloat, prime_tower_rib_width))((ConfigOptionPercent, prime_tower_infill_gap))((ConfigOptionBool, prime_tower_skip_points))((ConfigOptionBool, prime_tower_flat_ironing))((ConfigOptionBool, prime_tower_rib_wall))((ConfigOptionBool, prime_tower_fillet_wall)) + //((ConfigOptionFloat, wipe_tower_bridging)) + ((ConfigOptionFloats, flush_volumes_matrix))((ConfigOptionFloats, flush_volumes_vector)) + // BBS: wipe tower is only used for priming + ((ConfigOptionFloats, flush_multiplier)) + //((ConfigOptionFloat, z_offset)) + // BBS: project filaments + ((ConfigOptionFloats, filament_colour_new)) + // BBS: not in any preset, calculated before slicing + ((ConfigOptionBool, has_prime_tower))((ConfigOptionFloatsNullable, nozzle_volume))((ConfigOptionPoints, start_end_points))((ConfigOptionEnum, timelapse_type))((ConfigOptionFloat, default_jerk))((ConfigOptionFloat, outer_wall_jerk))((ConfigOptionFloat, inner_wall_jerk))((ConfigOptionFloat, infill_jerk))((ConfigOptionFloat, top_surface_jerk))((ConfigOptionFloat, initial_layer_jerk))((ConfigOptionFloat, travel_jerk))((ConfigOptionBool, is_outer_second))((ConfigOptionBool, is_infill_first)) + // BBS: move from PrintObjectConfig + ((ConfigOptionBool, independent_support_layer_height))((ConfigOptionBool, top_z_overrides_xy_distance))((ConfigOptionBool, exclude_object))((ConfigOptionPercents, filament_shrink))((ConfigOptionFloats, grab_length))((ConfigOptionFloats, filament_velocity_adaptation_factor)) + // BBS + ((ConfigOptionFloats, circle_compensation_speed))((ConfigOptionFloats, diameter_limit))((ConfigOptionFloats, counter_coef_1))((ConfigOptionFloats, counter_coef_2))((ConfigOptionFloats, counter_coef_3))((ConfigOptionFloats, hole_coef_1))((ConfigOptionFloats, hole_coef_2))((ConfigOptionFloats, hole_coef_3))((ConfigOptionFloats, counter_limit_min))((ConfigOptionFloats, counter_limit_max))((ConfigOptionFloats, hole_limit_min))((ConfigOptionFloats, hole_limit_max))((ConfigOptionFloats, filament_prime_volume))((ConfigOptionFloats, filament_prime_volume_nc))((ConfigOptionFloatsNullable, filament_cooling_before_tower))) + // This object is mapped to Perl as Slic3r::Config::Full. + PRINT_CONFIG_CLASS_DERIVED_DEFINE0( + FullPrintConfig, + (PrintObjectConfig, PrintRegionConfig, PrintConfig)) + + // Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned. + std::map validate(const FullPrintConfig &config, bool under_cli = false); + + PRINT_CONFIG_CLASS_DEFINE( + SLAPrintConfig, + ((ConfigOptionString, filename_format))) + + PRINT_CONFIG_CLASS_DEFINE( + SLAPrintObjectConfig, + + ((ConfigOptionFloat, layer_height)) + + // Number of the layers needed for the exposure time fade [3;20] + ((ConfigOptionInt, faded_layers)) /*= 10*/ + + ((ConfigOptionFloat, slice_closing_radius)) + + // Enabling or disabling support creation + ((ConfigOptionBool, supports_enable)) + + // Diameter in mm of the pointing side of the head. + ((ConfigOptionFloat, support_head_front_diameter)) /*= 0.2*/ + + // How much the pinhead has to penetrate the model surface + ((ConfigOptionFloat, support_head_penetration)) /*= 0.2*/ + + // Width in mm from the back sphere center to the front sphere center. + ((ConfigOptionFloat, support_head_width)) /*= 1.0*/ + + // Radius in mm of the support pillars. + ((ConfigOptionFloat, support_pillar_diameter)) /*= 0.8*/ + + // The percentage of smaller pillars compared to the normal pillar diameter + // which are used in problematic areas where a normal pilla cannot fit. + ((ConfigOptionPercent, support_small_pillar_diameter_percent)) + + // How much bridge (supporting another pinhead) can be placed on a pillar. + ((ConfigOptionInt, support_max_bridges_on_pillar)) + + // How the pillars are bridged together + ((ConfigOptionEnum, support_pillar_connection_mode)) + + // Generate only ground facing supports + ((ConfigOptionBool, support_buildplate_only)) + + // TODO: unimplemented at the moment. This coefficient will have an impact + // when bridges and pillars are merged. The resulting pillar should be a bit + // thicker than the ones merging into it. How much thicker? I don't know + // but it will be derived from this value. + ((ConfigOptionFloat, support_pillar_widening_factor)) + + // Radius in mm of the pillar base. + ((ConfigOptionFloat, support_base_diameter)) /*= 2.0*/ + + // The height of the pillar base cone in mm. + ((ConfigOptionFloat, support_base_height)) /*= 1.0*/ + + // The minimum distance of the pillar base from the model in mm. + ((ConfigOptionFloat, support_base_safety_distance)) /*= 1.0*/ + + // The default angle for connecting support sticks and junctions. + ((ConfigOptionFloat, support_critical_angle)) /*= 45*/ + + // The max length of a bridge in mm + ((ConfigOptionFloat, support_max_bridge_length)) /*= 15.0*/ + + // The max distance of two pillars to get cross linked. + ((ConfigOptionFloat, support_max_pillar_link_distance)) + + // The elevation in Z direction upwards. This is the space between the pad + // and the model object's bounding box bottom. Units in mm. + ((ConfigOptionFloat, support_object_elevation)) /*= 5.0*/ + + /////// Following options influence automatic support points placement: + ((ConfigOptionInt, support_points_density_relative))((ConfigOptionFloat, support_points_minimal_distance)) + + // Now for the base pool (pad) ///////////////////////////////////////////// + + // Enabling or disabling support creation + ((ConfigOptionBool, pad_enable)) + + // The thickness of the pad walls + ((ConfigOptionFloat, pad_wall_thickness)) /*= 2*/ + + // The height of the pad from the bottom to the top not considering the pit + ((ConfigOptionFloat, pad_wall_height)) /*= 5*/ + + // How far should the pad extend around the contained geometry + ((ConfigOptionFloat, pad_brim_size)) + + // The greatest distance where two individual pads are merged into one. The + // distance is measured roughly from the centroids of the pads. + ((ConfigOptionFloat, pad_max_merge_distance)) /*= 50*/ + + // The smoothing radius of the pad edges + // ((ConfigOptionFloat, pad_edge_radius))/*= 1*/; + + // The slope of the pad wall... + ((ConfigOptionFloat, pad_wall_slope)) + + // ///////////////////////////////////////////////////////////////////////// + // Zero elevation mode parameters: + // - The object pad will be derived from the model geometry. + // - There will be a gap between the object pad and the generated pad + // according to the support_base_safety_distance parameter. + // - The two pads will be connected with tiny connector sticks + // ///////////////////////////////////////////////////////////////////////// + + // Disable the elevation (ignore its value) and use the zero elevation mode + ((ConfigOptionBool, pad_around_object)) + + ((ConfigOptionBool, pad_around_object_everywhere)) + + // This is the gap between the object bottom and the generated pad + ((ConfigOptionFloat, pad_object_gap)) + + // How far to place the connector sticks on the object pad perimeter + ((ConfigOptionFloat, pad_object_connector_stride)) + + // The width of the connectors sticks + ((ConfigOptionFloat, pad_object_connector_width)) + + // How much should the tiny connectors penetrate into the model body + ((ConfigOptionFloat, pad_object_connector_penetration)) + + // ///////////////////////////////////////////////////////////////////////// + // Model hollowing parameters: + // - Models can be hollowed out as part of the SLA print process + // - Thickness of the hollowed model walls can be adjusted + // - + // - Additional holes will be drilled into the hollow model to allow for + // - resin removal. + // ///////////////////////////////////////////////////////////////////////// + + ((ConfigOptionBool, hollowing_enable)) + + // The minimum thickness of the model walls to maintain. Note that the + // resulting walls may be thicker due to smoothing out fine cavities where + // resin could stuck. + ((ConfigOptionFloat, hollowing_min_thickness)) + + // Indirectly controls the voxel size (resolution) used by openvdb + ((ConfigOptionFloat, hollowing_quality)) + + // Indirectly controls the minimum size of created cavities. + ((ConfigOptionFloat, hollowing_closing_distance))) + + enum SLAMaterialSpeed + { + slamsSlow, + slamsFast + }; + + PRINT_CONFIG_CLASS_DEFINE( + SLAMaterialConfig, + + ((ConfigOptionFloat, initial_layer_height))((ConfigOptionFloat, bottle_cost))((ConfigOptionFloat, bottle_volume))((ConfigOptionFloat, bottle_weight))((ConfigOptionFloat, material_density))((ConfigOptionFloat, exposure_time))((ConfigOptionFloat, initial_exposure_time))((ConfigOptionFloats, material_correction))((ConfigOptionFloat, material_correction_x))((ConfigOptionFloat, material_correction_y))((ConfigOptionFloat, material_correction_z))((ConfigOptionEnum, material_print_speed))) + + PRINT_CONFIG_CLASS_DEFINE( + SLAPrinterConfig, - // The slope of the pad wall... - ((ConfigOptionFloat, pad_wall_slope)) - - // ///////////////////////////////////////////////////////////////////////// - // Zero elevation mode parameters: - // - The object pad will be derived from the model geometry. - // - There will be a gap between the object pad and the generated pad - // according to the support_base_safety_distance parameter. - // - The two pads will be connected with tiny connector sticks - // ///////////////////////////////////////////////////////////////////////// + ((ConfigOptionEnum, printer_technology))((ConfigOptionPoints, printable_area))((ConfigOptionFloat, printable_height))((ConfigOptionFloat, display_width))((ConfigOptionFloat, display_height))((ConfigOptionInt, display_pixels_x))((ConfigOptionInt, display_pixels_y))((ConfigOptionEnum, display_orientation))((ConfigOptionBool, display_mirror_x))((ConfigOptionBool, display_mirror_y))((ConfigOptionFloats, relative_correction))((ConfigOptionFloat, relative_correction_x))((ConfigOptionFloat, relative_correction_y))((ConfigOptionFloat, relative_correction_z))((ConfigOptionFloat, absolute_correction))((ConfigOptionFloat, elefant_foot_compensation))((ConfigOptionFloat, elefant_foot_min_width))((ConfigOptionFloat, gamma_correction))((ConfigOptionFloat, fast_tilt_time))((ConfigOptionFloat, slow_tilt_time))((ConfigOptionFloat, area_fill))((ConfigOptionFloat, min_exposure_time))((ConfigOptionFloat, max_exposure_time))((ConfigOptionFloat, min_initial_exposure_time))((ConfigOptionFloat, max_initial_exposure_time))) - // Disable the elevation (ignore its value) and use the zero elevation mode - ((ConfigOptionBool, pad_around_object)) - - ((ConfigOptionBool, pad_around_object_everywhere)) - - // This is the gap between the object bottom and the generated pad - ((ConfigOptionFloat, pad_object_gap)) - - // How far to place the connector sticks on the object pad perimeter - ((ConfigOptionFloat, pad_object_connector_stride)) - - // The width of the connectors sticks - ((ConfigOptionFloat, pad_object_connector_width)) - - // How much should the tiny connectors penetrate into the model body - ((ConfigOptionFloat, pad_object_connector_penetration)) - - // ///////////////////////////////////////////////////////////////////////// - // Model hollowing parameters: - // - Models can be hollowed out as part of the SLA print process - // - Thickness of the hollowed model walls can be adjusted - // - - // - Additional holes will be drilled into the hollow model to allow for - // - resin removal. - // ///////////////////////////////////////////////////////////////////////// - - ((ConfigOptionBool, hollowing_enable)) - - // The minimum thickness of the model walls to maintain. Note that the - // resulting walls may be thicker due to smoothing out fine cavities where - // resin could stuck. - ((ConfigOptionFloat, hollowing_min_thickness)) - - // Indirectly controls the voxel size (resolution) used by openvdb - ((ConfigOptionFloat, hollowing_quality)) - - // Indirectly controls the minimum size of created cavities. - ((ConfigOptionFloat, hollowing_closing_distance)) -) - -enum SLAMaterialSpeed { slamsSlow, slamsFast }; - -PRINT_CONFIG_CLASS_DEFINE( - SLAMaterialConfig, - - ((ConfigOptionFloat, initial_layer_height)) - ((ConfigOptionFloat, bottle_cost)) - ((ConfigOptionFloat, bottle_volume)) - ((ConfigOptionFloat, bottle_weight)) - ((ConfigOptionFloat, material_density)) - ((ConfigOptionFloat, exposure_time)) - ((ConfigOptionFloat, initial_exposure_time)) - ((ConfigOptionFloats, material_correction)) - ((ConfigOptionFloat, material_correction_x)) - ((ConfigOptionFloat, material_correction_y)) - ((ConfigOptionFloat, material_correction_z)) - ((ConfigOptionEnum, material_print_speed)) -) - -PRINT_CONFIG_CLASS_DEFINE( - SLAPrinterConfig, - - ((ConfigOptionEnum, printer_technology)) - ((ConfigOptionPoints, printable_area)) - ((ConfigOptionFloat, printable_height)) - ((ConfigOptionFloat, display_width)) - ((ConfigOptionFloat, display_height)) - ((ConfigOptionInt, display_pixels_x)) - ((ConfigOptionInt, display_pixels_y)) - ((ConfigOptionEnum,display_orientation)) - ((ConfigOptionBool, display_mirror_x)) - ((ConfigOptionBool, display_mirror_y)) - ((ConfigOptionFloats, relative_correction)) - ((ConfigOptionFloat, relative_correction_x)) - ((ConfigOptionFloat, relative_correction_y)) - ((ConfigOptionFloat, relative_correction_z)) - ((ConfigOptionFloat, absolute_correction)) - ((ConfigOptionFloat, elefant_foot_compensation)) - ((ConfigOptionFloat, elefant_foot_min_width)) - ((ConfigOptionFloat, gamma_correction)) - ((ConfigOptionFloat, fast_tilt_time)) - ((ConfigOptionFloat, slow_tilt_time)) - ((ConfigOptionFloat, area_fill)) - ((ConfigOptionFloat, min_exposure_time)) - ((ConfigOptionFloat, max_exposure_time)) - ((ConfigOptionFloat, min_initial_exposure_time)) - ((ConfigOptionFloat, max_initial_exposure_time)) -) - -PRINT_CONFIG_CLASS_DERIVED_DEFINE0( - SLAFullPrintConfig, - (SLAPrinterConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAMaterialConfig) -) + PRINT_CONFIG_CLASS_DERIVED_DEFINE0( + SLAFullPrintConfig, + (SLAPrinterConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAMaterialConfig)) #undef STATIC_PRINT_CONFIG_CACHE #undef STATIC_PRINT_CONFIG_CACHE_BASE @@ -1587,208 +1278,271 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE0( #undef PRINT_CONFIG_CLASS_DERIVED_INITIALIZER #undef PRINT_CONFIG_CLASS_DERIVED_INITIALIZER_ITEM -class CLIActionsConfigDef : public ConfigDef -{ -public: - CLIActionsConfigDef(); -}; + class CLIActionsConfigDef : public ConfigDef + { + public: + CLIActionsConfigDef(); + }; -class CLITransformConfigDef : public ConfigDef -{ -public: - CLITransformConfigDef(); -}; + class CLITransformConfigDef : public ConfigDef + { + public: + CLITransformConfigDef(); + }; -class CLIMiscConfigDef : public ConfigDef -{ -public: - CLIMiscConfigDef(); -}; + class CLIMiscConfigDef : public ConfigDef + { + public: + CLIMiscConfigDef(); + }; -// This class defines the command line options representing actions. -extern const CLIActionsConfigDef cli_actions_config_def; + // This class defines the command line options representing actions. + extern const CLIActionsConfigDef cli_actions_config_def; -// This class defines the command line options representing transforms. -extern const CLITransformConfigDef cli_transform_config_def; + // This class defines the command line options representing transforms. + extern const CLITransformConfigDef cli_transform_config_def; -// This class defines all command line options that are not actions or transforms. -extern const CLIMiscConfigDef cli_misc_config_def; + // This class defines all command line options that are not actions or transforms. + extern const CLIMiscConfigDef cli_misc_config_def; -class DynamicPrintAndCLIConfig : public DynamicPrintConfig -{ -public: - DynamicPrintAndCLIConfig() {} - DynamicPrintAndCLIConfig(const DynamicPrintAndCLIConfig &other) : DynamicPrintConfig(other) {} + class DynamicPrintAndCLIConfig : public DynamicPrintConfig + { + public: + DynamicPrintAndCLIConfig() {} + DynamicPrintAndCLIConfig(const DynamicPrintAndCLIConfig &other) : DynamicPrintConfig(other) {} - // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. - const ConfigDef* def() const override { return &s_def; } + // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. + const ConfigDef *def() const override { return &s_def; } - // Verify whether the opt_key has not been obsoleted or renamed. - // Both opt_key and value may be modified by handle_legacy(). - // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). - // handle_legacy() is called internally by set_deserialize(). - void handle_legacy(t_config_option_key &opt_key, std::string &value) const override; + // Verify whether the opt_key has not been obsoleted or renamed. + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). + // handle_legacy() is called internally by set_deserialize(). + void handle_legacy(t_config_option_key &opt_key, std::string &value) const override; + + private: + class PrintAndCLIConfigDef : public ConfigDef + { + public: + PrintAndCLIConfigDef() + { + this->options.insert(print_config_def.options.begin(), print_config_def.options.end()); + this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end()); + this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); + this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); + for (const auto &kvp : this->options) + this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second; + } + // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def. + ~PrintAndCLIConfigDef() { this->options.clear(); } + }; + static PrintAndCLIConfigDef s_def; + }; -private: - class PrintAndCLIConfigDef : public ConfigDef + Polygon get_shared_poly(const std::vector &extruder_polys); + Points get_bed_shape(const DynamicPrintConfig &cfg, bool use_share = true); + Points get_bed_shape(const PrintConfig &cfg, bool use_share = false); + Points get_bed_shape(const SLAPrinterConfig &cfg); + Slic3r::Polygon get_bed_shape_with_excluded_area(const PrintConfig &cfg, bool use_share = false); + bool has_skirt(const DynamicPrintConfig &cfg); + float get_real_skirt_dist(const DynamicPrintConfig &cfg); + + // ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp. + // Each change of ModelConfig is tracked by assigning a new timestamp from a global counter. + // The counter is used for faster synchronization of the background slicing thread + // with the front end by skipping synchronization of equal config dictionaries. + // The global counter is also used for avoiding unnecessary serialization of config + // dictionaries when taking an Undo snapshot. + // + // The global counter is NOT thread safe, therefore it is recommended to use ModelConfig from + // the main thread only. + // + // As there is a global counter and it is being increased with each change to any ModelConfig, + // if two ModelConfig dictionaries differ, they should differ with their timestamp as well. + // Therefore copying the ModelConfig including its timestamp is safe as there is no harm + // in having multiple ModelConfig with equal timestamps as long as their dictionaries are equal. + // + // The timestamp is used by the Undo/Redo stack. As zero timestamp means invalid timestamp + // to the Undo/Redo stack (zero timestamp means the Undo/Redo stack needs to serialize and + // compare serialized data for differences), zero timestamp shall never be used. + // Timestamp==1 shall only be used for empty dictionaries. + class ModelConfig { public: - PrintAndCLIConfigDef() { - this->options.insert(print_config_def.options.begin(), print_config_def.options.end()); - this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end()); - this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); - this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); - for (const auto &kvp : this->options) - this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second; + // Following method clears the config and increases its timestamp, so the deleted + // state is considered changed from perspective of the undo/redo stack. + void reset() + { + m_data.clear(); + touch(); } - // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def. - ~PrintAndCLIConfigDef() { this->options.clear(); } - }; - static PrintAndCLIConfigDef s_def; -}; - -Polygon get_shared_poly(const std::vector& extruder_polys); -Points get_bed_shape(const DynamicPrintConfig &cfg, bool use_share = true); -Points get_bed_shape(const PrintConfig &cfg, bool use_share = false); -Points get_bed_shape(const SLAPrinterConfig &cfg); -Slic3r::Polygon get_bed_shape_with_excluded_area(const PrintConfig& cfg, bool use_share = false); -bool has_skirt(const DynamicPrintConfig& cfg); -float get_real_skirt_dist(const DynamicPrintConfig& cfg); - -// ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp. -// Each change of ModelConfig is tracked by assigning a new timestamp from a global counter. -// The counter is used for faster synchronization of the background slicing thread -// with the front end by skipping synchronization of equal config dictionaries. -// The global counter is also used for avoiding unnecessary serialization of config -// dictionaries when taking an Undo snapshot. -// -// The global counter is NOT thread safe, therefore it is recommended to use ModelConfig from -// the main thread only. -// -// As there is a global counter and it is being increased with each change to any ModelConfig, -// if two ModelConfig dictionaries differ, they should differ with their timestamp as well. -// Therefore copying the ModelConfig including its timestamp is safe as there is no harm -// in having multiple ModelConfig with equal timestamps as long as their dictionaries are equal. -// -// The timestamp is used by the Undo/Redo stack. As zero timestamp means invalid timestamp -// to the Undo/Redo stack (zero timestamp means the Undo/Redo stack needs to serialize and -// compare serialized data for differences), zero timestamp shall never be used. -// Timestamp==1 shall only be used for empty dictionaries. -class ModelConfig -{ -public: - // Following method clears the config and increases its timestamp, so the deleted - // state is considered changed from perspective of the undo/redo stack. - void reset() { m_data.clear(); touch(); } - - void assign_config(const ModelConfig &rhs) { - if (m_timestamp != rhs.m_timestamp) { - m_data = rhs.m_data; - m_timestamp = rhs.m_timestamp; + + void assign_config(const ModelConfig &rhs) + { + if (m_timestamp != rhs.m_timestamp) + { + m_data = rhs.m_data; + m_timestamp = rhs.m_timestamp; + } } - } - void assign_config(ModelConfig &&rhs) { - if (m_timestamp != rhs.m_timestamp) { - m_data = std::move(rhs.m_data); - m_timestamp = rhs.m_timestamp; - rhs.reset(); + void assign_config(ModelConfig &&rhs) + { + if (m_timestamp != rhs.m_timestamp) + { + m_data = std::move(rhs.m_data); + m_timestamp = rhs.m_timestamp; + rhs.reset(); + } } - } - // Modification of the ModelConfig is not thread safe due to the global timestamp counter! - // Don't call modification methods from the back-end! - // Assign methods don't assign if src==dst to not having to bump the timestamp in case they are equal. - void assign_config(const DynamicPrintConfig &rhs) { if (m_data != rhs) { m_data = rhs; this->touch(); } } - void assign_config(DynamicPrintConfig &&rhs) { if (m_data != rhs) { m_data = std::move(rhs); this->touch(); } } - void apply(const ModelConfig &other, bool ignore_nonexistent = false) { this->apply(other.get(), ignore_nonexistent); } - void apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_data.apply_only(other, other.keys(), ignore_nonexistent); this->touch(); } - void apply_only(const ModelConfig &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->apply_only(other.get(), keys, ignore_nonexistent); } - void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { m_data.apply_only(other, keys, ignore_nonexistent); this->touch(); } - bool set_key_value(const std::string &opt_key, ConfigOption *opt) { bool out = m_data.set_key_value(opt_key, opt); this->touch(); return out; } - template - void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); } - void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false) - { m_data.set_deserialize(opt_key, str, substitution_context, append); this->touch(); } - bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; } - - // Getters are thread safe. - // The following implicit conversion breaks the Cereal serialization. -// operator const DynamicPrintConfig&() const throw() { return this->get(); } - const DynamicPrintConfig& get() const throw() { return m_data; } - bool empty() const throw() { return m_data.empty(); } - size_t size() const throw() { return m_data.size(); } - auto cbegin() const { return m_data.cbegin(); } - auto cend() const { return m_data.cend(); } - t_config_option_keys keys() const { return m_data.keys(); } - bool has(const t_config_option_key &opt_key) const { return m_data.has(opt_key); } - const ConfigOption* option(const t_config_option_key &opt_key) const { return m_data.option(opt_key); } - int opt_int(const t_config_option_key &opt_key) const { return m_data.opt_int(opt_key); } - int extruder() const { return opt_int("extruder"); } - double opt_float(const t_config_option_key &opt_key) const { return m_data.opt_float(opt_key); } - std::string opt_serialize(const t_config_option_key &opt_key) const { return m_data.opt_serialize(opt_key); } - - // Return an optional timestamp of this object. - // If the timestamp returned is non-zero, then the serialization framework will - // only save this object on the Undo/Redo stack if the timestamp is different - // from the timestmap of the object at the top of the Undo / Redo stack. - virtual uint64_t timestamp() const throw() { return m_timestamp; } - bool timestamp_matches(const ModelConfig &rhs) const throw() { return m_timestamp == rhs.m_timestamp; } - // Not thread safe! Should not be called from other than the main thread! - void touch() { m_timestamp = ++ s_last_timestamp; } - -private: - friend class cereal::access; - template void serialize(Archive& ar) { ar(m_timestamp); ar(m_data); } - - uint64_t m_timestamp { 1 }; - DynamicPrintConfig m_data; - - static uint64_t s_last_timestamp; -}; - -// const std::vector &fv_matrix: origin matrix from json -// size_t extruder_id: -1 means single-nozzle for old file, 0 means the 1st extruder, 1 means the 2nd extruder -template -static std::vector get_flush_volumes_matrix(const std::vector &fv_matrix, size_t extruder_id = -1, size_t nozzle_nums = 1) -{ - if (extruder_id != -1 && nozzle_nums != 1) { - return std::vector(fv_matrix.begin() + size_t(fv_matrix.size() / nozzle_nums * extruder_id + EPSILON), - fv_matrix.begin() + size_t(fv_matrix.size() / nozzle_nums * (extruder_id + 1) + EPSILON)); - } - return fv_matrix; -} + // Modification of the ModelConfig is not thread safe due to the global timestamp counter! + // Don't call modification methods from the back-end! + // Assign methods don't assign if src==dst to not having to bump the timestamp in case they are equal. + void assign_config(const DynamicPrintConfig &rhs) + { + if (m_data != rhs) + { + m_data = rhs; + this->touch(); + } + } + void assign_config(DynamicPrintConfig &&rhs) + { + if (m_data != rhs) + { + m_data = std::move(rhs); + this->touch(); + } + } + void apply(const ModelConfig &other, bool ignore_nonexistent = false) { this->apply(other.get(), ignore_nonexistent); } + void apply(const ConfigBase &other, bool ignore_nonexistent = false) + { + m_data.apply_only(other, other.keys(), ignore_nonexistent); + this->touch(); + } + void apply_only(const ModelConfig &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->apply_only(other.get(), keys, ignore_nonexistent); } + void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) + { + m_data.apply_only(other, keys, ignore_nonexistent); + this->touch(); + } + bool set_key_value(const std::string &opt_key, ConfigOption *opt) + { + bool out = m_data.set_key_value(opt_key, opt); + this->touch(); + return out; + } + template + void set(const std::string &opt_key, T value) + { + m_data.set(opt_key, value, true); + this->touch(); + } + void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false) + { + m_data.set_deserialize(opt_key, str, substitution_context, append); + this->touch(); + } + bool erase(const t_config_option_key &opt_key) + { + bool out = m_data.erase(opt_key); + if (out) + this->touch(); + return out; + } -// std::vector &out_matrix: -// const std::vector &fv_matrix: the matrix of one nozzle -// size_t extruder_id: -1 means single-nozzle for old file, 0 means the 1st extruder, 1 means the 2nd extruder -template -static void set_flush_volumes_matrix(std::vector &out_matrix, const std::vector &fv_matrix, size_t extruder_id = -1, size_t nozzle_nums = 1) -{ - bool is_multi_extruder = false; - if (extruder_id != -1 && nozzle_nums != 1) { - std::copy(fv_matrix.begin(), fv_matrix.end(), out_matrix.begin() + size_t(out_matrix.size() / nozzle_nums * extruder_id + EPSILON)); + // Getters are thread safe. + // The following implicit conversion breaks the Cereal serialization. + // operator const DynamicPrintConfig&() const throw() { return this->get(); } + const DynamicPrintConfig &get() const throw() { return m_data; } + bool empty() const throw() { return m_data.empty(); } + size_t size() const throw() { return m_data.size(); } + auto cbegin() const { return m_data.cbegin(); } + auto cend() const { return m_data.cend(); } + t_config_option_keys keys() const { return m_data.keys(); } + bool has(const t_config_option_key &opt_key) const { return m_data.has(opt_key); } + const ConfigOption *option(const t_config_option_key &opt_key) const { return m_data.option(opt_key); } + int opt_int(const t_config_option_key &opt_key) const { return m_data.opt_int(opt_key); } + int extruder() const { return opt_int("extruder"); } + double opt_float(const t_config_option_key &opt_key) const { return m_data.opt_float(opt_key); } + std::string opt_serialize(const t_config_option_key &opt_key) const { return m_data.opt_serialize(opt_key); } + + // Return an optional timestamp of this object. + // If the timestamp returned is non-zero, then the serialization framework will + // only save this object on the Undo/Redo stack if the timestamp is different + // from the timestmap of the object at the top of the Undo / Redo stack. + virtual uint64_t timestamp() const throw() { return m_timestamp; } + bool timestamp_matches(const ModelConfig &rhs) const throw() { return m_timestamp == rhs.m_timestamp; } + // Not thread safe! Should not be called from other than the main thread! + void touch() { m_timestamp = ++s_last_timestamp; } + + private: + friend class cereal::access; + template + void serialize(Archive &ar) + { + ar(m_timestamp); + ar(m_data); + } + + uint64_t m_timestamp{1}; + DynamicPrintConfig m_data; + + static uint64_t s_last_timestamp; + }; + + // const std::vector &fv_matrix: origin matrix from json + // size_t extruder_id: -1 means single-nozzle for old file, 0 means the 1st extruder, 1 means the 2nd extruder + template + static std::vector get_flush_volumes_matrix(const std::vector &fv_matrix, size_t extruder_id = -1, size_t nozzle_nums = 1) + { + if (extruder_id != -1 && nozzle_nums != 1) + { + return std::vector(fv_matrix.begin() + size_t(fv_matrix.size() / nozzle_nums * extruder_id + EPSILON), + fv_matrix.begin() + size_t(fv_matrix.size() / nozzle_nums * (extruder_id + 1) + EPSILON)); + } + return fv_matrix; } - else { - out_matrix = std::vector(fv_matrix.begin(), fv_matrix.end()); + + // std::vector &out_matrix: + // const std::vector &fv_matrix: the matrix of one nozzle + // size_t extruder_id: -1 means single-nozzle for old file, 0 means the 1st extruder, 1 means the 2nd extruder + template + static void set_flush_volumes_matrix(std::vector &out_matrix, const std::vector &fv_matrix, size_t extruder_id = -1, size_t nozzle_nums = 1) + { + bool is_multi_extruder = false; + if (extruder_id != -1 && nozzle_nums != 1) + { + std::copy(fv_matrix.begin(), fv_matrix.end(), out_matrix.begin() + size_t(out_matrix.size() / nozzle_nums * extruder_id + EPSILON)); + } + else + { + out_matrix = std::vector(fv_matrix.begin(), fv_matrix.end()); + } } -} -size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id); -size_t get_config_idx_for_filament(const GCodeConfig& config, unsigned int filament_id); + size_t get_extruder_index(const GCodeConfig &config, unsigned int filament_id); + size_t get_config_idx_for_filament(const GCodeConfig &config, unsigned int filament_id); } // namespace Slic3r // Serialization through the Cereal library -namespace cereal { +namespace cereal +{ // Let cereal know that there are load / save non-member functions declared for DynamicPrintConfig, ignore serialize / load / save from parent class DynamicConfig. - template struct specialize {}; + template + struct specialize + { + }; - template void load(Archive& archive, Slic3r::DynamicPrintConfig &config) + template + void load(Archive &archive, Slic3r::DynamicPrintConfig &config) { size_t cnt; archive(cnt); config.clear(); - for (size_t i = 0; i < cnt; ++ i) { + for (size_t i = 0; i < cnt; ++i) + { size_t serialization_key_ordinal; archive(serialization_key_ordinal); assert(serialization_key_ordinal > 0); @@ -1798,12 +1552,14 @@ namespace cereal { } } - template void save(Archive& archive, const Slic3r::DynamicPrintConfig &config) + template + void save(Archive &archive, const Slic3r::DynamicPrintConfig &config) { size_t cnt = config.size(); archive(cnt); - for (auto it = config.cbegin(); it != config.cend(); ++it) { - const Slic3r::ConfigOptionDef* optdef = Slic3r::print_config_def.get(it->first); + for (auto it = config.cbegin(); it != config.cend(); ++it) + { + const Slic3r::ConfigOptionDef *optdef = Slic3r::print_config_def.get(it->first); assert(optdef != nullptr); assert(optdef->serialization_key_ordinal > 0); archive(optdef->serialization_key_ordinal); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index bb9073ded6..df0a7b733d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1,5 +1,5 @@ // #include "libslic3r/GCodeSender.hpp" -//#include "slic3r/Utils/Serial.hpp" +// #include "slic3r/Utils/Serial.hpp" #include "Tab.hpp" #include "PresetHints.hpp" #include "libslic3r/PresetBundle.hpp" @@ -48,165 +48,190 @@ #include "DeviceCore/DevManager.h" #ifdef WIN32 - #include +#include #endif // WIN32 -namespace Slic3r { +namespace Slic3r +{ -t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other, bool strict = true); + t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other, bool strict = true); -namespace GUI { + namespace GUI + { #define DISABLE_UNDO_SYS -static const std::vector plate_keys = { "curr_bed_type", "first_layer_print_sequence", "first_layer_sequence_choice", "other_layers_print_sequence", "other_layers_sequence_choice", "print_sequence", "spiral_mode"}; + static const std::vector plate_keys = {"curr_bed_type", "first_layer_print_sequence", "first_layer_sequence_choice", "other_layers_print_sequence", "other_layers_sequence_choice", "print_sequence", "spiral_mode"}; -static std::pair extruder_variant_keys[]{ - {}, {"print_extruder_id", "print_extruder_variant"}, // Preset::TYPE_PRINT - {}, {"", "filament_extruder_variant"}, // Preset::TYPE_FILAMENT filament don't use id anymore - {}, {"printer_extruder_id", "printer_extruder_variant"}, // Preset::TYPE_PRINTER -}; + static std::pair extruder_variant_keys[]{ + {}, + {"print_extruder_id", "print_extruder_variant"}, // Preset::TYPE_PRINT + {}, + {"", "filament_extruder_variant"}, // Preset::TYPE_FILAMENT filament don't use id anymore + {}, + {"printer_extruder_id", "printer_extruder_variant"}, // Preset::TYPE_PRINTER + }; -void Tab::Highlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) -{ - m_timer.SetOwner(owner, timerid); -} + void Tab::Highlighter::set_timer_owner(wxEvtHandler *owner, int timerid /* = wxID_ANY*/) + { + m_timer.SetOwner(owner, timerid); + } -void Tab::Highlighter::init(std::pair params) -{ - if (m_timer.IsRunning()) - invalidate(); - if (!params.first || !params.second) - return; + void Tab::Highlighter::init(std::pair params) + { + if (m_timer.IsRunning()) + invalidate(); + if (!params.first || !params.second) + return; - m_timer.Start(300, false); + m_timer.Start(300, false); - m_custom_ctrl = params.first; - m_show_blink_ptr = params.second; + m_custom_ctrl = params.first; + m_show_blink_ptr = params.second; - *m_show_blink_ptr = true; - m_custom_ctrl->Refresh(); -} + *m_show_blink_ptr = true; + m_custom_ctrl->Refresh(); + } -void Tab::Highlighter::invalidate() -{ - m_timer.Stop(); + void Tab::Highlighter::invalidate() + { + m_timer.Stop(); - if (m_custom_ctrl && m_show_blink_ptr) { - *m_show_blink_ptr = false; - m_custom_ctrl->Refresh(); - m_show_blink_ptr = nullptr; - m_custom_ctrl = nullptr; - } + if (m_custom_ctrl && m_show_blink_ptr) + { + *m_show_blink_ptr = false; + m_custom_ctrl->Refresh(); + m_show_blink_ptr = nullptr; + m_custom_ctrl = nullptr; + } - m_blink_counter = 0; -} + m_blink_counter = 0; + } -void Tab::Highlighter::blink() -{ - if (m_custom_ctrl && m_show_blink_ptr) { - *m_show_blink_ptr = !*m_show_blink_ptr; - m_custom_ctrl->Refresh(); - } - else - return; + void Tab::Highlighter::blink() + { + if (m_custom_ctrl && m_show_blink_ptr) + { + *m_show_blink_ptr = !*m_show_blink_ptr; + m_custom_ctrl->Refresh(); + } + else + return; - if ((++m_blink_counter) == 11) - invalidate(); -} + if ((++m_blink_counter) == 11) + invalidate(); + } -//BBS: GUI refactor -Tab::Tab(ParamsPanel* parent, const wxString& title, Preset::Type type) : - m_parent(parent), m_title(title), m_type(type) -{ - Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL/*, name*/); - this->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + // BBS: GUI refactor + Tab::Tab(ParamsPanel *parent, const wxString &title, Preset::Type type) : m_parent(parent), m_title(title), m_type(type) + { + Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL /*, name*/); + this->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - wxGetApp().UpdateDarkUI(this); - SetBackgroundColour(*wxWHITE); + wxGetApp().UpdateDarkUI(this); + SetBackgroundColour(*wxWHITE); - m_compatible_printers.type = Preset::TYPE_PRINTER; - m_compatible_printers.key_list = "compatible_printers"; - m_compatible_printers.key_condition = "compatible_printers_condition"; - //m_compatible_printers.dialog_title = _L("Compatible printers"); - //m_compatible_printers.dialog_label = _L("Select the printers this profile is compatible with."); + m_compatible_printers.type = Preset::TYPE_PRINTER; + m_compatible_printers.key_list = "compatible_printers"; + m_compatible_printers.key_condition = "compatible_printers_condition"; + // m_compatible_printers.dialog_title = _L("Compatible printers"); + // m_compatible_printers.dialog_label = _L("Select the printers this profile is compatible with."); - m_compatible_prints.type = Preset::TYPE_PRINT; - m_compatible_prints.key_list = "compatible_prints"; - m_compatible_prints.key_condition = "compatible_prints_condition"; - //m_compatible_prints.dialog_title = _L("Compatible print profiles"); - //m_compatible_prints.dialog_label = _L("Select the print profiles this profile is compatible with."); + m_compatible_prints.type = Preset::TYPE_PRINT; + m_compatible_prints.key_list = "compatible_prints"; + m_compatible_prints.key_condition = "compatible_prints_condition"; + // m_compatible_prints.dialog_title = _L("Compatible print profiles"); + // m_compatible_prints.dialog_label = _L("Select the print profiles this profile is compatible with."); - wxGetApp().tabs_list.push_back(this); + wxGetApp().tabs_list.push_back(this); - m_em_unit = em_unit(m_parent); //wxGetApp().em_unit(); + m_em_unit = em_unit(m_parent); // wxGetApp().em_unit(); - m_config_manipulation = get_config_manipulation(); + m_config_manipulation = get_config_manipulation(); - Bind(wxEVT_SIZE, ([](wxSizeEvent &evt) { + Bind(wxEVT_SIZE, ([](wxSizeEvent &evt) + { //for (auto page : m_pages) // if (! page.get()->IsShown()) // page->layout_valid = false; - evt.Skip(); - })); + evt.Skip(); })); - m_highlighter.set_timer_owner(this, 0); - this->Bind(wxEVT_TIMER, [this](wxTimerEvent&) - { - m_highlighter.blink(); - }); -} + m_highlighter.set_timer_owner(this, 0); + this->Bind(wxEVT_TIMER, [this](wxTimerEvent &) + { m_highlighter.blink(); }); + } -void Tab::set_type() -{ - if (m_name == PRESET_PRINT_NAME) { m_type = Slic3r::Preset::TYPE_PRINT; } - else if (m_name == "sla_print") { m_type = Slic3r::Preset::TYPE_SLA_PRINT; } - else if (m_name == PRESET_FILAMENT_NAME) { m_type = Slic3r::Preset::TYPE_FILAMENT; } - else if (m_name == "sla_material") { m_type = Slic3r::Preset::TYPE_SLA_MATERIAL; } - else if (m_name == PRESET_PRINTER_NAME) { m_type = Slic3r::Preset::TYPE_PRINTER; } - else { m_type = Slic3r::Preset::TYPE_INVALID; assert(false); } -} - -// sub new -//BBS: GUI refactor, change tab to fit into ParamsPanel -void Tab::create_preset_tab() -{ -//move to ParamsPanel -/*#ifdef __WINDOWS__ - SetDoubleBuffered(true); -#endif //__WINDOWS__*/ - auto panel = this; - - m_preset_bundle = wxGetApp().preset_bundle; - - // Vertical sizer to hold the choice menu and the rest of the page. -/*#ifdef __WXOSX__ - auto *main_sizer = new wxBoxSizer(wxVERTICAL); - main_sizer->SetSizeHints(this); - this->SetSizer(main_sizer); - - // Create additional panel to Fit() it from OnActivate() - // It's needed for tooltip showing on OSX - m_tmp_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); - auto panel = m_tmp_panel; - auto sizer = new wxBoxSizer(wxVERTICAL); - m_tmp_panel->SetSizer(sizer); - m_tmp_panel->Layout(); - - main_sizer->Add(m_tmp_panel, 1, wxEXPAND | wxALL, 0); -#else - Tab *panel = this; - auto *sizer = new wxBoxSizer(wxVERTICAL); - sizer->SetSizeHints(panel); - panel->SetSizer(sizer); -#endif //__WXOSX__*/ - - // BBS: model config - if (m_type < Preset::TYPE_COUNT) { - // preset chooser - m_presets_choice = new TabPresetComboBox(panel, m_type); - // m_presets_choice->SetFont(Label::Body_10); // BBS - m_presets_choice->set_selection_changed_function([this](int selection) { + void Tab::set_type() + { + if (m_name == PRESET_PRINT_NAME) + { + m_type = Slic3r::Preset::TYPE_PRINT; + } + else if (m_name == "sla_print") + { + m_type = Slic3r::Preset::TYPE_SLA_PRINT; + } + else if (m_name == PRESET_FILAMENT_NAME) + { + m_type = Slic3r::Preset::TYPE_FILAMENT; + } + else if (m_name == "sla_material") + { + m_type = Slic3r::Preset::TYPE_SLA_MATERIAL; + } + else if (m_name == PRESET_PRINTER_NAME) + { + m_type = Slic3r::Preset::TYPE_PRINTER; + } + else + { + m_type = Slic3r::Preset::TYPE_INVALID; + assert(false); + } + } + + // sub new + // BBS: GUI refactor, change tab to fit into ParamsPanel + void Tab::create_preset_tab() + { + // move to ParamsPanel + /*#ifdef __WINDOWS__ + SetDoubleBuffered(true); + #endif //__WINDOWS__*/ + auto panel = this; + + m_preset_bundle = wxGetApp().preset_bundle; + + // Vertical sizer to hold the choice menu and the rest of the page. + /*#ifdef __WXOSX__ + auto *main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->SetSizeHints(this); + this->SetSizer(main_sizer); + + // Create additional panel to Fit() it from OnActivate() + // It's needed for tooltip showing on OSX + m_tmp_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + auto panel = m_tmp_panel; + auto sizer = new wxBoxSizer(wxVERTICAL); + m_tmp_panel->SetSizer(sizer); + m_tmp_panel->Layout(); + + main_sizer->Add(m_tmp_panel, 1, wxEXPAND | wxALL, 0); + #else + Tab *panel = this; + auto *sizer = new wxBoxSizer(wxVERTICAL); + sizer->SetSizeHints(panel); + panel->SetSizer(sizer); + #endif //__WXOSX__*/ + + // BBS: model config + if (m_type < Preset::TYPE_COUNT) + { + // preset chooser + m_presets_choice = new TabPresetComboBox(panel, m_type); + // m_presets_choice->SetFont(Label::Body_10); // BBS + m_presets_choice->set_selection_changed_function([this](int selection) + { if (!m_presets_choice->selection_is_changed_according_to_physical_printers()) { if (m_type == Preset::TYPE_PRINTER && !m_presets_choice->is_selected_physical_printer()) @@ -215,91 +240,87 @@ void Tab::create_preset_tab() // select preset std::string preset_name = m_presets_choice->GetString(selection).ToUTF8().data(); select_preset(Preset::remove_suffix_modified(preset_name)); + } }); } - }); - } - - auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - - //buttons - m_scaled_buttons.reserve(6); - m_scaled_bitmaps.reserve(4); - - m_top_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); - // BBS: open this tab by select first - m_top_panel->SetBackgroundColour(*wxWHITE); - m_top_panel->Bind(wxEVT_LEFT_UP, [this](auto & e) { - restore_last_select_item(); - }); - - //add_scaled_button(panel, &m_btn_compare_preset, "compare"); - add_scaled_button(m_top_panel, &m_btn_save_preset, "save"); - add_scaled_button(m_top_panel, &m_btn_delete_preset, "cross"); - //if (m_type == Preset::Type::TYPE_PRINTER) - // add_scaled_button(panel, &m_btn_edit_ph_printer, "cog"); - - m_show_incompatible_presets = false; - add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); - add_scaled_bitmap(this, m_bmp_hide_incompatible_presets, "flag_green"); - - //add_scaled_button(panel, &m_btn_hide_incompatible_presets, m_bmp_hide_incompatible_presets.name()); - - //m_btn_compare_preset->SetToolTip(_L("Compare presets")); - // TRN "Save current Settings" - m_btn_save_preset->SetToolTip(wxString::Format(_L("Save current %s"), m_title)); - m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); - m_btn_delete_preset->Hide(); - - /*add_scaled_button(panel, &m_question_btn, "question"); - m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" - "or click this button."))); - - add_scaled_button(panel, &m_search_btn, "search"); - m_search_btn->SetToolTip(format_wxstr(_L("Search in settings [%1%]"), "Ctrl+F"));*/ - - // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - add_scaled_bitmap(this, m_bmp_value_lock , "unlock_normal"); - add_scaled_bitmap(this, m_bmp_value_unlock, "lock_normal"); - m_bmp_non_system = &m_bmp_white_bullet; - // Bitmaps to be shown on the "Undo user changes" button next to each input field. - add_scaled_bitmap(this, m_bmp_value_revert, "undo"); - add_scaled_bitmap(this, m_bmp_white_bullet, "dot"); - - set_tooltips_text(); - - add_scaled_button(m_top_panel, &m_undo_btn, m_bmp_white_bullet.name()); - add_scaled_button(m_top_panel, &m_undo_to_sys_btn, m_bmp_white_bullet.name()); - add_scaled_button(m_top_panel, &m_btn_search, "search"); - m_btn_search->SetToolTip(_L("Search in preset")); - - //search input - m_search_item = new StaticBox(m_top_panel); - StateColor box_colour(std::pair(*wxWHITE, StateColor::Normal)); - StateColor box_border_colour(std::pair(wxColour(238, 238, 238), StateColor::Normal)); - - m_search_item->SetBackgroundColor(box_colour); - m_search_item->SetBorderColor(box_border_colour); - m_search_item->SetCornerRadius(5); + auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - //StateColor::darkModeColorFor(wxColour(238, 238, 238)), wxDefaultPosition, wxSize(m_top_panel->GetSize().GetWidth(), 3 * wxGetApp().em_unit()), 8); - auto search_sizer = new wxBoxSizer(wxHORIZONTAL); - m_search_input = new TextInput(m_search_item, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 | wxBORDER_NONE); - m_search_input->SetBackgroundColour(wxColour(238, 238, 238)); - m_search_input->SetForegroundColour(wxColour(43, 52, 54)); - m_search_input->SetFont(wxGetApp().bold_font()); + // buttons + m_scaled_buttons.reserve(6); + m_scaled_bitmaps.reserve(4); - search_sizer->Add(new wxWindow(m_search_item, wxID_ANY, wxDefaultPosition, wxSize(0, 0)), 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(6)); - search_sizer->Add(m_search_input, 1, wxEXPAND | wxALL, FromDIP(2)); - //bbl for linux - //search_sizer->Add(new wxWindow(m_search_input, wxID_ANY, wxDefaultPosition, wxSize(0, 0)), 0, wxEXPAND | wxLEFT, 16); + m_top_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); + // BBS: open this tab by select first + m_top_panel->SetBackgroundColour(*wxWHITE); + m_top_panel->Bind(wxEVT_LEFT_UP, [this](auto &e) + { restore_last_select_item(); }); + // add_scaled_button(panel, &m_btn_compare_preset, "compare"); + add_scaled_button(m_top_panel, &m_btn_save_preset, "save"); + add_scaled_button(m_top_panel, &m_btn_delete_preset, "cross"); + // if (m_type == Preset::Type::TYPE_PRINTER) + // add_scaled_button(panel, &m_btn_edit_ph_printer, "cog"); - m_search_item->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { - m_search_input->SetFocus(); - }); - - m_search_input->Bind(wxCUSTOMEVT_EXIT_SEARCH, [this](wxCommandEvent &) { + m_show_incompatible_presets = false; + add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); + add_scaled_bitmap(this, m_bmp_hide_incompatible_presets, "flag_green"); + + // add_scaled_button(panel, &m_btn_hide_incompatible_presets, m_bmp_hide_incompatible_presets.name()); + + // m_btn_compare_preset->SetToolTip(_L("Compare presets")); + // TRN "Save current Settings" + m_btn_save_preset->SetToolTip(wxString::Format(_L("Save current %s"), m_title)); + m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); + m_btn_delete_preset->Hide(); + + /*add_scaled_button(panel, &m_question_btn, "question"); + m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" + "or click this button."))); + + add_scaled_button(panel, &m_search_btn, "search"); + m_search_btn->SetToolTip(format_wxstr(_L("Search in settings [%1%]"), "Ctrl+F"));*/ + + // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + add_scaled_bitmap(this, m_bmp_value_lock, "unlock_normal"); + add_scaled_bitmap(this, m_bmp_value_unlock, "lock_normal"); + m_bmp_non_system = &m_bmp_white_bullet; + // Bitmaps to be shown on the "Undo user changes" button next to each input field. + add_scaled_bitmap(this, m_bmp_value_revert, "undo"); + add_scaled_bitmap(this, m_bmp_white_bullet, "dot"); + + set_tooltips_text(); + + add_scaled_button(m_top_panel, &m_undo_btn, m_bmp_white_bullet.name()); + add_scaled_button(m_top_panel, &m_undo_to_sys_btn, m_bmp_white_bullet.name()); + add_scaled_button(m_top_panel, &m_btn_search, "search"); + m_btn_search->SetToolTip(_L("Search in preset")); + + // search input + m_search_item = new StaticBox(m_top_panel); + StateColor box_colour(std::pair(*wxWHITE, StateColor::Normal)); + StateColor box_border_colour(std::pair(wxColour(238, 238, 238), StateColor::Normal)); + + m_search_item->SetBackgroundColor(box_colour); + m_search_item->SetBorderColor(box_border_colour); + m_search_item->SetCornerRadius(5); + + // StateColor::darkModeColorFor(wxColour(238, 238, 238)), wxDefaultPosition, wxSize(m_top_panel->GetSize().GetWidth(), 3 * wxGetApp().em_unit()), 8); + auto search_sizer = new wxBoxSizer(wxHORIZONTAL); + m_search_input = new TextInput(m_search_item, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 | wxBORDER_NONE); + m_search_input->SetBackgroundColour(wxColour(238, 238, 238)); + m_search_input->SetForegroundColour(wxColour(43, 52, 54)); + m_search_input->SetFont(wxGetApp().bold_font()); + + search_sizer->Add(new wxWindow(m_search_item, wxID_ANY, wxDefaultPosition, wxSize(0, 0)), 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(6)); + search_sizer->Add(m_search_input, 1, wxEXPAND | wxALL, FromDIP(2)); + // bbl for linux + // search_sizer->Add(new wxWindow(m_search_input, wxID_ANY, wxDefaultPosition, wxSize(0, 0)), 0, wxEXPAND | wxLEFT, 16); + + m_search_item->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) + { m_search_input->SetFocus(); }); + + m_search_input->Bind(wxCUSTOMEVT_EXIT_SEARCH, [this](wxCommandEvent &) + { Freeze(); if (m_presets_choice) m_presets_choice->Show(); @@ -314,89 +335,93 @@ void Tab::create_preset_tab() this->GetParent()->Refresh(); this->GetParent()->Update(); this->GetParent()->Layout(); - Thaw(); - }); - - m_search_item->SetSizer(search_sizer); - m_search_item->Layout(); - search_sizer->Fit(m_search_item); - - m_search_item->Hide(); - //m_btn_search->SetId(wxID_FIND_PROCESS); - - m_btn_search->Bind( - wxEVT_BUTTON, - [this](wxCommandEvent &) { - Freeze(); - if (m_presets_choice) - m_presets_choice->Hide(); - - m_btn_save_preset->Hide(); - m_btn_search->Hide(); - m_search_item->Show(); - - this->GetParent()->Refresh(); - this->GetParent()->Update(); - this->GetParent()->Layout(); - - wxGetApp().plater()->search(false, m_type, m_top_panel->GetParent(), m_search_input, m_btn_search); - Thaw(); - - }); - - m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(); })); - m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(true); })); - /* m_search_btn->Bind(wxEVT_BUTTON, [](wxCommandEvent) { wxGetApp().plater()->search(false); });*/ - - // Colors for ui "decoration" - m_sys_label_clr = wxGetApp().get_label_clr_sys(); - m_modified_label_clr = wxGetApp().get_label_clr_modified(); - m_default_text_clr = wxGetApp().get_label_clr_default(); - - m_main_sizer = new wxBoxSizer( wxVERTICAL ); - m_top_sizer = new wxBoxSizer( wxHORIZONTAL ); - - m_top_sizer->Add(m_undo_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); - // BBS: model config - if (m_presets_choice) { - m_presets_choice->Reparent(m_top_panel); - m_top_sizer->Add(m_presets_choice, 1, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); - } else { - m_top_sizer->AddSpacer(FromDIP(10)); - m_top_sizer->AddStretchSpacer(1); - } + Thaw(); }); + + m_search_item->SetSizer(search_sizer); + m_search_item->Layout(); + search_sizer->Fit(m_search_item); + + m_search_item->Hide(); + // m_btn_search->SetId(wxID_FIND_PROCESS); + + m_btn_search->Bind( + wxEVT_BUTTON, + [this](wxCommandEvent &) + { + Freeze(); + if (m_presets_choice) + m_presets_choice->Hide(); + + m_btn_save_preset->Hide(); + m_btn_search->Hide(); + m_search_item->Show(); + + this->GetParent()->Refresh(); + this->GetParent()->Update(); + this->GetParent()->Layout(); + + wxGetApp().plater()->search(false, m_type, m_top_panel->GetParent(), m_search_input, m_btn_search); + Thaw(); + }); + + m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) + { on_roll_back_value(); })); + m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) + { on_roll_back_value(true); })); + /* m_search_btn->Bind(wxEVT_BUTTON, [](wxCommandEvent) { wxGetApp().plater()->search(false); });*/ + + // Colors for ui "decoration" + m_sys_label_clr = wxGetApp().get_label_clr_sys(); + m_modified_label_clr = wxGetApp().get_label_clr_modified(); + m_default_text_clr = wxGetApp().get_label_clr_default(); + + m_main_sizer = new wxBoxSizer(wxVERTICAL); + m_top_sizer = new wxBoxSizer(wxHORIZONTAL); + + m_top_sizer->Add(m_undo_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + // BBS: model config + if (m_presets_choice) + { + m_presets_choice->Reparent(m_top_panel); + m_top_sizer->Add(m_presets_choice, 1, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + } + else + { + m_top_sizer->AddSpacer(FromDIP(10)); + m_top_sizer->AddStretchSpacer(1); + } - const float scale_factor = /*wxGetApp().*/em_unit(this)*0.1;// GetContentScaleFactor(); + const float scale_factor = /*wxGetApp().*/ em_unit(this) * 0.1; // GetContentScaleFactor(); #ifndef DISABLE_UNDO_SYS - m_top_sizer->Add( m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); - m_top_sizer->AddSpacer(8); + m_top_sizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); + m_top_sizer->AddSpacer(8); #endif - m_top_sizer->Add( m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(12)); - m_top_sizer->Add( m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(12) ); - m_top_sizer->Add( m_btn_search, 0, wxALIGN_CENTER_VERTICAL | wxLEFT , FromDIP(12) ); - m_top_sizer->Add(m_search_item, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, FromDIP(12)); - - if (dynamic_cast(this) == nullptr) { - m_static_title = new Label(m_top_panel, Label::Body_12, _L("Advance")); - m_static_title->Wrap( -1 ); - // BBS: open this tab by select first - m_static_title->Bind(wxEVT_LEFT_UP, [this](auto& e) { - restore_last_select_item(); - }); - m_top_sizer->Add( m_static_title, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 8 ); - m_mode_view = new SwitchButton(m_top_panel, wxID_ABOUT); - m_top_sizer->AddSpacer(4); - m_top_sizer->Add( m_mode_view, 0, wxALIGN_CENTER_VERTICAL); - } + m_top_sizer->Add(m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(12)); + m_top_sizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(12)); + m_top_sizer->Add(m_btn_search, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(12)); + m_top_sizer->Add(m_search_item, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, FromDIP(12)); + + if (dynamic_cast(this) == nullptr) + { + m_static_title = new Label(m_top_panel, Label::Body_12, _L("Advance")); + m_static_title->Wrap(-1); + // BBS: open this tab by select first + m_static_title->Bind(wxEVT_LEFT_UP, [this](auto &e) + { restore_last_select_item(); }); + m_top_sizer->Add(m_static_title, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 8); + m_mode_view = new SwitchButton(m_top_panel, wxID_ABOUT); + m_top_sizer->AddSpacer(4); + m_top_sizer->Add(m_mode_view, 0, wxALIGN_CENTER_VERTICAL); + } - m_top_sizer->AddSpacer(FromDIP(16)); + m_top_sizer->AddSpacer(FromDIP(16)); - m_top_sizer->SetMinSize(-1, 3 * m_em_unit); - m_top_panel->SetSizer(m_top_sizer); - if (m_presets_choice) - m_main_sizer->Add(m_top_panel, 0, wxEXPAND | wxUP | wxDOWN, m_em_unit); - else - m_top_panel->Hide(); + m_top_sizer->SetMinSize(-1, 3 * m_em_unit); + m_top_panel->SetSizer(m_top_sizer); + if (m_presets_choice) + m_main_sizer->Add(m_top_panel, 0, wxEXPAND | wxUP | wxDOWN, m_em_unit); + else + m_top_panel->Hide(); #if 0 #ifdef _MSW_DARK_MODE @@ -448,24 +473,25 @@ void Tab::create_preset_tab() m_left_sizer = new wxBoxSizer(wxVERTICAL); m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); #endif - // tree - m_tabctrl = new TabCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(20 * m_em_unit, -1), - wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_NONE | wxWANTS_CHARS | wxTR_FULL_ROW_HIGHLIGHT); - m_tabctrl->Bind(wxEVT_RIGHT_DOWN, [this](auto &e) {}); // disable right select - m_tabctrl->SetFont(Label::Body_14); - //m_left_sizer->Add(m_tabctrl, 1, wxEXPAND); - const int img_sz = int(32 * scale_factor + 0.5f); - m_icons = new wxImageList(img_sz, img_sz, false, 1); - // Index of the last icon inserted into $self->{icons}. - m_icon_count = -1; - m_tabctrl->AssignImageList(m_icons); - wxGetApp().UpdateDarkUI(m_tabctrl); - - // Delay processing of the following handler until the message queue is flushed. - // This helps to process all the cursor key events on Windows in the tree control, - // so that the cursor jumps to the last item. - // BBS: bold selection - m_tabctrl->Bind(wxEVT_TAB_SEL_CHANGING, [this](wxCommandEvent& event) { + // tree + m_tabctrl = new TabCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(20 * m_em_unit, -1), + wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_NONE | wxWANTS_CHARS | wxTR_FULL_ROW_HIGHLIGHT); + m_tabctrl->Bind(wxEVT_RIGHT_DOWN, [this](auto &e) {}); // disable right select + m_tabctrl->SetFont(Label::Body_14); + // m_left_sizer->Add(m_tabctrl, 1, wxEXPAND); + const int img_sz = int(32 * scale_factor + 0.5f); + m_icons = new wxImageList(img_sz, img_sz, false, 1); + // Index of the last icon inserted into $self->{icons}. + m_icon_count = -1; + m_tabctrl->AssignImageList(m_icons); + wxGetApp().UpdateDarkUI(m_tabctrl); + + // Delay processing of the following handler until the message queue is flushed. + // This helps to process all the cursor key events on Windows in the tree control, + // so that the cursor jumps to the last item. + // BBS: bold selection + m_tabctrl->Bind(wxEVT_TAB_SEL_CHANGING, [this](wxCommandEvent &event) + { if (m_disable_tree_sel_changed_event) return; const auto sel_item = m_tabctrl->GetSelection(); @@ -474,9 +500,9 @@ void Tab::create_preset_tab() //const auto selection = sel_item >= 0 ? m_tabctrl->GetItemText(sel_item) : ""; //OutputDebugString(selection); //OutputDebugStringA("\n"); - m_tabctrl->SetItemBold(sel_item, false); - }); - m_tabctrl->Bind(wxEVT_TAB_SEL_CHANGED, [this](wxCommandEvent& event) { + m_tabctrl->SetItemBold(sel_item, false); }); + m_tabctrl->Bind(wxEVT_TAB_SEL_CHANGED, [this](wxCommandEvent &event) + { #ifdef __linux__ // Events queue is opposite On Linux. wxEVT_SET_FOCUS invokes after wxEVT_TAB_SEL_CHANGED, // and a result wxEVT_KILL_FOCUS doesn't invoke for the TextCtrls. @@ -494,17 +520,18 @@ void Tab::create_preset_tab() } while (this->tree_sel_change_delayed(event)); m_page_switch_running = false; } - } - }); + } }); - m_tabctrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); + m_tabctrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); - m_main_sizer->Add(m_tabctrl, 0, wxEXPAND | wxALL, 0 ); + m_main_sizer->Add(m_tabctrl, 0, wxEXPAND | wxALL, 0); - if (dynamic_cast(this) || dynamic_cast(this)) { - m_extruder_switch = new MultiSwitchButton(panel); - m_extruder_switch->SetMaxSize({em_unit(this) * 40, -1}); - m_extruder_switch->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](auto &evt) { + if (dynamic_cast(this) || dynamic_cast(this)) + { + m_extruder_switch = new MultiSwitchButton(panel); + m_extruder_switch->SetMaxSize({em_unit(this) * 40, -1}); + m_extruder_switch->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](auto &evt) + { evt.Skip(); int selection = evt.GetInt(); @@ -516,1187 +543,1302 @@ void Tab::create_preset_tab() if (extruder_id >= 0 && extruder_id < m_preset_bundle->get_printer_extruder_count()) m_actual_nozzle_volumes[extruder_id] = nozzle_type; - switch_excluder(extruder_id); - }); - m_extruder_sync_box = new wxPanel(panel, wxID_ANY); - m_extruder_sync_box->SetBackgroundColour(panel->GetBackgroundColour()); - m_extruder_sync_box->SetToolTip(_L("Synchronization of different extruder drives or nozzle volume types is not supported.")); - m_extruder_sync = new ScalableButton(m_extruder_sync_box, wxID_ANY, "extruder_sync"); - m_extruder_sync->SetToolTip(_L("Synchronize the modification of parameters to the corresponding parameters of another extruder.")); - m_extruder_sync->Bind(wxEVT_BUTTON, [this](auto &evt) { + switch_excluder(extruder_id); }); + m_extruder_sync_box = new wxPanel(panel, wxID_ANY); + m_extruder_sync_box->SetBackgroundColour(panel->GetBackgroundColour()); + m_extruder_sync_box->SetToolTip(_L("Synchronization of different extruder drives or nozzle volume types is not supported.")); + m_extruder_sync = new ScalableButton(m_extruder_sync_box, wxID_ANY, "extruder_sync"); + m_extruder_sync->SetToolTip(_L("Synchronize the modification of parameters to the corresponding parameters of another extruder.")); + m_extruder_sync->Bind(wxEVT_BUTTON, [this](auto &evt) + { evt.Skip(); - sync_excluder(); - }); - - auto sync_box_sizer = new wxBoxSizer(wxHORIZONTAL); - sync_box_sizer->Add(m_extruder_sync, 1, wxEXPAND); - m_extruder_sync_box->SetSizer(sync_box_sizer); - - m_variant_sizer = new wxBoxSizer(wxHORIZONTAL); - auto right_sizer = new wxBoxSizer(wxHORIZONTAL); - - m_variant_sizer->AddStretchSpacer(1); - m_variant_sizer->Add(m_extruder_switch, 0, wxALIGN_CENTER, 0); - m_variant_sizer->Add(right_sizer, 1, wxALIGN_CENTER); - right_sizer->AddStretchSpacer(1); - right_sizer->Add(m_extruder_sync_box, 0, wxALIGN_CENTER | wxRIGHT, m_em_unit); - - m_main_sizer->Add(m_variant_sizer, 0, wxEXPAND | wxTOP, m_em_unit); - } else if (dynamic_cast(this)) { - m_variant_combo = new MultiSwitchButton(panel); - m_variant_combo->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](auto &evt) { - evt.Skip(); - switch_excluder(evt.GetInt()); - }); - - wxBoxSizer *wiki_sizer = new wxBoxSizer(wxHORIZONTAL); - auto wiki_icon = new ScalableBitmap(panel, "wiki", 16); - auto wiki_icon_hover = new ScalableBitmap(panel, "wiki_hover", 16); - m_wiki_bmp = new wxStaticBitmap(panel, wxID_ANY, wiki_icon->bmp()); - wiki_sizer->Add(m_wiki_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxBOTTOM, 2); - m_wiki_label = new wxStaticText(panel, wxID_ANY, _L("Wiki")); - m_wiki_label->SetForegroundColour(wxColour("#6B6B6B")); - m_wiki_label->SetFont(Label::Body_13); - m_wiki_label->SetToolTip(_L("Click to learn more")); - m_wiki_label->Hide(); - m_wiki_bmp->Hide(); - wiki_sizer->Add(m_wiki_label, 0, wxALIGN_CENTER_VERTICAL); - auto set_hover = [this, wiki_icon, wiki_icon_hover](bool hover) { - wxColour color = hover ? wxColour("#00AE42") : wxColour("#6B6B6B"); - m_wiki_bmp->SetBitmap(hover ? wiki_icon_hover->bmp() : wiki_icon->bmp()); - m_wiki_label->SetForegroundColour(color); - m_wiki_label->SetFont(hover ? Label::Body_13.Underlined() : Label::Body_13); - m_wiki_label->SetCursor(hover ? wxCURSOR_HAND : wxCURSOR_ARROW); - }; - auto open_wiki = [](wxMouseEvent&) { - wxLaunchDefaultBrowser("https://wiki.bambulab.com"); - }; - m_wiki_label->Bind(wxEVT_LEFT_DOWN, open_wiki); - m_wiki_bmp->Bind(wxEVT_ENTER_WINDOW, [set_hover](wxMouseEvent &) { set_hover(true); }); - m_wiki_label->Bind(wxEVT_ENTER_WINDOW, [set_hover](wxMouseEvent &) { set_hover(true); }); - m_wiki_bmp->Bind(wxEVT_LEAVE_WINDOW, [set_hover](wxMouseEvent &) { set_hover(false); }); - m_wiki_label->Bind(wxEVT_LEAVE_WINDOW, [set_hover](wxMouseEvent &) { set_hover(false); }); - - wxBoxSizer *combo_sizer = new wxBoxSizer(wxHORIZONTAL); - combo_sizer->Add(m_variant_combo, 1, wxEXPAND); - wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); - top_sizer->Add(combo_sizer, 1, wxEXPAND | wxLEFT, m_em_unit); - top_sizer->AddStretchSpacer(1); - top_sizer->Add(wiki_sizer, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, m_em_unit); - m_nozzle_status_sizer = new wxBoxSizer(wxHORIZONTAL); - m_variant_sizer = new wxBoxSizer(wxVERTICAL); - m_variant_sizer->Add(top_sizer, 0, wxLEFT, m_em_unit); - m_variant_sizer->Add(m_nozzle_status_sizer, 0, wxEXPAND | wxLEFT | wxTOP, m_em_unit * 0.8); - m_main_sizer->Add(m_variant_sizer, 0, wxEXPAND | wxTOP, m_em_unit); - } + sync_excluder(); }); - this->SetSizer(m_main_sizer); - //this->Layout(); - m_page_view = m_parent->get_paged_view(); + auto sync_box_sizer = new wxBoxSizer(wxHORIZONTAL); + sync_box_sizer->Add(m_extruder_sync, 1, wxEXPAND); + m_extruder_sync_box->SetSizer(sync_box_sizer); - // Initialize the page. -/*#ifdef __WXOSX__ - auto page_parent = m_tmp_panel; -#else - auto page_parent = this; -#endif + m_variant_sizer = new wxBoxSizer(wxHORIZONTAL); + auto right_sizer = new wxBoxSizer(wxHORIZONTAL); - m_page_view = new wxScrolledWindow(page_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - m_page_sizer = new wxBoxSizer(wxVERTICAL); - m_page_view->SetSizer(m_page_sizer); - m_page_view->SetScrollbars(1, 20, 1, 2); - m_hsizer->Add(m_page_view, 1, wxEXPAND | wxLEFT, 5);*/ - - //m_btn_compare_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { compare_preset(); })); - m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); - m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); - /*m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { - toggle_show_hide_incompatible(); - })); - - if (m_btn_edit_ph_printer) - m_btn_edit_ph_printer->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) { - if (m_preset_bundle->physical_printers.has_selection()) - m_presets_choice->edit_physical_printer(); - else - m_presets_choice->add_physical_printer(); - });*/ + m_variant_sizer->AddStretchSpacer(1); + m_variant_sizer->Add(m_extruder_switch, 0, wxALIGN_CENTER, 0); + m_variant_sizer->Add(right_sizer, 1, wxALIGN_CENTER); + right_sizer->AddStretchSpacer(1); + right_sizer->Add(m_extruder_sync_box, 0, wxALIGN_CENTER | wxRIGHT, m_em_unit); + + m_main_sizer->Add(m_variant_sizer, 0, wxEXPAND | wxTOP, m_em_unit); + } + else if (dynamic_cast(this)) + { + m_variant_combo = new MultiSwitchButton(panel); + m_variant_combo->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](auto &evt) + { + evt.Skip(); + switch_excluder(evt.GetInt()); }); + + wxBoxSizer *wiki_sizer = new wxBoxSizer(wxHORIZONTAL); + auto wiki_icon = new ScalableBitmap(panel, "wiki", 16); + auto wiki_icon_hover = new ScalableBitmap(panel, "wiki_hover", 16); + m_wiki_bmp = new wxStaticBitmap(panel, wxID_ANY, wiki_icon->bmp()); + wiki_sizer->Add(m_wiki_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxBOTTOM, 2); + m_wiki_label = new wxStaticText(panel, wxID_ANY, _L("Wiki")); + m_wiki_label->SetForegroundColour(wxColour("#6B6B6B")); + m_wiki_label->SetFont(Label::Body_13); + m_wiki_label->SetToolTip(_L("Click to learn more")); + m_wiki_label->Hide(); + m_wiki_bmp->Hide(); + wiki_sizer->Add(m_wiki_label, 0, wxALIGN_CENTER_VERTICAL); + auto set_hover = [this, wiki_icon, wiki_icon_hover](bool hover) + { + wxColour color = hover ? wxColour("#00AE42") : wxColour("#6B6B6B"); + m_wiki_bmp->SetBitmap(hover ? wiki_icon_hover->bmp() : wiki_icon->bmp()); + m_wiki_label->SetForegroundColour(color); + m_wiki_label->SetFont(hover ? Label::Body_13.Underlined() : Label::Body_13); + m_wiki_label->SetCursor(hover ? wxCURSOR_HAND : wxCURSOR_ARROW); + }; + auto open_wiki = [](wxMouseEvent &) + { + wxLaunchDefaultBrowser("https://wiki.bambulab.com"); + }; + m_wiki_label->Bind(wxEVT_LEFT_DOWN, open_wiki); + m_wiki_bmp->Bind(wxEVT_ENTER_WINDOW, [set_hover](wxMouseEvent &) + { set_hover(true); }); + m_wiki_label->Bind(wxEVT_ENTER_WINDOW, [set_hover](wxMouseEvent &) + { set_hover(true); }); + m_wiki_bmp->Bind(wxEVT_LEAVE_WINDOW, [set_hover](wxMouseEvent &) + { set_hover(false); }); + m_wiki_label->Bind(wxEVT_LEAVE_WINDOW, [set_hover](wxMouseEvent &) + { set_hover(false); }); + + wxBoxSizer *combo_sizer = new wxBoxSizer(wxHORIZONTAL); + combo_sizer->Add(m_variant_combo, 1, wxEXPAND); + wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); + top_sizer->Add(combo_sizer, 1, wxEXPAND | wxLEFT, m_em_unit); + top_sizer->AddStretchSpacer(1); + top_sizer->Add(wiki_sizer, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, m_em_unit); + m_nozzle_status_sizer = new wxBoxSizer(wxHORIZONTAL); + m_variant_sizer = new wxBoxSizer(wxVERTICAL); + m_variant_sizer->Add(top_sizer, 0, wxLEFT, m_em_unit); + m_variant_sizer->Add(m_nozzle_status_sizer, 0, wxEXPAND | wxLEFT | wxTOP, m_em_unit * 0.8); + m_main_sizer->Add(m_variant_sizer, 0, wxEXPAND | wxTOP, m_em_unit); + } - // Initialize the DynamicPrintConfig by default keys/values. - build(); + this->SetSizer(m_main_sizer); + // this->Layout(); + m_page_view = m_parent->get_paged_view(); + + // Initialize the page. + /*#ifdef __WXOSX__ + auto page_parent = m_tmp_panel; + #else + auto page_parent = this; + #endif + + m_page_view = new wxScrolledWindow(page_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_page_sizer = new wxBoxSizer(wxVERTICAL); + m_page_view->SetSizer(m_page_sizer); + m_page_view->SetScrollbars(1, 20, 1, 2); + m_hsizer->Add(m_page_view, 1, wxEXPAND | wxLEFT, 5);*/ + + // m_btn_compare_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { compare_preset(); })); + m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) + { save_preset(); })); + m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) + { delete_preset(); })); + /*m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { + toggle_show_hide_incompatible(); + })); + + if (m_btn_edit_ph_printer) + m_btn_edit_ph_printer->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) { + if (m_preset_bundle->physical_printers.has_selection()) + m_presets_choice->edit_physical_printer(); + else + m_presets_choice->add_physical_printer(); + });*/ - // ys_FIXME: Following should not be needed, the function will be called later - // (update_mode->update_visibility->rebuild_page_tree). This does not work, during the - // second call of rebuild_page_tree m_tabctrl->GetFirstVisibleItem(); returns zero - // for some unknown reason (and the page is not refreshed until user does a selection). - rebuild_page_tree(); + // Initialize the DynamicPrintConfig by default keys/values. + build(); - m_completed = true; -} + // ys_FIXME: Following should not be needed, the function will be called later + // (update_mode->update_visibility->rebuild_page_tree). This does not work, during the + // second call of rebuild_page_tree m_tabctrl->GetFirstVisibleItem(); returns zero + // for some unknown reason (and the page is not refreshed until user does a selection). + rebuild_page_tree(); -void Tab::parse_extruder_selection(int selection, int &extruder_id, NozzleVolumeType &nozzle_type) -{ - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - int extruder_nums = m_preset_bundle->get_printer_extruder_count(); + m_completed = true; + } - int current_index = 0; + void Tab::parse_extruder_selection(int selection, int &extruder_id, NozzleVolumeType &nozzle_type) + { + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + int extruder_nums = m_preset_bundle->get_printer_extruder_count(); - for (int i = 0; i < extruder_nums; ++i) { - NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]); + int current_index = 0; - if (volume_type == NozzleVolumeType::nvtHybrid) { - if (selection == current_index) { - extruder_id = i; - nozzle_type = NozzleVolumeType::nvtStandard; - return; - } else if (selection == current_index + 1) { - extruder_id = i; - nozzle_type = NozzleVolumeType::nvtHighFlow; - return; - } - current_index += 2; - } else { - if (selection == current_index) { - extruder_id = i; - nozzle_type = volume_type; - return; + for (int i = 0; i < extruder_nums; ++i) + { + NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]); + + if (volume_type == NozzleVolumeType::nvtHybrid) + { + if (selection == current_index) + { + extruder_id = i; + nozzle_type = NozzleVolumeType::nvtStandard; + return; + } + else if (selection == current_index + 1) + { + extruder_id = i; + nozzle_type = NozzleVolumeType::nvtHighFlow; + return; + } + current_index += 2; + } + else + { + if (selection == current_index) + { + extruder_id = i; + nozzle_type = volume_type; + return; + } + current_index += 1; + } } - current_index += 1; + + extruder_id = 0; + nozzle_type = NozzleVolumeType::nvtStandard; } - } - extruder_id = 0; - nozzle_type = NozzleVolumeType::nvtStandard; -} + int Tab::calculate_selection_index_for_extruder(int extruder_id, NozzleVolumeType nozzle_type) + { + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + int extruder_nums = m_preset_bundle->get_printer_extruder_count(); -int Tab::calculate_selection_index_for_extruder(int extruder_id, NozzleVolumeType nozzle_type) -{ - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - int extruder_nums = m_preset_bundle->get_printer_extruder_count(); + int index = 0; - int index = 0; + for (int i = 0; i < extruder_nums; ++i) + { + if (i == extruder_id) + { + NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]); + if (volume_type == NozzleVolumeType::nvtHybrid) + { + return nozzle_type == NozzleVolumeType::nvtHighFlow ? index + 1 : index; + } + else + { + return index; + } + } - for (int i = 0; i < extruder_nums; ++i) { - if (i == extruder_id) { - NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]); - if (volume_type == NozzleVolumeType::nvtHybrid) { - return nozzle_type == NozzleVolumeType::nvtHighFlow ? index + 1 : index; - } else { - return index; + NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]); + index += (volume_type == NozzleVolumeType::nvtHybrid) ? 2 : 1; } + + return 0; } - NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]); - index += (volume_type == NozzleVolumeType::nvtHybrid) ? 2 : 1; - } + int Tab::get_current_active_extruder() + { + if (m_extruder_switch && m_extruder_switch->IsThisEnabled()) + { + int selection = m_extruder_switch->GetSelection(); + int extruder_id; + NozzleVolumeType nozzle_type; + parse_extruder_selection(selection, extruder_id, nozzle_type); + return extruder_id; + } + return 0; + } - return 0; -} + void Tab::add_scaled_button(wxWindow *parent, + ScalableButton **btn, + const std::string &icon_name, + const wxString &label /* = wxEmptyString*/, + long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) + { + *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style, true); + (*btn)->SetBackgroundColour(parent->GetBackgroundColour()); + m_scaled_buttons.push_back(*btn); + } -int Tab::get_current_active_extruder() -{ - if (m_extruder_switch && m_extruder_switch->IsThisEnabled()) { - int selection = m_extruder_switch->GetSelection(); - int extruder_id; - NozzleVolumeType nozzle_type; - parse_extruder_selection(selection, extruder_id, nozzle_type); - return extruder_id; - } - return 0; -} - -void Tab::add_scaled_button(wxWindow* parent, - ScalableButton** btn, - const std::string& icon_name, - const wxString& label/* = wxEmptyString*/, - long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) -{ - *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style, true); - (*btn)->SetBackgroundColour(parent->GetBackgroundColour()); - m_scaled_buttons.push_back(*btn); -} - -void Tab::add_scaled_bitmap(wxWindow* parent, - ScalableBitmap& bmp, - const std::string& icon_name) -{ - bmp = ScalableBitmap(parent, icon_name); - m_scaled_bitmaps.push_back(&bmp); -} + void Tab::add_scaled_bitmap(wxWindow *parent, + ScalableBitmap &bmp, + const std::string &icon_name) + { + bmp = ScalableBitmap(parent, icon_name); + m_scaled_bitmaps.push_back(&bmp); + } -void Tab::load_initial_data() -{ - m_config = &m_presets->get_edited_preset().config; - bool has_parent = m_presets->get_selected_preset_parent() != nullptr; - m_bmp_non_system = has_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_ttg_non_system = has_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = has_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; -} - -Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages /*= false*/) -{ - // Index of icon in an icon list $self->{icons}. - auto icon_idx = 0; - if (!icon.empty()) { - icon_idx = (m_icon_index.find(icon) == m_icon_index.end()) ? -1 : m_icon_index.at(icon); - if (icon_idx == -1) { - // Add a new icon to the icon list. - m_scaled_icons_list.push_back(ScalableBitmap(this, icon, 32, false, true)); - //m_icons->Add(m_scaled_icons_list.back().bmp()); - icon_idx = ++m_icon_count; - m_icon_index[icon] = icon_idx; - } - - if (m_category_icon.find(title) == m_category_icon.end()) { - // Add new category to the category_to_icon list. - m_category_icon[title] = icon; + void Tab::load_initial_data() + { + m_config = &m_presets->get_edited_preset().config; + bool has_parent = m_presets->get_selected_preset_parent() != nullptr; + m_bmp_non_system = has_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = has_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = has_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; } - } - // Initialize the page. - //BBS: GUI refactor - PageShp page(new Page(m_page_view, title, icon_idx, this)); + + Slic3r::GUI::PageShp Tab::add_options_page(const wxString &title, const std::string &icon, bool is_extruder_pages /*= false*/) + { + // Index of icon in an icon list $self->{icons}. + auto icon_idx = 0; + if (!icon.empty()) + { + icon_idx = (m_icon_index.find(icon) == m_icon_index.end()) ? -1 : m_icon_index.at(icon); + if (icon_idx == -1) + { + // Add a new icon to the icon list. + m_scaled_icons_list.push_back(ScalableBitmap(this, icon, 32, false, true)); + // m_icons->Add(m_scaled_icons_list.back().bmp()); + icon_idx = ++m_icon_count; + m_icon_index[icon] = icon_idx; + } + + if (m_category_icon.find(title) == m_category_icon.end()) + { + // Add new category to the category_to_icon list. + m_category_icon[title] = icon; + } + } + // Initialize the page. + // BBS: GUI refactor + PageShp page(new Page(m_page_view, title, icon_idx, this)); // page->SetBackgroundStyle(wxBG_STYLE_SYSTEM); #ifdef __WINDOWS__ // page->SetDoubleBuffered(true); #endif //__WINDOWS__ - if (dynamic_cast(this)) { - page->m_split_multi_line = true; - page->m_option_label_at_right = true; - } + if (dynamic_cast(this)) + { + page->m_split_multi_line = true; + page->m_option_label_at_right = true; + } - if (!is_extruder_pages) - m_pages.push_back(page); + if (!is_extruder_pages) + m_pages.push_back(page); - page->set_config(m_config); - return page; -} + page->set_config(m_config); + return page; + } -// Names of categories is save in English always. We translate them only for UI. -// But category "Extruder n" can't be translated regularly (using _()), so -// just for this category we should splite the title and translate "Extruder" word separately -wxString Tab::translate_category(const wxString& title, Preset::Type preset_type) -{ - if (preset_type == Preset::TYPE_PRINTER && title.Contains("Extruder ")) { - if (title == "Extruder 1") return _("Left Extruder"); - if (title == "Extruder 2") return _("Right Extruder"); - return _("Extruder") + title.SubString(8, title.Last()); - } - return _(title); -} + // Names of categories is save in English always. We translate them only for UI. + // But category "Extruder n" can't be translated regularly (using _()), so + // just for this category we should splite the title and translate "Extruder" word separately + wxString Tab::translate_category(const wxString &title, Preset::Type preset_type) + { + if (preset_type == Preset::TYPE_PRINTER && title.Contains("Extruder ")) + { + if (title == "Extruder 1") + return _("Left Extruder"); + if (title == "Extruder 2") + return _("Right Extruder"); + return _("Extruder") + title.SubString(8, title.Last()); + } + return _(title); + } -void Tab::OnActivate() -{ - //BBS: GUI refactor - //noUpdates seems not working - //wxWindowUpdateLocker noUpdates(this); -/*#ifdef __WXOSX__ -// wxWindowUpdateLocker noUpdates(this); - auto size = GetSizer()->GetSize(); - m_tmp_panel->GetSizer()->SetMinSize(size.x + m_size_move, size.y); - Fit(); - m_size_move *= -1; -#endif // __WXOSX__*/ + void Tab::OnActivate() + { + // BBS: GUI refactor + // noUpdates seems not working + // wxWindowUpdateLocker noUpdates(this); + /*#ifdef __WXOSX__ + // wxWindowUpdateLocker noUpdates(this); + auto size = GetSizer()->GetSize(); + m_tmp_panel->GetSizer()->SetMinSize(size.x + m_size_move, size.y); + Fit(); + m_size_move *= -1; + #endif // __WXOSX__*/ #ifdef __WXMSW__ - // Workaround for tooltips over Tree Controls displayed over excessively long - // tree control items, stealing the window focus. - // - // In case the Tab was reparented from the MainFrame to the floating dialog, - // the tooltip created by the Tree Control before reparenting is not reparented, - // but it still points to the MainFrame. If the tooltip pops up, the MainFrame - // is incorrectly focussed, stealing focus from the floating dialog. - // - // The workaround is to delete the tooltip control. - // Vojtech tried to reparent the tooltip control, but it did not work, - // and if the Tab was later reparented back to MainFrame, the tooltip was displayed - // at an incorrect position, therefore it is safer to just discard the tooltip control - // altogether. - HWND hwnd_tt = TreeView_GetToolTips(m_tabctrl->GetHandle()); - if (hwnd_tt) { - HWND hwnd_toplevel = find_toplevel_parent(m_tabctrl)->GetHandle(); - HWND hwnd_parent = ::GetParent(hwnd_tt); - if (hwnd_parent != hwnd_toplevel) { - ::DestroyWindow(hwnd_tt); - TreeView_SetToolTips(m_tabctrl->GetHandle(), nullptr); - } - } + // Workaround for tooltips over Tree Controls displayed over excessively long + // tree control items, stealing the window focus. + // + // In case the Tab was reparented from the MainFrame to the floating dialog, + // the tooltip created by the Tree Control before reparenting is not reparented, + // but it still points to the MainFrame. If the tooltip pops up, the MainFrame + // is incorrectly focussed, stealing focus from the floating dialog. + // + // The workaround is to delete the tooltip control. + // Vojtech tried to reparent the tooltip control, but it did not work, + // and if the Tab was later reparented back to MainFrame, the tooltip was displayed + // at an incorrect position, therefore it is safer to just discard the tooltip control + // altogether. + HWND hwnd_tt = TreeView_GetToolTips(m_tabctrl->GetHandle()); + if (hwnd_tt) + { + HWND hwnd_toplevel = find_toplevel_parent(m_tabctrl)->GetHandle(); + HWND hwnd_parent = ::GetParent(hwnd_tt); + if (hwnd_parent != hwnd_toplevel) + { + ::DestroyWindow(hwnd_tt); + TreeView_SetToolTips(m_tabctrl->GetHandle(), nullptr); + } + } #endif - // BBS: select on first active - if (!m_active_page) - restore_last_select_item(); + // BBS: select on first active + if (!m_active_page) + restore_last_select_item(); - //BBS: GUI refactor - m_page_view->Freeze(); + // BBS: GUI refactor + m_page_view->Freeze(); - // create controls on active page - activate_selected_page([](){}); - //BBS: GUI refactor - //m_main_sizer->Layout(); - m_parent->Layout(); + // create controls on active page + activate_selected_page([]() {}); + // BBS: GUI refactor + // m_main_sizer->Layout(); + m_parent->Layout(); #ifdef _MSW_DARK_MODE - // Because of DarkMode we use our own Notebook (inherited from wxSiplebook) instead of wxNotebook - // And it looks like first Layout of the page doesn't update a size of the m_presets_choice - // So we have to set correct size explicitely - /* if (wxSize ok_sz = wxSize(35 * m_em_unit, m_presets_choice->GetBestSize().y); - ok_sz != m_presets_choice->GetSize()) { - m_presets_choice->SetMinSize(ok_sz); - m_presets_choice->SetSize(ok_sz); - GetSizer()->GetItem(size_t(0))->GetSizer()->Layout(); - if (wxGetApp().tabs_as_menu()) - m_presets_choice->update(); - }*/ + // Because of DarkMode we use our own Notebook (inherited from wxSiplebook) instead of wxNotebook + // And it looks like first Layout of the page doesn't update a size of the m_presets_choice + // So we have to set correct size explicitely + /* if (wxSize ok_sz = wxSize(35 * m_em_unit, m_presets_choice->GetBestSize().y); + ok_sz != m_presets_choice->GetSize()) { + m_presets_choice->SetMinSize(ok_sz); + m_presets_choice->SetSize(ok_sz); + GetSizer()->GetItem(size_t(0))->GetSizer()->Layout(); + if (wxGetApp().tabs_as_menu()) + m_presets_choice->update(); + }*/ #endif // _MSW_DARK_MODE - Refresh(); + Refresh(); - //BBS: GUI refactor - m_page_view->Thaw(); -} - -void Tab::update_label_colours() -{ - m_default_text_clr = wxGetApp().get_label_clr_default(); - if (m_sys_label_clr == wxGetApp().get_label_clr_sys() && m_modified_label_clr == wxGetApp().get_label_clr_modified()) - return; - m_sys_label_clr = wxGetApp().get_label_clr_sys(); - m_modified_label_clr = wxGetApp().get_label_clr_modified(); - - //update options "decoration" - for (const auto& opt : m_options_list) - { - const wxColour *color = &m_sys_label_clr; - - // value isn't equal to system value - if ((opt.second & osSystemValue) == 0) { - // value is equal to last saved - if ((opt.second & osInitValue) != 0) - color = &m_default_text_clr; - // value is modified - else - color = &m_modified_label_clr; + // BBS: GUI refactor + m_page_view->Thaw(); } - if (opt.first == "printable_area" || - opt.first == "compatible_prints" || opt.first == "compatible_printers" ) { - if (m_colored_Label_colors.find(opt.first) != m_colored_Label_colors.end()) - m_colored_Label_colors.at(opt.first) = *color; - continue; - } - - Field* field = get_field(opt.first); - if (field == nullptr) continue; - field->set_label_colour(color); - } - auto cur_item = m_tabctrl->GetFirstVisibleItem(); - if (cur_item < 0 || !m_tabctrl->IsVisible(cur_item)) - return; - while (cur_item >= 0) { - auto title = m_tabctrl->GetItemText(cur_item); - for (auto page : m_pages) + void Tab::update_label_colours() { - if (translate_category(page->title(), m_type) != title) - continue; + m_default_text_clr = wxGetApp().get_label_clr_default(); + if (m_sys_label_clr == wxGetApp().get_label_clr_sys() && m_modified_label_clr == wxGetApp().get_label_clr_modified()) + return; + m_sys_label_clr = wxGetApp().get_label_clr_sys(); + m_modified_label_clr = wxGetApp().get_label_clr_modified(); - const wxColor *clr = !page->m_is_nonsys_values ? &m_sys_label_clr : - page->m_is_modified_values ? &m_modified_label_clr : - (m_type < Preset::TYPE_COUNT ? &m_default_text_clr : &m_modified_label_clr); + // update options "decoration" + for (const auto &opt : m_options_list) + { + const wxColour *color = &m_sys_label_clr; + + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0) + { + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified + else + color = &m_modified_label_clr; + } + if (opt.first == "printable_area" || + opt.first == "compatible_prints" || opt.first == "compatible_printers") + { + if (m_colored_Label_colors.find(opt.first) != m_colored_Label_colors.end()) + m_colored_Label_colors.at(opt.first) = *color; + continue; + } - m_tabctrl->SetItemTextColour(cur_item, clr == &m_modified_label_clr ? *clr : StateColor( - std::make_pair(0x6B6B6C, (int) StateColor::NotChecked), - std::make_pair(*clr, (int) StateColor::Normal))); - break; - } - cur_item = m_tabctrl->GetNextVisible(cur_item); - } + Field *field = get_field(opt.first); + if (field == nullptr) + continue; + field->set_label_colour(color); + } - decorate(); -} + auto cur_item = m_tabctrl->GetFirstVisibleItem(); + if (cur_item < 0 || !m_tabctrl->IsVisible(cur_item)) + return; + while (cur_item >= 0) + { + auto title = m_tabctrl->GetItemText(cur_item); + for (auto page : m_pages) + { + if (translate_category(page->title(), m_type) != title) + continue; -void Tab::decorate() -{ - for (const auto& opt : m_options_list) - { - Field* field = nullptr; - wxColour* colored_label_clr = nullptr; + const wxColor *clr = !page->m_is_nonsys_values ? &m_sys_label_clr : page->m_is_modified_values ? &m_modified_label_clr + : (m_type < Preset::TYPE_COUNT ? &m_default_text_clr : &m_modified_label_clr); - if (opt.first == "printable_area" || - opt.first == "compatible_prints" || opt.first == "compatible_printers") - colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : &m_colored_Label_colors.at(opt.first); + m_tabctrl->SetItemTextColour(cur_item, clr == &m_modified_label_clr ? *clr : StateColor(std::make_pair(0x6B6B6C, (int)StateColor::NotChecked), std::make_pair(*clr, (int)StateColor::Normal))); + break; + } + cur_item = m_tabctrl->GetNextVisible(cur_item); + } - if (!colored_label_clr) { - field = get_field(opt.first); - if (!field) - continue; + decorate(); } - bool is_nonsys_value = false; - bool is_modified_value = true; - const ScalableBitmap* sys_icon = &m_bmp_value_lock; - const ScalableBitmap* icon = &m_bmp_value_revert; + void Tab::decorate() + { + for (const auto &opt : m_options_list) + { + Field *field = nullptr; + wxColour *colored_label_clr = nullptr; - const wxColour* color = m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; + if (opt.first == "printable_area" || + opt.first == "compatible_prints" || opt.first == "compatible_printers") + colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : &m_colored_Label_colors.at(opt.first); - const wxString* sys_tt = &m_tt_value_lock; - const wxString* tt = &m_tt_value_revert; + if (!colored_label_clr) + { + field = get_field(opt.first); + if (!field) + continue; + } - // value isn't equal to system value - if ((opt.second & osSystemValue) == 0) { - is_nonsys_value = true; - sys_icon = m_bmp_non_system; - sys_tt = m_tt_non_system; - // value is equal to last saved - if ((opt.second & osInitValue) != 0) - color = &m_default_text_clr; - // value is modified - else - color = &m_modified_label_clr; - } - if ((opt.second & osInitValue) != 0) - { - is_modified_value = false; - icon = &m_bmp_white_bullet; - tt = &m_tt_white_bullet; - } + bool is_nonsys_value = false; + bool is_modified_value = true; + const ScalableBitmap *sys_icon = &m_bmp_value_lock; + const ScalableBitmap *icon = &m_bmp_value_revert; + + const wxColour *color = m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; + + const wxString *sys_tt = &m_tt_value_lock; + const wxString *tt = &m_tt_value_revert; + + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0) + { + is_nonsys_value = true; + sys_icon = m_bmp_non_system; + sys_tt = m_tt_non_system; + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified + else + color = &m_modified_label_clr; + } + if ((opt.second & osInitValue) != 0) + { + is_modified_value = false; + icon = &m_bmp_white_bullet; + tt = &m_tt_white_bullet; + } - if (colored_label_clr) { - *colored_label_clr = *color; - continue; - } + if (colored_label_clr) + { + *colored_label_clr = *color; + continue; + } - field->m_is_nonsys_value = is_nonsys_value; - field->m_is_modified_value = is_modified_value; - field->set_undo_bitmap(icon); - //BBS: GUI refactor - field->set_undo_to_sys_bitmap(sys_icon); - field->set_undo_tooltip(tt); - field->set_undo_to_sys_tooltip(sys_tt); - field->set_label_colour(color); - } + field->m_is_nonsys_value = is_nonsys_value; + field->m_is_modified_value = is_modified_value; + field->set_undo_bitmap(icon); + // BBS: GUI refactor + field->set_undo_to_sys_bitmap(sys_icon); + field->set_undo_tooltip(tt); + field->set_undo_to_sys_tooltip(sys_tt); + field->set_label_colour(color); + } - if (m_active_page) - m_active_page->refresh(); -} + if (m_active_page) + m_active_page->refresh(); + } -void Tab::filter_diff_option(std::vector &options) -{ - for (auto &opt : options) { - auto n = opt.find_last_of('#'); - if (n == std::string::npos) continue; - bool found = false; - for (auto page : m_pages) { - if (auto iter = page->m_opt_id_map.find(opt); iter != page->m_opt_id_map.end()) { - opt = iter->second; - found = true; - break; + void Tab::filter_diff_option(std::vector &options) + { + for (auto &opt : options) + { + auto n = opt.find_last_of('#'); + if (n == std::string::npos) + continue; + bool found = false; + for (auto page : m_pages) + { + if (auto iter = page->m_opt_id_map.find(opt); iter != page->m_opt_id_map.end()) + { + opt = iter->second; + found = true; + break; + } + } + if (!found) + opt = opt.substr(0, n); } + options.erase(std::remove(options.begin(), options.end(), ""), options.end()); } - if (!found) opt = opt.substr(0, n); - } - options.erase(std::remove(options.begin(), options.end(), ""), options.end()); -} -// Update UI according to changes -void Tab::update_changed_ui() -{ - if (m_postpone_update_ui) - return; - - const bool deep_compare = (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_PRINT || m_type == Preset::TYPE_FILAMENT - || m_type == Preset::TYPE_SLA_MATERIAL || m_type == Preset::TYPE_MODEL); - auto dirty_options = m_presets->current_dirty_options(deep_compare); - auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); - if (m_type == Preset::TYPE_PRINTER && static_cast(this)->m_printer_technology == ptFFF) { - TabPrinter* tab = static_cast(this); - if (tab->m_initial_extruders_count != tab->m_extruders_count) - dirty_options.emplace_back("extruders_count"); - if (tab->m_sys_extruders_count != tab->m_extruders_count) - nonsys_options.emplace_back("extruders_count"); - } + // Update UI according to changes + void Tab::update_changed_ui() + { + if (m_postpone_update_ui) + return; - update_custom_dirty(dirty_options, nonsys_options); - update_all_extruder_options_status(); + const bool deep_compare = (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_PRINT || m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL || m_type == Preset::TYPE_MODEL); + auto dirty_options = m_presets->current_dirty_options(deep_compare); + auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); + if (m_type == Preset::TYPE_PRINTER && static_cast(this)->m_printer_technology == ptFFF) + { + TabPrinter *tab = static_cast(this); + if (tab->m_initial_extruders_count != tab->m_extruders_count) + dirty_options.emplace_back("extruders_count"); + if (tab->m_sys_extruders_count != tab->m_extruders_count) + nonsys_options.emplace_back("extruders_count"); + } - filter_diff_option(dirty_options); - filter_diff_option(nonsys_options); + update_custom_dirty(dirty_options, nonsys_options); + update_all_extruder_options_status(); - for (auto& it : m_options_list) - it.second = m_opt_status_value; + filter_diff_option(dirty_options); + filter_diff_option(nonsys_options); - for (auto opt_key : dirty_options) { - auto iter = m_options_list.find(opt_key); - if (iter != m_options_list.end()) - iter->second &= ~osInitValue; - } - for (auto opt_key : nonsys_options) { - auto iter = m_options_list.find(opt_key); - if (iter != m_options_list.end()) - iter->second &= ~osSystemValue; - } + for (auto &it : m_options_list) + it.second = m_opt_status_value; - decorate(); - update_extruder_switch_colors(); + for (auto opt_key : dirty_options) + { + auto iter = m_options_list.find(opt_key); + if (iter != m_options_list.end()) + iter->second &= ~osInitValue; + } + for (auto opt_key : nonsys_options) + { + auto iter = m_options_list.find(opt_key); + if (iter != m_options_list.end()) + iter->second &= ~osSystemValue; + } - wxTheApp->CallAfter([this]() { - if (parent()) //To avoid a crash, parent should be exist for a moment of a tree updating - update_changed_tree_ui(); - }); - // BBS: - update_undo_buttons(); -} + decorate(); + update_extruder_switch_colors(); -template -void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, Tab *tab, const int& value) -{ - map.emplace(opt_key + "#0", value); -} + wxTheApp->CallAfter([this]() + { + if (parent()) //To avoid a crash, parent should be exist for a moment of a tree updating + update_changed_tree_ui(); }); + // BBS: + update_undo_buttons(); + } -void Tab::update_all_extruder_options_status() -{ - if (!m_extruder_switch && !m_variant_combo) { - return; - } - m_all_extruder_options_status.clear(); - - int extruder_count = m_preset_bundle->get_printer_extruder_count(); - auto extruders = m_preset_bundle->printers.get_edited_preset().config.option("extruder_type"); - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - - std::set all_config_indices; - for (int extruder_id = 0; extruder_id < extruder_count; ++extruder_id) { - for (auto nozzle_type : {NozzleVolumeType::nvtStandard, NozzleVolumeType::nvtHighFlow}) { - auto variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]; - int config_index = m_config->get_index_for_extruder( - extruder_id + 1, - variant_keys.first, - ExtruderType(extruders->values[extruder_id]), - nozzle_type, - variant_keys.second - ); - if (config_index >= 0) { - all_config_indices.insert(config_index); - } + template + void add_correct_opts_to_options_list(const std::string &opt_key, std::map &map, Tab *tab, const int &value) + { + map.emplace(opt_key + "#0", value); } - } - auto dirty_options = m_presets->current_dirty_options(true); - auto nonsys_options = m_presets->current_different_from_parent_options(true); - auto filter_extruder_options = [](const std::vector& options) { - std::vector filtered_options; - for (const auto& opt : options) { - if (opt.find('#') != std::string::npos) { - filtered_options.push_back(opt); + void Tab::update_all_extruder_options_status() + { + if (!m_extruder_switch && !m_variant_combo) + { + return; } - } - return filtered_options; - }; + m_all_extruder_options_status.clear(); - auto filtered_dirty_options = filter_extruder_options(dirty_options); - auto filtered_nonsys_options = filter_extruder_options(nonsys_options); + int extruder_count = m_preset_bundle->get_printer_extruder_count(); + auto extruders = m_preset_bundle->printers.get_edited_preset().config.option("extruder_type"); + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - for (int config_index : all_config_indices) { - int status_value = m_opt_status_value; - for (const auto &opt_key : filtered_dirty_options) { - m_all_extruder_options_status[opt_key] = status_value & ~osInitValue; - } - for (const auto &opt_key : filtered_nonsys_options) { - auto iter = m_all_extruder_options_status.find(opt_key); - if (iter != m_all_extruder_options_status.end()) { - iter->second &= ~osSystemValue; - } else { - m_all_extruder_options_status[opt_key] = status_value & ~osSystemValue; + std::set all_config_indices; + for (int extruder_id = 0; extruder_id < extruder_count; ++extruder_id) + { + for (auto nozzle_type : {NozzleVolumeType::nvtStandard, NozzleVolumeType::nvtHighFlow}) + { + auto variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]; + int config_index = m_config->get_index_for_extruder( + extruder_id + 1, + variant_keys.first, + ExtruderType(extruders->values[extruder_id]), + nozzle_type, + variant_keys.second); + if (config_index >= 0) + { + all_config_indices.insert(config_index); + } + } } - } - } -} - -void Tab::update_extruder_switch_colors() -{ - if (!m_extruder_switch && !m_variant_combo) { - return; - } - - auto options = generate_extruder_options(); - auto extruders = m_preset_bundle->printers.get_edited_preset().config.option("extruder_type"); - for (size_t switch_index = 0; switch_index < options.size(); ++switch_index) { - int selection = m_extruder_switch ? m_extruder_switch->GetSelection() : (m_variant_combo ? m_variant_combo->GetSelection() : 0); - if (switch_index == selection) continue; + auto dirty_options = m_presets->current_dirty_options(true); + auto nonsys_options = m_presets->current_different_from_parent_options(true); + auto filter_extruder_options = [](const std::vector &options) + { + std::vector filtered_options; + for (const auto &opt : options) + { + if (opt.find('#') != std::string::npos) + { + filtered_options.push_back(opt); + } + } + return filtered_options; + }; - bool sys_extruder = true; - bool modified_extruder = false; - std::vector pages_to_check; + auto filtered_dirty_options = filter_extruder_options(dirty_options); + auto filtered_nonsys_options = filter_extruder_options(nonsys_options); - if (m_active_page) { - if (m_active_page->title() == "Speed" || m_active_page->title() == "Motion ability" || m_active_page->title() == "Filament" || - m_active_page->title() == "Setting Overrides" || m_active_page->title() == "Multi Filament") { - for (auto page_ptr : m_pages) { - if (page_ptr.get() == m_active_page) { - pages_to_check.push_back(page_ptr); - break; + for (int config_index : all_config_indices) + { + int status_value = m_opt_status_value; + for (const auto &opt_key : filtered_dirty_options) + { + m_all_extruder_options_status[opt_key] = status_value & ~osInitValue; + } + for (const auto &opt_key : filtered_nonsys_options) + { + auto iter = m_all_extruder_options_status.find(opt_key); + if (iter != m_all_extruder_options_status.end()) + { + iter->second &= ~osSystemValue; + } + else + { + m_all_extruder_options_status[opt_key] = status_value & ~osSystemValue; } } } } - if (pages_to_check.empty()) { - continue; - } - check_extruder_options_status(switch_index, sys_extruder, modified_extruder, pages_to_check); - StateColor default_color(std::make_pair(0x6B6B6B, (int) StateColor::NotChecked), std::make_pair(0xFFFFFE, (int) StateColor::Normal)); - StateColor color = (modified_extruder || m_type >= Preset::TYPE_COUNT) ? StateColor(m_modified_label_clr) : default_color; + void Tab::update_extruder_switch_colors() + { + if (!m_extruder_switch && !m_variant_combo) + { + return; + } + + auto options = generate_extruder_options(); + auto extruders = m_preset_bundle->printers.get_edited_preset().config.option("extruder_type"); + + for (size_t switch_index = 0; switch_index < options.size(); ++switch_index) + { + int selection = m_extruder_switch ? m_extruder_switch->GetSelection() : (m_variant_combo ? m_variant_combo->GetSelection() : 0); + if (switch_index == selection) + continue; - if (m_extruder_switch) - m_extruder_switch->SetButtonTextColor(switch_index, color); - if (m_variant_combo) { - StateColor default_color_grayed(std::make_pair(0x999999, (int) StateColor::NotChecked), std::make_pair(0x99DFB2, (int) StateColor::Normal)); - Button *btn = m_variant_combo->GetButton(switch_index); - if (btn) { - m_variant_combo->SetButtonTextColor(switch_index, btn->IsGrayed() ? default_color_grayed : color); + bool sys_extruder = true; + bool modified_extruder = false; + std::vector pages_to_check; + + if (m_active_page) + { + if (m_active_page->title() == "Speed" || m_active_page->title() == "Motion ability" || m_active_page->title() == "Filament" || + m_active_page->title() == "Setting Overrides" || m_active_page->title() == "Multi Filament") + { + for (auto page_ptr : m_pages) + { + if (page_ptr.get() == m_active_page) + { + pages_to_check.push_back(page_ptr); + break; + } + } + } + } + if (pages_to_check.empty()) + { + continue; + } + check_extruder_options_status(switch_index, sys_extruder, modified_extruder, pages_to_check); + + StateColor default_color(std::make_pair(0x6B6B6B, (int)StateColor::NotChecked), std::make_pair(0xFFFFFE, (int)StateColor::Normal)); + StateColor color = (modified_extruder || m_type >= Preset::TYPE_COUNT) ? StateColor(m_modified_label_clr) : default_color; + + if (m_extruder_switch) + m_extruder_switch->SetButtonTextColor(switch_index, color); + if (m_variant_combo) + { + StateColor default_color_grayed(std::make_pair(0x999999, (int)StateColor::NotChecked), std::make_pair(0x99DFB2, (int)StateColor::Normal)); + Button *btn = m_variant_combo->GetButton(switch_index); + if (btn) + { + m_variant_combo->SetButtonTextColor(switch_index, btn->IsGrayed() ? default_color_grayed : color); + } + } } } - } -} -void Tab::check_extruder_options_status(int index, bool &sys_extruder, bool &modified_extruder, const std::vector& pages_to_check) -{ - int config_index = index; - if (m_type == Preset::TYPE_PRINT || m_type == Preset::TYPE_PRINTER) { - int extruder_id; - NozzleVolumeType nozzle_type; - parse_extruder_selection(index, extruder_id, nozzle_type); - - auto extruders = m_preset_bundle->printers.get_edited_preset().config.option("extruder_type"); - auto variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]; - config_index = m_config->get_index_for_extruder( - extruder_id + 1, - variant_keys.first, - ExtruderType(extruders->values[extruder_id]), - nozzle_type, - variant_keys.second - ); - } + void Tab::check_extruder_options_status(int index, bool &sys_extruder, bool &modified_extruder, const std::vector &pages_to_check) + { + int config_index = index; + if (m_type == Preset::TYPE_PRINT || m_type == Preset::TYPE_PRINTER) + { + int extruder_id; + NozzleVolumeType nozzle_type; + parse_extruder_selection(index, extruder_id, nozzle_type); + + auto extruders = m_preset_bundle->printers.get_edited_preset().config.option("extruder_type"); + auto variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]; + config_index = m_config->get_index_for_extruder( + extruder_id + 1, + variant_keys.first, + ExtruderType(extruders->values[extruder_id]), + nozzle_type, + variant_keys.second); + } - for (auto page : pages_to_check) { - /*if (page->title() != "Speed" && page->title() != "Motion ability" && page->title() != "Filament" && page->title() != "Setting Overrides" && page->title() != "Multi Filament") { - continue; - }*/ - for (auto group : page->m_optgroups) { - for (const auto &kvp : group->opt_map()) { - std::string base_opt_key = kvp.second.first; - // For filament tab, common options will not change color when edited - if (m_type == Preset::TYPE_FILAMENT && kvp.second.second == -1) { + for (auto page : pages_to_check) + { + /*if (page->title() != "Speed" && page->title() != "Motion ability" && page->title() != "Filament" && page->title() != "Setting Overrides" && page->title() != "Multi Filament") { continue; - } - std::string target_opt_key = base_opt_key + "#" + std::to_string(config_index); - - auto status_iter = m_all_extruder_options_status.find(target_opt_key); - if (status_iter != m_all_extruder_options_status.end()) { - bool found_modified_for_this_config = false; - const bool deep_compare = (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_PRINT || m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL || - m_type == Preset::TYPE_MODEL); - auto original_dirty_options = m_presets->current_dirty_options(deep_compare); - for (const std::string &orig_opt : original_dirty_options) { - if (orig_opt == target_opt_key) { - found_modified_for_this_config = true; - break; + }*/ + for (auto group : page->m_optgroups) + { + for (const auto &kvp : group->opt_map()) + { + std::string base_opt_key = kvp.second.first; + // For filament tab, common options will not change color when edited + if (m_type == Preset::TYPE_FILAMENT && kvp.second.second == -1) + { + continue; } - } + std::string target_opt_key = base_opt_key + "#" + std::to_string(config_index); + + auto status_iter = m_all_extruder_options_status.find(target_opt_key); + if (status_iter != m_all_extruder_options_status.end()) + { + bool found_modified_for_this_config = false; + const bool deep_compare = (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_PRINT || m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL || + m_type == Preset::TYPE_MODEL); + auto original_dirty_options = m_presets->current_dirty_options(deep_compare); + for (const std::string &orig_opt : original_dirty_options) + { + if (orig_opt == target_opt_key) + { + found_modified_for_this_config = true; + break; + } + } - if (found_modified_for_this_config) { - sys_extruder = (status_iter->second & osSystemValue) != 0; - modified_extruder |= (status_iter->second & osInitValue) == 0; + if (found_modified_for_this_config) + { + sys_extruder = (status_iter->second & osSystemValue) != 0; + modified_extruder |= (status_iter->second & osInitValue) == 0; - if (!sys_extruder && modified_extruder) { return; } + if (!sys_extruder && modified_extruder) + { + return; + } + } + } } } } } - } -} -void Tab::init_options_list() -{ - if (!m_options_list.empty()) - m_options_list.clear(); + void Tab::init_options_list() + { + if (!m_options_list.empty()) + m_options_list.clear(); - for (const std::string& opt_key : m_config->keys()) - { - if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnail_size" || - opt_key == "wrapping_exclude_area" || opt_key == "post_process") { - m_options_list.emplace(opt_key, m_opt_status_value); - continue; - } - if (m_config->option(opt_key)->is_vector()) - m_options_list.emplace(opt_key + "#0", m_opt_status_value); - else - m_options_list.emplace(opt_key, m_opt_status_value); - } -} + for (const std::string &opt_key : m_config->keys()) + { + if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnail_size" || + opt_key == "wrapping_exclude_area" || opt_key == "post_process") + { + m_options_list.emplace(opt_key, m_opt_status_value); + continue; + } + if (m_config->option(opt_key)->is_vector()) + m_options_list.emplace(opt_key + "#0", m_opt_status_value); + else + m_options_list.emplace(opt_key, m_opt_status_value); + } + } -void TabPrinter::init_options_list() -{ - Tab::init_options_list(); - if (m_printer_technology == ptFFF) - m_options_list.emplace("extruders_count", m_opt_status_value); - for (size_t i = 1; i < m_extruders_count; ++i) { - auto extruder_page = m_pages[3 + i]; - for (auto group : extruder_page->m_optgroups) { - for (auto & opt : group->opt_map()) - m_options_list.emplace(opt.first, m_opt_status_value); + void TabPrinter::init_options_list() + { + Tab::init_options_list(); + if (m_printer_technology == ptFFF) + m_options_list.emplace("extruders_count", m_opt_status_value); + for (size_t i = 1; i < m_extruders_count; ++i) + { + auto extruder_page = m_pages[3 + i]; + for (auto group : extruder_page->m_optgroups) + { + for (auto &opt : group->opt_map()) + m_options_list.emplace(opt.first, m_opt_status_value); + } + } } - } -} -void TabPrinter::msw_rescale() -{ - Tab::msw_rescale(); + void TabPrinter::msw_rescale() + { + Tab::msw_rescale(); - if (m_reset_to_filament_color) - m_reset_to_filament_color->msw_rescale(); + if (m_reset_to_filament_color) + m_reset_to_filament_color->msw_rescale(); - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); -} + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); + } -void TabFilament::init_options_list() -{ - if (!m_options_list.empty()) - m_options_list.clear(); - - for (const std::string &opt_key : m_config->keys()) { - if (filament_options_with_variant.find(opt_key) == filament_options_with_variant.end()) - m_options_list.emplace(opt_key, m_opt_status_value); - else - m_options_list.emplace(opt_key + "#0", m_opt_status_value); - } -} + void TabFilament::init_options_list() + { + if (!m_options_list.empty()) + m_options_list.clear(); -void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) -{ - auto opt = m_options_list.find(opt_key); - if (opt == m_options_list.end()) - return; + for (const std::string &opt_key : m_config->keys()) + { + if (filament_options_with_variant.find(opt_key) == filament_options_with_variant.end()) + m_options_list.emplace(opt_key, m_opt_status_value); + else + m_options_list.emplace(opt_key + "#0", m_opt_status_value); + } + } - if (sys_page) sys_page = (opt->second & osSystemValue) != 0; - modified_page |= (opt->second & osInitValue) == 0; -} + void Tab::get_sys_and_mod_flags(const std::string &opt_key, bool &sys_page, bool &modified_page) + { + auto opt = m_options_list.find(opt_key); + if (opt == m_options_list.end()) + return; -void Tab::update_changed_tree_ui() -{ - if (m_options_list.empty()) { - if (m_type == Preset::Type::TYPE_PLATE) { - for (auto page : m_pages) { - page->m_is_nonsys_values = false; - } + if (sys_page) + sys_page = (opt->second & osSystemValue) != 0; + modified_page |= (opt->second & osInitValue) == 0; } - return; - } - auto cur_item = m_tabctrl->GetFirstVisibleItem(); - if (cur_item < 0 || !m_tabctrl->IsVisible(cur_item)) - return; - - auto selected_item = m_tabctrl->GetSelection(); - auto selection = selected_item >= 0 ? m_tabctrl->GetItemText(selected_item) : ""; - - while (cur_item >= 0) { - auto title = m_tabctrl->GetItemText(cur_item); - for (auto page : m_pages) - { - if (translate_category(page->title(), m_type) != title) - continue; - bool sys_page = true; - bool modified_page = false; - if (page->title() == "General") { - std::initializer_list optional_keys{ "extruders_count", "printable_area" }; - for (auto &opt_key : optional_keys) { - get_sys_and_mod_flags(opt_key, sys_page, modified_page); - } - } - if (page->title() == "Dependencies") { - if (m_type == Slic3r::Preset::TYPE_PRINTER) { - sys_page = m_presets->get_selected_preset_parent() != nullptr; - modified_page = false; - } else { - if (m_type == Slic3r::Preset::TYPE_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL) - get_sys_and_mod_flags("compatible_prints", sys_page, modified_page); - get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); - } - } - if (page->title() == "Speed" || page->title() == "Motion ability" || page->title() == "Filament" || page->title() == "Setting Overrides" || page->title() == "Multi Filament") { - auto options = generate_extruder_options(); - for (size_t switch_index = 0; switch_index < options.size(); ++switch_index) { - std::vector pages_to_check = { page }; - check_extruder_options_status(switch_index, sys_page, modified_page, pages_to_check); + + void Tab::update_changed_tree_ui() + { + if (m_options_list.empty()) + { + if (m_type == Preset::Type::TYPE_PLATE) + { + for (auto page : m_pages) + { + page->m_is_nonsys_values = false; + } } + return; } - for (auto group : page->m_optgroups) + auto cur_item = m_tabctrl->GetFirstVisibleItem(); + if (cur_item < 0 || !m_tabctrl->IsVisible(cur_item)) + return; + + auto selected_item = m_tabctrl->GetSelection(); + auto selection = selected_item >= 0 ? m_tabctrl->GetItemText(selected_item) : ""; + + while (cur_item >= 0) { - if (!sys_page && modified_page) + auto title = m_tabctrl->GetItemText(cur_item); + for (auto page : m_pages) + { + if (translate_category(page->title(), m_type) != title) + continue; + bool sys_page = true; + bool modified_page = false; + if (page->title() == "General") + { + std::initializer_list optional_keys{"extruders_count", "printable_area"}; + for (auto &opt_key : optional_keys) + { + get_sys_and_mod_flags(opt_key, sys_page, modified_page); + } + } + if (page->title() == "Dependencies") + { + if (m_type == Slic3r::Preset::TYPE_PRINTER) + { + sys_page = m_presets->get_selected_preset_parent() != nullptr; + modified_page = false; + } + else + { + if (m_type == Slic3r::Preset::TYPE_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL) + get_sys_and_mod_flags("compatible_prints", sys_page, modified_page); + get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); + } + } + if (page->title() == "Speed" || page->title() == "Motion ability" || page->title() == "Filament" || page->title() == "Setting Overrides" || page->title() == "Multi Filament") + { + auto options = generate_extruder_options(); + for (size_t switch_index = 0; switch_index < options.size(); ++switch_index) + { + std::vector pages_to_check = {page}; + check_extruder_options_status(switch_index, sys_page, modified_page, pages_to_check); + } + } + for (auto group : page->m_optgroups) + { + if (!sys_page && modified_page) + break; + for (const auto &kvp : group->opt_map()) + { + const std::string &opt_key = kvp.first; + get_sys_and_mod_flags(opt_key, sys_page, modified_page); + } + } + + const wxColor *clr = sys_page ? (m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr) : (modified_page || m_type >= Preset::TYPE_COUNT) ? &m_modified_label_clr + : &m_default_text_clr; + + if (page->set_item_colour(clr)) + m_tabctrl->SetItemTextColour(cur_item, clr == &m_modified_label_clr ? *clr : StateColor(std::make_pair(0x6B6B6C, (int)StateColor::NotChecked), std::make_pair(*clr, (int)StateColor::Normal))); + + page->m_is_nonsys_values = !sys_page; + page->m_is_modified_values = modified_page; + + if (selection == title) + { + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; + } break; - for (const auto &kvp : group->opt_map()) { - const std::string &opt_key = kvp.first; - get_sys_and_mod_flags(opt_key, sys_page, modified_page); } + auto next_item = m_tabctrl->GetNextVisible(cur_item); + cur_item = next_item; } + } - const wxColor *clr = sys_page ? (m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr) : - (modified_page || m_type >= Preset::TYPE_COUNT) ? &m_modified_label_clr : &m_default_text_clr; + void Tab::update_undo_buttons() + { + // BBS: restore all pages in preset + m_undo_btn->SetBitmap_(m_presets->get_edited_preset().is_dirty ? m_bmp_value_revert : m_bmp_white_bullet); + m_undo_to_sys_btn->SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); - if (page->set_item_colour(clr)) - m_tabctrl->SetItemTextColour(cur_item, clr == &m_modified_label_clr ? *clr : StateColor( - std::make_pair(0x6B6B6C, (int) StateColor::NotChecked), - std::make_pair(*clr, (int) StateColor::Normal))); + m_undo_btn->SetToolTip(m_presets->get_edited_preset().is_dirty ? _L("Click to reset all settings to the last saved preset.") : m_ttg_white_bullet); + m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); + } - page->m_is_nonsys_values = !sys_page; - page->m_is_modified_values = modified_page; + void Tab::on_roll_back_value(const bool to_sys /*= true*/) + { + // BBS: restore all pages in preset + // if (!m_active_page) return; - if (selection == title) { - m_is_nonsys_values = page->m_is_nonsys_values; - m_is_modified_values = page->m_is_modified_values; + int os; + if (to_sys) + { + if (!m_is_nonsys_values) + return; + os = osSystemValue; + } + else + { + // BBS: restore all pages in preset + if (!m_presets->get_edited_preset().is_dirty) + return; + os = osInitValue; } - break; + + m_postpone_update_ui = true; + + // BBS: restore all preset + for (auto page : m_pages) + for (auto group : page->m_optgroups) + { + if (group->title == "Capabilities") + { + if ((m_options_list["extruders_count"] & os) == 0) + to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); + } + if (group->title == "Size and coordinates") + { + if ((m_options_list["printable_area"] & os) == 0) + { + to_sys ? group->back_to_sys_value("printable_area") : group->back_to_initial_value("printable_area"); + load_key_value("printable_area", true /*some value*/, true); + } + } + // if (group->title == "Profile dependencies") { + // // "compatible_printers" option doesn't exists in Printer Settimgs Tab + // if (m_type != Preset::TYPE_PRINTER && (m_options_list["compatible_printers"] & os) == 0) { + // to_sys ? group->back_to_sys_value("compatible_printers") : group->back_to_initial_value("compatible_printers"); + // load_key_value("compatible_printers", true/*some value*/, true); + + // bool is_empty = m_config->option("compatible_printers")->values.empty(); + // m_compatible_printers.checkbox->SetValue(is_empty); + // is_empty ? m_compatible_printers.btn->Disable() : m_compatible_printers.btn->Enable(); + // } + // // "compatible_prints" option exists only in Filament Settimgs and Materials Tabs + // if ((m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) && (m_options_list["compatible_prints"] & os) == 0) { + // to_sys ? group->back_to_sys_value("compatible_prints") : group->back_to_initial_value("compatible_prints"); + // load_key_value("compatible_prints", true/*some value*/, true); + + // bool is_empty = m_config->option("compatible_prints")->values.empty(); + // m_compatible_prints.checkbox->SetValue(is_empty); + // is_empty ? m_compatible_prints.btn->Disable() : m_compatible_prints.btn->Enable(); + // } + //} + for (const auto &kvp : group->opt_map()) + { + const std::string &opt_key = kvp.first; + auto iter = m_options_list.find(opt_key); + if (iter != m_options_list.end() && (iter->second & os) == 0) + to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); + } + } + + // BBS: restore all pages in preset + m_presets->discard_current_changes(); + + m_postpone_update_ui = false; + + // When all values are rolled, then we hane to update whole tab in respect to the reverted values + update(); + + // BBS: restore all pages in preset, update_dirty also update combobox + update_dirty(); } - auto next_item = m_tabctrl->GetNextVisible(cur_item); - cur_item = next_item; - } -} -void Tab::update_undo_buttons() -{ - // BBS: restore all pages in preset - m_undo_btn-> SetBitmap_(m_presets->get_edited_preset().is_dirty ? m_bmp_value_revert: m_bmp_white_bullet); - m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); + // Update the combo box label of the selected preset based on its "dirty" state, + // comparing the selected preset config with $self->{config}. + void Tab::update_dirty() + { + if (m_postpone_update_ui) + return; - m_undo_btn->SetToolTip(m_presets->get_edited_preset().is_dirty ? _L("Click to reset all settings to the last saved preset.") : m_ttg_white_bullet); - m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); -} + if (m_presets_choice) + { + m_presets_choice->update_dirty(); + on_presets_changed(); + } + else + { + m_presets->update_dirty(); + } + update_changed_ui(); + } -void Tab::on_roll_back_value(const bool to_sys /*= true*/) -{ - // BBS: restore all pages in preset - // if (!m_active_page) return; + void Tab::update_tab_ui(bool update_plater_presets) + { + if (m_presets_choice) + { + m_presets_choice->update(); + if (update_plater_presets) + on_presets_changed(); + } + } - int os; - if (to_sys) { - if (!m_is_nonsys_values) return; - os = osSystemValue; - } - else { - // BBS: restore all pages in preset - if (!m_presets->get_edited_preset().is_dirty) return; - os = osInitValue; - } + // Load a provied DynamicConfig into the tab, modifying the active preset. + // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. + void Tab::load_config(const DynamicPrintConfig &config) + { + bool modified = 0; + for (auto opt_key : m_config->diff(config)) + { + m_config->set_key_value(opt_key, config.option(opt_key)->clone()); + modified = 1; + } + if (modified) + { + update_dirty(); + // # Initialize UI components with the config values. + reload_config(); + update(); + } + } - m_postpone_update_ui = true; - - // BBS: restore all preset - for (auto page : m_pages) - for (auto group : page->m_optgroups) { - if (group->title == "Capabilities") { - if ((m_options_list["extruders_count"] & os) == 0) - to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); - } - if (group->title == "Size and coordinates") { - if ((m_options_list["printable_area"] & os) == 0) { - to_sys ? group->back_to_sys_value("printable_area") : group->back_to_initial_value("printable_area"); - load_key_value("printable_area", true/*some value*/, true); - } - } - //if (group->title == "Profile dependencies") { - // // "compatible_printers" option doesn't exists in Printer Settimgs Tab - // if (m_type != Preset::TYPE_PRINTER && (m_options_list["compatible_printers"] & os) == 0) { - // to_sys ? group->back_to_sys_value("compatible_printers") : group->back_to_initial_value("compatible_printers"); - // load_key_value("compatible_printers", true/*some value*/, true); - - // bool is_empty = m_config->option("compatible_printers")->values.empty(); - // m_compatible_printers.checkbox->SetValue(is_empty); - // is_empty ? m_compatible_printers.btn->Disable() : m_compatible_printers.btn->Enable(); - // } - // // "compatible_prints" option exists only in Filament Settimgs and Materials Tabs - // if ((m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) && (m_options_list["compatible_prints"] & os) == 0) { - // to_sys ? group->back_to_sys_value("compatible_prints") : group->back_to_initial_value("compatible_prints"); - // load_key_value("compatible_prints", true/*some value*/, true); - - // bool is_empty = m_config->option("compatible_prints")->values.empty(); - // m_compatible_prints.checkbox->SetValue(is_empty); - // is_empty ? m_compatible_prints.btn->Disable() : m_compatible_prints.btn->Enable(); - // } - //} - for (const auto &kvp : group->opt_map()) { - const std::string &opt_key = kvp.first; - auto iter = m_options_list.find(opt_key); - if (iter != m_options_list.end() && (iter->second & os) == 0) - to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); + // Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. + void Tab::reload_config() + { + if (m_active_page) + m_active_page->reload_config(); } - } - // BBS: restore all pages in preset - m_presets->discard_current_changes(); + void Tab::update_mode() + { + m_mode = wxGetApp().get_mode(); - m_postpone_update_ui = false; + // BBS: GUI refactor + // update mode for ModeSizer + // if (m_mode_sizer) + // m_mode_sizer->SetMode(m_mode); - // When all values are rolled, then we hane to update whole tab in respect to the reverted values - update(); + update_visibility(); - // BBS: restore all pages in preset, update_dirty also update combobox - update_dirty(); -} + update_changed_tree_ui(); + } -// Update the combo box label of the selected preset based on its "dirty" state, -// comparing the selected preset config with $self->{config}. -void Tab::update_dirty() -{ - if (m_postpone_update_ui) - return; - - if (m_presets_choice) { - m_presets_choice->update_dirty(); - on_presets_changed(); - } else { - m_presets->update_dirty(); - } - update_changed_ui(); -} + void Tab::update_visibility() + { + Freeze(); // There is needed Freeze/Thaw to avoid a flashing after Show/Layout -void Tab::update_tab_ui(bool update_plater_presets) -{ - if (m_presets_choice) { - m_presets_choice->update(); - if (update_plater_presets) - on_presets_changed(); - } -} + for (auto page : m_pages) + page->update_visibility(m_mode, page.get() == m_active_page); + rebuild_page_tree(); -// Load a provied DynamicConfig into the tab, modifying the active preset. -// This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. -void Tab::load_config(const DynamicPrintConfig& config) -{ - bool modified = 0; - for(auto opt_key : m_config->diff(config)) { - m_config->set_key_value(opt_key, config.option(opt_key)->clone()); - modified = 1; - } - if (modified) { - update_dirty(); - //# Initialize UI components with the config values. - reload_config(); - update(); - } -} + if (m_type == Preset::TYPE_SLA_PRINT) + update_description_lines(); -// Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. -void Tab::reload_config() -{ - if (m_active_page) - m_active_page->reload_config(); -} + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); + Thaw(); + } -void Tab::update_mode() -{ - m_mode = wxGetApp().get_mode(); + void Tab::msw_rescale() + { + m_em_unit = em_unit(m_parent); - //BBS: GUI refactor - // update mode for ModeSizer - //if (m_mode_sizer) - // m_mode_sizer->SetMode(m_mode); + m_top_sizer->SetMinSize(-1, 3 * m_em_unit); - update_visibility(); + // BBS: GUI refactor + // if (m_mode_sizer) + // m_mode_sizer->msw_rescale(); + if (m_presets_choice) + m_presets_choice->msw_rescale(); - update_changed_tree_ui(); -} + m_tabctrl->SetMinSize(wxSize(20 * m_em_unit, -1)); -void Tab::update_visibility() -{ - Freeze(); // There is needed Freeze/Thaw to avoid a flashing after Show/Layout + // rescale buttons and cached bitmaps + for (const auto btn : m_scaled_buttons) + btn->msw_rescale(); + for (const auto bmp : m_scaled_bitmaps) + bmp->msw_rescale(); - for (auto page : m_pages) - page->update_visibility(m_mode, page.get() == m_active_page); - rebuild_page_tree(); + if (m_mode_view) + m_mode_view->Rescale(); + if (m_extruder_switch) + m_extruder_switch->Rescale(); + if (m_variant_combo) + m_variant_combo->Rescale(); - if (m_type == Preset::TYPE_SLA_PRINT) - update_description_lines(); + if (m_detach_preset_btn) + m_detach_preset_btn->msw_rescale(); - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); - Thaw(); -} + // rescale icons for tree_ctrl + for (ScalableBitmap &bmp : m_scaled_icons_list) + bmp.msw_rescale(); + // recreate and set new ImageList for tree_ctrl + m_icons->RemoveAll(); + m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight(), false); + for (ScalableBitmap &bmp : m_scaled_icons_list) + // m_icons->Add(bmp.bmp()); + m_tabctrl->AssignImageList(m_icons); -void Tab::msw_rescale() -{ - m_em_unit = em_unit(m_parent); - - m_top_sizer->SetMinSize(-1, 3 * m_em_unit); - - //BBS: GUI refactor - //if (m_mode_sizer) - // m_mode_sizer->msw_rescale(); - if (m_presets_choice) - m_presets_choice->msw_rescale(); - - m_tabctrl->SetMinSize(wxSize(20 * m_em_unit, -1)); - - // rescale buttons and cached bitmaps - for (const auto btn : m_scaled_buttons) - btn->msw_rescale(); - for (const auto bmp : m_scaled_bitmaps) - bmp->msw_rescale(); - - if (m_mode_view) - m_mode_view->Rescale(); - if (m_extruder_switch) - m_extruder_switch->Rescale(); - if (m_variant_combo) - m_variant_combo->Rescale(); - - if (m_detach_preset_btn) - m_detach_preset_btn->msw_rescale(); - - // rescale icons for tree_ctrl - for (ScalableBitmap& bmp : m_scaled_icons_list) - bmp.msw_rescale(); - // recreate and set new ImageList for tree_ctrl - m_icons->RemoveAll(); - m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight(), false); - for (ScalableBitmap& bmp : m_scaled_icons_list) - //m_icons->Add(bmp.bmp()); - m_tabctrl->AssignImageList(m_icons); - - // rescale options_groups - if (m_active_page) - m_active_page->msw_rescale(); - - m_tabctrl->Rescale(); - - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); -} - -void Tab::sys_color_changed() -{ - if (m_presets_choice) - m_presets_choice->sys_color_changed(); - - // update buttons and cached bitmaps - for (const auto btn : m_scaled_buttons) - btn->msw_rescale(); - for (const auto bmp : m_scaled_bitmaps) - bmp->msw_rescale(); - if (m_detach_preset_btn) - m_detach_preset_btn->msw_rescale(); - if (m_extruder_sync) - m_extruder_sync->msw_rescale(); - - // update icons for tree_ctrl - for (ScalableBitmap& bmp : m_scaled_icons_list) - bmp.msw_rescale(); - // recreate and set new ImageList for tree_ctrl - m_icons->RemoveAll(); - m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight(), false); - for (ScalableBitmap& bmp : m_scaled_icons_list) - //m_icons->Add(bmp.bmp()); - m_tabctrl->AssignImageList(m_icons); - - // Colors for ui "decoration" - update_label_colours(); + // rescale options_groups + if (m_active_page) + m_active_page->msw_rescale(); + + m_tabctrl->Rescale(); + + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); + } + + void Tab::sys_color_changed() + { + if (m_presets_choice) + m_presets_choice->sys_color_changed(); + + // update buttons and cached bitmaps + for (const auto btn : m_scaled_buttons) + btn->msw_rescale(); + for (const auto bmp : m_scaled_bitmaps) + bmp->msw_rescale(); + if (m_detach_preset_btn) + m_detach_preset_btn->msw_rescale(); + if (m_extruder_sync) + m_extruder_sync->msw_rescale(); + + // update icons for tree_ctrl + for (ScalableBitmap &bmp : m_scaled_icons_list) + bmp.msw_rescale(); + // recreate and set new ImageList for tree_ctrl + m_icons->RemoveAll(); + m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight(), false); + for (ScalableBitmap &bmp : m_scaled_icons_list) + // m_icons->Add(bmp.bmp()); + m_tabctrl->AssignImageList(m_icons); + + // Colors for ui "decoration" + update_label_colours(); #ifdef _WIN32 - wxWindowUpdateLocker noUpdates(this); - //BBS: GUI refactor - //if (m_mode_sizer) - // m_mode_sizer->msw_rescale(); - wxGetApp().UpdateDarkUI(this); - wxGetApp().UpdateDarkUI(m_tabctrl); + wxWindowUpdateLocker noUpdates(this); + // BBS: GUI refactor + // if (m_mode_sizer) + // m_mode_sizer->msw_rescale(); + wxGetApp().UpdateDarkUI(this); + wxGetApp().UpdateDarkUI(m_tabctrl); #endif - update_changed_tree_ui(); + update_changed_tree_ui(); - // update options_groups - if (m_active_page) - m_active_page->sys_color_changed(); - if (m_extruder_switch) - m_extruder_switch->Rescale(); + // update options_groups + if (m_active_page) + m_active_page->sys_color_changed(); + if (m_extruder_switch) + m_extruder_switch->Rescale(); - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); -} + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); + } -Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const -{ - return m_active_page ? m_active_page->get_field(opt_key, opt_index) : nullptr; -} + Field *Tab::get_field(const t_config_option_key &opt_key, int opt_index /* = -1*/) const + { + return m_active_page ? m_active_page->get_field(opt_key, opt_index) : nullptr; + } -std::pair Tab::get_custom_ctrl_with_blinking_ptr(const t_config_option_key& opt_key, int opt_index/* = -1*/) -{ - if (!m_active_page) - return {nullptr, nullptr}; + std::pair Tab::get_custom_ctrl_with_blinking_ptr(const t_config_option_key &opt_key, int opt_index /* = -1*/) + { + if (!m_active_page) + return {nullptr, nullptr}; - std::pair ret = {nullptr, nullptr}; + std::pair ret = {nullptr, nullptr}; - for (auto opt_group : m_active_page->m_optgroups) { - ret = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, opt_index); - if (ret.first && ret.second) - break; - } - return ret; -} + for (auto opt_group : m_active_page->m_optgroups) + { + ret = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, opt_index); + if (ret.first && ret.second) + break; + } + return ret; + } -Field* Tab::get_field(const t_config_option_key& opt_key, Page** selected_page, int opt_index/* = -1*/) -{ - Field* field = nullptr; - for (auto page : m_pages) { - field = page->get_field(opt_key, opt_index); - if (field != nullptr) { - *selected_page = page.get(); + Field *Tab::get_field(const t_config_option_key &opt_key, Page **selected_page, int opt_index /* = -1*/) + { + Field *field = nullptr; + for (auto page : m_pages) + { + field = page->get_field(opt_key, opt_index); + if (field != nullptr) + { + *selected_page = page.get(); + return field; + } + } return field; } - } - return field; -} -void Tab::toggle_option(const std::string& opt_key, bool toggle, int opt_index/* = -1*/) -{ - if (!m_active_page) - return; - Field *field = m_active_page->get_field(opt_key, opt_index); - if (field) - field->toggle(toggle); -} - -void Tab::toggle_line(const std::string &opt_key, bool toggle, int opt_index) -{ - if (!m_active_page) return; - Line *line = m_active_page->get_line(opt_key, opt_index); - if (line) line->toggle_visible = toggle; -}; - -// To be called by custom widgets, load a value into a config, -// update the preset selection boxes (the dirty flags) -// If value is saved before calling this function, put saved_value = true, -// and value can be some random value because in this case it will not been used -void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value /*= false*/) -{ - if (!saved_value) change_opt_value(*m_config, opt_key, value); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - if (opt_key == "compatible_printers" || opt_key == "compatible_prints") { - // Don't select another profile if this profile happens to become incompatible. - m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); - } - if (m_presets_choice) - m_presets_choice->update_dirty(); - on_presets_changed(); - update(); -} + void Tab::toggle_option(const std::string &opt_key, bool toggle, int opt_index /* = -1*/) + { + if (!m_active_page) + return; + Field *field = m_active_page->get_field(opt_key, opt_index); + if (field) + field->toggle(toggle); + } -static wxString support_combo_value_for_config(const DynamicPrintConfig &config, bool is_fff) -{ - const std::string support = is_fff ? "enable_support" : "supports_enable"; - const std::string buildplate_only = is_fff ? "support_on_build_plate_only" : "support_buildplate_only"; + void Tab::toggle_line(const std::string &opt_key, bool toggle, int opt_index) + { + if (!m_active_page) + return; + Line *line = m_active_page->get_line(opt_key, opt_index); + if (line) + line->toggle_visible = toggle; + }; + + // To be called by custom widgets, load a value into a config, + // update the preset selection boxes (the dirty flags) + // If value is saved before calling this function, put saved_value = true, + // and value can be some random value because in this case it will not been used + void Tab::load_key_value(const std::string &opt_key, const boost::any &value, bool saved_value /*= false*/) + { + if (!saved_value) + change_opt_value(*m_config, opt_key, value); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + if (opt_key == "compatible_printers" || opt_key == "compatible_prints") + { + // Don't select another profile if this profile happens to become incompatible. + m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); + } + if (m_presets_choice) + m_presets_choice->update_dirty(); + on_presets_changed(); + update(); + } + + static wxString support_combo_value_for_config(const DynamicPrintConfig &config, bool is_fff) + { + const std::string support = is_fff ? "enable_support" : "supports_enable"; + const std::string buildplate_only = is_fff ? "support_on_build_plate_only" : "support_buildplate_only"; - // BBS + // BBS #if 0 return ! config.opt_bool(support) ? @@ -1706,1148 +1848,1268 @@ static wxString support_combo_value_for_config(const DynamicPrintConfig &config, (config.opt_bool(buildplate_only) ? _("Support on build plate only") : _("Everywhere")); #else - if (config.opt_bool(support)) { - return (config.opt_bool(buildplate_only) ? _("Support on build plate only") : _("Everywhere")); - } else { - return _("For support enforcers only"); - } + if (config.opt_bool(support)) + { + return (config.opt_bool(buildplate_only) ? _("Support on build plate only") : _("Everywhere")); + } + else + { + return _("For support enforcers only"); + } #endif -} - -static wxString pad_combo_value_for_config(const DynamicPrintConfig &config) -{ - return config.opt_bool("pad_enable") ? (config.opt_bool("pad_around_object") ? _("Around object") : _("Below object")) : _("None"); -} - -void Tab::on_value_change(const std::string& opt_key, const boost::any& value) -{ - if (wxGetApp().plater() == nullptr) { - return; - } + } - const bool is_fff = supports_printer_technology(ptFFF); - ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); - //BBS: GUI refactor - if (og_freq_chng_params) { - if (opt_key == "sparse_infill_density" || opt_key == "pad_enable") + static wxString pad_combo_value_for_config(const DynamicPrintConfig &config) { - boost::any val = og_freq_chng_params->get_config_value(*m_config, opt_key); - og_freq_chng_params->set_value(opt_key, val); + return config.opt_bool("pad_enable") ? (config.opt_bool("pad_around_object") ? _("Around object") : _("Below object")) : _("None"); } - if (opt_key == "pad_around_object") { - for (PageShp& pg : m_pages) { - Field* fld = pg->get_field(opt_key); /// !!! ysFIXME ???? - if (fld) fld->set_value(value, false); + void Tab::on_value_change(const std::string &opt_key, const boost::any &value) + { + if (wxGetApp().plater() == nullptr) + { + return; } - } - if (is_fff ? - (opt_key == "enable_support" || opt_key == "support_type" || opt_key == "support_on_build_plate_only") : - (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) - og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); + const bool is_fff = supports_printer_technology(ptFFF); + ConfigOptionsGroup *og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); + // BBS: GUI refactor + if (og_freq_chng_params) + { + if (opt_key == "sparse_infill_density" || opt_key == "pad_enable") + { + boost::any val = og_freq_chng_params->get_config_value(*m_config, opt_key); + og_freq_chng_params->set_value(opt_key, val); + } - if (!is_fff && (opt_key == "pad_enable" || opt_key == "pad_around_object")) - og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); + if (opt_key == "pad_around_object") + { + for (PageShp &pg : m_pages) + { + Field *fld = pg->get_field(opt_key); /// !!! ysFIXME ???? + if (fld) + fld->set_value(value, false); + } + } - if (opt_key == "brim_width") - { - bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; - og_freq_chng_params->set_value("brim", val); - } - } + if (is_fff ? (opt_key == "enable_support" || opt_key == "support_type" || opt_key == "support_on_build_plate_only") : (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) + og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); - if (opt_key == "sparse_infill_density") { - ConfigOptionPercent density = *m_config->option("sparse_infill_density"); - DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("skeleton_infill_density", new ConfigOptionPercent(density)); - new_conf.set_key_value("skin_infill_density", new ConfigOptionPercent(density)); - m_config_manipulation.apply(m_config, &new_conf); - } + if (!is_fff && (opt_key == "pad_enable" || opt_key == "pad_around_object")) + og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); - if (opt_key == "sparse_infill_line_width") { - ConfigOptionFloat line_width = *m_config->option("sparse_infill_line_width"); - DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("skin_infill_line_width", new ConfigOptionFloat(line_width)); - new_conf.set_key_value("skeleton_infill_line_width", new ConfigOptionFloat(line_width)); - m_config_manipulation.apply(m_config, &new_conf); - } + if (opt_key == "brim_width") + { + bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; + og_freq_chng_params->set_value("brim", val); + } + } + + if (opt_key == "sparse_infill_density") + { + ConfigOptionPercent density = *m_config->option("sparse_infill_density"); + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("skeleton_infill_density", new ConfigOptionPercent(density)); + new_conf.set_key_value("skin_infill_density", new ConfigOptionPercent(density)); + m_config_manipulation.apply(m_config, &new_conf); + } + + if (opt_key == "sparse_infill_line_width") + { + ConfigOptionFloat line_width = *m_config->option("sparse_infill_line_width"); + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("skin_infill_line_width", new ConfigOptionFloat(line_width)); + new_conf.set_key_value("skeleton_infill_line_width", new ConfigOptionFloat(line_width)); + m_config_manipulation.apply(m_config, &new_conf); + } + + if (opt_key == "single_extruder_multi_material" || opt_key == "extruders_count") + update_wiping_button_visibility(); + + if (opt_key == "enable_prime_tower") + { + auto timelapse_type = m_config->option>("timelapse_type"); + bool timelapse_enabled = timelapse_type->value == TimelapseType::tlSmooth; + if (!boost::any_cast(value)) + { + + { // Add prime tower disabling confirm dialog for multi nozzle + const auto &extruder_max_nozzle_count_temp = + GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config.option("extruder_max_nozzle_count")->values; + int max_total_nozzle = std::accumulate(extruder_max_nozzle_count_temp.begin(), extruder_max_nozzle_count_temp.end(), 0); + if (max_total_nozzle >= 2) + { + MessageDialog dlg( + wxGetApp().plater(), + _L("Prime tower is required for nozzle changing. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?"), + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_NO) + { + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); + m_config_manipulation.apply(m_config, &new_conf); + } + } + } - if (opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) - update_wiping_button_visibility(); - - if (opt_key == "enable_prime_tower") { - auto timelapse_type = m_config->option>("timelapse_type"); - bool timelapse_enabled = timelapse_type->value == TimelapseType::tlSmooth; - if (!boost::any_cast(value)) { - - {// Add prime tower disabling confirm dialog for multi nozzle - const auto &extruder_max_nozzle_count_temp = - GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config.option("extruder_max_nozzle_count")->values; - int max_total_nozzle = std::accumulate(extruder_max_nozzle_count_temp.begin(), extruder_max_nozzle_count_temp.end(), 0); - if (max_total_nozzle>=2 ) { - MessageDialog dlg( - wxGetApp().plater(), - _L("Prime tower is required for nozzle changing. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?"), - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - if (dlg.ShowModal() == wxID_NO) { + bool set_enable_prime_tower = false; + if (timelapse_enabled) + { + MessageDialog + dlg(wxGetApp().plater(), + _L("Prime tower is required for smooth timeplase. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?"), + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_NO) + { + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); + m_config_manipulation.apply(m_config, &new_conf); + set_enable_prime_tower = true; + } + } + bool enable_wrapping = m_config->option("enable_wrapping_detection")->value; + if (enable_wrapping && !set_enable_prime_tower) + { + MessageDialog dlg(wxGetApp().plater(), + _L("Prime tower is required for clumping detection. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?"), + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_NO) + { + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); + m_config_manipulation.apply(m_config, &new_conf); + set_enable_prime_tower = true; + } + } + wxGetApp().plater()->update(); + } + bool is_precise_z_height = m_config->option("precise_z_height")->value; + if (boost::any_cast(value) && is_precise_z_height) + { + MessageDialog dlg(wxGetApp().plater(), _L("Enabling both precise Z height and the prime tower may cause the size of prime tower to increase. Do you still want to enable?"), + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_NO) + { DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); + new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(false)); m_config_manipulation.apply(m_config, &new_conf); } + wxGetApp().plater()->update(); } + update_wiping_button_visibility(); } - bool set_enable_prime_tower = false; - if (timelapse_enabled) { - MessageDialog - dlg(wxGetApp().plater(), - _L("Prime tower is required for smooth timeplase. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?"), - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - if (dlg.ShowModal() == wxID_NO) { - DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); - m_config_manipulation.apply(m_config, &new_conf); - set_enable_prime_tower = true; + if (opt_key == "enable_wrapping_detection") + { + bool wipe_tower_enabled = m_config->option("enable_prime_tower")->value; + if (boost::any_cast(value) && !wipe_tower_enabled) + { + MessageDialog dlg(wxGetApp().plater(), + _L("Prime tower is required for clumping detection. There may be flaws on the model without prime tower. Do you still want to enable clumping detection?"), + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_NO) + { + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("enable_wrapping_detection", new ConfigOptionBool(false)); + m_config_manipulation.apply(m_config, &new_conf); + wxGetApp().plater()->update(); + } + } + else + { + wxGetApp().plater()->update(); } } - bool enable_wrapping = m_config->option("enable_wrapping_detection")->value; - if (enable_wrapping && !set_enable_prime_tower) { - MessageDialog dlg(wxGetApp().plater(), - _L("Prime tower is required for clumping detection. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?"), - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - if (dlg.ShowModal() == wxID_NO) { - DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); - m_config_manipulation.apply(m_config, &new_conf); - set_enable_prime_tower = true; + + if (opt_key == "precise_z_height") + { + bool wipe_tower_enabled = m_config->option("enable_prime_tower")->value; + if (boost::any_cast(value) && wipe_tower_enabled) + { + MessageDialog dlg(wxGetApp().plater(), _L("Enabling both precise Z height and the prime tower may cause the size of prime tower to increase. Do you still want to enable?"), + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_NO) + { + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("precise_z_height", new ConfigOptionBool(false)); + m_config_manipulation.apply(m_config, &new_conf); + } + wxGetApp().plater()->update(); } } - wxGetApp().plater()->update(); - } - bool is_precise_z_height = m_config->option("precise_z_height")->value; - if (boost::any_cast(value) && is_precise_z_height) { - MessageDialog dlg(wxGetApp().plater(), _L("Enabling both precise Z height and the prime tower may cause the size of prime tower to increase. Do you still want to enable?"), - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - if (dlg.ShowModal() == wxID_NO) { - DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(false)); - m_config_manipulation.apply(m_config, &new_conf); + + // reload scene to update timelapse wipe tower + if (opt_key == "timelapse_type") + { + bool wipe_tower_enabled = m_config->option("enable_prime_tower")->value; + if (!wipe_tower_enabled && boost::any_cast(value) == (int)TimelapseType::tlSmooth) + { + MessageDialog dlg(wxGetApp().plater(), _L("Prime tower is required for smooth timelapse. There may be flaws on the model without prime tower. Do you want to enable prime tower?"), + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + { + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); + m_config_manipulation.apply(m_config, &new_conf); + wxGetApp().plater()->update(); + } + } + else + { + wxGetApp().plater()->update(); + } } - wxGetApp().plater()->update(); - } - update_wiping_button_visibility(); - } - if (opt_key == "enable_wrapping_detection") { - bool wipe_tower_enabled = m_config->option("enable_prime_tower")->value; - if (boost::any_cast(value) && !wipe_tower_enabled) { - MessageDialog dlg(wxGetApp().plater(), - _L("Prime tower is required for clumping detection. There may be flaws on the model without prime tower. Do you still want to enable clumping detection?"), - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - if (dlg.ShowModal() == wxID_NO) { - DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("enable_wrapping_detection", new ConfigOptionBool(false)); - m_config_manipulation.apply(m_config, &new_conf); - wxGetApp().plater()->update(); + if (opt_key == "print_sequence" && m_config->opt_enum("print_sequence") == PrintSequence::ByObject) + { + auto printer_structure_opt = m_preset_bundle->printers.get_edited_preset().config.option>("printer_structure"); + if (printer_structure_opt && printer_structure_opt->value == PrinterStructure::psI3) + { + wxString msg_text = _(L("The current printer does not support timelapse in Traditional Mode when printing By-Object.")); + msg_text += "\n\n" + _(L("Still print by object?")); + + MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); + auto answer = dialog.ShowModal(); + if (answer == wxID_NO) + { + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("print_sequence", new ConfigOptionEnum(PrintSequence::ByLayer)); + m_config_manipulation.apply(m_config, &new_conf); + wxGetApp().plater()->update(); + } + } } - } else { - wxGetApp().plater()->update(); - } - } - if (opt_key == "precise_z_height") { - bool wipe_tower_enabled = m_config->option("enable_prime_tower")->value; - if (boost::any_cast(value) && wipe_tower_enabled) { - MessageDialog dlg(wxGetApp().plater(), _L("Enabling both precise Z height and the prime tower may cause the size of prime tower to increase. Do you still want to enable?"), - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - if (dlg.ShowModal() == wxID_NO) { + // BBS set support style to default when support type changes + if (opt_key == "support_type") + { DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("precise_z_height", new ConfigOptionBool(false)); + new_conf.set_key_value("support_style", new ConfigOptionEnum(smsDefault)); m_config_manipulation.apply(m_config, &new_conf); } - wxGetApp().plater()->update(); - } - } - // reload scene to update timelapse wipe tower - if (opt_key == "timelapse_type") { - bool wipe_tower_enabled = m_config->option("enable_prime_tower")->value; - if (!wipe_tower_enabled && boost::any_cast(value) == (int)TimelapseType::tlSmooth) { - MessageDialog dlg(wxGetApp().plater(), _L("Prime tower is required for smooth timelapse. There may be flaws on the model without prime tower. Do you want to enable prime tower?"), - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) { - DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); - m_config_manipulation.apply(m_config, &new_conf); - wxGetApp().plater()->update(); + if (opt_key == "support_filament") + { + int filament_id = m_config->opt_int("support_filament") - 1; // the displayed id is based from 1, while internal id is based from 0 + int interface_filament_id = m_config->opt_int("support_interface_filament") - 1; + auto &filament_presets = Slic3r::GUI::wxGetApp().preset_bundle->filament_presets; + auto &filaments = Slic3r::GUI::wxGetApp().preset_bundle->filaments; + bool support_TPU = false; + if (filament_id >= 0 && filament_id < filament_presets.size()) + { + Slic3r::Preset *filament = filaments.find_preset(filament_presets[filament_id]); + if (filament) + { + std::string filament_type = filament->config.option("filament_type")->values[0]; + support_TPU = filament_type == "PLA" && has_filaments({"TPU", "TPU-AMS"}); + } + } + if (is_support_filament(filament_id, false) && !is_soluble_filament(filament_id) && !has_filaments({"TPU", "TPU-AMS"})) + { + wxString msg_text = _L("Non-soluble support materials are not recommended for support base. \n" + "Are you sure to use them for support base? \n"); + MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_NO) + { + new_conf.set_key_value("support_filament", new ConfigOptionInt(0)); + m_config_manipulation.apply(m_config, &new_conf); + on_value_change(opt_key, 0); + } + wxGetApp().plater()->update(); + } + if ((is_soluble_filament(filament_id) || support_TPU) && + !(m_config->opt_float("support_top_z_distance") == 0 && m_config->opt_float("support_interface_spacing") == 0 && + m_config->opt_float("support_object_xy_distance") == 0 /*&& m_config->opt_bool("top_z_overrides_xy_distance")*/ && + m_config->opt_enum("support_interface_pattern") == SupportMaterialInterfacePattern::smipRectilinearInterlaced && + filament_id == interface_filament_id)) + { + wxString msg_text; + if (support_TPU) + msg_text = _L("When using PLA to support TPU, We recommend the following settings:\n" + "0 top z distance, 0 interface spacing, 0 support/object xy distance, interlaced rectilinear pattern, disable \n" + "independent support layer height and use PLA for both support interface and support base"); + else + msg_text = _L("When using soluble material for the support, We recommend the following settings:\n" + "0 top z distance, 0 interface spacing, 0 support/object xy distance, interlaced rectilinear pattern, disable \n" + "independent support layer height and use soluble materials for both support interface and support base"); + msg_text += "\n\n" + _L("Change these settings automatically? \n" + "Yes - Change these settings automatically\n" + "No - Do not change these settings for me"); + MessageDialog dialog(wxGetApp().plater(), msg_text, "Suggestion", wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) + { + new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(0)); + new_conf.set_key_value("support_interface_spacing", new ConfigOptionFloat(0)); + new_conf.set_key_value("support_object_xy_distance", new ConfigOptionFloat(0)); + new_conf.set_key_value("support_interface_pattern", + new ConfigOptionEnum(SupportMaterialInterfacePattern::smipRectilinearInterlaced)); + // new_conf.set_key_value("top_z_overrides_xy_distance", new ConfigOptionBool(true)); + new_conf.set_key_value("independent_support_layer_height", new ConfigOptionBool(false)); + new_conf.set_key_value("support_interface_filament", new ConfigOptionInt(filament_id + 1)); + m_config_manipulation.apply(m_config, &new_conf); + } + wxGetApp().plater()->update(); + } } - } else { - wxGetApp().plater()->update(); - } - } - if (opt_key == "print_sequence" && m_config->opt_enum("print_sequence") == PrintSequence::ByObject) { - auto printer_structure_opt = m_preset_bundle->printers.get_edited_preset().config.option>("printer_structure"); - if (printer_structure_opt && printer_structure_opt->value == PrinterStructure::psI3) { - wxString msg_text = _(L("The current printer does not support timelapse in Traditional Mode when printing By-Object.")); - msg_text += "\n\n" + _(L("Still print by object?")); + // BBS popup a message to ask the user to set optimum parameters for support interface if support materials are used + if (opt_key == "support_interface_filament") + { + int filament_id = m_config->opt_int("support_filament") - 1; + int interface_filament_id = m_config->opt_int("support_interface_filament") - 1; // the displayed id is based from 1, while internal id is based from 0 + auto &filament_presets = Slic3r::GUI::wxGetApp().preset_bundle->filament_presets; + auto &filaments = Slic3r::GUI::wxGetApp().preset_bundle->filaments; + bool support_TPU = false; + if (interface_filament_id >= 0 && interface_filament_id < filament_presets.size()) + { + Slic3r::Preset *filament = filaments.find_preset(filament_presets[interface_filament_id]); + if (filament) + { + std::string filament_type = filament->config.option("filament_type")->values[0]; + support_TPU = filament_type == "PLA" && has_filaments({"TPU", "TPU-AMS"}); + } + } - MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); - auto answer = dialog.ShowModal(); - if (answer == wxID_NO) { - DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("print_sequence", new ConfigOptionEnum(PrintSequence::ByLayer)); - m_config_manipulation.apply(m_config, &new_conf); - wxGetApp().plater()->update(); + if ((is_support_filament(interface_filament_id, false) && + !(m_config->opt_float("support_top_z_distance") == 0 && m_config->opt_float("support_interface_spacing") == 0 /*&& m_config->opt_bool("top_z_overrides_xy_distance")*/ && + m_config->opt_enum("support_interface_pattern") == SupportMaterialInterfacePattern::smipRectilinearInterlaced && + (support_TPU ? m_config->opt_float("support_object_xy_distance") == 0 : -1))) || + (is_soluble_filament(interface_filament_id) && !is_soluble_filament(filament_id))) + { + wxString msg_text; + if (support_TPU) + { + msg_text = _L("When using PLA to support TPU, We recommend the following settings:\n" + "0 top z distance, 0 interface spacing, 0 support/object xy distance, interlaced rectilinear pattern, disable \n" + "independent support layer height and use PLA for both support interface and support base"); + } + else if (!is_soluble_filament(interface_filament_id)) + { + msg_text = _L("When using support material for the support interface, We recommend the following settings:\n" + "0 top z distance, 0 interface spacing, interlaced rectilinear pattern and disable independent support layer height"); + } + else + { + msg_text = _L("When using soluble material for the support interface, We recommend the following settings:\n" + "0 top z distance, 0 interface spacing, 0 support/object xy distance, interlaced rectilinear pattern, disable \n" + "independent support layer height and use soluble materials for both support interface and support base"); + } + msg_text += "\n\n" + _L("Change these settings automatically? \n" + "Yes - Change these settings automatically\n" + "No - Do not change these settings for me"); + MessageDialog dialog(wxGetApp().plater(), msg_text, "Suggestion", wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) + { + new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(0)); + new_conf.set_key_value("support_interface_spacing", new ConfigOptionFloat(0)); + new_conf.set_key_value("support_interface_pattern", + new ConfigOptionEnum(SupportMaterialInterfacePattern::smipRectilinearInterlaced)); + // new_conf.set_key_value("top_z_overrides_xy_distance", new ConfigOptionBool(true)); + new_conf.set_key_value("independent_support_layer_height", new ConfigOptionBool(false)); + if (support_TPU || (is_soluble_filament(interface_filament_id) && !is_soluble_filament(filament_id))) + { + new_conf.set_key_value("support_object_xy_distance", new ConfigOptionFloat(0)); + new_conf.set_key_value("support_filament", new ConfigOptionInt(interface_filament_id + 1)); + } + m_config_manipulation.apply(m_config, &new_conf); + } + wxGetApp().plater()->update(); + } } - } - } - // BBS set support style to default when support type changes - if (opt_key == "support_type") { - DynamicPrintConfig new_conf = *m_config; - new_conf.set_key_value("support_style", new ConfigOptionEnum(smsDefault)); - m_config_manipulation.apply(m_config, &new_conf); - } + if (opt_key == "layer_height") + { + auto min_layer_height_from_nozzle = m_preset_bundle->full_config().option("min_layer_height")->values; + auto max_layer_height_from_nozzle = m_preset_bundle->full_config().option("max_layer_height")->values; + auto layer_height_floor = *std::min_element(min_layer_height_from_nozzle.begin(), min_layer_height_from_nozzle.end()); + auto layer_height_ceil = *std::max_element(max_layer_height_from_nozzle.begin(), max_layer_height_from_nozzle.end()); + float layer_height = m_config->opt_float("layer_height"); + bool exceed_minimum_flag = (layer_height_floor - layer_height) > EPSILON; + bool exceed_maximum_flag = (layer_height - layer_height_ceil) > EPSILON; + + if (exceed_maximum_flag || exceed_minimum_flag) + { + if (layer_height < EPSILON) + { + wxString msg_text = _(L("Layer height is too small.\nIt will set to min_layer_height\n")); + MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxOK); + dialog.SetButtonLabel(wxID_OK, _L("OK")); + dialog.ShowModal(); + auto new_conf = *m_config; + new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_floor)); + m_config_manipulation.apply(m_config, &new_conf); + } + else + { + wxString msg_text = _(L("Layer height exceeds the limit in Printer Settings -> Extruder -> Layer height limits ,this may cause printing quality issues.")); + msg_text += "\n\n" + _(L("Adjust to the set range automatically? \n")); + MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); + dialog.SetButtonLabel(wxID_YES, _L("Adjust")); + dialog.SetButtonLabel(wxID_NO, _L("Ignore")); + auto answer = dialog.ShowModal(); + auto new_conf = *m_config; + if (answer == wxID_YES) + { + if (exceed_maximum_flag) + new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_ceil)); + if (exceed_minimum_flag) + new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_floor)); + m_config_manipulation.apply(m_config, &new_conf); + } + wxGetApp().plater()->update(); + } + } + } - if (opt_key == "support_filament") { - int filament_id = m_config->opt_int("support_filament") - 1; // the displayed id is based from 1, while internal id is based from 0 - int interface_filament_id = m_config->opt_int("support_interface_filament") - 1; - auto &filament_presets = Slic3r::GUI::wxGetApp().preset_bundle->filament_presets; - auto &filaments = Slic3r::GUI::wxGetApp().preset_bundle->filaments; - bool support_TPU = false; - if (filament_id >= 0 && filament_id < filament_presets.size()) { - Slic3r::Preset *filament = filaments.find_preset(filament_presets[filament_id]); - if (filament) { - std::string filament_type = filament->config.option("filament_type")->values[0]; - support_TPU = filament_type == "PLA" && has_filaments({"TPU", "TPU-AMS"}); - } - } - if (is_support_filament(filament_id, false) && !is_soluble_filament(filament_id) && !has_filaments({"TPU", "TPU-AMS"})) { - wxString msg_text = _L("Non-soluble support materials are not recommended for support base. \n" - "Are you sure to use them for support base? \n"); - MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog.ShowModal() == wxID_NO) { - new_conf.set_key_value("support_filament", new ConfigOptionInt(0)); - m_config_manipulation.apply(m_config, &new_conf); - on_value_change(opt_key, 0); - } - wxGetApp().plater()->update(); - } - if ((is_soluble_filament(filament_id) || support_TPU) && - !(m_config->opt_float("support_top_z_distance") == 0 && m_config->opt_float("support_interface_spacing") == 0 && - m_config->opt_float("support_object_xy_distance") == 0 /*&& m_config->opt_bool("top_z_overrides_xy_distance")*/ && - m_config->opt_enum("support_interface_pattern") == SupportMaterialInterfacePattern::smipRectilinearInterlaced && - filament_id == interface_filament_id)) { - wxString msg_text; - if (support_TPU) - msg_text = _L("When using PLA to support TPU, We recommend the following settings:\n" - "0 top z distance, 0 interface spacing, 0 support/object xy distance, interlaced rectilinear pattern, disable \n" - "independent support layer height and use PLA for both support interface and support base"); - else - msg_text = _L("When using soluble material for the support, We recommend the following settings:\n" - "0 top z distance, 0 interface spacing, 0 support/object xy distance, interlaced rectilinear pattern, disable \n" - "independent support layer height and use soluble materials for both support interface and support base"); - msg_text += "\n\n" + _L("Change these settings automatically? \n" - "Yes - Change these settings automatically\n" - "No - Do not change these settings for me"); - MessageDialog dialog(wxGetApp().plater(), msg_text, "Suggestion", wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog.ShowModal() == wxID_YES) { - new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(0)); - new_conf.set_key_value("support_interface_spacing", new ConfigOptionFloat(0)); - new_conf.set_key_value("support_object_xy_distance", new ConfigOptionFloat(0)); - new_conf.set_key_value("support_interface_pattern", - new ConfigOptionEnum(SupportMaterialInterfacePattern::smipRectilinearInterlaced)); - //new_conf.set_key_value("top_z_overrides_xy_distance", new ConfigOptionBool(true)); - new_conf.set_key_value("independent_support_layer_height", new ConfigOptionBool(false)); - new_conf.set_key_value("support_interface_filament", new ConfigOptionInt(filament_id + 1)); - m_config_manipulation.apply(m_config, &new_conf); + string opt_key_without_idx = opt_key.substr(0, opt_key.find('#')); + + if (opt_key_without_idx == "long_retractions_when_cut") + { + unsigned char activate = boost::any_cast(value); + if (activate == 1) + { + MessageDialog dialog(wxGetApp().plater(), + _L("Experimental feature: Retracting and cutting off the filament at a greater distance during filament changes to minimize flush." + "Although it can notably reduce flush, it may also elevate the risk of nozzle clogs or other printing complications."), + "", wxICON_WARNING | wxOK); + dialog.ShowModal(); + } + if (wxGetApp().app_config->get("auto_calculate_flush") == "all") + { + wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(-1); + } } - wxGetApp().plater()->update(); - } - } - // BBS popup a message to ask the user to set optimum parameters for support interface if support materials are used - if (opt_key == "support_interface_filament") { - int filament_id = m_config->opt_int("support_filament") - 1; - int interface_filament_id = m_config->opt_int("support_interface_filament") - 1; // the displayed id is based from 1, while internal id is based from 0 - auto &filament_presets = Slic3r::GUI::wxGetApp().preset_bundle->filament_presets; - auto &filaments = Slic3r::GUI::wxGetApp().preset_bundle->filaments; - bool support_TPU = false; - if (interface_filament_id >= 0 && interface_filament_id < filament_presets.size()) { - Slic3r::Preset *filament = filaments.find_preset(filament_presets[interface_filament_id]); - if (filament) { - std::string filament_type = filament->config.option("filament_type")->values[0]; - support_TPU = filament_type == "PLA" && has_filaments({"TPU", "TPU-AMS"}); - } - } - - if ((is_support_filament(interface_filament_id, false) && - !(m_config->opt_float("support_top_z_distance") == 0 && m_config->opt_float("support_interface_spacing") == 0 /*&& m_config->opt_bool("top_z_overrides_xy_distance")*/ && - m_config->opt_enum("support_interface_pattern") == SupportMaterialInterfacePattern::smipRectilinearInterlaced && - (support_TPU ? m_config->opt_float("support_object_xy_distance") == 0 : -1))) || - (is_soluble_filament(interface_filament_id) && !is_soluble_filament(filament_id))) { - wxString msg_text; - if (support_TPU) { - msg_text = _L("When using PLA to support TPU, We recommend the following settings:\n" - "0 top z distance, 0 interface spacing, 0 support/object xy distance, interlaced rectilinear pattern, disable \n" - "independent support layer height and use PLA for both support interface and support base"); - } else if (!is_soluble_filament(interface_filament_id)) { - msg_text = _L("When using support material for the support interface, We recommend the following settings:\n" - "0 top z distance, 0 interface spacing, interlaced rectilinear pattern and disable independent support layer height"); - } else { - msg_text = _L("When using soluble material for the support interface, We recommend the following settings:\n" - "0 top z distance, 0 interface spacing, 0 support/object xy distance, interlaced rectilinear pattern, disable \n" - "independent support layer height and use soluble materials for both support interface and support base"); - } - msg_text += "\n\n" + _L("Change these settings automatically? \n" - "Yes - Change these settings automatically\n" - "No - Do not change these settings for me"); - MessageDialog dialog(wxGetApp().plater(), msg_text, "Suggestion", wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog.ShowModal() == wxID_YES) { - new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(0)); - new_conf.set_key_value("support_interface_spacing", new ConfigOptionFloat(0)); - new_conf.set_key_value("support_interface_pattern", - new ConfigOptionEnum(SupportMaterialInterfacePattern::smipRectilinearInterlaced)); - //new_conf.set_key_value("top_z_overrides_xy_distance", new ConfigOptionBool(true)); - new_conf.set_key_value("independent_support_layer_height", new ConfigOptionBool(false)); - if (support_TPU || (is_soluble_filament(interface_filament_id) && !is_soluble_filament(filament_id))) { - new_conf.set_key_value("support_object_xy_distance", new ConfigOptionFloat(0)); - new_conf.set_key_value("support_filament", new ConfigOptionInt(interface_filament_id + 1)); + if (opt_key == "filament_long_retractions_when_cut") + { + unsigned char activate = boost::any_cast(value); + if (activate == 1) + { + MessageDialog dialog(wxGetApp().plater(), + _L("Experimental feature: Retracting and cutting off the filament at a greater distance during filament changes to minimize flush." + "Although it can notably reduce flush, it may also elevate the risk of nozzle clogs or other printing complications.Please use with the latest printer firmware."), + "", wxICON_WARNING | wxOK); + dialog.ShowModal(); + } + if (wxGetApp().app_config->get("auto_calculate_flush") == "all") + { + wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(-1); } - m_config_manipulation.apply(m_config, &new_conf); } - wxGetApp().plater()->update(); - } - } - if(opt_key=="layer_height"){ - auto min_layer_height_from_nozzle=m_preset_bundle->full_config().option("min_layer_height")->values; - auto max_layer_height_from_nozzle=m_preset_bundle->full_config().option("max_layer_height")->values; - auto layer_height_floor = *std::min_element(min_layer_height_from_nozzle.begin(), min_layer_height_from_nozzle.end()); - auto layer_height_ceil = *std::max_element(max_layer_height_from_nozzle.begin(), max_layer_height_from_nozzle.end()); - float layer_height = m_config->opt_float("layer_height"); - bool exceed_minimum_flag = (layer_height_floor - layer_height) > EPSILON; - bool exceed_maximum_flag = (layer_height - layer_height_ceil) > EPSILON; - - if (exceed_maximum_flag || exceed_minimum_flag) { - if(layer_height < EPSILON){ - wxString msg_text = _(L("Layer height is too small.\nIt will set to min_layer_height\n")); - MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxOK); - dialog.SetButtonLabel(wxID_OK, _L("OK")); - dialog.ShowModal(); - auto new_conf = *m_config; - new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_floor)); - m_config_manipulation.apply(m_config, &new_conf); + // BBS +#if 0 + if (opt_key == "extruders_count") + wxGetApp().plater()->on_extruders_change(boost::any_cast(value)); +#endif + + if (opt_key.find("nozzle_volume_type") != std::string::npos) + { + int extruder_idx = std::atoi(opt_key.substr(opt_key.find_last_of('#') + 1).c_str()); + for (auto tab : wxGetApp().tabs_list) + { + tab->update_extruder_variants(extruder_idx); + } + if (auto tab = wxGetApp().plate_tab) + { + tab->update_extruder_variants(extruder_idx); + } + for (auto tab : wxGetApp().model_tabs_list) + { + tab->update_extruder_variants(extruder_idx); + } + if (wxGetApp().app_config->get("auto_calculate_flush") == "all") + { + wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(-1, extruder_idx); + } } - else{ - wxString msg_text = _(L("Layer height exceeds the limit in Printer Settings -> Extruder -> Layer height limits ,this may cause printing quality issues.")); - msg_text += "\n\n" + _(L("Adjust to the set range automatically? \n")); - MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); - dialog.SetButtonLabel(wxID_YES, _L("Adjust")); - dialog.SetButtonLabel(wxID_NO, _L("Ignore")); - auto answer = dialog.ShowModal(); - auto new_conf = *m_config; - if (answer == wxID_YES) { - if (exceed_maximum_flag) - new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_ceil)); - if (exceed_minimum_flag) - new_conf.set_key_value("layer_height",new ConfigOptionFloat(layer_height_floor)); + + if (m_preset_bundle->get_printer_extruder_count() > 1) + { + int extruder_idx = std::atoi(opt_key.substr(opt_key.find_last_of('#') + 1).c_str()); + if (opt_key.find("min_layer_height") != std::string::npos) + { + auto min_layer_height_from_nozzle = m_preset_bundle->full_config().option("min_layer_height")->values; + if (extruder_idx < min_layer_height_from_nozzle.size()) + { + double value = min_layer_height_from_nozzle[extruder_idx]; + std::fill(min_layer_height_from_nozzle.begin(), min_layer_height_from_nozzle.end(), value); + } + auto new_conf = *m_config; + new_conf.set_key_value("min_layer_height", new ConfigOptionFloatsNullable(min_layer_height_from_nozzle)); + m_config_manipulation.apply(m_config, &new_conf); + } + else if (opt_key.find("max_layer_height") != std::string::npos) + { + auto max_layer_height_from_nozzle = m_preset_bundle->full_config().option("max_layer_height")->values; + if (extruder_idx < max_layer_height_from_nozzle.size()) + { + double value = max_layer_height_from_nozzle[extruder_idx]; + std::fill(max_layer_height_from_nozzle.begin(), max_layer_height_from_nozzle.end(), value); + } + auto new_conf = *m_config; + new_conf.set_key_value("max_layer_height", new ConfigOptionFloatsNullable(max_layer_height_from_nozzle)); m_config_manipulation.apply(m_config, &new_conf); } - wxGetApp().plater()->update(); } - } - } - string opt_key_without_idx = opt_key.substr(0, opt_key.find('#')); + if (m_postpone_update_ui) + { + // It means that not all values are rolled to the system/last saved values jet. + // And call of the update() can causes a redundant check of the config values, + return; + } - if (opt_key_without_idx == "long_retractions_when_cut") { - unsigned char activate = boost::any_cast(value); - if (activate == 1) { - MessageDialog dialog(wxGetApp().plater(), - _L("Experimental feature: Retracting and cutting off the filament at a greater distance during filament changes to minimize flush." - "Although it can notably reduce flush, it may also elevate the risk of nozzle clogs or other printing complications."), "", wxICON_WARNING | wxOK); - dialog.ShowModal(); - } - if (wxGetApp().app_config->get("auto_calculate_flush") == "all"){ - wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(-1); + update(); + if (m_active_page) + m_active_page->update_visibility(m_mode, true); + m_page_view->GetParent()->Layout(); } - } - if (opt_key == "filament_long_retractions_when_cut"){ - unsigned char activate = boost::any_cast(value); - if (activate == 1) { - MessageDialog dialog(wxGetApp().plater(), - _L("Experimental feature: Retracting and cutting off the filament at a greater distance during filament changes to minimize flush." - "Although it can notably reduce flush, it may also elevate the risk of nozzle clogs or other printing complications.Please use with the latest printer firmware."), "", wxICON_WARNING | wxOK); - dialog.ShowModal(); + void Tab::show_timelapse_warning_dialog() + { + if (!m_is_timelapse_wipe_tower_already_prompted) + { + wxString msg_text = _(L("When recording timelapse without toolhead, it is recommended to add a \"Timelapse Wipe Tower\" \n" + "by right-click the empty position of build plate and choose \"Add Primitive\"->\"Timelapse Wipe Tower\".")); + msg_text += "\n"; + MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); + dialog.ShowModal(); + m_is_timelapse_wipe_tower_already_prompted = true; + } } - if (wxGetApp().app_config->get("auto_calculate_flush") == "all"){ - wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(-1); + + // Show/hide the 'purging volumes' button + void Tab::update_wiping_button_visibility() + { + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME + bool wipe_tower_enabled = dynamic_cast((m_preset_bundle->prints.get_edited_preset().config).option("enable_prime_tower"))->value; + bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; + + auto wiping_dialog_button = wxGetApp().sidebar().get_wiping_dialog_button(); + if (wiping_dialog_button) + { + wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders); + wiping_dialog_button->GetParent()->Layout(); + } } - } + void Tab::activate_option(const std::string &opt_key, const wxString &category) + { + wxString page_title = translate_category(category, m_type); - // BBS -#if 0 - if (opt_key == "extruders_count") - wxGetApp().plater()->on_extruders_change(boost::any_cast(value)); -#endif + auto cur_item = m_tabctrl->GetFirstVisibleItem(); + if (cur_item < 0) + return; - if (opt_key.find("nozzle_volume_type") != std::string::npos) { - int extruder_idx = std::atoi(opt_key.substr(opt_key.find_last_of('#') + 1).c_str()); - for (auto tab : wxGetApp().tabs_list) { - tab->update_extruder_variants(extruder_idx); - } - if (auto tab = wxGetApp().plate_tab) { - tab->update_extruder_variants(extruder_idx); - } - for (auto tab : wxGetApp().model_tabs_list) { - tab->update_extruder_variants(extruder_idx); - } - if (wxGetApp().app_config->get("auto_calculate_flush") == "all") { - wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(-1,extruder_idx); - } - } + // We should to activate a tab with searched option, if it doesn't. + // And do it before finding of the cur_item to avoid a case when Tab isn't activated jet and all treeItems are invisible + // BBS: GUI refactor + // wxGetApp().mainframe->select_tab(this); + wxGetApp().mainframe->select_tab((wxPanel *)m_parent); - if (m_preset_bundle->get_printer_extruder_count() > 1){ - int extruder_idx = std::atoi(opt_key.substr(opt_key.find_last_of('#') + 1).c_str()); - if (opt_key.find("min_layer_height") != std::string::npos) { - auto min_layer_height_from_nozzle = m_preset_bundle->full_config().option("min_layer_height")->values; - if (extruder_idx < min_layer_height_from_nozzle.size()) { - double value = min_layer_height_from_nozzle[extruder_idx]; - std::fill(min_layer_height_from_nozzle.begin(), min_layer_height_from_nozzle.end(), value); + while (cur_item >= 0) + { + if (page_title.empty()) + { + bool has = false; + for (auto &g : m_pages[cur_item]->m_optgroups) + { + for (auto &l : g->get_lines()) + { + for (auto &o : l.get_options()) + { + if (o.opt.opt_key == opt_key) + { + has = true; + break; + } + } + if (has) + break; + } + if (has) + break; + } + if (!has) + { + cur_item = m_tabctrl->GetNextVisible(cur_item); + continue; + } + } + else + { + auto title = m_tabctrl->GetItemText(cur_item); + if (page_title != title) + { + cur_item = m_tabctrl->GetNextVisible(cur_item); + continue; + } + } + + m_tabctrl->SelectItem(cur_item); + break; } - auto new_conf = *m_config; - new_conf.set_key_value("min_layer_height", new ConfigOptionFloatsNullable(min_layer_height_from_nozzle)); - m_config_manipulation.apply(m_config, &new_conf); - } - else if (opt_key.find("max_layer_height") != std::string::npos) { - auto max_layer_height_from_nozzle = m_preset_bundle->full_config().option("max_layer_height")->values; - if (extruder_idx < max_layer_height_from_nozzle.size()) { - double value = max_layer_height_from_nozzle[extruder_idx]; - std::fill(max_layer_height_from_nozzle.begin(), max_layer_height_from_nozzle.end(), value); + + auto set_focus = [](wxWindow *win) + { + win->SetFocus(); +#ifdef WIN32 + if (wxTextCtrl *text = dynamic_cast(win)) + text->SetSelection(-1, -1); + else if (wxSpinCtrl *spin = dynamic_cast(win)) + spin->SetSelection(-1, -1); +#endif // WIN32 + }; + + Field *field = get_field(opt_key); + + // focused selected field + if (field) + { + set_focus(field->getWindow()); + if (!field->getWindow()->HasFocus()) + { + wxScrollEvent evt(wxEVT_SCROLL_CHANGED); + evt.SetEventObject(field->getWindow()); + wxPostEvent(m_page_view, evt); + } } - auto new_conf = *m_config; - new_conf.set_key_value("max_layer_height", new ConfigOptionFloatsNullable(max_layer_height_from_nozzle)); - m_config_manipulation.apply(m_config, &new_conf); + // else if (category == "Single extruder MM setup") { + // // When we show and hide "Single extruder MM setup" page, + // // related options are still in the search list + // // So, let's hightlighte a "single_extruder_multi_material" option, + // // as a "way" to show hidden page again + // field = get_field("single_extruder_multi_material"); + // if (field) + // set_focus(field->getWindow()); + // } + + m_highlighter.init(get_custom_ctrl_with_blinking_ptr(opt_key)); } - } - if (m_postpone_update_ui) { - // It means that not all values are rolled to the system/last saved values jet. - // And call of the update() can causes a redundant check of the config values, - return; - } + void Tab::apply_searcher() + { + wxGetApp().sidebar().get_searcher().apply(m_config, m_type, m_mode); + } - update(); - if (m_active_page) - m_active_page->update_visibility(m_mode, true); - m_page_view->GetParent()->Layout(); -} - -void Tab::show_timelapse_warning_dialog() { - if (!m_is_timelapse_wipe_tower_already_prompted) { - wxString msg_text = _(L("When recording timelapse without toolhead, it is recommended to add a \"Timelapse Wipe Tower\" \n" - "by right-click the empty position of build plate and choose \"Add Primitive\"->\"Timelapse Wipe Tower\".")); - msg_text += "\n"; - MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); - dialog.ShowModal(); - m_is_timelapse_wipe_tower_already_prompted = true; - } -} - -// Show/hide the 'purging volumes' button -void Tab::update_wiping_button_visibility() { - if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) - return; // ys_FIXME - bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("enable_prime_tower"))->value; - bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; - - auto wiping_dialog_button = wxGetApp().sidebar().get_wiping_dialog_button(); - if (wiping_dialog_button) { - wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders); - wiping_dialog_button->GetParent()->Layout(); - } -} + void Tab::cache_config_diff(const std::vector &selected_options) + { + m_cache_options = selected_options; + m_cache_config.apply_only(m_presets->get_edited_preset().config, selected_options); + } -void Tab::activate_option(const std::string& opt_key, const wxString& category) -{ - wxString page_title = translate_category(category, m_type); + void Tab::apply_config_from_cache() + { + bool was_applied = false; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": enter"); + // check and apply extruders count for printer preset + if (m_type == Preset::TYPE_PRINTER) + was_applied = static_cast(this)->apply_extruder_cnt_from_cache(); - auto cur_item = m_tabctrl->GetFirstVisibleItem(); - if (cur_item < 0) - return; + if (!m_cache_config.empty()) + { + auto variants_key = extruder_variant_keys[m_type].second; + if (m_cache_options.back() == variants_key) + { + m_cache_options.pop_back(); + ConfigOptionStrings *old_variants = dynamic_cast(m_cache_config.option(variants_key)); + ConfigOptionStrings *new_variants = dynamic_cast(m_config->option(variants_key)); + std::vector variant_options; + boost::split(variant_options, m_cache_options.back(), boost::is_any_of(";")); + m_cache_options.pop_back(); + auto title = m_cache_options.back(); + m_cache_options.pop_back(); + if (!(*old_variants == *new_variants) && old_variants->size() == 1) + { + for (auto &opt : variant_options) + { + auto copy = dynamic_cast(m_cache_config.option(opt)->clone()); + copy->resize(new_variants->size()); + m_cache_config.set_key_value(opt, copy); + } + old_variants = new_variants; + } + if (*old_variants == *new_variants) + { + m_cache_options.insert(m_cache_options.end(), variant_options.begin(), variant_options.end()); + } + else + { + auto msg = _L("Switching to a printer with different extruder types or numbers will discard or reset changes to extruder or multi-nozzle-related parameters."); + MessageDialog(wxGetApp().plater(), msg, from_u8(title), wxOK | wxICON_WARNING).ShowModal(); + } + } + m_presets->get_edited_preset().config.apply_only(m_cache_config, m_cache_options); + m_cache_config.clear(); + m_cache_options.clear(); - // We should to activate a tab with searched option, if it doesn't. - // And do it before finding of the cur_item to avoid a case when Tab isn't activated jet and all treeItems are invisible - //BBS: GUI refactor - //wxGetApp().mainframe->select_tab(this); - wxGetApp().mainframe->select_tab((wxPanel*)m_parent); + was_applied = true; + } - while (cur_item >= 0) { - if (page_title.empty()) { - bool has = false; - for (auto &g : m_pages[cur_item]->m_optgroups) { - for (auto &l : g->get_lines()) { - for (auto &o : l.get_options()) { if (o.opt.opt_key == opt_key) { has = true; break; } } - if (has) break; + if (was_applied) + update_dirty(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": exit, was_applied=%1%") % was_applied; + } + + // Call a callback to update the selection of presets on the plater: + // To update the content of the selection boxes, + // to update the filament colors of the selection boxes, + // to update the "dirty" flags of the selection boxes, + // to update number of "filament" selection boxes when the number of extruders change. + void Tab::on_presets_changed() + { + if (wxGetApp().plater() == nullptr) + return; + + // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets + wxGetApp().plater()->sidebar().update_presets(m_type); + + bool is_bbl_vendor_preset = m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(wxGetApp().preset_bundle); + if (is_bbl_vendor_preset) + { + wxGetApp().plater()->get_partplate_list().set_render_option(true, true); + if (m_preset_bundle->printers.get_edited_preset().has_cali_lines(wxGetApp().preset_bundle)) + { + wxGetApp().plater()->get_partplate_list().set_render_cali(true); + } + else + { + wxGetApp().plater()->get_partplate_list().set_render_cali(false); } - if (has) break; } - if (!has) { - cur_item = m_tabctrl->GetNextVisible(cur_item); - continue; + else + { + wxGetApp().plater()->get_partplate_list().set_render_option(false, false); + wxGetApp().plater()->get_partplate_list().set_render_cali(false); } - } else { - auto title = m_tabctrl->GetItemText(cur_item); - if (page_title != title) { - cur_item = m_tabctrl->GetNextVisible(cur_item); - continue; + + // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. + for (auto t : m_dependent_tabs) + { + Tab *tab = wxGetApp().get_tab(t); + // If the printer tells us that the print or filament/sla_material preset has been switched or invalidated, + // refresh the print or filament/sla_material tab page. + // But if there are options, moved from the previously selected preset, update them to edited preset + tab->apply_config_from_cache(); + tab->load_current_preset(); } + // clear m_dependent_tabs after first update from select_preset() + // to avoid needless preset loading from update() function + m_dependent_tabs.clear(); + + wxGetApp().plater()->update_project_dirty_from_presets(); } - m_tabctrl->SelectItem(cur_item); - break; - } + void Tab::build_preset_description_line(ConfigOptionsGroup *optgroup) + { + auto description_line = [this](wxWindow *parent) + { + return description_line_widget(parent, &m_parent_preset_description_line); + }; - auto set_focus = [](wxWindow* win) { - win->SetFocus(); -#ifdef WIN32 - if (wxTextCtrl* text = dynamic_cast(win)) - text->SetSelection(-1, -1); - else if (wxSpinCtrl* spin = dynamic_cast(win)) - spin->SetSelection(-1, -1); -#endif // WIN32 - }; + auto detach_preset_btn = [this](wxWindow *parent) + { + m_detach_preset_btn = new ScalableButton(parent, wxID_ANY, "lock_normal_sys", "", + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + ScalableButton *btn = m_detach_preset_btn; + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - Field* field = get_field(opt_key); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - // focused selected field - if (field) { - set_focus(field->getWindow()); - if (!field->getWindow()->HasFocus()) { - wxScrollEvent evt(wxEVT_SCROLL_CHANGED); - evt.SetEventObject(field->getWindow()); - wxPostEvent(m_page_view, evt); - } - } - //else if (category == "Single extruder MM setup") { - // // When we show and hide "Single extruder MM setup" page, - // // related options are still in the search list - // // So, let's hightlighte a "single_extruder_multi_material" option, - // // as a "way" to show hidden page again - // field = get_field("single_extruder_multi_material"); - // if (field) - // set_focus(field->getWindow()); - //} - - m_highlighter.init(get_custom_ctrl_with_blinking_ptr(opt_key)); -} - -void Tab::apply_searcher() -{ - wxGetApp().sidebar().get_searcher().apply(m_config, m_type, m_mode); -} + // btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent&) + //{ + // bool system = m_presets->get_edited_preset().is_system; + // bool dirty = m_presets->get_edited_preset().is_dirty; + // wxString msg_text = system ? + // _(L("A copy of the current system preset will be created, which will be detached from the system preset.")) : + // _(L("The current custom preset will be detached from the parent system preset.")); + // if (dirty) { + // msg_text += "\n\n"; + // msg_text += _(L("Modifications to the current profile will be saved.")); + // } + // msg_text += "\n\n"; + // msg_text += _(L("This action is not revertible.\nDo you want to proceed?")); -void Tab::cache_config_diff(const std::vector& selected_options) -{ - m_cache_options = selected_options; - m_cache_config.apply_only(m_presets->get_edited_preset().config, selected_options); -} + // //wxMessageDialog dialog(parent, msg_text, _(L("Detach preset")), wxICON_WARNING | wxYES_NO | wxCANCEL); + // MessageDialog dialog(parent, msg_text, _(L("Detach preset")), wxICON_WARNING | wxYES_NO | wxCANCEL); + // if (dialog.ShowModal() == wxID_YES) + // save_preset(m_presets->get_edited_preset().is_system ? std::string() : m_presets->get_edited_preset().name, true); + //}); -void Tab::apply_config_from_cache() -{ - bool was_applied = false; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<(this)->apply_extruder_cnt_from_cache(); - - if (!m_cache_config.empty()) { - auto variants_key = extruder_variant_keys[m_type].second; - if (m_cache_options.back() == variants_key) { - m_cache_options.pop_back(); - ConfigOptionStrings *old_variants = dynamic_cast(m_cache_config.option(variants_key)); - ConfigOptionStrings *new_variants = dynamic_cast(m_config->option(variants_key)); - std::vector variant_options; - boost::split(variant_options, m_cache_options.back(), boost::is_any_of(";")); - m_cache_options.pop_back(); - auto title = m_cache_options.back(); - m_cache_options.pop_back(); - if (!(*old_variants == *new_variants) && old_variants->size() == 1) { - for (auto &opt : variant_options) { - auto copy = dynamic_cast(m_cache_config.option(opt)->clone()); - copy->resize(new_variants->size()); - m_cache_config.set_key_value(opt, copy); - } - old_variants = new_variants; - } - if (*old_variants == *new_variants) { - m_cache_options.insert(m_cache_options.end(), variant_options.begin(), variant_options.end()); - } else { - auto msg = _L("Switching to a printer with different extruder types or numbers will discard or reset changes to extruder or multi-nozzle-related parameters."); - MessageDialog(wxGetApp().plater(), msg, from_u8(title), wxOK | wxICON_WARNING).ShowModal(); - } - } - m_presets->get_edited_preset().config.apply_only(m_cache_config, m_cache_options); - m_cache_config.clear(); - m_cache_options.clear(); - - was_applied = true; - } + btn->Hide(); - if (was_applied) - update_dirty(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<sidebar().update_presets(m_type); - - bool is_bbl_vendor_preset = m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(wxGetApp().preset_bundle); - if (is_bbl_vendor_preset) { - wxGetApp().plater()->get_partplate_list().set_render_option(true, true); - if (m_preset_bundle->printers.get_edited_preset().has_cali_lines(wxGetApp().preset_bundle)) { - wxGetApp().plater()->get_partplate_list().set_render_cali(true); - } else { - wxGetApp().plater()->get_partplate_list().set_render_cali(false); - } - } else { - wxGetApp().plater()->get_partplate_list().set_render_option(false, false); - wxGetApp().plater()->get_partplate_list().set_render_cali(false); - } + return sizer; + }; - // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. - for (auto t: m_dependent_tabs) - { - Tab* tab = wxGetApp().get_tab(t); - // If the printer tells us that the print or filament/sla_material preset has been switched or invalidated, - // refresh the print or filament/sla_material tab page. - // But if there are options, moved from the previously selected preset, update them to edited preset - tab->apply_config_from_cache(); - tab->load_current_preset(); - } - // clear m_dependent_tabs after first update from select_preset() - // to avoid needless preset loading from update() function - m_dependent_tabs.clear(); + Line line = Line{"", ""}; + line.full_width = 1; - wxGetApp().plater()->update_project_dirty_from_presets(); -} + line.append_widget(description_line); + line.append_widget(detach_preset_btn); + optgroup->append_line(line); + } -void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup) -{ - auto description_line = [this](wxWindow* parent) { - return description_line_widget(parent, &m_parent_preset_description_line); - }; + void Tab::update_preset_description_line() + { + const Preset *parent = m_presets->get_selected_preset_parent(); + const Preset &preset = m_presets->get_edited_preset(); + + wxString description_line; + + // if (preset.is_default) { + // description_line = _(L("This is a default preset.")); + // } else if (preset.is_system) { + // description_line = _(L("This is a system preset.")); + // } else if (parent == nullptr) { + // description_line = _(L("Current preset is inherited from the default preset.")); + // } else { + // std::string name = parent->name; + // boost::replace_all(name, "&", "&&"); + // description_line = _(L("Current preset is inherited from")) + ":\n\t" + from_u8(name); + // } + + // if (preset.is_default || preset.is_system) + // description_line += "\n\t" + _(L("It can't be deleted or modified.")) + + // "\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one.")) + + // "\n\t" + _(L("To do that please specify a new name for the preset.")); + + // if (parent && parent->vendor) + //{ + // description_line += "\n\n" + _(L("Additional information:")) + "\n"; + // description_line += "\t" + _(L("vendor")) + ": " + (m_type == Slic3r::Preset::TYPE_PRINTER ? "\n\t\t" : "") + parent->vendor->name + + // ", ver: " + parent->vendor->config_version.to_string(); + // if (m_type == Slic3r::Preset::TYPE_PRINTER) { + // const std::string &printer_model = preset.config.opt_string("printer_model"); + // if (! printer_model.empty()) + // description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model; + // switch (preset.printer_technology()) { + // case ptFFF: + // { + // //FIXME add prefered_sla_material_profile for SLA + // const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); + // const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; + // if (!default_print_profile.empty()) + // description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; + // if (!default_filament_profiles.empty()) + // { + // description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; + // for (auto& profile : default_filament_profiles) { + // if (&profile != &*default_filament_profiles.begin()) + // description_line += ", "; + // description_line += profile; + // } + // } + // break; + // } + // case ptSLA: + // { + // //FIXME add prefered_sla_material_profile for SLA + // const std::string &default_sla_material_profile = preset.config.opt_string("default_sla_material_profile"); + // if (!default_sla_material_profile.empty()) + // description_line += "\n\n\t" + _(L("default SLA material profile")) + ": \n\t\t" + default_sla_material_profile; + + // const std::string &default_sla_print_profile = preset.config.opt_string("default_sla_print_profile"); + // if (!default_sla_print_profile.empty()) + // description_line += "\n\n\t" + _(L("default SLA print profile")) + ": \n\t\t" + default_sla_print_profile; + // break; + // } + // default: break; + // } + // } + // else if (!preset.alias.empty()) + // { + // description_line += "\n\n\t" + _(L("full profile name")) + ": \n\t\t" + preset.name; + // description_line += "\n\t" + _(L("symbolic profile name")) + ": \n\t\t" + preset.alias; + // } + //} - auto detach_preset_btn = [this](wxWindow* parent) { - m_detach_preset_btn = new ScalableButton(parent, wxID_ANY, "lock_normal_sys", "", - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); - ScalableButton* btn = m_detach_preset_btn; - btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + m_parent_preset_description_line->SetText(description_line, false); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + if (m_detach_preset_btn) + m_detach_preset_btn->Show(parent && parent->is_system && !preset.is_default); + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); + } - //btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent&) - //{ - // bool system = m_presets->get_edited_preset().is_system; - // bool dirty = m_presets->get_edited_preset().is_dirty; - // wxString msg_text = system ? - // _(L("A copy of the current system preset will be created, which will be detached from the system preset.")) : - // _(L("The current custom preset will be detached from the parent system preset.")); - // if (dirty) { - // msg_text += "\n\n"; - // msg_text += _(L("Modifications to the current profile will be saved.")); - // } - // msg_text += "\n\n"; - // msg_text += _(L("This action is not revertible.\nDo you want to proceed?")); - - // //wxMessageDialog dialog(parent, msg_text, _(L("Detach preset")), wxICON_WARNING | wxYES_NO | wxCANCEL); - // MessageDialog dialog(parent, msg_text, _(L("Detach preset")), wxICON_WARNING | wxYES_NO | wxCANCEL); - // if (dialog.ShowModal() == wxID_YES) - // save_preset(m_presets->get_edited_preset().is_system ? std::string() : m_presets->get_edited_preset().name, true); - //}); - - btn->Hide(); - - return sizer; - }; - - Line line = Line{ "", "" }; - line.full_width = 1; - - line.append_widget(description_line); - line.append_widget(detach_preset_btn); - optgroup->append_line(line); -} - -void Tab::update_preset_description_line() -{ - const Preset* parent = m_presets->get_selected_preset_parent(); - const Preset& preset = m_presets->get_edited_preset(); - - wxString description_line; - - //if (preset.is_default) { - // description_line = _(L("This is a default preset.")); - //} else if (preset.is_system) { - // description_line = _(L("This is a system preset.")); - //} else if (parent == nullptr) { - // description_line = _(L("Current preset is inherited from the default preset.")); - //} else { - // std::string name = parent->name; - // boost::replace_all(name, "&", "&&"); - // description_line = _(L("Current preset is inherited from")) + ":\n\t" + from_u8(name); - //} - - //if (preset.is_default || preset.is_system) - // description_line += "\n\t" + _(L("It can't be deleted or modified.")) + - // "\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one.")) + - // "\n\t" + _(L("To do that please specify a new name for the preset.")); - - //if (parent && parent->vendor) - //{ - // description_line += "\n\n" + _(L("Additional information:")) + "\n"; - // description_line += "\t" + _(L("vendor")) + ": " + (m_type == Slic3r::Preset::TYPE_PRINTER ? "\n\t\t" : "") + parent->vendor->name + - // ", ver: " + parent->vendor->config_version.to_string(); - // if (m_type == Slic3r::Preset::TYPE_PRINTER) { - // const std::string &printer_model = preset.config.opt_string("printer_model"); - // if (! printer_model.empty()) - // description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model; - // switch (preset.printer_technology()) { - // case ptFFF: - // { - // //FIXME add prefered_sla_material_profile for SLA - // const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); - // const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; - // if (!default_print_profile.empty()) - // description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; - // if (!default_filament_profiles.empty()) - // { - // description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; - // for (auto& profile : default_filament_profiles) { - // if (&profile != &*default_filament_profiles.begin()) - // description_line += ", "; - // description_line += profile; - // } - // } - // break; - // } - // case ptSLA: - // { - // //FIXME add prefered_sla_material_profile for SLA - // const std::string &default_sla_material_profile = preset.config.opt_string("default_sla_material_profile"); - // if (!default_sla_material_profile.empty()) - // description_line += "\n\n\t" + _(L("default SLA material profile")) + ": \n\t\t" + default_sla_material_profile; - - // const std::string &default_sla_print_profile = preset.config.opt_string("default_sla_print_profile"); - // if (!default_sla_print_profile.empty()) - // description_line += "\n\n\t" + _(L("default SLA print profile")) + ": \n\t\t" + default_sla_print_profile; - // break; - // } - // default: break; - // } - // } - // else if (!preset.alias.empty()) - // { - // description_line += "\n\n\t" + _(L("full profile name")) + ": \n\t\t" + preset.name; - // description_line += "\n\t" + _(L("symbolic profile name")) + ": \n\t\t" + preset.alias; - // } - //} - - m_parent_preset_description_line->SetText(description_line, false); - - if (m_detach_preset_btn) - m_detach_preset_btn->Show(parent && parent->is_system && !preset.is_default); - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); -} - -static void validate_custom_note_cb(Tab *tab, ConfigOptionsGroupShp opt_group, const t_config_option_key &opt_key, const boost::any &value) -{ - tab->update_dirty(); - tab->on_value_change(opt_key, value); - if (boost::any_cast(value).size() > 40 * 1024) { - MessageDialog dialog(static_cast(wxGetApp().mainframe), _L("The notes are too large, and may not be synchronized to the cloud. Please keep it within 40k."), - "", wxICON_WARNING | wxOK); - dialog.ShowModal(); - } -} + static void validate_custom_note_cb(Tab *tab, ConfigOptionsGroupShp opt_group, const t_config_option_key &opt_key, const boost::any &value) + { + tab->update_dirty(); + tab->on_value_change(opt_key, value); + if (boost::any_cast(value).size() > 40 * 1024) + { + MessageDialog dialog(static_cast(wxGetApp().mainframe), _L("The notes are too large, and may not be synchronized to the cloud. Please keep it within 40k."), + "", wxICON_WARNING | wxOK); + dialog.ShowModal(); + } + } -void Tab::update_frequently_changed_parameters() -{ - const bool is_fff = supports_printer_technology(ptFFF); - auto og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); - if (!og_freq_chng_params) return; + void Tab::update_frequently_changed_parameters() + { + const bool is_fff = supports_printer_technology(ptFFF); + auto og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); + if (!og_freq_chng_params) + return; - og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); - if (! is_fff) - og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); + og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); + if (!is_fff) + og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); - const std::string updated_value_key = is_fff ? "sparse_infill_density" : "pad_enable"; + const std::string updated_value_key = is_fff ? "sparse_infill_density" : "pad_enable"; - const boost::any val = og_freq_chng_params->get_config_value(*m_config, updated_value_key); - og_freq_chng_params->set_value(updated_value_key, val); + const boost::any val = og_freq_chng_params->get_config_value(*m_config, updated_value_key); + og_freq_chng_params->set_value(updated_value_key, val); - if (is_fff) - { - og_freq_chng_params->set_value("brim", bool(m_config->opt_float("brim_width") > 0.0)); - update_wiping_button_visibility(); - } -} + if (is_fff) + { + og_freq_chng_params->set_value("brim", bool(m_config->opt_float("brim_width") > 0.0)); + update_wiping_button_visibility(); + } + } -//BBS: BBS new parameter list -void TabPrint::build() -{ - if (m_presets == nullptr) - m_presets = &m_preset_bundle->prints; - load_initial_data(); - - auto page = add_options_page(L("Quality"), "empty"); - auto optgroup = page->new_optgroup(L("Layer height"), L"param_layer_height"); - optgroup->append_single_option_line("layer_height", "layer-height"); - optgroup->append_single_option_line("initial_layer_print_height", "layer-height"); - - optgroup = page->new_optgroup(L("Line width"), L"param_line_width"); - optgroup->append_single_option_line("line_width","parameter/line-width"); - optgroup->append_single_option_line("initial_layer_line_width","parameter/line-width"); - optgroup->append_single_option_line("outer_wall_line_width","parameter/line-width"); - optgroup->append_single_option_line("inner_wall_line_width","parameter/line-width"); - optgroup->append_single_option_line("top_surface_line_width","parameter/line-width"); - optgroup->append_single_option_line("sparse_infill_line_width","parameter/line-width"); - optgroup->append_single_option_line("internal_solid_infill_line_width","parameter/line-width"); - optgroup->append_single_option_line("support_line_width","parameter/line-width"); - - optgroup = page->new_optgroup(L("Seam"), L"param_seam"); - optgroup->append_single_option_line("seam_position", "Seam"); - optgroup->append_single_option_line("seam_placement_away_from_overhangs", "Seam"); - optgroup->append_single_option_line("seam_gap", "Seam"); - optgroup->append_single_option_line("seam_slope_conditional", "Seam"); - optgroup->append_single_option_line("scarf_angle_threshold", "Seam"); - optgroup->append_single_option_line("seam_slope_entire_loop", "Seam"); - optgroup->append_single_option_line("seam_slope_steps", "Seam"); - optgroup->append_single_option_line("seam_slope_inner_walls", "Seam"); - optgroup->append_single_option_line("override_filament_scarf_seam_setting", "Seam"); - optgroup->append_single_option_line("seam_slope_type", "Seam"); - optgroup->append_single_option_line("seam_slope_start_height", "Seam"); - optgroup->append_single_option_line("seam_slope_gap", "Seam"); - optgroup->append_single_option_line("seam_slope_min_length", "Seam"); - optgroup->append_single_option_line("wipe_speed", "Seam"); - optgroup->append_single_option_line("role_base_wipe_speed", "Seam"); - - optgroup = page->new_optgroup(L("Precision"), L"param_precision"); - optgroup->append_single_option_line("slice_closing_radius"); - optgroup->append_single_option_line("resolution","acr-move"); - optgroup->append_single_option_line("enable_arc_fitting", "acr-move"); - optgroup->append_single_option_line("xy_hole_compensation", "xy-hole-contour-compensation"); - optgroup->append_single_option_line("xy_contour_compensation", "xy-hole-contour-compensation"); - optgroup->append_single_option_line("enable_circle_compensation"); - optgroup->append_single_option_line("circle_compensation_manual_offset"); - - optgroup->append_single_option_line("elefant_foot_compensation", "parameter/elephant-foot"); - optgroup->append_single_option_line("precise_outer_wall"); - optgroup->append_single_option_line("precise_z_height"); - - optgroup = page->new_optgroup(L("Ironing"), L"param_ironing"); - optgroup->append_single_option_line("ironing_type", "parameter/ironing"); - optgroup->append_single_option_line("ironing_pattern"); - optgroup->append_single_option_line("ironing_speed"); - optgroup->append_single_option_line("ironing_flow"); - optgroup->append_single_option_line("ironing_spacing"); - optgroup->append_single_option_line("ironing_inset"); - optgroup->append_single_option_line("ironing_direction"); - - optgroup = page->new_optgroup(L("Wall generator"), L"param_wall"); - optgroup->append_single_option_line("wall_generator", "wall-generator"); - optgroup->append_single_option_line("wall_transition_angle"); - optgroup->append_single_option_line("wall_transition_filter_deviation"); - optgroup->append_single_option_line("wall_transition_length"); - optgroup->append_single_option_line("wall_distribution_count"); - optgroup->append_single_option_line("min_bead_width"); - optgroup->append_single_option_line("min_feature_size"); - - optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - optgroup->append_single_option_line("wall_sequence","parameter/quality-advance-settings"); - optgroup->append_single_option_line("is_infill_first","parameter/quality-advance-settings"); - optgroup->append_single_option_line("bridge_flow","parameter/bridge"); - optgroup->append_single_option_line("thick_bridges","parameter/bridge"); - optgroup->append_single_option_line("print_flow_ratio"); - optgroup->append_single_option_line("top_solid_infill_flow_ratio","parameter/quality-advance-settings"); - optgroup->append_single_option_line("initial_layer_flow_ratio","parameter/quality-advance-settings"); - optgroup->append_single_option_line("top_one_wall_type","parameter/quality-advance-settings"); - optgroup->append_single_option_line("top_area_threshold","parameter/quality-advance-settings"); - optgroup->append_single_option_line("only_one_wall_first_layer","parameter/quality-advance-settings"); - optgroup->append_single_option_line("detect_overhang_wall","parameter/quality-advance-settings"); - optgroup->append_single_option_line("smooth_speed_discontinuity_area","parameter/quality-advance-settings"); - optgroup->append_single_option_line("smooth_coefficient","parameter/quality-advance-settings"); - optgroup->append_single_option_line("reduce_crossing_wall","parameter/quality-advance-settings"); - optgroup->append_single_option_line("max_travel_detour_distance","parameter/quality-advance-settings"); - optgroup->append_single_option_line("avoid_crossing_wall_includes_support","parameter/quality-advance-settings"); - optgroup->append_single_option_line("z_direction_outwall_speed_continuous", "parameter/quality-advance-settings"); - - page = add_options_page(L("Strength"), "empty"); - optgroup = page->new_optgroup(L("Walls"), L"param_wall"); - optgroup->append_single_option_line("wall_loops","wall-generator"); - optgroup->append_single_option_line("embedding_wall_into_infill"); - - optgroup->append_single_option_line("detect_thin_wall","wall-generator"); - - optgroup = page->new_optgroup(L("Top/bottom shells"), L"param_shell"); - optgroup->append_single_option_line("interface_shells"); - optgroup->append_single_option_line("top_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); - optgroup->append_single_option_line("top_shell_layers"); - optgroup->append_single_option_line("top_shell_thickness"); - optgroup->append_single_option_line("top_color_penetration_layers"); - optgroup->append_single_option_line("bottom_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); - optgroup->append_single_option_line("bottom_shell_layers"); - optgroup->append_single_option_line("bottom_shell_thickness"); - optgroup->append_single_option_line("bottom_color_penetration_layers"); - optgroup->append_single_option_line("infill_instead_top_bottom_surfaces"); - optgroup->append_single_option_line("internal_solid_infill_pattern"); - - optgroup = page->new_optgroup(L("Sparse infill"), L"param_infill"); - optgroup->append_single_option_line("sparse_infill_density"); - optgroup->append_single_option_line("fill_multiline"); - optgroup->append_single_option_line("sparse_infill_pattern", "fill-patterns#infill types and their properties of sparse"); - optgroup->append_single_option_line("locked_skin_infill_pattern", "fill-patterns#infill types and their properties of sparse", -1, true); - optgroup->append_single_option_line("skin_infill_density", "", -1, true); - optgroup->append_single_option_line("locked_skeleton_infill_pattern", "fill-patterns#infill types and their properties of sparse", -1, true); - optgroup->append_single_option_line("skeleton_infill_density", "", -1, true); - optgroup->append_single_option_line("infill_lock_depth", "", -1, true); - optgroup->append_single_option_line("skin_infill_depth", "", -1, true); - optgroup->append_single_option_line("skin_infill_line_width", "parameter/line-width", -1, true); - optgroup->append_single_option_line("skeleton_infill_line_width", "parameter/line-width", -1, true); - - optgroup->append_single_option_line("symmetric_infill_y_axis"); - optgroup->append_single_option_line("infill_shift_step"); - - optgroup->append_single_option_line("infill_rotate_step"); - optgroup->append_single_option_line("sparse_infill_anchor"); - optgroup->append_single_option_line("sparse_infill_anchor_max"); - optgroup->append_single_option_line("filter_out_gap_fill"); - - optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - optgroup->append_single_option_line("infill_wall_overlap","parameter/strength-advance-settings"); - optgroup->append_single_option_line("infill_direction","parameter/strength-advance-settings"); - optgroup->append_single_option_line("bridge_angle","parameter/strength-advance-settings"); - optgroup->append_single_option_line("minimum_sparse_infill_area","parameter/strength-advance-settings"); - optgroup->append_single_option_line("infill_combination","parameter/strength-advance-settings"); - optgroup->append_single_option_line("detect_narrow_internal_solid_infill","parameter/strength-advance-settings"); - optgroup->append_single_option_line("ensure_vertical_shell_thickness","parameter/strength-advance-settings"); - optgroup->append_single_option_line("detect_floating_vertical_shell","parameter/strength-advance-settings"); - //optgroup->append_single_option_line("internal_bridge_support_thickness","parameter/strength-advance-settings"); - - page = add_options_page(L("Speed"), "empty"); - optgroup = page->new_optgroup(L("Initial layer speed"), L"param_speed_first", 15); - optgroup->append_single_option_line("initial_layer_speed", "", 0); - optgroup->append_single_option_line("initial_layer_infill_speed", "", 0); - optgroup = page->new_optgroup(L("Other layers speed"), L"param_speed", 15); - optgroup->append_single_option_line("outer_wall_speed", "", 0); - optgroup->append_single_option_line("inner_wall_speed", "", 0); - optgroup->append_single_option_line("small_perimeter_speed", "", 0); - optgroup->append_single_option_line("small_perimeter_threshold", "", 0); - optgroup->append_single_option_line("sparse_infill_speed", "", 0); - optgroup->append_single_option_line("internal_solid_infill_speed", "", 0); - optgroup->append_single_option_line("vertical_shell_speed", "", 0); - optgroup->append_single_option_line("top_surface_speed", "", 0); - optgroup->append_single_option_line("enable_overhang_speed", "slow-down-for-overhang", 0); - Line line = { L("Overhang speed"), L("This is the speed for various overhang degrees. Overhang degrees are expressed as a percentage of line width. 0 speed means no slowing down for the overhang degree range and wall speed is used") }; - line.label_path = "slow-down-for-overhang"; - line.append_option(optgroup->get_option("overhang_1_4_speed", 0)); - line.append_option(optgroup->get_option("overhang_2_4_speed", 0)); - line.append_option(optgroup->get_option("overhang_3_4_speed", 0)); - line.append_option(optgroup->get_option("overhang_4_4_speed", 0)); - line.append_option(optgroup->get_option("overhang_totally_speed", 0)); - optgroup->append_line(line); - optgroup->append_single_option_line("enable_height_slowdown", "", 0); - optgroup->append_single_option_line("slowdown_start_height", "", 0); - optgroup->append_single_option_line("slowdown_start_speed", "", 0); - optgroup->append_single_option_line("slowdown_start_acc", "", 0); - optgroup->append_single_option_line("slowdown_end_height", "", 0); - optgroup->append_single_option_line("slowdown_end_speed", "", 0); - optgroup->append_single_option_line("slowdown_end_acc", "", 0); - optgroup->append_single_option_line("bridge_speed", "", 0); - optgroup->append_single_option_line("gap_infill_speed", "", 0); - optgroup->append_single_option_line("support_speed", "", 0); - optgroup->append_single_option_line("support_interface_speed", "", 0); - - optgroup = page->new_optgroup(L("Travel speed"), L"param_travel_speed", 15); - optgroup->append_single_option_line("travel_speed", "", 0); - - optgroup = page->new_optgroup(L("Acceleration"), L"param_acceleration", 15); - optgroup->append_single_option_line("default_acceleration", "", 0); - optgroup->append_single_option_line("travel_acceleration", "", 0); - optgroup->append_single_option_line("initial_layer_travel_acceleration", "", 0); - optgroup->append_single_option_line("initial_layer_acceleration", "", 0); - optgroup->append_single_option_line("outer_wall_acceleration", "", 0); - optgroup->append_single_option_line("inner_wall_acceleration", "", 0); - optgroup->append_single_option_line("top_surface_acceleration", "", 0); - optgroup->append_single_option_line("sparse_infill_acceleration", "", 0); - optgroup->append_single_option_line("accel_to_decel_enable", ""); - optgroup->append_single_option_line("accel_to_decel_factor", ""); - - optgroup = page->new_optgroup(L("Jerk(XY)"), L"param_acceleration", 15); - optgroup->append_single_option_line("default_jerk", ""); - optgroup->append_single_option_line("outer_wall_jerk", ""); - optgroup->append_single_option_line("inner_wall_jerk", ""); - optgroup->append_single_option_line("infill_jerk", ""); - optgroup->append_single_option_line("top_surface_jerk", ""); - optgroup->append_single_option_line("initial_layer_jerk", ""); - optgroup->append_single_option_line("travel_jerk", ""); + // BBS: BBS new parameter list + void TabPrint::build() + { + if (m_presets == nullptr) + m_presets = &m_preset_bundle->prints; + load_initial_data(); + + auto page = add_options_page(L("Quality"), "empty"); + auto optgroup = page->new_optgroup(L("Layer height"), L"param_layer_height"); + optgroup->append_single_option_line("layer_height", "layer-height"); + optgroup->append_single_option_line("initial_layer_print_height", "layer-height"); + + optgroup = page->new_optgroup(L("Line width"), L"param_line_width"); + optgroup->append_single_option_line("line_width", "parameter/line-width"); + optgroup->append_single_option_line("initial_layer_line_width", "parameter/line-width"); + optgroup->append_single_option_line("outer_wall_line_width", "parameter/line-width"); + optgroup->append_single_option_line("inner_wall_line_width", "parameter/line-width"); + optgroup->append_single_option_line("top_surface_line_width", "parameter/line-width"); + optgroup->append_single_option_line("sparse_infill_line_width", "parameter/line-width"); + optgroup->append_single_option_line("internal_solid_infill_line_width", "parameter/line-width"); + optgroup->append_single_option_line("support_line_width", "parameter/line-width"); + + optgroup = page->new_optgroup(L("Seam"), L"param_seam"); + optgroup->append_single_option_line("seam_position", "Seam"); + optgroup->append_single_option_line("seam_placement_away_from_overhangs", "Seam"); + optgroup->append_single_option_line("seam_gap", "Seam"); + optgroup->append_single_option_line("seam_slope_conditional", "Seam"); + optgroup->append_single_option_line("scarf_angle_threshold", "Seam"); + optgroup->append_single_option_line("seam_slope_entire_loop", "Seam"); + optgroup->append_single_option_line("seam_slope_steps", "Seam"); + optgroup->append_single_option_line("seam_slope_inner_walls", "Seam"); + optgroup->append_single_option_line("override_filament_scarf_seam_setting", "Seam"); + optgroup->append_single_option_line("seam_slope_type", "Seam"); + optgroup->append_single_option_line("seam_slope_start_height", "Seam"); + optgroup->append_single_option_line("seam_slope_gap", "Seam"); + optgroup->append_single_option_line("seam_slope_min_length", "Seam"); + optgroup->append_single_option_line("wipe_speed", "Seam"); + optgroup->append_single_option_line("role_base_wipe_speed", "Seam"); + + optgroup = page->new_optgroup(L("Precision"), L"param_precision"); + optgroup->append_single_option_line("slice_closing_radius"); + optgroup->append_single_option_line("resolution", "acr-move"); + optgroup->append_single_option_line("enable_arc_fitting", "acr-move"); + optgroup->append_single_option_line("xy_hole_compensation", "xy-hole-contour-compensation"); + optgroup->append_single_option_line("xy_contour_compensation", "xy-hole-contour-compensation"); + optgroup->append_single_option_line("enable_circle_compensation"); + optgroup->append_single_option_line("circle_compensation_manual_offset"); + + optgroup->append_single_option_line("elefant_foot_compensation", "parameter/elephant-foot"); + optgroup->append_single_option_line("precise_outer_wall"); + optgroup->append_single_option_line("precise_z_height"); + + optgroup = page->new_optgroup(L("Ironing"), L"param_ironing"); + optgroup->append_single_option_line("ironing_type", "parameter/ironing"); + optgroup->append_single_option_line("ironing_pattern"); + optgroup->append_single_option_line("ironing_speed"); + optgroup->append_single_option_line("ironing_flow"); + optgroup->append_single_option_line("ironing_spacing"); + optgroup->append_single_option_line("ironing_inset"); + optgroup->append_single_option_line("ironing_direction"); + + optgroup = page->new_optgroup(L("Wall generator"), L"param_wall"); + optgroup->append_single_option_line("wall_generator", "wall-generator"); + optgroup->append_single_option_line("wall_transition_angle"); + optgroup->append_single_option_line("wall_transition_filter_deviation"); + optgroup->append_single_option_line("wall_transition_length"); + optgroup->append_single_option_line("wall_distribution_count"); + optgroup->append_single_option_line("min_bead_width"); + optgroup->append_single_option_line("min_feature_size"); + + optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); + optgroup->append_single_option_line("wall_sequence", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("is_outer_second", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("is_infill_first", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("bridge_flow", "parameter/bridge"); + optgroup->append_single_option_line("thick_bridges", "parameter/bridge"); + optgroup->append_single_option_line("print_flow_ratio"); + optgroup->append_single_option_line("top_solid_infill_flow_ratio", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("initial_layer_flow_ratio", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("top_one_wall_type", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("top_area_threshold", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("only_one_wall_first_layer", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("detect_overhang_wall", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("smooth_speed_discontinuity_area", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("smooth_coefficient", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("reduce_crossing_wall", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("max_travel_detour_distance", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("avoid_crossing_wall_includes_support", "parameter/quality-advance-settings"); + optgroup->append_single_option_line("z_direction_outwall_speed_continuous", "parameter/quality-advance-settings"); + + page = add_options_page(L("Strength"), "empty"); + optgroup = page->new_optgroup(L("Walls"), L"param_wall"); + optgroup->append_single_option_line("wall_loops", "wall-generator"); + optgroup->append_single_option_line("embedding_wall_into_infill"); + + optgroup->append_single_option_line("detect_thin_wall", "wall-generator"); + + optgroup = page->new_optgroup(L("Top/bottom shells"), L"param_shell"); + optgroup->append_single_option_line("interface_shells"); + optgroup->append_single_option_line("top_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); + optgroup->append_single_option_line("top_shell_layers"); + optgroup->append_single_option_line("top_shell_thickness"); + optgroup->append_single_option_line("top_color_penetration_layers"); + optgroup->append_single_option_line("bottom_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); + optgroup->append_single_option_line("bottom_shell_layers"); + optgroup->append_single_option_line("bottom_shell_thickness"); + optgroup->append_single_option_line("bottom_color_penetration_layers"); + optgroup->append_single_option_line("infill_instead_top_bottom_surfaces"); + optgroup->append_single_option_line("internal_solid_infill_pattern"); + + optgroup = page->new_optgroup(L("Sparse infill"), L"param_infill"); + optgroup->append_single_option_line("sparse_infill_density"); + optgroup->append_single_option_line("fill_multiline"); + optgroup->append_single_option_line("sparse_infill_pattern", "fill-patterns#infill types and their properties of sparse"); + optgroup->append_single_option_line("locked_skin_infill_pattern", "fill-patterns#infill types and their properties of sparse", -1, true); + optgroup->append_single_option_line("skin_infill_density", "", -1, true); + optgroup->append_single_option_line("locked_skeleton_infill_pattern", "fill-patterns#infill types and their properties of sparse", -1, true); + optgroup->append_single_option_line("skeleton_infill_density", "", -1, true); + optgroup->append_single_option_line("infill_lock_depth", "", -1, true); + optgroup->append_single_option_line("skin_infill_depth", "", -1, true); + optgroup->append_single_option_line("skin_infill_line_width", "parameter/line-width", -1, true); + optgroup->append_single_option_line("skeleton_infill_line_width", "parameter/line-width", -1, true); + + optgroup->append_single_option_line("symmetric_infill_y_axis"); + optgroup->append_single_option_line("infill_shift_step"); + + optgroup->append_single_option_line("infill_rotate_step"); + optgroup->append_single_option_line("sparse_infill_anchor"); + optgroup->append_single_option_line("sparse_infill_anchor_max"); + optgroup->append_single_option_line("filter_out_gap_fill"); + + optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); + optgroup->append_single_option_line("infill_wall_overlap", "parameter/strength-advance-settings"); + optgroup->append_single_option_line("infill_direction", "parameter/strength-advance-settings"); + optgroup->append_single_option_line("bridge_angle", "parameter/strength-advance-settings"); + optgroup->append_single_option_line("minimum_sparse_infill_area", "parameter/strength-advance-settings"); + optgroup->append_single_option_line("infill_combination", "parameter/strength-advance-settings"); + optgroup->append_single_option_line("detect_narrow_internal_solid_infill", "parameter/strength-advance-settings"); + optgroup->append_single_option_line("ensure_vertical_shell_thickness", "parameter/strength-advance-settings"); + optgroup->append_single_option_line("detect_floating_vertical_shell", "parameter/strength-advance-settings"); + // optgroup->append_single_option_line("internal_bridge_support_thickness","parameter/strength-advance-settings"); + + page = add_options_page(L("Speed"), "empty"); + optgroup = page->new_optgroup(L("Initial layer speed"), L"param_speed_first", 15); + optgroup->append_single_option_line("initial_layer_speed", "", 0); + optgroup->append_single_option_line("initial_layer_infill_speed", "", 0); + optgroup = page->new_optgroup(L("Other layers speed"), L"param_speed", 15); + optgroup->append_single_option_line("outer_wall_speed", "", 0); + optgroup->append_single_option_line("inner_wall_speed", "", 0); + optgroup->append_single_option_line("small_perimeter_speed", "", 0); + optgroup->append_single_option_line("small_perimeter_threshold", "", 0); + optgroup->append_single_option_line("sparse_infill_speed", "", 0); + optgroup->append_single_option_line("internal_solid_infill_speed", "", 0); + optgroup->append_single_option_line("vertical_shell_speed", "", 0); + optgroup->append_single_option_line("top_surface_speed", "", 0); + optgroup->append_single_option_line("enable_overhang_speed", "slow-down-for-overhang", 0); + Line line = {L("Overhang speed"), L("This is the speed for various overhang degrees. Overhang degrees are expressed as a percentage of line width. 0 speed means no slowing down for the overhang degree range and wall speed is used")}; + line.label_path = "slow-down-for-overhang"; + line.append_option(optgroup->get_option("overhang_1_4_speed", 0)); + line.append_option(optgroup->get_option("overhang_2_4_speed", 0)); + line.append_option(optgroup->get_option("overhang_3_4_speed", 0)); + line.append_option(optgroup->get_option("overhang_4_4_speed", 0)); + line.append_option(optgroup->get_option("overhang_totally_speed", 0)); + optgroup->append_line(line); + optgroup->append_single_option_line("enable_height_slowdown", "", 0); + optgroup->append_single_option_line("slowdown_start_height", "", 0); + optgroup->append_single_option_line("slowdown_start_speed", "", 0); + optgroup->append_single_option_line("slowdown_start_acc", "", 0); + optgroup->append_single_option_line("slowdown_end_height", "", 0); + optgroup->append_single_option_line("slowdown_end_speed", "", 0); + optgroup->append_single_option_line("slowdown_end_acc", "", 0); + optgroup->append_single_option_line("bridge_speed", "", 0); + optgroup->append_single_option_line("gap_infill_speed", "", 0); + optgroup->append_single_option_line("support_speed", "", 0); + optgroup->append_single_option_line("support_interface_speed", "", 0); + + optgroup = page->new_optgroup(L("Travel speed"), L"param_travel_speed", 15); + optgroup->append_single_option_line("travel_speed", "", 0); + + optgroup = page->new_optgroup(L("Acceleration"), L"param_acceleration", 15); + optgroup->append_single_option_line("default_acceleration", "", 0); + optgroup->append_single_option_line("travel_acceleration", "", 0); + optgroup->append_single_option_line("initial_layer_travel_acceleration", "", 0); + optgroup->append_single_option_line("initial_layer_acceleration", "", 0); + optgroup->append_single_option_line("outer_wall_acceleration", "", 0); + optgroup->append_single_option_line("inner_wall_acceleration", "", 0); + optgroup->append_single_option_line("top_surface_acceleration", "", 0); + optgroup->append_single_option_line("sparse_infill_acceleration", "", 0); + optgroup->append_single_option_line("accel_to_decel_enable", ""); + optgroup->append_single_option_line("accel_to_decel_factor", ""); + + optgroup = page->new_optgroup(L("Jerk(XY)"), L"param_acceleration", 15); + optgroup->append_single_option_line("default_jerk", ""); + optgroup->append_single_option_line("outer_wall_jerk", ""); + optgroup->append_single_option_line("inner_wall_jerk", ""); + optgroup->append_single_option_line("infill_jerk", ""); + optgroup->append_single_option_line("top_surface_jerk", ""); + optgroup->append_single_option_line("initial_layer_jerk", ""); + optgroup->append_single_option_line("travel_jerk", ""); #ifdef HAS_PRESSURE_EQUALIZER - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_positive"); - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_negative"); + optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_positive"); + optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_negative"); #endif /* HAS_PRESSURE_EQUALIZER */ - page = add_options_page(L("Support"), "support"); - optgroup = page->new_optgroup(L("Support"), L"param_support"); - optgroup->append_single_option_line("enable_support", "support"); - optgroup->append_single_option_line("support_type", "support#support-types"); - optgroup->append_single_option_line("support_style", "support#support-styles"); - optgroup->append_single_option_line("support_threshold_angle", "support#threshold-angle"); - optgroup->append_single_option_line("support_on_build_plate_only"); - optgroup->append_single_option_line("support_critical_regions_only"); - optgroup->append_single_option_line("support_remove_small_overhang"); - //optgroup->append_single_option_line("enforce_support_layers"); - - optgroup = page->new_optgroup(L("Raft"), L"param_raft"); - optgroup->append_single_option_line("raft_layers"); - optgroup->append_single_option_line("raft_contact_distance"); - - optgroup = page->new_optgroup(L("Support filament"), L"param_support_filament"); - optgroup->append_single_option_line("support_filament", "support#support-filament"); - optgroup->append_single_option_line("support_interface_filament", "support#support-filament"); - optgroup->append_single_option_line("support_interface_not_for_body", "support#support-filament"); - - //optgroup = page->new_optgroup(L("Options for support material and raft")); - - //BBS - optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - optgroup->append_single_option_line("raft_first_layer_density"); // not only for raft, but for support too - optgroup->append_single_option_line("raft_first_layer_expansion"); // not only for raft, but for support too - optgroup->append_single_option_line("tree_support_wall_count"); - optgroup->append_single_option_line("support_top_z_distance", "support#top-z-distance"); - optgroup->append_single_option_line("support_bottom_z_distance", "support#bottom-z-distance"); - optgroup->append_single_option_line("support_base_pattern", "support#base-pattern"); - optgroup->append_single_option_line("support_base_pattern_spacing", "support#base-pattern"); - optgroup->append_single_option_line("support_angle"); - optgroup->append_single_option_line("support_interface_top_layers", "support#base-pattern"); - optgroup->append_single_option_line("support_interface_bottom_layers", "support#base-pattern"); - optgroup->append_single_option_line("support_interface_pattern", "support#base-pattern"); - optgroup->append_single_option_line("support_interface_spacing", "support#base-pattern"); - optgroup->append_single_option_line("support_bottom_interface_spacing"); - optgroup->append_single_option_line("support_expansion", "support#base-pattern"); - //optgroup->append_single_option_line("support_interface_loop_pattern"); - - optgroup->append_single_option_line("support_object_xy_distance", "support"); - optgroup->append_single_option_line("top_z_overrides_xy_distance", "support"); - optgroup->append_single_option_line("support_object_first_layer_gap", "support"); - optgroup->append_single_option_line("bridge_no_support", "support#base-pattern"); - optgroup->append_single_option_line("max_bridge_length", "support#tree-support-only-options"); - optgroup->append_single_option_line("independent_support_layer_height", "support"); - - optgroup = page->new_optgroup(L("Tree Support"), L"param_advanced"); - optgroup->append_single_option_line("tree_support_branch_distance", "support#tree-support-only-options"); - optgroup->append_single_option_line("tree_support_branch_diameter", "support#tree-support-only-options"); - optgroup->append_single_option_line("tree_support_branch_angle", "support#tree-support-only-options"); - optgroup->append_single_option_line("tree_support_branch_diameter_angle", "support#tree-support-only-options"); - - page = add_options_page(L("Others"), "advanced"); - optgroup = page->new_optgroup(L("Bed adhension"), L"param_adhension"); - optgroup->append_single_option_line("skirt_loops"); - optgroup->append_single_option_line("skirt_height"); - optgroup->append_single_option_line("skirt_distance"); - //optgroup->append_single_option_line("draft_shield"); - optgroup->append_single_option_line("brim_type", "auto-brim"); - optgroup->append_single_option_line("brim_width", "auto-brim#manual"); - optgroup->append_single_option_line("brim_object_gap", "auto-brim#brim-object-gap"); - - optgroup = page->new_optgroup(L("Prime tower"), L"param_tower"); - optgroup->append_single_option_line("enable_prime_tower","parameter/prime-tower"); - optgroup->append_single_option_line("prime_tower_skip_points", "parameter/prime-tower"); - optgroup->append_single_option_line("prime_tower_enable_framework", "parameter/prime-tower#internal-ribs"); - optgroup->append_single_option_line("prime_tower_width","parameter/prime-tower"); - optgroup->append_single_option_line("prime_tower_max_speed","parameter/prime-tower#max-speed"); - optgroup->append_single_option_line("prime_tower_brim_width","parameter/prime-tower"); - optgroup->append_single_option_line("prime_tower_infill_gap","parameter/prime-tower#infill-gap"); - optgroup->append_single_option_line("prime_tower_rib_wall", "parameter/prime-tower#rib-wall"); - optgroup->append_single_option_line("prime_tower_extra_rib_length","parameter/prime-tower#rib-wall"); - optgroup->append_single_option_line("prime_tower_rib_width","parameter/prime-tower#rib-wall"); - optgroup->append_single_option_line("prime_tower_fillet_wall","parameter/prime-tower"); - - optgroup = page->new_optgroup(L("Flush options"), L"param_flush"); - optgroup->append_single_option_line("flush_into_infill", "reduce-wasting-during-filament-change#wipe-into-infill"); - optgroup->append_single_option_line("flush_into_objects", "reduce-wasting-during-filament-change#wipe-into-object"); - optgroup->append_single_option_line("flush_into_support", "reduce-wasting-during-filament-change#wipe-into-support-enabled-by-default"); - - optgroup = page->new_optgroup(L("Special mode"), L"param_special"); - optgroup->append_single_option_line("slicing_mode", "special-slicing-modes"); - optgroup->append_single_option_line("print_sequence", "sequent-print"); - optgroup->append_single_option_line("spiral_mode", "spiral-vase"); - optgroup->append_single_option_line("spiral_mode_smooth", "spiral-vase#smooth"); - optgroup->append_single_option_line("spiral_mode_max_xy_smoothing", "spiral-vase#max-xy-smoothing"); - optgroup->append_single_option_line("timelapse_type", "Timelapse"); - - optgroup->append_single_option_line("fuzzy_skin", "parameter/fuzzy-skin"); - optgroup->append_single_option_line("fuzzy_skin_point_distance"); - optgroup->append_single_option_line("fuzzy_skin_thickness"); - - optgroup = page->new_optgroup(L("Advanced"), L"advanced"); - optgroup->append_single_option_line("enable_wrapping_detection", "nozzle-clumping-detection-by-probing"); - optgroup->append_single_option_line("interlocking_beam"); - // optgroup->append_single_option_line("mmu_segmented_region_max_width"); - optgroup->append_single_option_line("mmu_segmented_region_interlocking_depth"); - optgroup->append_single_option_line("interlocking_beam_width"); - optgroup->append_single_option_line("interlocking_orientation"); - optgroup->append_single_option_line("interlocking_beam_layer_count"); - optgroup->append_single_option_line("interlocking_depth"); - optgroup->append_single_option_line("interlocking_boundary_avoidance"); - optgroup->append_single_option_line("sparse_infill_filament"); - optgroup->append_single_option_line("solid_infill_filament"); - optgroup->append_single_option_line("wall_filament"); - - optgroup = page->new_optgroup(L("G-code output"), L"param_gcode"); - optgroup->append_single_option_line("reduce_infill_retraction"); - optgroup->append_single_option_line("gcode_add_line_number"); - optgroup->append_single_option_line("exclude_object"); - Option option = optgroup->get_option("filename_format"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + page = add_options_page(L("Support"), "support"); + optgroup = page->new_optgroup(L("Support"), L"param_support"); + optgroup->append_single_option_line("enable_support", "support"); + optgroup->append_single_option_line("support_type", "support#support-types"); + optgroup->append_single_option_line("support_style", "support#support-styles"); + optgroup->append_single_option_line("support_threshold_angle", "support#threshold-angle"); + optgroup->append_single_option_line("support_on_build_plate_only"); + optgroup->append_single_option_line("support_critical_regions_only"); + optgroup->append_single_option_line("support_remove_small_overhang"); + // optgroup->append_single_option_line("enforce_support_layers"); + + optgroup = page->new_optgroup(L("Raft"), L"param_raft"); + optgroup->append_single_option_line("raft_layers"); + optgroup->append_single_option_line("raft_contact_distance"); + + optgroup = page->new_optgroup(L("Support filament"), L"param_support_filament"); + optgroup->append_single_option_line("support_filament", "support#support-filament"); + optgroup->append_single_option_line("support_interface_filament", "support#support-filament"); + optgroup->append_single_option_line("support_interface_not_for_body", "support#support-filament"); + + // optgroup = page->new_optgroup(L("Options for support material and raft")); + + // BBS + optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); + optgroup->append_single_option_line("raft_first_layer_density"); // not only for raft, but for support too + optgroup->append_single_option_line("raft_first_layer_expansion"); // not only for raft, but for support too + optgroup->append_single_option_line("tree_support_wall_count"); + optgroup->append_single_option_line("support_top_z_distance", "support#top-z-distance"); + optgroup->append_single_option_line("support_bottom_z_distance", "support#bottom-z-distance"); + optgroup->append_single_option_line("support_base_pattern", "support#base-pattern"); + optgroup->append_single_option_line("support_base_pattern_spacing", "support#base-pattern"); + optgroup->append_single_option_line("support_angle"); + optgroup->append_single_option_line("support_interface_top_layers", "support#base-pattern"); + optgroup->append_single_option_line("support_interface_bottom_layers", "support#base-pattern"); + optgroup->append_single_option_line("support_interface_pattern", "support#base-pattern"); + optgroup->append_single_option_line("support_interface_spacing", "support#base-pattern"); + optgroup->append_single_option_line("support_bottom_interface_spacing"); + optgroup->append_single_option_line("support_expansion", "support#base-pattern"); + // optgroup->append_single_option_line("support_interface_loop_pattern"); + + optgroup->append_single_option_line("support_object_xy_distance", "support"); + optgroup->append_single_option_line("top_z_overrides_xy_distance", "support"); + optgroup->append_single_option_line("support_object_first_layer_gap", "support"); + optgroup->append_single_option_line("bridge_no_support", "support#base-pattern"); + optgroup->append_single_option_line("max_bridge_length", "support#tree-support-only-options"); + optgroup->append_single_option_line("independent_support_layer_height", "support"); + + optgroup = page->new_optgroup(L("Tree Support"), L"param_advanced"); + optgroup->append_single_option_line("tree_support_branch_distance", "support#tree-support-only-options"); + optgroup->append_single_option_line("tree_support_branch_diameter", "support#tree-support-only-options"); + optgroup->append_single_option_line("tree_support_branch_angle", "support#tree-support-only-options"); + optgroup->append_single_option_line("tree_support_branch_diameter_angle", "support#tree-support-only-options"); + + page = add_options_page(L("Others"), "advanced"); + optgroup = page->new_optgroup(L("Bed adhension"), L"param_adhension"); + optgroup->append_single_option_line("skirt_loops"); + optgroup->append_single_option_line("skirt_height"); + optgroup->append_single_option_line("skirt_distance"); + // optgroup->append_single_option_line("draft_shield"); + optgroup->append_single_option_line("brim_type", "auto-brim"); + optgroup->append_single_option_line("brim_width", "auto-brim#manual"); + optgroup->append_single_option_line("brim_object_gap", "auto-brim#brim-object-gap"); + + optgroup = page->new_optgroup(L("Prime tower"), L"param_tower"); + optgroup->append_single_option_line("enable_prime_tower", "parameter/prime-tower"); + optgroup->append_single_option_line("prime_tower_skip_points", "parameter/prime-tower"); + optgroup->append_single_option_line("prime_tower_enable_framework", "parameter/prime-tower#internal-ribs"); + optgroup->append_single_option_line("prime_tower_width", "parameter/prime-tower"); + optgroup->append_single_option_line("prime_tower_max_speed", "parameter/prime-tower#max-speed"); + optgroup->append_single_option_line("prime_tower_brim_width", "parameter/prime-tower"); + optgroup->append_single_option_line("prime_tower_infill_gap", "parameter/prime-tower#infill-gap"); + optgroup->append_single_option_line("prime_tower_rib_wall", "parameter/prime-tower#rib-wall"); + optgroup->append_single_option_line("prime_tower_extra_rib_length", "parameter/prime-tower#rib-wall"); + optgroup->append_single_option_line("prime_tower_rib_width", "parameter/prime-tower#rib-wall"); + optgroup->append_single_option_line("prime_tower_fillet_wall", "parameter/prime-tower"); + + optgroup = page->new_optgroup(L("Flush options"), L"param_flush"); + optgroup->append_single_option_line("flush_into_infill", "reduce-wasting-during-filament-change#wipe-into-infill"); + optgroup->append_single_option_line("flush_into_objects", "reduce-wasting-during-filament-change#wipe-into-object"); + optgroup->append_single_option_line("flush_into_support", "reduce-wasting-during-filament-change#wipe-into-support-enabled-by-default"); + + optgroup = page->new_optgroup(L("Special mode"), L"param_special"); + optgroup->append_single_option_line("slicing_mode", "special-slicing-modes"); + optgroup->append_single_option_line("print_sequence", "sequent-print"); + optgroup->append_single_option_line("spiral_mode", "spiral-vase"); + optgroup->append_single_option_line("spiral_mode_smooth", "spiral-vase#smooth"); + optgroup->append_single_option_line("spiral_mode_max_xy_smoothing", "spiral-vase#max-xy-smoothing"); + optgroup->append_single_option_line("timelapse_type", "Timelapse"); + + optgroup->append_single_option_line("fuzzy_skin", "parameter/fuzzy-skin"); + optgroup->append_single_option_line("fuzzy_skin_point_distance"); + optgroup->append_single_option_line("fuzzy_skin_thickness"); + + optgroup = page->new_optgroup(L("Advanced"), L"advanced"); + optgroup->append_single_option_line("enable_wrapping_detection", "nozzle-clumping-detection-by-probing"); + optgroup->append_single_option_line("interlocking_beam"); + // optgroup->append_single_option_line("mmu_segmented_region_max_width"); + optgroup->append_single_option_line("mmu_segmented_region_interlocking_depth"); + optgroup->append_single_option_line("interlocking_beam_width"); + optgroup->append_single_option_line("interlocking_orientation"); + optgroup->append_single_option_line("interlocking_beam_layer_count"); + optgroup->append_single_option_line("interlocking_depth"); + optgroup->append_single_option_line("interlocking_boundary_avoidance"); + optgroup->append_single_option_line("sparse_infill_filament"); + optgroup->append_single_option_line("solid_infill_filament"); + optgroup->append_single_option_line("wall_filament"); + + optgroup = page->new_optgroup(L("G-code output"), L"param_gcode"); + optgroup->append_single_option_line("reduce_infill_retraction"); + optgroup->append_single_option_line("gcode_add_line_number"); + optgroup->append_single_option_line("exclude_object"); + Option option = optgroup->get_option("filename_format"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Post-processing scripts"), L"param_gcode", 0); - option = optgroup->get_option("post_process"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = 15; - optgroup->append_single_option_line(option); - optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { validate_custom_note_cb(this, optgroup, opt_key, value); }; + optgroup = page->new_optgroup(L("Post-processing scripts"), L"param_gcode", 0); + option = optgroup->get_option("post_process"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = 15; + optgroup->append_single_option_line(option); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { validate_custom_note_cb(this, optgroup, opt_key, value); }; - optgroup = page->new_optgroup(L("Notes"),"note"); - optgroup->label_width = 0; - option = optgroup->get_option("process_notes"); - option.opt.full_width = true; - option.opt.height = 25; - optgroup->append_single_option_line(option); - optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { validate_custom_note_cb(this, optgroup, opt_key, value); }; + optgroup = page->new_optgroup(L("Notes"), "note"); + optgroup->label_width = 0; + option = optgroup->get_option("process_notes"); + option.opt.full_width = true; + option.opt.height = 25; + optgroup->append_single_option_line(option); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { validate_custom_note_cb(this, optgroup, opt_key, value); }; #if 0 //page = add_options_page(L("Dependencies"), "advanced.png"); @@ -2863,773 +3125,876 @@ void TabPrint::build() // build_preset_description_line(optgroup.get()); #endif -} + } + + // Reload current config (aka presets->edited_preset->config) into the UI fields. + void TabPrint::reload_config() + { + this->compatible_widget_reload(m_compatible_printers); + Tab::reload_config(); + } -// Reload current config (aka presets->edited_preset->config) into the UI fields. -void TabPrint::reload_config() -{ - this->compatible_widget_reload(m_compatible_printers); - Tab::reload_config(); -} + void TabPrint::update_description_lines() + { + Tab::update_description_lines(); -void TabPrint::update_description_lines() -{ - Tab::update_description_lines(); + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) + return; - if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) - return; + if (m_active_page && m_active_page->title() == "Layers and perimeters" && + m_recommended_thin_wall_thickness_description_line && m_top_bottom_shell_thickness_explanation) + { + m_recommended_thin_wall_thickness_description_line->SetText( + from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + m_top_bottom_shell_thickness_explanation->SetText( + from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle))); + } + } - if (m_active_page && m_active_page->title() == "Layers and perimeters" && - m_recommended_thin_wall_thickness_description_line && m_top_bottom_shell_thickness_explanation) - { - m_recommended_thin_wall_thickness_description_line->SetText( - from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); - m_top_bottom_shell_thickness_explanation->SetText( - from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle))); - } + void TabPrint::toggle_options() + { + if (!m_active_page) + return; + // BBS: whether the preset is Bambu Lab printer + if (m_preset_bundle) + { + bool is_BBL_printer = m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(m_preset_bundle); + m_config_manipulation.set_is_BBL_Printer(is_BBL_printer); + } -} + m_config_manipulation.toggle_print_fff_options(m_config, int(intptr_t(m_extruder_switch->GetClientData())), m_type < Preset::TYPE_COUNT); -void TabPrint::toggle_options() -{ - if (!m_active_page) return; - // BBS: whether the preset is Bambu Lab printer - if (m_preset_bundle) { - bool is_BBL_printer = m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(m_preset_bundle); - m_config_manipulation.set_is_BBL_Printer(is_BBL_printer); - } + Field *field = m_active_page->get_field("support_style"); + auto support_type = m_config->opt_enum("support_type"); + if (auto choice = dynamic_cast(field)) + { + auto def = print_config_def.get("support_style"); + std::vector enum_set_normal = {smsDefault, smsGrid, smsSnug}; + std::vector enum_set_tree = {smsDefault, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsTreeOrganic}; + auto &set = is_tree(support_type) ? enum_set_tree : enum_set_normal; + auto &opt = const_cast(field->m_opt); + auto cb = dynamic_cast(choice->window); + auto n = cb->GetValue(); + opt.enum_values.clear(); + opt.enum_labels.clear(); + cb->Clear(); + for (auto i : set) + { + opt.enum_values.push_back(def->enum_values[i]); + opt.enum_labels.push_back(def->enum_labels[i]); + cb->Append(_(def->enum_labels[i])); + } + cb->SetValue(n); + } + } - m_config_manipulation.toggle_print_fff_options(m_config, int(intptr_t(m_extruder_switch->GetClientData())), m_type < Preset::TYPE_COUNT); - - Field *field = m_active_page->get_field("support_style"); - auto support_type = m_config->opt_enum("support_type"); - if (auto choice = dynamic_cast(field)) { - auto def = print_config_def.get("support_style"); - std::vector enum_set_normal = {smsDefault, smsGrid, smsSnug }; - std::vector enum_set_tree = { smsDefault, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsTreeOrganic }; - auto & set = is_tree(support_type) ? enum_set_tree : enum_set_normal; - auto & opt = const_cast(field->m_opt); - auto cb = dynamic_cast(choice->window); - auto n = cb->GetValue(); - opt.enum_values.clear(); - opt.enum_labels.clear(); - cb->Clear(); - for (auto i : set) { - opt.enum_values.push_back(def->enum_values[i]); - opt.enum_labels.push_back(def->enum_labels[i]); - cb->Append(_(def->enum_labels[i])); - } - cb->SetValue(n); - } -} + void TabPrint::update() + { + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME -void TabPrint::update() -{ - if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) - return; // ys_FIXME + m_update_cnt++; - m_update_cnt++; + // ysFIXME: It's temporary workaround and should be clewer reworked: + // Note: This workaround works till "enable_support" and "overhangs" is exclusive sets of mutually no-exclusive parameters. + // But it should be corrected when we will have more such sets. + // Disable check of the compatibility of the "enable_support" and "overhangs" options for saved user profile + // NOTE: Initialization of the support_material_overhangs_queried value have to be processed just ones + if (!m_config_manipulation.is_initialized_support_material_overhangs_queried()) + { + const Preset &selected_preset = m_preset_bundle->prints.get_selected_preset(); + bool is_user_and_saved_preset = !selected_preset.is_system && !selected_preset.is_dirty; + bool support_material_overhangs_queried = m_config->opt_bool("enable_support") && !m_config->opt_bool("detect_overhang_wall"); + m_config_manipulation.initialize_support_material_overhangs_queried(is_user_and_saved_preset && support_material_overhangs_queried); + } - // ysFIXME: It's temporary workaround and should be clewer reworked: - // Note: This workaround works till "enable_support" and "overhangs" is exclusive sets of mutually no-exclusive parameters. - // But it should be corrected when we will have more such sets. - // Disable check of the compatibility of the "enable_support" and "overhangs" options for saved user profile - // NOTE: Initialization of the support_material_overhangs_queried value have to be processed just ones - if (!m_config_manipulation.is_initialized_support_material_overhangs_queried()) - { - const Preset& selected_preset = m_preset_bundle->prints.get_selected_preset(); - bool is_user_and_saved_preset = !selected_preset.is_system && !selected_preset.is_dirty; - bool support_material_overhangs_queried = m_config->opt_bool("enable_support") && !m_config->opt_bool("detect_overhang_wall"); - m_config_manipulation.initialize_support_material_overhangs_queried(is_user_and_saved_preset && support_material_overhangs_queried); - } + m_config_manipulation.update_print_fff_config(m_config, m_type < Preset::TYPE_COUNT, m_type == Preset::TYPE_PLATE); - m_config_manipulation.update_print_fff_config(m_config, m_type < Preset::TYPE_COUNT, m_type == Preset::TYPE_PLATE); + update_description_lines(); + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); - update_description_lines(); - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); + m_update_cnt--; - m_update_cnt--; + if (m_update_cnt == 0) + { + toggle_options(); - if (m_update_cnt==0) { - toggle_options(); + // update() could be called during undo/redo execution + // Update of objectList can cause a crash in this case (because m_objects doesn't match ObjectList) + if (m_type != Preset::TYPE_MODEL && !wxGetApp().plater()->inside_snapshot_capture()) + wxGetApp().obj_list()->update_and_show_object_settings_item(); - // update() could be called during undo/redo execution - // Update of objectList can cause a crash in this case (because m_objects doesn't match ObjectList) - if (m_type != Preset::TYPE_MODEL && !wxGetApp().plater()->inside_snapshot_capture()) - wxGetApp().obj_list()->update_and_show_object_settings_item(); + wxGetApp().mainframe->on_config_changed(m_config); + } + } - wxGetApp().mainframe->on_config_changed(m_config); - } -} + void TabPrint::clear_pages() + { + Tab::clear_pages(); -void TabPrint::clear_pages() -{ - Tab::clear_pages(); + m_recommended_thin_wall_thickness_description_line = nullptr; + m_top_bottom_shell_thickness_explanation = nullptr; + } - m_recommended_thin_wall_thickness_description_line = nullptr; - m_top_bottom_shell_thickness_explanation = nullptr; -} + // BBS: GUI refactor -//BBS: GUI refactor + static std::vector intersect(std::vector const &l, std::vector const &r) + { + std::vector t; + std::copy_if(r.begin(), r.end(), std::back_inserter(t), [&l](auto &e) + { return std::find(l.begin(), l.end(), e) != l.end(); }); + return t; + } -static std::vector intersect(std::vector const& l, std::vector const& r) -{ - std::vector t; - std::copy_if(r.begin(), r.end(), std::back_inserter(t), [&l](auto & e) { return std::find(l.begin(), l.end(), e) != l.end(); }); - return t; -} + static std::vector concat(std::vector const &l, std::vector const &r) + { + std::vector t; + std::set_union(l.begin(), l.end(), r.begin(), r.end(), std::back_inserter(t)); + return t; + } -static std::vector concat(std::vector const& l, std::vector const& r) -{ - std::vector t; - std::set_union(l.begin(), l.end(), r.begin(), r.end(), std::back_inserter(t)); - return t; -} + static std::vector substruct(std::vector const &l, std::vector const &r) + { + std::vector t; + std::copy_if(l.begin(), l.end(), std::back_inserter(t), [&r](auto &e) + { return std::find(r.begin(), r.end(), e) == r.end(); }); + return t; + } -static std::vector substruct(std::vector const& l, std::vector const& r) -{ - std::vector t; - std::copy_if(l.begin(), l.end(), std::back_inserter(t), [&r](auto & e) { return std::find(r.begin(), r.end(), e) == r.end(); }); - return t; -} - -TabPrintModel::TabPrintModel(ParamsPanel* parent, std::vector const & keys) - : TabPrint(parent, Preset::TYPE_MODEL) - , m_keys(intersect(Preset::print_options(), keys)) - , m_prints(Preset::TYPE_MODEL, Preset::print_options(), static_cast(FullPrintConfig::defaults())) -{ - m_opt_status_value = osInitValue | osSystemValue; - m_is_default_preset = true; -} + TabPrintModel::TabPrintModel(ParamsPanel *parent, std::vector const &keys) + : TabPrint(parent, Preset::TYPE_MODEL), m_keys(intersect(Preset::print_options(), keys)), m_prints(Preset::TYPE_MODEL, Preset::print_options(), static_cast(FullPrintConfig::defaults())) + { + m_opt_status_value = osInitValue | osSystemValue; + m_is_default_preset = true; + } -void TabPrintModel::build() -{ - m_presets = &m_prints; - TabPrint::build(); - init_options_list(); + void TabPrintModel::build() + { + m_presets = &m_prints; + TabPrint::build(); + init_options_list(); - auto page = add_options_page(L("Frequent"), "empty"); - auto optgroup = page->new_optgroup(""); + auto page = add_options_page(L("Frequent"), "empty"); + auto optgroup = page->new_optgroup(""); optgroup->append_single_option_line("layer_height"); optgroup->append_single_option_line("sparse_infill_density"); optgroup->append_single_option_line("wall_loops"); optgroup->append_single_option_line("enable_support", "support"); - m_pages.pop_back(); - m_pages.insert(m_pages.begin(), page); + m_pages.pop_back(); + m_pages.insert(m_pages.begin(), page); - for (auto p : m_pages) { - for (auto g : p->m_optgroups) { - g->remove_option_if([this](auto &key) { return !has_key(key); }); - g->have_sys_config = [this] { m_back_to_sys = true; return true; }; + for (auto p : m_pages) + { + for (auto g : p->m_optgroups) + { + g->remove_option_if([this](auto &key) + { return !has_key(key); }); + g->have_sys_config = [this] + { m_back_to_sys = true; return true; }; + } + p->m_optgroups.erase(std::remove_if(p->m_optgroups.begin(), p->m_optgroups.end(), [](auto &g) + { return g->get_lines().empty(); }), + p->m_optgroups.end()); + } + m_pages.erase(std::remove_if(m_pages.begin(), m_pages.end(), [](auto &p) + { return p->m_optgroups.empty(); }), + m_pages.end()); } - p->m_optgroups.erase(std::remove_if(p->m_optgroups.begin(), p->m_optgroups.end(), [](auto & g) { - return g->get_lines().empty(); - }), p->m_optgroups.end()); - } - m_pages.erase(std::remove_if(m_pages.begin(), m_pages.end(), [](auto & p) { - return p->m_optgroups.empty(); - }), m_pages.end()); -} -void TabPrintModel::set_model_config(std::map const & object_configs) -{ - m_object_configs = object_configs; - m_prints.get_selected_preset().config.apply(*m_parent_tab->m_config); - update_model_config(); -} + void TabPrintModel::set_model_config(std::map const &object_configs) + { + m_object_configs = object_configs; + m_prints.get_selected_preset().config.apply(*m_parent_tab->m_config); + update_model_config(); + } -static std::vector variant_keys(DynamicPrintConfig const & config) -{ - std::vector t; - for (const std::string &opt_key : config.keys()) { - auto opt = config.option(opt_key); - if (opt->type() & coVectorType) { - auto vec = dynamic_cast(opt); - for (size_t i = 0; i < vec->size(); i++) - if (!vec->is_nil(i)) - t.push_back(opt_key + "#" + std::to_string(i)); - } else { - t.push_back(opt_key); + static std::vector variant_keys(DynamicPrintConfig const &config) + { + std::vector t; + for (const std::string &opt_key : config.keys()) + { + auto opt = config.option(opt_key); + if (opt->type() & coVectorType) + { + auto vec = dynamic_cast(opt); + for (size_t i = 0; i < vec->size(); i++) + if (!vec->is_nil(i)) + t.push_back(opt_key + "#" + std::to_string(i)); + } + else + { + t.push_back(opt_key); + } + } + return t; } - } - return t; -} -void TabPrintModel::update_model_config() -{ - if (m_config_manipulation.is_applying()) { - return; - } - m_config->apply(*m_parent_tab->m_config); - if (m_type != Preset::TYPE_PLATE) { - m_config->apply_only(*wxGetApp().plate_tab->get_config(), plate_keys); - } - m_null_keys.clear(); - if (!m_object_configs.empty()) { - DynamicPrintConfig const & global_config= *m_config; - DynamicPrintConfig const & local_config = m_object_configs.begin()->second->get(); - DynamicPrintConfig diff_config; - std::vector all_keys = variant_keys(local_config); // at least one has these keys - std::vector local_diffs; // all diff keys to first config - if (m_object_configs.size() > 1) { - std::vector global_diffs; // all diff keys to global config - for (auto & config : m_object_configs) { - all_keys = concat(all_keys, variant_keys(config.second->get())); - auto diffs = deep_diff(config.second->get(), global_config, false); - global_diffs = concat(global_diffs, diffs); - diff_config.apply_only(config.second->get(), diffs); - if (&config.second->get() == &local_config) continue; - local_diffs = concat(local_diffs, deep_diff(local_config, config.second->get())); - } - m_null_keys = intersect(global_diffs, local_diffs); - m_config->apply(diff_config); - } - m_all_keys.clear(); - std::copy_if(all_keys.begin(), all_keys.end(), std::back_inserter(m_all_keys), [this](std::string & e) { + void TabPrintModel::update_model_config() + { + if (m_config_manipulation.is_applying()) + { + return; + } + m_config->apply(*m_parent_tab->m_config); + if (m_type != Preset::TYPE_PLATE) + { + m_config->apply_only(*wxGetApp().plate_tab->get_config(), plate_keys); + } + m_null_keys.clear(); + if (!m_object_configs.empty()) + { + DynamicPrintConfig const &global_config = *m_config; + DynamicPrintConfig const &local_config = m_object_configs.begin()->second->get(); + DynamicPrintConfig diff_config; + std::vector all_keys = variant_keys(local_config); // at least one has these keys + std::vector local_diffs; // all diff keys to first config + if (m_object_configs.size() > 1) + { + std::vector global_diffs; // all diff keys to global config + for (auto &config : m_object_configs) + { + all_keys = concat(all_keys, variant_keys(config.second->get())); + auto diffs = deep_diff(config.second->get(), global_config, false); + global_diffs = concat(global_diffs, diffs); + diff_config.apply_only(config.second->get(), diffs); + if (&config.second->get() == &local_config) + continue; + local_diffs = concat(local_diffs, deep_diff(local_config, config.second->get())); + } + m_null_keys = intersect(global_diffs, local_diffs); + m_config->apply(diff_config); + } + m_all_keys.clear(); + std::copy_if(all_keys.begin(), all_keys.end(), std::back_inserter(m_all_keys), [this](std::string &e) + { auto iter = std::lower_bound(m_keys.begin(), m_keys.end(), e); if (auto n = e.find('#'); n == std::string::npos) return iter != m_keys.end() && e == *iter; else - return iter != m_keys.begin() && e.compare(0, n, *--iter) == 0; - }); - // except those than all equal on - auto local_keys = substruct(m_all_keys, local_diffs); - auto iter = std::partition(local_keys.begin(), local_keys.end(), [] (auto & e) { - return e.find('#') == std::string::npos; - }); - m_config->apply_only(local_config, std::vector(local_keys.begin(), iter)); - for (; iter != local_keys.end(); ++iter) { - int n = iter->find('#'); - auto opt_key = iter->substr(0, n); - n = std::atoi(iter->c_str() + n + 1); - auto vec = dynamic_cast(m_config->option(opt_key)); - vec->set_at(local_config.option(opt_key), n, n); - } - m_config_manipulation.apply_null_fff_config(m_config, m_null_keys, m_object_configs); - - if (m_type == Preset::Type::TYPE_PLATE) { - // Reset m_config manually because there's no corresponding config in m_parent_tab->m_config - for (auto plate_item : m_object_configs) { - const DynamicPrintConfig& plate_config = plate_item.second->get(); - BedType plate_bed_type = (BedType)0; - PrintSequence plate_print_seq = (PrintSequence)0; - if (!plate_config.has("curr_bed_type")) { - // same as global - DynamicConfig& global_cfg = m_preset_bundle->project_config; - if (global_cfg.has("curr_bed_type")) { - BedType global_bed_type = global_cfg.opt_enum("curr_bed_type"); - m_config->set_key_value("curr_bed_type", new ConfigOptionEnum(global_bed_type)); - } - } - if (!plate_config.has("first_layer_print_sequence")) { - m_config->set_key_value("first_layer_sequence_choice", new ConfigOptionEnum(flsAuto)); - } - else { - replace(m_all_keys.begin(), m_all_keys.end(), std::string("first_layer_print_sequence"), std::string("first_layer_sequence_choice")); - m_config->set_key_value("first_layer_sequence_choice", new ConfigOptionEnum(flsCutomize)); + return iter != m_keys.begin() && e.compare(0, n, *--iter) == 0; }); + // except those than all equal on + auto local_keys = substruct(m_all_keys, local_diffs); + auto iter = std::partition(local_keys.begin(), local_keys.end(), [](auto &e) + { return e.find('#') == std::string::npos; }); + m_config->apply_only(local_config, std::vector(local_keys.begin(), iter)); + for (; iter != local_keys.end(); ++iter) + { + int n = iter->find('#'); + auto opt_key = iter->substr(0, n); + n = std::atoi(iter->c_str() + n + 1); + auto vec = dynamic_cast(m_config->option(opt_key)); + vec->set_at(local_config.option(opt_key), n, n); } - if (!plate_config.has("other_layers_print_sequence")) { - m_config->set_key_value("other_layers_sequence_choice", new ConfigOptionEnum(flsAuto)); + m_config_manipulation.apply_null_fff_config(m_config, m_null_keys, m_object_configs); + + if (m_type == Preset::Type::TYPE_PLATE) + { + // Reset m_config manually because there's no corresponding config in m_parent_tab->m_config + for (auto plate_item : m_object_configs) + { + const DynamicPrintConfig &plate_config = plate_item.second->get(); + BedType plate_bed_type = (BedType)0; + PrintSequence plate_print_seq = (PrintSequence)0; + if (!plate_config.has("curr_bed_type")) + { + // same as global + DynamicConfig &global_cfg = m_preset_bundle->project_config; + if (global_cfg.has("curr_bed_type")) + { + BedType global_bed_type = global_cfg.opt_enum("curr_bed_type"); + m_config->set_key_value("curr_bed_type", new ConfigOptionEnum(global_bed_type)); + } + } + if (!plate_config.has("first_layer_print_sequence")) + { + m_config->set_key_value("first_layer_sequence_choice", new ConfigOptionEnum(flsAuto)); + } + else + { + replace(m_all_keys.begin(), m_all_keys.end(), std::string("first_layer_print_sequence"), std::string("first_layer_sequence_choice")); + m_config->set_key_value("first_layer_sequence_choice", new ConfigOptionEnum(flsCutomize)); + } + if (!plate_config.has("other_layers_print_sequence")) + { + m_config->set_key_value("other_layers_sequence_choice", new ConfigOptionEnum(flsAuto)); + } + else + { + replace(m_all_keys.begin(), m_all_keys.end(), std::string("other_layers_print_sequence"), std::string("other_layers_sequence_choice")); + m_config->set_key_value("other_layers_sequence_choice", new ConfigOptionEnum(flsCutomize)); + } + notify_changed(plate_item.first); + } } - else { - replace(m_all_keys.begin(), m_all_keys.end(), std::string("other_layers_print_sequence"), std::string("other_layers_sequence_choice")); - m_config->set_key_value("other_layers_sequence_choice", new ConfigOptionEnum(flsCutomize)); + } + + toggle_options(); + if (m_active_page) + m_active_page->update_visibility(m_mode, true); // for taggle line + update_dirty(); + TabPrint::reload_config(); + // update(); + if (!m_null_keys.empty()) + { + if (m_active_page) + { + auto null_keys = m_null_keys; + filter_diff_option(null_keys); + for (auto k : null_keys) + { + auto f = m_active_page->get_field(k); + if (f) + f->set_value(boost::any(), false); + } } - notify_changed(plate_item.first); } } - } - toggle_options(); - if (m_active_page) - m_active_page->update_visibility(m_mode, true); // for taggle line - update_dirty(); - TabPrint::reload_config(); - //update(); - if (!m_null_keys.empty()) { - if (m_active_page) { - auto null_keys = m_null_keys; - filter_diff_option(null_keys); - for (auto k : null_keys) { - auto f = m_active_page->get_field(k); - if (f) - f->set_value(boost::any(), false); + void TabPrintModel::reset_model_config() + { + if (m_object_configs.empty()) + return; + wxGetApp().plater()->take_snapshot(std::string("Reset Options")); + for (auto config : m_object_configs) + { + auto rmkeys = intersect(m_keys, config.second->keys()); + for (auto &k : rmkeys) + { + config.second->erase(k); + } + notify_changed(config.first); } + update_model_config(); + wxGetApp().mainframe->on_config_changed(m_config); } - } -} - -void TabPrintModel::reset_model_config() -{ - if (m_object_configs.empty()) return; - wxGetApp().plater()->take_snapshot(std::string("Reset Options")); - for (auto config : m_object_configs) { - auto rmkeys = intersect(m_keys, config.second->keys()); - for (auto& k : rmkeys) { - config.second->erase(k); - } - notify_changed(config.first); - } - update_model_config(); - wxGetApp().mainframe->on_config_changed(m_config); -} -bool TabPrintModel::has_key(std::string const& key) -{ - return std::find(m_keys.begin(), m_keys.end(), key) != m_keys.end(); -} + bool TabPrintModel::has_key(std::string const &key) + { + return std::find(m_keys.begin(), m_keys.end(), key) != m_keys.end(); + } -void TabPrintModel::activate_selected_page(std::function throw_if_canceled) -{ - TabPrint::activate_selected_page(throw_if_canceled); - if (m_active_page) { - auto null_keys = m_null_keys; - filter_diff_option(null_keys); - for (auto k : null_keys) { - auto f = m_active_page->get_field(k); - if (f) - f->set_value(boost::any(), false); + void TabPrintModel::activate_selected_page(std::function throw_if_canceled) + { + TabPrint::activate_selected_page(throw_if_canceled); + if (m_active_page) + { + auto null_keys = m_null_keys; + filter_diff_option(null_keys); + for (auto k : null_keys) + { + auto f = m_active_page->get_field(k); + if (f) + f->set_value(boost::any(), false); + } + } } - } -} -void TabPrintModel::on_value_change(const std::string& opt_id, const boost::any& value) -{ - // TODO: support opt_index, translate by OptionsGroup's m_opt_map - if (m_config_manipulation.is_applying()) { - TabPrint::on_value_change(opt_id, value); - return; - } - auto opt_key = opt_id; - auto opt_id2 = opt_id; - int opt_index = -1; - if (auto n = opt_key.find('#'); n != std::string::npos) { - opt_key = opt_key.substr(0, n); - auto iter = m_active_page->m_opt_id_map.lower_bound(opt_key); - assert(iter != m_active_page->m_opt_id_map.end() && iter->second == opt_id); - opt_id2 = iter->first; - opt_index = std::atoi(opt_id2.c_str() + n + 1); - } - if (!has_key(opt_key)) - return; - if (!m_object_configs.empty()) - wxGetApp().plater()->take_snapshot((boost::format("Change Option %s") % opt_id2).str()); - auto inull = std::find(m_null_keys.begin(), m_null_keys.end(), opt_id2); - // always add object config - bool set = true; // *m_config->option(k) != *m_prints.get_selected_preset().config.option(k) || inull != m_null_keys.end(); - auto tab_opt = dynamic_cast(m_config->option(opt_key)); - static std::map null_vecs { - {coBools, new ConfigOptionBoolsNullable(std::initializer_list{ConfigOptionBoolsNullable::nil_value()})}, - {coInts, new ConfigOptionIntsNullable(1, ConfigOptionIntsNullable::nil_value())}, - {coFloats, new ConfigOptionFloatsNullable(1, ConfigOptionFloatsNullable::nil_value())}, - {coPercents, new ConfigOptionPercentsNullable(1, ConfigOptionPercentsNullable::nil_value())}, - {coFloatsOrPercents, new ConfigOptionFloatsOrPercentsNullable(1, ConfigOptionFloatsOrPercentsNullable::nil_value())} - }; - if (m_back_to_sys) { - if (opt_key == opt_id) { - for (auto config : m_object_configs) - config.second->erase(opt_key); - } else { - for (auto config : m_object_configs) { - auto opt = config.second->option(opt_key); - if (opt) { - auto opt2 = opt->clone(); - dynamic_cast(opt2)->resize(tab_opt->size()); - dynamic_cast(opt2)->set_at(null_vecs[tab_opt->type()], opt_index, 0); - if (opt2->is_nil()) { - delete opt2; + void TabPrintModel::on_value_change(const std::string &opt_id, const boost::any &value) + { + // TODO: support opt_index, translate by OptionsGroup's m_opt_map + if (m_config_manipulation.is_applying()) + { + TabPrint::on_value_change(opt_id, value); + return; + } + auto opt_key = opt_id; + auto opt_id2 = opt_id; + int opt_index = -1; + if (auto n = opt_key.find('#'); n != std::string::npos) + { + opt_key = opt_key.substr(0, n); + auto iter = m_active_page->m_opt_id_map.lower_bound(opt_key); + assert(iter != m_active_page->m_opt_id_map.end() && iter->second == opt_id); + opt_id2 = iter->first; + opt_index = std::atoi(opt_id2.c_str() + n + 1); + } + if (!has_key(opt_key)) + return; + if (!m_object_configs.empty()) + wxGetApp().plater()->take_snapshot((boost::format("Change Option %s") % opt_id2).str()); + auto inull = std::find(m_null_keys.begin(), m_null_keys.end(), opt_id2); + // always add object config + bool set = true; // *m_config->option(k) != *m_prints.get_selected_preset().config.option(k) || inull != m_null_keys.end(); + auto tab_opt = dynamic_cast(m_config->option(opt_key)); + static std::map null_vecs{ + {coBools, new ConfigOptionBoolsNullable(std::initializer_list{ConfigOptionBoolsNullable::nil_value()})}, + {coInts, new ConfigOptionIntsNullable(1, ConfigOptionIntsNullable::nil_value())}, + {coFloats, new ConfigOptionFloatsNullable(1, ConfigOptionFloatsNullable::nil_value())}, + {coPercents, new ConfigOptionPercentsNullable(1, ConfigOptionPercentsNullable::nil_value())}, + {coFloatsOrPercents, new ConfigOptionFloatsOrPercentsNullable(1, ConfigOptionFloatsOrPercentsNullable::nil_value())}}; + if (m_back_to_sys) + { + if (opt_key == opt_id) + { + for (auto config : m_object_configs) config.second->erase(opt_key); - } else { + } + else + { + for (auto config : m_object_configs) + { + auto opt = config.second->option(opt_key); + if (opt) + { + auto opt2 = opt->clone(); + dynamic_cast(opt2)->resize(tab_opt->size()); + dynamic_cast(opt2)->set_at(null_vecs[tab_opt->type()], opt_index, 0); + if (opt2->is_nil()) + { + delete opt2; + config.second->erase(opt_key); + } + else + { + config.second->set_key_value(opt_key, opt2); + } + } + } + } + m_all_keys.erase(std::remove(m_all_keys.begin(), m_all_keys.end(), opt_id2), m_all_keys.end()); + } + else if (set) + { + if (opt_key == opt_id) + { + for (auto config : m_object_configs) + config.second->apply_only(*m_config, {opt_key}); + } + else + { + for (auto config : m_object_configs) + { + auto opt = config.second->option(opt_key); + auto opt2 = opt ? opt->clone() : null_vecs[tab_opt->type()]->clone(); + dynamic_cast(opt2)->resize(tab_opt->size()); + dynamic_cast(opt2)->set_at(tab_opt, opt_index, opt_index); config.second->set_key_value(opt_key, opt2); } } + m_all_keys = concat(m_all_keys, {opt_id2}); } - } - m_all_keys.erase(std::remove(m_all_keys.begin(), m_all_keys.end(), opt_id2), m_all_keys.end()); - } else if (set) { - if (opt_key == opt_id) { + if (inull != m_null_keys.end()) + m_null_keys.erase(inull); + if (m_back_to_sys || set) + update_changed_ui(); + m_back_to_sys = false; + TabPrint::on_value_change(opt_id, value); for (auto config : m_object_configs) - config.second->apply_only(*m_config, {opt_key}); - } else { - for (auto config : m_object_configs) { - auto opt = config.second->option(opt_key); - auto opt2 = opt ? opt->clone() : null_vecs[tab_opt->type()]->clone(); - dynamic_cast(opt2)->resize(tab_opt->size()); - dynamic_cast(opt2)->set_at(tab_opt, opt_index, opt_index); - config.second->set_key_value(opt_key, opt2); + { + config.second->touch(); + notify_changed(config.first); } + wxGetApp().params_panel()->notify_object_config_changed(); } - m_all_keys = concat(m_all_keys, {opt_id2}); - } - if (inull != m_null_keys.end()) - m_null_keys.erase(inull); - if (m_back_to_sys || set) update_changed_ui(); - m_back_to_sys = false; - TabPrint::on_value_change(opt_id, value); - for (auto config : m_object_configs) { - config.second->touch(); - notify_changed(config.first); - } - wxGetApp().params_panel()->notify_object_config_changed(); -} -void TabPrintModel::reload_config() -{ - TabPrint::reload_config(); - if (m_active_page) { - auto null_keys = m_null_keys; - filter_diff_option(null_keys); - for (auto k : null_keys) { - auto f = m_active_page->get_field(k); - if (f) f->set_value(boost::any(), false); - } - } - auto keys = m_config_manipulation.applying_keys(); - bool super_changed = false; - for (auto & k : keys) { - if (has_key(k)) { - auto inull = std::find(m_null_keys.begin(), m_null_keys.end(), k); - bool set = *m_config->option(k) != *m_prints.get_selected_preset().config.option(k) || inull != m_null_keys.end(); - if (set) { - for (auto config : m_object_configs) - config.second->apply_only(*m_config, {k}); - m_all_keys = concat(m_all_keys, {k}); + void TabPrintModel::reload_config() + { + TabPrint::reload_config(); + if (m_active_page) + { + auto null_keys = m_null_keys; + filter_diff_option(null_keys); + for (auto k : null_keys) + { + auto f = m_active_page->get_field(k); + if (f) + f->set_value(boost::any(), false); + } + } + auto keys = m_config_manipulation.applying_keys(); + bool super_changed = false; + for (auto &k : keys) + { + if (has_key(k)) + { + auto inull = std::find(m_null_keys.begin(), m_null_keys.end(), k); + bool set = *m_config->option(k) != *m_prints.get_selected_preset().config.option(k) || inull != m_null_keys.end(); + if (set) + { + for (auto config : m_object_configs) + config.second->apply_only(*m_config, {k}); + m_all_keys = concat(m_all_keys, {k}); + } + if (inull != m_null_keys.end()) + m_null_keys.erase(inull); + } + else + { + m_parent_tab->m_config->apply_only(*m_config, {k}); + super_changed = true; + } + } + if (super_changed) + { + m_parent_tab->update_dirty(); + m_parent_tab->reload_config(); + m_parent_tab->update(); } - if (inull != m_null_keys.end()) m_null_keys.erase(inull); - } else { - m_parent_tab->m_config->apply_only(*m_config, {k}); - super_changed = true; } - } - if (super_changed) { - m_parent_tab->update_dirty(); - m_parent_tab->reload_config(); - m_parent_tab->update(); - } -} -void TabPrintModel::update_custom_dirty(std::vector &dirty_options, std::vector &nonsys_options) -{ - dirty_options = concat(dirty_options, m_null_keys); - nonsys_options = concat(nonsys_options, m_null_keys); - nonsys_options = concat(nonsys_options, m_all_keys); -} - -//BBS: GUI refactor -TabPrintPlate::TabPrintPlate(ParamsPanel* parent) : - TabPrintModel(parent, plate_keys) -{ - m_parent_tab = wxGetApp().get_tab(Preset::TYPE_PRINT); - m_type = Preset::TYPE_PLATE; - m_keys = concat(m_keys, plate_keys); -} + void TabPrintModel::update_custom_dirty(std::vector &dirty_options, std::vector &nonsys_options) + { + dirty_options = concat(dirty_options, m_null_keys); + nonsys_options = concat(nonsys_options, m_null_keys); + nonsys_options = concat(nonsys_options, m_all_keys); + } -void TabPrintPlate::build() -{ - m_presets = &m_prints; - load_initial_data(); - - m_config->option("curr_bed_type", true); - if (m_preset_bundle->project_config.has("curr_bed_type")) { - BedType global_bed_type = m_preset_bundle->project_config.opt_enum("curr_bed_type"); - global_bed_type = BedType(global_bed_type - 1); - m_config->set_key_value("curr_bed_type", new ConfigOptionEnum(global_bed_type)); - } - m_config->option("first_layer_sequence_choice", true); - m_config->option("first_layer_print_sequence", true); - m_config->option("other_layers_print_sequence", true); - m_config->option("other_layers_sequence_choice", true); - - auto page = add_options_page(L("Plate Settings"), "empty"); - auto optgroup = page->new_optgroup(""); - optgroup->append_single_option_line("curr_bed_type"); - optgroup->append_single_option_line("print_sequence", "sequent-print"); - optgroup->append_single_option_line("spiral_mode", "spiral-vase"); - optgroup->append_single_option_line("first_layer_sequence_choice", "parameter/filament-sequence-for-different-layers"); - optgroup->append_single_option_line("other_layers_sequence_choice", "parameter/filament-sequence-for-different-layers"); - - for (auto& line : const_cast&>(optgroup->get_lines())) { - line.undo_to_sys = true; - } - optgroup->have_sys_config = [this] { m_back_to_sys = true; return true; }; -} + // BBS: GUI refactor + TabPrintPlate::TabPrintPlate(ParamsPanel *parent) : TabPrintModel(parent, plate_keys) + { + m_parent_tab = wxGetApp().get_tab(Preset::TYPE_PRINT); + m_type = Preset::TYPE_PLATE; + m_keys = concat(m_keys, plate_keys); + } -void TabPrintPlate::reset_model_config() -{ - if (m_object_configs.empty()) return; - wxGetApp().plater()->take_snapshot(std::string("Reset Options")); - for (auto plate_item : m_object_configs) { - auto rmkeys = intersect(m_keys, plate_item.second->keys()); - for (auto& k : rmkeys) { - plate_item.second->erase(k); - } - auto plate = dynamic_cast(plate_item.first); - plate->reset_bed_type(); - plate->set_print_seq(PrintSequence::ByDefault); - plate->set_first_layer_print_sequence({}); - plate->set_spiral_vase_mode(false, true); - notify_changed(plate_item.first); - } - update_model_config(); - wxGetApp().mainframe->on_config_changed(m_config); -} + void TabPrintPlate::build() + { + m_presets = &m_prints; + load_initial_data(); -void TabPrintPlate::on_value_change(const std::string& opt_key, const boost::any& value) -{ - auto k = opt_key; - if (m_config_manipulation.is_applying()) { - return; - } - if (!has_key(k)) - return; - if (!m_object_configs.empty()) - wxGetApp().plater()->take_snapshot((boost::format("Change Option %s") % k).str()); - bool set = true; - if (m_back_to_sys) { - for (auto plate_item : m_object_configs) { - plate_item.second->erase(k); - auto plate = dynamic_cast(plate_item.first); - if (k == "curr_bed_type") + m_config->option("curr_bed_type", true); + if (m_preset_bundle->project_config.has("curr_bed_type")) + { + BedType global_bed_type = m_preset_bundle->project_config.opt_enum("curr_bed_type"); + global_bed_type = BedType(global_bed_type - 1); + m_config->set_key_value("curr_bed_type", new ConfigOptionEnum(global_bed_type)); + } + m_config->option("first_layer_sequence_choice", true); + m_config->option("first_layer_print_sequence", true); + m_config->option("other_layers_print_sequence", true); + m_config->option("other_layers_sequence_choice", true); + + auto page = add_options_page(L("Plate Settings"), "empty"); + auto optgroup = page->new_optgroup(""); + optgroup->append_single_option_line("curr_bed_type"); + optgroup->append_single_option_line("print_sequence", "sequent-print"); + optgroup->append_single_option_line("spiral_mode", "spiral-vase"); + optgroup->append_single_option_line("first_layer_sequence_choice", "parameter/filament-sequence-for-different-layers"); + optgroup->append_single_option_line("other_layers_sequence_choice", "parameter/filament-sequence-for-different-layers"); + + for (auto &line : const_cast &>(optgroup->get_lines())) + { + line.undo_to_sys = true; + } + optgroup->have_sys_config = [this] + { m_back_to_sys = true; return true; }; + } + + void TabPrintPlate::reset_model_config() + { + if (m_object_configs.empty()) + return; + wxGetApp().plater()->take_snapshot(std::string("Reset Options")); + for (auto plate_item : m_object_configs) + { + auto rmkeys = intersect(m_keys, plate_item.second->keys()); + for (auto &k : rmkeys) + { + plate_item.second->erase(k); + } + auto plate = dynamic_cast(plate_item.first); plate->reset_bed_type(); - if (k == "print_sequence") plate->set_print_seq(PrintSequence::ByDefault); - if (k == "first_layer_sequence_choice") plate->set_first_layer_print_sequence({}); - if (k == "other_layers_sequence_choice") - plate->set_other_layers_print_sequence({}); - if (k == "spiral_mode") plate->set_spiral_vase_mode(false, true); + notify_changed(plate_item.first); + } + update_model_config(); + wxGetApp().mainframe->on_config_changed(m_config); } - m_all_keys.erase(std::remove(m_all_keys.begin(), m_all_keys.end(), k), m_all_keys.end()); - } - else if (set) { - for (auto plate_item : m_object_configs) { - plate_item.second->apply_only(*m_config, { k }); - auto plate = dynamic_cast(plate_item.first); - BedType bed_type; - PrintSequence print_seq; - LayerSeq first_layer_seq_choice; - LayerSeq other_layer_seq_choice; - if (k == "curr_bed_type") { - bed_type = m_config->opt_enum("curr_bed_type"); - plate->set_bed_type(BedType(bed_type)); - } - if (k == "print_sequence") { - print_seq = m_config->opt_enum("print_sequence"); - plate->set_print_seq(print_seq); - } - if (k == "first_layer_sequence_choice") { - first_layer_seq_choice = m_config->opt_enum("first_layer_sequence_choice"); - if (first_layer_seq_choice == LayerSeq::flsAuto) { - plate->set_first_layer_print_sequence({}); - } - else if (first_layer_seq_choice == LayerSeq::flsCutomize) { - const DynamicPrintConfig& plate_config = plate_item.second->get(); - if (!plate_config.has("first_layer_print_sequence")) { - std::vector initial_sequence; - for (int i = 0; i < wxGetApp().filaments_cnt(); i++) { - initial_sequence.push_back(i + 1); + + void TabPrintPlate::on_value_change(const std::string &opt_key, const boost::any &value) + { + auto k = opt_key; + if (m_config_manipulation.is_applying()) + { + return; + } + if (!has_key(k)) + return; + if (!m_object_configs.empty()) + wxGetApp().plater()->take_snapshot((boost::format("Change Option %s") % k).str()); + bool set = true; + if (m_back_to_sys) + { + for (auto plate_item : m_object_configs) + { + plate_item.second->erase(k); + auto plate = dynamic_cast(plate_item.first); + if (k == "curr_bed_type") + plate->reset_bed_type(); + if (k == "print_sequence") + plate->set_print_seq(PrintSequence::ByDefault); + if (k == "first_layer_sequence_choice") + plate->set_first_layer_print_sequence({}); + if (k == "other_layers_sequence_choice") + plate->set_other_layers_print_sequence({}); + if (k == "spiral_mode") + plate->set_spiral_vase_mode(false, true); + } + m_all_keys.erase(std::remove(m_all_keys.begin(), m_all_keys.end(), k), m_all_keys.end()); + } + else if (set) + { + for (auto plate_item : m_object_configs) + { + plate_item.second->apply_only(*m_config, {k}); + auto plate = dynamic_cast(plate_item.first); + BedType bed_type; + PrintSequence print_seq; + LayerSeq first_layer_seq_choice; + LayerSeq other_layer_seq_choice; + if (k == "curr_bed_type") + { + bed_type = m_config->opt_enum("curr_bed_type"); + plate->set_bed_type(BedType(bed_type)); + } + if (k == "print_sequence") + { + print_seq = m_config->opt_enum("print_sequence"); + plate->set_print_seq(print_seq); + } + if (k == "first_layer_sequence_choice") + { + first_layer_seq_choice = m_config->opt_enum("first_layer_sequence_choice"); + if (first_layer_seq_choice == LayerSeq::flsAuto) + { + plate->set_first_layer_print_sequence({}); + } + else if (first_layer_seq_choice == LayerSeq::flsCutomize) + { + const DynamicPrintConfig &plate_config = plate_item.second->get(); + if (!plate_config.has("first_layer_print_sequence")) + { + std::vector initial_sequence; + for (int i = 0; i < wxGetApp().filaments_cnt(); i++) + { + initial_sequence.push_back(i + 1); + } + plate->set_first_layer_print_sequence(initial_sequence); + } + wxCommandEvent evt(EVT_OPEN_PLATESETTINGSDIALOG); + evt.SetInt(plate->get_index()); + evt.SetString("only_layer_sequence"); + evt.SetEventObject(wxGetApp().plater()); + wxPostEvent(wxGetApp().plater(), evt); } - plate->set_first_layer_print_sequence(initial_sequence); } - wxCommandEvent evt(EVT_OPEN_PLATESETTINGSDIALOG); - evt.SetInt(plate->get_index()); - evt.SetString("only_layer_sequence"); - evt.SetEventObject(wxGetApp().plater()); - wxPostEvent(wxGetApp().plater(), evt); - } - } - if (k == "other_layers_sequence_choice") { - other_layer_seq_choice = m_config->opt_enum("other_layers_sequence_choice"); - if (other_layer_seq_choice == LayerSeq::flsAuto) { - plate->set_other_layers_print_sequence({}); - } - else if (other_layer_seq_choice == LayerSeq::flsCutomize) { - const DynamicPrintConfig& plate_config = plate_item.second->get(); - if (!plate_config.has("other_layers_print_sequence")) { - std::vector initial_sequence; - for (int i = 0; i < wxGetApp().filaments_cnt(); i++) { - initial_sequence.push_back(i + 1); + if (k == "other_layers_sequence_choice") + { + other_layer_seq_choice = m_config->opt_enum("other_layers_sequence_choice"); + if (other_layer_seq_choice == LayerSeq::flsAuto) + { + plate->set_other_layers_print_sequence({}); } - std::vector initial_layer_sequence{ std::make_pair(std::make_pair(2, INT_MAX-1), initial_sequence) }; - plate->set_other_layers_print_sequence(initial_layer_sequence); + else if (other_layer_seq_choice == LayerSeq::flsCutomize) + { + const DynamicPrintConfig &plate_config = plate_item.second->get(); + if (!plate_config.has("other_layers_print_sequence")) + { + std::vector initial_sequence; + for (int i = 0; i < wxGetApp().filaments_cnt(); i++) + { + initial_sequence.push_back(i + 1); + } + std::vector initial_layer_sequence{std::make_pair(std::make_pair(2, INT_MAX - 1), initial_sequence)}; + plate->set_other_layers_print_sequence(initial_layer_sequence); + } + wxCommandEvent evt(EVT_OPEN_PLATESETTINGSDIALOG); + evt.SetInt(plate->get_index()); + evt.SetString("only_layer_sequence"); + evt.SetEventObject(wxGetApp().plater()); + wxPostEvent(wxGetApp().plater(), evt); + } + } + if (k == "spiral_mode") + { + plate->set_spiral_vase_mode(m_config->opt_bool("spiral_mode"), false); } - wxCommandEvent evt(EVT_OPEN_PLATESETTINGSDIALOG); - evt.SetInt(plate->get_index()); - evt.SetString("only_layer_sequence"); - evt.SetEventObject(wxGetApp().plater()); - wxPostEvent(wxGetApp().plater(), evt); } + m_all_keys = concat(m_all_keys, {k}); } - if (k == "spiral_mode") { - plate->set_spiral_vase_mode(m_config->opt_bool("spiral_mode"), false); + if (m_back_to_sys || set) + update_changed_ui(); + m_back_to_sys = false; + for (auto plate_item : m_object_configs) + { + plate_item.second->touch(); + notify_changed(plate_item.first); } - } - m_all_keys = concat(m_all_keys, { k }); - } - if (m_back_to_sys || set) update_changed_ui(); - m_back_to_sys = false; - for (auto plate_item : m_object_configs) { - plate_item.second->touch(); - notify_changed(plate_item.first); - } - wxGetApp().params_panel()->notify_object_config_changed(); - update(); -} - -void TabPrintPlate::notify_changed(ObjectBase* object) -{ - auto plate = dynamic_cast(object); - auto objects_list = wxGetApp().obj_list(); - wxDataViewItemArray items; - objects_list->GetSelections(items); - for (auto item : items) { - if (objects_list->GetModel()->GetItemType(item) == itPlate) { - ObjectDataViewModelNode* node = static_cast(item.GetID()); - if (node) - node->set_action_icon(!m_all_keys.empty()); + wxGetApp().params_panel()->notify_object_config_changed(); + update(); } - } -} -void TabPrintPlate::update_custom_dirty(std::vector &dirty_options, std::vector &nonsys_options) -{ - TabPrintModel::update_custom_dirty(dirty_options, nonsys_options); - for (auto k : m_all_keys) { - if (k == "first_layer_sequence_choice" || k == "other_layers_sequence_choice") { - if (m_config->opt_enum("first_layer_sequence_choice") != LayerSeq::flsAuto) { - dirty_options.push_back(k); - } - if (m_config->opt_enum("other_layers_sequence_choice") != LayerSeq::flsAuto) { - dirty_options.push_back(k); + void TabPrintPlate::notify_changed(ObjectBase *object) + { + auto plate = dynamic_cast(object); + auto objects_list = wxGetApp().obj_list(); + wxDataViewItemArray items; + objects_list->GetSelections(items); + for (auto item : items) + { + if (objects_list->GetModel()->GetItemType(item) == itPlate) + { + ObjectDataViewModelNode *node = static_cast(item.GetID()); + if (node) + node->set_action_icon(!m_all_keys.empty()); + } } } - if (k == "curr_bed_type") { - DynamicConfig& global_cfg = m_preset_bundle->project_config; - if (global_cfg.has("curr_bed_type")) { - BedType global_bed_type = global_cfg.opt_enum("curr_bed_type"); - if (m_config->opt_enum("curr_bed_type") != global_bed_type) { - dirty_options.push_back(k); + + void TabPrintPlate::update_custom_dirty(std::vector &dirty_options, std::vector &nonsys_options) + { + TabPrintModel::update_custom_dirty(dirty_options, nonsys_options); + for (auto k : m_all_keys) + { + if (k == "first_layer_sequence_choice" || k == "other_layers_sequence_choice") + { + if (m_config->opt_enum("first_layer_sequence_choice") != LayerSeq::flsAuto) + { + dirty_options.push_back(k); + } + if (m_config->opt_enum("other_layers_sequence_choice") != LayerSeq::flsAuto) + { + dirty_options.push_back(k); + } + } + if (k == "curr_bed_type") + { + DynamicConfig &global_cfg = m_preset_bundle->project_config; + if (global_cfg.has("curr_bed_type")) + { + BedType global_bed_type = global_cfg.opt_enum("curr_bed_type"); + if (m_config->opt_enum("curr_bed_type") != global_bed_type) + { + dirty_options.push_back(k); + } + } } } } - } -} -TabPrintObject::TabPrintObject(ParamsPanel* parent) : - TabPrintModel(parent, concat(PrintObjectConfig().keys(), PrintRegionConfig().keys())) -{ - m_parent_tab = wxGetApp().get_tab(Preset::TYPE_PRINT); -} + TabPrintObject::TabPrintObject(ParamsPanel *parent) : TabPrintModel(parent, concat(PrintObjectConfig().keys(), PrintRegionConfig().keys())) + { + m_parent_tab = wxGetApp().get_tab(Preset::TYPE_PRINT); + } -void TabPrintObject::notify_changed(ObjectBase * object) -{ - auto obj = dynamic_cast(object); - wxGetApp().obj_list()->object_config_options_changed({obj, nullptr}); -} + void TabPrintObject::notify_changed(ObjectBase *object) + { + auto obj = dynamic_cast(object); + wxGetApp().obj_list()->object_config_options_changed({obj, nullptr}); + } -//BBS: GUI refactor + // BBS: GUI refactor -TabPrintPart::TabPrintPart(ParamsPanel* parent) : - TabPrintModel(parent, PrintRegionConfig().keys()) -{ - m_parent_tab = wxGetApp().get_model_tab(); -} + TabPrintPart::TabPrintPart(ParamsPanel *parent) : TabPrintModel(parent, PrintRegionConfig().keys()) + { + m_parent_tab = wxGetApp().get_model_tab(); + } -void TabPrintPart::notify_changed(ObjectBase * object) -{ - auto vol = dynamic_cast(object); - wxGetApp().obj_list()->object_config_options_changed({vol->get_object(), vol}); -} + void TabPrintPart::notify_changed(ObjectBase *object) + { + auto vol = dynamic_cast(object); + wxGetApp().obj_list()->object_config_options_changed({vol->get_object(), vol}); + } -static std::string layer_height = "layer_height"; -TabPrintLayer::TabPrintLayer(ParamsPanel* parent) : - TabPrintModel(parent, concat({ layer_height }, PrintRegionConfig().keys())) -{ - m_parent_tab = wxGetApp().get_model_tab(); -} + static std::string layer_height = "layer_height"; + TabPrintLayer::TabPrintLayer(ParamsPanel *parent) : TabPrintModel(parent, concat({layer_height}, PrintRegionConfig().keys())) + { + m_parent_tab = wxGetApp().get_model_tab(); + } + + void TabPrintLayer::notify_changed(ObjectBase *object) + { + for (auto config : m_object_configs) + { + if (!config.second->has(layer_height)) + { + auto option = m_parent_tab->get_config()->option(layer_height); + config.second->set_key_value(layer_height, option->clone()); + } + auto objects_list = wxGetApp().obj_list(); + wxDataViewItemArray items; + objects_list->GetSelections(items); + for (auto item : items) + objects_list->add_settings_item(item, &config.second->get()); + } + } -void TabPrintLayer::notify_changed(ObjectBase * object) -{ - for (auto config : m_object_configs) { - if (!config.second->has(layer_height)) { + void TabPrintLayer::update_custom_dirty(std::vector &dirty_options, std::vector &nonsys_options) + { + TabPrintModel::update_custom_dirty(dirty_options, nonsys_options); auto option = m_parent_tab->get_config()->option(layer_height); - config.second->set_key_value(layer_height, option->clone()); + for (auto config : m_object_configs) + { + if (!config.second->has(layer_height)) + { + config.second->set_key_value(layer_height, option->clone()); + dirty_options.erase(std::remove(dirty_options.begin(), dirty_options.end(), layer_height), dirty_options.end()); + nonsys_options.erase(std::remove(nonsys_options.begin(), nonsys_options.end(), layer_height), nonsys_options.end()); + } + else if (config.second->opt_float(layer_height) == option->getFloat()) + { + dirty_options.erase(std::remove(dirty_options.begin(), dirty_options.end(), layer_height), dirty_options.end()); + nonsys_options.erase(std::remove(nonsys_options.begin(), nonsys_options.end(), layer_height), nonsys_options.end()); + } + } } - auto objects_list = wxGetApp().obj_list(); - wxDataViewItemArray items; - objects_list->GetSelections(items); - for (auto item : items) - objects_list->add_settings_item(item, &config.second->get()); - } -} -void TabPrintLayer::update_custom_dirty(std::vector &dirty_options, std::vector &nonsys_options) -{ - TabPrintModel::update_custom_dirty(dirty_options, nonsys_options); - auto option = m_parent_tab->get_config()->option(layer_height); - for (auto config : m_object_configs) { - if (!config.second->has(layer_height)) { - config.second->set_key_value(layer_height, option->clone()); - dirty_options.erase(std::remove(dirty_options.begin(), dirty_options.end(), layer_height), dirty_options.end()); - nonsys_options.erase(std::remove(nonsys_options.begin(), nonsys_options.end(), layer_height), nonsys_options.end()); - } - else if (config.second->opt_float(layer_height) == option->getFloat()) { - dirty_options.erase(std::remove(dirty_options.begin(), dirty_options.end(), layer_height), dirty_options.end()); - nonsys_options.erase(std::remove(nonsys_options.begin(), nonsys_options.end(), layer_height), nonsys_options.end()); + bool Tab::validate_custom_gcode(const wxString &title, const std::string &gcode) + { + std::vector tags; + bool invalid = GCodeProcessor::contains_reserved_tags(gcode, 5, tags); + if (invalid) + { + std::string lines = ":\n"; + for (const std::string &keyword : tags) + lines += ";" + keyword + "\n"; + wxString reports = format_wxstr( + _L_PLURAL("Following line %s contains reserved keywords.\nPlease remove it, or will beat G-code visualization and printing time estimation.", + "Following lines %s contain reserved keywords.\nPlease remove them, or will beat G-code visualization and printing time estimation.", + tags.size()), + lines); + // wxMessageDialog dialog(wxGetApp().mainframe, reports, _L("Found reserved keywords in") + " " + _(title), wxICON_WARNING | wxOK); + MessageDialog dialog(wxGetApp().mainframe, reports, _L("Reserved keywords found") + " " + _(title), wxICON_WARNING | wxOK); + dialog.ShowModal(); + } + return !invalid; } - } -} -bool Tab::validate_custom_gcode(const wxString& title, const std::string& gcode) -{ - std::vector tags; - bool invalid = GCodeProcessor::contains_reserved_tags(gcode, 5, tags); - if (invalid) { - std::string lines = ":\n"; - for (const std::string& keyword : tags) - lines += ";" + keyword + "\n"; - wxString reports = format_wxstr( - _L_PLURAL("Following line %s contains reserved keywords.\nPlease remove it, or will beat G-code visualization and printing time estimation.", - "Following lines %s contain reserved keywords.\nPlease remove them, or will beat G-code visualization and printing time estimation.", - tags.size()), - lines); - //wxMessageDialog dialog(wxGetApp().mainframe, reports, _L("Found reserved keywords in") + " " + _(title), wxICON_WARNING | wxOK); - MessageDialog dialog(wxGetApp().mainframe, reports, _L("Reserved keywords found") + " " + _(title), wxICON_WARNING | wxOK); - dialog.ShowModal(); - } - return !invalid; -} - -static void validate_custom_gcode_cb(Tab* tab, ConfigOptionsGroupShp opt_group, const t_config_option_key& opt_key, const boost::any& value) { - tab->validate_custom_gcodes_was_shown = !Tab::validate_custom_gcode(opt_group->title, boost::any_cast(value)); - tab->update_dirty(); - tab->on_value_change(opt_key, value); - if (boost::any_cast(value).size() > 40 * 1024) { - MessageDialog dialog(static_cast(wxGetApp().mainframe), _L("Custom G-code files are too large, and may not be synchronized to the cloud. Please keep it within 40k."), "", wxICON_WARNING | wxOK); - dialog.ShowModal(); - } -} + static void validate_custom_gcode_cb(Tab *tab, ConfigOptionsGroupShp opt_group, const t_config_option_key &opt_key, const boost::any &value) + { + tab->validate_custom_gcodes_was_shown = !Tab::validate_custom_gcode(opt_group->title, boost::any_cast(value)); + tab->update_dirty(); + tab->on_value_change(opt_key, value); + if (boost::any_cast(value).size() > 40 * 1024) + { + MessageDialog dialog(static_cast(wxGetApp().mainframe), _L("Custom G-code files are too large, and may not be synchronized to the cloud. Please keep it within 40k."), "", wxICON_WARNING | wxOK); + dialog.ShowModal(); + } + } -void TabFilament::add_filament_overrides_page() -{ - //BBS - PageShp page = add_options_page(L("Setting Overrides"), "empty"); - ConfigOptionsGroupShp optgroup = page->new_optgroup(L("Retraction"), L"param_retraction"); + void TabFilament::add_filament_overrides_page() + { + // BBS + PageShp page = add_options_page(L("Setting Overrides"), "empty"); + ConfigOptionsGroupShp optgroup = page->new_optgroup(L("Retraction"), L"param_retraction"); - auto append_single_option_line = [optgroup, this](const std::string& opt_key, int opt_index) - { - Line line {"",""}; - //BBS - line = optgroup->create_single_option_line(optgroup->get_option(opt_key, opt_index)); + auto append_single_option_line = [optgroup, this](const std::string &opt_key, int opt_index) + { + Line line{"", ""}; + // BBS + line = optgroup->create_single_option_line(optgroup->get_option(opt_key, opt_index)); - line.near_label_widget = [this, optgroup, opt_key, opt_index](wxWindow* parent) { - wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); + line.near_label_widget = [this, optgroup, opt_key, opt_index](wxWindow *parent) + { + wxCheckBox *check_box = new wxCheckBox(parent, wxID_ANY, ""); - check_box->Bind(wxEVT_CHECKBOX, [optgroup, opt_key, opt_index](wxCommandEvent& evt) { + check_box->Bind(wxEVT_CHECKBOX, [optgroup, opt_key, opt_index](wxCommandEvent &evt) + { const bool is_checked = evt.IsChecked(); Field* field = optgroup->get_fieldc(opt_key, opt_index); if (field != nullptr) { @@ -3638,382 +4003,387 @@ void TabFilament::add_filament_overrides_page() field->set_last_meaningful_value(); else field->set_na_value(); - } - }, check_box->GetId()); + } }, check_box->GetId()); - m_overrides_options[opt_key] = check_box; - return check_box; - }; + m_overrides_options[opt_key] = check_box; + return check_box; + }; - optgroup->append_line(line); - }; - - const int extruder_idx = 0; // #ys_FIXME - - for (const std::string opt_key : { "filament_retraction_length", - "filament_z_hop", - "filament_z_hop_types", - "filament_retraction_speed", - "filament_deretraction_speed", - "filament_retract_length_nc", - "filament_retract_restart_extra", - "filament_retraction_minimum_travel", - "filament_retract_when_changing_layer", - "filament_wipe", - //BBS - "filament_wipe_distance", - "filament_retract_before_wipe", - "filament_long_retractions_when_cut", - "filament_retraction_distances_when_cut" - }) - append_single_option_line(opt_key, extruder_idx); -} - -void TabFilament::update_filament_overrides_page() -{ - if (!m_active_page || m_active_page->title() != "Setting Overrides") - return; - - //BBS: GUI refactor - if (m_overrides_options.size() <= 0) - return; - - Page* page = m_active_page; - - const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) { return og->title == "Retraction"; }); - if (og_it == page->m_optgroups.end()) - return; - ConfigOptionsGroupShp optgroup = *og_it; - - std::vector opt_keys = { "filament_retraction_length", - "filament_z_hop", - "filament_z_hop_types", - "filament_retraction_speed", - "filament_deretraction_speed", - "filament_retract_length_nc", - "filament_retract_restart_extra", - "filament_retraction_minimum_travel", - "filament_retract_when_changing_layer", - "filament_wipe", - //BBS - "filament_wipe_distance", - "filament_retract_before_wipe", - "filament_long_retractions_when_cut", - "filament_retraction_distances_when_cut" - }; - - const int selection = m_variant_combo->GetSelection(); - auto opt = dynamic_cast(m_config->option("filament_retraction_length")); - const int extruder_idx = selection < 0 || selection >= static_cast(opt->size()) ? 0 : selection; - - const bool have_retract_length = dynamic_cast(m_config->option("filament_retraction_length"))->is_nil(extruder_idx) || - m_config->opt_float_nullable("filament_retraction_length", extruder_idx) > 0; - - for (const std::string& opt_key : opt_keys) - { - bool is_checked = opt_key=="filament_retraction_length" ? true : have_retract_length; - m_overrides_options[opt_key]->Enable(is_checked); - - is_checked &= !dynamic_cast(m_config->option(opt_key))->is_nil(extruder_idx); - m_overrides_options[opt_key]->SetValue(is_checked); - - Field* field = optgroup->get_fieldc(opt_key, 0); - if (field != nullptr) { - if (opt_key == "filament_long_retractions_when_cut") { - int machine_enabled_level = m_preset_bundle->printers.get_edited_preset().config.option("enable_long_retraction_when_cut")->value; - bool machine_enabled = machine_enabled_level == LongRectrationLevel::EnableFilament; - toggle_line(opt_key, machine_enabled, extruder_idx + 256); - field->toggle(is_checked && machine_enabled); - } - else if (opt_key == "filament_retraction_distances_when_cut") { - int machine_enabled_level = m_preset_bundle->printers.get_edited_preset().config.option("enable_long_retraction_when_cut")->value; - bool machine_enabled = machine_enabled_level == LongRectrationLevel::EnableFilament; - bool filament_enabled = m_config->option("filament_long_retractions_when_cut")->values[extruder_idx] == 1; - toggle_line(opt_key, filament_enabled && machine_enabled, extruder_idx + 256); - field->toggle(is_checked && filament_enabled && machine_enabled); - } - else - field->toggle(is_checked); - } - } -} + optgroup->append_line(line); + }; -void TabFilament::build() -{ - m_presets = &m_preset_bundle->filaments; - load_initial_data(); - - auto page = add_options_page(L("Filament"), "spool"); - //BBS - auto optgroup = page->new_optgroup(L("Basic information"), L"param_information"); - // Set size as all another fields for a better alignment - Option option = optgroup->get_option("filament_type"); - option.opt.width = Field::def_width_wider(); - optgroup->append_single_option_line(option); - optgroup->append_single_option_line("filament_vendor"); - optgroup->append_single_option_line("filament_soluble"); - // BBS - optgroup->append_single_option_line("filament_is_support"); - optgroup->append_single_option_line("impact_strength_z"); - - //optgroup->append_single_option_line("filament_prime_volume", "parameter/prime-tower#primevolume"); - //optgroup->append_single_option_line("filament_colour"); - optgroup->append_single_option_line("required_nozzle_HRC"); - optgroup->append_single_option_line("default_filament_colour"); - optgroup->append_single_option_line("filament_diameter"); - optgroup->append_single_option_line("filament_adhesiveness_category", "parameter/prime-tower#adhesiveness"); - optgroup->append_single_option_line("filament_flow_ratio", "", 0); - optgroup->append_single_option_line("enable_pressure_advance"); - optgroup->append_single_option_line("pressure_advance"); - optgroup->append_single_option_line("filament_density"); - optgroup->append_single_option_line("filament_shrink"); - optgroup->append_single_option_line("filament_velocity_adaptation_factor"); - optgroup->append_single_option_line("filament_cost"); - optgroup->append_single_option_line("temperature_vitrification"); - - optgroup->append_single_option_line("filament_cooling_before_tower"); - //BBS - Line line = {L("Filament prime volume"), L("The volume of material to prime extruder on tower.")}; - line.append_option(optgroup->get_option("filament_prime_volume")); - line.append_option(optgroup->get_option("filament_prime_volume_nc")); - optgroup->append_line(line); - - - line = {L("Filament ramming length"), L("Filament length used in ramming.")}; - line.append_option(optgroup->get_option("filament_change_length")); - line.append_option(optgroup->get_option("filament_change_length_nc")); - optgroup->append_line(line); - - line = {L("Travel time after ramming"), L("A reverse travel movement after ramming.")}; - line.append_option(optgroup->get_option("filament_ramming_travel_time",0)); - line.append_option(optgroup->get_option("filament_ramming_travel_time_nc",0)); - optgroup->append_line(line); - - line = { L("Precooling target temperature"), L("Precooling target temperature during ramming.") }; - line.append_option(optgroup->get_option("filament_pre_cooling_temperature",0)); - line.append_option(optgroup->get_option("filament_pre_cooling_temperature_nc",0)); - optgroup->append_line(line); - - line = { L("Recommended nozzle temperature"), L("Recommended nozzle temperature range of this filament. 0 means no set") }; - line.append_option(optgroup->get_option("nozzle_temperature_range_low")); - line.append_option(optgroup->get_option("nozzle_temperature_range_high")); - optgroup->append_line(line); - - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { - DynamicPrintConfig &filament_config = m_preset_bundle->filaments.get_edited_preset().config; + const int extruder_idx = 0; // #ys_FIXME + + for (const std::string opt_key : {"filament_retraction_length", + "filament_z_hop", + "filament_z_hop_types", + "filament_retraction_speed", + "filament_deretraction_speed", + "filament_retract_length_nc", + "filament_retract_restart_extra", + "filament_retraction_minimum_travel", + "filament_retract_when_changing_layer", + "filament_wipe", + // BBS + "filament_wipe_distance", + "filament_retract_before_wipe", + "filament_long_retractions_when_cut", + "filament_retraction_distances_when_cut"}) + append_single_option_line(opt_key, extruder_idx); + } + + void TabFilament::update_filament_overrides_page() + { + if (!m_active_page || m_active_page->title() != "Setting Overrides") + return; - update_dirty(); - if (!m_postpone_update_ui && (opt_key == "nozzle_temperature_range_low" || opt_key == "nozzle_temperature_range_high")) { - m_config_manipulation.check_nozzle_recommended_temperature_range(&filament_config); - } - on_value_change(opt_key, value); - }; + // BBS: GUI refactor + if (m_overrides_options.size() <= 0) + return; + + Page *page = m_active_page; + + const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) + { return og->title == "Retraction"; }); + if (og_it == page->m_optgroups.end()) + return; + ConfigOptionsGroupShp optgroup = *og_it; + + std::vector opt_keys = {"filament_retraction_length", + "filament_z_hop", + "filament_z_hop_types", + "filament_retraction_speed", + "filament_deretraction_speed", + "filament_retract_length_nc", + "filament_retract_restart_extra", + "filament_retraction_minimum_travel", + "filament_retract_when_changing_layer", + "filament_wipe", + // BBS + "filament_wipe_distance", + "filament_retract_before_wipe", + "filament_long_retractions_when_cut", + "filament_retraction_distances_when_cut"}; + + const int selection = m_variant_combo->GetSelection(); + auto opt = dynamic_cast(m_config->option("filament_retraction_length")); + const int extruder_idx = selection < 0 || selection >= static_cast(opt->size()) ? 0 : selection; + + const bool have_retract_length = dynamic_cast(m_config->option("filament_retraction_length"))->is_nil(extruder_idx) || + m_config->opt_float_nullable("filament_retraction_length", extruder_idx) > 0; + + for (const std::string &opt_key : opt_keys) + { + bool is_checked = opt_key == "filament_retraction_length" ? true : have_retract_length; + m_overrides_options[opt_key]->Enable(is_checked); + is_checked &= !dynamic_cast(m_config->option(opt_key))->is_nil(extruder_idx); + m_overrides_options[opt_key]->SetValue(is_checked); - optgroup = page->new_optgroup(L("Print temperature"), L"param_temperature"); - optgroup->append_single_option_line("chamber_temperatures","chamber-temperature"); + Field *field = optgroup->get_fieldc(opt_key, 0); + if (field != nullptr) + { + if (opt_key == "filament_long_retractions_when_cut") + { + int machine_enabled_level = m_preset_bundle->printers.get_edited_preset().config.option("enable_long_retraction_when_cut")->value; + bool machine_enabled = machine_enabled_level == LongRectrationLevel::EnableFilament; + toggle_line(opt_key, machine_enabled, extruder_idx + 256); + field->toggle(is_checked && machine_enabled); + } + else if (opt_key == "filament_retraction_distances_when_cut") + { + int machine_enabled_level = m_preset_bundle->printers.get_edited_preset().config.option("enable_long_retraction_when_cut")->value; + bool machine_enabled = machine_enabled_level == LongRectrationLevel::EnableFilament; + bool filament_enabled = m_config->option("filament_long_retractions_when_cut")->values[extruder_idx] == 1; + toggle_line(opt_key, filament_enabled && machine_enabled, extruder_idx + 256); + field->toggle(is_checked && filament_enabled && machine_enabled); + } + else + field->toggle(is_checked); + } + } + } + void TabFilament::build() + { + m_presets = &m_preset_bundle->filaments; + load_initial_data(); + + auto page = add_options_page(L("Filament"), "spool"); + // BBS + auto optgroup = page->new_optgroup(L("Basic information"), L"param_information"); + // Set size as all another fields for a better alignment + Option option = optgroup->get_option("filament_type"); + option.opt.width = Field::def_width_wider(); + optgroup->append_single_option_line(option); + optgroup->append_single_option_line("filament_vendor"); + optgroup->append_single_option_line("filament_soluble"); + // BBS + optgroup->append_single_option_line("filament_is_support"); + optgroup->append_single_option_line("impact_strength_z"); + + // optgroup->append_single_option_line("filament_prime_volume", "parameter/prime-tower#primevolume"); + // optgroup->append_single_option_line("filament_colour"); + optgroup->append_single_option_line("required_nozzle_HRC"); + optgroup->append_single_option_line("default_filament_colour"); + optgroup->append_single_option_line("filament_diameter"); + optgroup->append_single_option_line("filament_adhesiveness_category", "parameter/prime-tower#adhesiveness"); + optgroup->append_single_option_line("filament_flow_ratio", "", 0); + optgroup->append_single_option_line("enable_pressure_advance"); + optgroup->append_single_option_line("pressure_advance"); + optgroup->append_single_option_line("filament_density"); + optgroup->append_single_option_line("filament_shrink"); + optgroup->append_single_option_line("filament_velocity_adaptation_factor"); + optgroup->append_single_option_line("filament_cost"); + optgroup->append_single_option_line("temperature_vitrification"); + + optgroup->append_single_option_line("filament_cooling_before_tower"); + // BBS + Line line = {L("Filament prime volume"), L("The volume of material to prime extruder on tower.")}; + line.append_option(optgroup->get_option("filament_prime_volume")); + line.append_option(optgroup->get_option("filament_prime_volume_nc")); + optgroup->append_line(line); + + line = {L("Filament ramming length"), L("Filament length used in ramming.")}; + line.append_option(optgroup->get_option("filament_change_length")); + line.append_option(optgroup->get_option("filament_change_length_nc")); + optgroup->append_line(line); + + line = {L("Travel time after ramming"), L("A reverse travel movement after ramming.")}; + line.append_option(optgroup->get_option("filament_ramming_travel_time", 0)); + line.append_option(optgroup->get_option("filament_ramming_travel_time_nc", 0)); + optgroup->append_line(line); + + line = {L("Precooling target temperature"), L("Precooling target temperature during ramming.")}; + line.append_option(optgroup->get_option("filament_pre_cooling_temperature", 0)); + line.append_option(optgroup->get_option("filament_pre_cooling_temperature_nc", 0)); + optgroup->append_line(line); + + line = {L("Recommended nozzle temperature"), L("Recommended nozzle temperature range of this filament. 0 means no set")}; + line.append_option(optgroup->get_option("nozzle_temperature_range_low")); + line.append_option(optgroup->get_option("nozzle_temperature_range_high")); + optgroup->append_line(line); + + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) + { + DynamicPrintConfig &filament_config = m_preset_bundle->filaments.get_edited_preset().config; - line = {L("Bambu Cool Plate SuperTack"), L("Bed temperature when cool plate is installed. Value 0 means the filament does not support to print on the Bambu Cool Plate SuperTack")}; - line.append_option(optgroup->get_option("supertack_plate_temp_initial_layer")); - line.append_option(optgroup->get_option("supertack_plate_temp")); - optgroup->append_line(line); + update_dirty(); + if (!m_postpone_update_ui && (opt_key == "nozzle_temperature_range_low" || opt_key == "nozzle_temperature_range_high")) + { + m_config_manipulation.check_nozzle_recommended_temperature_range(&filament_config); + } + on_value_change(opt_key, value); + }; - line = { L("Cool Plate"), L("Bed temperature when cool plate is installed. Value 0 means the filament does not support to print on the Cool Plate") }; - line.append_option(optgroup->get_option("cool_plate_temp_initial_layer")); - line.append_option(optgroup->get_option("cool_plate_temp")); - optgroup->append_line(line); + optgroup = page->new_optgroup(L("Print temperature"), L"param_temperature"); + optgroup->append_single_option_line("chamber_temperatures", "chamber-temperature"); - line = { L("Engineering Plate"), L("Bed temperature when engineering plate is installed. Value 0 means the filament does not support to print on the Engineering Plate") }; - line.append_option(optgroup->get_option("eng_plate_temp_initial_layer")); - line.append_option(optgroup->get_option("eng_plate_temp")); - optgroup->append_line(line); + line = {L("Bambu Cool Plate SuperTack"), L("Bed temperature when cool plate is installed. Value 0 means the filament does not support to print on the Bambu Cool Plate SuperTack")}; + line.append_option(optgroup->get_option("supertack_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("supertack_plate_temp")); + optgroup->append_line(line); - line = {L("Smooth PEI Plate / High Temp Plate"), L("Bed temperature when Smooth PEI Plate/High temperature plate is installed. Value 0 means the filament does not support to print on the Smooth PEI Plate/High Temp Plate") }; - line.append_option(optgroup->get_option("hot_plate_temp_initial_layer")); - line.append_option(optgroup->get_option("hot_plate_temp")); - optgroup->append_line(line); + line = {L("Cool Plate"), L("Bed temperature when cool plate is installed. Value 0 means the filament does not support to print on the Cool Plate")}; + line.append_option(optgroup->get_option("cool_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("cool_plate_temp")); + optgroup->append_line(line); - line = {L("Textured PEI Plate"), L("Bed temperature when Textured PEI Plate is installed. Value 0 means the filament does not support to print on the Textured PEI Plate")}; - line.append_option(optgroup->get_option("textured_plate_temp_initial_layer")); - line.append_option(optgroup->get_option("textured_plate_temp")); - optgroup->append_line(line); + line = {L("Engineering Plate"), L("Bed temperature when engineering plate is installed. Value 0 means the filament does not support to print on the Engineering Plate")}; + line.append_option(optgroup->get_option("eng_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("eng_plate_temp")); + optgroup->append_line(line); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) - { - DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config; + line = {L("Smooth PEI Plate / High Temp Plate"), L("Bed temperature when Smooth PEI Plate/High temperature plate is installed. Value 0 means the filament does not support to print on the Smooth PEI Plate/High Temp Plate")}; + line.append_option(optgroup->get_option("hot_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("hot_plate_temp")); + optgroup->append_line(line); - update_dirty(); - /*if (opt_key == "cool_plate_temp" || opt_key == "cool_plate_temp_initial_layer") { - m_config_manipulation.check_bed_temperature_difference(BedType::btPC, &filament_config); - } - else if (opt_key == "eng_plate_temp" || opt_key == "eng_plate_temp_initial_layer") { - m_config_manipulation.check_bed_temperature_difference(BedType::btEP, &filament_config); - } - else if (opt_key == "hot_plate_temp" || opt_key == "hot_plate_temp_initial_layer") { - m_config_manipulation.check_bed_temperature_difference(BedType::btPEI, &filament_config); - } - else if (opt_key == "textured_plate_temp" || opt_key == "textured_plate_temp_initial_layer") { - m_config_manipulation.check_bed_temperature_difference(BedType::btPTE, &filament_config); - } - else */if (opt_key == "nozzle_temperature") { - m_config_manipulation.check_nozzle_temperature_range(&filament_config); - } - else if (opt_key == "nozzle_temperature_initial_layer") { - m_config_manipulation.check_nozzle_temperature_initial_layer_range(&filament_config); - } - else if (opt_key == "chamber_temperatures") { - m_config_manipulation.check_chamber_temperature(&filament_config); - } + line = {L("Textured PEI Plate"), L("Bed temperature when Textured PEI Plate is installed. Value 0 means the filament does not support to print on the Textured PEI Plate")}; + line.append_option(optgroup->get_option("textured_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("textured_plate_temp")); + optgroup->append_line(line); - on_value_change(opt_key, value); - }; + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) + { + DynamicPrintConfig &filament_config = m_preset_bundle->filaments.get_edited_preset().config; - line = { L("Nozzle"), L("Nozzle temperature when printing") }; - line.append_option(optgroup->get_option("nozzle_temperature_initial_layer", 0)); - line.append_option(optgroup->get_option("nozzle_temperature", 0)); - optgroup->append_line(line); - - //BBS - optgroup = page->new_optgroup(L("Volumetric speed limitation"), L"param_volumetric_speed"); - optgroup->append_single_option_line("filament_adaptive_volumetric_speed", "", 0); - optgroup->append_single_option_line("filament_max_volumetric_speed", "", 0); - line = {L("Ramming volumetric speed"), L("The maximum volumetric speed for ramming, where -1 means using the maximum volumetric speed.")}; - line.append_option(optgroup->get_option("filament_ramming_volumetric_speed", 0)); - line.append_option(optgroup->get_option("filament_ramming_volumetric_speed_nc", 0)); - optgroup->append_line(line); - - // BBS - optgroup = page->new_optgroup(L("Filament scarf seam settings"), L"param_volumetric_speed"); - optgroup->append_single_option_line("filament_scarf_seam_type"); - optgroup->append_single_option_line("filament_scarf_height"); - optgroup->append_single_option_line("filament_scarf_gap"); - optgroup->append_single_option_line("filament_scarf_length"); - - //optgroup = page->new_optgroup(L("Filament circle compensation setting"), L"param_volumetric_speed"); - //optgroup->append_single_option_line("circle_compensation_speed"); - //optgroup->append_single_option_line("counter_coef_1"); - //optgroup->append_single_option_line("counter_coef_2"); - //optgroup->append_single_option_line("counter_coef_3"); - //optgroup->append_single_option_line("hole_coef_1"); - //optgroup->append_single_option_line("hole_coef_2"); - //optgroup->append_single_option_line("hole_coef_3"); - //optgroup->append_single_option_line("counter_limit_min"); - //optgroup->append_single_option_line("counter_limit_max"); - //optgroup->append_single_option_line("hole_limit_min"); - //optgroup->append_single_option_line("hole_limit_max"); - //optgroup->append_single_option_line("diameter_limit"); - //line = { "", "" }; - //line.full_width = 1; - //line.widget = [this](wxWindow* parent) { - // return description_line_widget(parent, &m_volumetric_speed_description_line); - //}; - //optgroup->append_line(line); + update_dirty(); + /*if (opt_key == "cool_plate_temp" || opt_key == "cool_plate_temp_initial_layer") { + m_config_manipulation.check_bed_temperature_difference(BedType::btPC, &filament_config); + } + else if (opt_key == "eng_plate_temp" || opt_key == "eng_plate_temp_initial_layer") { + m_config_manipulation.check_bed_temperature_difference(BedType::btEP, &filament_config); + } + else if (opt_key == "hot_plate_temp" || opt_key == "hot_plate_temp_initial_layer") { + m_config_manipulation.check_bed_temperature_difference(BedType::btPEI, &filament_config); + } + else if (opt_key == "textured_plate_temp" || opt_key == "textured_plate_temp_initial_layer") { + m_config_manipulation.check_bed_temperature_difference(BedType::btPTE, &filament_config); + } + else */ + if (opt_key == "nozzle_temperature") + { + m_config_manipulation.check_nozzle_temperature_range(&filament_config); + } + else if (opt_key == "nozzle_temperature_initial_layer") + { + m_config_manipulation.check_nozzle_temperature_initial_layer_range(&filament_config); + } + else if (opt_key == "chamber_temperatures") + { + m_config_manipulation.check_chamber_temperature(&filament_config); + } - page = add_options_page(L("Cooling"), "empty"); + on_value_change(opt_key, value); + }; - //line = { "", "" }; - //line.full_width = 1; - //line.widget = [this](wxWindow* parent) { - // return description_line_widget(parent, &m_cooling_description_line); - //}; - //optgroup->append_line(line); - optgroup = page->new_optgroup(L("Cooling for specific layer"), L"param_cooling"); - //optgroup->append_single_option_line("close_fan_the_first_x_layers", "auto-cooling"); - //optgroup->append_single_option_line("full_fan_speed_layer"); - line = {L("Special cooling settings"), - L("set addition fan speed before fist x layer")}; - line.label_path = "auto-cooling"; - line.append_option(optgroup->get_option("close_fan_the_first_x_layers")); - line.append_option(optgroup->get_option("first_x_layer_fan_speed")); - optgroup->append_line(line); - - optgroup = page->new_optgroup(L("Part cooling fan"), L"param_cooling_fan"); - line = { L("Min fan speed threshold"), L("Part cooling fan speed will start to run at min speed when the estimated layer time is no longer than the layer time in setting. When layer time is shorter than threshold, fan speed is interpolated between the minimum and maximum fan speed according to layer printing time") }; - line.label_path = "auto-cooling"; - line.append_option(optgroup->get_option("fan_min_speed")); - line.append_option(optgroup->get_option("fan_cooling_layer_time")); - optgroup->append_line(line); - line = { L("Max fan speed threshold"), L("Part cooling fan speed will be max when the estimated layer time is shorter than the setting value") }; - line.label_path = "auto-cooling"; - line.append_option(optgroup->get_option("fan_max_speed")); - line.append_option(optgroup->get_option("slow_down_layer_time")); - optgroup->append_line(line); - optgroup->append_single_option_line("reduce_fan_stop_start_freq", "auto-cooling"); - optgroup->append_single_option_line("slow_down_for_layer_cooling", "auto-cooling"); - optgroup->append_single_option_line("no_slow_down_for_cooling_on_outwalls", "auto-cooling"); - optgroup->append_single_option_line("slow_down_min_speed","auto-cooling"); - - optgroup->append_single_option_line("enable_overhang_bridge_fan", "auto-cooling"); - optgroup->append_single_option_line("overhang_fan_threshold", "auto-cooling"); - optgroup->append_single_option_line("overhang_threshold_participating_cooling", "auto-cooling"); - optgroup->append_single_option_line("overhang_fan_speed", "auto-cooling"); - optgroup->append_single_option_line("pre_start_fan_time", "auto-cooling"); - - optgroup = page->new_optgroup(L("Auxiliary part cooling fan"), L"param_cooling_fan"); - optgroup->append_single_option_line("additional_cooling_fan_speed"); - - optgroup = page->new_optgroup(L("Exhaust fan"),L"param_cooling_fan"); - - optgroup->append_single_option_line("activate_air_filtration"); - - line = {L("During print"), ""}; - line.append_option(optgroup->get_option("during_print_exhaust_fan_speed")); - optgroup->append_line(line); - - - line = {L("Complete print"), ""}; - line.append_option(optgroup->get_option("complete_print_exhaust_fan_speed")); - optgroup->append_line(line); - //BBS - add_filament_overrides_page(); + line = {L("Nozzle"), L("Nozzle temperature when printing")}; + line.append_option(optgroup->get_option("nozzle_temperature_initial_layer", 0)); + line.append_option(optgroup->get_option("nozzle_temperature", 0)); + optgroup->append_line(line); + + // BBS + optgroup = page->new_optgroup(L("Volumetric speed limitation"), L"param_volumetric_speed"); + optgroup->append_single_option_line("filament_adaptive_volumetric_speed", "", 0); + optgroup->append_single_option_line("filament_max_volumetric_speed", "", 0); + line = {L("Ramming volumetric speed"), L("The maximum volumetric speed for ramming, where -1 means using the maximum volumetric speed.")}; + line.append_option(optgroup->get_option("filament_ramming_volumetric_speed", 0)); + line.append_option(optgroup->get_option("filament_ramming_volumetric_speed_nc", 0)); + optgroup->append_line(line); + + // BBS + optgroup = page->new_optgroup(L("Filament scarf seam settings"), L"param_volumetric_speed"); + optgroup->append_single_option_line("filament_scarf_seam_type"); + optgroup->append_single_option_line("filament_scarf_height"); + optgroup->append_single_option_line("filament_scarf_gap"); + optgroup->append_single_option_line("filament_scarf_length"); + + // optgroup = page->new_optgroup(L("Filament circle compensation setting"), L"param_volumetric_speed"); + // optgroup->append_single_option_line("circle_compensation_speed"); + // optgroup->append_single_option_line("counter_coef_1"); + // optgroup->append_single_option_line("counter_coef_2"); + // optgroup->append_single_option_line("counter_coef_3"); + // optgroup->append_single_option_line("hole_coef_1"); + // optgroup->append_single_option_line("hole_coef_2"); + // optgroup->append_single_option_line("hole_coef_3"); + // optgroup->append_single_option_line("counter_limit_min"); + // optgroup->append_single_option_line("counter_limit_max"); + // optgroup->append_single_option_line("hole_limit_min"); + // optgroup->append_single_option_line("hole_limit_max"); + // optgroup->append_single_option_line("diameter_limit"); + // line = { "", "" }; + // line.full_width = 1; + // line.widget = [this](wxWindow* parent) { + // return description_line_widget(parent, &m_volumetric_speed_description_line); + // }; + // optgroup->append_line(line); + + page = add_options_page(L("Cooling"), "empty"); + + // line = { "", "" }; + // line.full_width = 1; + // line.widget = [this](wxWindow* parent) { + // return description_line_widget(parent, &m_cooling_description_line); + // }; + // optgroup->append_line(line); + optgroup = page->new_optgroup(L("Cooling for specific layer"), L"param_cooling"); + // optgroup->append_single_option_line("close_fan_the_first_x_layers", "auto-cooling"); + // optgroup->append_single_option_line("full_fan_speed_layer"); + line = {L("Special cooling settings"), + L("set addition fan speed before fist x layer")}; + line.label_path = "auto-cooling"; + line.append_option(optgroup->get_option("close_fan_the_first_x_layers")); + line.append_option(optgroup->get_option("first_x_layer_fan_speed")); + optgroup->append_line(line); + + optgroup = page->new_optgroup(L("Part cooling fan"), L"param_cooling_fan"); + line = {L("Min fan speed threshold"), L("Part cooling fan speed will start to run at min speed when the estimated layer time is no longer than the layer time in setting. When layer time is shorter than threshold, fan speed is interpolated between the minimum and maximum fan speed according to layer printing time")}; + line.label_path = "auto-cooling"; + line.append_option(optgroup->get_option("fan_min_speed")); + line.append_option(optgroup->get_option("fan_cooling_layer_time")); + optgroup->append_line(line); + line = {L("Max fan speed threshold"), L("Part cooling fan speed will be max when the estimated layer time is shorter than the setting value")}; + line.label_path = "auto-cooling"; + line.append_option(optgroup->get_option("fan_max_speed")); + line.append_option(optgroup->get_option("slow_down_layer_time")); + optgroup->append_line(line); + optgroup->append_single_option_line("reduce_fan_stop_start_freq", "auto-cooling"); + optgroup->append_single_option_line("slow_down_for_layer_cooling", "auto-cooling"); + optgroup->append_single_option_line("no_slow_down_for_cooling_on_outwalls", "auto-cooling"); + optgroup->append_single_option_line("slow_down_min_speed", "auto-cooling"); + + optgroup->append_single_option_line("enable_overhang_bridge_fan", "auto-cooling"); + optgroup->append_single_option_line("overhang_fan_threshold", "auto-cooling"); + optgroup->append_single_option_line("overhang_threshold_participating_cooling", "auto-cooling"); + optgroup->append_single_option_line("overhang_fan_speed", "auto-cooling"); + optgroup->append_single_option_line("pre_start_fan_time", "auto-cooling"); + + optgroup = page->new_optgroup(L("Auxiliary part cooling fan"), L"param_cooling_fan"); + optgroup->append_single_option_line("additional_cooling_fan_speed"); + + optgroup = page->new_optgroup(L("Exhaust fan"), L"param_cooling_fan"); + + optgroup->append_single_option_line("activate_air_filtration"); + + line = {L("During print"), ""}; + line.append_option(optgroup->get_option("during_print_exhaust_fan_speed")); + optgroup->append_line(line); + + line = {L("Complete print"), ""}; + line.append_option(optgroup->get_option("complete_print_exhaust_fan_speed")); + optgroup->append_line(line); + // BBS + add_filament_overrides_page(); #if 0 //page = add_options_page(L("Advanced"), "advanced"); // optgroup = page->new_optgroup(L("Wipe tower parameters")); // optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); #endif - const int gcode_field_height = 15; // 150 - const int notes_field_height = 25; // 250 - - page = add_options_page(L("Advanced"), "advanced"); - optgroup = page->new_optgroup(L("Filament start G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("filament_start_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height;// 150; - optgroup->append_single_option_line(option); - - optgroup = page->new_optgroup(L("Filament end G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("filament_end_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height;// 150; - optgroup->append_single_option_line(option); + const int gcode_field_height = 15; // 150 + const int notes_field_height = 25; // 250 - page = add_options_page(L("Notes"), "note"); - optgroup = page->new_optgroup(L("Notes"),"note"); - optgroup->label_width = 0; - option = optgroup->get_option("filament_notes"); - option.opt.full_width = true; - option.opt.height = notes_field_height; - optgroup->append_single_option_line(option); - optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { validate_custom_note_cb(this, optgroup, opt_key, value); }; + page = add_options_page(L("Advanced"), "advanced"); + optgroup = page->new_optgroup(L("Filament start G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("filament_start_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Filament end G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("filament_end_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); - page = add_options_page(L("Multi Filament"), "advanced"); - optgroup = page->new_optgroup(L("Multi Filament")); - // optgroup->append_single_option_line("filament_flush_temp", "", 0); - // optgroup->append_single_option_line("filament_flush_volumetric_speed", "", 0); - optgroup->append_single_option_line("long_retractions_when_ec", "" , 0); - optgroup->append_single_option_line("retraction_distances_when_ec", "" , 0); - //BBS + page = add_options_page(L("Notes"), "note"); + optgroup = page->new_optgroup(L("Notes"), "note"); + optgroup->label_width = 0; + option = optgroup->get_option("filament_notes"); + option.opt.full_width = true; + option.opt.height = notes_field_height; + optgroup->append_single_option_line(option); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { validate_custom_note_cb(this, optgroup, opt_key, value); }; + + page = add_options_page(L("Multi Filament"), "advanced"); + optgroup = page->new_optgroup(L("Multi Filament")); + // optgroup->append_single_option_line("filament_flush_temp", "", 0); + // optgroup->append_single_option_line("filament_flush_volumetric_speed", "", 0); + optgroup->append_single_option_line("long_retractions_when_ec", "", 0); + optgroup->append_single_option_line("retraction_distances_when_ec", "", 0); + // BBS #if 0 //page = add_options_page(L("Dependencies"), "advanced"); // optgroup = page->new_optgroup(L("Profile dependencies")); @@ -4035,211 +4405,211 @@ void TabFilament::build() // build_preset_description_line(optgroup.get()); #endif -} + } -// Reload current config (aka presets->edited_preset->config) into the UI fields. -void TabFilament::reload_config() -{ - this->compatible_widget_reload(m_compatible_printers); - this->compatible_widget_reload(m_compatible_prints); - Tab::reload_config(); -} - -//void TabFilament::update_volumetric_flow_preset_hints() -//{ -// wxString text; -// try { -// text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); -// } catch (std::exception &ex) { -// text = _(L("Volumetric flow hints not available")) + "\n\n" + from_u8(ex.what()); -// } -// m_volumetric_speed_description_line->SetText(text); -//} - -void TabFilament::update_description_lines() -{ - Tab::update_description_lines(); + // Reload current config (aka presets->edited_preset->config) into the UI fields. + void TabFilament::reload_config() + { + this->compatible_widget_reload(m_compatible_printers); + this->compatible_widget_reload(m_compatible_prints); + Tab::reload_config(); + } - if (!m_active_page) - return; + // void TabFilament::update_volumetric_flow_preset_hints() + //{ + // wxString text; + // try { + // text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); + // } catch (std::exception &ex) { + // text = _(L("Volumetric flow hints not available")) + "\n\n" + from_u8(ex.what()); + // } + // m_volumetric_speed_description_line->SetText(text); + // } + + void TabFilament::update_description_lines() + { + Tab::update_description_lines(); - if (m_active_page->title() == "Cooling" && m_cooling_description_line) - m_cooling_description_line->SetText(from_u8(PresetHints::cooling_description(m_presets->get_edited_preset()))); - //BBS - //if (m_active_page->title() == "Filament" && m_volumetric_speed_description_line) - // this->update_volumetric_flow_preset_hints(); -} + if (!m_active_page) + return; -void TabFilament::toggle_options() -{ - if (!m_active_page) - return; - - bool is_BBL_printer = false; - if (m_preset_bundle) { - is_BBL_printer = - m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset( - m_preset_bundle); - } - bool is_multi_extruder = m_preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->size() > 1; + if (m_active_page->title() == "Cooling" && m_cooling_description_line) + m_cooling_description_line->SetText(from_u8(PresetHints::cooling_description(m_presets->get_edited_preset()))); + // BBS + // if (m_active_page->title() == "Filament" && m_volumetric_speed_description_line) + // this->update_volumetric_flow_preset_hints(); + } - if (m_active_page->title() == "Cooling") - { - bool cooling = m_config->opt_bool("slow_down_for_layer_cooling", 0); - toggle_option("slow_down_min_speed", cooling); - toggle_option("no_slow_down_for_cooling_on_outwalls", cooling); + void TabFilament::toggle_options() + { + if (!m_active_page) + return; - bool has_enable_overhang_bridge_fan = m_config->opt_bool("enable_overhang_bridge_fan", 0); - for (auto el : {"overhang_fan_speed", "pre_start_fan_time", "overhang_fan_threshold"}) - toggle_option(el, has_enable_overhang_bridge_fan); + bool is_BBL_printer = false; + if (m_preset_bundle) + { + is_BBL_printer = + m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset( + m_preset_bundle); + } + bool is_multi_extruder = m_preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->size() > 1; - bool support_air_filtration = m_preset_bundle->printers.get_edited_preset().config.opt_bool("support_air_filtration"); - toggle_line("activate_air_filtration",is_BBL_printer && support_air_filtration); + if (m_active_page->title() == "Cooling") + { + bool cooling = m_config->opt_bool("slow_down_for_layer_cooling", 0); + toggle_option("slow_down_min_speed", cooling); + toggle_option("no_slow_down_for_cooling_on_outwalls", cooling); - for (auto elem : { "during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed" }) - toggle_line(elem, m_config->opt_bool("activate_air_filtration",0)&&support_air_filtration); + bool has_enable_overhang_bridge_fan = m_config->opt_bool("enable_overhang_bridge_fan", 0); + for (auto el : {"overhang_fan_speed", "pre_start_fan_time", "overhang_fan_threshold"}) + toggle_option(el, has_enable_overhang_bridge_fan); - } - if (m_active_page->title() == "Filament") - { - toggle_option("filament_type", false); - toggle_option("filament_vendor", false); - toggle_option("impact_strength_z", false); - //BBS: hide these useless option for bambu printer - toggle_line("enable_pressure_advance", !is_BBL_printer); - if (is_BBL_printer) - toggle_line("pressure_advance", false); - else { - toggle_line("pressure_advance", true); - toggle_option("pressure_advance", m_config->opt_bool("enable_pressure_advance", 0)); + bool support_air_filtration = m_preset_bundle->printers.get_edited_preset().config.opt_bool("support_air_filtration"); + toggle_line("activate_air_filtration", is_BBL_printer && support_air_filtration); + + for (auto elem : {"during_print_exhaust_fan_speed", "complete_print_exhaust_fan_speed"}) + toggle_line(elem, m_config->opt_bool("activate_air_filtration", 0) && support_air_filtration); + } + if (m_active_page->title() == "Filament") + { + toggle_option("filament_type", false); + toggle_option("filament_vendor", false); + toggle_option("impact_strength_z", false); + // BBS: hide these useless option for bambu printer + toggle_line("enable_pressure_advance", !is_BBL_printer); + if (is_BBL_printer) + toggle_line("pressure_advance", false); + else + { + toggle_line("pressure_advance", true); + toggle_option("pressure_advance", m_config->opt_bool("enable_pressure_advance", 0)); + } + + bool support_chamber_temp_control = this->m_preset_bundle->printers.get_edited_preset().config.opt_bool("support_chamber_temp_control"); + toggle_line("chamber_temperatures", support_chamber_temp_control); + + for (auto el : {"supertack_plate_temp", "supertack_plate_temp_initial_layer", "cool_plate_temp", "cool_plate_temp_initial_layer", "eng_plate_temp", "eng_plate_temp_initial_layer", "textured_plate_temp", "textured_plate_temp_initial_layer"}) + toggle_line(el, is_BBL_printer); + + std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", (unsigned int)(m_variant_combo->GetSelection())); + bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0"; + toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + (unsigned int)(m_variant_combo->GetSelection())); + auto prime_volume = this->m_preset_bundle->project_config.option>("prime_volume_mode")->value; + toggle_option("filament_prime_volume", prime_volume == PrimeVolumeMode::pvmDefault, 256 + (unsigned int)(m_variant_combo->GetSelection())); + toggle_option("filament_prime_volume_nc", prime_volume == PrimeVolumeMode::pvmDefault, 256 + (unsigned int)(m_variant_combo->GetSelection())); + } + + if (m_active_page->title() == "Multi Filament") + { + const int extruder_idx = m_variant_combo->GetSelection(); + toggle_line("long_retractions_when_ec", is_multi_extruder && is_BBL_printer, 256 + extruder_idx); + toggle_line("retraction_distances_when_ec", is_multi_extruder && is_BBL_printer && m_config->opt_bool_nullable("long_retractions_when_ec", extruder_idx), 256 + extruder_idx); + } + + if (m_active_page->title() == "Setting Overrides") + update_filament_overrides_page(); } - bool support_chamber_temp_control = this->m_preset_bundle->printers.get_edited_preset().config.opt_bool("support_chamber_temp_control"); - toggle_line("chamber_temperatures", support_chamber_temp_control); + void TabFilament::update() + { + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME - for (auto el : {"supertack_plate_temp", "supertack_plate_temp_initial_layer", "cool_plate_temp", "cool_plate_temp_initial_layer", "eng_plate_temp", "eng_plate_temp_initial_layer", "textured_plate_temp", "textured_plate_temp_initial_layer"}) - toggle_line(el, is_BBL_printer); + m_config_manipulation.check_filament_max_volumetric_speed(m_config); + m_config_manipulation.check_filament_scarf_setting(m_config); - std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", (unsigned int)(m_variant_combo->GetSelection())); - bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0"; - toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + (unsigned int) (m_variant_combo->GetSelection())); - auto prime_volume = this->m_preset_bundle->project_config.option>("prime_volume_mode")->value; - toggle_option("filament_prime_volume", prime_volume == PrimeVolumeMode::pvmDefault, 256 + (unsigned int) (m_variant_combo->GetSelection())); - toggle_option("filament_prime_volume_nc", prime_volume == PrimeVolumeMode::pvmDefault, 256 + (unsigned int) (m_variant_combo->GetSelection())); - } + m_update_cnt++; - if (m_active_page->title() == "Multi Filament") { - const int extruder_idx = m_variant_combo->GetSelection(); - toggle_line("long_retractions_when_ec", is_multi_extruder && is_BBL_printer, 256 + extruder_idx); - toggle_line("retraction_distances_when_ec", is_multi_extruder && is_BBL_printer && m_config->opt_bool_nullable("long_retractions_when_ec", extruder_idx), 256 + extruder_idx); - } + update_description_lines(); + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); - if (m_active_page->title() == "Setting Overrides") - update_filament_overrides_page(); -} + toggle_options(); -void TabFilament::update() -{ - if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) - return; // ys_FIXME + m_update_cnt--; + + if (m_update_cnt == 0) + wxGetApp().mainframe->on_config_changed(m_config); + } - m_config_manipulation.check_filament_max_volumetric_speed(m_config); - m_config_manipulation.check_filament_scarf_setting(m_config); + void TabFilament::clear_pages() + { + Tab::clear_pages(); - m_update_cnt++; + m_volumetric_speed_description_line = nullptr; + m_cooling_description_line = nullptr; - update_description_lines(); - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); + // BBS: GUI refactor + m_overrides_options.clear(); + } - toggle_options(); + wxSizer *Tab::description_line_widget(wxWindow *parent, ogStaticText **StaticText, wxString text /*= wxEmptyString*/) + { + *StaticText = new ogStaticText(parent, text); - m_update_cnt--; + // auto font = (new wxSystemSettings)->GetFont(wxSYS_DEFAULT_GUI_FONT); + (*StaticText)->SetFont(wxGetApp().normal_font()); - if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); -} + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(*StaticText, 1, wxEXPAND | wxALL, 0); + return sizer; + } -void TabFilament::clear_pages() -{ - Tab::clear_pages(); + bool Tab::saved_preset_is_dirty() const { return m_presets->saved_is_dirty(); } + void Tab::update_saved_preset_from_current_preset() { m_presets->update_saved_preset_from_current_preset(); } + bool Tab::current_preset_is_dirty() const { return m_presets->current_is_dirty(); } - m_volumetric_speed_description_line = nullptr; - m_cooling_description_line = nullptr; + void TabPrinter::build() + { + m_presets = &m_preset_bundle->printers; + m_printer_technology = m_presets->get_selected_preset().printer_technology(); - //BBS: GUI refactor - m_overrides_options.clear(); -} + // For DiffPresetDialog we use options list which is saved in Searcher class. + // Options for the Searcher is added in the moment of pages creation. + // So, build first of all printer pages for non-selected printer technology... + // std::string def_preset_name = "- default " + std::string(m_printer_technology == ptSLA ? "FFF" : "SLA") + " -"; + std::string def_preset_name = "Default Printer"; + m_config = &m_presets->find_preset(def_preset_name)->config; + m_printer_technology == ptSLA ? build_fff() : build_sla(); + if (m_printer_technology == ptSLA) + m_extruders_count_old = 0; // revert this value -wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText, wxString text /*= wxEmptyString*/) -{ - *StaticText = new ogStaticText(parent, text); + // ... and than for selected printer technology + load_initial_data(); + m_printer_technology == ptSLA ? build_sla() : build_fff(); + } -// auto font = (new wxSystemSettings)->GetFont(wxSYS_DEFAULT_GUI_FONT); - (*StaticText)->SetFont(wxGetApp().normal_font()); + void TabPrinter::build_fff() + { + if (!m_pages.empty()) + m_pages.resize(0); + // to avoid redundant memory allocation / deallocation during extruders count changing + m_pages.reserve(30); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(*StaticText, 1, wxEXPAND|wxALL, 0); - return sizer; -} + auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); + m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); + m_extruder_variant_list = m_config->option("printer_extruder_variant")->values; + // BBS + // wxGetApp().obj_list()->update_objects_list_filament_column(m_initial_extruders_count); -bool Tab::saved_preset_is_dirty() const { return m_presets->saved_is_dirty(); } -void Tab::update_saved_preset_from_current_preset() { m_presets->update_saved_preset_from_current_preset(); } -bool Tab::current_preset_is_dirty() const { return m_presets->current_is_dirty(); } + const Preset *parent_preset = m_printer_technology == ptSLA ? nullptr // just for first build, if SLA printer preset is selected + : m_presets->get_selected_preset_parent(); + m_sys_extruders_count = parent_preset == nullptr ? 0 : static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); -void TabPrinter::build() -{ - m_presets = &m_preset_bundle->printers; - m_printer_technology = m_presets->get_selected_preset().printer_technology(); - - // For DiffPresetDialog we use options list which is saved in Searcher class. - // Options for the Searcher is added in the moment of pages creation. - // So, build first of all printer pages for non-selected printer technology... - //std::string def_preset_name = "- default " + std::string(m_printer_technology == ptSLA ? "FFF" : "SLA") + " -"; - std::string def_preset_name = "Default Printer"; - m_config = &m_presets->find_preset(def_preset_name)->config; - m_printer_technology == ptSLA ? build_fff() : build_sla(); - if (m_printer_technology == ptSLA) - m_extruders_count_old = 0;// revert this value - - // ... and than for selected printer technology - load_initial_data(); - m_printer_technology == ptSLA ? build_sla() : build_fff(); -} - -void TabPrinter::build_fff() -{ - if (!m_pages.empty()) - m_pages.resize(0); - // to avoid redundant memory allocation / deallocation during extruders count changing - m_pages.reserve(30); - - auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); - m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); - m_extruder_variant_list = m_config->option("printer_extruder_variant")->values; - // BBS - //wxGetApp().obj_list()->update_objects_list_filament_column(m_initial_extruders_count); - - const Preset* parent_preset = m_printer_technology == ptSLA ? nullptr // just for first build, if SLA printer preset is selected - : m_presets->get_selected_preset_parent(); - m_sys_extruders_count = parent_preset == nullptr ? 0 : - static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); - - auto page = add_options_page(L("Basic information"), "printer"); - auto optgroup = page->new_optgroup(L("Printable space")/*, L"param_printable_space"*/); - - create_line_with_widget(optgroup.get(), "printable_area", "", [this](wxWindow* parent) { - return create_bed_shape_widget(parent); - }); - - Option option = optgroup->get_option("bed_exclude_area"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); - optgroup->append_single_option_line("printable_height"); - optgroup->append_single_option_line("best_object_pos"); - // todo: for multi_extruder test - // BBS + auto page = add_options_page(L("Basic information"), "printer"); + auto optgroup = page->new_optgroup(L("Printable space") /*, L"param_printable_space"*/); + + create_line_with_widget(optgroup.get(), "printable_area", "", [this](wxWindow *parent) + { return create_bed_shape_widget(parent); }); + + Option option = optgroup->get_option("bed_exclude_area"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + optgroup->append_single_option_line("printable_height"); + optgroup->append_single_option_line("best_object_pos"); + // todo: for multi_extruder test + // BBS #if 0 //optgroup->append_single_option_line("z_offset"); @@ -4335,76 +4705,79 @@ void TabPrinter::build_fff() //}; #endif - optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - optgroup->append_single_option_line("printer_structure"); - optgroup->append_single_option_line("gcode_flavor"); - optgroup->append_single_option_line("apply_top_surface_compensation"); + optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); + optgroup->append_single_option_line("printer_structure"); + optgroup->append_single_option_line("gcode_flavor"); + optgroup->append_single_option_line("apply_top_surface_compensation"); - option =optgroup->get_option("thumbnail_size"); - option.opt.full_width=true; - optgroup->append_single_option_line(option); + option = optgroup->get_option("thumbnail_size"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); - optgroup->append_single_option_line("scan_first_layer"); - //option = optgroup->get_option("wrapping_exclude_area"); - //option.opt.full_width = true; - //optgroup->append_single_option_line(option); - optgroup->append_single_option_line("use_relative_e_distances"); - optgroup->append_single_option_line("use_firmware_retraction"); - optgroup->append_single_option_line("bed_temperature_formula"); - // optgroup->append_single_option_line("spaghetti_detector"); - optgroup->append_single_option_line("machine_load_filament_time"); - optgroup->append_single_option_line("machine_unload_filament_time"); - optgroup->append_single_option_line("machine_switch_extruder_time"); - optgroup->append_single_option_line("machine_hotend_change_time"); - - optgroup = page->new_optgroup(L("Extruder Clearance")); - optgroup->append_single_option_line("extruder_clearance_max_radius"); - optgroup->append_single_option_line("extruder_clearance_dist_to_rod"); - optgroup->append_single_option_line("extruder_clearance_height_to_rod"); - optgroup->append_single_option_line("extruder_clearance_height_to_lid"); - - optgroup = page->new_optgroup(L("Accessory") /*, L"param_accessory"*/); - optgroup->append_single_option_line("nozzle_type"); - optgroup->append_single_option_line("auxiliary_fan"); - optgroup->append_single_option_line("fan_direction"); - optgroup->append_single_option_line("support_chamber_temp_control"); - optgroup->append_single_option_line("support_air_filtration"); - optgroup->append_single_option_line("cooling_filter_enabled"); - optgroup->append_single_option_line("auto_disable_filter_on_overheat"); - - const int gcode_field_height = 15; // 150 - const int notes_field_height = 25; // 250 - - page = add_options_page(L("Machine gcode"), "cog"); - optgroup = page->new_optgroup(L("Machine start G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("machine_start_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line("scan_first_layer"); + // option = optgroup->get_option("wrapping_exclude_area"); + // option.opt.full_width = true; + // optgroup->append_single_option_line(option); + optgroup->append_single_option_line("use_relative_e_distances"); + optgroup->append_single_option_line("use_firmware_retraction"); + optgroup->append_single_option_line("bed_temperature_formula"); + // optgroup->append_single_option_line("spaghetti_detector"); + optgroup->append_single_option_line("machine_load_filament_time"); + optgroup->append_single_option_line("machine_unload_filament_time"); + optgroup->append_single_option_line("machine_switch_extruder_time"); + optgroup->append_single_option_line("machine_hotend_change_time"); + + optgroup = page->new_optgroup(L("Extruder Clearance")); + optgroup->append_single_option_line("extruder_clearance_max_radius"); + optgroup->append_single_option_line("extruder_clearance_dist_to_rod"); + optgroup->append_single_option_line("extruder_clearance_height_to_rod"); + optgroup->append_single_option_line("extruder_clearance_height_to_lid"); + + optgroup = page->new_optgroup(L("Accessory") /*, L"param_accessory"*/); + optgroup->append_single_option_line("nozzle_type"); + optgroup->append_single_option_line("auxiliary_fan"); + optgroup->append_single_option_line("fan_direction"); + optgroup->append_single_option_line("support_chamber_temp_control"); + optgroup->append_single_option_line("support_air_filtration"); + optgroup->append_single_option_line("cooling_filter_enabled"); + optgroup->append_single_option_line("auto_disable_filter_on_overheat"); + + const int gcode_field_height = 15; // 150 + const int notes_field_height = 25; // 250 + + page = add_options_page(L("Machine gcode"), "cog"); + optgroup = page->new_optgroup(L("Machine start G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("machine_start_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Machine end G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("machine_end_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Machine end G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("machine_end_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Printing by object G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("printing_by_object_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height; // 150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Printing by object G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("printing_by_object_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); #if 0 optgroup = page->new_optgroup(L("Before layer change G-code"), 0); optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { @@ -4417,63 +4790,69 @@ void TabPrinter::build_fff() optgroup->append_single_option_line(option); #endif - optgroup = page->new_optgroup(L("Layer change G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("layer_change_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Layer change G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("layer_change_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Time lapse G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("time_lapse_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Time lapse G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("time_lapse_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Clumping Detection G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("wrapping_detection_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height; // 150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Clumping Detection G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("wrapping_detection_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Change filament G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("change_filament_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Change filament G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("change_filament_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Pause G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("machine_pause_gcode"); - option.opt.is_code = true; - option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Pause G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("machine_pause_gcode"); + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Template Custom G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); - }; - option = optgroup->get_option("template_custom_gcode"); - option.opt.is_code = true; - option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Template Custom G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { + validate_custom_gcode_cb(this, optgroup, opt_key, value); + }; + option = optgroup->get_option("template_custom_gcode"); + option.opt.is_code = true; + option.opt.height = gcode_field_height; // 150; + optgroup->append_single_option_line(option); #if 0 //page = add_options_page(L("Dependencies"), "advanced"); @@ -4482,223 +4861,231 @@ void TabPrinter::build_fff() // build_preset_description_line(optgroup.get()); #endif - page = add_options_page(L("Notes"),"note"); - optgroup = page->new_optgroup(L("Notes"),"note"); - optgroup->label_width = 0; - option = optgroup->get_option("printer_notes"); - option.opt.full_width = true; - option.opt.height = notes_field_height; - optgroup->append_single_option_line(option); - optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { validate_custom_note_cb(this, optgroup, opt_key, value); }; - - build_unregular_pages(true); -} - -void TabPrinter::build_sla() -{ - //if (!m_pages.empty()) - // m_pages.resize(0); - //auto page = add_options_page(L("General"), "printer"); - //auto optgroup = page->new_optgroup(L("Size and coordinates")); - - //create_line_with_widget(optgroup.get(), "printable_area", "custom-svg-and-png-bed-textures_124612", [this](wxWindow* parent) { - // return create_bed_shape_widget(parent); - //}); - //optgroup->append_single_option_line("printable_height"); - - //optgroup = page->new_optgroup(L("Display")); - //optgroup->append_single_option_line("display_width"); - //optgroup->append_single_option_line("display_height"); - - //auto option = optgroup->get_option("display_pixels_x"); - //Line line = { option.opt.full_label, "" }; - //line.append_option(option); - //line.append_option(optgroup->get_option("display_pixels_y")); - //optgroup->append_line(line); - //optgroup->append_single_option_line("display_orientation"); - - //// FIXME: This should be on one line in the UI - //optgroup->append_single_option_line("display_mirror_x"); - //optgroup->append_single_option_line("display_mirror_y"); - - //optgroup = page->new_optgroup(L("Tilt")); - //line = { L("Tilt time"), "" }; - //line.append_option(optgroup->get_option("fast_tilt_time")); - //line.append_option(optgroup->get_option("slow_tilt_time")); - //optgroup->append_line(line); - //optgroup->append_single_option_line("area_fill"); - - //optgroup = page->new_optgroup(L("Corrections")); - //line = Line{ m_config->def()->get("relative_correction")->full_label, "" }; - //for (auto& axis : { "X", "Y", "Z" }) { - // auto opt = optgroup->get_option(std::string("relative_correction_") + char(std::tolower(axis[0]))); - // opt.opt.label = axis; - // line.append_option(opt); - //} - //optgroup->append_line(line); - //optgroup->append_single_option_line("absolute_correction"); - //optgroup->append_single_option_line("elefant_foot_compensation"); - //optgroup->append_single_option_line("elefant_foot_min_width"); - //optgroup->append_single_option_line("gamma_correction"); - // - //optgroup = page->new_optgroup(L("Exposure")); - //optgroup->append_single_option_line("min_exposure_time"); - //optgroup->append_single_option_line("max_exposure_time"); - //optgroup->append_single_option_line("min_initial_exposure_time"); - //optgroup->append_single_option_line("max_initial_exposure_time"); - - //page = add_options_page(L("Dependencies"), "wrench.png"); - //optgroup = page->new_optgroup(L("Profile dependencies")); + page = add_options_page(L("Notes"), "note"); + optgroup = page->new_optgroup(L("Notes"), "note"); + optgroup->label_width = 0; + option = optgroup->get_option("printer_notes"); + option.opt.full_width = true; + option.opt.height = notes_field_height; + optgroup->append_single_option_line(option); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) + { validate_custom_note_cb(this, optgroup, opt_key, value); }; - //build_preset_description_line(optgroup.get()); -} + build_unregular_pages(true); + } -void TabPrinter::extruders_count_changed(size_t extruders_count) -{ - bool is_count_changed = false; - if (m_extruders_count != extruders_count) { - m_extruders_count = extruders_count; - m_preset_bundle->on_extruders_count_changed(extruders_count); - is_count_changed = true; + void TabPrinter::build_sla() + { + // if (!m_pages.empty()) + // m_pages.resize(0); + // auto page = add_options_page(L("General"), "printer"); + // auto optgroup = page->new_optgroup(L("Size and coordinates")); + + // create_line_with_widget(optgroup.get(), "printable_area", "custom-svg-and-png-bed-textures_124612", [this](wxWindow* parent) { + // return create_bed_shape_widget(parent); + // }); + // optgroup->append_single_option_line("printable_height"); + + // optgroup = page->new_optgroup(L("Display")); + // optgroup->append_single_option_line("display_width"); + // optgroup->append_single_option_line("display_height"); + + // auto option = optgroup->get_option("display_pixels_x"); + // Line line = { option.opt.full_label, "" }; + // line.append_option(option); + // line.append_option(optgroup->get_option("display_pixels_y")); + // optgroup->append_line(line); + // optgroup->append_single_option_line("display_orientation"); + + //// FIXME: This should be on one line in the UI + // optgroup->append_single_option_line("display_mirror_x"); + // optgroup->append_single_option_line("display_mirror_y"); + + // optgroup = page->new_optgroup(L("Tilt")); + // line = { L("Tilt time"), "" }; + // line.append_option(optgroup->get_option("fast_tilt_time")); + // line.append_option(optgroup->get_option("slow_tilt_time")); + // optgroup->append_line(line); + // optgroup->append_single_option_line("area_fill"); + + // optgroup = page->new_optgroup(L("Corrections")); + // line = Line{ m_config->def()->get("relative_correction")->full_label, "" }; + // for (auto& axis : { "X", "Y", "Z" }) { + // auto opt = optgroup->get_option(std::string("relative_correction_") + char(std::tolower(axis[0]))); + // opt.opt.label = axis; + // line.append_option(opt); + // } + // optgroup->append_line(line); + // optgroup->append_single_option_line("absolute_correction"); + // optgroup->append_single_option_line("elefant_foot_compensation"); + // optgroup->append_single_option_line("elefant_foot_min_width"); + // optgroup->append_single_option_line("gamma_correction"); + // + // optgroup = page->new_optgroup(L("Exposure")); + // optgroup->append_single_option_line("min_exposure_time"); + // optgroup->append_single_option_line("max_exposure_time"); + // optgroup->append_single_option_line("min_initial_exposure_time"); + // optgroup->append_single_option_line("max_initial_exposure_time"); + + // page = add_options_page(L("Dependencies"), "wrench.png"); + // optgroup = page->new_optgroup(L("Profile dependencies")); + + // build_preset_description_line(optgroup.get()); + } + + void TabPrinter::extruders_count_changed(size_t extruders_count) + { + bool is_count_changed = false; + if (m_extruders_count != extruders_count) + { + m_extruders_count = extruders_count; + m_preset_bundle->on_extruders_count_changed(extruders_count); + is_count_changed = true; - wxGetApp().plater()->get_partplate_list().on_extruder_count_changed((int)m_extruders_count); - } - // BBS + wxGetApp().plater()->get_partplate_list().on_extruder_count_changed((int)m_extruders_count); + } + // BBS #if 1 - else if (m_extruders_count == 1 && - m_preset_bundle->project_config.option("flush_volumes_matrix")->values.size()>1) - m_preset_bundle->update_multi_material_filament_presets(); + else if (m_extruders_count == 1 && + m_preset_bundle->project_config.option("flush_volumes_matrix")->values.size() > 1) + m_preset_bundle->update_multi_material_filament_presets(); #endif - /* This function should be call in any case because of correct updating/rebuilding - * of unregular pages of a Printer Settings - */ - build_unregular_pages(); - - if (is_count_changed) { - on_value_change("extruders_count", extruders_count); - // BBS - //wxGetApp().obj_list()->update_objects_list_filament_column(extruders_count); - } -} - -void TabPrinter::append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key) -{ - auto option = optgroup->get_option(opt_key, 0); - auto line = Line{ option.opt.full_label, "" }; - line.append_option(option); - if (m_use_silent_mode - || m_printer_technology == ptSLA // just for first build, if SLA printer preset is selected - ) - line.append_option(optgroup->get_option(opt_key, 1)); - optgroup->append_line(line); -} - -PageShp TabPrinter::build_kinematics_page() -{ - auto page = add_options_page(L("Motion ability"), "cog", true); - - if (m_use_silent_mode) { - // Legend for OptionsGroups - auto optgroup = page->new_optgroup(""); - auto line = Line{ "", "" }; - - ConfigOptionDef def; - def.type = coString; - def.width = Field::def_width(); - def.gui_type = ConfigOptionDef::GUIType::legend; - def.mode = comDevelop; - //def.tooltip = L("Values in this column are for Normal mode"); - def.set_default_value(new ConfigOptionString{ _(L("Normal")).ToUTF8().data() }); - - auto option = Option(def, "full_power_legend"); - line.append_option(option); - - //def.tooltip = L("Values in this column are for Stealth mode"); - def.set_default_value(new ConfigOptionString{ _(L("Silent")).ToUTF8().data() }); - option = Option(def, "silent_legend"); - line.append_option(option); - - optgroup->append_line(line); - } + /* This function should be call in any case because of correct updating/rebuilding + * of unregular pages of a Printer Settings + */ + build_unregular_pages(); - const std::vector speed_axes{ - "machine_max_speed_x", - "machine_max_speed_y", - "machine_max_speed_z", - "machine_max_speed_e" - }; - auto optgroup = page->new_optgroup(L("Speed limitation"), "param_speed"); - for (const std::string &speed_axis : speed_axes) { - append_option_line(optgroup, speed_axis); + if (is_count_changed) + { + on_value_change("extruders_count", extruders_count); + // BBS + // wxGetApp().obj_list()->update_objects_list_filament_column(extruders_count); + } } - const std::vector axes{ "x", "y", "z", "e" }; - optgroup = page->new_optgroup(L("Acceleration limitation"), "param_acceleration"); - for (const std::string &axis : axes) { - append_option_line(optgroup, "machine_max_acceleration_" + axis); + void TabPrinter::append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key) + { + auto option = optgroup->get_option(opt_key, 0); + auto line = Line{option.opt.full_label, ""}; + line.append_option(option); + if (m_use_silent_mode || m_printer_technology == ptSLA // just for first build, if SLA printer preset is selected + ) + line.append_option(optgroup->get_option(opt_key, 1)); + optgroup->append_line(line); } - append_option_line(optgroup, "machine_max_acceleration_extruding"); - append_option_line(optgroup, "machine_max_acceleration_retracting"); - auto gcf = m_config->option>("gcode_flavor")->value; - if (gcf == gcfMarlinFirmware || gcf == gcfMarlinLegacy || gcf == gcfKlipper) { - append_option_line(optgroup, "machine_max_acceleration_travel"); - } + PageShp TabPrinter::build_kinematics_page() + { + auto page = add_options_page(L("Motion ability"), "cog", true); - optgroup = page->new_optgroup(L("Jerk limitation")); - for (const std::string &axis : axes) { - append_option_line(optgroup, "machine_max_jerk_" + axis); - } + if (m_use_silent_mode) + { + // Legend for OptionsGroups + auto optgroup = page->new_optgroup(""); + auto line = Line{"", ""}; + + ConfigOptionDef def; + def.type = coString; + def.width = Field::def_width(); + def.gui_type = ConfigOptionDef::GUIType::legend; + def.mode = comDevelop; + // def.tooltip = L("Values in this column are for Normal mode"); + def.set_default_value(new ConfigOptionString{_(L("Normal")).ToUTF8().data()}); + + auto option = Option(def, "full_power_legend"); + line.append_option(option); + + // def.tooltip = L("Values in this column are for Stealth mode"); + def.set_default_value(new ConfigOptionString{_(L("Silent")).ToUTF8().data()}); + option = Option(def, "silent_legend"); + line.append_option(option); + + optgroup->append_line(line); + } - //optgroup = page->new_optgroup(L("Minimum feedrates")); - // append_option_line(optgroup, "machine_min_extruding_rate"); - // append_option_line(optgroup, "machine_min_travel_rate"); + const std::vector speed_axes{ + "machine_max_speed_x", + "machine_max_speed_y", + "machine_max_speed_z", + "machine_max_speed_e"}; + auto optgroup = page->new_optgroup(L("Speed limitation"), "param_speed"); + for (const std::string &speed_axis : speed_axes) + { + append_option_line(optgroup, speed_axis); + } - return page; -} + const std::vector axes{"x", "y", "z", "e"}; + optgroup = page->new_optgroup(L("Acceleration limitation"), "param_acceleration"); + for (const std::string &axis : axes) + { + append_option_line(optgroup, "machine_max_acceleration_" + axis); + } + append_option_line(optgroup, "machine_max_acceleration_extruding"); + append_option_line(optgroup, "machine_max_acceleration_retracting"); -/* Previous name build_extruder_pages(). - * - * This function was renamed because of now it implements not just an extruder pages building, - * but "Motion ability" and "Single extruder MM setup" too - * (These pages can changes according to the another values of a current preset) - * */ -void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) -{ - size_t n_before_extruders = 2; // Count of pages before Extruder pages - auto flavor = m_config->option>("gcode_flavor")->value; - bool is_marlin_flavor = (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfKlipper); - - /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages - * and be cause of application crash, when try to change Preset in moment, - * when one of unregular pages is selected. - * */ - Freeze(); - - // Add/delete Kinematics page according to is_marlin_flavor - size_t existed_page = 0; - for (size_t i = n_before_extruders; i < m_pages.size(); ++i) // first make sure it's not there already - if (m_pages[i]->title().find(L("Motion ability")) != std::string::npos) { - if (!is_marlin_flavor || m_rebuild_kinematics_page) - m_pages.erase(m_pages.begin() + i); - else - existed_page = i; - break; + auto gcf = m_config->option>("gcode_flavor")->value; + if (gcf == gcfMarlinFirmware || gcf == gcfMarlinLegacy || gcf == gcfKlipper) + { + append_option_line(optgroup, "machine_max_acceleration_travel"); + } + + optgroup = page->new_optgroup(L("Jerk limitation")); + for (const std::string &axis : axes) + { + append_option_line(optgroup, "machine_max_jerk_" + axis); + } + + // optgroup = page->new_optgroup(L("Minimum feedrates")); + // append_option_line(optgroup, "machine_min_extruding_rate"); + // append_option_line(optgroup, "machine_min_travel_rate"); + + return page; } - m_rebuild_kinematics_page = false; - if (existed_page < n_before_extruders && (is_marlin_flavor || from_initial_build)) { - auto page = build_kinematics_page(); - if (from_initial_build && !is_marlin_flavor) - page->clear(); - else - m_pages.insert(m_pages.begin() + n_before_extruders, page); - } + /* Previous name build_extruder_pages(). + * + * This function was renamed because of now it implements not just an extruder pages building, + * but "Motion ability" and "Single extruder MM setup" too + * (These pages can changes according to the another values of a current preset) + * */ + void TabPrinter::build_unregular_pages(bool from_initial_build /* = false*/) + { + size_t n_before_extruders = 2; // Count of pages before Extruder pages + auto flavor = m_config->option>("gcode_flavor")->value; + bool is_marlin_flavor = (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfKlipper); + + /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages + * and be cause of application crash, when try to change Preset in moment, + * when one of unregular pages is selected. + * */ + Freeze(); + + // Add/delete Kinematics page according to is_marlin_flavor + size_t existed_page = 0; + for (size_t i = n_before_extruders; i < m_pages.size(); ++i) // first make sure it's not there already + if (m_pages[i]->title().find(L("Motion ability")) != std::string::npos) + { + if (!is_marlin_flavor || m_rebuild_kinematics_page) + m_pages.erase(m_pages.begin() + i); + else + existed_page = i; + break; + } + m_rebuild_kinematics_page = false; + + if (existed_page < n_before_extruders && (is_marlin_flavor || from_initial_build)) + { + auto page = build_kinematics_page(); + if (from_initial_build && !is_marlin_flavor) + page->clear(); + else + m_pages.insert(m_pages.begin() + n_before_extruders, page); + } - if (is_marlin_flavor) - n_before_extruders++; - size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page + if (is_marlin_flavor) + n_before_extruders++; + size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page #if 0 if (m_extruders_count_old == m_extruders_count || @@ -4730,93 +5117,94 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) } #endif - // BBS. Just create one extruder page because BBL machine has only on physical extruder. - // Build missed extruder pages - for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) { - const wxString& page_name = (m_extruders_count > 1) ? wxString::Format("Extruder %d", int(extruder_idx + 1)) : wxString::Format("Extruder"); - - //# build page - //const wxString& page_name = wxString::Format("Extruder %d", int(extruder_idx + 1)); - auto page = add_options_page(page_name, "empty", true); - m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page); - - auto optgroup = page->new_optgroup(L("Basic information"), L"param_type", -1, true); - optgroup->append_single_option_line("extruder_type", "", extruder_idx); - optgroup->append_single_option_line("nozzle_diameter", "", extruder_idx); - //optgroup->append_single_option_line("default_nozzle_volume_type", "", extruder_idx); - - optgroup->append_single_option_line("nozzle_volume", "", extruder_idx); - optgroup->append_single_option_line("extruder_printable_height", "", extruder_idx); - Option option = optgroup->get_option("extruder_printable_area", extruder_idx); - option.opt.full_width = true; - optgroup->append_single_option_line(option); - - optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value) + // BBS. Just create one extruder page because BBL machine has only on physical extruder. + // Build missed extruder pages + for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) { - //if (m_config->opt_bool("single_extruder_multi_material") && m_extruders_count > 1 && opt_key.find("nozzle_diameter") != std::string::npos) - //{ - // SuppressBackgroundProcessingUpdate sbpu; - // const double new_nd = boost::any_cast(value); - // std::vector nozzle_diameters = static_cast(m_config->option("nozzle_diameter"))->values; - - // // if value was changed - // if (fabs(nozzle_diameters[extruder_idx == 0 ? 1 : 0] - new_nd) > EPSILON) - // { - // const wxString msg_text = _(L("This is a single extruder multimaterial printer, diameters of all extruders " - // "will be set to the new value. Do you want to proceed?")); - // //wxMessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); - // MessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); - - // DynamicPrintConfig new_conf = *m_config; - // if (dialog.ShowModal() == wxID_YES) { - // for (size_t i = 0; i < nozzle_diameters.size(); i++) { - // if (i==extruder_idx) - // continue; - // nozzle_diameters[i] = new_nd; - // } - // } - // else - // nozzle_diameters[extruder_idx] = nozzle_diameters[extruder_idx == 0 ? 1 : 0]; - - // new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); - // load_config(new_conf); - // } - //} - - update_dirty(); - on_value_change(opt_key, value); - update(); - }; + const wxString &page_name = (m_extruders_count > 1) ? wxString::Format("Extruder %d", int(extruder_idx + 1)) : wxString::Format("Extruder"); + + // # build page + // const wxString& page_name = wxString::Format("Extruder %d", int(extruder_idx + 1)); + auto page = add_options_page(page_name, "empty", true); + m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page); + + auto optgroup = page->new_optgroup(L("Basic information"), L"param_type", -1, true); + optgroup->append_single_option_line("extruder_type", "", extruder_idx); + optgroup->append_single_option_line("nozzle_diameter", "", extruder_idx); + // optgroup->append_single_option_line("default_nozzle_volume_type", "", extruder_idx); + + optgroup->append_single_option_line("nozzle_volume", "", extruder_idx); + optgroup->append_single_option_line("extruder_printable_height", "", extruder_idx); + Option option = optgroup->get_option("extruder_printable_area", extruder_idx); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + optgroup->m_on_change = [this, extruder_idx](const t_config_option_key &opt_key, boost::any value) + { + // if (m_config->opt_bool("single_extruder_multi_material") && m_extruders_count > 1 && opt_key.find("nozzle_diameter") != std::string::npos) + //{ + // SuppressBackgroundProcessingUpdate sbpu; + // const double new_nd = boost::any_cast(value); + // std::vector nozzle_diameters = static_cast(m_config->option("nozzle_diameter"))->values; + + // // if value was changed + // if (fabs(nozzle_diameters[extruder_idx == 0 ? 1 : 0] - new_nd) > EPSILON) + // { + // const wxString msg_text = _(L("This is a single extruder multimaterial printer, diameters of all extruders " + // "will be set to the new value. Do you want to proceed?")); + // //wxMessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + // MessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + + // DynamicPrintConfig new_conf = *m_config; + // if (dialog.ShowModal() == wxID_YES) { + // for (size_t i = 0; i < nozzle_diameters.size(); i++) { + // if (i==extruder_idx) + // continue; + // nozzle_diameters[i] = new_nd; + // } + // } + // else + // nozzle_diameters[extruder_idx] = nozzle_diameters[extruder_idx == 0 ? 1 : 0]; + + // new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); + // load_config(new_conf); + // } + //} + + update_dirty(); + on_value_change(opt_key, value); + update(); + }; - optgroup = page->new_optgroup(L("Layer height limits"), L"param_layer_height", -1, true); - optgroup->append_single_option_line("min_layer_height", "adaptive-layer-height", extruder_idx); - optgroup->append_single_option_line("max_layer_height", "adaptive-layer-height", extruder_idx); - - optgroup = page->new_optgroup(L("Position"), L"param_retraction", -1, true); - optgroup->append_single_option_line("extruder_offset"); - - //BBS: don't show retract related config menu in machine page - optgroup = page->new_optgroup(L("Retraction"), L"param_retraction"); - optgroup->append_single_option_line("retraction_length", "parameter/retraction#length", extruder_idx); - optgroup->append_single_option_line("z_hop", "parameter/retraction#z-hop-when-retracting", extruder_idx); - optgroup->append_single_option_line("retract_lift_above", "parameter/retraction#z-hop-upper-and-lower-boundary", extruder_idx); - optgroup->append_single_option_line("retract_lift_below", "parameter/retraction#z-hop-upper-and-lower-boundary", extruder_idx); - optgroup->append_single_option_line("z_hop_types", "parameter/retraction#z-hop-type", extruder_idx); - optgroup->append_single_option_line("retraction_speed", "parameter/retraction#retraction-speed", extruder_idx); - optgroup->append_single_option_line("deretraction_speed", "parameter/retraction#deretraction-speed", extruder_idx); - optgroup->append_single_option_line("retract_restart_extra", "", extruder_idx); - optgroup->append_single_option_line("retraction_minimum_travel", "parameter/retraction#travel-distance-threshold", extruder_idx); - optgroup->append_single_option_line("retract_when_changing_layer", "parameter/retraction#retract-on-layer-change", extruder_idx); - optgroup->append_single_option_line("wipe", "parameter/retraction#wipe-while-retracting", extruder_idx); - optgroup->append_single_option_line("wipe_distance", "parameter/retraction#wipe-distance", extruder_idx); - optgroup->append_single_option_line("retract_before_wipe", "parameter/retraction#retract-amount-before-wipe", extruder_idx); - - optgroup = page->new_optgroup(L("Retraction when switching material"), L"param_retraction", -1, true); - optgroup->append_single_option_line("retract_length_toolchange", "", extruder_idx); - optgroup->append_single_option_line("retract_restart_extra_toolchange", "", extruder_idx); - // do not display this params now - optgroup->append_single_option_line("long_retractions_when_cut", "", extruder_idx); - optgroup->append_single_option_line("retraction_distances_when_cut", "", extruder_idx); + optgroup = page->new_optgroup(L("Layer height limits"), L"param_layer_height", -1, true); + optgroup->append_single_option_line("min_layer_height", "adaptive-layer-height", extruder_idx); + optgroup->append_single_option_line("max_layer_height", "adaptive-layer-height", extruder_idx); + + optgroup = page->new_optgroup(L("Position"), L"param_retraction", -1, true); + optgroup->append_single_option_line("extruder_offset"); + + // BBS: don't show retract related config menu in machine page + optgroup = page->new_optgroup(L("Retraction"), L"param_retraction"); + optgroup->append_single_option_line("retraction_length", "parameter/retraction#length", extruder_idx); + optgroup->append_single_option_line("z_hop", "parameter/retraction#z-hop-when-retracting", extruder_idx); + optgroup->append_single_option_line("retract_lift_above", "parameter/retraction#z-hop-upper-and-lower-boundary", extruder_idx); + optgroup->append_single_option_line("retract_lift_below", "parameter/retraction#z-hop-upper-and-lower-boundary", extruder_idx); + optgroup->append_single_option_line("z_hop_types", "parameter/retraction#z-hop-type", extruder_idx); + optgroup->append_single_option_line("retraction_speed", "parameter/retraction#retraction-speed", extruder_idx); + optgroup->append_single_option_line("deretraction_speed", "parameter/retraction#deretraction-speed", extruder_idx); + optgroup->append_single_option_line("retract_restart_extra", "", extruder_idx); + optgroup->append_single_option_line("retraction_minimum_travel", "parameter/retraction#travel-distance-threshold", extruder_idx); + optgroup->append_single_option_line("retract_when_changing_layer", "parameter/retraction#retract-on-layer-change", extruder_idx); + optgroup->append_single_option_line("wipe", "parameter/retraction#wipe-while-retracting", extruder_idx); + optgroup->append_single_option_line("wipe_distance", "parameter/retraction#wipe-distance", extruder_idx); + optgroup->append_single_option_line("retract_before_wipe", "parameter/retraction#retract-amount-before-wipe", extruder_idx); + + optgroup = page->new_optgroup(L("Retraction when switching material"), L"param_retraction", -1, true); + optgroup->append_single_option_line("retract_length_toolchange", "", extruder_idx); + optgroup->append_single_option_line("retract_restart_extra_toolchange", "", extruder_idx); + // do not display this params now + optgroup->append_single_option_line("long_retractions_when_cut", "", extruder_idx); + optgroup->append_single_option_line("retraction_distances_when_cut", "", extruder_idx); #if 0 //optgroup = page->new_optgroup(L("Preview"), -1, true); @@ -4849,410 +5237,440 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) //line.append_widget(reset_to_filament_color); //optgroup->append_line(line); #endif - } - // BBS. No extra extruder page for single physical extruder machine - // # remove extra pages + } + // BBS. No extra extruder page for single physical extruder machine + // # remove extra pages #if 1 - auto &first_extruder_title = const_cast(m_pages[n_before_extruders]->title()); - if (m_extruders_count < m_extruders_count_old) { - m_pages.erase( m_pages.begin() + n_before_extruders + m_extruders_count, - m_pages.begin() + n_before_extruders + m_extruders_count_old); - if (m_extruders_count == 1) - first_extruder_title = wxString::Format("Extruder"); - } else if (m_extruders_count_old == 1) { - first_extruder_title = wxString::Format("Extruder %d", 1); - } - auto & searcher = wxGetApp().sidebar().get_searcher(); - for (auto &group : m_pages[n_before_extruders]->m_optgroups) { - group->set_config_category_and_type(first_extruder_title, m_type); - for (auto &opt : group->opt_map()) - searcher.add_key(opt.first + "#0", m_type, group->title, first_extruder_title); - } + auto &first_extruder_title = const_cast(m_pages[n_before_extruders]->title()); + if (m_extruders_count < m_extruders_count_old) + { + m_pages.erase(m_pages.begin() + n_before_extruders + m_extruders_count, + m_pages.begin() + n_before_extruders + m_extruders_count_old); + if (m_extruders_count == 1) + first_extruder_title = wxString::Format("Extruder"); + } + else if (m_extruders_count_old == 1) + { + first_extruder_title = wxString::Format("Extruder %d", 1); + } + auto &searcher = wxGetApp().sidebar().get_searcher(); + for (auto &group : m_pages[n_before_extruders]->m_optgroups) + { + group->set_config_category_and_type(first_extruder_title, m_type); + for (auto &opt : group->opt_map()) + searcher.add_key(opt.first + "#0", m_type, group->title, first_extruder_title); + } #endif - Thaw(); + Thaw(); - m_extruders_count_old = m_extruders_count; + m_extruders_count_old = m_extruders_count; - if (from_initial_build && m_printer_technology == ptSLA) - return; // next part of code is no needed to execute at this moment + if (from_initial_build && m_printer_technology == ptSLA) + return; // next part of code is no needed to execute at this moment - rebuild_page_tree(); + rebuild_page_tree(); - // Reload preset pages with current configuration values - reload_config(); + // Reload preset pages with current configuration values + reload_config(); - // apply searcher with current configuration - apply_searcher(); -} + // apply searcher with current configuration + apply_searcher(); + } -// this gets executed after preset is loaded and before GUI fields are updated -void TabPrinter::on_preset_loaded() -{ - // BBS - //update nozzle_volume_type - const Preset& current_printer = m_preset_bundle->printers.get_selected_preset(); - const Preset* base_printer = m_preset_bundle->printers.get_preset_base(current_printer); - if (!base_printer) - base_printer = ¤t_printer; - std::string base_name = base_printer->name; - std::string base_model = base_printer->config.option("printer_model")->value; - // update the extruders count field - auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); - size_t extruders_count = nozzle_diameter->values.size(); - // update the GUI field according to the number of nozzle diameters supplied - if (m_extruders_count != extruders_count) - extruders_count_changed(extruders_count); - - m_extruder_variant_list = m_config->option("printer_extruder_variant")->values; - - if (base_name != m_base_preset_name) { - bool use_default_nozzle_volume_type = true; - m_base_preset_name = base_name; - std::string prev_nozzle_volume_type = wxGetApp().app_config->get_nozzle_volume_types_from_config(base_name); - if (!prev_nozzle_volume_type.empty()) { - ConfigOptionEnumsGeneric* nozzle_volume_type_option = m_preset_bundle->project_config.option("nozzle_volume_type"); - if (nozzle_volume_type_option->deserialize(prev_nozzle_volume_type)) { - for (size_t idx = 0; idx < nozzle_volume_type_option->size(); ++idx) { - NozzleVolumeType volume_type=NozzleVolumeType(nozzle_volume_type_option->values[idx]); - m_preset_bundle->extruder_nozzle_stat.on_volume_type_switch(idx, volume_type); - if (wxGetApp().plater()) { - wxGetApp().plater()->update_filament_volume_map(idx, volume_type); + // this gets executed after preset is loaded and before GUI fields are updated + void TabPrinter::on_preset_loaded() + { + // BBS + // update nozzle_volume_type + const Preset ¤t_printer = m_preset_bundle->printers.get_selected_preset(); + const Preset *base_printer = m_preset_bundle->printers.get_preset_base(current_printer); + if (!base_printer) + base_printer = ¤t_printer; + std::string base_name = base_printer->name; + std::string base_model = base_printer->config.option("printer_model")->value; + // update the extruders count field + auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); + size_t extruders_count = nozzle_diameter->values.size(); + // update the GUI field according to the number of nozzle diameters supplied + if (m_extruders_count != extruders_count) + extruders_count_changed(extruders_count); + + m_extruder_variant_list = m_config->option("printer_extruder_variant")->values; + + if (base_name != m_base_preset_name) + { + bool use_default_nozzle_volume_type = true; + m_base_preset_name = base_name; + std::string prev_nozzle_volume_type = wxGetApp().app_config->get_nozzle_volume_types_from_config(base_name); + if (!prev_nozzle_volume_type.empty()) + { + ConfigOptionEnumsGeneric *nozzle_volume_type_option = m_preset_bundle->project_config.option("nozzle_volume_type"); + if (nozzle_volume_type_option->deserialize(prev_nozzle_volume_type)) + { + for (size_t idx = 0; idx < nozzle_volume_type_option->size(); ++idx) + { + NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volume_type_option->values[idx]); + m_preset_bundle->extruder_nozzle_stat.on_volume_type_switch(idx, volume_type); + if (wxGetApp().plater()) + { + wxGetApp().plater()->update_filament_volume_map(idx, volume_type); + } + updateNozzleCountDisplay(m_preset_bundle, idx, volume_type); + }; + use_default_nozzle_volume_type = false; } - updateNozzleCountDisplay(m_preset_bundle, idx, volume_type); - }; - use_default_nozzle_volume_type = false; - } - } - if (use_default_nozzle_volume_type) { - auto default_nozzle_volume_type = current_printer.config.option("default_nozzle_volume_type")->values; - for (size_t eid = 0; eid < default_nozzle_volume_type.size(); ++eid) - set_extruder_volume_type(eid, (NozzleVolumeType)(default_nozzle_volume_type[eid])); - } + } + if (use_default_nozzle_volume_type) + { + auto default_nozzle_volume_type = current_printer.config.option("default_nozzle_volume_type")->values; + for (size_t eid = 0; eid < default_nozzle_volume_type.size(); ++eid) + set_extruder_volume_type(eid, (NozzleVolumeType)(default_nozzle_volume_type[eid])); + } - // only reset nozzle count when printer model is changed - if (base_model != m_base_preset_model) { - auto extruder_max_nozzle_count = current_printer.config.option("extruder_max_nozzle_count"); - auto nozzle_volume_type = m_preset_bundle->project_config.option("nozzle_volume_type"); - bool has_multiple_nozzle = std::any_of(extruder_max_nozzle_count->values.begin(), extruder_max_nozzle_count->values.end(), [](int i) { return i > 1; }); - if (extruder_max_nozzle_count && nozzle_volume_type) { - wxGetApp().plater()->sidebar().enable_nozzle_count_edit(has_multiple_nozzle); - m_preset_bundle->extruder_nozzle_stat.on_printer_model_change(m_preset_bundle); - for (size_t idx = 0; idx < extruders_count; ++idx) { - updateNozzleCountDisplay(m_preset_bundle, idx, NozzleVolumeType(nozzle_volume_type->values[idx])); + // only reset nozzle count when printer model is changed + if (base_model != m_base_preset_model) + { + auto extruder_max_nozzle_count = current_printer.config.option("extruder_max_nozzle_count"); + auto nozzle_volume_type = m_preset_bundle->project_config.option("nozzle_volume_type"); + bool has_multiple_nozzle = std::any_of(extruder_max_nozzle_count->values.begin(), extruder_max_nozzle_count->values.end(), [](int i) + { return i > 1; }); + if (extruder_max_nozzle_count && nozzle_volume_type) + { + wxGetApp().plater()->sidebar().enable_nozzle_count_edit(has_multiple_nozzle); + m_preset_bundle->extruder_nozzle_stat.on_printer_model_change(m_preset_bundle); + for (size_t idx = 0; idx < extruders_count; ++idx) + { + updateNozzleCountDisplay(m_preset_bundle, idx, NozzleVolumeType(nozzle_volume_type->values[idx])); + } + } + m_preset_bundle->extruder_nozzle_stat.set_nozzle_data_flag(ExtruderNozzleStat::ndfNone); + + // only trigger prime volume type for printers with multi nozzle + auto prime_volume_type = m_preset_bundle->project_config.option>("prime_volume_mode"); + if (!has_multiple_nozzle) + prime_volume_type->value = PrimeVolumeMode::pvmDefault; + wxGetApp().plater()->sidebar().enable_purge_mode_btn(has_multiple_nozzle); } + m_base_preset_model = base_model; } - m_preset_bundle->extruder_nozzle_stat.set_nozzle_data_flag(ExtruderNozzleStat::ndfNone); - - // only trigger prime volume type for printers with multi nozzle - auto prime_volume_type = m_preset_bundle->project_config.option>("prime_volume_mode"); - if(!has_multiple_nozzle) - prime_volume_type->value = PrimeVolumeMode::pvmDefault; - wxGetApp().plater()->sidebar().enable_purge_mode_btn(has_multiple_nozzle); } - m_base_preset_model = base_model; - } -} -void TabPrinter::update_pages() -{ - // update m_pages ONLY if printer technology is changed - const PrinterTechnology new_printer_technology = m_presets->get_edited_preset().printer_technology(); - if (new_printer_technology == m_printer_technology) - return; + void TabPrinter::update_pages() + { + // update m_pages ONLY if printer technology is changed + const PrinterTechnology new_printer_technology = m_presets->get_edited_preset().printer_technology(); + if (new_printer_technology == m_printer_technology) + return; - //clear all active pages before switching - clear_pages(); + // clear all active pages before switching + clear_pages(); - // set m_pages to m_pages_(technology before changing) - m_printer_technology == ptFFF ? m_pages.swap(m_pages_fff) : m_pages.swap(m_pages_sla); + // set m_pages to m_pages_(technology before changing) + m_printer_technology == ptFFF ? m_pages.swap(m_pages_fff) : m_pages.swap(m_pages_sla); - // build Tab according to the technology, if it's not exist jet OR - // set m_pages_(technology after changing) to m_pages - // m_printer_technology will be set by Tab::load_current_preset() - if (new_printer_technology == ptFFF) - { - if (m_pages_fff.empty()) - { - build_fff(); - if (m_extruders_count > 1) + // build Tab according to the technology, if it's not exist jet OR + // set m_pages_(technology after changing) to m_pages + // m_printer_technology will be set by Tab::load_current_preset() + if (new_printer_technology == ptFFF) { - m_preset_bundle->update_multi_material_filament_presets(); - on_value_change("extruders_count", m_extruders_count); - } - } - else - m_pages.swap(m_pages_fff); - - wxGetApp().obj_list()->update_objects_list_filament_column(m_extruders_count); - } - else - m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla); - - rebuild_page_tree(); -} + if (m_pages_fff.empty()) + { + build_fff(); + if (m_extruders_count > 1) + { + m_preset_bundle->update_multi_material_filament_presets(); + on_value_change("extruders_count", m_extruders_count); + } + } + else + m_pages.swap(m_pages_fff); -void TabPrinter::reload_config() -{ - Tab::reload_config(); + wxGetApp().obj_list()->update_objects_list_filament_column(m_extruders_count); + } + else + m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla); - // "extruders_count" doesn't update from the update_config(), - // so update it implicitly - if (m_active_page && m_active_page->title() == "General") - m_active_page->set_value("extruders_count", int(m_extruders_count)); -} + rebuild_page_tree(); + } -void TabPrinter::activate_selected_page(std::function throw_if_canceled) -{ - Tab::activate_selected_page(throw_if_canceled); + void TabPrinter::reload_config() + { + Tab::reload_config(); - // "extruders_count" doesn't update from the update_config(), - // so update it implicitly - if (m_active_page && m_active_page->title() == "General") - m_active_page->set_value("extruders_count", int(m_extruders_count)); -} + // "extruders_count" doesn't update from the update_config(), + // so update it implicitly + if (m_active_page && m_active_page->title() == "General") + m_active_page->set_value("extruders_count", int(m_extruders_count)); + } -void TabPrinter::clear_pages() -{ - Tab::clear_pages(); - m_reset_to_filament_color = nullptr; -} + void TabPrinter::activate_selected_page(std::function throw_if_canceled) + { + Tab::activate_selected_page(throw_if_canceled); -void TabPrinter::toggle_options() -{ - if (!m_active_page || m_presets->get_edited_preset().printer_technology() == ptSLA) - return; - - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - auto extruders = m_config->option("extruder_type"); - auto get_index_for_extruder = - [this, &extruders, &nozzle_volumes](int extruder_id, int stride = 1) { - return m_config->get_index_for_extruder(extruder_id + 1, "printer_extruder_id", - ExtruderType(extruders->values[extruder_id]), get_actual_nozzle_volume_type(extruder_id), "printer_extruder_variant", stride); - }; - - auto config_mode = wxGetApp().get_mode(); - //BBS: whether the preset is Bambu Lab printer - bool is_BBL_printer = false; - if (m_preset_bundle) { - is_BBL_printer = m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(m_preset_bundle); - } + // "extruders_count" doesn't update from the update_config(), + // so update it implicitly + if (m_active_page && m_active_page->title() == "General") + m_active_page->set_value("extruders_count", int(m_extruders_count)); + } - bool have_multiple_extruders = m_extruders_count > 1; - //if (m_active_page->title() == "Custom G-code") { - // toggle_option("change_filament_gcode", have_multiple_extruders); - //} - if (m_active_page->title() == "Basic information") { - //toggle_line("printable_area", !is_configed_by_BBL);//all printer can entry and view data - toggle_option("single_extruder_multi_material", have_multiple_extruders); - //BBS: gcode_flavore of BBL printer can't be edited and changed - toggle_option("gcode_flavor", !is_BBL_printer); - toggle_option("thumbnail_size",!is_BBL_printer); - toggle_option("printer_structure", !is_BBL_printer); - toggle_option("use_relative_e_distances", !is_BBL_printer); - toggle_option("support_chamber_temp_control",!is_BBL_printer); - toggle_option("use_firmware_retraction", !is_BBL_printer); - toggle_line("support_air_filtration", !m_config->opt_bool("support_cooling_filter") && is_BBL_printer); - toggle_line("cooling_filter_enabled", m_config->opt_bool("support_cooling_filter") && is_BBL_printer); - toggle_line("auto_disable_filter_on_overheat", m_config->opt_bool("cooling_filter_enabled") && is_BBL_printer); - auto flavor = m_config->option>("gcode_flavor")->value; - bool is_marlin_flavor = flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware; - // Disable silent mode for non-marlin firmwares. - toggle_option("silent_mode", is_marlin_flavor); - //BBS: extruder clearance of BBL printer can't be edited. - for (auto el : {"extruder_clearance_max_radius", "extruder_clearance_dist_to_rod", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid"}) - toggle_option(el, !is_BBL_printer); - } + void TabPrinter::clear_pages() + { + Tab::clear_pages(); + m_reset_to_filament_color = nullptr; + } - if (m_active_page->title() == "Machine gcode") { - PresetBundle *preset_bundle = wxGetApp().preset_bundle; - std::string printer_type = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); - toggle_line("wrapping_detection_gcode", DevPrinterConfigUtil::support_wrapping_detection(printer_type)); - } + void TabPrinter::toggle_options() + { + if (!m_active_page || m_presets->get_edited_preset().printer_technology() == ptSLA) + return; - wxString extruder_number; - long val = 1; - if ( m_active_page->title().IsSameAs("Extruder") || - (m_active_page->title().StartsWith("Extruder ", &extruder_number) && extruder_number.ToLong(&val) && - val > 0 && (size_t)val <= m_extruders_count)) - { - size_t i = size_t(val - 1); - int variant_index = get_index_for_extruder(i); - bool have_retract_length = m_config->opt_float_nullable("retraction_length", variant_index) > 0; + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + auto extruders = m_config->option("extruder_type"); + auto get_index_for_extruder = + [this, &extruders, &nozzle_volumes](int extruder_id, int stride = 1) + { + return m_config->get_index_for_extruder(extruder_id + 1, "printer_extruder_id", + ExtruderType(extruders->values[extruder_id]), get_actual_nozzle_volume_type(extruder_id), "printer_extruder_variant", stride); + }; - //BBS - for (auto el : { "extruder_type" , "nozzle_diameter"}) { - toggle_option(el, !is_BBL_printer, i); - } + auto config_mode = wxGetApp().get_mode(); + // BBS: whether the preset is Bambu Lab printer + bool is_BBL_printer = false; + if (m_preset_bundle) + { + is_BBL_printer = m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(m_preset_bundle); + } - toggle_option("extruder_type", !is_BBL_printer, i); - toggle_option("nozzle_diameter", !is_BBL_printer || config_mode == ConfigOptionMode::comDevelop, i); - toggle_option("extruder_offset", !is_BBL_printer || config_mode == ConfigOptionMode::comDevelop, i); + bool have_multiple_extruders = m_extruders_count > 1; + // if (m_active_page->title() == "Custom G-code") { + // toggle_option("change_filament_gcode", have_multiple_extruders); + // } + if (m_active_page->title() == "Basic information") + { + // toggle_line("printable_area", !is_configed_by_BBL);//all printer can entry and view data + toggle_option("single_extruder_multi_material", have_multiple_extruders); + // BBS: gcode_flavore of BBL printer can't be edited and changed + toggle_option("gcode_flavor", !is_BBL_printer); + toggle_option("thumbnail_size", !is_BBL_printer); + toggle_option("printer_structure", !is_BBL_printer); + toggle_option("use_relative_e_distances", !is_BBL_printer); + toggle_option("support_chamber_temp_control", !is_BBL_printer); + toggle_option("use_firmware_retraction", !is_BBL_printer); + toggle_line("support_air_filtration", !m_config->opt_bool("support_cooling_filter") && is_BBL_printer); + toggle_line("cooling_filter_enabled", m_config->opt_bool("support_cooling_filter") && is_BBL_printer); + toggle_line("auto_disable_filter_on_overheat", m_config->opt_bool("cooling_filter_enabled") && is_BBL_printer); + auto flavor = m_config->option>("gcode_flavor")->value; + bool is_marlin_flavor = flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware; + // Disable silent mode for non-marlin firmwares. + toggle_option("silent_mode", is_marlin_flavor); + // BBS: extruder clearance of BBL printer can't be edited. + for (auto el : {"extruder_clearance_max_radius", "extruder_clearance_dist_to_rod", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid"}) + toggle_option(el, !is_BBL_printer); + } - toggle_option("extruder_printable_area", false, i); // disable - toggle_line("extruder_printable_area", m_preset_bundle->get_printer_extruder_count() == 2, i); //hide - toggle_option("extruder_printable_height", false, i); - toggle_line("extruder_printable_height", m_preset_bundle->get_printer_extruder_count() == 2, i); + if (m_active_page->title() == "Machine gcode") + { + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + std::string printer_type = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); + toggle_line("wrapping_detection_gcode", DevPrinterConfigUtil::support_wrapping_detection(printer_type)); + } - bool use_firmware_retraction = m_config->opt_bool("use_firmware_retraction"); - toggle_option("retract_length",!use_firmware_retraction, i); + wxString extruder_number; + long val = 1; + if (m_active_page->title().IsSameAs("Extruder") || + (m_active_page->title().StartsWith("Extruder ", &extruder_number) && extruder_number.ToLong(&val) && + val > 0 && (size_t)val <= m_extruders_count)) + { + size_t i = size_t(val - 1); + int variant_index = get_index_for_extruder(i); + bool have_retract_length = m_config->opt_float_nullable("retraction_length", variant_index) > 0; + + // BBS + for (auto el : {"extruder_type", "nozzle_diameter"}) + { + toggle_option(el, !is_BBL_printer, i); + } - // user can customize travel length if we have retraction length or we"re using - // firmware retraction - toggle_option("retraction_minimum_travel", have_retract_length||use_firmware_retraction, i); + toggle_option("extruder_type", !is_BBL_printer, i); + toggle_option("nozzle_diameter", !is_BBL_printer || config_mode == ConfigOptionMode::comDevelop, i); + toggle_option("extruder_offset", !is_BBL_printer || config_mode == ConfigOptionMode::comDevelop, i); + + toggle_option("extruder_printable_area", false, i); // disable + toggle_line("extruder_printable_area", m_preset_bundle->get_printer_extruder_count() == 2, i); // hide + toggle_option("extruder_printable_height", false, i); + toggle_line("extruder_printable_height", m_preset_bundle->get_printer_extruder_count() == 2, i); + + bool use_firmware_retraction = m_config->opt_bool("use_firmware_retraction"); + toggle_option("retract_length", !use_firmware_retraction, i); + + // user can customize travel length if we have retraction length or we"re using + // firmware retraction + toggle_option("retraction_minimum_travel", have_retract_length || use_firmware_retraction, i); + + // user can customize other retraction options if retraction is enabled + // BBS + bool retraction = have_retract_length || use_firmware_retraction; + std::vector vec = {"z_hop", "retract_when_changing_layer"}; + for (auto el : vec) + toggle_option(el, retraction, i); + + // some options only apply when not using firmware retraction + vec.resize(0); + vec = {"retraction_speed", "deretraction_speed", "retract_before_wipe", "retract_restart_extra", "wipe", "wipe_distance"}; + for (auto el : vec) + // BBS + toggle_option(el, retraction && !use_firmware_retraction, i); + + bool wipe = retraction && m_config->opt_bool_nullable("wipe", variant_index); + toggle_option("retract_before_wipe", wipe, i); + + if (use_firmware_retraction && wipe) + { + // wxMessageDialog dialog(parent(), + MessageDialog dialog(parent(), + _(L("The Wipe option is not available when using the Firmware Retraction mode.\n" + "\nShall I disable it in order to enable Firmware Retraction?")), + _(L("Firmware Retraction")), wxICON_WARNING | wxYES | wxNO); - // user can customize other retraction options if retraction is enabled - //BBS - bool retraction = have_retract_length || use_firmware_retraction; - std::vector vec = { "z_hop", "retract_when_changing_layer" }; - for (auto el : vec) - toggle_option(el, retraction, i); + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) + { + auto wipe = static_cast(m_config->option("wipe")->clone()); + for (size_t w = 0; w < wipe->values.size(); w++) + wipe->values[w] = false; + new_conf.set_key_value("wipe", wipe); + } + else + { + new_conf.set_key_value("use_firmware_retraction", new ConfigOptionBool(false)); + } + load_config(new_conf); + } - // some options only apply when not using firmware retraction - vec.resize(0); - vec = { "retraction_speed", "deretraction_speed", "retract_before_wipe", "retract_restart_extra", "wipe", "wipe_distance" }; - for (auto el : vec) - //BBS - toggle_option(el, retraction && !use_firmware_retraction, i); + // BBS + toggle_option("wipe_distance", wipe, i); - bool wipe = retraction && m_config->opt_bool_nullable("wipe", variant_index); - toggle_option("retract_before_wipe", wipe, i); + toggle_option("retract_length_toolchange", have_multiple_extruders, i); - if (use_firmware_retraction && wipe) { - //wxMessageDialog dialog(parent(), - MessageDialog dialog(parent(), - _(L("The Wipe option is not available when using the Firmware Retraction mode.\n" - "\nShall I disable it in order to enable Firmware Retraction?")), - _(L("Firmware Retraction")), wxICON_WARNING | wxYES | wxNO); + bool toolchange_retraction = m_config->opt_float_nullable("retract_length_toolchange", variant_index) > 0; + toggle_option("retract_restart_extra_toolchange", have_multiple_extruders && toolchange_retraction, i); - DynamicPrintConfig new_conf = *m_config; - if (dialog.ShowModal() == wxID_YES) { - auto wipe = static_cast(m_config->option("wipe")->clone()); - for (size_t w = 0; w < wipe->values.size(); w++) - wipe->values[w] = false; - new_conf.set_key_value("wipe", wipe); + toggle_option("long_retractions_when_cut", !use_firmware_retraction && m_config->opt_int("enable_long_retraction_when_cut"), i); + toggle_line("retraction_distances_when_cut", m_config->opt_bool_nullable("long_retractions_when_cut", variant_index), i); } - else { - new_conf.set_key_value("use_firmware_retraction", new ConfigOptionBool(false)); - } - load_config(new_conf); - } - // BBS - toggle_option("wipe_distance", wipe, i); - - toggle_option("retract_length_toolchange", have_multiple_extruders, i); + if (m_active_page->title() == "Motion ability") + { + assert(m_config->option>("gcode_flavor")->value == gcfMarlinLegacy || m_config->option>("gcode_flavor")->value == gcfMarlinFirmware || m_config->option>("gcode_flavor")->value == gcfKlipper); + bool silent_mode = m_config->opt_bool("silent_mode"); + int max_field = silent_mode ? 2 : 1; + // BBS: limits of BBL printer can't be edited, except jerk. + for (const std::string &opt : {"machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel", + "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", + "machine_max_speed_x", "machine_max_speed_y", "machine_max_speed_z", "machine_max_speed_e", + "machine_min_extruding_rate", "machine_min_travel_rate"}) + for (int i = 0; i < max_field; ++i) + toggle_option(opt, !is_BBL_printer, i); + } - bool toolchange_retraction = m_config->opt_float_nullable("retract_length_toolchange", variant_index) > 0; - toggle_option("retract_restart_extra_toolchange", have_multiple_extruders && toolchange_retraction, i); + toggle_line("fan_direction", m_config->opt_bool("auxiliary_fan")); + } - toggle_option("long_retractions_when_cut", !use_firmware_retraction && m_config->opt_int("enable_long_retraction_when_cut"), i); - toggle_line("retraction_distances_when_cut", m_config->opt_bool_nullable("long_retractions_when_cut", variant_index), i); - } + void TabPrinter::update() + { + m_update_cnt++; + m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla(); + m_update_cnt--; - if (m_active_page->title() == "Motion ability") { - assert(m_config->option>("gcode_flavor")->value == gcfMarlinLegacy - || m_config->option>("gcode_flavor")->value == gcfMarlinFirmware - || m_config->option>("gcode_flavor")->value == gcfKlipper); - bool silent_mode = m_config->opt_bool("silent_mode"); - int max_field = silent_mode ? 2 : 1; - //BBS: limits of BBL printer can't be edited, except jerk. - for (const std::string& opt : { "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel", - "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", - "machine_max_speed_x", "machine_max_speed_y", "machine_max_speed_z", "machine_max_speed_e", - "machine_min_extruding_rate", "machine_min_travel_rate" }) - for (int i = 0; i < max_field; ++ i) - toggle_option(opt, !is_BBL_printer, i); - } + update_description_lines(); + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); - toggle_line("fan_direction", m_config->opt_bool("auxiliary_fan")); -} + if (m_update_cnt == 0) + wxGetApp().mainframe->on_config_changed(m_config); + } -void TabPrinter::update() -{ - m_update_cnt++; - m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla(); - m_update_cnt--; + void TabPrinter::update_fff() + { + if (m_use_silent_mode != m_config->opt_bool("silent_mode")) + { + m_rebuild_kinematics_page = true; + m_use_silent_mode = m_config->opt_bool("silent_mode"); + } - update_description_lines(); - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); + toggle_options(); + } - if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); -} + void TabPrinter::update_sla() + { + ; + } -void TabPrinter::update_fff() -{ - if (m_use_silent_mode != m_config->opt_bool("silent_mode")) { - m_rebuild_kinematics_page = true; - m_use_silent_mode = m_config->opt_bool("silent_mode"); - } + void Tab::update_ui_items_related_on_parent_preset(const Preset *selected_preset_parent) + { + m_is_default_preset = selected_preset_parent != nullptr && selected_preset_parent->is_default; - toggle_options(); -} + m_bmp_non_system = selected_preset_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = selected_preset_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = selected_preset_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + } -void TabPrinter::update_sla() -{ ; } + // BBS: reactive the preset combo box + void Tab::reactive_preset_combo_box() + { + if (!m_presets_choice) + return; + // BBS: add workaround to fix the issue caused by wxwidget 3.15 upgrading + m_presets_choice->Enable(false); + m_presets_choice->Enable(true); + } -void Tab::update_ui_items_related_on_parent_preset(const Preset* selected_preset_parent) -{ - m_is_default_preset = selected_preset_parent != nullptr && selected_preset_parent->is_default; + // Initialize the UI from the current preset + void Tab::load_current_preset() + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": enter, m_type %1%") % Preset::get_type_string(m_type); + const Preset &preset = m_presets->get_edited_preset(); + std::vector prev_variant_list; + int prev_extruder_count = 0; - m_bmp_non_system = selected_preset_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_ttg_non_system = selected_preset_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = selected_preset_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; -} + update_btns_enabling(); -//BBS: reactive the preset combo box -void Tab::reactive_preset_combo_box() -{ - if (!m_presets_choice) return; - //BBS: add workaround to fix the issue caused by wxwidget 3.15 upgrading - m_presets_choice->Enable(false); - m_presets_choice->Enable(true); -} - -// Initialize the UI from the current preset -void Tab::load_current_preset() -{ - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<get_edited_preset(); - std::vector prev_variant_list; - int prev_extruder_count = 0; - - update_btns_enabling(); - - if (m_type == Slic3r::Preset::TYPE_PRINTER) { - // For the printer profile, generate the extruder pages. - if (preset.printer_technology() == ptFFF) { - prev_variant_list = static_cast(this)->m_extruder_variant_list; - prev_extruder_count = static_cast(this)->m_extruders_count; - on_preset_loaded(); - } - else - wxGetApp().obj_list()->update_objects_list_filament_column(1); - } - if (m_type == Preset::TYPE_PRINT) { - if (auto tab = wxGetApp().plate_tab) { - tab->m_config->apply(*m_config); - tab->update_extruder_variants(); - } - for (auto tab : wxGetApp().model_tabs_list) { - tab->m_config->apply(*m_config); - tab->update_extruder_variants(); - } - } - update(); + if (m_type == Slic3r::Preset::TYPE_PRINTER) + { + // For the printer profile, generate the extruder pages. + if (preset.printer_technology() == ptFFF) + { + prev_variant_list = static_cast(this)->m_extruder_variant_list; + prev_extruder_count = static_cast(this)->m_extruders_count; + on_preset_loaded(); + } + else + wxGetApp().obj_list()->update_objects_list_filament_column(1); + } + if (m_type == Preset::TYPE_PRINT) + { + if (auto tab = wxGetApp().plate_tab) + { + tab->m_config->apply(*m_config); + tab->update_extruder_variants(); + } + for (auto tab : wxGetApp().model_tabs_list) + { + tab->m_config->apply(*m_config); + tab->update_extruder_variants(); + } + } + update(); - // Reload preset pages with the new configuration values. - update_extruder_variants(-1, false); - reload_config(); + // Reload preset pages with the new configuration values. + update_extruder_variants(-1, false); + reload_config(); - update_ui_items_related_on_parent_preset(m_presets->get_selected_preset_parent()); + update_ui_items_related_on_parent_preset(m_presets->get_selected_preset_parent()); -// m_undo_to_sys_btn->Enable(!preset.is_default); + // m_undo_to_sys_btn->Enable(!preset.is_default); #if 0 // use CallAfter because some field triggers schedule on_change calls using CallAfter, @@ -5261,1270 +5679,1393 @@ void Tab::load_current_preset() // (not sure this is true anymore now that update_dirty is idempotent) wxTheApp->CallAfter([this] #endif - { - // checking out if this Tab exists till this moment - if (!wxGetApp().checked_tab(this)) - return; - update_tab_ui(); - - // update show/hide tabs - if (m_type == Slic3r::Preset::TYPE_PRINTER) { - const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); - if (printer_technology != static_cast(this)->m_printer_technology) - { - // The change of the technology requires to remove some of unrelated Tabs - // During this action, wxNoteBook::RemovePage invoke wxEVT_NOTEBOOK_PAGE_CHANGED - // and as a result a function select_active_page() is called fron Tab::OnActive() - // But we don't need it. So, to avoid activation of the page, set m_active_page to NULL - // till unusable Tabs will be deleted - Page* tmp_page = m_active_page; - m_active_page = nullptr; - for (auto tab : wxGetApp().tabs_list) { - if (tab->type() == Preset::TYPE_PRINTER) { // Printer tab is shown every time - int cur_selection = wxGetApp().tab_panel()->GetSelection(); - if (cur_selection != 0) - wxGetApp().tab_panel()->SetSelection(wxGetApp().tab_panel()->GetPageCount() - 1); - continue; - } - if (tab->supports_printer_technology(printer_technology)) + { + // checking out if this Tab exists till this moment + if (!wxGetApp().checked_tab(this)) + return; + update_tab_ui(); + + // update show/hide tabs + if (m_type == Slic3r::Preset::TYPE_PRINTER) + { + const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); + if (printer_technology != static_cast(this)->m_printer_technology) { + // The change of the technology requires to remove some of unrelated Tabs + // During this action, wxNoteBook::RemovePage invoke wxEVT_NOTEBOOK_PAGE_CHANGED + // and as a result a function select_active_page() is called fron Tab::OnActive() + // But we don't need it. So, to avoid activation of the page, set m_active_page to NULL + // till unusable Tabs will be deleted + Page *tmp_page = m_active_page; + m_active_page = nullptr; + for (auto tab : wxGetApp().tabs_list) + { + if (tab->type() == Preset::TYPE_PRINTER) + { // Printer tab is shown every time + int cur_selection = wxGetApp().tab_panel()->GetSelection(); + if (cur_selection != 0) + wxGetApp().tab_panel()->SetSelection(wxGetApp().tab_panel()->GetPageCount() - 1); + continue; + } + if (tab->supports_printer_technology(printer_technology)) + { #ifdef _MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) { - std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" : - tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "" : "cog"; - tab->Hide(); // #ys_WORKAROUND : Hide tab before inserting to avoid unwanted rendering of the tab - dynamic_cast(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name); + if (!wxGetApp().tabs_as_menu()) + { + std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" : tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "" + : "cog"; + tab->Hide(); // #ys_WORKAROUND : Hide tab before inserting to avoid unwanted rendering of the tab + dynamic_cast(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name); + } + else +#endif + wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), ""); +#ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563) + int page_id = wxGetApp().tab_panel()->FindPage(tab); + wxGetApp().tab_panel()->GetPage(page_id)->Show(true); +#endif // __linux__ + } + else + { + int page_id = wxGetApp().tab_panel()->FindPage(tab); + wxGetApp().tab_panel()->GetPage(page_id)->Show(false); + wxGetApp().tab_panel()->RemovePage(page_id); + } } - else + static_cast(this)->m_printer_technology = printer_technology; + m_active_page = tmp_page; +#ifdef _MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(wxGetApp().tab_panel())->SetPageImage(wxGetApp().tab_panel()->FindPage(this), printer_technology == ptFFF ? "printer" : "sla_printer"); +#endif + } + // update the object config due to extruder count change + DynamicPrintConfig &new_print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + std::vector new_variant_list = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("printer_extruder_variant")->values; + int new_extruder_count = wxGetApp().preset_bundle->get_printer_extruder_count(); + if (prev_extruder_count != new_extruder_count || prev_variant_list.size() != new_variant_list.size()) + { + // process the object params here + Model &model = wxGetApp().plater()->model(); + size_t num_objects = model.objects.size(); + for (int i = 0; i < num_objects; ++i) + { + ModelObject *object = model.objects[i]; + DynamicPrintConfig object_config = object->config.get(); + if (!object_config.empty()) + { + object_config.update_values_from_multi_to_multi_2(prev_variant_list, new_variant_list, new_print_config, print_options_with_variant); + object->config.assign_config(std::move(object_config)); + } + for (ModelVolume *v : object->volumes) + { + if (v->is_model_part() || v->is_modifier()) + { + DynamicPrintConfig volume_config = v->config.get(); + if (!volume_config.empty()) + { + volume_config.update_values_from_multi_to_multi_2(prev_variant_list, new_variant_list, new_print_config, print_options_with_variant); + v->config.assign_config(std::move(volume_config)); + } + } + } + + for (auto &layer_config_it : object->layer_config_ranges) + { + ModelConfig &layer_model_config = layer_config_it.second; + DynamicPrintConfig layer_config = layer_model_config.get(); + if (!layer_config.empty()) + { + layer_config.update_values_from_multi_to_multi_2(prev_variant_list, new_variant_list, new_print_config, print_options_with_variant); + layer_model_config.assign_config(std::move(layer_config)); + } + } + } + } + + on_presets_changed(); + if (printer_technology == ptFFF) + { + static_cast(this)->m_initial_extruders_count = static_cast(m_presets->get_selected_preset().config.option("nozzle_diameter"))->values.size(); // static_cast(this)->m_extruders_count; + const Preset *parent_preset = m_presets->get_selected_preset_parent(); + static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + } + } + else + { + on_presets_changed(); + if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT) + update_frequently_changed_parameters(); + } + m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; + init_options_list(); + if (m_type == Preset::TYPE_PRINT) + { + for (auto tab : wxGetApp().model_tabs_list) + { + tab->init_options_list(); + } + } + update_visibility(); + update_changed_ui(); + } +#if 0 + ); #endif - wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), ""); - #ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563) - int page_id = wxGetApp().tab_panel()->FindPage(tab); - wxGetApp().tab_panel()->GetPage(page_id)->Show(true); - #endif // __linux__ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": exit"); + } + + // Regerenerate content of the page tree. + void Tab::rebuild_page_tree() + { + // get label of the currently selected item + auto sel_item = m_tabctrl->GetSelection(); + // BBS: fix new layout, record last select + if (sel_item < 0) + sel_item = m_last_select_item; + const auto selected = sel_item >= 0 ? m_tabctrl->GetItemText(sel_item) : ""; + + int item = -1; + + // Delete/Append events invoke wxEVT_TAB_SEL_CHANGED event. + // To avoid redundant clear/activate functions call + // suppress activate page before page_tree rebuilding + m_disable_tree_sel_changed_event = true; + m_tabctrl->DeleteAllItems(); + + for (auto p : m_pages) + { + if (!p->get_show()) + continue; + auto itemId = m_tabctrl->AppendItem(translate_category(p->title(), m_type), p->iconID()); + m_tabctrl->SetItemTextColour(itemId, p->get_item_colour() == m_modified_label_clr ? p->get_item_colour() : StateColor(std::make_pair(0x6B6B6C, (int)StateColor::NotChecked), std::make_pair(p->get_item_colour(), (int)StateColor::Normal))); + if (translate_category(p->title(), m_type) == selected) + item = itemId; + } + // BBS: on mac, root is selected, this fix it + m_tabctrl->Unselect(); + // BBS: not select on hide tab + if (item == -1 && m_parent->is_active_and_shown_tab(this)) + { + // this is triggered on first load, so we don't disable the sel change event + item = m_tabctrl->GetFirstVisibleItem(); + } + // BBS: fix new layout, record last select + if (sel_item == m_last_select_item) + m_last_select_item = item; + else + m_last_select_item = NULL; + + // allow activate page before selection of a page_tree item + m_disable_tree_sel_changed_event = false; + // BBS: GUI refactor + if (item >= 0) + { + bool ret = update_current_page_in_background(item); + // if m_active_page is changed in update_current_page_in_background + // will just update the selected item of the treectrl + if (m_parent->is_active_and_shown_tab(this)) // FIX: modify state not update + m_tabctrl->SelectItem(item); + } + } + + void Tab::update_btns_enabling() + { + // we can delete any preset from the physical printer + // and any user preset + const Preset &preset = m_presets->get_edited_preset(); + m_btn_delete_preset->Show((m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) || (!preset.is_default && !preset.is_system)); + + // if (m_btn_edit_ph_printer) + // m_btn_edit_ph_printer->SetToolTip( m_preset_bundle->physical_printers.has_selection() ? + // _L("Edit physical printer") : _L("Add physical printer")); + } + + void Tab::update_preset_choice() + { + if (m_presets_choice) + m_presets_choice->update(); + update_btns_enabling(); + } + + // Called by the UI combo box when the user switches profiles, and also to delete the current profile. + // Select a preset by a name.If !defined(name), then the default preset is selected. + // If the current profile is modified, user is asked to save the changes. + bool Tab::select_preset( + std::string preset_name, bool delete_current /*=false*/, const std::string &last_selected_ph_printer_name /* =""*/, bool force_select, bool force_no_transfer) + { + auto app_config = wxGetApp().app_config; + bool auto_transfer = app_config->get("auto_transfer_when_switch_preset") == "true"; + ForceOption option = auto_transfer ? ForceOption::fopTransfer : ForceOption::fopNone; + BOOST_LOG_TRIVIAL(info) << boost::format("select preset, name %1%, delete_current %2%") % preset_name % delete_current; + if (preset_name.empty()) + { + if (delete_current) + { + // Find an alternate preset to be selected after the current preset is deleted. + const std::deque &presets = m_presets->get_presets(); + size_t idx_current = m_presets->get_idx_selected(); + // Find the next visible preset. + preset_name = presets[idx_current].inherits(); + if (preset_name.empty()) + { + size_t idx_new = idx_current + 1; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && !presets[idx_new].is_visible; ++idx_new) + ; + if (idx_new == presets.size()) + for (idx_new = idx_current - 1; idx_new > 0 && !presets[idx_new].is_visible; --idx_new) + ; + preset_name = presets[idx_new].name; + BOOST_LOG_TRIVIAL(info) << boost::format("cause by delete current ,choose the next visible, idx %1%, name %2%") % idx_new % preset_name; } - else { - int page_id = wxGetApp().tab_panel()->FindPage(tab); - wxGetApp().tab_panel()->GetPage(page_id)->Show(false); - wxGetApp().tab_panel()->RemovePage(page_id); + else + { + BOOST_LOG_TRIVIAL(info) << boost::format("cause by delete current ,choose base, name %1%") % preset_name; + } + } + else + { + // BBS select first visible item first + const std::deque &presets = this->m_presets->get_presets(); + size_t idx_new = 0; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && !presets[idx_new].is_visible; ++idx_new) + ; + preset_name = presets[idx_new].name; + if (idx_new == presets.size()) + { + // If no name is provided, select the "-- default --" preset. + preset_name = m_presets->default_preset().name; } + BOOST_LOG_TRIVIAL(info) << boost::format("not cause by delete current ,choose the first visible, idx %1%, name %2%") % idx_new % preset_name; } - static_cast(this)->m_printer_technology = printer_technology; - m_active_page = tmp_page; -#ifdef _MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(wxGetApp().tab_panel())->SetPageImage(wxGetApp().tab_panel()->FindPage(this), printer_technology == ptFFF ? "printer" : "sla_printer"); -#endif } - //update the object config due to extruder count change - DynamicPrintConfig& new_print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; - std::vector new_variant_list = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("printer_extruder_variant")->values; - int new_extruder_count = wxGetApp().preset_bundle->get_printer_extruder_count(); - if (prev_extruder_count != new_extruder_count || prev_variant_list.size() != new_variant_list.size()) - { - //process the object params here - Model& model = wxGetApp().plater()->model(); - size_t num_objects = model.objects.size(); - for (int i = 0; i < num_objects; ++i) { - ModelObject* object = model.objects[i]; - DynamicPrintConfig object_config = object->config.get(); - if (!object_config.empty()) { - object_config.update_values_from_multi_to_multi_2(prev_variant_list, new_variant_list, new_print_config, print_options_with_variant); - object->config.assign_config(std::move(object_config)); - } - for (ModelVolume* v : object->volumes) { - if (v->is_model_part() || v->is_modifier()) { - DynamicPrintConfig volume_config = v->config.get(); - if (!volume_config.empty()) { - volume_config.update_values_from_multi_to_multi_2(prev_variant_list,new_variant_list,new_print_config, print_options_with_variant); - v->config.assign_config(std::move(volume_config)); - } - } + // BBS: add project embedded preset logic and refine is_external + assert(!delete_current || (m_presets->get_edited_preset().name != preset_name && (m_presets->get_edited_preset().is_user() || m_presets->get_edited_preset().is_project_embedded))); + // assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && m_presets->get_edited_preset().is_user())); + bool current_dirty = !delete_current && m_presets->current_is_dirty(); + bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; + bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER; + bool canceled = false; + bool no_transfer = false; + bool technology_changed = false; + m_dependent_tabs.clear(); + if ((m_presets->type() == Preset::TYPE_FILAMENT) && !preset_name.empty()) + { + Preset *to_be_selected = m_presets->find_preset(preset_name, false, true); + if (to_be_selected) + { + std::string current_type, to_select_type; + ConfigOptionStrings *cur_opt = dynamic_cast(m_presets->get_edited_preset().config.option("filament_type")); + ConfigOptionStrings *to_select_opt = dynamic_cast(to_be_selected->config.option("filament_type")); + if (cur_opt && (cur_opt->values.size() > 0)) + { + current_type = cur_opt->values[0]; } - - for (auto &layer_config_it : object->layer_config_ranges) { - ModelConfig& layer_model_config = layer_config_it.second; - DynamicPrintConfig layer_config = layer_model_config.get(); - if (!layer_config.empty()) { - layer_config.update_values_from_multi_to_multi_2(prev_variant_list,new_variant_list,new_print_config, print_options_with_variant); - layer_model_config.assign_config(std::move(layer_config)); - } + if (to_select_opt && (to_select_opt->values.size() > 0)) + { + to_select_type = to_select_opt->values[0]; } + if (current_type != to_select_type) + no_transfer = true; } } + else if (printer_tab) + no_transfer = true; - on_presets_changed(); - if (printer_technology == ptFFF) { - static_cast(this)->m_initial_extruders_count = static_cast(m_presets->get_selected_preset().config.option("nozzle_diameter"))->values.size(); //static_cast(this)->m_extruders_count; - const Preset* parent_preset = m_presets->get_selected_preset_parent(); - static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : - static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + if (force_no_transfer) + { + no_transfer = true; } - } - else { - on_presets_changed(); - if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT) - update_frequently_changed_parameters(); - } - m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; - init_options_list(); - if (m_type == Preset::TYPE_PRINT) { - for (auto tab : wxGetApp().model_tabs_list) { - tab->init_options_list(); + ForceOption option_for_dirty_preset = ForceOption::fopNone; + if (current_dirty && print_tab) + { + option_for_dirty_preset = option; } - } - update_visibility(); - update_changed_ui(); - } -#if 0 - ); -#endif - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<GetSelection(); - // BBS: fix new layout, record last select - if (sel_item < 0) - sel_item = m_last_select_item; - const auto selected = sel_item >= 0 ? m_tabctrl->GetItemText(sel_item) : ""; - - int item = -1; - - // Delete/Append events invoke wxEVT_TAB_SEL_CHANGED event. - // To avoid redundant clear/activate functions call - // suppress activate page before page_tree rebuilding - m_disable_tree_sel_changed_event = true; - m_tabctrl->DeleteAllItems(); - - for (auto p : m_pages) - { - if (!p->get_show()) - continue; - auto itemId = m_tabctrl->AppendItem(translate_category(p->title(), m_type), p->iconID()); - m_tabctrl->SetItemTextColour(itemId, p->get_item_colour() == m_modified_label_clr ? p->get_item_colour() : StateColor( - std::make_pair(0x6B6B6C, (int) StateColor::NotChecked), - std::make_pair(p->get_item_colour(), (int) StateColor::Normal))); - if (translate_category(p->title(), m_type) == selected) - item = itemId; - } - // BBS: on mac, root is selected, this fix it - m_tabctrl->Unselect(); - // BBS: not select on hide tab - if (item == -1 && m_parent->is_active_and_shown_tab(this)) { - // this is triggered on first load, so we don't disable the sel change event - item = m_tabctrl->GetFirstVisibleItem(); - } - // BBS: fix new layout, record last select - if (sel_item == m_last_select_item) - m_last_select_item = item; - else - m_last_select_item = NULL; - - // allow activate page before selection of a page_tree item - m_disable_tree_sel_changed_event = false; - //BBS: GUI refactor - if (item >= 0) - { - bool ret = update_current_page_in_background(item); - //if m_active_page is changed in update_current_page_in_background - //will just update the selected item of the treectrl - if (m_parent->is_active_and_shown_tab(this)) // FIX: modify state not update - m_tabctrl->SelectItem(item); - } -} - -void Tab::update_btns_enabling() -{ - // we can delete any preset from the physical printer - // and any user preset - const Preset& preset = m_presets->get_edited_preset(); - m_btn_delete_preset->Show((m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) - || (!preset.is_default && !preset.is_system)); - - //if (m_btn_edit_ph_printer) - // m_btn_edit_ph_printer->SetToolTip( m_preset_bundle->physical_printers.has_selection() ? - // _L("Edit physical printer") : _L("Add physical printer")); -} - -void Tab::update_preset_choice() -{ - if (m_presets_choice) - m_presets_choice->update(); - update_btns_enabling(); -} - -// Called by the UI combo box when the user switches profiles, and also to delete the current profile. -// Select a preset by a name.If !defined(name), then the default preset is selected. -// If the current profile is modified, user is asked to save the changes. -bool Tab::select_preset( - std::string preset_name, bool delete_current /*=false*/, const std::string &last_selected_ph_printer_name /* =""*/, bool force_select, bool force_no_transfer) -{ - auto app_config = wxGetApp().app_config; - bool auto_transfer = app_config->get("auto_transfer_when_switch_preset") == "true"; - ForceOption option = auto_transfer ? ForceOption::fopTransfer : ForceOption::fopNone; - BOOST_LOG_TRIVIAL(info) << boost::format("select preset, name %1%, delete_current %2%") - %preset_name %delete_current; - if (preset_name.empty()) { - if (delete_current) { - // Find an alternate preset to be selected after the current preset is deleted. - const std::deque &presets = m_presets->get_presets(); - size_t idx_current = m_presets->get_idx_selected(); - // Find the next visible preset. - preset_name = presets[idx_current].inherits(); - if (preset_name.empty()) { - size_t idx_new = idx_current + 1; - if (idx_new < presets.size()) - for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; - if (idx_new == presets.size()) - for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); - preset_name = presets[idx_new].name; - BOOST_LOG_TRIVIAL(info) << boost::format("cause by delete current ,choose the next visible, idx %1%, name %2%") - %idx_new %preset_name; - } else { - BOOST_LOG_TRIVIAL(info) << boost::format("cause by delete current ,choose base, name %1%") % preset_name; - } - } else { - //BBS select first visible item first - const std::deque &presets = this->m_presets->get_presets(); - size_t idx_new = 0; - if (idx_new < presets.size()) - for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; - preset_name = presets[idx_new].name; - if (idx_new == presets.size()) { - // If no name is provided, select the "-- default --" preset. - preset_name = m_presets->default_preset().name; - } - BOOST_LOG_TRIVIAL(info) << boost::format("not cause by delete current ,choose the first visible, idx %1%, name %2%") - %idx_new %preset_name; - } - } - //BBS: add project embedded preset logic and refine is_external - assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && (m_presets->get_edited_preset().is_user() || m_presets->get_edited_preset().is_project_embedded))); - //assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && m_presets->get_edited_preset().is_user())); - bool current_dirty = ! delete_current && m_presets->current_is_dirty(); - bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; - bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER; - bool canceled = false; - bool no_transfer = false; - bool technology_changed = false; - m_dependent_tabs.clear(); - if ((m_presets->type() == Preset::TYPE_FILAMENT) && !preset_name.empty()) - { - Preset *to_be_selected = m_presets->find_preset(preset_name, false, true); - if (to_be_selected) { - std::string current_type, to_select_type; - ConfigOptionStrings* cur_opt = dynamic_cast (m_presets->get_edited_preset().config.option("filament_type")); - ConfigOptionStrings* to_select_opt = dynamic_cast (to_be_selected->config.option("filament_type")); - if (cur_opt && (cur_opt->values.size() > 0)) { - current_type = cur_opt->values[0]; - } - if (to_select_opt && (to_select_opt->values.size() > 0)) { - to_select_type = to_select_opt->values[0]; - } - if (current_type != to_select_type) - no_transfer = true; - } - } - else if (printer_tab) - no_transfer = true; - - if (force_no_transfer) { - no_transfer = true; - } - ForceOption option_for_dirty_preset = ForceOption::fopNone; - if (current_dirty && print_tab) { - option_for_dirty_preset = option; - } - if (current_dirty && ! may_discard_current_dirty_preset(nullptr, preset_name, no_transfer, option_for_dirty_preset) && !force_select) { - canceled = true; - BOOST_LOG_TRIVIAL(info) << boost::format("current dirty and cancelled"); - } else if (print_tab) { - // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material - // are compatible with the new print. - // If it is not compatible and the current filament or SLA material are dirty, let user decide - // whether to discard the changes or keep the current print selection. - PresetWithVendorProfile printer_profile = m_preset_bundle->printers.get_edited_preset_with_vendor_profile(); - PrinterTechnology printer_technology = printer_profile.preset.printer_technology(); - PresetCollection &dependent = (printer_technology == ptFFF) ? m_preset_bundle->filaments : m_preset_bundle->sla_materials; - bool old_preset_dirty = dependent.current_is_dirty(); - bool new_preset_compatible = is_compatible_with_print(dependent.get_edited_preset_with_vendor_profile(), - m_presets->get_preset_with_vendor_profile(*m_presets->find_preset(preset_name, true)), printer_profile); - if (! canceled) - canceled = old_preset_dirty && ! new_preset_compatible && ! may_discard_current_dirty_preset(&dependent, preset_name, false, option) && !force_select; - if (! canceled) { - // The preset will be switched to a different, compatible preset, or the '-- default --'. - m_dependent_tabs.emplace_back((printer_technology == ptFFF) ? Preset::Type::TYPE_FILAMENT : Preset::Type::TYPE_SLA_MATERIAL); - if (old_preset_dirty && ! new_preset_compatible) - dependent.discard_current_changes(); - } - BOOST_LOG_TRIVIAL(info) << boost::format("select process, new_preset_compatible %1%, old_preset_dirty %2%, cancelled %3%") - %new_preset_compatible %old_preset_dirty % canceled; - } else if (printer_tab) { - // Before switching the printer to a new one, verify, whether the currently active print and filament - // are compatible with the new printer. - // If they are not compatible and the current print or filament are dirty, let user decide - // whether to discard the changes or keep the current printer selection. - // - // With the introduction of the SLA printer types, we need to support switching between - // the FFF and SLA printers. - const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true); - const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset); - PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); - PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); - if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_omitL("New printer preset selected"))) - canceled = true; - else { - struct PresetUpdate { - Preset::Type tab_type; - PresetCollection *presets; - PrinterTechnology technology; - bool old_preset_dirty; - bool new_preset_compatible; - }; - std::vector updates = { - { Preset::Type::TYPE_PRINT, &m_preset_bundle->prints, ptFFF }, - //{ Preset::Type::TYPE_SLA_PRINT, &m_preset_bundle->sla_prints, ptSLA }, - { Preset::Type::TYPE_FILAMENT, &m_preset_bundle->filaments, ptFFF }, - //{ Preset::Type::TYPE_SLA_MATERIAL, &m_preset_bundle->sla_materials,ptSLA } - }; - Preset *to_be_selected = m_presets->find_preset(preset_name, false, true); - for (PresetUpdate &pu : updates) { - pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty(); - pu.new_preset_compatible = (new_printer_technology == pu.technology) && is_compatible_with_printer(pu.presets->get_edited_preset_with_vendor_profile(), new_printer_preset_with_vendor_profile); - if (!canceled) - canceled = pu.old_preset_dirty && !pu.new_preset_compatible && !may_discard_current_dirty_preset(pu.presets, preset_name, false, option) && !force_select; + if (current_dirty && !may_discard_current_dirty_preset(nullptr, preset_name, no_transfer, option_for_dirty_preset) && !force_select) + { + canceled = true; + BOOST_LOG_TRIVIAL(info) << boost::format("current dirty and cancelled"); } - if (!canceled) { - for (PresetUpdate &pu : updates) { + else if (print_tab) + { + // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material + // are compatible with the new print. + // If it is not compatible and the current filament or SLA material are dirty, let user decide + // whether to discard the changes or keep the current print selection. + PresetWithVendorProfile printer_profile = m_preset_bundle->printers.get_edited_preset_with_vendor_profile(); + PrinterTechnology printer_technology = printer_profile.preset.printer_technology(); + PresetCollection &dependent = (printer_technology == ptFFF) ? m_preset_bundle->filaments : m_preset_bundle->sla_materials; + bool old_preset_dirty = dependent.current_is_dirty(); + bool new_preset_compatible = is_compatible_with_print(dependent.get_edited_preset_with_vendor_profile(), + m_presets->get_preset_with_vendor_profile(*m_presets->find_preset(preset_name, true)), printer_profile); + if (!canceled) + canceled = old_preset_dirty && !new_preset_compatible && !may_discard_current_dirty_preset(&dependent, preset_name, false, option) && !force_select; + if (!canceled) + { // The preset will be switched to a different, compatible preset, or the '-- default --'. - if (pu.technology == new_printer_technology) - m_dependent_tabs.emplace_back(pu.tab_type); - if (pu.old_preset_dirty && !pu.new_preset_compatible) - pu.presets->discard_current_changes(); + m_dependent_tabs.emplace_back((printer_technology == ptFFF) ? Preset::Type::TYPE_FILAMENT : Preset::Type::TYPE_SLA_MATERIAL); + if (old_preset_dirty && !new_preset_compatible) + dependent.discard_current_changes(); } + BOOST_LOG_TRIVIAL(info) << boost::format("select process, new_preset_compatible %1%, old_preset_dirty %2%, cancelled %3%") % new_preset_compatible % old_preset_dirty % canceled; } - } - if (! canceled) - technology_changed = old_printer_technology != new_printer_technology; + else if (printer_tab) + { + // Before switching the printer to a new one, verify, whether the currently active print and filament + // are compatible with the new printer. + // If they are not compatible and the current print or filament are dirty, let user decide + // whether to discard the changes or keep the current printer selection. + // + // With the introduction of the SLA printer types, we need to support switching between + // the FFF and SLA printers. + const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true); + const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset); + PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); + PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); + if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_omitL("New printer preset selected"))) + canceled = true; + else + { + struct PresetUpdate + { + Preset::Type tab_type; + PresetCollection *presets; + PrinterTechnology technology; + bool old_preset_dirty; + bool new_preset_compatible; + }; + std::vector updates = { + {Preset::Type::TYPE_PRINT, &m_preset_bundle->prints, ptFFF}, + //{ Preset::Type::TYPE_SLA_PRINT, &m_preset_bundle->sla_prints, ptSLA }, + {Preset::Type::TYPE_FILAMENT, &m_preset_bundle->filaments, ptFFF}, + //{ Preset::Type::TYPE_SLA_MATERIAL, &m_preset_bundle->sla_materials,ptSLA } + }; + Preset *to_be_selected = m_presets->find_preset(preset_name, false, true); + for (PresetUpdate &pu : updates) + { + pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty(); + pu.new_preset_compatible = (new_printer_technology == pu.technology) && is_compatible_with_printer(pu.presets->get_edited_preset_with_vendor_profile(), new_printer_preset_with_vendor_profile); + if (!canceled) + canceled = pu.old_preset_dirty && !pu.new_preset_compatible && !may_discard_current_dirty_preset(pu.presets, preset_name, false, option) && !force_select; + } + if (!canceled) + { + for (PresetUpdate &pu : updates) + { + // The preset will be switched to a different, compatible preset, or the '-- default --'. + if (pu.technology == new_printer_technology) + m_dependent_tabs.emplace_back(pu.tab_type); + if (pu.old_preset_dirty && !pu.new_preset_compatible) + pu.presets->discard_current_changes(); + } + } + } + if (!canceled) + technology_changed = old_printer_technology != new_printer_technology; - BOOST_LOG_TRIVIAL(info) << boost::format("select machine, technology_changed %1%, canceled %2%") - %technology_changed % canceled; - } + BOOST_LOG_TRIVIAL(info) << boost::format("select machine, technology_changed %1%, canceled %2%") % technology_changed % canceled; + } - BOOST_LOG_TRIVIAL(info) << boost::format("before delete action, canceled %1%, delete_current %2%") %canceled %delete_current; - bool delete_third_printer = false; - std::deque filament_presets; - std::deque process_presets; - if (! canceled && delete_current) { - // Delete the file and select some other reasonable preset. - // It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable. - // The 'external' presets will only be removed from the preset list, their files will not be deleted. - try { - //BBS delete preset - Preset ¤t_preset = m_presets->get_selected_preset(); - - // Obtain compatible filament and process presets for printers - if (m_preset_bundle && m_presets->get_preset_base(current_preset) == ¤t_preset && printer_tab && !current_preset.is_system) { - delete_third_printer = true; - for (const Preset &preset : m_preset_bundle->filaments.get_presets()) { - if (preset.is_compatible && !preset.is_default) { - if (preset.inherits() != "") - filament_presets.push_front(preset); - else - filament_presets.push_back(preset); - if (!preset.setting_id.empty()) { m_preset_bundle->filaments.set_sync_info_and_save(preset.name, preset.setting_id, "delete", 0); } + BOOST_LOG_TRIVIAL(info) << boost::format("before delete action, canceled %1%, delete_current %2%") % canceled % delete_current; + bool delete_third_printer = false; + std::deque filament_presets; + std::deque process_presets; + if (!canceled && delete_current) + { + // Delete the file and select some other reasonable preset. + // It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable. + // The 'external' presets will only be removed from the preset list, their files will not be deleted. + try + { + // BBS delete preset + Preset ¤t_preset = m_presets->get_selected_preset(); + + // Obtain compatible filament and process presets for printers + if (m_preset_bundle && m_presets->get_preset_base(current_preset) == ¤t_preset && printer_tab && !current_preset.is_system) + { + delete_third_printer = true; + for (const Preset &preset : m_preset_bundle->filaments.get_presets()) + { + if (preset.is_compatible && !preset.is_default) + { + if (preset.inherits() != "") + filament_presets.push_front(preset); + else + filament_presets.push_back(preset); + if (!preset.setting_id.empty()) + { + m_preset_bundle->filaments.set_sync_info_and_save(preset.name, preset.setting_id, "delete", 0); + } + } + } + for (const Preset &preset : m_preset_bundle->prints.get_presets()) + { + if (preset.is_compatible && !preset.is_default) + { + if (preset.inherits() != "") + process_presets.push_front(preset); + else + process_presets.push_back(preset); + if (!preset.setting_id.empty()) + { + m_preset_bundle->filaments.set_sync_info_and_save(preset.name, preset.setting_id, "delete", 0); + } + } + } } - } - for (const Preset &preset : m_preset_bundle->prints.get_presets()) { - if (preset.is_compatible && !preset.is_default) { - if (preset.inherits() != "") - process_presets.push_front(preset); - else - process_presets.push_back(preset); - if (!preset.setting_id.empty()) { m_preset_bundle->filaments.set_sync_info_and_save(preset.name, preset.setting_id, "delete", 0); } + if (!current_preset.setting_id.empty()) + { + m_presets->set_sync_info_and_save(current_preset.name, current_preset.setting_id, "delete", 0); + wxGetApp().delete_preset_from_cloud(current_preset.setting_id); } + BOOST_LOG_TRIVIAL(info) << "delete preset = " << current_preset.name << ", setting_id = " << current_preset.setting_id; + BOOST_LOG_TRIVIAL(info) << boost::format("will delete current preset..."); + m_presets->delete_current_preset(); + } + catch (const std::exception &ex) + { + // FIXME add some error reporting! + canceled = true; + BOOST_LOG_TRIVIAL(info) << boost::format("found exception when delete: %1%") % ex.what(); } } - if (!current_preset.setting_id.empty()) { - m_presets->set_sync_info_and_save(current_preset.name, current_preset.setting_id, "delete", 0); - wxGetApp().delete_preset_from_cloud(current_preset.setting_id); - } - BOOST_LOG_TRIVIAL(info) << "delete preset = " << current_preset.name << ", setting_id = " << current_preset.setting_id; - BOOST_LOG_TRIVIAL(info) << boost::format("will delete current preset..."); - m_presets->delete_current_preset(); - } catch (const std::exception & ex) { - //FIXME add some error reporting! - canceled = true; - BOOST_LOG_TRIVIAL(info) << boost::format("found exception when delete: %1%") %ex.what(); - } - } - if (canceled) { - BOOST_LOG_TRIVIAL(info) << boost::format("canceled delete, update ui..."); - if (m_type == Preset::TYPE_PRINTER) { - if (!last_selected_ph_printer_name.empty() && - m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) { - // If preset selection was canceled and previously was selected physical printer, we should select it back - m_preset_bundle->physical_printers.select_printer(last_selected_ph_printer_name); - } - if (m_preset_bundle->physical_printers.has_selection()) { - // If preset selection was canceled and physical printer was selected - // we must disable selection marker for the physical printers - m_preset_bundle->physical_printers.unselect_printer(); + if (canceled) + { + BOOST_LOG_TRIVIAL(info) << boost::format("canceled delete, update ui..."); + if (m_type == Preset::TYPE_PRINTER) + { + if (!last_selected_ph_printer_name.empty() && + m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) + { + // If preset selection was canceled and previously was selected physical printer, we should select it back + m_preset_bundle->physical_printers.select_printer(last_selected_ph_printer_name); + } + if (m_preset_bundle->physical_printers.has_selection()) + { + // If preset selection was canceled and physical printer was selected + // we must disable selection marker for the physical printers + m_preset_bundle->physical_printers.unselect_printer(); + } + } + + update_tab_ui(); + + // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, + // if this action was initiated from the plater. + on_presets_changed(); } - } + else + { + BOOST_LOG_TRIVIAL(info) << boost::format("successfully delete, will update compatibility"); + if (current_dirty) + m_presets->discard_current_changes(); + + const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current; + assert(m_presets->get_edited_preset().name == preset_name || !is_selected); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // The following method should not discard changes of current print or filament presets on change of a printer profile, + // if they are compatible with the current printer. + auto update_compatible_type = [delete_current](bool technology_changed, bool on_page, bool show_incompatible_presets) + { + return (delete_current || technology_changed) ? PresetSelectCompatibleType::Always : on_page ? PresetSelectCompatibleType::Never + : show_incompatible_presets ? PresetSelectCompatibleType::OnlyIfWasCompatible + : PresetSelectCompatibleType::Always; + }; + if (current_dirty || delete_current || print_tab || printer_tab) + m_preset_bundle->update_compatible( + update_compatible_type(technology_changed, print_tab, (print_tab ? this : wxGetApp().get_tab(Preset::TYPE_PRINT))->m_show_incompatible_presets), + update_compatible_type(technology_changed, false, wxGetApp().get_tab(Preset::TYPE_FILAMENT)->m_show_incompatible_presets)); + // Initialize the UI from the current preset. + if (printer_tab) + static_cast(this)->update_pages(); + + if (!is_selected && printer_tab) + { + /* There is a case, when : + * after Config Wizard applying we try to select previously selected preset, but + * in a current configuration this one: + * 1. doesn't exist now, + * 2. have another printer_technology + * So, it is necessary to update list of dependent tabs + * to the corresponding printer_technology + */ + const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); + if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_PRINT) + m_dependent_tabs = {Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT}; + else if (printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT) + m_dependent_tabs = {Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL}; + } - update_tab_ui(); + // check if there is something in the cache to move to the new selected preset + apply_config_from_cache(); + + load_current_preset(); + + if (delete_third_printer) + { + wxGetApp().CallAfter([filament_presets, process_presets]() + { + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + std::string old_filament_name = preset_bundle->filaments.get_edited_preset().name; + std::string old_process_name = preset_bundle->prints.get_edited_preset().name; + + for (const Preset &preset : filament_presets) + { + if (!preset.setting_id.empty()) + { + wxGetApp().delete_preset_from_cloud(preset.setting_id); + } + BOOST_LOG_TRIVIAL(info) << "delete filament preset = " << preset.name << ", setting_id = " << preset.setting_id; + preset_bundle->filaments.delete_preset(preset.name); + } + + for (const Preset &preset : process_presets) + { + if (!preset.setting_id.empty()) + { + wxGetApp().delete_preset_from_cloud(preset.setting_id); + } + BOOST_LOG_TRIVIAL(info) << "delete print preset = " << preset.name << ", setting_id = " << preset.setting_id; + preset_bundle->prints.delete_preset(preset.name); + } + + preset_bundle->update_compatible(PresetSelectCompatibleType::Always); + preset_bundle->filaments.select_preset_by_name(old_filament_name, true); + preset_bundle->prints.select_preset_by_name(old_process_name, true); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " old filament name is:" << old_filament_name << " old process name is: " << old_process_name; + }); + } + } - // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, - // if this action was initiated from the plater. - on_presets_changed(); - } else { - BOOST_LOG_TRIVIAL(info) << boost::format("successfully delete, will update compatibility"); - if (current_dirty) - m_presets->discard_current_changes(); + if (technology_changed) + wxGetApp().mainframe->technology_changed(); + BOOST_LOG_TRIVIAL(info) << boost::format("select preset, exit"); - const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current; - assert(m_presets->get_edited_preset().name == preset_name || ! is_selected); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - // The following method should not discard changes of current print or filament presets on change of a printer profile, - // if they are compatible with the current printer. - auto update_compatible_type = [delete_current](bool technology_changed, bool on_page, bool show_incompatible_presets) { - return (delete_current || technology_changed) ? PresetSelectCompatibleType::Always : - on_page ? PresetSelectCompatibleType::Never : - show_incompatible_presets ? PresetSelectCompatibleType::OnlyIfWasCompatible : PresetSelectCompatibleType::Always; - }; - if (current_dirty || delete_current || print_tab || printer_tab) - m_preset_bundle->update_compatible( - update_compatible_type(technology_changed, print_tab, (print_tab ? this : wxGetApp().get_tab(Preset::TYPE_PRINT))->m_show_incompatible_presets), - update_compatible_type(technology_changed, false, wxGetApp().get_tab(Preset::TYPE_FILAMENT)->m_show_incompatible_presets)); - // Initialize the UI from the current preset. - if (printer_tab) - static_cast(this)->update_pages(); - - if (! is_selected && printer_tab) - { - /* There is a case, when : - * after Config Wizard applying we try to select previously selected preset, but - * in a current configuration this one: - * 1. doesn't exist now, - * 2. have another printer_technology - * So, it is necessary to update list of dependent tabs - * to the corresponding printer_technology - */ - const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); - if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_PRINT) - m_dependent_tabs = { Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT }; - else if (printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT) - m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; + return !canceled; } - // check if there is something in the cache to move to the new selected preset - apply_config_from_cache(); + // If the current preset is dirty, the user is asked whether the changes may be discarded. + // if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. + bool Tab::may_discard_current_dirty_preset(PresetCollection *presets /*= nullptr*/, const std::string &new_printer_name /*= ""*/, bool no_transfer, ForceOption force_op) + { + if (presets == nullptr) + presets = m_presets; + + UnsavedChangesDialog dlg(m_type, presets, new_printer_name, no_transfer); - load_current_preset(); + auto handle_save_action = [&dlg, this, presets]() + { + const std::vector &unselected_options = dlg.get_unselected_options(presets->type()); + const std::string &name = dlg.get_preset_name(); + // BBS: add project embedded preset relate logic + bool save_to_project = dlg.get_save_to_project_option(); + + if (m_type == presets->type()) // save changes for the current preset from this tab + { + // revert unselected options to the old values + presets->get_edited_preset().config.apply_only(presets->get_selected_preset().config, unselected_options); + // BBS: add project embedded preset relate logic + save_preset(name, false, save_to_project); + // save_preset(name); + } + else + { + // BBS: add project embedded preset relate logic + m_preset_bundle->save_changes_for_preset(name, presets->type(), unselected_options, save_to_project); + // m_preset_bundle->save_changes_for_preset(name, presets->type(), unselected_options); + + // If filament preset is saved for multi-material printer preset, + // there are cases when filament comboboxs are updated for old (non-modified) colors, + // but in full_config a filament_colors option aren't. + if (presets->type() == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + wxGetApp().plater()->force_filament_colors_update(); + } + }; - if (delete_third_printer) { - wxGetApp().CallAfter([filament_presets, process_presets]() { - PresetBundle *preset_bundle = wxGetApp().preset_bundle; - std::string old_filament_name = preset_bundle->filaments.get_edited_preset().name; - std::string old_process_name = preset_bundle->prints.get_edited_preset().name; + auto handle_transfer_action = [&dlg, this, presets, no_transfer]() + { + std::vector selected_options = dlg.get_selected_options(); - for (const Preset &preset : filament_presets) { - if (!preset.setting_id.empty()) { - wxGetApp().delete_preset_from_cloud(preset.setting_id); + if (m_type == presets->type()) // move changes for the current preset from this tab + { + if (m_type == Preset::TYPE_PRINTER) + { + auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); + if (it != selected_options.end()) + { + // erase "extruders_count" option from the list + selected_options.erase(it); + // cache the extruders count + static_cast(this)->cache_extruder_cnt(); + } } - BOOST_LOG_TRIVIAL(info) << "delete filament preset = " << preset.name << ", setting_id = " << preset.setting_id; - preset_bundle->filaments.delete_preset(preset.name); - } - for (const Preset &preset : process_presets) { - if (!preset.setting_id.empty()) { - wxGetApp().delete_preset_from_cloud(preset.setting_id); + // copy selected options to the cache from edited preset + cache_config_diff(selected_options); + } + else + wxGetApp().get_tab(presets->type())->cache_config_diff(selected_options); + + if (!no_transfer) + { + Preset::Type type = presets->type(); + Tab *tab = wxGetApp().get_tab(type); + auto &options_list = tab->m_options_list; + auto &cache_options = tab->m_cache_options; + auto &cache_config = tab->m_cache_config; + auto &edited_config = presets->get_edited_preset().config; + std::vector variant_options; + for (auto &opt : cache_options) + { + if (auto n = opt.find('#'); n != std::string::npos) + { + if (type == Preset::TYPE_FILAMENT && filament_options_with_variant.count(opt.substr(0, n)) == 0) + continue; + variant_options.push_back(opt.substr(0, n)); + opt.clear(); + } + } + if (!variant_options.empty()) + { + cache_options.erase(std::remove(cache_options.begin(), cache_options.end(), std::string{}), cache_options.end()); + cache_options.push_back(into_u8(dlg.GetTitle())); + cache_options.push_back(boost::join(variant_options, ";")); + cache_options.push_back(extruder_variant_keys[type].second); + variant_options.push_back(extruder_variant_keys[type].second); + cache_config.apply_only(edited_config, variant_options); } - BOOST_LOG_TRIVIAL(info) << "delete print preset = " << preset.name << ", setting_id = " << preset.setting_id; - preset_bundle->prints.delete_preset(preset.name); } + }; - preset_bundle->update_compatible(PresetSelectCompatibleType::Always); - preset_bundle->filaments.select_preset_by_name(old_filament_name, true); - preset_bundle->prints.select_preset_by_name(old_process_name, true); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " old filament name is:" << old_filament_name << " old process name is: " << old_process_name; + if (force_op == ForceOption::fopSave) + { + handle_save_action(); + return true; + } - }); - } + if (force_op == ForceOption::fopTransfer) + { + handle_transfer_action(); + return true; + } - } + if (dlg.ShowModal() == wxID_CANCEL) + return false; - if (technology_changed) - wxGetApp().mainframe->technology_changed(); - BOOST_LOG_TRIVIAL(info) << boost::format("select preset, exit"); + if (dlg.save_preset()) + { + handle_save_action(); + } + else if (dlg.transfer_changes()) + { + handle_transfer_action(); + } - return !canceled; -} + return true; + } -// If the current preset is dirty, the user is asked whether the changes may be discarded. -// if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. -bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/, bool no_transfer, ForceOption force_op) -{ - if (presets == nullptr) presets = m_presets; + void Tab::clear_pages() + { + // invalidated highlighter, if any exists + m_highlighter.invalidate(); + // clear pages from the controlls + for (auto p : m_pages) + p->clear(); + // BBS: clear page in Parent + // m_page_sizer->Clear(true); + m_parent->clear_page(); + + // nulling pointers + m_parent_preset_description_line = nullptr; + m_detach_preset_btn = nullptr; - UnsavedChangesDialog dlg(m_type, presets, new_printer_name, no_transfer); + m_compatible_printers.checkbox = nullptr; + m_compatible_printers.btn = nullptr; - auto handle_save_action = [&dlg, this, presets]() { - const std::vector& unselected_options = dlg.get_unselected_options(presets->type()); - const std::string& name = dlg.get_preset_name(); - //BBS: add project embedded preset relate logic - bool save_to_project = dlg.get_save_to_project_option(); + m_compatible_prints.checkbox = nullptr; + m_compatible_prints.btn = nullptr; + } - if (m_type == presets->type()) // save changes for the current preset from this tab + // BBS: GUI refactor: unselect current item + void Tab::unselect_tree_item() { - // revert unselected options to the old values - presets->get_edited_preset().config.apply_only(presets->get_selected_preset().config, unselected_options); - //BBS: add project embedded preset relate logic - save_preset(name, false, save_to_project); - //save_preset(name); + // BBS: bold selection + const auto sel_item = m_tabctrl->GetSelection(); + m_last_select_item = sel_item; + m_tabctrl->SetItemBold(sel_item, false); + m_tabctrl->Unselect(); + m_active_page = nullptr; } - else + + // BBS: open/close this tab + void Tab::set_expanded(bool value) { - //BBS: add project embedded preset relate logic - m_preset_bundle->save_changes_for_preset(name, presets->type(), unselected_options, save_to_project); - //m_preset_bundle->save_changes_for_preset(name, presets->type(), unselected_options); + if (value) + { + if (m_presets_choice) + m_main_sizer->Show(m_presets_choice); + m_main_sizer->Show(m_tabctrl); + } + else + { + m_active_page = NULL; + if (m_presets_choice) + m_main_sizer->Hide(m_presets_choice); + m_main_sizer->Hide(m_tabctrl); + } + } - // If filament preset is saved for multi-material printer preset, - // there are cases when filament comboboxs are updated for old (non-modified) colors, - // but in full_config a filament_colors option aren't. - if (presets->type() == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) - wxGetApp().plater()->force_filament_colors_update(); + // BBS: new layout + void Tab::restore_last_select_item() + { + auto item = m_last_select_item; + if (item == -1) + item = m_tabctrl->GetFirstVisibleItem(); + m_tabctrl->SelectItem(item); } - }; - auto handle_transfer_action = [&dlg, this, presets, no_transfer]() { - std::vector selected_options = dlg.get_selected_options(); + void Tab::update_description_lines() + { + if (m_active_page && m_active_page->title() == "Dependencies" && m_parent_preset_description_line) + update_preset_description_line(); + } - if (m_type == presets->type()) // move changes for the current preset from this tab + void Tab::activate_selected_page(std::function throw_if_canceled) { - if (m_type == Preset::TYPE_PRINTER) { - auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); - if (it != selected_options.end()) { - // erase "extruders_count" option from the list - selected_options.erase(it); - // cache the extruders count - static_cast(this)->cache_extruder_cnt(); - } - } + if (!m_active_page) + return; - // copy selected options to the cache from edited preset - cache_config_diff(selected_options); + m_active_page->activate(m_mode, throw_if_canceled); + update_changed_ui(); + update_description_lines(); + toggle_options(); + m_active_page->update_visibility(m_mode, true); // for taggle line } - else - wxGetApp().get_tab(presets->type())->cache_config_diff(selected_options); - if (!no_transfer) { - Preset::Type type = presets->type(); - Tab * tab = wxGetApp().get_tab(type); - auto& options_list = tab->m_options_list; - auto &cache_options = tab->m_cache_options; - auto &cache_config = tab->m_cache_config; - auto &edited_config = presets->get_edited_preset().config; - std::vector variant_options; - for (auto &opt : cache_options) { - if (auto n = opt.find('#'); n != std::string::npos) { - if (type == Preset::TYPE_FILAMENT && filament_options_with_variant.count(opt.substr(0, n)) == 0) - continue; - variant_options.push_back(opt.substr(0, n)); - opt.clear(); + // BBS: GUI refactor + bool Tab::update_current_page_in_background(int &item) + { + Page *page = nullptr; + + const auto selection = item >= 0 ? m_tabctrl->GetItemText(item) : ""; + for (auto p : m_pages) + if (translate_category(p->title(), m_type) == selection) + { + page = p.get(); + break; } - } - if (!variant_options.empty()) { - cache_options.erase(std::remove(cache_options.begin(), cache_options.end(), std::string{}), cache_options.end()); - cache_options.push_back(into_u8(dlg.GetTitle())); - cache_options.push_back(boost::join(variant_options, ";")); - cache_options.push_back(extruder_variant_keys[type].second); - variant_options.push_back(extruder_variant_keys[type].second); - cache_config.apply_only(edited_config, variant_options); - } - } - }; - if (force_op == ForceOption::fopSave) { - handle_save_action(); - return true; - } + if (page == nullptr || m_active_page == page) + return false; - if (force_op == ForceOption::fopTransfer) { - handle_transfer_action(); - return true; - } + bool active_tab = false; + if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->is_active_and_shown_tab(m_parent)) + active_tab = true; - if (dlg.ShowModal() == wxID_CANCEL) - return false; + if (!active_tab || (!m_parent->is_active_and_shown_tab((wxPanel *)this))) + { + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; + // BBS: not need active + // m_active_page = page; - if (dlg.save_preset()) { - handle_save_action(); - } - else if (dlg.transfer_changes()) { - handle_transfer_action(); - } + // invalidated highlighter, if any exists + m_highlighter.invalidate(); - return true; -} + // clear pages from the controlls + // BBS: fix after new layout, clear page in backgroud + for (auto p : m_pages) + p->clear(); + if (m_parent->is_active_and_shown_tab((wxPanel *)this)) + m_parent->clear_page(); -void Tab::clear_pages() -{ - // invalidated highlighter, if any exists - m_highlighter.invalidate(); - // clear pages from the controlls - for (auto p : m_pages) - p->clear(); - //BBS: clear page in Parent - //m_page_sizer->Clear(true); - m_parent->clear_page(); - - // nulling pointers - m_parent_preset_description_line = nullptr; - m_detach_preset_btn = nullptr; - - m_compatible_printers.checkbox = nullptr; - m_compatible_printers.btn = nullptr; - - m_compatible_prints.checkbox = nullptr; - m_compatible_prints.btn = nullptr; -} - -//BBS: GUI refactor: unselect current item -void Tab::unselect_tree_item() -{ - // BBS: bold selection - const auto sel_item = m_tabctrl->GetSelection(); - m_last_select_item = sel_item; - m_tabctrl->SetItemBold(sel_item, false); - m_tabctrl->Unselect(); - m_active_page = nullptr; -} - -// BBS: open/close this tab -void Tab::set_expanded(bool value) -{ - if (value) { - if (m_presets_choice) - m_main_sizer->Show(m_presets_choice); - m_main_sizer->Show(m_tabctrl); - } - else { - m_active_page = NULL; - if (m_presets_choice) - m_main_sizer->Hide(m_presets_choice); - m_main_sizer->Hide(m_tabctrl); - } -} + update_undo_buttons(); -// BBS: new layout -void Tab::restore_last_select_item() -{ - auto item = m_last_select_item; - if (item == -1) - item = m_tabctrl->GetFirstVisibleItem(); - m_tabctrl->SelectItem(item); -} + // BBS: this is not used, because we not SelectItem in background + // todo: update selected item of tree_ctrl + // wxTreeItemData item_data; + // m_tabctrl->SetItemData(item, &item_data); -void Tab::update_description_lines() -{ - if (m_active_page && m_active_page->title() == "Dependencies" && m_parent_preset_description_line) - update_preset_description_line(); -} + return false; + } -void Tab::activate_selected_page(std::function throw_if_canceled) -{ - if (!m_active_page) - return; - - m_active_page->activate(m_mode, throw_if_canceled); - update_changed_ui(); - update_description_lines(); - toggle_options(); - m_active_page->update_visibility(m_mode, true); // for taggle line -} - -//BBS: GUI refactor -bool Tab::update_current_page_in_background(int& item) -{ - Page* page = nullptr; + return true; + } - const auto selection = item >= 0 ? m_tabctrl->GetItemText(item) : ""; - for (auto p : m_pages) - if (translate_category(p->title(), m_type) == selection) + // BBS: GUI refactor + bool Tab::tree_sel_change_delayed(wxCommandEvent &event) { - page = p.get(); - break; - } + // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, + // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. +#ifdef __linux__ + std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); +#else + /* On Windows we use DoubleBuffering during rendering, + * so on Window is no needed to call a Freeze/Thaw functions. + * But under OSX (builds compiled with MacOSX10.14.sdk) wxStaticBitmap rendering is broken without Freeze/Thaw call. + */ +// #ifdef __WXOSX__ // Use Freeze/Thaw to avoid flickering during clear/activate new page +// wxWindowUpdateLocker noUpdates(this); +// #endif +#endif - if (page == nullptr || m_active_page == page) - return false; + // BBS: GUI refactor + Page *page = nullptr; + const auto sel_item = m_tabctrl->GetSelection(); + // BBS: bold selection + // OutputDebugStringA("tree_sel_change_delayed "); + // OutputDebugStringA(m_title.c_str()); + m_tabctrl->SetItemBold(sel_item, true); + const auto selection = sel_item >= 0 ? m_tabctrl->GetItemText(sel_item) : ""; + // OutputDebugString(selection); + // OutputDebugStringA("\n"); + for (auto p : m_pages) + if (translate_category(p->title(), m_type) == selection) + { + page = p.get(); + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; + break; + } - bool active_tab = false; - if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->is_active_and_shown_tab(m_parent)) - active_tab = true; + // BBS: GUI refactor + if (page == nullptr) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("can not find page with current selection %1%\n") % selection; + return false; + } + void *item_data = m_tabctrl->GetItemData(sel_item); + if (item_data) + { + // from update_current_page_in_background in not active tab + m_tabctrl->SetItemData(sel_item, NULL); + return false; + } - if (!active_tab || (!m_parent->is_active_and_shown_tab((wxPanel*)this))) - { - m_is_nonsys_values = page->m_is_nonsys_values; - m_is_modified_values = page->m_is_modified_values; - // BBS: not need active - // m_active_page = page; - - // invalidated highlighter, if any exists - m_highlighter.invalidate(); - - // clear pages from the controlls - // BBS: fix after new layout, clear page in backgroud - for (auto p : m_pages) - p->clear(); - if (m_parent->is_active_and_shown_tab((wxPanel*)this)) - m_parent->clear_page(); + if (!m_parent->is_active_and_shown_tab((wxPanel *)this)) + { + Tab *current_tab = dynamic_cast(m_parent->get_current_tab()); - update_undo_buttons(); + m_page_view->Freeze(); - // BBS: this is not used, because we not SelectItem in background - //todo: update selected item of tree_ctrl - // wxTreeItemData item_data; - // m_tabctrl->SetItemData(item, &item_data); + if (current_tab) + { + current_tab->clear_pages(); + current_tab->unselect_tree_item(); + } + m_active_page = page; + // BBS: not changed + // update_undo_buttons(); + this->OnActivate(); + m_parent->set_active_tab(this); + if (m_variant_sizer) + { + wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *)m_extruder_switch : m_variant_combo; + m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder ")); + if (m_extruder_sync) + m_extruder_sync->Show(variant_ctrl->IsShown()); + show_wiki(); + GetParent()->Layout(); + } - return false; - } + m_page_view->Thaw(); + return false; + } - return true; -} + // process logic in the same tab when select treeCtrlItem + if (m_active_page == page) + return false; + + m_active_page = page; + if (m_variant_sizer) + { + wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *)m_extruder_switch : m_variant_combo; + m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder")); + if (m_extruder_sync) + m_extruder_sync->Show(variant_ctrl->IsShown()); + show_wiki(); + GetParent()->Layout(); + } + + auto throw_if_canceled = std::function([this]() + { +#ifdef WIN32 + // BBS: GUI refactor + // TODO: remove this call currently, after refactor, there is Paint event in the queue + // this call will cause OnPaint immediately, which will cause crash + // wxCheckForInterrupt(m_tabctrl); + if (m_page_switch_planned) + throw UIBuildCanceled(); +#else // WIN32 + (void)this; // silence warning +#endif + }); + + try + { + m_page_view->Freeze(); + // clear pages from the controls + clear_pages(); + throw_if_canceled(); + + // BBS: GUI refactor + if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->is_active_and_shown_tab(m_parent)) + activate_selected_page(throw_if_canceled); -//BBS: GUI refactor -bool Tab::tree_sel_change_delayed(wxCommandEvent& event) -{ - // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, - // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. #ifdef __linux__ - std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); -#else - /* On Windows we use DoubleBuffering during rendering, - * so on Window is no needed to call a Freeze/Thaw functions. - * But under OSX (builds compiled with MacOSX10.14.sdk) wxStaticBitmap rendering is broken without Freeze/Thaw call. - */ -//#ifdef __WXOSX__ // Use Freeze/Thaw to avoid flickering during clear/activate new page -// wxWindowUpdateLocker noUpdates(this); -//#endif + no_updates.reset(nullptr); #endif - //BBS: GUI refactor - Page* page = nullptr; - const auto sel_item = m_tabctrl->GetSelection(); - // BBS: bold selection - //OutputDebugStringA("tree_sel_change_delayed "); - //OutputDebugStringA(m_title.c_str()); - m_tabctrl->SetItemBold(sel_item, true); - const auto selection = sel_item >= 0 ? m_tabctrl->GetItemText(sel_item) : ""; - //OutputDebugString(selection); - //OutputDebugStringA("\n"); - for (auto p : m_pages) - if (translate_category(p->title(), m_type) == selection) - { - page = p.get(); - m_is_nonsys_values = page->m_is_nonsys_values; - m_is_modified_values = page->m_is_modified_values; - break; - } - - //BBS: GUI refactor - if (page == nullptr) - { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("can not find page with current selection %1%\n") % selection; - return false; - } - void* item_data = m_tabctrl->GetItemData(sel_item); - if (item_data) - { - //from update_current_page_in_background in not active tab - m_tabctrl->SetItemData(sel_item, NULL); - return false; - } + // BBS: not changed + // update_undo_buttons(); + throw_if_canceled(); - if (!m_parent->is_active_and_shown_tab((wxPanel*)this)) - { - Tab* current_tab = dynamic_cast(m_parent->get_current_tab()); + // BBS: GUI refactor + // m_hsizer->Layout(); + m_parent->Layout(); + throw_if_canceled(); + // Refresh(); - m_page_view->Freeze(); + m_page_view->Thaw(); + } + catch (const UIBuildCanceled &) + { + if (m_active_page) + m_active_page->clear(); + m_page_view->Thaw(); + return true; + } - if (current_tab) - { - current_tab->clear_pages(); - current_tab->unselect_tree_item(); - } - m_active_page = page; - // BBS: not changed - // update_undo_buttons(); - this->OnActivate(); - m_parent->set_active_tab(this); - if (m_variant_sizer) { - wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo; - m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder ")); - if (m_extruder_sync) m_extruder_sync->Show(variant_ctrl->IsShown()); - show_wiki(); - GetParent()->Layout(); + return false; } - m_page_view->Thaw(); - return false; - } + void Tab::OnKeyDown(wxKeyEvent &event) + { + if (event.GetKeyCode() == WXK_TAB) + m_tabctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); + else + event.Skip(); + } - //process logic in the same tab when select treeCtrlItem - if (m_active_page == page) - return false; - - m_active_page = page; - if (m_variant_sizer) { - wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo; - m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder")); - if (m_extruder_sync) m_extruder_sync->Show(variant_ctrl->IsShown()); - show_wiki(); - GetParent()->Layout(); - } + void Tab::compare_preset() + { + wxGetApp().mainframe->diff_dialog.show(m_type); + } - auto throw_if_canceled = std::function([this](){ -#ifdef WIN32 - //BBS: GUI refactor - //TODO: remove this call currently, after refactor, there is Paint event in the queue - //this call will cause OnPaint immediately, which will cause crash - //wxCheckForInterrupt(m_tabctrl); - if (m_page_switch_planned) - throw UIBuildCanceled(); -#else // WIN32 - (void)this; // silence warning -#endif - }); - - try { - m_page_view->Freeze(); - // clear pages from the controls - clear_pages(); - throw_if_canceled(); - - //BBS: GUI refactor - if (wxGetApp().mainframe!=nullptr && wxGetApp().mainframe->is_active_and_shown_tab(m_parent)) - activate_selected_page(throw_if_canceled); - - #ifdef __linux__ - no_updates.reset(nullptr); - #endif - - // BBS: not changed - // update_undo_buttons(); - throw_if_canceled(); - - //BBS: GUI refactor - //m_hsizer->Layout(); - m_parent->Layout(); - throw_if_canceled(); - // Refresh(); - - m_page_view->Thaw(); - } catch (const UIBuildCanceled&) { - if (m_active_page) - m_active_page->clear(); - m_page_view->Thaw(); - return true; - } + // Save the current preset into file. + // This removes the "dirty" flag of the preset, possibly creates a new preset under a new name, + // and activates the new preset. + // Wizard calls save_preset with a name "My Settings", otherwise no name is provided and this method + // opens a Slic3r::GUI::SavePresetDialog dialog. + // BBS: add project embedded preset relate logic + void Tab::save_preset(std::string name /*= ""*/, bool detach, bool save_to_project, bool from_input, std::string input_name) + { + // since buttons(and choices too) don't get focus on Mac, we set focus manually + // to the treectrl so that the EVT_* events are fired for the input field having + // focus currently.is there anything better than this ? + //! m_tabctrl->OnSetFocus(); + if (from_input) + { + SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : ""); + dlg.Show(false); + dlg.input_name_from_other(input_name); + wxCommandEvent evt(wxEVT_TEXT, GetId()); + dlg.GetEventHandler()->ProcessEvent(evt); + dlg.confirm_from_other(); + name = input_name; + } - return false; -} + if (name.empty()) + { + SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : ""); + if (!m_just_edit) + { + if (dlg.ShowModal() != wxID_OK) + return; + } + name = dlg.get_name(); + // BBS: add project embedded preset relate logic + save_to_project = dlg.get_save_to_project_selection(m_type); + } -void Tab::OnKeyDown(wxKeyEvent& event) -{ - if (event.GetKeyCode() == WXK_TAB) - m_tabctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); - else - event.Skip(); -} + // BBS record current preset name + std::string curr_preset_name = m_presets->get_edited_preset().name; + auto curr_preset = m_presets->get_edited_preset(); + std::map extra_map; + { + bool is_configed_by_BBL = PresetUtils::system_printer_bed_model(curr_preset).size() > 0; + if (is_configed_by_BBL) + { // only record svg + if (wxGetApp().app_config->has_section("user_bbl_svg_list")) + { + auto user_bbl_svg_list = wxGetApp().app_config->get_section("user_bbl_svg_list"); + if (user_bbl_svg_list.size() > 0 && user_bbl_svg_list[curr_preset_name].size() > 0) + { + extra_map["bed_custom_texture"] = ConfigOptionString(user_bbl_svg_list[curr_preset_name]); + } + } + } + else + { // for cutom machine + auto bed_model_path = wxGetApp().plater()->get_partplate_list().get_bed3d()->get_model_filename(); + if (!bed_model_path.empty()) + { + extra_map["bed_custom_model"] = bed_model_path; + } + auto logo = wxGetApp().plater()->get_partplate_list().get_logo_texture_filename(); + if (!logo.empty()) + { + extra_map["bed_custom_texture"] = logo; + } + } + } + bool exist_preset = false; + Preset *new_preset = m_presets->find_preset(name, false); + if (new_preset) + { + exist_preset = true; + } -void Tab::compare_preset() -{ - wxGetApp().mainframe->diff_dialog.show(m_type); -} - -// Save the current preset into file. -// This removes the "dirty" flag of the preset, possibly creates a new preset under a new name, -// and activates the new preset. -// Wizard calls save_preset with a name "My Settings", otherwise no name is provided and this method -// opens a Slic3r::GUI::SavePresetDialog dialog. -//BBS: add project embedded preset relate logic -void Tab::save_preset(std::string name /*= ""*/, bool detach, bool save_to_project, bool from_input, std::string input_name ) -{ - // since buttons(and choices too) don't get focus on Mac, we set focus manually - // to the treectrl so that the EVT_* events are fired for the input field having - // focus currently.is there anything better than this ? -//! m_tabctrl->OnSetFocus(); - if (from_input) { - SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : ""); - dlg.Show(false); - dlg.input_name_from_other(input_name); - wxCommandEvent evt(wxEVT_TEXT, GetId()); - dlg.GetEventHandler()->ProcessEvent(evt); - dlg.confirm_from_other(); - name = input_name; - } + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini + m_presets->save_current_preset(name, detach, save_to_project, nullptr, &extra_map); - if (name.empty()) { - SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : ""); - if (!m_just_edit) { - if (dlg.ShowModal() != wxID_OK) + // BBS create new settings + new_preset = m_presets->find_preset(name, false, true); + // Preset* preset = &m_presets.preset(it - m_presets.begin(), true); + if (!new_preset) + { + BOOST_LOG_TRIVIAL(info) << "create new preset failed"; return; - } - name = dlg.get_name(); - //BBS: add project embedded preset relate logic - save_to_project = dlg.get_save_to_project_selection(m_type); - } + } - //BBS record current preset name - std::string curr_preset_name = m_presets->get_edited_preset().name; - auto curr_preset = m_presets->get_edited_preset(); - std::map extra_map; - { - bool is_configed_by_BBL = PresetUtils::system_printer_bed_model(curr_preset).size() > 0; - if (is_configed_by_BBL) {//only record svg - if (wxGetApp().app_config->has_section("user_bbl_svg_list")) { - auto user_bbl_svg_list = wxGetApp().app_config->get_section("user_bbl_svg_list"); - if (user_bbl_svg_list.size() > 0 && user_bbl_svg_list[curr_preset_name].size() > 0) { - extra_map["bed_custom_texture"] = ConfigOptionString(user_bbl_svg_list[curr_preset_name]); - } + // set sync_info for sync service + if (exist_preset) + { + new_preset->sync_info = "update"; + BOOST_LOG_TRIVIAL(info) << "sync_preset: update preset = " << new_preset->name; } - } - else {//for cutom machine - auto bed_model_path = wxGetApp().plater()->get_partplate_list().get_bed3d()->get_model_filename(); - if (!bed_model_path.empty()) { - extra_map["bed_custom_model"] = bed_model_path; + else + { + new_preset->sync_info = "create"; + if (wxGetApp().is_user_login()) + new_preset->user_id = wxGetApp().getAgent()->get_user_id(); + BOOST_LOG_TRIVIAL(info) << "sync_preset: create preset = " << new_preset->name; } - auto logo = wxGetApp().plater()->get_partplate_list().get_logo_texture_filename(); - if (!logo.empty()) { - extra_map["bed_custom_texture"] = logo; + new_preset->save_info(); + + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. + m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); + // Add the new item into the UI component, remove dirty flags and activate the saved item. + update_tab_ui(); + + // Update the selection boxes at the plater. + on_presets_changed(); + + // BBS if create a new prset name, preset changed from preset name to new preset name + if (!exist_preset) + { + wxGetApp().plater()->sidebar().update_presets_from_to(m_type, curr_preset_name, new_preset->name); } - } - } - bool exist_preset = false; - Preset* new_preset = m_presets->find_preset(name, false); - if (new_preset) { - exist_preset = true; - } - // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini - m_presets->save_current_preset(name, detach, save_to_project, nullptr, &extra_map); + // If current profile is saved, "delete preset" button have to be enabled + m_btn_delete_preset->Show(); + m_btn_delete_preset->GetParent()->Layout(); - //BBS create new settings - new_preset = m_presets->find_preset(name, false, true); - //Preset* preset = &m_presets.preset(it - m_presets.begin(), true); - if (!new_preset) { - BOOST_LOG_TRIVIAL(info) << "create new preset failed"; - return; - } + if (m_type == Preset::TYPE_PRINTER) + static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; - // set sync_info for sync service - if (exist_preset) { - new_preset->sync_info = "update"; - BOOST_LOG_TRIVIAL(info) << "sync_preset: update preset = " << new_preset->name; - } - else { - new_preset->sync_info = "create"; - if (wxGetApp().is_user_login()) - new_preset->user_id = wxGetApp().getAgent()->get_user_id(); - BOOST_LOG_TRIVIAL(info) << "sync_preset: create preset = " << new_preset->name; - } - new_preset->save_info(); + // Parent preset is "default" after detaching, so we should to update UI values, related on parent preset + if (detach) + update_ui_items_related_on_parent_preset(m_presets->get_selected_preset_parent()); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. - m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); - // Add the new item into the UI component, remove dirty flags and activate the saved item. - update_tab_ui(); + update_changed_ui(); - // Update the selection boxes at the plater. - on_presets_changed(); + /* If filament preset is saved for multi-material printer preset, + * there are cases when filament comboboxs are updated for old (non-modified) colors, + * but in full_config a filament_colors option aren't.*/ + if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + wxGetApp().plater()->force_filament_colors_update(); - //BBS if create a new prset name, preset changed from preset name to new preset name - if (!exist_preset) { - wxGetApp().plater()->sidebar().update_presets_from_to(m_type, curr_preset_name, new_preset->name); - } + { + // Profile compatiblity is updated first when the profile is saved. + // Update profile selection combo boxes at the depending tabs to reflect modifications in profile compatibility. + std::vector dependent; + switch (m_type) + { + case Preset::TYPE_PRINT: + dependent = {Preset::TYPE_FILAMENT}; + break; + case Preset::TYPE_SLA_PRINT: + dependent = {Preset::TYPE_SLA_MATERIAL}; + break; + case Preset::TYPE_PRINTER: + if (static_cast(this)->m_printer_technology == ptFFF) + dependent = {Preset::TYPE_PRINT, Preset::TYPE_FILAMENT}; + else + dependent = {Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL}; + break; + default: + break; + } + for (Preset::Type preset_type : dependent) + wxGetApp().get_tab(preset_type)->update_tab_ui(); + } - // If current profile is saved, "delete preset" button have to be enabled - m_btn_delete_preset->Show(); - m_btn_delete_preset->GetParent()->Layout(); + // update preset comboboxes in DiffPresetDlg + wxGetApp().mainframe->diff_dialog.update_presets(m_type); + } - if (m_type == Preset::TYPE_PRINTER) - static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + // Called for a currently selected preset. + void Tab::delete_preset() + { + auto current_preset = m_presets->get_selected_preset(); + // Don't let the user delete the ' - default - ' configuration. + // BBS: add project embedded preset logic and refine is_external + std::string action = _utf8(L("Delete")); + // std::string action = current_preset.is_external ? _utf8(L("remove")) : _utf8(L("delete")); + // TRN remove/delete + wxString msg; + bool confirm_delete_third_party_printer = false; + bool is_base_preset = false; + if (m_presets->get_preset_base(current_preset) == ¤t_preset) + { // root preset + is_base_preset = true; + if (current_preset.type == Preset::Type::TYPE_PRINTER && !current_preset.is_system) + { // Customize third-party printers + Preset ¤t_preset = m_presets->get_selected_preset(); + int filament_preset_num = 0; + int process_preset_num = 0; + for (const Preset &preset : m_preset_bundle->filaments.get_presets()) + { + if (preset.is_compatible && !preset.is_default) + { + filament_preset_num++; + } + } + for (const Preset &preset : m_preset_bundle->prints.get_presets()) + { + if (preset.is_compatible && !preset.is_default) + { + process_preset_num++; + } + } - // Parent preset is "default" after detaching, so we should to update UI values, related on parent preset - if (detach) - update_ui_items_related_on_parent_preset(m_presets->get_selected_preset_parent()); + DeleteConfirmDialog + dlg(parent(), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Delete"), + wxString::Format(_L("%d Filament Preset and %d Process Preset is attached to this printer. Those presets would be deleted if the printer is deleted."), + filament_preset_num, process_preset_num)); + int res = dlg.ShowModal(); + if (res != wxID_OK) + return; + confirm_delete_third_party_printer = true; + } + int count = 0; + wxString presets; + for (auto &preset2 : *m_presets) + if (preset2.inherits() == current_preset.name) + { + ++count; + presets += "\n - " + from_u8(preset2.name); + } + if (count > 0) + { + msg = _L("Presets inherited by other presets can not be deleted!"); + msg += "\n"; + msg += _L_PLURAL("The following presets inherit this preset.", + "The following preset inherits this preset.", count); + wxString title = from_u8((boost::format(_utf8(L("%1% Preset"))) % action).str()); // action + _(L(" Preset")); + MessageDialog(parent(), msg + presets, title, wxOK | wxICON_ERROR).ShowModal(); + return; + } + } - update_changed_ui(); + BOOST_LOG_TRIVIAL(info) << boost::format("delete preset %1%, setting_id %2%, user_id %3%, base_id %4%, sync_info %5%, type %6%") % current_preset.name % current_preset.setting_id % current_preset.user_id % current_preset.base_id % current_preset.sync_info % Preset::get_type_string(m_type); + PhysicalPrinterCollection &physical_printers = m_preset_bundle->physical_printers; - /* If filament preset is saved for multi-material printer preset, - * there are cases when filament comboboxs are updated for old (non-modified) colors, - * but in full_config a filament_colors option aren't.*/ - if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) - wxGetApp().plater()->force_filament_colors_update(); + if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty()) + { + // Check preset for delete in physical printers + // Ask a customer about next action, if there is a printer with just one preset and this preset is equal to delete + std::vector ph_printers = physical_printers.get_printers_with_preset(current_preset.name); + std::vector ph_printers_only = physical_printers.get_printers_with_only_preset(current_preset.name); + + // if (!ph_printers.empty()) { + // msg += _L_PLURAL("The physical printer below is based on the preset, you are going to delete.", + // "The physical printers below are based on the preset, you are going to delete.", ph_printers.size()); + // for (const std::string& printer : ph_printers) + // msg += "\n \"" + from_u8(printer) + "\","; + // msg.RemoveLast(); + // msg += "\n" + _L_PLURAL("Note, that the selected preset will be deleted from this printer too.", + // "Note, that the selected preset will be deleted from these printers too.", ph_printers.size()) + "\n\n"; + // } + + // if (!ph_printers_only.empty()) { + // msg += _L_PLURAL("The physical printer below is based only on the preset, you are going to delete.", + // "The physical printers below are based only on the preset, you are going to delete.", ph_printers_only.size()); + // for (const std::string& printer : ph_printers_only) + // msg += "\n \"" + from_u8(printer) + "\","; + // msg.RemoveLast(); + // msg += "\n" + _L_PLURAL("Note, that this printer will be deleted after deleting the selected preset.", + // "Note, that these printers will be deleted after deleting the selected preset.", ph_printers_only.size()) + "\n\n"; + // } + if (!ph_printers.empty() || !ph_printers_only.empty()) + { + msg += _L_PLURAL("Following preset will be deleted too.", "Following presets will be deleted too.", ph_printers.size() + ph_printers_only.size()); + for (const std::string &printer : ph_printers) + msg += "\n \"" + from_u8(printer) + "\","; + for (const std::string &printer : ph_printers_only) + msg += "\n \"" + from_u8(printer) + "\","; + msg.RemoveLast(); + // msg += "\n" + _L_PLURAL("Note, that the selected preset will be deleted from this printer too.", + // "Note, that the selected preset will be deleted from these printers too.", ph_printers.size()) + "\n\n"; + } + } - { - // Profile compatiblity is updated first when the profile is saved. - // Update profile selection combo boxes at the depending tabs to reflect modifications in profile compatibility. - std::vector dependent; - switch (m_type) { - case Preset::TYPE_PRINT: - dependent = { Preset::TYPE_FILAMENT }; - break; - case Preset::TYPE_SLA_PRINT: - dependent = { Preset::TYPE_SLA_MATERIAL }; - break; - case Preset::TYPE_PRINTER: - if (static_cast(this)->m_printer_technology == ptFFF) - dependent = { Preset::TYPE_PRINT, Preset::TYPE_FILAMENT }; + if (is_base_preset && (current_preset.type == Preset::Type::TYPE_FILAMENT) && action == _utf8(L("Delete"))) + { + msg += from_u8(_u8L("Are you sure to delete the selected preset? \nIf the preset corresponds to a filament currently in use on your printer, please reset the filament information for that slot.")); + } else - dependent = { Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL }; - break; - default: - break; - } - for (Preset::Type preset_type : dependent) - wxGetApp().get_tab(preset_type)->update_tab_ui(); - } - - // update preset comboboxes in DiffPresetDlg - wxGetApp().mainframe->diff_dialog.update_presets(m_type); -} + { + msg += from_u8((boost::format(_u8L("Are you sure to %1% the selected preset?")) % action).str()); + } -// Called for a currently selected preset. -void Tab::delete_preset() -{ - auto current_preset = m_presets->get_selected_preset(); - // Don't let the user delete the ' - default - ' configuration. - //BBS: add project embedded preset logic and refine is_external - std::string action = _utf8(L("Delete")); - //std::string action = current_preset.is_external ? _utf8(L("remove")) : _utf8(L("delete")); - // TRN remove/delete - wxString msg; - bool confirm_delete_third_party_printer = false; - bool is_base_preset = false; - if (m_presets->get_preset_base(current_preset) == ¤t_preset) { //root preset - is_base_preset = true; - if (current_preset.type == Preset::Type::TYPE_PRINTER && !current_preset.is_system) { //Customize third-party printers - Preset ¤t_preset = m_presets->get_selected_preset(); - int filament_preset_num = 0; - int process_preset_num = 0; - for (const Preset &preset : m_preset_bundle->filaments.get_presets()) { - if (preset.is_compatible && !preset.is_default) { filament_preset_num++; } - } - for (const Preset &preset : m_preset_bundle->prints.get_presets()) { - if (preset.is_compatible && !preset.is_default) { process_preset_num++; } - } - - DeleteConfirmDialog - dlg(parent(), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Delete"), - wxString::Format(_L("%d Filament Preset and %d Process Preset is attached to this printer. Those presets would be deleted if the printer is deleted."), - filament_preset_num, process_preset_num)); - int res = dlg.ShowModal(); - if (res != wxID_OK) return; - confirm_delete_third_party_printer = true; - } - int count = 0; - wxString presets; - for (auto &preset2 : *m_presets) - if (preset2.inherits() == current_preset.name) { - ++count; - presets += "\n - " + from_u8(preset2.name); - } - if (count > 0) { - msg = _L("Presets inherited by other presets can not be deleted!"); - msg += "\n"; - msg += _L_PLURAL("The following presets inherit this preset.", - "The following preset inherits this preset.", count); + // BBS: add project embedded preset logic and refine is_external + action = _utf8(L("Delete")); + // action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); + // TRN Remove/Delete wxString title = from_u8((boost::format(_utf8(L("%1% Preset"))) % action).str()); // action + _(L(" Preset")); - MessageDialog(parent(), msg + presets, title, wxOK | wxICON_ERROR).ShowModal(); - return; - } - } + if (current_preset.is_default || !(confirm_delete_third_party_printer || + // wxID_YES != wxMessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) + wxID_YES == MessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal())) + return; + auto delete_cur_bed_type_to_config = [this]() + { + PresetBundle &preset_bundle = *wxGetApp().preset_bundle; + auto cur_preset_name = preset_bundle.printers.get_edited_preset().name; + if (cur_preset_name.size() > 0 && wxGetApp().app_config->has_section("user_bed_type_list")) + { + auto data = wxGetApp().app_config->get_section("user_bed_type_list"); + auto data_modify = const_cast *>(&data); + if ((*data_modify).find(cur_preset_name) != data_modify->end()) + { + data_modify->erase(cur_preset_name); + wxGetApp().app_config->set_section("user_bed_type_list", *data_modify); + } + } + }; + delete_cur_bed_type_to_config(); + // if we just delete preset from the physical printer + if (m_presets_choice->is_selected_physical_printer()) + { + PhysicalPrinter &printer = physical_printers.get_selected_printer(); - BOOST_LOG_TRIVIAL(info) << boost::format("delete preset %1%, setting_id %2%, user_id %3%, base_id %4%, sync_info %5%, type %6%") - %current_preset.name%current_preset.setting_id%current_preset.user_id%current_preset.base_id%current_preset.sync_info - %Preset::get_type_string(m_type); - PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; + // just delete this preset from the current physical printer + printer.delete_preset(m_presets->get_edited_preset().name); + // select first from the possible presets for this printer + physical_printers.select_printer(printer); - if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty()) - { - // Check preset for delete in physical printers - // Ask a customer about next action, if there is a printer with just one preset and this preset is equal to delete - std::vector ph_printers = physical_printers.get_printers_with_preset(current_preset.name); - std::vector ph_printers_only = physical_printers.get_printers_with_only_preset(current_preset.name); - - //if (!ph_printers.empty()) { - // msg += _L_PLURAL("The physical printer below is based on the preset, you are going to delete.", - // "The physical printers below are based on the preset, you are going to delete.", ph_printers.size()); - // for (const std::string& printer : ph_printers) - // msg += "\n \"" + from_u8(printer) + "\","; - // msg.RemoveLast(); - // msg += "\n" + _L_PLURAL("Note, that the selected preset will be deleted from this printer too.", - // "Note, that the selected preset will be deleted from these printers too.", ph_printers.size()) + "\n\n"; - //} - - //if (!ph_printers_only.empty()) { - // msg += _L_PLURAL("The physical printer below is based only on the preset, you are going to delete.", - // "The physical printers below are based only on the preset, you are going to delete.", ph_printers_only.size()); - // for (const std::string& printer : ph_printers_only) - // msg += "\n \"" + from_u8(printer) + "\","; - // msg.RemoveLast(); - // msg += "\n" + _L_PLURAL("Note, that this printer will be deleted after deleting the selected preset.", - // "Note, that these printers will be deleted after deleting the selected preset.", ph_printers_only.size()) + "\n\n"; - //} - if (!ph_printers.empty() || !ph_printers_only.empty()) { - msg += _L_PLURAL("Following preset will be deleted too.", "Following presets will be deleted too.", ph_printers.size() + ph_printers_only.size()); - for (const std::string &printer : ph_printers) msg += "\n \"" + from_u8(printer) + "\","; - for (const std::string &printer : ph_printers_only) msg += "\n \"" + from_u8(printer) + "\","; - msg.RemoveLast(); - // msg += "\n" + _L_PLURAL("Note, that the selected preset will be deleted from this printer too.", - // "Note, that the selected preset will be deleted from these printers too.", ph_printers.size()) + "\n\n"; - } - } + this->select_preset(physical_printers.get_selected_printer_preset_name()); + return; + } - if (is_base_preset && (current_preset.type == Preset::Type::TYPE_FILAMENT) && action == _utf8(L("Delete"))) { - msg += from_u8(_u8L("Are you sure to delete the selected preset? \nIf the preset corresponds to a filament currently in use on your printer, please reset the filament information for that slot.")); - } else { - msg += from_u8((boost::format(_u8L("Are you sure to %1% the selected preset?")) % action).str()); - } + // delete selected preset from printers and printer, if it's needed + if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty()) + physical_printers.delete_preset_from_printers(current_preset.name); - //BBS: add project embedded preset logic and refine is_external - action = _utf8(L("Delete")); - //action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); - // TRN Remove/Delete - wxString title = from_u8((boost::format(_utf8(L("%1% Preset"))) % action).str()); //action + _(L(" Preset")); - if (current_preset.is_default || !(confirm_delete_third_party_printer || - //wxID_YES != wxMessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) - wxID_YES == MessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal())) - return; - auto delete_cur_bed_type_to_config = [this]() { - PresetBundle &preset_bundle = *wxGetApp().preset_bundle; - auto cur_preset_name = preset_bundle.printers.get_edited_preset().name; - if (cur_preset_name.size() > 0 && wxGetApp().app_config->has_section("user_bed_type_list")) { - auto data = wxGetApp().app_config->get_section("user_bed_type_list"); - auto data_modify = const_cast *>(&data); - if ((*data_modify).find(cur_preset_name) != data_modify->end()) { - data_modify->erase(cur_preset_name); - wxGetApp().app_config->set_section("user_bed_type_list", *data_modify); - } - } - }; - delete_cur_bed_type_to_config(); - // if we just delete preset from the physical printer - if (m_presets_choice->is_selected_physical_printer()) { - PhysicalPrinter& printer = physical_printers.get_selected_printer(); - - // just delete this preset from the current physical printer - printer.delete_preset(m_presets->get_edited_preset().name); - // select first from the possible presets for this printer - physical_printers.select_printer(printer); - - this->select_preset(physical_printers.get_selected_printer_preset_name()); - return; - } + // Select will handle of the preset dependencies, of saving & closing the depending profiles, and + // finally of deleting the preset. + this->select_preset("", true); - // delete selected preset from printers and printer, if it's needed - if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty()) - physical_printers.delete_preset_from_printers(current_preset.name); + BOOST_LOG_TRIVIAL(info) << boost::format("delete preset finished"); + } - // Select will handle of the preset dependencies, of saving & closing the depending profiles, and - // finally of deleting the preset. - this->select_preset("", true); + void Tab::toggle_show_hide_incompatible() + { + m_show_incompatible_presets = !m_show_incompatible_presets; + if (m_presets_choice) + m_presets_choice->set_show_incompatible_presets(m_show_incompatible_presets); + update_show_hide_incompatible_button(); + update_tab_ui(); + } - BOOST_LOG_TRIVIAL(info) << boost::format("delete preset finished"); -} + void Tab::update_show_hide_incompatible_button() + { + // BBS: GUI refactor + /*m_btn_hide_incompatible_presets->SetBitmap_(m_show_incompatible_presets ? + m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); + m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? + "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : + "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer.");*/ + } -void Tab::toggle_show_hide_incompatible() -{ - m_show_incompatible_presets = !m_show_incompatible_presets; - if (m_presets_choice) - m_presets_choice->set_show_incompatible_presets(m_show_incompatible_presets); - update_show_hide_incompatible_button(); - update_tab_ui(); -} - -void Tab::update_show_hide_incompatible_button() -{ - //BBS: GUI refactor - /*m_btn_hide_incompatible_presets->SetBitmap_(m_show_incompatible_presets ? - m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); - m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? - "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : - "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer.");*/ -} - -void Tab::update_ui_from_settings() -{ - // Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled - // in application preferences. - m_show_btn_incompatible_presets = true; - bool show = m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER; - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); - //show ? m_btn_hide_incompatible_presets->Show() : m_btn_hide_incompatible_presets->Hide(); - // If the 'show / hide presets' button is hidden, hide the incompatible presets. - if (show) { - update_show_hide_incompatible_button(); - } - else { - if (m_show_incompatible_presets) { - m_show_incompatible_presets = false; - update_tab_ui(); + void Tab::update_ui_from_settings() + { + // Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled + // in application preferences. + m_show_btn_incompatible_presets = true; + bool show = m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER; + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); + // show ? m_btn_hide_incompatible_presets->Show() : m_btn_hide_incompatible_presets->Hide(); + // If the 'show / hide presets' button is hidden, hide the incompatible presets. + if (show) + { + update_show_hide_incompatible_button(); + } + else + { + if (m_show_incompatible_presets) + { + m_show_incompatible_presets = false; + update_tab_ui(); + } + } } - } -} -void Tab::create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const std::string& path, widget_t widget) -{ - Line line = optgroup->create_single_option_line(opt_key); - line.widget = widget; - line.label_path = path; + void Tab::create_line_with_widget(ConfigOptionsGroup *optgroup, const std::string &opt_key, const std::string &path, widget_t widget) + { + Line line = optgroup->create_single_option_line(opt_key); + line.widget = widget; + line.label_path = path; - m_colored_Label_colors[opt_key] = m_default_text_clr; - line.full_Label_color = &m_colored_Label_colors[opt_key]; + m_colored_Label_colors[opt_key] = m_default_text_clr; + line.full_Label_color = &m_colored_Label_colors[opt_key]; - optgroup->append_line(line); -} + optgroup->append_line(line); + } -// Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer. -wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &deps) -{ - deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); - deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - wxGetApp().UpdateDarkUI(deps.checkbox, false, true); - deps.btn = new ScalableButton(parent, wxID_ANY, "printer", from_u8((boost::format(" %s %s") % _utf8(L("Set")) % std::string(dots.ToUTF8())).str()), - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); - deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - deps.btn->SetSize(deps.btn->GetBestSize()); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add((deps.checkbox), 0, wxALIGN_CENTER_VERTICAL); - sizer->Add((deps.btn), 0, wxALIGN_CENTER_VERTICAL); - - deps.checkbox->Bind(wxEVT_CHECKBOX, ([this, &deps](wxCommandEvent e) - { + // Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer. + wxSizer *Tab::compatible_widget_create(wxWindow *parent, PresetDependencies &deps) + { + deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); + deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + wxGetApp().UpdateDarkUI(deps.checkbox, false, true); + deps.btn = new ScalableButton(parent, wxID_ANY, "printer", from_u8((boost::format(" %s %s") % _utf8(L("Set")) % std::string(dots.ToUTF8())).str()), + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + deps.btn->SetSize(deps.btn->GetBestSize()); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add((deps.checkbox), 0, wxALIGN_CENTER_VERTICAL); + sizer->Add((deps.btn), 0, wxALIGN_CENTER_VERTICAL); + + deps.checkbox->Bind(wxEVT_CHECKBOX, ([this, &deps](wxCommandEvent e) + { deps.btn->Enable(! deps.checkbox->GetValue()); // All printers have been made compatible with this preset. if (deps.checkbox->GetValue()) this->load_key_value(deps.key_list, std::vector {}); this->get_field(deps.key_condition)->toggle(deps.checkbox->GetValue()); - this->update_changed_ui(); - }) ); + this->update_changed_ui(); })); - deps.btn->Bind(wxEVT_BUTTON, ([this, parent, &deps](wxCommandEvent e) - { + deps.btn->Bind(wxEVT_BUTTON, ([this, parent, &deps](wxCommandEvent e) + { // Collect names of non-default non-external profiles. PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); PresetCollection &depending_presets = (deps.type == Preset::TYPE_PRINTER) ? m_preset_bundle->printers : @@ -6570,57 +7111,60 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep // All depending_presets have been made compatible with this preset. this->load_key_value(deps.key_list, value); this->update_changed_ui(); + } })); + + return sizer; } - })); - return sizer; -} + void TabPrinter::set_extruder_volume_type(int extruder_id, NozzleVolumeType type) + { + // -1 means single extruder, so we should default use extruder id 0 + if (extruder_id == -1) + extruder_id = 0; + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + assert(nozzle_volumes->values.size() > (size_t)extruder_id); + nozzle_volumes->values[extruder_id] = type; -void TabPrinter::set_extruder_volume_type(int extruder_id, NozzleVolumeType type) -{ - // -1 means single extruder, so we should default use extruder id 0 - if (extruder_id == -1) - extruder_id = 0; - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - assert(nozzle_volumes->values.size() > (size_t)extruder_id); - nozzle_volumes->values[extruder_id] = type; - - m_preset_bundle->extruder_nozzle_stat.on_volume_type_switch(extruder_id, type); - if (wxGetApp().plater()) { - wxGetApp().plater()->update_filament_volume_map(extruder_id, int(type)); - } - updateNozzleCountDisplay(m_preset_bundle, extruder_id, type); + m_preset_bundle->extruder_nozzle_stat.on_volume_type_switch(extruder_id, type); + if (wxGetApp().plater()) + { + wxGetApp().plater()->update_filament_volume_map(extruder_id, int(type)); + } + updateNozzleCountDisplay(m_preset_bundle, extruder_id, type); - on_value_change((boost::format("nozzle_volume_type#%1%") % extruder_id).str(), int(type)); - update_dirty(); + on_value_change((boost::format("nozzle_volume_type#%1%") % extruder_id).str(), int(type)); + update_dirty(); - //save to app config - if (!m_base_preset_name.empty()) { - // do not save hybrid flow status to config - ConfigOptionEnumsGeneric nozzle_volume_type_option = *m_preset_bundle->project_config.option("nozzle_volume_type"); - for(size_t i = 0; i < nozzle_volume_type_option.values.size(); i++){ - if(nozzle_volume_type_option.values[i] == (int)(nvtHybrid)){ - nozzle_volume_type_option.values[i] = (int)(nvtStandard); + // save to app config + if (!m_base_preset_name.empty()) + { + // do not save hybrid flow status to config + ConfigOptionEnumsGeneric nozzle_volume_type_option = *m_preset_bundle->project_config.option("nozzle_volume_type"); + for (size_t i = 0; i < nozzle_volume_type_option.values.size(); i++) + { + if (nozzle_volume_type_option.values[i] == (int)(nvtHybrid)) + { + nozzle_volume_type_option.values[i] = (int)(nvtStandard); + } + } + std::string nozzle_volume_type_str = nozzle_volume_type_option.serialize(); + wxGetApp().app_config->save_nozzle_volume_types_to_config(m_base_preset_name, nozzle_volume_type_str); } } - std::string nozzle_volume_type_str = nozzle_volume_type_option.serialize(); - wxGetApp().app_config->save_nozzle_volume_types_to_config(m_base_preset_name, nozzle_volume_type_str); - } -} - -// Return a callback to create a TabPrinter widget to edit bed shape -wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) -{ - ScalableButton* btn = new ScalableButton(parent, wxID_ANY, "printer", " " + _(L("Set")) + " " + dots, - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); - btn->SetFont(wxGetApp().normal_font()); - btn->SetSize(btn->GetBestSize()); + // Return a callback to create a TabPrinter widget to edit bed shape + wxSizer *TabPrinter::create_bed_shape_widget(wxWindow *parent) + { + ScalableButton *btn = new ScalableButton(parent, wxID_ANY, "printer", " " + _(L("Set")) + " " + dots, + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + btn->SetFont(wxGetApp().normal_font()); + btn->SetSize(btn->GetBestSize()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL); - btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { + btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) + { bool is_configed_by_BBL = PresetUtils::system_printer_bed_model(m_preset_bundle->printers.get_edited_preset()).size() > 0; ConfigOptionString custom_texture = *m_config->option("bed_custom_texture"); PresetBundle & preset_bundle = *wxGetApp().preset_bundle; @@ -6669,680 +7213,772 @@ wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) } else { show_error(m_parent, _L("Invalid input.")); } - } - })); + } })); - { - Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher(); - const Search::GroupAndCategory& gc = searcher.get_group_and_category("printable_area"); - searcher.add_key("bed_custom_texture", m_type, gc.group, gc.category); - searcher.add_key("bed_custom_model", m_type, gc.group, gc.category); - } + { + Search::OptionsSearcher &searcher = wxGetApp().sidebar().get_searcher(); + const Search::GroupAndCategory &gc = searcher.get_group_and_category("printable_area"); + searcher.add_key("bed_custom_texture", m_type, gc.group, gc.category); + searcher.add_key("bed_custom_model", m_type, gc.group, gc.category); + } - return sizer; -} + return sizer; + } -void TabPrinter::cache_extruder_cnt() -{ - if (m_presets->get_edited_preset().printer_technology() == ptSLA) - return; + void TabPrinter::cache_extruder_cnt() + { + if (m_presets->get_edited_preset().printer_technology() == ptSLA) + return; - // BBS. Get extruder count from preset instead of m_extruders_count. - m_cache_extruder_count = dynamic_cast((m_presets->get_edited_preset().config).option("nozzle_diameter"))->values.size(); -} + // BBS. Get extruder count from preset instead of m_extruders_count. + m_cache_extruder_count = dynamic_cast((m_presets->get_edited_preset().config).option("nozzle_diameter"))->values.size(); + } -bool TabPrinter::apply_extruder_cnt_from_cache() -{ - if (m_presets->get_edited_preset().printer_technology() == ptSLA) - return false; + bool TabPrinter::apply_extruder_cnt_from_cache() + { + if (m_presets->get_edited_preset().printer_technology() == ptSLA) + return false; - if (m_cache_extruder_count > 0) { - m_presets->get_edited_preset().set_num_extruders(m_cache_extruder_count); - m_cache_extruder_count = 0; - return true; - } - return false; -} + if (m_cache_extruder_count > 0) + { + m_presets->get_edited_preset().set_num_extruders(m_cache_extruder_count); + m_cache_extruder_count = 0; + return true; + } + return false; + } -bool Tab::validate_custom_gcodes() -{ - if (m_type != Preset::TYPE_FILAMENT && - (m_type != Preset::TYPE_PRINTER || static_cast(this)->m_printer_technology != ptFFF)) - return true; - if (m_active_page->title() != L("Custom G-code")) - return true; - - // When we switch Settings tab after editing of the custom g-code, then warning message could ba already shown after KillFocus event - // and then it's no need to show it again - if (validate_custom_gcodes_was_shown) { - validate_custom_gcodes_was_shown = false; - return true; - } + bool Tab::validate_custom_gcodes() + { + if (m_type != Preset::TYPE_FILAMENT && + (m_type != Preset::TYPE_PRINTER || static_cast(this)->m_printer_technology != ptFFF)) + return true; + if (m_active_page->title() != L("Custom G-code")) + return true; + + // When we switch Settings tab after editing of the custom g-code, then warning message could ba already shown after KillFocus event + // and then it's no need to show it again + if (validate_custom_gcodes_was_shown) + { + validate_custom_gcodes_was_shown = false; + return true; + } - bool valid = true; - for (auto opt_group : m_active_page->m_optgroups) { - assert(opt_group->opt_map().size() == 1); - if (!opt_group->is_activated()) - break; - std::string key = opt_group->opt_map().begin()->first; - valid &= validate_custom_gcode(opt_group->title, boost::any_cast(opt_group->get_value(key))); - if (!valid) - break; - } - return valid; -} + bool valid = true; + for (auto opt_group : m_active_page->m_optgroups) + { + assert(opt_group->opt_map().size() == 1); + if (!opt_group->is_activated()) + break; + std::string key = opt_group->opt_map().begin()->first; + valid &= validate_custom_gcode(opt_group->title, boost::any_cast(opt_group->get_value(key))); + if (!valid) + break; + } + return valid; + } -void Tab::set_just_edit(bool just_edit) -{ - m_just_edit = just_edit; - if (just_edit) { - m_presets_choice->Disable(); - m_btn_delete_preset->Disable(); - } else { - m_presets_choice->Enable(); - m_btn_delete_preset->Enable(); - } -} + void Tab::set_just_edit(bool just_edit) + { + m_just_edit = just_edit; + if (just_edit) + { + m_presets_choice->Disable(); + m_btn_delete_preset->Disable(); + } + else + { + m_presets_choice->Enable(); + m_btn_delete_preset->Enable(); + } + } -MachineObject* get_current_machine_object() -{ - DeviceManager *dev_manager = wxGetApp().getDeviceManager(); - if (!dev_manager) return nullptr; + MachineObject *get_current_machine_object() + { + DeviceManager *dev_manager = wxGetApp().getDeviceManager(); + if (!dev_manager) + return nullptr; - return dev_manager->get_selected_machine(); -} + return dev_manager->get_selected_machine(); + } -/// -/// Call from: -/// 1: on_value_change "nozzle_volume_type" -/// 2: on_preset_loaded (extruder_id = -1) -/// -/// + /// + /// Call from: + /// 1: on_value_change "nozzle_volume_type" + /// 2: on_preset_loaded (extruder_id = -1) + /// + /// -void Tab::update_extruder_variants(int extruder_id, bool reload) -{ - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << extruder_id; - if (m_extruder_switch) { - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - int extruder_nums = m_preset_bundle->get_printer_extruder_count(); - nozzle_volumes->values.resize(extruder_nums); - if (extruder_nums == 2) { - auto options = generate_extruder_options(); - m_extruder_switch->SetOptions(options); + void Tab::update_extruder_variants(int extruder_id, bool reload) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << extruder_id; + if (m_extruder_switch) + { + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + int extruder_nums = m_preset_bundle->get_printer_extruder_count(); + nozzle_volumes->values.resize(extruder_nums); + if (extruder_nums == 2) + { + auto options = generate_extruder_options(); + m_extruder_switch->SetOptions(options); + + int selection_index; + if (extruder_id >= 0) + { + NozzleVolumeType current_nozzle_type = get_actual_nozzle_volume_type(extruder_id); + selection_index = calculate_selection_index_for_extruder(extruder_id, current_nozzle_type); + } + else + { + selection_index = m_extruder_switch->GetSelection(); + if (selection_index < 0) + { + selection_index = 0; + } + } - int selection_index; - if (extruder_id >= 0) { - NozzleVolumeType current_nozzle_type = get_actual_nozzle_volume_type(extruder_id); - selection_index = calculate_selection_index_for_extruder(extruder_id, current_nozzle_type); - } else { - selection_index = m_extruder_switch->GetSelection(); - if (selection_index < 0) { - selection_index = 0; + m_extruder_switch->SetSelection(selection_index); + m_extruder_switch->Enable(true); + m_extruder_sync->Enable(true); + } + else + { + m_extruder_switch->Enable(false); + m_extruder_sync->Enable(false); } } - - m_extruder_switch->SetSelection(selection_index); - m_extruder_switch->Enable(true); - m_extruder_sync->Enable(true); - } else { - m_extruder_switch->Enable(false); - m_extruder_sync->Enable(false); - } - } else if (m_variant_combo) { - if (extruder_id >= 0) // variant_combo did not depend on extruder - return; - int n = m_variant_combo->GetSelection(); - auto options = generate_extruder_options(); - m_variant_combo->SetOptions(options); - for (int i = 0; i < m_variant_combo->GetCount(); ++i) { - auto flow_type = get_actual_nozzle_flow_type(i); - auto ext_type = get_actual_extruder_type(i); - bool connected = false; - auto r_nozzles = collect_nozzles(MAIN_EXTRUDER_ID, ext_type, flow_type, connected); - std::vector l_nozzles; - if (m_preset_bundle->get_printer_extruder_count() > 1) - l_nozzles = collect_nozzles(DEPUTY_EXTRUDER_ID, ext_type, flow_type, connected); - if (connected && r_nozzles.empty() && l_nozzles.empty()) { - Button *btn = m_variant_combo->GetButton(i); - if (btn) { - btn->SetGrayed(true); + else if (m_variant_combo) + { + if (extruder_id >= 0) // variant_combo did not depend on extruder + return; + int n = m_variant_combo->GetSelection(); + auto options = generate_extruder_options(); + m_variant_combo->SetOptions(options); + for (int i = 0; i < m_variant_combo->GetCount(); ++i) + { + auto flow_type = get_actual_nozzle_flow_type(i); + auto ext_type = get_actual_extruder_type(i); + bool connected = false; + auto r_nozzles = collect_nozzles(MAIN_EXTRUDER_ID, ext_type, flow_type, connected); + std::vector l_nozzles; + if (m_preset_bundle->get_printer_extruder_count() > 1) + l_nozzles = collect_nozzles(DEPUTY_EXTRUDER_ID, ext_type, flow_type, connected); + if (connected && r_nozzles.empty() && l_nozzles.empty()) + { + Button *btn = m_variant_combo->GetButton(i); + if (btn) + { + btn->SetGrayed(true); + } + } } + m_variant_combo->SetSelection(n < 0 || (unsigned int)n >= m_variant_combo->GetCount() ? 0 : n); + m_variant_combo->Enable(m_variant_combo->GetCount() > 1); + } + switch_excluder(extruder_id, reload); + if (m_variant_sizer) + { + wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *)m_extruder_switch : m_variant_combo; + m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && m_active_page && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder ")); + if (m_extruder_sync) + m_extruder_sync->Show(variant_ctrl->IsShown()); + show_wiki(); + GetParent()->Layout(); } } - m_variant_combo->SetSelection(n < 0 || (unsigned int) n >= m_variant_combo->GetCount() ? 0 : n); - m_variant_combo->Enable(m_variant_combo->GetCount() > 1); - } - switch_excluder(extruder_id, reload); - if (m_variant_sizer) { - wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo; - m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && m_active_page && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder ")); - if (m_extruder_sync) m_extruder_sync->Show(variant_ctrl->IsShown()); - show_wiki(); - GetParent()->Layout(); - } -} -void Tab::show_wiki() -{ - if (m_wiki_bmp && m_wiki_label) { - auto variants = m_config->option("filament_extruder_variant")->values; - bool has_direct = std::any_of(variants.begin(), variants.end(), [](const std::string &v) { return v.find("Direct Drive") != std::string::npos; }); - m_wiki_bmp->Show(has_direct); - m_wiki_label->Show(has_direct); - } -} + void Tab::show_wiki() + { + if (m_wiki_bmp && m_wiki_label) + { + auto variants = m_config->option("filament_extruder_variant")->values; + bool has_direct = std::any_of(variants.begin(), variants.end(), [](const std::string &v) + { return v.find("Direct Drive") != std::string::npos; }); + m_wiki_bmp->Show(has_direct); + m_wiki_label->Show(has_direct); + } + } -std::vector Tab::generate_extruder_options() -{ - std::vector options; - if (m_type == Preset::TYPE_FILAMENT) { - auto variants = m_config->option("filament_extruder_variant"); - for (auto &v : variants->values) { - std::string drive, nozzle; - size_t pos = v.rfind(' '); - if (pos != std::string::npos) { - drive = v.substr(0, pos); - nozzle = v.substr(pos + 1); - if (nozzle == "Flow") { - size_t pos2 = drive.rfind(' '); - if (pos2 != std::string ::npos) { - nozzle = drive.substr(pos2 + 1) + " " + nozzle; - drive = drive.substr(0, pos2); + std::vector Tab::generate_extruder_options() + { + std::vector options; + if (m_type == Preset::TYPE_FILAMENT) + { + auto variants = m_config->option("filament_extruder_variant"); + for (auto &v : variants->values) + { + std::string drive, nozzle; + size_t pos = v.rfind(' '); + if (pos != std::string::npos) + { + drive = v.substr(0, pos); + nozzle = v.substr(pos + 1); + if (nozzle == "Flow") + { + size_t pos2 = drive.rfind(' '); + if (pos2 != std::string ::npos) + { + nozzle = drive.substr(pos2 + 1) + " " + nozzle; + drive = drive.substr(0, pos2); + } + } } + options.push_back(wxString::Format(_L("%s: %s"), _L(drive), _L(nozzle))); } + return options; } - options.push_back(wxString::Format(_L("%s: %s"), _L(drive), _L(nozzle))); - } - return options; - } - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - auto nozzle_volumes_def = m_preset_bundle->project_config.def()->get("nozzle_volume_type"); - int extruder_nums = m_preset_bundle->get_printer_extruder_count(); + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + auto nozzle_volumes_def = m_preset_bundle->project_config.def()->get("nozzle_volume_type"); + int extruder_nums = m_preset_bundle->get_printer_extruder_count(); - if (!nozzle_volumes || extruder_nums <= 0) { - return options; - } + if (!nozzle_volumes || extruder_nums <= 0) + { + return options; + } - for (int i = 0; i < extruder_nums; ++i) { - wxString extruder_name = (i == 0) ? _L("Left") : _L("Right"); - NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]); + for (int i = 0; i < extruder_nums; ++i) + { + wxString extruder_name = (i == 0) ? _L("Left") : _L("Right"); + NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]); - if (volume_type == NozzleVolumeType::nvtHybrid) { - options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("Standard"))); - options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("High Flow"))); - } else { - wxString volume_name = get_nozzle_volume_type_name(volume_type); - options.push_back(wxString::Format(_L("%s: %s"), extruder_name, volume_name)); + if (volume_type == NozzleVolumeType::nvtHybrid) + { + options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("Standard"))); + options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("High Flow"))); + } + else + { + wxString volume_name = get_nozzle_volume_type_name(volume_type); + options.push_back(wxString::Format(_L("%s: %s"), extruder_name, volume_name)); + } + } + return options; } - } - return options; -} -NozzleVolumeType Tab::get_actual_nozzle_volume_type(int extruder_id) -{ - int extruder_count = m_preset_bundle->get_printer_extruder_count(); - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - if (extruder_count == 1) { - if (extruder_id < 0) - return NozzleVolumeType::nvtStandard; + NozzleVolumeType Tab::get_actual_nozzle_volume_type(int extruder_id) + { + int extruder_count = m_preset_bundle->get_printer_extruder_count(); + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + if (extruder_count == 1) + { + if (extruder_id < 0) + return NozzleVolumeType::nvtStandard; - return NozzleVolumeType(nozzle_volumes->values[extruder_id]); - } + return NozzleVolumeType(nozzle_volumes->values[extruder_id]); + } - if (extruder_id < 0 || extruder_id >= extruder_count) - return NozzleVolumeType::nvtStandard; + if (extruder_id < 0 || extruder_id >= extruder_count) + return NozzleVolumeType::nvtStandard; - if (m_actual_nozzle_volumes.size() != static_cast(extruder_count)) - m_actual_nozzle_volumes.resize(extruder_count, NozzleVolumeType::nvtStandard); + if (m_actual_nozzle_volumes.size() != static_cast(extruder_count)) + m_actual_nozzle_volumes.resize(extruder_count, NozzleVolumeType::nvtStandard); - return m_actual_nozzle_volumes[extruder_id]; -} + return m_actual_nozzle_volumes[extruder_id]; + } -bool Tab::get_extruder_sync_enable_state(int extruder_id) -{ - Preset& printer_preset = m_preset_bundle->printers.get_edited_preset(); - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - auto extruders = printer_preset.config.option("extruder_type"); - if (nozzle_volumes->values.size() < 2 || extruders->values.size() < 2) { - return false; - } + bool Tab::get_extruder_sync_enable_state(int extruder_id) + { + Preset &printer_preset = m_preset_bundle->printers.get_edited_preset(); + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + auto extruders = printer_preset.config.option("extruder_type"); + if (nozzle_volumes->values.size() < 2 || extruders->values.size() < 2) + { + return false; + } - NozzleVolumeType left_nozzle = NozzleVolumeType(nozzle_volumes->values[0]); - NozzleVolumeType right_nozzle = NozzleVolumeType(nozzle_volumes->values[1]); - ExtruderType left_extruder = ExtruderType(extruders->values[0]); - ExtruderType right_extruder = ExtruderType(extruders->values[1]); + NozzleVolumeType left_nozzle = NozzleVolumeType(nozzle_volumes->values[0]); + NozzleVolumeType right_nozzle = NozzleVolumeType(nozzle_volumes->values[1]); + ExtruderType left_extruder = ExtruderType(extruders->values[0]); + ExtruderType right_extruder = ExtruderType(extruders->values[1]); - if (left_extruder != right_extruder) { - return false; - } + if (left_extruder != right_extruder) + { + return false; + } - if (left_nozzle == right_nozzle) { - return true; - } + if (left_nozzle == right_nozzle) + { + return true; + } - if (left_nozzle != NozzleVolumeType::nvtHybrid && right_nozzle != NozzleVolumeType::nvtHybrid) { - return false; - } + if (left_nozzle != NozzleVolumeType::nvtHybrid && right_nozzle != NozzleVolumeType::nvtHybrid) + { + return false; + } - if (left_nozzle == NozzleVolumeType::nvtHybrid && right_nozzle == NozzleVolumeType::nvtHybrid) { - return true; - } + if (left_nozzle == NozzleVolumeType::nvtHybrid && right_nozzle == NozzleVolumeType::nvtHybrid) + { + return true; + } - // Hybrid rules - auto current_nozzle = get_actual_nozzle_volume_type(extruder_id); - if (left_nozzle != NozzleVolumeType::nvtHybrid) { - if (extruder_id == 0) { - return true; - } - if (extruder_id == 1 && current_nozzle == left_nozzle) { - return true; - } - return false; - } - if (right_nozzle != NozzleVolumeType::nvtHybrid) { - if (extruder_id == 1) { - return true; + // Hybrid rules + auto current_nozzle = get_actual_nozzle_volume_type(extruder_id); + if (left_nozzle != NozzleVolumeType::nvtHybrid) + { + if (extruder_id == 0) + { + return true; + } + if (extruder_id == 1 && current_nozzle == left_nozzle) + { + return true; + } + return false; + } + if (right_nozzle != NozzleVolumeType::nvtHybrid) + { + if (extruder_id == 1) + { + return true; + } + if (extruder_id == 0 && current_nozzle == right_nozzle) + { + return true; + } + return false; + } + return false; } - if (extruder_id == 0 && current_nozzle == right_nozzle) { - return true; + + void Tab::switch_excluder(int extruder_id, bool reload) + { + Preset &printer_preset = m_preset_bundle->printers.get_edited_preset(); + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + auto extruders = printer_preset.config.option("extruder_type"); + if (m_extruder_switch) + { + int current_extruder = get_current_active_extruder(); + bool sync_enable = get_extruder_sync_enable_state(current_extruder); + m_extruder_sync->Enable(m_extruder_switch->IsThisEnabled() && sync_enable); + m_extruder_sync->Show(); + if (m_type != Preset::TYPE_PRINTER) + { + if (extruder_id == -1) + extruder_id = current_extruder; + else if (extruder_id != current_extruder) + return; + } + } + else if (m_variant_combo) + { + int current_variant = m_variant_combo->GetSelection(); + update_nozzle_status_display(); + if (extruder_id == -1) + extruder_id = current_variant; + else if (extruder_id != current_variant) + // assert(false) + return; + } + auto get_index_for_extruder = + [this, &extruders, &nozzle_volumes, variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]](int extruder_id, int stride = 1) + { + return m_config->get_index_for_extruder(extruder_id + 1, variant_keys.first, + ExtruderType(extruders->values[extruder_id]), get_actual_nozzle_volume_type(extruder_id), variant_keys.second, stride); + }; + auto index = m_variant_combo ? extruder_id : get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id); + if (index < 0) + return; + if (m_extruder_switch) + m_extruder_switch->SetClientData(reinterpret_cast(static_cast(index))); + if (m_variant_combo) + m_variant_combo->SetClientData(reinterpret_cast(static_cast(index))); + wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *)m_extruder_switch : m_variant_combo; + for (auto page : m_pages) + { + bool is_extruder = false; + if (m_type == Preset::TYPE_PRINTER) + { + if (page->title().StartsWith("Extruder")) + { + int extruder_id2 = std::atoi(page->title().Mid(9).ToUTF8()) - 1; + if (extruder_id >= 0 && extruder_id2 != extruder_id) + continue; + if (extruder_id2 > 0) + index = get_index_for_extruder(extruder_id2); + is_extruder = true; + } + else if (page->title().StartsWith("Speed limitation")) + { + index = get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id, 2); + } + } + page->m_opt_id_map.clear(); + for (auto group : page->m_optgroups) + { + for (auto &opt : group->opt_map()) + { + auto iter = std::find(printer_extruder_options.begin(), printer_extruder_options.end(), opt.second.first); + if (iter != printer_extruder_options.end()) + { + page->m_opt_id_map.insert({opt.first, opt.first}); + continue; + } + + if (opt.second.second >= 0) + { + const_cast(opt.second.second) = index; + page->m_opt_id_map.insert({opt.second.first + "#" + std::to_string(index), opt.first}); + group->draw_multi_extruder = !is_extruder && variant_ctrl->IsThisEnabled() && m_type == Preset::TYPE_FILAMENT; + } + } + } + } + if (reload) + { + reload_config(); + update_changed_ui(); + toggle_options(); + if (m_active_page) + m_active_page->update_visibility(m_mode, true); + m_page_view->GetParent()->Layout(); + } } - return false; - } - return false; -} -void Tab::switch_excluder(int extruder_id, bool reload) -{ - Preset & printer_preset = m_preset_bundle->printers.get_edited_preset(); - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - auto extruders = printer_preset.config.option("extruder_type"); - if (m_extruder_switch) { - int current_extruder = get_current_active_extruder(); - bool sync_enable = get_extruder_sync_enable_state(current_extruder); - m_extruder_sync->Enable(m_extruder_switch->IsThisEnabled() && sync_enable); - m_extruder_sync->Show(); - if (m_type != Preset::TYPE_PRINTER) { - if (extruder_id == -1) - extruder_id = current_extruder; - else if (extruder_id != current_extruder) - return; - } - } else if (m_variant_combo) { - int current_variant = m_variant_combo->GetSelection(); - update_nozzle_status_display(); - if (extruder_id == -1) - extruder_id = current_variant; - else if (extruder_id != current_variant) - // assert(false) - return; - } - auto get_index_for_extruder = - [this, &extruders, &nozzle_volumes, variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]](int extruder_id, int stride = 1) { - return m_config->get_index_for_extruder(extruder_id + 1, variant_keys.first, - ExtruderType(extruders->values[extruder_id]), get_actual_nozzle_volume_type(extruder_id), variant_keys.second, stride); - }; - auto index = m_variant_combo ? extruder_id : get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id); - if (index < 0) - return; - if (m_extruder_switch) m_extruder_switch->SetClientData(reinterpret_cast(static_cast(index))); - if (m_variant_combo) m_variant_combo->SetClientData(reinterpret_cast(static_cast(index))); - wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo; - for (auto page : m_pages) { - bool is_extruder = false; - if (m_type == Preset::TYPE_PRINTER) { - if (page->title().StartsWith("Extruder")) { - int extruder_id2 = std::atoi(page->title().Mid(9).ToUTF8()) - 1; - if (extruder_id >= 0 && extruder_id2 != extruder_id) + void Tab::sync_excluder() + { + Preset &printer_preset = m_preset_bundle->printers.get_edited_preset(); + auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); + auto extruders = printer_preset.config.option("extruder_type"); + auto get_index_for_extruder = + [this, &extruders, &nozzle_volumes, variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]](int extruder_id, NozzleVolumeType nozzle_type) + { + return m_config->get_index_for_extruder(extruder_id + 1, variant_keys.first, + ExtruderType(extruders->values[extruder_id]), nozzle_type, variant_keys.second); + }; + int active_index = get_current_active_extruder(); + auto active_nozzle = get_actual_nozzle_volume_type(active_index); + int from_index = get_index_for_extruder(active_index, active_nozzle); + int dest_index = get_index_for_extruder(1 - active_index, active_nozzle); + auto from_str = std::to_string(from_index); + auto dest_str = std::to_string(dest_index); + auto dirty_options = m_presets->current_dirty_options(true); + DynamicConfig config_origin, config_to_apply; + for (int i = 0; i < dirty_options.size(); ++i) + { + auto &opt = dirty_options[i]; + auto n = opt.find('#'); + if (n == std::string::npos) continue; - if (extruder_id2 > 0) - index = get_index_for_extruder(extruder_id2); - is_extruder = true; - } else if (page->title().StartsWith("Speed limitation")) { - index = get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id, 2); - } - } - page->m_opt_id_map.clear(); - for (auto group : page->m_optgroups) { - for (auto &opt : group->opt_map()) { - auto iter = std::find(printer_extruder_options.begin(), printer_extruder_options.end(), opt.second.first); - if (iter != printer_extruder_options.end()) { - page->m_opt_id_map.insert({opt.first, opt.first}); + auto field = m_active_page->get_field(opt.substr(0, n), from_index + 256); + auto line = m_active_page->get_line(opt.substr(0, n), from_index + 256); + if (field == nullptr || line == nullptr) continue; + ++n; + bool dirty = opt.substr(n) == from_str; + while (i + 1 < dirty_options.size() && dirty_options[i + 1].compare(0, n, opt, 0, n) == 0) + { + dirty |= dirty_options[i + 1].substr(n) == from_str; + ++i; } - - if (opt.second.second >= 0) { - const_cast(opt.second.second) = index; - page->m_opt_id_map.insert({opt.second.first + "#" + std::to_string(index), opt.first}); - group->draw_multi_extruder = !is_extruder && variant_ctrl->IsThisEnabled() && m_type == Preset::TYPE_FILAMENT; + if (dirty) + { + auto key = opt.substr(0, n - 1); + auto option = dynamic_cast(m_config->option(key)); + auto option2 = dynamic_cast(option->clone()); + option2->set_at(option, dest_index, from_index); + if (*option == *option2) + { + delete option2; + continue; + } + config_origin.set_key_value(key, option->clone()); + config_to_apply.set_key_value(key, option2); } } - } - } - if (reload) { - reload_config(); - update_changed_ui(); - toggle_options(); - if (m_active_page) - m_active_page->update_visibility(m_mode, true); - m_page_view->GetParent()->Layout(); - } -} + if (config_to_apply.empty()) + { + MessageDialog md(wxGetApp().plater(), _L("No modifications need to be copied."), _L("Copy paramters"), wxICON_INFORMATION | wxOK); + md.ShowModal(); + return; + } -void Tab::sync_excluder() -{ - Preset & printer_preset = m_preset_bundle->printers.get_edited_preset(); - auto nozzle_volumes = m_preset_bundle->project_config.option("nozzle_volume_type"); - auto extruders = printer_preset.config.option("extruder_type"); - auto get_index_for_extruder = - [this, &extruders, &nozzle_volumes, variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]](int extruder_id, NozzleVolumeType nozzle_type) { - return m_config->get_index_for_extruder(extruder_id + 1, variant_keys.first, - ExtruderType(extruders->values[extruder_id]), nozzle_type, variant_keys.second); - }; - int active_index = get_current_active_extruder(); - auto active_nozzle = get_actual_nozzle_volume_type(active_index); - int from_index = get_index_for_extruder(active_index, active_nozzle); - int dest_index = get_index_for_extruder(1 - active_index, active_nozzle); - auto from_str = std::to_string(from_index); - auto dest_str = std::to_string(dest_index); - auto dirty_options = m_presets->current_dirty_options(true); - DynamicConfig config_origin, config_to_apply; - for (int i = 0; i < dirty_options.size(); ++i) { - auto &opt = dirty_options[i]; - auto n= opt.find('#'); - if (n == std::string::npos) - continue; - auto field = m_active_page->get_field(opt.substr(0, n), from_index + 256); - auto line = m_active_page->get_line(opt.substr(0, n), from_index + 256); - if (field == nullptr || line == nullptr) - continue; - ++n; - bool dirty = opt.substr(n) == from_str; - while (i + 1 < dirty_options.size() && dirty_options[i + 1].compare(0, n, opt, 0, n) == 0) { - dirty |= dirty_options[i + 1].substr(n) == from_str; - ++i; - } - if (dirty) { - auto key = opt.substr(0, n - 1); - auto option = dynamic_cast(m_config->option(key)); - auto option2 = dynamic_cast(option->clone()); - option2->set_at(option, dest_index, from_index); - if (*option == *option2) { - delete option2; - continue; - } - config_origin.set_key_value(key, option->clone()); - config_to_apply.set_key_value(key, option2); + wxString title = active_index == 0 ? _L("Modify paramters of right nozzle") : _L("Modify paramters of left nozzle"); + wxString header = active_index == 0 ? _L("Do you want to modify the following parameters of the right nozzle to that of the left nozzle?") : _L("Do you want to modify the following parameters of the left nozzle to that of the right nozzle?"); + UnsavedChangesDialog dlg(title, header, &config_origin, from_index, dest_index, active_index == 0, active_nozzle); + dlg.ShowModal(); + if (dlg.transfer_changes()) + { + m_config->apply(config_to_apply); + auto &applying_keys = const_cast(m_config_manipulation.applying_keys()); + if (m_type > Preset::TYPE_COUNT) + applying_keys = config_to_apply.keys(); + reload_config(); + if (m_type > Preset::TYPE_COUNT) + applying_keys.clear(); + update_changed_ui(); + update(); + if (m_active_page) + m_active_page->update_visibility(m_mode, true); + m_page_view->GetParent()->Layout(); + } } - } - if (config_to_apply.empty()) { - MessageDialog md(wxGetApp().plater(), _L("No modifications need to be copied."), _L("Copy paramters"), wxICON_INFORMATION | wxOK); - md.ShowModal(); - return; - } - - wxString title = active_index == 0 ? _L("Modify paramters of right nozzle") : _L("Modify paramters of left nozzle"); - wxString header = active_index == 0 ? _L("Do you want to modify the following parameters of the right nozzle to that of the left nozzle?") : - _L("Do you want to modify the following parameters of the left nozzle to that of the right nozzle?"); - UnsavedChangesDialog dlg(title, header, &config_origin, from_index, dest_index, active_index == 0, active_nozzle); - dlg.ShowModal(); - if (dlg.transfer_changes()) { - m_config->apply(config_to_apply); - auto &applying_keys = const_cast(m_config_manipulation.applying_keys()); - if (m_type > Preset::TYPE_COUNT) - applying_keys = config_to_apply.keys(); - reload_config(); - if (m_type > Preset::TYPE_COUNT) - applying_keys.clear(); - update_changed_ui(); - update(); - if (m_active_page) - m_active_page->update_visibility(m_mode, true); - m_page_view->GetParent()->Layout(); - } -} -NozzleFlowType Tab::get_actual_nozzle_flow_type(int selection) -{ - if (!m_variant_combo || selection < 0) { - return NozzleFlowType::S_FLOW; - } - if (m_type == Preset::TYPE_FILAMENT) { - auto variants = m_config->option("filament_extruder_variant"); - if (selection < static_cast(variants->values.size())) { - const std::string& variant = variants->values[selection]; - if (variant.find("High Flow") != std::string::npos) { - return NozzleFlowType::H_FLOW; - } else if (variant.find("Standard") != std::string::npos) { + NozzleFlowType Tab::get_actual_nozzle_flow_type(int selection) + { + if (!m_variant_combo || selection < 0) + { return NozzleFlowType::S_FLOW; } + if (m_type == Preset::TYPE_FILAMENT) + { + auto variants = m_config->option("filament_extruder_variant"); + if (selection < static_cast(variants->values.size())) + { + const std::string &variant = variants->values[selection]; + if (variant.find("High Flow") != std::string::npos) + { + return NozzleFlowType::H_FLOW; + } + else if (variant.find("Standard") != std::string::npos) + { + return NozzleFlowType::S_FLOW; + } + return NozzleFlowType::S_FLOW; + } + } return NozzleFlowType::S_FLOW; } - } - return NozzleFlowType::S_FLOW; -} -ExtruderType Tab::get_actual_extruder_type(int selection) -{ - return ExtruderType::etDirectDrive; -} + ExtruderType Tab::get_actual_extruder_type(int selection) + { + return ExtruderType::etDirectDrive; + } -void Tab::update_nozzle_status_display() -{ - if (!m_nozzle_status_sizer) return; - Freeze(); - m_nozzle_status_sizer->Clear(true); - - const Preset ¤t_printer = m_preset_bundle->printers.get_selected_preset(); - auto extruder_max_nozzle_count = current_printer.config.option("extruder_max_nozzle_count"); - bool has_multiple_nozzle = std::any_of(extruder_max_nozzle_count->values.begin(), extruder_max_nozzle_count->values.end(), [](int i) { return i > 1; }); - if (!has_multiple_nozzle) { - Thaw(); - return; - } + void Tab::update_nozzle_status_display() + { + if (!m_nozzle_status_sizer) + return; + Freeze(); + m_nozzle_status_sizer->Clear(true); - int selection = m_variant_combo ? m_variant_combo->GetSelection() : -1; - NozzleFlowType flow_type = get_actual_nozzle_flow_type(selection); - ExtruderType extruder_type = get_actual_extruder_type(selection); - - bool connected = false; - auto r_nozzles = collect_nozzles(MAIN_EXTRUDER_ID, extruder_type, flow_type, connected); - std::vector l_nozzles; - if (m_preset_bundle->get_printer_extruder_count() > 1) - l_nozzles = collect_nozzles(DEPUTY_EXTRUDER_ID, extruder_type, flow_type, connected); - if (!connected) { - Thaw(); - return; - } - if (r_nozzles.empty() && l_nozzles.empty()) { - auto bmp = ScalableBitmap(this, "warning", 16); - auto warning_icon = new wxStaticBitmap(this, wxID_ANY, bmp.bmp(), wxDefaultPosition, wxDefaultSize, 0); - m_nozzle_status_sizer->Add(warning_icon, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); - wxStaticText *reminder_text = new wxStaticText(this, wxID_ANY, _L("No available nozzles for current preset")); - reminder_text->SetFont(Label::Body_13); - reminder_text->SetForegroundColour(m_modified_label_clr); - m_nozzle_status_sizer->Add(reminder_text, 1, wxALIGN_CENTER_VERTICAL); - - Thaw(); - return; - } + const Preset ¤t_printer = m_preset_bundle->printers.get_selected_preset(); + auto extruder_max_nozzle_count = current_printer.config.option("extruder_max_nozzle_count"); + bool has_multiple_nozzle = std::any_of(extruder_max_nozzle_count->values.begin(), extruder_max_nozzle_count->values.end(), [](int i) + { return i > 1; }); + if (!has_multiple_nozzle) + { + Thaw(); + return; + } - wxStaticText *reminder_text = new wxStaticText(this, wxID_ANY, _L("Available nozzles for current preset: ")); - reminder_text->SetFont(Label::Body_13); - reminder_text->SetForegroundColour(wxColour("#00AE42")); - m_nozzle_status_sizer->Add(reminder_text, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 10); - - auto create_nozzle_button = [this](const wxString &name) { - Button *btn = new Button(); - btn->Create(this, name, "", wxBORDER_NONE); - btn->SetMinSize(wxSize(24, 24)); - btn->SetFont(wxGetApp().bold_font()); - StateColor bg_color(wxColour("#E6F7ED")); - btn->SetBackgroundColor(bg_color); - StateColor fg_color(wxColour("#00AE42")); - btn->SetTextColor(fg_color); - btn->SetCornerRadius(6); - btn->Enable(false); - - m_nozzle_status_sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); - }; - - if (!l_nozzles.empty()) { - create_nozzle_button("L"); - } - if (!r_nozzles.empty() && !l_nozzles.empty()) { - wxPanel *line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(4, 16)); - line->SetBackgroundStyle(wxBG_STYLE_PAINT); - line->Bind(wxEVT_PAINT, [line](wxPaintEvent &) { + int selection = m_variant_combo ? m_variant_combo->GetSelection() : -1; + NozzleFlowType flow_type = get_actual_nozzle_flow_type(selection); + ExtruderType extruder_type = get_actual_extruder_type(selection); + + bool connected = false; + auto r_nozzles = collect_nozzles(MAIN_EXTRUDER_ID, extruder_type, flow_type, connected); + std::vector l_nozzles; + if (m_preset_bundle->get_printer_extruder_count() > 1) + l_nozzles = collect_nozzles(DEPUTY_EXTRUDER_ID, extruder_type, flow_type, connected); + if (!connected) + { + Thaw(); + return; + } + if (r_nozzles.empty() && l_nozzles.empty()) + { + auto bmp = ScalableBitmap(this, "warning", 16); + auto warning_icon = new wxStaticBitmap(this, wxID_ANY, bmp.bmp(), wxDefaultPosition, wxDefaultSize, 0); + m_nozzle_status_sizer->Add(warning_icon, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + wxStaticText *reminder_text = new wxStaticText(this, wxID_ANY, _L("No available nozzles for current preset")); + reminder_text->SetFont(Label::Body_13); + reminder_text->SetForegroundColour(m_modified_label_clr); + m_nozzle_status_sizer->Add(reminder_text, 1, wxALIGN_CENTER_VERTICAL); + + Thaw(); + return; + } + + wxStaticText *reminder_text = new wxStaticText(this, wxID_ANY, _L("Available nozzles for current preset: ")); + reminder_text->SetFont(Label::Body_13); + reminder_text->SetForegroundColour(wxColour("#00AE42")); + m_nozzle_status_sizer->Add(reminder_text, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 10); + + auto create_nozzle_button = [this](const wxString &name) + { + Button *btn = new Button(); + btn->Create(this, name, "", wxBORDER_NONE); + btn->SetMinSize(wxSize(24, 24)); + btn->SetFont(wxGetApp().bold_font()); + StateColor bg_color(wxColour("#E6F7ED")); + btn->SetBackgroundColor(bg_color); + StateColor fg_color(wxColour("#00AE42")); + btn->SetTextColor(fg_color); + btn->SetCornerRadius(6); + btn->Enable(false); + + m_nozzle_status_sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + }; + + if (!l_nozzles.empty()) + { + create_nozzle_button("L"); + } + if (!r_nozzles.empty() && !l_nozzles.empty()) + { + wxPanel *line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(4, 16)); + line->SetBackgroundStyle(wxBG_STYLE_PAINT); + line->Bind(wxEVT_PAINT, [line](wxPaintEvent &) + { wxPaintDC dc(line); wxColour color = wxGetApp().dark_mode() ? wxColour("#6B6B6B") : wxColour("#C8C8C8"); dc.SetPen(wxPen(color, 1)); int x = line->GetSize().GetWidth() / 2; - dc.DrawLine(x, 0, x, line->GetSize().GetHeight()); - }); - m_nozzle_status_sizer->Add(line, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); - } - if (!r_nozzles.empty()) { - for (const auto nozzle : r_nozzles) { - wxString name = nozzle.IsOnRack() ? "R" + std::to_string(nozzle.GetNozzleId() + 1) : "R"; - create_nozzle_button(name); + dc.DrawLine(x, 0, x, line->GetSize().GetHeight()); }); + m_nozzle_status_sizer->Add(line, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + if (!r_nozzles.empty()) + { + for (const auto nozzle : r_nozzles) + { + wxString name = nozzle.IsOnRack() ? "R" + std::to_string(nozzle.GetNozzleId() + 1) : "R"; + create_nozzle_button(name); + } + } + Thaw(); } - } - Thaw(); -} -std::vector Tab::collect_nozzles(int extruder_id, ExtruderType ext_type, NozzleFlowType flow_type, bool& connected) -{ - MachineObject *obj = get_current_machine_object(); - if (!obj) { - connected = false; - return {}; - } + std::vector Tab::collect_nozzles(int extruder_id, ExtruderType ext_type, NozzleFlowType flow_type, bool &connected) + { + MachineObject *obj = get_current_machine_object(); + if (!obj) + { + connected = false; + return {}; + } - std::string printer_type = obj->get_show_printer_type(); - Preset& printer_preset = m_preset_bundle->printers.get_edited_preset(); - auto preset_printer_type = printer_preset.get_current_printer_type(m_preset_bundle); - if (printer_type != preset_printer_type) { - connected = false; - return {}; - } + std::string printer_type = obj->get_show_printer_type(); + Preset &printer_preset = m_preset_bundle->printers.get_edited_preset(); + auto preset_printer_type = printer_preset.get_current_printer_type(m_preset_bundle); + if (printer_type != preset_printer_type) + { + connected = false; + return {}; + } - DevNozzleSystem *nozzle_sys = obj->GetNozzleSystem(); - DevExtderSystem *extder_sys = obj->GetExtderSystem(); - if (!nozzle_sys || !extder_sys) { - connected = false; - return {}; - } + DevNozzleSystem *nozzle_sys = obj->GetNozzleSystem(); + DevExtderSystem *extder_sys = obj->GetExtderSystem(); + if (!nozzle_sys || !extder_sys) + { + connected = false; + return {}; + } - connected = true; - auto extder_opt = extder_sys->GetExtderById(extruder_id); - if (!extder_opt.has_value()) { - BOOST_LOG_TRIVIAL(info) << "No extruder found for extruder id " << extruder_id; - return {}; - } + connected = true; + auto extder_opt = extder_sys->GetExtderById(extruder_id); + if (!extder_opt.has_value()) + { + BOOST_LOG_TRIVIAL(info) << "No extruder found for extruder id " << extruder_id; + return {}; + } - auto extder_type = ExtruderType::etDirectDrive; - if (extder_type != ext_type) { - return {}; - } - return nozzle_sys->CollectNozzles(extruder_id, flow_type); -} + auto extder_type = ExtruderType::etDirectDrive; + if (extder_type != ext_type) + { + return {}; + } + return nozzle_sys->CollectNozzles(extruder_id, flow_type); + } -void Tab::compatible_widget_reload(PresetDependencies &deps) -{ - Field* field = this->get_field(deps.key_condition); - if (!field) - return; + void Tab::compatible_widget_reload(PresetDependencies &deps) + { + Field *field = this->get_field(deps.key_condition); + if (!field) + return; - bool has_any = ! m_config->option(deps.key_list)->values.empty(); - has_any ? deps.btn->Enable() : deps.btn->Disable(); - deps.checkbox->SetValue(! has_any); + bool has_any = !m_config->option(deps.key_list)->values.empty(); + has_any ? deps.btn->Enable() : deps.btn->Disable(); + deps.checkbox->SetValue(!has_any); - field->toggle(! has_any); -} + field->toggle(!has_any); + } -void Tab::set_tooltips_text() -{ - // --- Tooltip text for reset buttons (for whole options group) - // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - //m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system (or default) values " - // "for the current option group")); - //m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " - // "to the system (or default) values for the current option group.\n" - // "Click to reset all settings for current option group to the system (or default) values.")); - //m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system (or non default) preset.")); - //m_ttg_non_system = &m_ttg_white_bullet_ns; - // Text to be shown on the "Undo user changes" button next to each input field. - //m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " - // "preset for the current option group.")); - //m_ttg_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " - // "the last saved preset for the current option group.\n" - // "Click to reset all settings for the current option group to the last saved preset.")); - - // --- Tooltip text for reset buttons (for each option in group) - // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - //m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system (or default) value.")); - m_tt_value_unlock = _(L("Click to reset current value and attach to the global value.")); - // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); - //m_tt_non_system = &m_ttg_white_bullet_ns; - // Text to be shown on the "Undo user changes" button next to each input field. - //m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the value is the same as in the last saved preset.")); - m_tt_value_revert = _(L("Click to drop current modify and reset to saved value.")); -} - -//BBS: GUI refactor -Page::Page(wxWindow* parent, const wxString& title, int iconID, wxPanel* tab_owner) : - m_tab_owner(tab_owner), - m_parent(parent), - m_title(title), - m_iconID(iconID) -{ - m_vsizer = (wxBoxSizer*)parent->GetSizer(); - m_page_title = NULL; - m_item_color = &wxGetApp().get_label_clr_default(); -} + void Tab::set_tooltips_text() + { + // --- Tooltip text for reset buttons (for whole options group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + // m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system (or default) values " + // "for the current option group")); + // m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " + // "to the system (or default) values for the current option group.\n" + // "Click to reset all settings for current option group to the system (or default) values.")); + // m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system (or non default) preset.")); + // m_ttg_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + // m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " + // "preset for the current option group.")); + // m_ttg_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " + // "the last saved preset for the current option group.\n" + // "Click to reset all settings for the current option group to the last saved preset.")); + + // --- Tooltip text for reset buttons (for each option in group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + // m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system (or default) value.")); + m_tt_value_unlock = _(L("Click to reset current value and attach to the global value.")); + // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); + // m_tt_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + // m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the value is the same as in the last saved preset.")); + m_tt_value_revert = _(L("Click to drop current modify and reset to saved value.")); + } + + // BBS: GUI refactor + Page::Page(wxWindow *parent, const wxString &title, int iconID, wxPanel *tab_owner) : m_tab_owner(tab_owner), + m_parent(parent), + m_title(title), + m_iconID(iconID) + { + m_vsizer = (wxBoxSizer *)parent->GetSizer(); + m_page_title = NULL; + m_item_color = &wxGetApp().get_label_clr_default(); + } -void Page::reload_config() -{ - for (auto group : m_optgroups) - group->reload_config(); -} + void Page::reload_config() + { + for (auto group : m_optgroups) + group->reload_config(); + } -void Page::update_visibility(ConfigOptionMode mode, bool update_contolls_visibility) -{ - bool ret_val = false; + void Page::update_visibility(ConfigOptionMode mode, bool update_contolls_visibility) + { + bool ret_val = false; #if HIDE_FIRST_SPLIT_LINE - // BBS: no line spliter for first group - bool first = true; + // BBS: no line spliter for first group + bool first = true; #endif - for (auto group : m_optgroups) { - ret_val = (update_contolls_visibility ? - group->update_visibility(mode) : // update visibility for all controlls in group - group->is_visible(mode) // just detect visibility for the group - ) || ret_val; + for (auto group : m_optgroups) + { + ret_val = (update_contolls_visibility ? group->update_visibility(mode) : // update visibility for all controlls in group + group->is_visible(mode) // just detect visibility for the group + ) || + ret_val; #if HIDE_FIRST_SPLIT_LINE - // BBS: no line spliter for first group - if (update_contolls_visibility && ret_val && first) { - if (group->stb) group->stb->Hide(); - first = false; - } + // BBS: no line spliter for first group + if (update_contolls_visibility && ret_val && first) + { + if (group->stb) + group->stb->Hide(); + first = false; + } #endif - } + } - m_show = ret_val; + m_show = ret_val; #ifdef __WXMSW__ - if (!m_show) return; - // BBS: fix field control position - wxTheApp->CallAfter([this]() { + if (!m_show) + return; + // BBS: fix field control position + wxTheApp->CallAfter([this]() + { for (auto group : m_optgroups) { if (group->custom_ctrl) group->custom_ctrl->fixup_items_positions(); - } - }); + } }); #endif -} + } -void Page::activate(ConfigOptionMode mode, std::function throw_if_canceled) -{ + void Page::activate(ConfigOptionMode mode, std::function throw_if_canceled) + { #if 0 // BBS: page title if (m_page_title == NULL) { m_page_title = new Label(Label::Head_18, _(m_title), m_parent); @@ -7351,491 +7987,511 @@ void Page::activate(ConfigOptionMode mode, std::function throw_if_cancel m_vsizer->AddSpacer(20); } #else - //m_vsizer->AddSpacer(10); + // m_vsizer->AddSpacer(10); #endif #if HIDE_FIRST_SPLIT_LINE - // BBS: no line spliter for first group - bool first = true; + // BBS: no line spliter for first group + bool first = true; #endif - for (auto group : m_optgroups) { - if (!group->activate(throw_if_canceled)) - continue; - m_vsizer->Add(group->sizer, 0, wxEXPAND | (group->is_legend_line() ? (wxLEFT|wxTOP) : wxALL), 10); - group->update_visibility(mode); + for (auto group : m_optgroups) + { + if (!group->activate(throw_if_canceled)) + continue; + m_vsizer->Add(group->sizer, 0, wxEXPAND | (group->is_legend_line() ? (wxLEFT | wxTOP) : wxALL), 10); + group->update_visibility(mode); #if HIDE_FIRST_SPLIT_LINE - if (first) group->stb->Hide(); - first = false; + if (first) + group->stb->Hide(); + first = false; #endif - group->reload_config(); - throw_if_canceled(); - } + group->reload_config(); + throw_if_canceled(); + } #ifdef __WXMSW__ - // BBS: fix field control position - wxTheApp->CallAfter([this]() { + // BBS: fix field control position + wxTheApp->CallAfter([this]() + { for (auto group : m_optgroups) { if (group->custom_ctrl) group->custom_ctrl->fixup_items_positions(); - } - }); + } }); #endif -} + } -void Page::clear() -{ - for (auto group : m_optgroups) - group->clear(); - m_page_title = NULL; -} + void Page::clear() + { + for (auto group : m_optgroups) + group->clear(); + m_page_title = NULL; + } -void Page::msw_rescale() -{ - for (auto group : m_optgroups) - group->msw_rescale(); -} + void Page::msw_rescale() + { + for (auto group : m_optgroups) + group->msw_rescale(); + } -void Page::sys_color_changed() -{ - for (auto group : m_optgroups) - group->sys_color_changed(); -} + void Page::sys_color_changed() + { + for (auto group : m_optgroups) + group->sys_color_changed(); + } -void Page::refresh() -{ - for (auto group : m_optgroups) - group->refresh(); -} + void Page::refresh() + { + for (auto group : m_optgroups) + group->refresh(); + } -Field *Page::get_field(const t_config_option_key &opt_key, int opt_index /*= -1*/) const -{ - Field *field = nullptr; - auto opt_key2 = opt_key; - if (opt_index >= 256) { - auto iter = m_opt_id_map.find(opt_key + '#' + std::to_string(opt_index - 256)); - if (iter != m_opt_id_map.end()) - opt_key2 = iter->second; - } - for (auto opt : m_optgroups) { - field = opt->get_fieldc(opt_key2, opt_index); - if (field != nullptr) return field; - } - return field; -} + Field *Page::get_field(const t_config_option_key &opt_key, int opt_index /*= -1*/) const + { + Field *field = nullptr; + auto opt_key2 = opt_key; + if (opt_index >= 256) + { + auto iter = m_opt_id_map.find(opt_key + '#' + std::to_string(opt_index - 256)); + if (iter != m_opt_id_map.end()) + opt_key2 = iter->second; + } + for (auto opt : m_optgroups) + { + field = opt->get_fieldc(opt_key2, opt_index); + if (field != nullptr) + return field; + } + return field; + } -Line *Page::get_line(const t_config_option_key &opt_key, int opt_index) -{ - Line *line = nullptr; - auto opt_key2 = opt_key; - if (opt_index >= 256) { - auto iter = m_opt_id_map.find(opt_key + '#' + std::to_string(opt_index - 256)); - if (iter != m_opt_id_map.end()) - opt_key2 = iter->second; - } else if (opt_index >= 0) { - assert(opt_key.find('#') == std::string::npos); - opt_key2 = opt_key + '#' + std::to_string(opt_index); - } - for (auto opt : m_optgroups) { - line = opt->get_line(opt_key2); - if (line != nullptr) return line; - } - return line; -} + Line *Page::get_line(const t_config_option_key &opt_key, int opt_index) + { + Line *line = nullptr; + auto opt_key2 = opt_key; + if (opt_index >= 256) + { + auto iter = m_opt_id_map.find(opt_key + '#' + std::to_string(opt_index - 256)); + if (iter != m_opt_id_map.end()) + opt_key2 = iter->second; + } + else if (opt_index >= 0) + { + assert(opt_key.find('#') == std::string::npos); + opt_key2 = opt_key + '#' + std::to_string(opt_index); + } + for (auto opt : m_optgroups) + { + line = opt->get_line(opt_key2); + if (line != nullptr) + return line; + } + return line; + } -bool Page::set_value(const t_config_option_key &opt_key, const boost::any &value) -{ - bool changed = false; - for(auto optgroup: m_optgroups) { - if (optgroup->set_value(opt_key, value)) - changed = true ; - } - return changed; -} + bool Page::set_value(const t_config_option_key &opt_key, const boost::any &value) + { + bool changed = false; + for (auto optgroup : m_optgroups) + { + if (optgroup->set_value(opt_key, value)) + changed = true; + } + return changed; + } -// package Slic3r::GUI::Tab::Page; -ConfigOptionsGroupShp Page::new_optgroup(const wxString &title, const wxString &icon, int noncommon_label_width /*= -1*/, bool is_extruder_og /* false */) -{ - //! config_ have to be "right" - ConfigOptionsGroupShp optgroup = is_extruder_og ? std::make_shared(m_parent, title, m_config, true) - : std::make_shared(m_parent, title, icon, m_config, true); - optgroup->split_multi_line = this->m_split_multi_line; - optgroup->option_label_at_right = this->m_option_label_at_right; - if (noncommon_label_width >= 0) - optgroup->label_width = noncommon_label_width; - -//BBS: GUI refactor -/*#ifdef __WXOSX__ - auto tab = parent()->GetParent()->GetParent();// GetParent()->GetParent(); -#else - auto tab = parent()->GetParent();// GetParent(); -#endif*/ - auto tab = m_tab_owner; - optgroup->set_config_category_and_type(m_title, static_cast(tab)->type()); - optgroup->m_on_change = [tab](t_config_option_key opt_key, boost::any value) { - //! This function will be called from OptionGroup. - //! Using of CallAfter is redundant. - //! And in some cases it causes update() function to be recalled again -//! wxTheApp->CallAfter([this, opt_key, value]() { - static_cast(tab)->update_dirty(); - static_cast(tab)->on_value_change(opt_key, value); -//! }); - }; - - optgroup->m_get_initial_config = [tab]() { - DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset().config; - return config; - }; - - optgroup->m_get_sys_config = [tab]() { - DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset_parent()->config; - return config; - }; - - optgroup->have_sys_config = [tab]() { - return static_cast(tab)->m_presets->get_selected_preset_parent() != nullptr; - }; - - optgroup->rescale_extra_column_item = [](wxWindow* win) { - auto *ctrl = dynamic_cast(win); - if (ctrl == nullptr) - return; + // package Slic3r::GUI::Tab::Page; + ConfigOptionsGroupShp Page::new_optgroup(const wxString &title, const wxString &icon, int noncommon_label_width /*= -1*/, bool is_extruder_og /* false */) + { + //! config_ have to be "right" + ConfigOptionsGroupShp optgroup = is_extruder_og ? std::make_shared(m_parent, title, m_config, true) + : std::make_shared(m_parent, title, icon, m_config, true); + optgroup->split_multi_line = this->m_split_multi_line; + optgroup->option_label_at_right = this->m_option_label_at_right; + if (noncommon_label_width >= 0) + optgroup->label_width = noncommon_label_width; + + // BBS: GUI refactor + /*#ifdef __WXOSX__ + auto tab = parent()->GetParent()->GetParent();// GetParent()->GetParent(); + #else + auto tab = parent()->GetParent();// GetParent(); + #endif*/ + auto tab = m_tab_owner; + optgroup->set_config_category_and_type(m_title, static_cast(tab)->type()); + optgroup->m_on_change = [tab](t_config_option_key opt_key, boost::any value) + { + //! This function will be called from OptionGroup. + //! Using of CallAfter is redundant. + //! And in some cases it causes update() function to be recalled again + //! wxTheApp->CallAfter([this, opt_key, value]() { + static_cast(tab)->update_dirty(); + static_cast(tab)->on_value_change(opt_key, value); + //! }); + }; - ctrl->SetBitmap(reinterpret_cast(ctrl->GetClientData())->bmp()); - }; + optgroup->m_get_initial_config = [tab]() + { + DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset().config; + return config; + }; - m_optgroups.push_back(optgroup); + optgroup->m_get_sys_config = [tab]() + { + DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset_parent()->config; + return config; + }; - return optgroup; -} + optgroup->have_sys_config = [tab]() + { + return static_cast(tab)->m_presets->get_selected_preset_parent() != nullptr; + }; + + optgroup->rescale_extra_column_item = [](wxWindow *win) + { + auto *ctrl = dynamic_cast(win); + if (ctrl == nullptr) + return; + + ctrl->SetBitmap(reinterpret_cast(ctrl->GetClientData())->bmp()); + }; + + m_optgroups.push_back(optgroup); -const ConfigOptionsGroupShp Page::get_optgroup(const wxString& title) const -{ - for (ConfigOptionsGroupShp optgroup : m_optgroups) { - if (optgroup->title == title) return optgroup; - } + } + + const ConfigOptionsGroupShp Page::get_optgroup(const wxString &title) const + { + for (ConfigOptionsGroupShp optgroup : m_optgroups) + { + if (optgroup->title == title) + return optgroup; + } - return nullptr; -} + return nullptr; + } -void TabSLAMaterial::build() -{ - //m_presets = &m_preset_bundle->sla_materials; - //load_initial_data(); - - //auto page = add_options_page(L("Material"), ""); - - //auto optgroup = page->new_optgroup(L("Material")); - //optgroup->append_single_option_line("material_colour"); - //optgroup->append_single_option_line("bottle_cost"); - //optgroup->append_single_option_line("bottle_volume"); - //optgroup->append_single_option_line("bottle_weight"); - //optgroup->append_single_option_line("material_density"); - - //optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) - //{ - // if (opt_key == "material_colour") { - // update_dirty(); - // on_value_change(opt_key, value); - // return; - // } - - // DynamicPrintConfig new_conf = *m_config; - - // if (opt_key == "bottle_volume") { - // double new_bottle_weight = boost::any_cast(value)*(new_conf.option("material_density")->getFloat() / 1000); - // new_conf.set_key_value("bottle_weight", new ConfigOptionFloat(new_bottle_weight)); - // } - // if (opt_key == "bottle_weight") { - // double new_bottle_volume = boost::any_cast(value)/new_conf.option("material_density")->getFloat() * 1000; - // new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume)); - // } - // if (opt_key == "material_density") { - // double new_bottle_volume = new_conf.option("bottle_weight")->getFloat() / boost::any_cast(value) * 1000; - // new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume)); - // } - - // load_config(new_conf); - - // update_dirty(); - - // // BBS - // // Change of any from those options influences for an update of "Sliced Info" - // //wxGetApp().sidebar().Layout(); - //}; - - //optgroup = page->new_optgroup(L("Layers")); - //optgroup->append_single_option_line("initial_layer_height"); - - //optgroup = page->new_optgroup(L("Exposure")); - //optgroup->append_single_option_line("exposure_time"); - //optgroup->append_single_option_line("initial_exposure_time"); - - //optgroup = page->new_optgroup(L("Corrections")); - //auto line = Line{ m_config->def()->get("material_correction")->full_label, "" }; - //for (auto& axis : { "X", "Y", "Z" }) { - // auto opt = optgroup->get_option(std::string("material_correction_") + char(std::tolower(axis[0]))); - // opt.opt.label = axis; - // line.append_option(opt); - //} - - //optgroup->append_line(line); - - //page = add_options_page(L("Dependencies"), "wrench.png"); - //optgroup = page->new_optgroup(L("Profile dependencies")); - - //create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { - // return compatible_widget_create(parent, m_compatible_printers); - //}); - // - //Option option = optgroup->get_option("compatible_printers_condition"); - //option.opt.full_width = true; - //optgroup->append_single_option_line(option); + void TabSLAMaterial::build() + { + // m_presets = &m_preset_bundle->sla_materials; + // load_initial_data(); + + // auto page = add_options_page(L("Material"), ""); + + // auto optgroup = page->new_optgroup(L("Material")); + // optgroup->append_single_option_line("material_colour"); + // optgroup->append_single_option_line("bottle_cost"); + // optgroup->append_single_option_line("bottle_volume"); + // optgroup->append_single_option_line("bottle_weight"); + // optgroup->append_single_option_line("material_density"); + + // optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) + //{ + // if (opt_key == "material_colour") { + // update_dirty(); + // on_value_change(opt_key, value); + // return; + // } + + // DynamicPrintConfig new_conf = *m_config; + + // if (opt_key == "bottle_volume") { + // double new_bottle_weight = boost::any_cast(value)*(new_conf.option("material_density")->getFloat() / 1000); + // new_conf.set_key_value("bottle_weight", new ConfigOptionFloat(new_bottle_weight)); + // } + // if (opt_key == "bottle_weight") { + // double new_bottle_volume = boost::any_cast(value)/new_conf.option("material_density")->getFloat() * 1000; + // new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume)); + // } + // if (opt_key == "material_density") { + // double new_bottle_volume = new_conf.option("bottle_weight")->getFloat() / boost::any_cast(value) * 1000; + // new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume)); + // } + + // load_config(new_conf); + + // update_dirty(); + + // // BBS + // // Change of any from those options influences for an update of "Sliced Info" + // //wxGetApp().sidebar().Layout(); + //}; - //create_line_with_widget(optgroup.get(), "compatible_prints", "", [this](wxWindow* parent) { - // return compatible_widget_create(parent, m_compatible_prints); - //}); + // optgroup = page->new_optgroup(L("Layers")); + // optgroup->append_single_option_line("initial_layer_height"); - //option = optgroup->get_option("compatible_prints_condition"); - //option.opt.full_width = true; - //optgroup->append_single_option_line(option); + // optgroup = page->new_optgroup(L("Exposure")); + // optgroup->append_single_option_line("exposure_time"); + // optgroup->append_single_option_line("initial_exposure_time"); - //build_preset_description_line(optgroup.get()); + // optgroup = page->new_optgroup(L("Corrections")); + // auto line = Line{ m_config->def()->get("material_correction")->full_label, "" }; + // for (auto& axis : { "X", "Y", "Z" }) { + // auto opt = optgroup->get_option(std::string("material_correction_") + char(std::tolower(axis[0]))); + // opt.opt.label = axis; + // line.append_option(opt); + // } - //page = add_options_page(L("Material printing profile"), "printer.png"); - //optgroup = page->new_optgroup(L("Material printing profile")); - //option = optgroup->get_option("material_print_speed"); - //optgroup->append_single_option_line(option); -} + // optgroup->append_line(line); -// Reload current config (aka presets->edited_preset->config) into the UI fields. -void TabSLAMaterial::reload_config() -{ - this->compatible_widget_reload(m_compatible_printers); - this->compatible_widget_reload(m_compatible_prints); - Tab::reload_config(); -} + // page = add_options_page(L("Dependencies"), "wrench.png"); + // optgroup = page->new_optgroup(L("Profile dependencies")); -void TabSLAMaterial::toggle_options() -{ - const Preset ¤t_printer = m_preset_bundle->printers.get_edited_preset(); - std::string model = current_printer.config.opt_string("printer_model"); - m_config_manipulation.toggle_field("material_print_speed", model != "SL1"); -} + // create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { + // return compatible_widget_create(parent, m_compatible_printers); + // }); + // + // Option option = optgroup->get_option("compatible_printers_condition"); + // option.opt.full_width = true; + // optgroup->append_single_option_line(option); -void TabSLAMaterial::update() -{ - if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; - - update_description_lines(); - Layout(); - -// #ys_FIXME. Just a template for this function -// m_update_cnt++; -// ! something to update -// m_update_cnt--; -// -// if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); -} - -void TabSLAPrint::build() -{ - m_presets = &m_preset_bundle->sla_prints; - load_initial_data(); - -// auto page = add_options_page(L("Layers and perimeters"), "layers"); -// -// auto optgroup = page->new_optgroup(L("Layers")); -// optgroup->append_single_option_line("layer_height"); -// optgroup->append_single_option_line("faded_layers"); -// -// page = add_options_page(L("Supports"), "support"/*"sla_supports"*/); -// optgroup = page->new_optgroup(L("Supports")); -// optgroup->append_single_option_line("supports_enable"); -// -// optgroup = page->new_optgroup(L("Support head")); -// optgroup->append_single_option_line("support_head_front_diameter"); -// optgroup->append_single_option_line("support_head_penetration"); -// optgroup->append_single_option_line("support_head_width"); -// -// optgroup = page->new_optgroup(L("Support pillar")); -// optgroup->append_single_option_line("support_pillar_diameter"); -// optgroup->append_single_option_line("support_small_pillar_diameter_percent"); -// optgroup->append_single_option_line("support_max_bridges_on_pillar"); -// -// optgroup->append_single_option_line("support_pillar_connection_mode"); -// optgroup->append_single_option_line("support_buildplate_only"); -// // TODO: This parameter is not used at the moment. -// // optgroup->append_single_option_line("support_pillar_widening_factor"); -// optgroup->append_single_option_line("support_base_diameter"); -// optgroup->append_single_option_line("support_base_height"); -// optgroup->append_single_option_line("support_base_safety_distance"); -// -// // Mirrored parameter from Pad page for toggling elevation on the same page -// optgroup->append_single_option_line("support_object_elevation"); -// -// Line line{ "", "" }; -// line.full_width = 1; -// line.widget = [this](wxWindow* parent) { -// return description_line_widget(parent, &m_support_object_elevation_description_line); -// }; -// optgroup->append_line(line); -// -// optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); -// optgroup->append_single_option_line("support_critical_angle"); -// optgroup->append_single_option_line("support_max_bridge_length"); -// optgroup->append_single_option_line("support_max_pillar_link_distance"); -// -// optgroup = page->new_optgroup(L("Automatic generation")); -// optgroup->append_single_option_line("support_points_density_relative"); -// optgroup->append_single_option_line("support_points_minimal_distance"); -// -// page = add_options_page(L("Pad"), ""); -// optgroup = page->new_optgroup(L("Pad")); -// optgroup->append_single_option_line("pad_enable"); -// optgroup->append_single_option_line("pad_wall_thickness"); -// optgroup->append_single_option_line("pad_wall_height"); -// optgroup->append_single_option_line("pad_brim_size"); -// optgroup->append_single_option_line("pad_max_merge_distance"); -// // TODO: Disabling this parameter for the beta release -//// optgroup->append_single_option_line("pad_edge_radius"); -// optgroup->append_single_option_line("pad_wall_slope"); -// -// optgroup->append_single_option_line("pad_around_object"); -// optgroup->append_single_option_line("pad_around_object_everywhere"); -// optgroup->append_single_option_line("pad_object_gap"); -// optgroup->append_single_option_line("pad_object_connector_stride"); -// optgroup->append_single_option_line("pad_object_connector_width"); -// optgroup->append_single_option_line("pad_object_connector_penetration"); -// -// page = add_options_page(L("Hollowing"), "hollowing"); -// optgroup = page->new_optgroup(L("Hollowing")); -// optgroup->append_single_option_line("hollowing_enable"); -// optgroup->append_single_option_line("hollowing_min_thickness"); -// optgroup->append_single_option_line("hollowing_quality"); -// optgroup->append_single_option_line("hollowing_closing_distance"); -// -// page = add_options_page(L("Advanced"), "advanced"); -// optgroup = page->new_optgroup(L("Slicing")); -// optgroup->append_single_option_line("slice_closing_radius"); -// optgroup->append_single_option_line("slicing_mode"); -// -// page = add_options_page(L("Output options"), "output+page_white"); -// optgroup = page->new_optgroup(L("Output file")); -// Option option = optgroup->get_option("filename_format"); -// option.opt.full_width = true; -// optgroup->append_single_option_line(option); -// -// page = add_options_page(L("Dependencies"), "advanced"); -// optgroup = page->new_optgroup(L("Profile dependencies")); -// -// create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { -// return compatible_widget_create(parent, m_compatible_printers); -// }); -// -// option = optgroup->get_option("compatible_printers_condition"); -// option.opt.full_width = true; -// optgroup->append_single_option_line(option); -// -// build_preset_description_line(optgroup.get()); -} - -// Reload current config (aka presets->edited_preset->config) into the UI fields. -void TabSLAPrint::reload_config() -{ - this->compatible_widget_reload(m_compatible_printers); - Tab::reload_config(); -} + // create_line_with_widget(optgroup.get(), "compatible_prints", "", [this](wxWindow* parent) { + // return compatible_widget_create(parent, m_compatible_prints); + // }); -void TabSLAPrint::update_description_lines() -{ - Tab::update_description_lines(); - - //if (m_active_page && m_active_page->title() == "Supports") - //{ - // bool is_visible = m_config->def()->get("support_object_elevation")->mode <= m_mode; - // if (m_support_object_elevation_description_line) - // { - // m_support_object_elevation_description_line->Show(is_visible); - // if (is_visible) - // { - // bool elev = !m_config->opt_bool("pad_enable") || !m_config->opt_bool("pad_around_object"); - // m_support_object_elevation_description_line->SetText(elev ? "" : - // from_u8((boost::format(_u8L("\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" - // "To enable \"%1%\", please switch off \"%2%\"")) - // % _L("Object elevation") % _L("Pad around object") % _L("Pad")).str())); - // } - // } - //} -} - -void TabSLAPrint::toggle_options() -{ - if (m_active_page) - m_config_manipulation.toggle_print_sla_options(m_config); -} + // option = optgroup->get_option("compatible_prints_condition"); + // option.opt.full_width = true; + // optgroup->append_single_option_line(option); -void TabSLAPrint::update() -{ - if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; + // build_preset_description_line(optgroup.get()); - m_update_cnt++; + // page = add_options_page(L("Material printing profile"), "printer.png"); + // optgroup = page->new_optgroup(L("Material printing profile")); + // option = optgroup->get_option("material_print_speed"); + // optgroup->append_single_option_line(option); + } + + // Reload current config (aka presets->edited_preset->config) into the UI fields. + void TabSLAMaterial::reload_config() + { + this->compatible_widget_reload(m_compatible_printers); + this->compatible_widget_reload(m_compatible_prints); + Tab::reload_config(); + } - m_config_manipulation.update_print_sla_config(m_config, true); + void TabSLAMaterial::toggle_options() + { + const Preset ¤t_printer = m_preset_bundle->printers.get_edited_preset(); + std::string model = current_printer.config.opt_string("printer_model"); + m_config_manipulation.toggle_field("material_print_speed", model != "SL1"); + } - update_description_lines(); - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); + void TabSLAMaterial::update() + { + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) + return; - m_update_cnt--; + update_description_lines(); + Layout(); - if (m_update_cnt == 0) { - toggle_options(); + // #ys_FIXME. Just a template for this function + // m_update_cnt++; + // ! something to update + // m_update_cnt--; + // + // if (m_update_cnt == 0) + wxGetApp().mainframe->on_config_changed(m_config); + } - // update() could be called during undo/redo execution - // Update of objectList can cause a crash in this case (because m_objects doesn't match ObjectList) - if (!wxGetApp().plater()->inside_snapshot_capture()) - wxGetApp().obj_list()->update_and_show_object_settings_item(); + void TabSLAPrint::build() + { + m_presets = &m_preset_bundle->sla_prints; + load_initial_data(); + + // auto page = add_options_page(L("Layers and perimeters"), "layers"); + // + // auto optgroup = page->new_optgroup(L("Layers")); + // optgroup->append_single_option_line("layer_height"); + // optgroup->append_single_option_line("faded_layers"); + // + // page = add_options_page(L("Supports"), "support"/*"sla_supports"*/); + // optgroup = page->new_optgroup(L("Supports")); + // optgroup->append_single_option_line("supports_enable"); + // + // optgroup = page->new_optgroup(L("Support head")); + // optgroup->append_single_option_line("support_head_front_diameter"); + // optgroup->append_single_option_line("support_head_penetration"); + // optgroup->append_single_option_line("support_head_width"); + // + // optgroup = page->new_optgroup(L("Support pillar")); + // optgroup->append_single_option_line("support_pillar_diameter"); + // optgroup->append_single_option_line("support_small_pillar_diameter_percent"); + // optgroup->append_single_option_line("support_max_bridges_on_pillar"); + // + // optgroup->append_single_option_line("support_pillar_connection_mode"); + // optgroup->append_single_option_line("support_buildplate_only"); + // // TODO: This parameter is not used at the moment. + // // optgroup->append_single_option_line("support_pillar_widening_factor"); + // optgroup->append_single_option_line("support_base_diameter"); + // optgroup->append_single_option_line("support_base_height"); + // optgroup->append_single_option_line("support_base_safety_distance"); + // + // // Mirrored parameter from Pad page for toggling elevation on the same page + // optgroup->append_single_option_line("support_object_elevation"); + // + // Line line{ "", "" }; + // line.full_width = 1; + // line.widget = [this](wxWindow* parent) { + // return description_line_widget(parent, &m_support_object_elevation_description_line); + // }; + // optgroup->append_line(line); + // + // optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); + // optgroup->append_single_option_line("support_critical_angle"); + // optgroup->append_single_option_line("support_max_bridge_length"); + // optgroup->append_single_option_line("support_max_pillar_link_distance"); + // + // optgroup = page->new_optgroup(L("Automatic generation")); + // optgroup->append_single_option_line("support_points_density_relative"); + // optgroup->append_single_option_line("support_points_minimal_distance"); + // + // page = add_options_page(L("Pad"), ""); + // optgroup = page->new_optgroup(L("Pad")); + // optgroup->append_single_option_line("pad_enable"); + // optgroup->append_single_option_line("pad_wall_thickness"); + // optgroup->append_single_option_line("pad_wall_height"); + // optgroup->append_single_option_line("pad_brim_size"); + // optgroup->append_single_option_line("pad_max_merge_distance"); + // // TODO: Disabling this parameter for the beta release + //// optgroup->append_single_option_line("pad_edge_radius"); + // optgroup->append_single_option_line("pad_wall_slope"); + // + // optgroup->append_single_option_line("pad_around_object"); + // optgroup->append_single_option_line("pad_around_object_everywhere"); + // optgroup->append_single_option_line("pad_object_gap"); + // optgroup->append_single_option_line("pad_object_connector_stride"); + // optgroup->append_single_option_line("pad_object_connector_width"); + // optgroup->append_single_option_line("pad_object_connector_penetration"); + // + // page = add_options_page(L("Hollowing"), "hollowing"); + // optgroup = page->new_optgroup(L("Hollowing")); + // optgroup->append_single_option_line("hollowing_enable"); + // optgroup->append_single_option_line("hollowing_min_thickness"); + // optgroup->append_single_option_line("hollowing_quality"); + // optgroup->append_single_option_line("hollowing_closing_distance"); + // + // page = add_options_page(L("Advanced"), "advanced"); + // optgroup = page->new_optgroup(L("Slicing")); + // optgroup->append_single_option_line("slice_closing_radius"); + // optgroup->append_single_option_line("slicing_mode"); + // + // page = add_options_page(L("Output options"), "output+page_white"); + // optgroup = page->new_optgroup(L("Output file")); + // Option option = optgroup->get_option("filename_format"); + // option.opt.full_width = true; + // optgroup->append_single_option_line(option); + // + // page = add_options_page(L("Dependencies"), "advanced"); + // optgroup = page->new_optgroup(L("Profile dependencies")); + // + // create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { + // return compatible_widget_create(parent, m_compatible_printers); + // }); + // + // option = optgroup->get_option("compatible_printers_condition"); + // option.opt.full_width = true; + // optgroup->append_single_option_line(option); + // + // build_preset_description_line(optgroup.get()); + } - wxGetApp().mainframe->on_config_changed(m_config); - } -} + // Reload current config (aka presets->edited_preset->config) into the UI fields. + void TabSLAPrint::reload_config() + { + this->compatible_widget_reload(m_compatible_printers); + Tab::reload_config(); + } -void TabSLAPrint::clear_pages() -{ - Tab::clear_pages(); + void TabSLAPrint::update_description_lines() + { + Tab::update_description_lines(); + + // if (m_active_page && m_active_page->title() == "Supports") + //{ + // bool is_visible = m_config->def()->get("support_object_elevation")->mode <= m_mode; + // if (m_support_object_elevation_description_line) + // { + // m_support_object_elevation_description_line->Show(is_visible); + // if (is_visible) + // { + // bool elev = !m_config->opt_bool("pad_enable") || !m_config->opt_bool("pad_around_object"); + // m_support_object_elevation_description_line->SetText(elev ? "" : + // from_u8((boost::format(_u8L("\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" + // "To enable \"%1%\", please switch off \"%2%\"")) + // % _L("Object elevation") % _L("Pad around object") % _L("Pad")).str())); + // } + // } + // } + } + + void TabSLAPrint::toggle_options() + { + if (m_active_page) + m_config_manipulation.toggle_print_sla_options(m_config); + } + + void TabSLAPrint::update() + { + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) + return; - m_support_object_elevation_description_line = nullptr; -} + m_update_cnt++; -ConfigManipulation Tab::get_config_manipulation() -{ - auto load_config = [this]() - { - update_dirty(); - // Initialize UI components with the config values. - reload_config(); - update(); - }; + m_config_manipulation.update_print_sla_config(m_config, true); + + update_description_lines(); + // BBS: GUI refactor + // Layout(); + m_parent->Layout(); + + m_update_cnt--; + + if (m_update_cnt == 0) + { + toggle_options(); + + // update() could be called during undo/redo execution + // Update of objectList can cause a crash in this case (because m_objects doesn't match ObjectList) + if (!wxGetApp().plater()->inside_snapshot_capture()) + wxGetApp().obj_list()->update_and_show_object_settings_item(); + + wxGetApp().mainframe->on_config_changed(m_config); + } + } - auto cb_toggle_field = [this](const t_config_option_key& opt_key, bool toggle, int opt_index) { - return toggle_option(opt_key, toggle, opt_index >= 0 ? opt_index + 256 : opt_index); - }; + void TabSLAPrint::clear_pages() + { + Tab::clear_pages(); + + m_support_object_elevation_description_line = nullptr; + } + + ConfigManipulation Tab::get_config_manipulation() + { + auto load_config = [this]() + { + update_dirty(); + // Initialize UI components with the config values. + reload_config(); + update(); + }; - auto cb_toggle_line = [this](const t_config_option_key &opt_key, bool toggle, int opt_index) { - return toggle_line(opt_key, toggle, opt_index >= 0 ? opt_index + 256 : opt_index); - }; + auto cb_toggle_field = [this](const t_config_option_key &opt_key, bool toggle, int opt_index) + { + return toggle_option(opt_key, toggle, opt_index >= 0 ? opt_index + 256 : opt_index); + }; - auto cb_value_change = [this](const std::string& opt_key, const boost::any& value) { - return on_value_change(opt_key, value); - }; + auto cb_toggle_line = [this](const t_config_option_key &opt_key, bool toggle, int opt_index) + { + return toggle_line(opt_key, toggle, opt_index >= 0 ? opt_index + 256 : opt_index); + }; - return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this); -} + auto cb_value_change = [this](const std::string &opt_key, const boost::any &value) + { + return on_value_change(opt_key, value); + }; + return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this); + } -} // GUI + } // GUI } // Slic3r