mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-07 06:57:28 -06:00
CURA-4557 it's now a small button next to the job spec when one of the warping materials is selected
This commit is contained in:
parent
25bf7a6aba
commit
d8d226c0d6
6 changed files with 190 additions and 57 deletions
|
@ -1,13 +1,16 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QTimer
|
import os
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
|
||||||
|
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Tool import Tool
|
from UM.Extension import Extension
|
||||||
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
@ -17,35 +20,34 @@ SHRINKAGE_THRESHOLD = 0.5
|
||||||
WARNING_SIZE_XY = 150
|
WARNING_SIZE_XY = 150
|
||||||
WARNING_SIZE_Z = 100
|
WARNING_SIZE_Z = 100
|
||||||
|
|
||||||
|
# Use this when actual shrinkage data is not in fdm_materials yet
|
||||||
|
MATERIALS_LOOKUP = {
|
||||||
|
"generic_abs": 1,
|
||||||
|
"generic_pc": 1,
|
||||||
|
"generic_pp": 1,
|
||||||
|
"generic_cpe_plus": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ModelChecker(QObject, Extension):
|
||||||
|
|
||||||
|
needCheckChanged = pyqtSignal()
|
||||||
|
|
||||||
class ModelChecker(Tool):
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._last_known_tool_id = None
|
self._button_view = None
|
||||||
|
self._need_checks = False
|
||||||
|
|
||||||
self._controller.activeToolChanged.connect(self._onActiveToolChanged)
|
Application.getInstance().initializationFinished.connect(self.bindSignals)
|
||||||
|
|
||||||
|
def bindSignals(self):
|
||||||
|
Application.getInstance().getMachineManager().rootMaterialChanged.connect(self._onChanged)
|
||||||
|
|
||||||
|
def checkObjectsForShrinkage(self, nodes_to_check):
|
||||||
|
material_shrinkage = self.getMaterialShrinkage()
|
||||||
|
|
||||||
def checkObjects(self, nodes_to_check):
|
|
||||||
warning_nodes = []
|
warning_nodes = []
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if global_container_stack is None:
|
|
||||||
return []
|
|
||||||
material_shrinkage = {}
|
|
||||||
need_check = False
|
|
||||||
|
|
||||||
# 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
|
|
||||||
if shrinkage > SHRINKAGE_THRESHOLD:
|
|
||||||
need_check = True
|
|
||||||
material_shrinkage[extruder_position] = shrinkage
|
|
||||||
|
|
||||||
# Check if we can bail out fast
|
|
||||||
if not need_check:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check node material shrinkage and bounding box size
|
# Check node material shrinkage and bounding box size
|
||||||
for node in nodes_to_check:
|
for node in nodes_to_check:
|
||||||
|
@ -64,7 +66,7 @@ class ModelChecker(Tool):
|
||||||
for node in DepthFirstIterator(scene.getRoot()):
|
for node in DepthFirstIterator(scene.getRoot()):
|
||||||
if node.callDecoration("isSliceable"):
|
if node.callDecoration("isSliceable"):
|
||||||
nodes_to_check.append(node)
|
nodes_to_check.append(node)
|
||||||
return self.checkObjects(nodes_to_check)
|
return self.checkObjectsForShrinkage(nodes_to_check)
|
||||||
|
|
||||||
## Display warning message
|
## Display warning message
|
||||||
def showWarningMessage(self, warning_nodes):
|
def showWarningMessage(self, warning_nodes):
|
||||||
|
@ -82,23 +84,77 @@ class ModelChecker(Tool):
|
||||||
def showHappyMessage(self):
|
def showHappyMessage(self):
|
||||||
happy_message = Message(catalog.i18nc(
|
happy_message = Message(catalog.i18nc(
|
||||||
"@info:status",
|
"@info:status",
|
||||||
"The Model Checker did not detect any problems with your model / print setup combination."),
|
"The Model Checker did not detect any problems with your model / chosen materials combination."),
|
||||||
lifetime = 5,
|
lifetime = 5,
|
||||||
title = catalog.i18nc("@info:title", "Model Checker"))
|
title = catalog.i18nc("@info:title", "Model Checker"))
|
||||||
happy_message.show()
|
happy_message.show()
|
||||||
|
|
||||||
def _onActiveToolChanged(self):
|
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
|
||||||
active_tool = self.getController().getActiveTool()
|
def _createView(self):
|
||||||
if active_tool is None:
|
Logger.log("d", "Creating model checker view.")
|
||||||
return
|
|
||||||
active_tool_id = active_tool.getPluginId()
|
# Create the plugin dialog component
|
||||||
if active_tool_id != self.getPluginId():
|
path = os.path.join(PluginRegistry.getInstance().getPluginPath("ModelChecker"), "ModelChecker.qml")
|
||||||
self._last_known_tool_id = active_tool_id
|
self._button_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||||
if active_tool_id == self.getPluginId():
|
|
||||||
|
# The qml is only the button
|
||||||
|
Application.getInstance().addAdditionalComponent("jobSpecsButton", self._button_view)
|
||||||
|
|
||||||
|
Logger.log("d", "Model checker view created.")
|
||||||
|
|
||||||
|
def _onChanged(self, *args):
|
||||||
|
if self._button_view is None:
|
||||||
|
self._createView()
|
||||||
|
old_need_checks = self._need_checks
|
||||||
|
self._need_checks = self.calculateNeedCheck()
|
||||||
|
if old_need_checks != self._need_checks:
|
||||||
|
self.needCheckChanged.emit()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def runChecks(self):
|
||||||
warning_nodes = self.checkAllSliceableNodes()
|
warning_nodes = self.checkAllSliceableNodes()
|
||||||
if warning_nodes:
|
if warning_nodes:
|
||||||
self.showWarningMessage(warning_nodes)
|
self.showWarningMessage(warning_nodes)
|
||||||
else:
|
else:
|
||||||
self.showHappyMessage()
|
self.showHappyMessage()
|
||||||
if self._last_known_tool_id is not None:
|
|
||||||
self.getController().setActiveTool(self._last_known_tool_id)
|
# TODO: use this if branch feature_model_check is merged in fdm_materials to master
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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():
|
||||||
|
base_file = extruder.material.getMetaDataEntry("base_file")
|
||||||
|
shrinkage = MATERIALS_LOOKUP.get(base_file, 0)
|
||||||
|
material_shrinkage[extruder_position] = shrinkage
|
||||||
|
return material_shrinkage
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = needCheckChanged)
|
||||||
|
def needCheck(self):
|
||||||
|
return self._need_checks
|
||||||
|
|
||||||
|
def calculateNeedCheck(self):
|
||||||
|
need_check = False
|
||||||
|
|
||||||
|
for shrinkage in self.getMaterialShrinkage().values():
|
||||||
|
if shrinkage > SHRINKAGE_THRESHOLD:
|
||||||
|
need_check = True
|
||||||
|
|
||||||
|
return need_check
|
||||||
|
|
47
plugins/ModelChecker/ModelChecker.qml
Normal file
47
plugins/ModelChecker/ModelChecker.qml
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
// Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import QtQuick 2.2
|
||||||
|
import QtQuick.Controls 1.1
|
||||||
|
import QtQuick.Controls.Styles 1.1
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import QtQuick.Dialogs 1.1
|
||||||
|
import QtQuick.Window 2.2
|
||||||
|
|
||||||
|
import UM 1.2 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: modelCheckerButton
|
||||||
|
|
||||||
|
UM.I18nCatalog{id: catalog; name:"cura"}
|
||||||
|
|
||||||
|
visible: manager.needCheck
|
||||||
|
tooltip: catalog.i18nc("@info:tooltip", "Check current setup for known problems.")
|
||||||
|
onClicked: manager.runChecks()
|
||||||
|
|
||||||
|
//anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
|
//anchors.right: parent.right
|
||||||
|
//anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: UM.Theme.getSize("save_button_specs_icons").width
|
||||||
|
height: UM.Theme.getSize("save_button_specs_icons").height
|
||||||
|
|
||||||
|
style: ButtonStyle
|
||||||
|
{
|
||||||
|
background: Item
|
||||||
|
{
|
||||||
|
UM.RecolorImage
|
||||||
|
{
|
||||||
|
width: UM.Theme.getSize("save_button_specs_icons").width;
|
||||||
|
height: UM.Theme.getSize("save_button_specs_icons").height;
|
||||||
|
sourceSize.width: width;
|
||||||
|
sourceSize.height: width;
|
||||||
|
color: control.hovered ? UM.Theme.getColor("text_scene_hover") : UM.Theme.getColor("text_scene");
|
||||||
|
source: "model_checker.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,14 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# This example is released under the terms of the AGPLv3 or higher.
|
# This example is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from . import ModelChecker
|
from . import ModelChecker
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
i18n_catalog = i18nCatalog("uranium")
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return { "tool": ModelChecker.ModelChecker() }
|
return { "extension": ModelChecker.ModelChecker() }
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<title>ModelChecker</title>
|
<title>ModelChecker</title>
|
||||||
<g id="ModelChecker" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g id="ModelChecker" stroke="none" fill="none">
|
||||||
<polygon id="Rectangle-3" fill="#000000" points="19 11 30 8 30 24 19 27"></polygon>
|
<polygon fill="#000000" points="19 11 30 8 30 24 19 27"></polygon>
|
||||||
<path d="M10,19 C5.581722,19 2,15.418278 2,11 C2,6.581722 5.581722,3 10,3 C14.418278,3 18,6.581722 18,11 C18,15.418278 14.418278,19 10,19 Z M10,17 C13.3137085,17 16,14.3137085 16,11 C16,7.6862915 13.3137085,5 10,5 C6.6862915,5 4,7.6862915 4,11 C4,14.3137085 6.6862915,17 10,17 Z" id="Combined-Shape" fill="#000000"></path>
|
<path d="M10,19 C5.581722,19 2,15.418278 2,11 C2,6.581722 5.581722,3 10,3 C14.418278,3 18,6.581722 18,11 C18,15.418278 14.418278,19 10,19 Z M10,17 C13.3137085,17 16,14.3137085 16,11 C16,7.6862915 13.3137085,5 10,5 C6.6862915,5 4,7.6862915 4,11 C4,14.3137085 6.6862915,17 10,17 Z" id="Combined-Shape" fill="#000000"></path>
|
||||||
<polygon id="Polygon" fill="#000000" points="4.2 15 6 16.8 1.8 21 0 19.2"></polygon>
|
<polygon fill="#000000" points="4.2 15 6 16.8 1.8 21 0 19.2"></polygon>
|
||||||
<path d="M18.7333454,8.81666365 C18.2107269,6.71940704 16.9524304,4.91317986 15.248379,3.68790525 L18,3 L30,6 L18.7333454,8.81666365 Z M17,16.6573343 L17,27 L6,24 L6,19.0644804 C7.20495897,19.6632939 8.56315852,20 10,20 C12.8272661,20 15.3500445,18.6963331 17,16.6573343 Z" id="Combined-Shape" fill="#000000"></path>
|
<path d="M18.7333454,8.81666365 C18.2107269,6.71940704 16.9524304,4.91317986 15.248379,3.68790525 L18,3 L30,6 L18.7333454,8.81666365 Z M17,16.6573343 L17,27 L6,24 L6,19.0644804 C7.20495897,19.6632939 8.56315852,20 10,20 C12.8272661,20 15.3500445,18.6963331 17,16.6573343 Z" id="Combined-Shape" fill="#000000"></path>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -124,15 +124,50 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: additionalComponentsRow
|
||||||
|
anchors.top: jobNameRow.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: boundingSpec
|
id: boundingSpec
|
||||||
anchors.top: jobNameRow.bottom
|
anchors.top: jobNameRow.bottom
|
||||||
anchors.right: parent.right
|
anchors.right: additionalComponentsRow.left
|
||||||
|
anchors.rightMargin:
|
||||||
|
{
|
||||||
|
if (additionalComponentsRow.width > 0)
|
||||||
|
{
|
||||||
|
return UM.Theme.getSize("default_margin").width
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
height: UM.Theme.getSize("jobspecs_line").height
|
height: UM.Theme.getSize("jobspecs_line").height
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
font: UM.Theme.getFont("small")
|
font: UM.Theme.getFont("small")
|
||||||
color: UM.Theme.getColor("text_scene")
|
color: UM.Theme.getColor("text_scene")
|
||||||
text: CuraApplication.getSceneBoundingBoxString
|
text: CuraApplication.getSceneBoundingBoxString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
base.addAdditionalComponents("jobSpecsButton")
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: CuraApplication
|
||||||
|
onAdditionalComponentsChanged: base.addAdditionalComponents("jobSpecsButton")
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAdditionalComponents (areaId) {
|
||||||
|
if(areaId == "jobSpecsButton") {
|
||||||
|
for (var component in CuraApplication.additionalComponents["jobSpecsButton"]) {
|
||||||
|
CuraApplication.additionalComponents["jobSpecsButton"][component].parent = additionalComponentsRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -411,6 +411,8 @@
|
||||||
"save_button_save_to_button": [0.3, 2.7],
|
"save_button_save_to_button": [0.3, 2.7],
|
||||||
"save_button_specs_icons": [1.4, 1.4],
|
"save_button_specs_icons": [1.4, 1.4],
|
||||||
|
|
||||||
|
"job_specs_button": [2.7, 2.7],
|
||||||
|
|
||||||
"monitor_preheat_temperature_control": [4.5, 2.0],
|
"monitor_preheat_temperature_control": [4.5, 2.0],
|
||||||
|
|
||||||
"modal_window_minimum": [60.0, 45],
|
"modal_window_minimum": [60.0, 45],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue