mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-04-30 15:54:32 +08:00
Merge branch 'master' into feature_sync_button
This commit is contained in:
commit
5280d21c26
5
.github/ISSUE_TEMPLATE.md
vendored
5
.github/ISSUE_TEMPLATE.md
vendored
@ -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.
|
||||
|
||||
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!
|
||||
-->
|
||||
@ -26,6 +26,9 @@ Thank you for using Cura!
|
||||
**Display Driver**
|
||||
<!-- Video driver name and version -->
|
||||
|
||||
**Printer**
|
||||
<!-- Which printer was selected in Cura. Please attach project file as .curaproject.3mf.zip -->
|
||||
|
||||
**Steps to Reproduce**
|
||||
<!-- Add the steps needed that lead up to the issue (replace this text) -->
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
|
||||
@ -25,6 +26,8 @@ class BaseMaterialsModel(ListModel):
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
self._application = Application.getInstance()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
|
||||
self.addRoleName(self.RootMaterialIdRole, "root_material_id")
|
||||
self.addRoleName(self.IdRole, "id")
|
||||
@ -35,12 +38,33 @@ class BaseMaterialsModel(ListModel):
|
||||
self.addRoleName(self.ContainerNodeRole, "container_node")
|
||||
|
||||
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):
|
||||
if self._extruder_position != position:
|
||||
self._extruder_position = position
|
||||
self._updateExtruderStack()
|
||||
self.extruderPositionChanged.emit()
|
||||
|
||||
@pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
|
||||
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
|
||||
|
@ -120,12 +120,19 @@ class BrandMaterialsModel(ListModel):
|
||||
material_type_item = {"name": material_type,
|
||||
"colors": BaseMaterialsModel(self)}
|
||||
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_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_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)
|
||||
|
@ -19,8 +19,6 @@ class ObjectsModel(ListModel):
|
||||
|
||||
self._build_plate_number = -1
|
||||
|
||||
self._stacks_have_errors = None # type:Optional[bool]
|
||||
|
||||
def setActiveBuildPlate(self, nr):
|
||||
self._build_plate_number = nr
|
||||
self._update()
|
||||
@ -69,11 +67,3 @@ class ObjectsModel(ListModel):
|
||||
@staticmethod
|
||||
def createObjectsModel():
|
||||
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
|
@ -968,30 +968,38 @@ class MachineManager(QObject):
|
||||
|
||||
## Update current quality type and machine after setting material
|
||||
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:
|
||||
current_quality = self._current_quality_group.quality_type
|
||||
quality_manager = Application.getInstance()._quality_manager
|
||||
candidate_quality_groups = quality_manager.getQualityGroups(self._global_container_stack)
|
||||
current_quality_type = self._current_quality_group.quality_type
|
||||
candidate_quality_groups = self._quality_manager.getQualityGroups(self._global_container_stack)
|
||||
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():
|
||||
Logger.log("i", "Active materials are not compatible, setting all qualities to empty (Not Supported).")
|
||||
self._setEmptyQuality()
|
||||
return
|
||||
|
||||
if not available_quality_types:
|
||||
Logger.log("i", "No available quality types found, setting all qualities to empty (Not Supported).")
|
||||
self._setEmptyQuality()
|
||||
return
|
||||
|
||||
if current_quality in available_quality_types:
|
||||
self._setQualityGroup(candidate_quality_groups[current_quality], empty_quality_changes = False)
|
||||
if current_quality_type in available_quality_types:
|
||||
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
|
||||
|
||||
# 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]
|
||||
preferred_quality_type = self._global_container_stack.getMetaDataEntry("preferred_quality_type")
|
||||
if preferred_quality_type in available_quality_types:
|
||||
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)
|
||||
|
||||
def _updateMaterialWithVariant(self, position: Optional[str]):
|
||||
@ -1006,9 +1014,8 @@ class MachineManager(QObject):
|
||||
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
|
||||
current_variant_name = extruder.variant.getMetaDataEntry("name")
|
||||
|
||||
material_manager = Application.getInstance()._material_manager
|
||||
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(),
|
||||
current_variant_name,
|
||||
material_diameter)
|
||||
@ -1065,7 +1072,7 @@ class MachineManager(QObject):
|
||||
|
||||
# See if we need to show the Discard or Keep changes screen
|
||||
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
|
||||
Application.getInstance().discardOrKeepProfileChanges()
|
||||
self._application.discardOrKeepProfileChanges()
|
||||
|
||||
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
|
||||
def activeQualityGroup(self):
|
||||
@ -1079,7 +1086,7 @@ class MachineManager(QObject):
|
||||
|
||||
# See if we need to show the Discard or Keep changes screen
|
||||
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
|
||||
Application.getInstance().discardOrKeepProfileChanges()
|
||||
self._application.discardOrKeepProfileChanges()
|
||||
|
||||
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
|
||||
def activeQualityChangesGroup(self):
|
||||
|
@ -9,8 +9,7 @@ from UM.Signal import Signal, signalemitter
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.Validator import ValidatorState
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
||||
from UM.Application import Application
|
||||
|
||||
from cura.Settings.PerObjectContainerStack import PerObjectContainerStack
|
||||
@ -40,10 +39,6 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
|
||||
|
||||
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)
|
||||
|
||||
@ -99,21 +94,9 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||
# Trigger slice/need slicing if the value has changed.
|
||||
if property_name == "value":
|
||||
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):
|
||||
hasErrors = False;
|
||||
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)
|
||||
Application.getInstance().getBackend().needsSlicing()
|
||||
Application.getInstance().getBackend().tickle()
|
||||
|
||||
## Makes sure that the stack upon which the container stack is placed is
|
||||
# kept up to date.
|
||||
|
@ -136,14 +136,10 @@ class StartSliceJob(Job):
|
||||
self.setResult(StartJobResult.MaterialIncompatible)
|
||||
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.
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
if node.isSelectable():
|
||||
if type(node) is not CuraSceneNode or not node.isSelectable():
|
||||
continue
|
||||
|
||||
if self._checkStackForErrors(node.callDecoration("getStack")):
|
||||
|
@ -25,20 +25,7 @@ UM.TooltipArea
|
||||
|
||||
onClicked:
|
||||
{
|
||||
// Important first set visible and then subscribe
|
||||
// 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);
|
||||
}
|
||||
addedSettingsModel.setVisible(model.key, checked);
|
||||
UM.ActiveTool.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
@ -240,10 +240,7 @@ Item {
|
||||
width: Math.round(UM.Theme.getSize("setting").height / 2)
|
||||
height: UM.Theme.getSize("setting").height
|
||||
|
||||
onClicked: {
|
||||
addedSettingsModel.setVisible(model.key, false)
|
||||
UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
|
||||
}
|
||||
onClicked: addedSettingsModel.setVisible(model.key, false)
|
||||
|
||||
style: ButtonStyle
|
||||
{
|
||||
|
@ -10,10 +10,7 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from UM.Settings.SettingInstance import SettingInstance
|
||||
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.
|
||||
# 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()
|
||||
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):
|
||||
super().event(event)
|
||||
@ -151,65 +142,3 @@ class PerObjectSettingsTool(Tool):
|
||||
else:
|
||||
self._single_model_selected = True
|
||||
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)
|
||||
|
@ -194,6 +194,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||
# the "reply" should be disconnected
|
||||
if self._latest_reply_handler:
|
||||
self._latest_reply_handler.disconnect()
|
||||
self._latest_reply_handler = None
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
|
@ -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.
|
||||
|
||||
import os.path
|
||||
@ -10,7 +10,7 @@ from UM.View.RenderPass import RenderPass
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
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
|
||||
|
||||
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)
|
||||
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())
|
||||
|
||||
self.bind()
|
||||
|
@ -13,9 +13,9 @@ vertex =
|
||||
}
|
||||
|
||||
fragment =
|
||||
uniform sampler2D u_layer0;
|
||||
uniform sampler2D u_layer1;
|
||||
uniform sampler2D u_layer2;
|
||||
uniform sampler2D u_layer0; //Default pass.
|
||||
uniform sampler2D u_layer1; //Selection pass.
|
||||
uniform sampler2D u_layer2; //X-ray pass.
|
||||
|
||||
uniform vec2 u_offset[9];
|
||||
|
||||
@ -83,9 +83,9 @@ vertex41core =
|
||||
|
||||
fragment41core =
|
||||
#version 410
|
||||
uniform sampler2D u_layer0;
|
||||
uniform sampler2D u_layer1;
|
||||
uniform sampler2D u_layer2;
|
||||
uniform sampler2D u_layer0; //Default pass.
|
||||
uniform sampler2D u_layer1; //Selection pass.
|
||||
uniform sampler2D u_layer2; //X-ray pass.
|
||||
|
||||
uniform vec2 u_offset[9];
|
||||
|
||||
|
@ -52,6 +52,7 @@ Menu
|
||||
{
|
||||
text: model.name
|
||||
checkable: model.available
|
||||
enabled: model.available
|
||||
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
|
||||
exclusiveGroup: group
|
||||
onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)
|
||||
|
@ -47,6 +47,19 @@ TabView
|
||||
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
|
||||
{
|
||||
title: catalog.i18nc("@title", "Information")
|
||||
|
@ -52,6 +52,24 @@ Item
|
||||
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
|
||||
{
|
||||
id: buttonRow
|
||||
|
Loading…
x
Reference in New Issue
Block a user