Merge branch 'master' into feature_sync_button

This commit is contained in:
Diego Prado Gesto 2018-03-06 16:10:44 +01:00
commit 5280d21c26
16 changed files with 101 additions and 145 deletions

View File

@ -6,7 +6,7 @@ Before filing, PLEASE check if the issue already exists (either open or closed)
Also, please note the application version in the title of the issue. For example: "[3.2.1] Cannot connect to 3rd-party printer". Please do not write thigns like "Request:" or "[BUG]" in the title; this is what labels are for. Also, please note the application version in the title of the issue. For example: "[3.2.1] Cannot connect to 3rd-party printer". Please do not write thigns like "Request:" or "[BUG]" in the title; this is what labels are for.
It is also helpful to attach a project (.3mf or .curaproject) file and Cura log file so we can debug issues quicker. It is also helpful to attach a project (.3mf or .curaproject) file and Cura log file so we can debug issues quicker.
Information about how to find the log file can be found at https://github.com/Ultimaker/Cura/wiki/Cura-Preferences-and-Settings-Locations. To upload a project, we recommend http://wetransfer.com, but other file hosts like Google Drive or Dropbox work well too. Information about how to find the log file can be found at https://github.com/Ultimaker/Cura/wiki/Cura-Preferences-and-Settings-Locations. To upload a project, try changing the extension to e.g. .curaproject.3mf.zip so that github accepts uploading the file. Otherwise we recommend http://wetransfer.com, but other file hosts like Google Drive or Dropbox work well too.
Thank you for using Cura! Thank you for using Cura!
--> -->
@ -26,6 +26,9 @@ Thank you for using Cura!
**Display Driver** **Display Driver**
<!-- Video driver name and version --> <!-- Video driver name and version -->
**Printer**
<!-- Which printer was selected in Cura. Please attach project file as .curaproject.3mf.zip -->
**Steps to Reproduce** **Steps to Reproduce**
<!-- Add the steps needed that lead up to the issue (replace this text) --> <!-- Add the steps needed that lead up to the issue (replace this text) -->

View File

@ -3,6 +3,7 @@
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from UM.Application import Application
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
@ -25,6 +26,8 @@ class BaseMaterialsModel(ListModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._application = Application.getInstance()
self._machine_manager = self._application.getMachineManager()
self.addRoleName(self.RootMaterialIdRole, "root_material_id") self.addRoleName(self.RootMaterialIdRole, "root_material_id")
self.addRoleName(self.IdRole, "id") self.addRoleName(self.IdRole, "id")
@ -35,12 +38,33 @@ class BaseMaterialsModel(ListModel):
self.addRoleName(self.ContainerNodeRole, "container_node") self.addRoleName(self.ContainerNodeRole, "container_node")
self._extruder_position = 0 self._extruder_position = 0
self._extruder_stack = None
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
def _updateExtruderStack(self):
global_stack = self._machine_manager.activeMachine
if global_stack is None:
return
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.disconnect(self._update)
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.connect(self._update)
def setExtruderPosition(self, position: int): def setExtruderPosition(self, position: int):
if self._extruder_position != position: if self._extruder_position != position:
self._extruder_position = position self._extruder_position = position
self._updateExtruderStack()
self.extruderPositionChanged.emit() self.extruderPositionChanged.emit()
@pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged) @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
def extruderPosition(self) -> int: def extruderPosition(self) -> int:
return self._extruder_positoin return self._extruder_position
#
# This is an abstract method that needs to be implemented by
#
def _update(self):
pass

View File

@ -120,12 +120,19 @@ class BrandMaterialsModel(ListModel):
material_type_item = {"name": material_type, material_type_item = {"name": material_type,
"colors": BaseMaterialsModel(self)} "colors": BaseMaterialsModel(self)}
material_type_item["colors"].clear() material_type_item["colors"].clear()
# Sort materials by name
material_list = sorted(material_list, key = lambda x: x["name"])
material_type_item["colors"].setItems(material_list) material_type_item["colors"].setItems(material_list)
material_type_item_list.append(material_type_item) material_type_item_list.append(material_type_item)
# Sort material type by name
material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"])
brand_item["materials"].setItems(material_type_item_list) brand_item["materials"].setItems(material_type_item_list)
brand_item_list.append(brand_item) brand_item_list.append(brand_item)
# Sort brand by name
brand_item_list = sorted(brand_item_list, key = lambda x: x["name"])
self.setItems(brand_item_list) self.setItems(brand_item_list)

View File

@ -19,8 +19,6 @@ class ObjectsModel(ListModel):
self._build_plate_number = -1 self._build_plate_number = -1
self._stacks_have_errors = None # type:Optional[bool]
def setActiveBuildPlate(self, nr): def setActiveBuildPlate(self, nr):
self._build_plate_number = nr self._build_plate_number = nr
self._update() self._update()
@ -69,11 +67,3 @@ class ObjectsModel(ListModel):
@staticmethod @staticmethod
def createObjectsModel(): def createObjectsModel():
return ObjectsModel() return ObjectsModel()
## Check if none of the model's stacks contain error states
# The setting applied for the settings per model
def stacksHaveErrors(self) -> bool:
return bool(self._stacks_have_errors)
def setStacksHaveErrors(self, value):
self._stacks_have_errors = value

View File

@ -968,30 +968,38 @@ class MachineManager(QObject):
## Update current quality type and machine after setting material ## Update current quality type and machine after setting material
def _updateQualityWithMaterial(self): def _updateQualityWithMaterial(self):
current_quality = None Logger.log("i", "Updating quality/quality_changes due to material change")
current_quality_type = None
if self._current_quality_group: if self._current_quality_group:
current_quality = self._current_quality_group.quality_type current_quality_type = self._current_quality_group.quality_type
quality_manager = Application.getInstance()._quality_manager candidate_quality_groups = self._quality_manager.getQualityGroups(self._global_container_stack)
candidate_quality_groups = quality_manager.getQualityGroups(self._global_container_stack)
available_quality_types = {qt for qt, g in candidate_quality_groups.items() if g.is_available} available_quality_types = {qt for qt, g in candidate_quality_groups.items() if g.is_available}
Logger.log("d", "Current quality type = [%s]", current_quality_type)
if not self.activeMaterialsCompatible(): if not self.activeMaterialsCompatible():
Logger.log("i", "Active materials are not compatible, setting all qualities to empty (Not Supported).")
self._setEmptyQuality() self._setEmptyQuality()
return return
if not available_quality_types: if not available_quality_types:
Logger.log("i", "No available quality types found, setting all qualities to empty (Not Supported).")
self._setEmptyQuality() self._setEmptyQuality()
return return
if current_quality in available_quality_types: if current_quality_type in available_quality_types:
self._setQualityGroup(candidate_quality_groups[current_quality], empty_quality_changes = False) Logger.log("i", "Current available quality type [%s] is available, applying changes.", current_quality_type)
self._setQualityGroup(candidate_quality_groups[current_quality_type], empty_quality_changes = False)
return return
# The current quality type is not available so we use the preferred quality type if it's available,
# otherwise use one of the available quality types.
quality_type = sorted(list(available_quality_types))[0] quality_type = sorted(list(available_quality_types))[0]
preferred_quality_type = self._global_container_stack.getMetaDataEntry("preferred_quality_type") preferred_quality_type = self._global_container_stack.getMetaDataEntry("preferred_quality_type")
if preferred_quality_type in available_quality_types: if preferred_quality_type in available_quality_types:
quality_type = preferred_quality_type quality_type = preferred_quality_type
Logger.log("i", "The current quality type [%s] is not available, switching to [%s] instead",
current_quality_type, quality_type)
self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True) self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True)
def _updateMaterialWithVariant(self, position: Optional[str]): def _updateMaterialWithVariant(self, position: Optional[str]):
@ -1006,9 +1014,8 @@ class MachineManager(QObject):
current_material_base_name = extruder.material.getMetaDataEntry("base_file") current_material_base_name = extruder.material.getMetaDataEntry("base_file")
current_variant_name = extruder.variant.getMetaDataEntry("name") current_variant_name = extruder.variant.getMetaDataEntry("name")
material_manager = Application.getInstance()._material_manager
material_diameter = self._global_container_stack.getProperty("material_diameter", "value") material_diameter = self._global_container_stack.getProperty("material_diameter", "value")
candidate_materials = material_manager.getAvailableMaterials( candidate_materials = self._material_manager.getAvailableMaterials(
self._global_container_stack.definition.getId(), self._global_container_stack.definition.getId(),
current_variant_name, current_variant_name,
material_diameter) material_diameter)
@ -1065,7 +1072,7 @@ class MachineManager(QObject):
# See if we need to show the Discard or Keep changes screen # See if we need to show the Discard or Keep changes screen
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
Application.getInstance().discardOrKeepProfileChanges() self._application.discardOrKeepProfileChanges()
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged) @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
def activeQualityGroup(self): def activeQualityGroup(self):
@ -1079,7 +1086,7 @@ class MachineManager(QObject):
# See if we need to show the Discard or Keep changes screen # See if we need to show the Discard or Keep changes screen
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
Application.getInstance().discardOrKeepProfileChanges() self._application.discardOrKeepProfileChanges()
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged) @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
def activeQualityChangesGroup(self): def activeQualityChangesGroup(self):

View File

@ -9,8 +9,7 @@ from UM.Signal import Signal, signalemitter
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Logger import Logger from UM.Logger import Logger
from UM.Settings.Validator import ValidatorState
from PyQt5.QtCore import QTimer
from UM.Application import Application from UM.Application import Application
from cura.Settings.PerObjectContainerStack import PerObjectContainerStack from cura.Settings.PerObjectContainerStack import PerObjectContainerStack
@ -40,10 +39,6 @@ class SettingOverrideDecorator(SceneNodeDecorator):
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
self._is_non_printing_mesh = False self._is_non_printing_mesh = False
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._checkStackForErrors)
self._stack.propertyChanged.connect(self._onSettingChanged) self._stack.propertyChanged.connect(self._onSettingChanged)
@ -99,21 +94,9 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# Trigger slice/need slicing if the value has changed. # Trigger slice/need slicing if the value has changed.
if property_name == "value": if property_name == "value":
self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings) self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
if not self._is_non_printing_mesh:
# self._error_check_timer.start()
self._checkStackForErrors()
Application.getInstance().getBackend().needsSlicing()
Application.getInstance().getBackend().tickle()
def _checkStackForErrors(self): Application.getInstance().getBackend().needsSlicing()
hasErrors = False; Application.getInstance().getBackend().tickle()
for key in self._stack.getAllKeys():
validation_state = self._stack.getProperty(key, "validationState")
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
Logger.log("w", "Setting Per Object %s is not valid.", key)
hasErrors = True
break
Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors)
## Makes sure that the stack upon which the container stack is placed is ## Makes sure that the stack upon which the container stack is placed is
# kept up to date. # kept up to date.

View File

@ -136,14 +136,10 @@ class StartSliceJob(Job):
self.setResult(StartJobResult.MaterialIncompatible) self.setResult(StartJobResult.MaterialIncompatible)
return return
# Validate settings per selectable model
if Application.getInstance().getObjectsModel().stacksHaveErrors():
self.setResult(StartJobResult.ObjectSettingError)
return
# Don't slice if there is a per object setting with an error value. # Don't slice if there is a per object setting with an error value.
for node in DepthFirstIterator(self._scene.getRoot()): for node in DepthFirstIterator(self._scene.getRoot()):
if node.isSelectable(): if type(node) is not CuraSceneNode or not node.isSelectable():
continue continue
if self._checkStackForErrors(node.callDecoration("getStack")): if self._checkStackForErrors(node.callDecoration("getStack")):

View File

@ -25,20 +25,7 @@ UM.TooltipArea
onClicked: onClicked:
{ {
// Important first set visible and then subscribe addedSettingsModel.setVisible(model.key, checked);
// otherwise the setting is not yet in list
// For unsubscribe is important first remove the subscription and then
// set as invisible
if(checked)
{
addedSettingsModel.setVisible(model.key, checked);
UM.ActiveTool.triggerActionWithData("subscribeForSettingValidation", model.key)
}
else
{
UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
addedSettingsModel.setVisible(model.key, checked);
}
UM.ActiveTool.forceUpdate(); UM.ActiveTool.forceUpdate();
} }
} }

View File

@ -240,10 +240,7 @@ Item {
width: Math.round(UM.Theme.getSize("setting").height / 2) width: Math.round(UM.Theme.getSize("setting").height / 2)
height: UM.Theme.getSize("setting").height height: UM.Theme.getSize("setting").height
onClicked: { onClicked: addedSettingsModel.setVisible(model.key, false)
addedSettingsModel.setVisible(model.key, false)
UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
}
style: ButtonStyle style: ButtonStyle
{ {

View File

@ -10,10 +10,7 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.SettingInstance import SettingInstance from UM.Settings.SettingInstance import SettingInstance
from UM.Event import Event from UM.Event import Event
from UM.Settings.Validator import ValidatorState
from UM.Logger import Logger
from PyQt5.QtCore import QTimer
## This tool allows the user to add & change settings per node in the scene. ## This tool allows the user to add & change settings per node in the scene.
# The settings per object are kept in a ContainerStack, which is linked to a node by decorator. # The settings per object are kept in a ContainerStack, which is linked to a node by decorator.
@ -37,12 +34,6 @@ class PerObjectSettingsTool(Tool):
self._onGlobalContainerChanged() self._onGlobalContainerChanged()
Selection.selectionChanged.connect(self._updateEnabled) Selection.selectionChanged.connect(self._updateEnabled)
self._scene = Application.getInstance().getController().getScene()
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
def event(self, event): def event(self, event):
super().event(event) super().event(event)
@ -151,65 +142,3 @@ class PerObjectSettingsTool(Tool):
else: else:
self._single_model_selected = True self._single_model_selected = True
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected) Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected)
def _onPropertyChanged(self, key: str, property_name: str) -> None:
if property_name == "validationState":
# self._error_check_timer.start()
return
def _updateStacksHaveErrors(self) -> None:
return
# self._checkStacksHaveErrors()
def _checkStacksHaveErrors(self):
for node in DepthFirstIterator(self._scene.getRoot()):
# valdiate only objects which can be selected because the settings per object
# can be applied only for them
if not node.isSelectable():
continue
hasErrors = self._checkStackForErrors(node.callDecoration("getStack"))
Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors)
#If any of models has an error then no reason check next objects on the build plate
if hasErrors:
break
def _checkStackForErrors(self, stack):
print("checking for errors")
if stack is None:
return False
for key in stack.getAllKeys():
validation_state = stack.getProperty(key, "validationState")
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
Logger.log("w", "Setting Per Object %s is not valid.", key)
return True
return False
def subscribeForSettingValidation(self, setting_name):
selected_object = Selection.getSelectedObject(0)
stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway.
if not stack:
return ""
settings = stack.getTop()
setting_instance = settings.getInstance(setting_name)
if setting_instance:
setting_instance.propertyChanged.connect(self._onPropertyChanged)
def unsubscribeForSettingValidation(self, setting_name):
selected_object = Selection.getSelectedObject(0)
stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway.
if not stack:
return ""
settings = stack.getTop()
setting_instance = settings.getInstance(setting_name)
if setting_instance:
setting_instance.propertyChanged.disconnect(self._onPropertyChanged)

View File

@ -194,6 +194,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# the "reply" should be disconnected # the "reply" should be disconnected
if self._latest_reply_handler: if self._latest_reply_handler:
self._latest_reply_handler.disconnect() self._latest_reply_handler.disconnect()
self._latest_reply_handler = None
@pyqtSlot() @pyqtSlot()

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015 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.
import os.path import os.path
@ -10,7 +10,7 @@ from UM.View.RenderPass import RenderPass
from UM.View.RenderBatch import RenderBatch from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL from UM.View.GL.OpenGL import OpenGL
from UM.Scene.SceneNode import SceneNode from cura.Scene.CuraSceneNode import CuraSceneNode
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
class XRayPass(RenderPass): class XRayPass(RenderPass):
@ -27,7 +27,7 @@ class XRayPass(RenderPass):
batch = RenderBatch(self._shader, type = RenderBatch.RenderType.NoType, backface_cull = False, blend_mode = RenderBatch.BlendMode.Additive) batch = RenderBatch(self._shader, type = RenderBatch.RenderType.NoType, backface_cull = False, blend_mode = RenderBatch.BlendMode.Additive)
for node in DepthFirstIterator(self._scene.getRoot()): for node in DepthFirstIterator(self._scene.getRoot()):
if type(node) is SceneNode and node.getMeshData() and node.isVisible(): if isinstance(node, CuraSceneNode) and node.getMeshData() and node.isVisible():
batch.addItem(node.getWorldTransformation(), node.getMeshData()) batch.addItem(node.getWorldTransformation(), node.getMeshData())
self.bind() self.bind()

View File

@ -13,9 +13,9 @@ vertex =
} }
fragment = fragment =
uniform sampler2D u_layer0; uniform sampler2D u_layer0; //Default pass.
uniform sampler2D u_layer1; uniform sampler2D u_layer1; //Selection pass.
uniform sampler2D u_layer2; uniform sampler2D u_layer2; //X-ray pass.
uniform vec2 u_offset[9]; uniform vec2 u_offset[9];
@ -83,9 +83,9 @@ vertex41core =
fragment41core = fragment41core =
#version 410 #version 410
uniform sampler2D u_layer0; uniform sampler2D u_layer0; //Default pass.
uniform sampler2D u_layer1; uniform sampler2D u_layer1; //Selection pass.
uniform sampler2D u_layer2; uniform sampler2D u_layer2; //X-ray pass.
uniform vec2 u_offset[9]; uniform vec2 u_offset[9];

View File

@ -52,6 +52,7 @@ Menu
{ {
text: model.name text: model.name
checkable: model.available checkable: model.available
enabled: model.available
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
exclusiveGroup: group exclusiveGroup: group
onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group) onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)

View File

@ -47,6 +47,19 @@ TabView
return Math.round(diameter); return Math.round(diameter);
} }
// This trick makes sure to make all fields lose focus so their onEditingFinished will be triggered
// and modified values will be saved. This can happen when a user changes a value and then closes the
// dialog directly.
//
// Please note that somehow this callback is ONLY triggered when visible is false.
onVisibleChanged:
{
if (!visible)
{
base.focus = false;
}
}
Tab Tab
{ {
title: catalog.i18nc("@title", "Information") title: catalog.i18nc("@title", "Information")

View File

@ -52,6 +52,24 @@ Item
return base.currentItem.root_material_id == root_material_id; return base.currentItem.root_material_id == root_material_id;
} }
Component.onCompleted:
{
// Select the activated material when this page shows up
const extruder_position = Cura.ExtruderManager.activeExtruderIndex;
const active_root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position];
var itemIndex = -1;
for (var i = 0; i < materialsModel.rowCount(); ++i)
{
var item = materialsModel.getItem(i);
if (item.root_material_id == active_root_material_id)
{
itemIndex = i;
break;
}
}
materialListView.currentIndex = itemIndex;
}
Row // Button Row Row // Button Row
{ {
id: buttonRow id: buttonRow