mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 22:47:29 -06:00
CURA-4557 model checker is now a tool, click to activate
This commit is contained in:
parent
e958516913
commit
1aba1cfe6a
3 changed files with 68 additions and 66 deletions
|
@ -5,7 +5,7 @@ from PyQt5.QtCore import QTimer
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Extension import Extension
|
from UM.Tool import Tool
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
@ -17,31 +17,20 @@ SHRINKAGE_THRESHOLD = 0.5
|
||||||
WARNING_SIZE_XY = 150
|
WARNING_SIZE_XY = 150
|
||||||
WARNING_SIZE_Z = 100
|
WARNING_SIZE_Z = 100
|
||||||
|
|
||||||
MESSAGE_LIFETIME = 10
|
|
||||||
|
|
||||||
|
class ModelChecker(Tool):
|
||||||
class ModelChecker(Extension):
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._update_timer = QTimer()
|
self._last_known_tool_id = None
|
||||||
self._update_timer.setInterval(2000)
|
|
||||||
self._update_timer.setSingleShot(True)
|
|
||||||
self._update_timer.timeout.connect(self.checkObjects)
|
|
||||||
|
|
||||||
self._nodes_to_check = set()
|
self._controller.activeToolChanged.connect(self._onActiveToolChanged)
|
||||||
|
|
||||||
self._warning_model_names = set() # Collect the names of models so we show the next warning with timeout
|
def checkObjects(self, nodes_to_check):
|
||||||
|
|
||||||
Application.getInstance().initializationFinished.connect(self.bindSignals)
|
|
||||||
|
|
||||||
def bindSignals(self):
|
|
||||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged)
|
|
||||||
Application.getInstance().getMachineManager().rootMaterialChanged.connect(self._checkAllSliceableNodes)
|
|
||||||
|
|
||||||
def checkObjects(self):
|
|
||||||
warning_nodes = []
|
warning_nodes = []
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack is None:
|
||||||
|
return []
|
||||||
material_shrinkage = {}
|
material_shrinkage = {}
|
||||||
need_check = False
|
need_check = False
|
||||||
|
|
||||||
|
@ -59,42 +48,57 @@ class ModelChecker(Extension):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check node material shrinkage and bounding box size
|
# Check node material shrinkage and bounding box size
|
||||||
for node in self._nodes_to_check:
|
for node in nodes_to_check:
|
||||||
node_extruder_position = node.callDecoration("getActiveExtruderPosition")
|
node_extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||||
if material_shrinkage[node_extruder_position] > SHRINKAGE_THRESHOLD:
|
if material_shrinkage[node_extruder_position] > SHRINKAGE_THRESHOLD:
|
||||||
bbox = node.getBoundingBox()
|
bbox = node.getBoundingBox()
|
||||||
if bbox.width >= WARNING_SIZE_XY or bbox.depth >= WARNING_SIZE_XY or bbox.height >= WARNING_SIZE_Z:
|
if bbox.width >= WARNING_SIZE_XY or bbox.depth >= WARNING_SIZE_XY or bbox.height >= WARNING_SIZE_Z:
|
||||||
warning_nodes.append(node)
|
warning_nodes.append(node)
|
||||||
self._nodes_to_check = set()
|
|
||||||
|
|
||||||
# Display warning message
|
return warning_nodes
|
||||||
if warning_nodes:
|
|
||||||
message_lifetime = MESSAGE_LIFETIME
|
def checkAllSliceableNodes(self):
|
||||||
for node in warning_nodes:
|
# Add all sliceable scene nodes to check
|
||||||
if node.getName() not in self._warning_model_names:
|
scene = Application.getInstance().getController().getScene()
|
||||||
message_lifetime = 0 # infinite
|
nodes_to_check = []
|
||||||
self._warning_model_names.add(node.getName())
|
for node in DepthFirstIterator(scene.getRoot()):
|
||||||
|
if node.callDecoration("isSliceable"):
|
||||||
|
nodes_to_check.append(node)
|
||||||
|
return self.checkObjects(nodes_to_check)
|
||||||
|
|
||||||
|
## Display warning message
|
||||||
|
def showWarningMessage(self, warning_nodes):
|
||||||
caution_message = Message(catalog.i18nc(
|
caution_message = Message(catalog.i18nc(
|
||||||
"@info:warning",
|
"@info:status",
|
||||||
"Some models may not be printed optimal due to object size and material chosen [%s].\n"
|
"Some models may not be printed optimal due to object size and material chosen [%s].\n"
|
||||||
"Tips that may be useful to improve the print quality:\n"
|
"Tips that may be useful to improve the print quality:\n"
|
||||||
"1) Use rounded corners\n"
|
"1) Use rounded corners\n"
|
||||||
"2) Turn the fan off (only if the are no tiny details on the model)\n"
|
"2) Turn the fan off (only if the are no tiny details on the model)\n"
|
||||||
"3) Use a different material") % ", ".join([n.getName() for n in warning_nodes]),
|
"3) Use a different material") % ", ".join([n.getName() for n in warning_nodes]),
|
||||||
lifetime = message_lifetime,
|
lifetime = 0,
|
||||||
title = catalog.i18nc("@info:title", "Model Warning"))
|
title = catalog.i18nc("@info:title", "Model Checker Warning"))
|
||||||
caution_message.show()
|
caution_message.show()
|
||||||
|
|
||||||
def _onSceneChanged(self, source):
|
def showHappyMessage(self):
|
||||||
if isinstance(source, CuraSceneNode) and source.callDecoration("isSliceable"):
|
happy_message = Message(catalog.i18nc(
|
||||||
self._nodes_to_check.add(source)
|
"@info:status",
|
||||||
self._update_timer.start()
|
"The Model Checker did not detect any problems with your model / print setup combination."),
|
||||||
|
lifetime = 5,
|
||||||
|
title = catalog.i18nc("@info:title", "Model Checker"))
|
||||||
|
happy_message.show()
|
||||||
|
|
||||||
def _checkAllSliceableNodes(self, *args):
|
def _onActiveToolChanged(self):
|
||||||
# Add all scene nodes
|
active_tool = self.getController().getActiveTool()
|
||||||
scene = Application.getInstance().getController().getScene()
|
if active_tool is None:
|
||||||
for node in DepthFirstIterator(scene.getRoot()):
|
return
|
||||||
if node.callDecoration("isSliceable"):
|
active_tool_id = active_tool.getPluginId()
|
||||||
self._nodes_to_check.add(node)
|
if active_tool_id != self.getPluginId():
|
||||||
if self._nodes_to_check:
|
self._last_known_tool_id = active_tool_id
|
||||||
self._update_timer.start()
|
if active_tool_id == self.getPluginId():
|
||||||
|
warning_nodes = self.checkAllSliceableNodes()
|
||||||
|
if warning_nodes:
|
||||||
|
self.showWarningMessage(warning_nodes)
|
||||||
|
else:
|
||||||
|
self.showHappyMessage()
|
||||||
|
if self._last_known_tool_id is not None:
|
||||||
|
self.getController().setActiveTool(self._last_known_tool_id)
|
||||||
|
|
|
@ -3,21 +3,19 @@
|
||||||
|
|
||||||
from . import ModelChecker
|
from . import ModelChecker
|
||||||
|
|
||||||
## Defines additional metadata for the plug-in.
|
from UM.i18n import i18nCatalog
|
||||||
#
|
i18n_catalog = i18nCatalog("uranium")
|
||||||
# Some types of plug-ins require additional metadata, such as which file types
|
|
||||||
# they are able to read or the name of the tool they define. In the case of
|
|
||||||
# the "Extension" type plug-in, there is no additional metadata though.
|
def getMetaData():
|
||||||
def getMetaData():
|
return {
|
||||||
return {}
|
"tool": {
|
||||||
|
"name": i18n_catalog.i18nc("@label", "Model Checker"),
|
||||||
|
"description": i18n_catalog.i18nc("@info:tooltip", "Checks models and print configuration for possible printing issues and give suggestions."),
|
||||||
|
"icon": "model_checker.svg",
|
||||||
|
"weight": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
## Lets Uranium know that this plug-in exists.
|
|
||||||
#
|
|
||||||
# This is called when starting the application to find out which plug-ins
|
|
||||||
# exist and what their types are. We need to return a dictionary mapping from
|
|
||||||
# strings representing plug-in types (in this case "extension") to objects
|
|
||||||
# that inherit from PluginObject.
|
|
||||||
#
|
|
||||||
# \param app The application that the plug-in needs to register with.
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return {"extension": ModelChecker.ModelChecker()}
|
return { "tool": ModelChecker.ModelChecker() }
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "Model Checker",
|
"name": "Model Checker",
|
||||||
"author": "Ultimaker",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
"api": 4,
|
"api": 4,
|
||||||
"description": "Checks models for possible printing issues and give suggestions.",
|
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
||||||
"catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue