Merge branch 'master' into feature_gz_reader

This commit is contained in:
Jack Ha 2018-03-22 14:26:29 +01:00
commit a0badf121a
16 changed files with 323 additions and 11 deletions

View file

@ -85,7 +85,7 @@ class CrashHandler:
dialog = QDialog()
dialog.setMinimumWidth(500)
dialog.setMinimumHeight(170)
dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura can't startup"))
dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura can't start"))
dialog.finished.connect(self._closeEarlyCrashDialog)
layout = QVBoxLayout(dialog)

View file

@ -498,8 +498,13 @@ class CuraApplication(QtApplication):
def getStaticVersion(cls):
return CuraVersion
## Handle removing the unneeded plugins
# \sa PluginRegistry
def _removePlugins(self):
self._plugin_registry.removePlugins()
## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistery
# \sa PluginRegistry
def _loadPlugins(self):
self._plugin_registry.addType("profile_reader", self._addProfileReader)
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
@ -1280,8 +1285,11 @@ class CuraApplication(QtApplication):
def reloadAll(self):
Logger.log("i", "Reloading all loaded mesh data.")
nodes = []
has_merged_nodes = False
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if not isinstance(node, CuraSceneNode) or not node.getMeshData():
if not isinstance(node, CuraSceneNode) or not node.getMeshData() :
if node.getName() == "MergedMesh":
has_merged_nodes = True
continue
nodes.append(node)
@ -1295,10 +1303,14 @@ class CuraApplication(QtApplication):
job = ReadMeshJob(file_name)
job._node = node
job.finished.connect(self._reloadMeshFinished)
if has_merged_nodes:
job.finished.connect(self.updateOriginOfMergedMeshes)
job.start()
else:
Logger.log("w", "Unable to reload data because we don't have a filename.")
## Get logging data of the backend engine
# \returns \type{string} Logging data
@pyqtSlot(result = str)
@ -1368,6 +1380,58 @@ class CuraApplication(QtApplication):
# Use the previously found center of the group bounding box as the new location of the group
group_node.setPosition(group_node.getBoundingBox().center)
group_node.setName("MergedMesh") # add a specific name to distinguish this node
## Updates origin position of all merged meshes
# \param jobNode \type{Job} empty object which passed which is required by JobQueue
def updateOriginOfMergedMeshes(self, jobNode):
group_nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if isinstance(node, CuraSceneNode) and node.getName() == "MergedMesh":
#checking by name might be not enough, the merged mesh should has "GroupDecorator" decorator
for decorator in node.getDecorators():
if isinstance(decorator, GroupDecorator):
group_nodes.append(node)
break
for group_node in group_nodes:
meshes = [node.getMeshData() for node in group_node.getAllChildren() if node.getMeshData()]
# Compute the center of the objects
object_centers = []
# Forget about the translation that the original objects have
zero_translation = Matrix(data=numpy.zeros(3))
for mesh, node in zip(meshes, group_node.getChildren()):
transformation = node.getLocalTransformation()
transformation.setTranslation(zero_translation)
transformed_mesh = mesh.getTransformed(transformation)
center = transformed_mesh.getCenterPosition()
if center is not None:
object_centers.append(center)
if object_centers and len(object_centers) > 0:
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
offset = Vector(middle_x, middle_y, middle_z)
else:
offset = Vector(0, 0, 0)
# Move each node to the same position.
for mesh, node in zip(meshes, group_node.getChildren()):
transformation = node.getLocalTransformation()
transformation.setTranslation(zero_translation)
transformed_mesh = mesh.getTransformed(transformation)
# Align the object around its zero position
# and also apply the offset to center it inside the group.
node.setPosition(-transformed_mesh.getZeroPosition() - offset)
# Use the previously found center of the group bounding box as the new location of the group
group_node.setPosition(group_node.getBoundingBox().center)
@pyqtSlot()
def groupSelected(self):

View file

@ -69,7 +69,7 @@ class FirmwareUpdateCheckerJob(Job):
# If we do this in a cool way, the download url should be available in the JSON file
if self._set_download_url_callback:
self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware")
self._set_download_url_callback("https://ultimaker.com/en/resources/23129-updating-the-firmware?utm_source=cura&utm_medium=software&utm_campaign=hw-update")
message.actionTriggered.connect(self._callback)
message.show()

View file

@ -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

View file

@ -0,0 +1,43 @@
// 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.runChecks
tooltip: catalog.i18nc("@info:tooltip", "Some things could be problematic in this print. Click to see tips for adjustment.")
onClicked: manager.showWarnings()
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"
}
}
}
}

View file

@ -0,0 +1,14 @@
# Copyright (c) 2018 Ultimaker B.V.
# This example is released under the terms of the AGPLv3 or higher.
from . import ModelChecker
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {}
def register(app):
return { "extension": ModelChecker.ModelChecker() }

View file

@ -0,0 +1,7 @@
<?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">
<polygon fill="#000000" points="19 11 30 8 30 24 19 27" />
<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" fill="#000000" />
<polygon fill="#000000" points="4.2 15 6 16.8 1.8 21 0 19.2" />
<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" fill="#000000" />
</svg>

After

Width:  |  Height:  |  Size: 877 B

View file

@ -0,0 +1,8 @@
{
"name": "Model Checker",
"author": "Ultimaker B.V.",
"version": "0.1",
"api": 4,
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
"i18n-catalog": "cura"
}

View file

@ -27,18 +27,18 @@ class FilamentChange(Script):
"initial_retract":
{
"label": "Initial Retraction",
"description": "Initial filament retraction distance",
"description": "Initial filament retraction distance. The filament will be retracted with this amount before moving the nozzle away from the ongoing print.",
"unit": "mm",
"type": "float",
"default_value": 300.0
"default_value": 30.0
},
"later_retract":
{
"label": "Later Retraction Distance",
"description": "Later filament retraction distance for removal",
"description": "Later filament retraction distance for removal. The filament will be retracted all the way out of the printer so that you can change the filament.",
"unit": "mm",
"type": "float",
"default_value": 30.0
"default_value": 300.0
}
}
}"""

View file

@ -74,7 +74,7 @@ class SimulationView(View):
self._global_container_stack = None
self._proxy = SimulationViewProxy()
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._controller.getScene().sceneChanged.connect(self._onSceneChanged)
self._resetSettings()
self._legend_items = None

View file

@ -127,6 +127,9 @@ class VersionUpgrade32to33(VersionUpgrade):
parser["metadata"]["position"] = str(extruder_position)
del parser["metadata"]["extruder"]
quality_type = parser["metadata"]["quality_type"]
parser["metadata"]["quality_type"] = quality_type.lower()
#Update version number.
parser["general"]["version"] = "3"

View file

@ -983,7 +983,8 @@ class XmlMaterialProfile(InstanceContainer):
"retraction amount": "retraction_amount",
"retraction speed": "retraction_speed",
"adhesion tendency": "material_adhesion_tendency",
"surface energy": "material_surface_energy"
"surface energy": "material_surface_energy",
"shrinkage percentage": "material_shrinkage_percentage",
}
__unmapped_settings = [
"hardware compatible",

View file

@ -2097,6 +2097,19 @@
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_shrinkage_percentage":
{
"label": "Shrinkage Ratio",
"description": "Shrinkage ratio in percentage.",
"unit": "%",
"type": "float",
"default_value": 0,
"minimum_value": "0",
"maximum_value": "100",
"enabled": false,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_flow":
{
"label": "Flow",

View file

@ -115,15 +115,50 @@ Item {
}
}
Row {
id: additionalComponentsRow
anchors.top: jobNameRow.bottom
anchors.right: parent.right
}
Label
{
id: boundingSpec
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
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("small")
color: UM.Theme.getColor("text_scene")
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
}
}
}
}

View file

@ -98,4 +98,10 @@ Button
target: Cura.MachineManager
onCurrentConfigurationChanged: updateOnSync()
}
Connections
{
target: Cura.MachineManager
onOutputDevicesChanged: updateOnSync()
}
}

View file

@ -411,6 +411,8 @@
"save_button_save_to_button": [0.3, 2.7],
"save_button_specs_icons": [1.4, 1.4],
"job_specs_button": [2.7, 2.7],
"monitor_preheat_temperature_control": [4.5, 2.0],
"modal_window_minimum": [60.0, 45],