|
@@ -0,0 +1,116 @@
|
|
|
+# Copyright (c) 2018 Ultimaker B.V.
|
|
|
+# Cura is released under the terms of the LGPLv3 or higher.
|
|
|
+
|
|
|
+import os
|
|
|
+
|
|
|
+from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty
|
|
|
+
|
|
|
+from UM.Application import Application
|
|
|
+from UM.Extension import Extension
|
|
|
+from UM.Logger import Logger
|
|
|
+from UM.Message import Message
|
|
|
+from UM.i18n import i18nCatalog
|
|
|
+from UM.PluginRegistry import PluginRegistry
|
|
|
+from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|
|
+
|
|
|
+catalog = i18nCatalog("cura")
|
|
|
+
|
|
|
+
|
|
|
+class ModelChecker(QObject, Extension):
|
|
|
+ ## Signal that gets emitted when anything changed that we need to check.
|
|
|
+ onChanged = pyqtSignal()
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super().__init__()
|
|
|
+
|
|
|
+ self._button_view = None
|
|
|
+
|
|
|
+ self._caution_message = Message("", #Message text gets set when the message gets shown, to display the models in question.
|
|
|
+ lifetime = 0,
|
|
|
+ title = catalog.i18nc("@info:title", "Model Checker Warning"))
|
|
|
+
|
|
|
+ Application.getInstance().initializationFinished.connect(self._pluginsInitialized)
|
|
|
+ Application.getInstance().getController().getScene().sceneChanged.connect(self._onChanged)
|
|
|
+
|
|
|
+ ## Pass-through to allow UM.Signal to connect with a pyqtSignal.
|
|
|
+ def _onChanged(self, _):
|
|
|
+ self.onChanged.emit()
|
|
|
+
|
|
|
+ ## Called when plug-ins are initialized.
|
|
|
+ #
|
|
|
+ # This makes sure that we listen to changes of the material and that the
|
|
|
+ # button is created that indicates warnings with the current set-up.
|
|
|
+ def _pluginsInitialized(self):
|
|
|
+ Application.getInstance().getMachineManager().rootMaterialChanged.connect(self.onChanged)
|
|
|
+ self._createView()
|
|
|
+
|
|
|
+ def checkObjectsForShrinkage(self):
|
|
|
+ shrinkage_threshold = 0.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.
|
|
|
+
|
|
|
+ material_shrinkage = self.getMaterialShrinkage()
|
|
|
+
|
|
|
+ warning_nodes = []
|
|
|
+
|
|
|
+ # Check node material shrinkage and bounding box size
|
|
|
+ for node in self.sliceableNodes():
|
|
|
+ node_extruder_position = node.callDecoration("getActiveExtruderPosition")
|
|
|
+ if material_shrinkage[node_extruder_position] > shrinkage_threshold:
|
|
|
+ bbox = node.getBoundingBox()
|
|
|
+ if bbox.width >= warning_size_xy or bbox.depth >= warning_size_xy or bbox.height >= warning_size_z:
|
|
|
+ warning_nodes.append(node)
|
|
|
+
|
|
|
+ self._caution_message.setText(catalog.i18nc(
|
|
|
+ "@info:status",
|
|
|
+ "Some models may not be printed optimal due to object size and chosen material for models: {model_names}.\n"
|
|
|
+ "Tips that may be useful to improve the print quality:\n"
|
|
|
+ "1) Use rounded corners\n"
|
|
|
+ "2) Turn the fan off (only if the are no tiny details on the model)\n"
|
|
|
+ "3) Use a different material").format(model_names = ", ".join([n.getName() for n in warning_nodes])))
|
|
|
+
|
|
|
+ return len(warning_nodes) > 0
|
|
|
+
|
|
|
+ def sliceableNodes(self):
|
|
|
+ # Add all sliceable scene nodes to check
|
|
|
+ scene = Application.getInstance().getController().getScene()
|
|
|
+ for node in DepthFirstIterator(scene.getRoot()):
|
|
|
+ if node.callDecoration("isSliceable"):
|
|
|
+ yield node
|
|
|
+
|
|
|
+ ## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
|
|
|
+ def _createView(self):
|
|
|
+ Logger.log("d", "Creating model checker view.")
|
|
|
+
|
|
|
+ # Create the plugin dialog component
|
|
|
+ path = os.path.join(PluginRegistry.getInstance().getPluginPath("ModelChecker"), "ModelChecker.qml")
|
|
|
+ self._button_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
|
|
+
|
|
|
+ # The qml is only the button
|
|
|
+ Application.getInstance().addAdditionalComponent("jobSpecsButton", self._button_view)
|
|
|
+
|
|
|
+ Logger.log("d", "Model checker view created.")
|
|
|
+
|
|
|
+ @pyqtProperty(bool, notify = onChanged)
|
|
|
+ def runChecks(self):
|
|
|
+ danger_shrinkage = self.checkObjectsForShrinkage()
|
|
|
+
|
|
|
+ return any((danger_shrinkage, )) #If any of the checks fail, show the warning button.
|
|
|
+
|
|
|
+ @pyqtSlot()
|
|
|
+ def showWarnings(self):
|
|
|
+ self._caution_message.show()
|
|
|
+
|
|
|
+ def getMaterialShrinkage(self):
|
|
|
+ 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 global_container_stack.extruders.items():
|
|
|
+ shrinkage = extruder.material.getProperty("material_shrinkage_percentage", "value")
|
|
|
+ if shrinkage is None:
|
|
|
+ shrinkage = 0
|
|
|
+ material_shrinkage[extruder_position] = shrinkage
|
|
|
+ return material_shrinkage
|