Browse Source

Merge pull request #8297 from Ultimaker/CURA-7118_shrinkage_compensation

Shrinkage compensation
Konstantinos Karmas 4 years ago
parent
commit
36a2535650

+ 1 - 1
cura/CuraApplication.py

@@ -126,7 +126,7 @@ class CuraApplication(QtApplication):
     # SettingVersion represents the set of settings available in the machine/extruder definitions.
     # You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
     # changes of the settings.
-    SettingVersion = 15
+    SettingVersion = 16
 
     Created = False
 

+ 43 - 14
cura/Scene/ConvexHullDecorator.py

@@ -1,11 +1,10 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2020 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
 from PyQt5.QtCore import QTimer
 
 from UM.Application import Application
 from UM.Math.Polygon import Polygon
-
 from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
 from UM.Settings.ContainerRegistry import ContainerRegistry
 
@@ -50,8 +49,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
         self._build_volume.raftThicknessChanged.connect(self._onChanged)
 
         CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
-        CuraApplication.getInstance().getController().toolOperationStarted.connect(self._onChanged)
-        CuraApplication.getInstance().getController().toolOperationStopped.connect(self._onChanged)
+        controller = CuraApplication.getInstance().getController()
+        controller.toolOperationStarted.connect(self._onChanged)
+        controller.toolOperationStopped.connect(self._onChanged)
+        #CuraApplication.getInstance().sceneBoundingBoxChanged.connect(self._onChanged)
 
         self._root = Application.getInstance().getController().getScene().getRoot()
 
@@ -188,7 +189,6 @@ class ConvexHullDecorator(SceneNodeDecorator):
 
     def recomputeConvexHullDelayed(self) -> None:
         """The same as recomputeConvexHull, but using a timer if it was set."""
-
         if self._recompute_convex_hull_timer is not None:
             self._recompute_convex_hull_timer.start()
         else:
@@ -263,16 +263,17 @@ class ConvexHullDecorator(SceneNodeDecorator):
             return offset_hull
 
         else:
+            convex_hull = Polygon([])
             offset_hull = Polygon([])
             mesh = self._node.getMeshData()
             if mesh is None:
                 return Polygon([])  # Node has no mesh data, so just return an empty Polygon.
 
-            world_transform = self._node.getWorldTransformation(copy= False)
+            world_transform = self._node.getWorldTransformation(copy = False)
 
             # Check the cache
             if mesh is self._2d_convex_hull_mesh and world_transform == self._2d_convex_hull_mesh_world_transform:
-                return self._2d_convex_hull_mesh_result
+                return self._offsetHull(self._2d_convex_hull_mesh_result)
 
             vertex_data = mesh.getConvexHullTransformedVertices(world_transform)
             # Don't use data below 0.
@@ -307,7 +308,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
             # Store the result in the cache
             self._2d_convex_hull_mesh = mesh
             self._2d_convex_hull_mesh_world_transform = world_transform
-            self._2d_convex_hull_mesh_result = offset_hull
+            self._2d_convex_hull_mesh_result = convex_hull
 
             return offset_hull
 
@@ -379,12 +380,41 @@ class ConvexHullDecorator(SceneNodeDecorator):
         :return: New Polygon instance that is offset with everything that
         influences the collision area.
         """
-
+        # Shrinkage compensation.
+        if not self._global_stack:  # Should never happen.
+            return convex_hull
+        scale_factor = self._global_stack.getProperty("material_shrinkage_percentage", "value") / 100.0
+        result = convex_hull
+        if scale_factor != 1.0 and not self.getNode().callDecoration("isGroup"):
+            center = None
+            if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time":
+                # Find the root node that's placed in the scene; the root of the mesh group.
+                ancestor = self.getNode()
+                while ancestor.getParent() != self._root:
+                    ancestor = ancestor.getParent()
+                center = ancestor.getBoundingBox().center
+            else:
+                # Find the bounding box of the entire scene, which is all one mesh group then.
+                aabb = None
+                for printed_node in self._root.getChildren():
+                    if not printed_node.callDecoration("isSliceable") and not printed_node.callDecoration("isGroup"):
+                        continue  # Not a printed node.
+                    if aabb is None:
+                        aabb = printed_node.getBoundingBox()
+                    else:
+                        aabb = aabb + printed_node.getBoundingBox()
+                if aabb:
+                    center = aabb.center
+            if center:
+                result = convex_hull.scale(scale_factor, [center.x, center.z])  # Yes, use Z instead of Y. Mixed conventions there with how the OpenGL coordinates are transmitted.
+
+        # Horizontal expansion.
         horizontal_expansion = max(
             self._getSettingProperty("xy_offset", "value"),
             self._getSettingProperty("xy_offset_layer_0", "value")
         )
 
+        # Mold.
         mold_width = 0
         if self._getSettingProperty("mold_enabled", "value"):
             mold_width = self._getSettingProperty("mold_width", "value")
@@ -396,14 +426,13 @@ class ConvexHullDecorator(SceneNodeDecorator):
                 [hull_offset, hull_offset],
                 [hull_offset, -hull_offset]
             ], numpy.float32))
-            return convex_hull.getMinkowskiHull(expansion_polygon)
+            return result.getMinkowskiHull(expansion_polygon)
         else:
-            return convex_hull
+            return result
 
     def _onChanged(self, *args) -> None:
         self._raft_thickness = self._build_volume.getRaftThickness()
-        if not args or args[0] == self._node:
-            self.recomputeConvexHullDelayed()
+        self.recomputeConvexHullDelayed()
 
     def _onGlobalStackChanged(self) -> None:
         if self._global_stack:
@@ -469,7 +498,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
         "adhesion_type", "raft_margin", "print_sequence",
         "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
 
-    _influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width", "anti_overhang_mesh", "infill_mesh", "cutting_mesh"}
+    _influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width", "anti_overhang_mesh", "infill_mesh", "cutting_mesh", "material_shrinkage_percentage"}
     """Settings that change the convex hull.
 
     If these settings change, the convex hull should be recalculated.

+ 5 - 13
plugins/ModelChecker/ModelChecker.py

@@ -58,7 +58,7 @@ class ModelChecker(QObject, Extension):
         self._createView()
 
     def checkObjectsForShrinkage(self):
-        shrinkage_threshold = 0.5 #From what shrinkage percentage a warning will be issued about the model size.
+        shrinkage_threshold = 100.5 #From what shrinkage percentage a warning will be issued about the model size.
         warning_size_xy = 150 #The horizontal size of a model that would be too large when dealing with shrinking materials.
         warning_size_z = 100 #The vertical size of a model that would be too large when dealing with shrinking materials.
 
@@ -86,7 +86,7 @@ class ModelChecker(QObject, Extension):
                 Application.getInstance().callLater(lambda: self.onChanged.emit())
                 return False
 
-            if material_shrinkage[node_extruder_position] > shrinkage_threshold:
+            if material_shrinkage > shrinkage_threshold:
                 bbox = node.getBoundingBox()
                 if bbox is not None and (bbox.width >= warning_size_xy or bbox.depth >= warning_size_xy or bbox.height >= warning_size_z):
                     warning_nodes.append(node)
@@ -134,16 +134,8 @@ class ModelChecker(QObject, Extension):
     def showWarnings(self):
         self._caution_message.show()
 
-    def _getMaterialShrinkage(self):
+    def _getMaterialShrinkage(self) -> float:
         global_container_stack = Application.getInstance().getGlobalContainerStack()
         if global_container_stack is None:
-            return {}
-
-        material_shrinkage = {}
-        # Get all shrinkage values of materials used
-        for extruder_position, extruder in enumerate(global_container_stack.extruderList):
-            shrinkage = extruder.material.getProperty("material_shrinkage_percentage", "value")
-            if shrinkage is None:
-                shrinkage = 0
-            material_shrinkage[str(extruder_position)] = shrinkage
-        return material_shrinkage
+            return 100
+        return global_container_stack.getProperty("material_shrinkage_percentage", "value")

+ 79 - 0
plugins/VersionUpgrade/VersionUpgrade47to48/VersionUpgrade47to48.py

@@ -0,0 +1,79 @@
+# Copyright (c) 2020 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import configparser
+from typing import Tuple, List
+import io
+
+from UM.VersionUpgrade import VersionUpgrade
+
+
+class VersionUpgrade47to48(VersionUpgrade):
+    def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
+        """
+        Upgrades preferences to have the new version number.
+        :param serialized: The original contents of the preferences file.
+        :param filename: The file name of the preferences file.
+        :return: A list of new file names, and a list of the new contents for
+        those files.
+        """
+        parser = configparser.ConfigParser(interpolation = None)
+        parser.read_string(serialized)
+
+        # Update version number.
+        parser["metadata"]["setting_version"] = "16"
+
+        result = io.StringIO()
+        parser.write(result)
+        return [filename], [result.getvalue()]
+
+    def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
+        """
+        Upgrades instance containers to have the new version number.
+
+        This this also changes the unit of the Scaling Factor Shrinkage
+        Compensation setting.
+        :param serialized: The original contents of the instance container.
+        :param filename: The original file name of the instance container.
+        :return: A list of new file names, and a list of the new contents for
+        those files.
+        """
+        parser = configparser.ConfigParser(interpolation = None, comment_prefixes = ())
+        parser.read_string(serialized)
+
+        # Update version number.
+        parser["metadata"]["setting_version"] = "16"
+
+        if "values" in parser:
+            # Shrinkage factor used to be a percentage based around 0% (where 0% meant that it doesn't shrink or expand).
+            # Since 4.8, it is a percentage based around 100% (where 100% means that it doesn't shrink or expand).
+            if "material_shrinkage_percentage" in parser["values"]:
+                shrinkage_percentage = parser["values"]["meshfix_maximum_deviation"]
+                if shrinkage_percentage.startswith("="):
+                    shrinkage_percentage = shrinkage_percentage[1:]
+                shrinkage_percentage = "=(" + shrinkage_percentage + ") + 100"
+                parser["values"]["material_shrinkage_percentage"] = shrinkage_percentage
+
+        result = io.StringIO()
+        parser.write(result)
+        return [filename], [result.getvalue()]
+
+    def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
+        """
+        Upgrades stacks to have the new version number.
+        :param serialized: The original contents of the stack.
+        :param filename: The original file name of the stack.
+        :return: A list of new file names, and a list of the new contents for
+        those files.
+        """
+        parser = configparser.ConfigParser(interpolation = None)
+        parser.read_string(serialized)
+
+        # Update version number.
+        if "metadata" not in parser:
+            parser["metadata"] = {}
+        parser["metadata"]["setting_version"] = "16"
+
+        result = io.StringIO()
+        parser.write(result)
+        return [filename], [result.getvalue()]

+ 59 - 0
plugins/VersionUpgrade/VersionUpgrade47to48/__init__.py

@@ -0,0 +1,59 @@
+# Copyright (c) 2020 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Any, Dict, TYPE_CHECKING
+
+from . import VersionUpgrade47to48
+
+if TYPE_CHECKING:
+    from UM.Application import Application
+
+upgrade = VersionUpgrade47to48.VersionUpgrade47to48()
+
+def getMetaData() -> Dict[str, Any]:
+    return {
+        "version_upgrade": {
+            # From                           To                              Upgrade function
+            ("preferences", 6000015):        ("preferences", 6000016,        upgrade.upgradePreferences),
+            ("machine_stack", 4000015):      ("machine_stack", 4000016,      upgrade.upgradeStack),
+            ("extruder_train", 4000015):     ("extruder_train", 4000016,     upgrade.upgradeStack),
+            ("definition_changes", 4000015): ("definition_changes", 4000016, upgrade.upgradeInstanceContainer),
+            ("quality_changes", 4000015):    ("quality_changes", 4000016,    upgrade.upgradeInstanceContainer),
+            ("quality", 4000015):            ("quality", 4000016,            upgrade.upgradeInstanceContainer),
+            ("user", 4000015):               ("user", 4000016,               upgrade.upgradeInstanceContainer),
+        },
+        "sources": {
+            "preferences": {
+                "get_version": upgrade.getCfgVersion,
+                "location": {"."}
+            },
+            "machine_stack": {
+                "get_version": upgrade.getCfgVersion,
+                "location": {"./machine_instances"}
+            },
+            "extruder_train": {
+                "get_version": upgrade.getCfgVersion,
+                "location": {"./extruders"}
+            },
+            "definition_changes": {
+                "get_version": upgrade.getCfgVersion,
+                "location": {"./definition_changes"}
+            },
+            "quality_changes": {
+                "get_version": upgrade.getCfgVersion,
+                "location": {"./quality_changes"}
+            },
+            "quality": {
+                "get_version": upgrade.getCfgVersion,
+                "location": {"./quality"}
+            },
+            "user": {
+                "get_version": upgrade.getCfgVersion,
+                "location": {"./user"}
+            }
+        }
+    }
+
+
+def register(app: "Application") -> Dict[str, Any]:
+    return {"version_upgrade": upgrade}

+ 8 - 0
plugins/VersionUpgrade/VersionUpgrade47to48/plugin.json

@@ -0,0 +1,8 @@
+{
+    "name": "Version Upgrade 4.7 to 4.8",
+    "author": "Ultimaker B.V.",
+    "version": "1.0.0",
+    "description": "Upgrades configurations from Cura 4.7 to Cura 4.8.",
+    "api": "7.3.0",
+    "i18n-catalog": "cura"
+}

+ 1 - 1
resources/definitions/fdmextruder.def.json

@@ -6,7 +6,7 @@
         "type": "extruder",
         "author": "Ultimaker",
         "manufacturer": "Unknown",
-        "setting_version": 15,
+        "setting_version": 16,
         "visible": false,
         "position": "0"
     },

+ 9 - 8
resources/definitions/fdmprinter.def.json

@@ -6,7 +6,7 @@
         "type": "machine",
         "author": "Ultimaker",
         "manufacturer": "Unknown",
-        "setting_version": 15,
+        "setting_version": 16,
         "file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
         "visible": false,
         "has_materials": true,
@@ -2319,16 +2319,17 @@
                 },
                 "material_shrinkage_percentage":
                 {
-                    "label": "Shrinkage Ratio",
-                    "description": "Shrinkage ratio in percentage.",
+                    "label": "Scaling Factor Shrinkage Compensation",
+                    "description": "To compensate for the shrinkage of the material as it cools down, the model will be scaled with this factor.",
                     "unit": "%",
                     "type": "float",
-                    "default_value": 0,
-                    "minimum_value": "0",
-                    "maximum_value": "100",
-                    "enabled": false,
+                    "default_value": 100.0,
+                    "minimum_value": "0.001",
+                    "minimum_value_warning": "100",
+                    "maximum_value_warning": "120",
                     "settable_per_mesh": false,
-                    "settable_per_extruder": true
+                    "settable_per_extruder": false,
+                    "resolve": "sum(extruderValues(\"material_shrinkage_percentage\")) / len(extruderValues(\"material_shrinkage_percentage\"))"
                 },
                 "material_crystallinity":
                 {

+ 1 - 1
resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Draft_Print_Quick.inst.cfg

@@ -4,7 +4,7 @@ name = Quick
 definition = ultimaker_s3
 
 [metadata]
-setting_version = 15
+setting_version = 16
 type = intent
 intent_category = quick
 quality_type = draft

+ 1 - 1
resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Fast_Print_Accurate.inst.cfg

@@ -4,7 +4,7 @@ name = Accurate
 definition = ultimaker_s3
 
 [metadata]
-setting_version = 15
+setting_version = 16
 type = intent
 intent_category = engineering
 quality_type = fast

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