Browse Source

Merge branch 'main' into main

Jelle Spijker 2 years ago
parent
commit
0f5cfba1bc

+ 8 - 1
.github/workflows/conan-package.yml

@@ -75,6 +75,13 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v3
 
+      - name: Cache Conan data
+        id: cache-conan
+        uses: actions/cache@v3
+        with:
+          path: ~/.conan
+          key: ${{ runner.os }}-conan
+
       - name: Setup Python and pip
         uses: actions/setup-python@v4
         with:
@@ -101,7 +108,7 @@ jobs:
           sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
 
       - name: Create the default Conan profile
-        run: conan profile new default --detect
+        run: conan profile new default --detect --force
 
       - name: Get Conan configuration
         run: conan config install https://github.com/Ultimaker/conan-config.git

+ 8 - 1
.github/workflows/update-translation.yml

@@ -27,6 +27,13 @@ jobs:
             -   name: Checkout
                 uses: actions/checkout@v3
 
+            -   name: Cache Conan data
+                id: cache-conan
+                uses: actions/cache@v3
+                with:
+                    path: ~/.conan
+                    key: ${{ runner.os }}-conan
+
             -   name: Setup Python and pip
                 uses: actions/setup-python@v4
                 with:
@@ -53,7 +60,7 @@ jobs:
                     sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
 
             -   name: Create the default Conan profile
-                run: conan profile new default --detect
+                run: conan profile new default --detect --force
 
             -   name: Get Conan configuration
                 run: conan config install https://github.com/Ultimaker/conan-config.git

+ 1 - 1
packaging/AppImage/AppRun

@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 
 scriptdir=$(dirname $0)
 

+ 44 - 34
plugins/3MFReader/ThreeMFWorkspaceReader.py

@@ -43,7 +43,7 @@ from .WorkspaceDialog import WorkspaceDialog
 i18n_catalog = i18nCatalog("cura")
 
 
-_ignored_machine_network_metadata = {
+_ignored_machine_network_metadata: Set[str] = {
     "um_cloud_cluster_id",
     "um_network_key",
     "um_linked_to_account",
@@ -55,7 +55,7 @@ _ignored_machine_network_metadata = {
     "capabilities",
     "octoprint_api_key",
     "is_abstract_machine"
-}  # type: Set[str]
+}
 
 
 class ContainerInfo:
@@ -69,41 +69,41 @@ class ContainerInfo:
 
 class QualityChangesInfo:
     def __init__(self) -> None:
-        self.name = None
+        self.name: Optional[str] = None
         self.global_info = None
-        self.extruder_info_dict = {} # type: Dict[str, ContainerInfo]
+        self.extruder_info_dict: Dict[str, ContainerInfo] = {}
 
 
 class MachineInfo:
     def __init__(self) -> None:
-        self.container_id = None
-        self.name = None
-        self.definition_id = None
+        self.container_id: Optional[str] = None
+        self.name: Optional[str] = None
+        self.definition_id: Optional[str] = None
 
-        self.metadata_dict = {}  # type: Dict[str, str]
+        self.metadata_dict: Dict[str, str] = {}
 
-        self.quality_type = None
-        self.intent_category = None
-        self.custom_quality_name = None
-        self.quality_changes_info = None
-        self.variant_info = None
+        self.quality_type: Optional[str] = None
+        self.intent_category: Optional[str] = None
+        self.custom_quality_name: Optional[str] = None
+        self.quality_changes_info: Optional[QualityChangesInfo] = None
+        self.variant_info: Optional[ContainerInfo] = None
 
-        self.definition_changes_info = None
-        self.user_changes_info = None
+        self.definition_changes_info: Optional[ContainerInfo] = None
+        self.user_changes_info: Optional[ContainerInfo] = None
 
-        self.extruder_info_dict = {} # type: Dict[str, ExtruderInfo]
+        self.extruder_info_dict: Dict[str, str] = {}
 
 
 class ExtruderInfo:
     def __init__(self) -> None:
         self.position = None
         self.enabled = True
-        self.variant_info = None
-        self.root_material_id = None
+        self.variant_info: Optional[ContainerInfo] = None
+        self.root_material_id: Optional[str] = None
 
-        self.definition_changes_info = None
-        self.user_changes_info = None
-        self.intent_info = None
+        self.definition_changes_info: Optional[ContainerInfo] = None
+        self.user_changes_info: Optional[ContainerInfo] = None
+        self.intent_info: Optional[ContainerInfo] = None
 
 
 class ThreeMFWorkspaceReader(WorkspaceReader):
@@ -131,14 +131,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         #  - variant
         self._ignored_instance_container_types = {"quality", "variant"}
 
-        self._resolve_strategies = {} # type: Dict[str, str]
+        self._resolve_strategies: Dict[str, str] = {}
 
-        self._id_mapping = {} # type: Dict[str, str]
+        self._id_mapping: Dict[str, str] = {}
 
         # In Cura 2.5 and 2.6, the empty profiles used to have those long names
         self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]}
 
-        self._old_new_materials = {} # type: Dict[str, str]
+        self._old_new_materials: Dict[str, str] = {}
         self._machine_info = None
 
     def _clearState(self):
@@ -461,11 +461,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
 
         materials_in_extruders_dict = {}  # Which material is in which extruder
 
-        # if the global stack is found, we check if there are conflicts in the extruder stacks
+        # If the global stack is found, we check if there are conflicts in the extruder stacks
         for extruder_stack_file in extruder_stack_files:
             serialized = archive.open(extruder_stack_file).read().decode("utf-8")
+
+            not_upgraded_parser = ConfigParser(interpolation=None)
+            not_upgraded_parser.read_string(serialized)
+
             serialized = ExtruderStack._updateSerialized(serialized, extruder_stack_file)
-            parser = ConfigParser(interpolation = None)
+            parser = ConfigParser(interpolation=None)
             parser.read_string(serialized)
 
             # The check should be done for the extruder stack that's associated with the existing global stack,
@@ -497,19 +501,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
                 extruder_info.user_changes_info = instance_container_info_dict[user_changes_id]
             self._machine_info.extruder_info_dict[position] = extruder_info
 
+            intent_container_id = parser["containers"][str(_ContainerIndexes.Intent)]
+
             intent_id = parser["containers"][str(_ContainerIndexes.Intent)]
             if intent_id not in ("empty", "empty_intent"):
-                extruder_info.intent_info = instance_container_info_dict[intent_id]
+                if intent_container_id in instance_container_info_dict:
+                    extruder_info.intent_info = instance_container_info_dict[intent_id]
+                else:
+                    # It can happen that an intent has been renamed. In that case, we should still use the old
+                    # name, since we used that to generate the instance_container_info_dict keys. 
+                    extruder_info.intent_info = instance_container_info_dict[not_upgraded_parser["containers"][str(_ContainerIndexes.Intent)]]
 
             if not machine_conflict and containers_found_dict["machine"] and global_stack:
                 if int(position) >= len(global_stack.extruderList):
                     continue
 
                 existing_extruder_stack = global_stack.extruderList[int(position)]
-                # check if there are any changes at all in any of the container stacks.
+                # Check if there are any changes at all in any of the container stacks.
                 id_list = self._getContainerIdListFromSerialized(serialized)
                 for index, container_id in enumerate(id_list):
-                    # take into account the old empty container IDs
+                    # Take into account the old empty container IDs
                     container_id = self._old_empty_profile_id_dict.get(container_id, container_id)
                     if existing_extruder_stack.getContainer(index).getId() != container_id:
                         machine_conflict = True
@@ -740,7 +751,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
             # quality_changes file. If that's the case, take the extruder count into account when creating the machine
             # or else the extruderList will return only the first extruder, leading to missing non-global settings in
             # the other extruders.
-            machine_extruder_count = self._getMachineExtruderCount()  # type: Optional[int]
+            machine_extruder_count: Optional[int] = self._getMachineExtruderCount()
             global_stack = CuraStackBuilder.createMachine(machine_name, self._machine_info.definition_id, machine_extruder_count)
             if global_stack:  # Only switch if creating the machine was successful.
                 extruder_stack_dict = {str(position): extruder for position, extruder in enumerate(global_stack.extruderList)}
@@ -867,7 +878,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
 
     @staticmethod
     def _loadMetadata(file_name: str) -> Dict[str, Dict[str, Any]]:
-        result = dict()  # type: Dict[str, Dict[str, Any]]
+        result: Dict[str, Dict[str, Any]] = dict()
         try:
             archive = zipfile.ZipFile(file_name, "r")
         except zipfile.BadZipFile:
@@ -879,7 +890,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
 
         metadata_files = [name for name in archive.namelist() if name.endswith("plugin_metadata.json")]
 
-
         for metadata_file in metadata_files:
             try:
                 plugin_id = metadata_file.split("/")[0]
@@ -920,7 +930,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
                 quality_changes_name = self._container_registry.uniqueName(quality_changes_name)
                 for position, container_info in container_info_dict.items():
                     extruder_stack = None
-                    intent_category = None  # type: Optional[str]
+                    intent_category: Optional[str] = None
                     if position is not None:
                         try:
                             extruder_stack = global_stack.extruderList[int(position)]
@@ -1161,7 +1171,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
             root_material_id = self._old_new_materials.get(root_material_id, root_material_id)
 
             material_node = machine_node.variants[extruder_stack.variant.getName()].materials[root_material_id]
-            extruder_stack.material = material_node.container  # type: InstanceContainer
+            extruder_stack.material = material_node.container
 
     def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
         # Clear all first

+ 8 - 7
plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml

@@ -303,18 +303,17 @@ Item
 
                     Component.onCompleted:
                     {
-                        update()
+                        updateModel();
                     }
 
-                    function update()
+                    function updateModel()
                     {
-                        clear()
-                        for (var i = 1; i <= Cura.MachineManager.activeMachine.maxExtruderCount; i++)
-                        {
+                        clear();
+                        for (var i = 1; i <= Cura.MachineManager.activeMachine.maxExtruderCount; i ++) {
                             // Use String as value. JavaScript only has Number. PropertyProvider.setPropertyValue()
                             // takes a QVariant as value, and Number gets translated into a float. This will cause problem
                             // for integer settings such as "Number of Extruders".
-                            append({ text: String(i), value: String(i) })
+                            append({ text: String(i), value: String(i) });
                         }
                     }
                 }
@@ -322,7 +321,9 @@ Item
                 Connections
                 {
                     target: Cura.MachineManager
-                    function onGlobalContainerChanged() { extruderCountModel.update() }
+                    function onGlobalContainerChanged() {
+                        extruderCountModel.updateModel();
+                    }
                 }
             }
 

+ 1 - 1
plugins/PostProcessingPlugin/scripts/PauseAtHeight.py

@@ -46,7 +46,7 @@ class PauseAtHeight(Script):
                 "pause_layer":
                 {
                     "label": "Pause Layer",
-                    "description": "Enter the Number of the LAST layer you want to finish prior to the pause (from the Cura preview).",
+                    "description": "Enter the Number of the LAST layer you want to finish prior to the pause. Note that 0 is the first layer printed.",
                     "type": "int",
                     "value": "math.floor((pause_height - 0.27) / 0.1) + 1",
                     "minimum_value": "0",

+ 1 - 1
plugins/USBPrinting/AutoDetectBaudJob.py

@@ -53,7 +53,7 @@ class AutoDetectBaudJob(Job):
                     try:
                         serial = Serial(str(self._serial_port), baud_rate, timeout = read_timeout, writeTimeout = write_timeout)
                     except SerialException:
-                        Logger.logException("w", "Unable to create serial")
+                        Logger.warning(f"Unable to create serial connection to {serial} with baud rate {baud_rate}")
                         continue
                 else:
                     # We already have a serial connection, just change the baud rate.

+ 2 - 0
plugins/VersionUpgrade/VersionUpgrade52to53/__init__.py

@@ -10,6 +10,7 @@ if TYPE_CHECKING:
 
 upgrade = VersionUpgrade52to53.VersionUpgrade52to53()
 
+
 def getMetaData() -> Dict[str, Any]:
     return {
         "version_upgrade": {
@@ -21,6 +22,7 @@ def getMetaData() -> Dict[str, Any]:
             ("quality_changes", 4000020):    ("quality_changes", 4000021,    upgrade.upgradeInstanceContainer),
             ("quality", 4000020):            ("quality", 4000021,            upgrade.upgradeInstanceContainer),
             ("user", 4000020):               ("user", 4000021,               upgrade.upgradeInstanceContainer),
+            ("intent", 4000020):               ("intent", 4000021,               upgrade.upgradeInstanceContainer),
         },
         "sources": {
             "preferences": {

+ 37 - 42
requirements.txt

@@ -1,46 +1,41 @@
 ### Direct requirements for Uranium and libCharon ###
-PyQt6-sip==13.2.1 \
-    --hash=sha256:b7bce59900b2e0a04f70246de2ccf79ee7933036b6b9183cf039b62eeae2b858 \
-    --hash=sha256:8b52d42e42e6e9f934ac7528cd154ac0210a532bb33fa1edfb4a8bbfb73ff88b \
-    --hash=sha256:0314d011633bc697e99f3f9897b484720e81a5f4ba0eaa5f05c5811e2e74ea53 \
-    --hash=sha256:226e9e349aa16dc1132f106ca01fa99cf7cb8e59daee29304c2fea5fa33212ec
-PyQt6==6.2.3 \
-    --hash=sha256:a9bfcac198fe4b703706f809bb686c7cef5f60a7c802fc145c6b57929c7a6a34 \
-    --hash=sha256:11c039b07962b29246de2da0912f4f663786185fd74d48daac7a270a43c8d92a \
-    --hash=sha256:8a2f357b86fec8598f52f16d5f93416931017ca1986d5f68679c9565bfc21fff \
-    --hash=sha256:577334c9d4518022a4cb6f9799dfbd1b996167eb31404b5a63d6c43d603e6418
-PyQt6-Qt6==6.2.4 \
-    --hash=sha256:42c37475a50ec7e06e0445ac9ce39465f69a86af407ad9b28b183da178d401ee \
-    --hash=sha256:b68543e5d5a4f5d24c26b517569da3cd30b0fbe75390b841e142c160399b3c0a \
-    --hash=sha256:0aa93581b92e01deaf2dcaad88ed6718996a6d84de59ee88316bcba143f008c9 \
-    --hash=sha256:48bc5b7400d6bca13d8c0a145f82295a6da317952ee1a3f107f1cd7d078c8140
-PyQt6-NetworkAuth==6.2.0 \
-    --hash=sha256:23e730cc0d6b828bec2f92d9fac3607871e6033a8af4620e5d4e3afc13bd6c3c \
-    --hash=sha256:b85ee25b01d6cb38d6141df0052b96de2df7f6e69066eaddb22ae238f56be40b \
-    --hash=sha256:e637781a00dd2032d0fd2025af09274898335033763e1dc765a5a99348f60c3b \
-    --hash=sha256:542e9d9a8a5bb78e1f26fa3d35ee01f45209bcf5a35b0cc367aaa85932c29750
-PyQt6-NetworkAuth-Qt6==6.2.4 \
-    --hash=sha256:c7996a9d8c4ce024529ec37981fbfd525ab1a2d497af1281f81f2b6054452d2e \
-    --hash=sha256:1ae9e08e03bd9d5ebdb42dfaccf484a9cc62eeea7504621fe42c005ff1745e66 \
-    --hash=sha256:1363ea81e5c6ac10bfd643e41ba0d215c0d031a57ff1e5972cc4c2a918efe712 \
-    --hash=sha256:8ed4e5e0eaaa42a6f91aba6745eea23fb3ffcbddc6b162016936530ed28dd0ad
-PyQt6-sip==13.2.1 \
-    --hash=sha256:0314d011633bc697e99f3f9897b484720e81a5f4ba0eaa5f05c5811e2e74ea53 \
-    --hash=sha256:082a80264699d4e2e919a7de8b6662886a353863d2b30a0047fe73d42e65c98e \
-    --hash=sha256:0a49f2d0bb49bc9d72665d62fb5ab6549c72dcf49e1e52dc2046edb8832a17a3 \
-    --hash=sha256:0ede42e84a79871022e1a8e4d5c05f70821b2795910c4cd103e863ce62bc8d68 \
-    --hash=sha256:226e9e349aa16dc1132f106ca01fa99cf7cb8e59daee29304c2fea5fa33212ec \
-    --hash=sha256:43afd9c9fdbc5f6ed2e22cae0752a8b8d9545c6d85f314bd27b861e21d4a97fe \
-    --hash=sha256:4b119a8fd880ece15a5bdff583edccd89dbc79d49de2e11cbbd6bba72713d1f3 \
-    --hash=sha256:616b6bad827e9c6e7ce5179883ca0f44110a42dcb045344aa28a495c05e19795 \
-    --hash=sha256:65f5aee6195bd0e785bd74f75ee080a5d5fb840c03210956e4ccbdde481b487c \
-    --hash=sha256:8b52d42e42e6e9f934ac7528cd154ac0210a532bb33fa1edfb4a8bbfb73ff88b \
-    --hash=sha256:a3d53fab72f959b45aeb954124255b585ff8d497a514a2582e0afd808fc2f3da \
-    --hash=sha256:b0d92f4a21706b18ab80c088cded94cd64d32a0c48e1729a4cc53fe5ab93cc1a \
-    --hash=sha256:b7bce59900b2e0a04f70246de2ccf79ee7933036b6b9183cf039b62eeae2b858 \
-    --hash=sha256:c456d5ccc4478254052082e298db01bb9d0495471c1659046697bb5dc9d2506c \
-    --hash=sha256:e2e6a3972169891dbc33d806f50ebf17eaa47a487ff6e4910fe2485c47cb6c2b \
-    --hash=sha256:f4226d4ab239d8655f94c42b397f23e6e85b246f614ff81162ef9321e47f7619
+PyQt6-sip==13.4.1 \
+    --hash=sha256:0df998f2b6ceeacfd10de773441572e215be0c9cae566cc7dd36e231bf714a12 \
+    --hash=sha256:224575e84805c4317bacd5d1b8e93e0ad5c48685dadbbe1e902d4ebe16f22828 \
+    --hash=sha256:36ae29cdc223cacc1257d0f5075cf81474550c6d26b728f922487a2aa935f130 \
+    --hash=sha256:3a674c591d4274d4ea8127205290e927a7dab0eb87a0038d4f4ea1d430782649 \
+    --hash=sha256:3ef9392e4ae29d393b79237d85840cdc6b8831f36eed5d56c7d9b329b380cc8d \
+    --hash=sha256:43935873d60f57719632840d517afee04ef8f30e92cfe0dadc7e6326691920fc \
+    --hash=sha256:5731f22618435654352ef07684549a17be82b75254227fc80b4b5b0b59fc6656 \
+    --hash=sha256:5bc4beb6fb1de4c9ba8beee7b1a4a813fa888c3b095206dafcd25d7e6e4ed2a7 \
+    --hash=sha256:5c36ab984402e96792eebf4b031abfaa589aa20af3190a79c54502c16964d97e \
+    --hash=sha256:a2a0461992c6657f343308b150c4d6b57e9e7a0e5c2f79538434e7fb869ea827 \
+    --hash=sha256:a81490ee84d7a41a126b116081bd97d758f41bf706aee0a8cec24d6e4c660184 \
+    --hash=sha256:e00e287ea05bbc293fc6e2198301962af9b7b622bd2daf4288f925a88ae35dc9 \
+    --hash=sha256:e670a7b2fb7e32204ce67d274017bfff3e21139d217d60cebbfcb75b019c91ee \
+    --hash=sha256:ee06f255787a0b4957f357f93b78d2a11ca3761916833e3afa83f1381d4d1a46 \
+    --hash=sha256:fbee0d554e0e98f56dbf6d94b00a28cc32425938ad7ae98fd91f8822c5b24d45 \
+    --hash=sha256:fcc6d78314783f4a193f02353f431b7ea4d357f47c3c7a7d0740e723f69c64dc
+PyQt6==6.4.2 \
+    --hash=sha256:18d1daf98d9236d55102cdadafd1056f5802f3c9288fcf7238569937b71a89f0 \
+    --hash=sha256:25bd399b4a95dce65d5f937c1aa85d3c7e14a21745ae2a4ca14c0116cd104290 \
+    --hash=sha256:740244f608fe15ee1d89695c43f31a14caeca41c4f02ac36c86dfba4a5d5813d \
+    --hash=sha256:c128bc0f17833e324593e3db83e99470d451a197dd17ff0333927b946c935bd9
+PyQt6-Qt6==6.4.2 \
+    --hash=sha256:9f07c3c100cb46cca4074965e7494d4df4f0fc016497d5303c1fe135822876e1 \
+    --hash=sha256:a29b8c858babd523e80c8db5f8fd19792641588ec04eab49af18b7a4423eb99f \
+    --hash=sha256:c0e91d0275d428496cacff717a9b719c52bfa52b21f124d638b79cc2217bc81e \
+    --hash=sha256:d19c4e72615762cd6f0b043f23fa5f0b02656091427ce6de1efccd58e10e6a53
+PyQt6-NetworkAuth==6.4.0 \
+    --hash=sha256:ab6178b3b2902ae9939a148555cfcee8c7803d6b0d5924cd1bd8f3407b8b9210 \
+    --hash=sha256:c16ec80232d88024b60d04386a23cc93067e5644a65f47f26ffb13d84dcd4a6d \
+    --hash=sha256:c302cd0d838c7229eda5e26e0b1b3d3ec4f8720f8d9379472bce5a89ff0735c2 \
+    --hash=sha256:d948fc0cf43b64afbda2acb5bf2392f785a1e7a2950d79ea850c1a3f4ae12f1a
+PyQt6-NetworkAuth-Qt6==6.4.2 \
+    --hash=sha256:179094bcb4d4d056316c22d3d067cd94d4591da23f804461bfb025ccfa29b2b4 \
+    --hash=sha256:1de6abbb5fa6585b97ae49d3f64b0dfad40bd56b1a31744d9775ff26247241c8 \
+    --hash=sha256:79ec4b0fc9450bbedbff03541b93b10d1c7e761cd2cc16ce70d2b09dcdf8c720 \
+    --hash=sha256:d96d557fe61edb9b68d189f270f0393d6579c8d308e6b0d41bc0699371d7cb4e
 certifi==2021.10.8 \
     --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
     --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569

+ 52 - 0
resources/definitions/fdmprinter.def.json

@@ -1298,6 +1298,18 @@
                     "limit_to_extruder": "wall_0_extruder_nr",
                     "settable_per_mesh": true
                 },
+                "hole_xy_offset_max_diameter":
+                {
+                    "label": "Hole Horizontal Expansion Max Diameter",
+                    "description": "When greater than zero, the Hole Horizontal Expansion is gradually applied on small holes (small holes are expanded more). When set to zero the Hole Horizontal Expansion will be applied to all holes. Holes larger than the Hole Horizontal Expansion Max Diameter are not expanded.",
+                    "unit": "mm",
+                    "type": "float",
+                    "default_value": 0,
+                    "minimum_value": "0",
+                    "enabled": "hole_xy_offset > 0",
+                    "limit_to_extruder": "wall_0_extruder_nr",
+                    "settable_per_mesh": true
+                },
                 "z_seam_type":
                 {
                     "label": "Z Seam Alignment",
@@ -1649,6 +1661,20 @@
                     "limit_to_extruder": "top_bottom_extruder_nr",
                     "settable_per_mesh": true
                 },
+                "small_skin_width":
+                {
+                    "label": "Small Top/Bottom Width",
+                    "description": "Small top/bottom regions are filled with walls instead of the default top/bottom pattern. This helps to avoids jerky motions.",
+                    "value": "skin_line_width * 2",
+                    "default_value": 1,
+                    "minimum_value": "0",
+                    "maximum_value_warning": "skin_line_width * 10",
+                    "type": "float",
+                    "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern != 'concentric'",
+                    "limit_to_extruder": "top_bottom_extruder_nr",
+                    "settable_per_mesh": true,
+                    "unit": "mm"
+                },
                 "skin_no_small_gaps_heuristic":
                 {
                     "label": "No Skin in Z Gaps",
@@ -5679,6 +5705,21 @@
                     "settable_per_mesh": false,
                     "settable_per_extruder": true
                 },
+                "skirt_height":
+                {
+                    "label": "Skirt Height",
+                    "description": "Printing the innermost skirt line with multiple layers makes it easy to remove the skirt.",
+                    "type": "int",
+                    "default_value": 3,
+                    "value": "3 if resolveOrValue('skirt_gap') > 0.0 else 1",
+                    "minimum_value": "1",
+                    "maximum_value_warning": "10",
+                    "maximum_value": "machine_height / layer_height",
+                    "enabled": "resolveOrValue('adhesion_type') == 'skirt'",
+                    "limit_to_extruder": "skirt_brim_extruder_nr",
+                    "settable_per_mesh": false,
+                    "settable_per_extruder": true
+                },
                 "skirt_gap":
                 {
                     "label": "Skirt Distance",
@@ -5789,6 +5830,17 @@
                     "settable_per_mesh": false,
                     "settable_per_extruder": true
                 },
+                "brim_smart_ordering":
+                {
+                    "label": "Smart Brim",
+                    "description": "Swap print order of the innermost and second innermost brim lines. This improves brim removal.",
+                    "type": "bool",
+                    "enabled": "resolveOrValue('adhesion_type') == 'brim'",
+                    "default_value": true,
+                    "limit_to_extruder": "skirt_brim_extruder_nr",
+                    "settable_per_mesh": false,
+                    "settable_per_extruder": false
+                },
                 "raft_margin":
                 {
                     "label": "Raft Extra Margin",

Some files were not shown because too many files changed in this diff