Merge branch 'master' of github.com:Ultimaker/Cura

This commit is contained in:
Jaime van Kessel 2018-03-16 15:58:47 +01:00
commit f407663508
67 changed files with 1613 additions and 621 deletions

View File

@ -136,6 +136,7 @@ class BuildVolume(SceneNode):
if active_extruder_changed is not None: if active_extruder_changed is not None:
node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild) node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild)
node.decoratorsChanged.disconnect(self._updateNodeListeners) node.decoratorsChanged.disconnect(self._updateNodeListeners)
self._updateDisallowedAreasAndRebuild() # make sure we didn't miss anything before we updated the node listeners
self._scene_objects = new_scene_objects self._scene_objects = new_scene_objects
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered. self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
@ -150,7 +151,6 @@ class BuildVolume(SceneNode):
active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal") active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal")
if active_extruder_changed is not None: if active_extruder_changed is not None:
active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild) active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild)
self._updateDisallowedAreasAndRebuild()
def setWidth(self, width): def setWidth(self, width):
if width is not None: if width is not None:

View File

@ -109,10 +109,6 @@ class CuraActions(QObject):
nodes_to_change = [] nodes_to_change = []
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():
# Do not change any nodes that already have the right extruder set.
if node.callDecoration("getActiveExtruder") == extruder_id:
continue
# If the node is a group, apply the active extruder to all children of the group. # If the node is a group, apply the active extruder to all children of the group.
if node.callDecoration("isGroup"): if node.callDecoration("isGroup"):
for grouped_node in BreadthFirstIterator(node): for grouped_node in BreadthFirstIterator(node):
@ -125,6 +121,10 @@ class CuraActions(QObject):
nodes_to_change.append(grouped_node) nodes_to_change.append(grouped_node)
continue continue
# Do not change any nodes that already have the right extruder set.
if node.callDecoration("getActiveExtruder") == extruder_id:
continue
nodes_to_change.append(node) nodes_to_change.append(node)
if not nodes_to_change: if not nodes_to_change:

View File

@ -1,10 +1,7 @@
# 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.
#Type hinting. from PyQt5.QtCore import QObject, QTimer
from typing import Dict
from PyQt5.QtCore import QObject
from PyQt5.QtNetwork import QLocalServer from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket from PyQt5.QtNetwork import QLocalSocket
@ -68,6 +65,8 @@ from cura.Machines.Models.QualityManagementModel import QualityManagementModel
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
from cura.Machines.Models.MachineManagementModel import MachineManagementModel from cura.Machines.Models.MachineManagementModel import MachineManagementModel
from cura.Machines.Models.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
from cura.Machines.MachineErrorChecker import MachineErrorChecker from cura.Machines.MachineErrorChecker import MachineErrorChecker
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
@ -91,7 +90,6 @@ from cura.Settings.UserChangesModel import UserChangesModel
from cura.Settings.ExtrudersModel import ExtrudersModel from cura.Settings.ExtrudersModel import ExtrudersModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Settings.ContainerManager import ContainerManager from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
from cura.ObjectsModel import ObjectsModel from cura.ObjectsModel import ObjectsModel
@ -101,7 +99,6 @@ from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
from configparser import ConfigParser
import sys import sys
import os.path import os.path
import numpy import numpy
@ -226,6 +223,7 @@ class CuraApplication(QtApplication):
self._object_manager = None self._object_manager = None
self._build_plate_model = None self._build_plate_model = None
self._multi_build_plate_model = None self._multi_build_plate_model = None
self._setting_visibility_presets_model = None
self._setting_inheritance_manager = None self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None self._simple_mode_settings_manager = None
self._cura_scene_controller = None self._cura_scene_controller = None
@ -286,10 +284,15 @@ class CuraApplication(QtApplication):
self._preferred_mimetype = "" self._preferred_mimetype = ""
self._i18n_catalog = i18nCatalog("cura") self._i18n_catalog = i18nCatalog("cura")
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity) self._update_platform_activity_timer = QTimer()
self._update_platform_activity_timer.setInterval(500)
self._update_platform_activity_timer.setSingleShot(True)
self._update_platform_activity_timer.timeout.connect(self.updatePlatformActivity)
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivityDelayed)
self.getController().toolOperationStopped.connect(self._onToolOperationStopped) self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
self.getController().contextMenuRequested.connect(self._onContextMenuRequested) self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivity) self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed)
Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware") Resources.addType(self.ResourceTypes.Firmware, "firmware")
@ -376,10 +379,6 @@ class CuraApplication(QtApplication):
preferences.setDefault("local_file/last_used_type", "text/x-gcode") preferences.setDefault("local_file/last_used_type", "text/x-gcode")
default_visibility_profile = SettingVisibilityPresetsModel.getInstance().getItem(0)
preferences.setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
self.applicationShuttingDown.connect(self.saveSettings) self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated) self.engineCreatedSignal.connect(self._onEngineCreated)
@ -452,27 +451,18 @@ class CuraApplication(QtApplication):
@pyqtSlot(str) @pyqtSlot(str)
def discardOrKeepProfileChangesClosed(self, option): def discardOrKeepProfileChangesClosed(self, option):
if option == "discard":
global_stack = self.getGlobalContainerStack() global_stack = self.getGlobalContainerStack()
for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()): if option == "discard":
extruder.getTop().clear() for extruder in global_stack.extruders.values():
global_stack.getTop().clear() extruder.userChanges.clear()
global_stack.userChanges.clear()
# if the user decided to keep settings then the user settings should be re-calculated and validated for errors # if the user decided to keep settings then the user settings should be re-calculated and validated for errors
# before slicing. To ensure that slicer uses right settings values # before slicing. To ensure that slicer uses right settings values
elif option == "keep": elif option == "keep":
global_stack = self.getGlobalContainerStack() for extruder in global_stack.extruders.values():
for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()): extruder.userChanges.update()
user_extruder_container = extruder.getTop() global_stack.userChanges.update()
if user_extruder_container:
user_extruder_container.update()
user_global_container = global_stack.getTop()
if user_global_container:
user_global_container.update()
# notify listeners that quality has changed (after user selected discard or keep)
self.getMachineManager().activeQualityChanged.emit()
@pyqtSlot(int) @pyqtSlot(int)
def messageBoxClosed(self, button): def messageBoxClosed(self, button):
@ -682,6 +672,11 @@ class CuraApplication(QtApplication):
self._print_information = PrintInformation.PrintInformation() self._print_information = PrintInformation.PrintInformation()
self._cura_actions = CuraActions.CuraActions(self) self._cura_actions = CuraActions.CuraActions(self)
# Initialize setting visibility presets model
self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self)
default_visibility_profile = self._setting_visibility_presets_model.getItem(0)
Preferences.getInstance().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
# Detect in which mode to run and execute that mode # Detect in which mode to run and execute that mode
if self.getCommandLineOption("headless", False): if self.getCommandLineOption("headless", False):
self.runWithoutGUI() self.runWithoutGUI()
@ -764,6 +759,10 @@ class CuraApplication(QtApplication):
def hasGui(self): def hasGui(self):
return self._use_gui return self._use_gui
@pyqtSlot(result = QObject)
def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
return self._setting_visibility_presets_model
def getMachineErrorChecker(self, *args) -> MachineErrorChecker: def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
return self._machine_error_checker return self._machine_error_checker
@ -890,11 +889,11 @@ class CuraApplication(QtApplication):
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel") qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
qmlRegisterType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel")
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel") qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
qmlRegisterSingletonType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel", SettingVisibilityPresetsModel.createSettingVisibilityPresetsModel)
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
@ -970,6 +969,10 @@ class CuraApplication(QtApplication):
def getSceneBoundingBoxString(self): def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()} return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
def updatePlatformActivityDelayed(self, node = None):
if node is not None and node.getMeshData() is not None:
self._update_platform_activity_timer.start()
## Update scene bounding box for current build plate ## Update scene bounding box for current build plate
def updatePlatformActivity(self, node = None): def updatePlatformActivity(self, node = None):
count = 0 count = 0

View File

@ -107,6 +107,7 @@ class MaterialManager(QObject):
# Map #2 # Map #2
# Lookup table for material type -> fallback material metadata, only for read-only materials # Lookup table for material type -> fallback material metadata, only for read-only materials
grouped_by_type_dict = dict() grouped_by_type_dict = dict()
material_types_without_fallback = set()
for root_material_id, material_node in self._material_group_map.items(): for root_material_id, material_node in self._material_group_map.items():
if not self._container_registry.isReadOnly(root_material_id): if not self._container_registry.isReadOnly(root_material_id):
continue continue
@ -114,6 +115,7 @@ class MaterialManager(QObject):
if material_type not in grouped_by_type_dict: if material_type not in grouped_by_type_dict:
grouped_by_type_dict[material_type] = {"generic": None, grouped_by_type_dict[material_type] = {"generic": None,
"others": []} "others": []}
material_types_without_fallback.add(material_type)
brand = material_node.root_material_node.metadata["brand"] brand = material_node.root_material_node.metadata["brand"]
if brand.lower() == "generic": if brand.lower() == "generic":
to_add = True to_add = True
@ -123,6 +125,10 @@ class MaterialManager(QObject):
to_add = False # don't add if it's not the default diameter to_add = False # don't add if it's not the default diameter
if to_add: if to_add:
grouped_by_type_dict[material_type] = material_node.root_material_node.metadata grouped_by_type_dict[material_type] = material_node.root_material_node.metadata
material_types_without_fallback.remove(material_type)
# Remove the materials that have no fallback materials
for material_type in material_types_without_fallback:
del grouped_by_type_dict[material_type]
self._fallback_materials_map = grouped_by_type_dict self._fallback_materials_map = grouped_by_type_dict
# Map #3 # Map #3

View File

@ -3,7 +3,7 @@
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt, pyqtSignal from PyQt5.QtCore import Qt
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
@ -11,6 +11,7 @@ from UM.Settings.ContainerStack import ContainerStack
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
# #
# This the QML model for the quality management page. # This the QML model for the quality management page.
# #
@ -39,7 +40,7 @@ class MachineManagementModel(ListModel):
## Handler for container added/removed events from registry ## Handler for container added/removed events from registry
def _onContainerChanged(self, container): def _onContainerChanged(self, container):
# We only need to update when the added / removed container is a stack. # We only need to update when the added / removed container is a stack.
if isinstance(container, ContainerStack): if isinstance(container, ContainerStack) and container.getMetaDataEntry("type") == "machine":
self._update() self._update()
## Private convenience function to reset & repopulate the model. ## Private convenience function to reset & repopulate the model.
@ -47,7 +48,9 @@ class MachineManagementModel(ListModel):
items = [] items = []
# Get first the network enabled printers # Get first the network enabled printers
network_filter_printers = {"type": "machine", "um_network_key": "*", "hidden": "False"} network_filter_printers = {"type": "machine",
"um_network_key": "*",
"hidden": "False"}
self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers) self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers)
self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("connect_group_name")) self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("connect_group_name"))
@ -61,7 +64,7 @@ class MachineManagementModel(ListModel):
"metadata": metadata, "metadata": metadata,
"group": catalog.i18nc("@info:title", "Network enabled printers")}) "group": catalog.i18nc("@info:title", "Network enabled printers")})
# Get now the local printes # Get now the local printers
local_filter_printers = {"type": "machine", "um_network_key": None} local_filter_printers = {"type": "machine", "um_network_key": None}
self._local_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**local_filter_printers) self._local_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**local_filter_printers)
self._local_container_stacks.sort(key = lambda i: i.getName()) self._local_container_stacks.sort(key = lambda i: i.getName())

View File

@ -1,7 +1,7 @@
# 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 pyqtSignal, pyqtProperty from PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty
from UM.Application import Application from UM.Application import Application
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
@ -21,8 +21,13 @@ class MultiBuildPlateModel(ListModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._update_timer = QTimer()
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._updateSelectedObjectBuildPlateNumbers)
self._application = Application.getInstance() self._application = Application.getInstance()
self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers) self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbersDelayed)
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers) Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
self._max_build_plate = 1 # default self._max_build_plate = 1 # default
@ -45,6 +50,9 @@ class MultiBuildPlateModel(ListModel):
def activeBuildPlate(self): def activeBuildPlate(self):
return self._active_build_plate return self._active_build_plate
def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args):
self._update_timer.start()
def _updateSelectedObjectBuildPlateNumbers(self, *args): def _updateSelectedObjectBuildPlateNumbers(self, *args):
result = set() result = set()
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():

View File

@ -0,0 +1,178 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
import os
import urllib.parse
from configparser import ConfigParser
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class SettingVisibilityPresetsModel(ListModel):
IdRole = Qt.UserRole + 1
NameRole = Qt.UserRole + 2
SettingsRole = Qt.UserRole + 3
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.SettingsRole, "settings")
self._populate()
basic_item = self.items[1]
basic_visibile_settings = ";".join(basic_item["settings"])
self._preferences = Preferences.getInstance()
# Preference to store which preset is currently selected
self._preferences.addPreference("cura/active_setting_visibility_preset", "basic")
# Preference that stores the "custom" set so it can always be restored (even after a restart)
self._preferences.addPreference("cura/custom_visible_settings", basic_visibile_settings)
self._preferences.preferenceChanged.connect(self._onPreferencesChanged)
self._active_preset_item = self._getItem(self._preferences.getValue("cura/active_setting_visibility_preset"))
# Initialize visible settings if it is not done yet
visible_settings = self._preferences.getValue("general/visible_settings")
if not visible_settings:
self._preferences.setValue("general/visible_settings", ";".join(self._active_preset_item["settings"]))
else:
self._onPreferencesChanged("general/visible_settings")
self.activePresetChanged.emit()
def _getItem(self, item_id: str) -> Optional[dict]:
result = None
for item in self.items:
if item["id"] == item_id:
result = item
break
return result
def _populate(self):
from cura.CuraApplication import CuraApplication
items = []
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
except MimeTypeNotFoundError:
Logger.log("e", "Could not determine mime type of file %s", file_path)
continue
item_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_path)))
if not os.path.isfile(file_path):
Logger.log("e", "[%s] is not a file", file_path)
continue
parser = ConfigParser(allow_no_value = True) # accept options without any value,
try:
parser.read([file_path])
if not parser.has_option("general", "name") or not parser.has_option("general", "weight"):
continue
settings = []
for section in parser.sections():
if section == 'general':
continue
settings.append(section)
for option in parser[section].keys():
settings.append(option)
items.append({
"id": item_id,
"name": catalog.i18nc("@action:inmenu", parser["general"]["name"]),
"weight": parser["general"]["weight"],
"settings": settings,
})
except Exception:
Logger.logException("e", "Failed to load setting preset %s", file_path)
items.sort(key = lambda k: (int(k["weight"]), k["id"]))
# Put "custom" at the top
items.insert(0, {"id": "custom",
"name": "Custom selection",
"weight": -100,
"settings": []})
self.setItems(items)
@pyqtSlot(str)
def setActivePreset(self, preset_id: str):
if preset_id == self._active_preset_item["id"]:
Logger.log("d", "Same setting visibility preset [%s] selected, do nothing.", preset_id)
return
preset_item = None
for item in self.items:
if item["id"] == preset_id:
preset_item = item
break
if preset_item is None:
Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id)
return
need_to_save_to_custom = self._active_preset_item["id"] == "custom" and preset_id != "custom"
if need_to_save_to_custom:
# Save the current visibility settings to custom
current_visibility_string = self._preferences.getValue("general/visible_settings")
if current_visibility_string:
self._preferences.setValue("cura/custom_visible_settings", current_visibility_string)
new_visibility_string = ";".join(preset_item["settings"])
if preset_id == "custom":
# Get settings from the stored custom data
new_visibility_string = self._preferences.getValue("cura/custom_visible_settings")
if new_visibility_string is None:
new_visibility_string = self._preferences.getValue("general/visible_settings")
self._preferences.setValue("general/visible_settings", new_visibility_string)
self._preferences.setValue("cura/active_setting_visibility_preset", preset_id)
self._active_preset_item = preset_item
self.activePresetChanged.emit()
activePresetChanged = pyqtSignal()
@pyqtProperty(str, notify = activePresetChanged)
def activePreset(self) -> str:
return self._active_preset_item["id"]
def _onPreferencesChanged(self, name: str):
if name != "general/visible_settings":
return
# Find the preset that matches with the current visible settings setup
visibility_string = self._preferences.getValue("general/visible_settings")
if not visibility_string:
return
visibility_set = set(visibility_string.split(";"))
matching_preset_item = None
for item in self.items:
if item["id"] == "custom":
continue
if set(item["settings"]) == visibility_set:
matching_preset_item = item
break
if matching_preset_item is None:
# The new visibility setup is "custom" should be custom
if self._active_preset_item["id"] == "custom":
# We are already in custom, just save the settings
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
else:
self._active_preset_item = self.items[0] # 0 is custom
self.activePresetChanged.emit()
else:
self._active_preset_item = matching_preset_item
self.activePresetChanged.emit()

View File

@ -1,3 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QTimer
from UM.Application import Application from UM.Application import Application
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -14,8 +19,13 @@ class ObjectsModel(ListModel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
Application.getInstance().getController().getScene().sceneChanged.connect(self._update) Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed)
Preferences.getInstance().preferenceChanged.connect(self._update) Preferences.getInstance().preferenceChanged.connect(self._updateDelayed)
self._update_timer = QTimer()
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._update)
self._build_plate_number = -1 self._build_plate_number = -1
@ -23,6 +33,9 @@ class ObjectsModel(ListModel):
self._build_plate_number = nr self._build_plate_number = nr
self._update() self._update()
def _updateDelayed(self, *args):
self._update_timer.start()
def _update(self, *args): def _update(self, *args):
nodes = [] nodes = []
filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate") filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate")

69
cura/PickingPass.py Normal file
View File

@ -0,0 +1,69 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Math.Vector import Vector
from UM.Resources import Resources
from UM.View.RenderPass import RenderPass
from UM.View.GL.OpenGL import OpenGL
from UM.View.RenderBatch import RenderBatch
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
## A RenderPass subclass that renders a the distance of selectable objects from the active camera to a texture.
# The texture is used to map a 2d location (eg the mouse location) to a world space position
#
# Note that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels
class PickingPass(RenderPass):
def __init__(self, width: int, height: int):
super().__init__("picking", width, height)
self._renderer = Application.getInstance().getRenderer()
self._shader = None
self._scene = Application.getInstance().getController().getScene()
def render(self) -> None:
if not self._shader:
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "camera_distance.shader"))
width, height = self.getSize()
self._gl.glViewport(0, 0, width, height)
self._gl.glClearColor(1.0, 1.0, 1.0, 0.0)
self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
# Create a new batch to be rendered
batch = RenderBatch(self._shader)
# Fill up the batch with objects that can be sliced. `
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
batch.addItem(node.getWorldTransformation(), node.getMeshData())
self.bind()
batch.render(self._scene.getActiveCamera())
self.release()
## Get the distance in mm from the camera to at a certain pixel coordinate.
def getPickedDepth(self, x: int, y: int) -> float:
output = self.getOutput()
window_size = self._renderer.getWindowSize()
px = (0.5 + x / 2.0) * window_size[0]
py = (0.5 + y / 2.0) * window_size[1]
if px < 0 or px > (output.width() - 1) or py < 0 or py > (output.height() - 1):
return -1
distance = output.pixel(px, py) # distance in micron, from in r, g & b channels
distance = (distance & 0x00ffffff) / 1000. # drop the alpha channel and covert to mm
return distance
## Get the world coordinates of a picked point
def getPickedPosition(self, x: int, y: int) -> Vector:
distance = self.getPickedDepth(x, y)
ray = self._scene.getActiveCamera().getRay(x, y)
return ray.getPointAlongRay(distance)

View File

@ -71,7 +71,7 @@ class PlatformPhysics:
# Move it downwards if bottom is above platform # Move it downwards if bottom is above platform
move_vector = Vector() move_vector = Vector()
if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup")) and node.isEnabled(): #If an object is grouped, don't move it down if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
move_vector = move_vector.set(y = -bbox.bottom + z_offset) move_vector = move_vector.set(y = -bbox.bottom + z_offset)

View File

@ -18,6 +18,7 @@ class ExtruderOutputModel(QObject):
hotendTemperatureChanged = pyqtSignal() hotendTemperatureChanged = pyqtSignal()
activeMaterialChanged = pyqtSignal() activeMaterialChanged = pyqtSignal()
extruderConfigurationChanged = pyqtSignal() extruderConfigurationChanged = pyqtSignal()
isPreheatingChanged = pyqtSignal()
def __init__(self, printer: "PrinterOutputModel", position, parent=None): def __init__(self, printer: "PrinterOutputModel", position, parent=None):
super().__init__(parent) super().__init__(parent)
@ -30,6 +31,21 @@ class ExtruderOutputModel(QObject):
self._extruder_configuration = ExtruderConfigurationModel() self._extruder_configuration = ExtruderConfigurationModel()
self._extruder_configuration.position = self._position self._extruder_configuration.position = self._position
self._is_preheating = False
def getPrinter(self):
return self._printer
def getPosition(self):
return self._position
# Does the printer support pre-heating the bed at all
@pyqtProperty(bool, constant=True)
def canPreHeatHotends(self):
if self._printer:
return self._printer.canPreHeatHotends
return False
@pyqtProperty(QObject, notify = activeMaterialChanged) @pyqtProperty(QObject, notify = activeMaterialChanged)
def activeMaterial(self) -> "MaterialOutputModel": def activeMaterial(self) -> "MaterialOutputModel":
return self._active_material return self._active_material
@ -82,3 +98,25 @@ class ExtruderOutputModel(QObject):
if self._extruder_configuration.isValid(): if self._extruder_configuration.isValid():
return self._extruder_configuration return self._extruder_configuration
return None return None
def updateIsPreheating(self, pre_heating):
if self._is_preheating != pre_heating:
self._is_preheating = pre_heating
self.isPreheatingChanged.emit()
@pyqtProperty(bool, notify=isPreheatingChanged)
def isPreheating(self):
return self._is_preheating
## Pre-heats the extruder before printer.
#
# \param temperature The temperature to heat the extruder to, in degrees
# Celsius.
# \param duration How long the bed should stay warm, in seconds.
@pyqtSlot(float, float)
def preheatHotend(self, temperature, duration):
self._printer._controller.preheatHotend(self, temperature, duration)
@pyqtSlot()
def cancelPreheatHotend(self):
self._printer._controller.cancelPreheatHotend(self)

View File

@ -0,0 +1,151 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from PyQt5.QtCore import QTimer
MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
class GenericOutputController(PrinterOutputController):
def __init__(self, output_device):
super().__init__(output_device)
self._preheat_bed_timer = QTimer()
self._preheat_bed_timer.setSingleShot(True)
self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
self._preheat_printer = None
self._preheat_hotends_timer = QTimer()
self._preheat_hotends_timer.setSingleShot(True)
self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished)
self._preheat_hotends = set()
self._output_device.printersChanged.connect(self._onPrintersChanged)
self._active_printer = None
def _onPrintersChanged(self):
if self._active_printer:
self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged)
self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged)
for extruder in self._active_printer.extruders:
extruder.targetHotendTemperatureChanged.disconnect(self._onTargetHotendTemperatureChanged)
self._active_printer = self._output_device.activePrinter
if self._active_printer:
self._active_printer.stateChanged.connect(self._onPrinterStateChanged)
self._active_printer.targetBedTemperatureChanged.connect(self._onTargetBedTemperatureChanged)
for extruder in self._active_printer.extruders:
extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged)
def _onPrinterStateChanged(self):
if self._active_printer.state != "idle":
if self._preheat_bed_timer.isActive():
self._preheat_bed_timer.stop()
self._preheat_printer.updateIsPreheating(False)
if self._preheat_hotends_timer.isActive():
self._preheat_hotends_timer.stop()
for extruder in self._preheat_hotends:
extruder.updateIsPreheating(False)
self._preheat_hotends = set()
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
self._output_device.sendCommand("G91")
self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
self._output_device.sendCommand("G90")
def homeHead(self, printer):
self._output_device.sendCommand("G28 X")
self._output_device.sendCommand("G28 Y")
def homeBed(self, printer):
self._output_device.sendCommand("G28 Z")
def setJobState(self, job: "PrintJobOutputModel", state: str):
if state == "pause":
self._output_device.pausePrint()
job.updateState("paused")
elif state == "print":
self._output_device.resumePrint()
job.updateState("printing")
elif state == "abort":
self._output_device.cancelPrint()
pass
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
self._output_device.sendCommand("M140 S%s" % temperature)
def _onTargetBedTemperatureChanged(self):
if self._preheat_bed_timer.isActive() and self._preheat_printer.targetBedTemperature == 0:
self._preheat_bed_timer.stop()
self._preheat_printer.updateIsPreheating(False)
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
try:
temperature = round(temperature) # The API doesn't allow floating point.
duration = round(duration)
except ValueError:
return # Got invalid values, can't pre-heat.
self.setTargetBedTemperature(printer, temperature=temperature)
self._preheat_bed_timer.setInterval(duration * 1000)
self._preheat_bed_timer.start()
self._preheat_printer = printer
printer.updateIsPreheating(True)
def cancelPreheatBed(self, printer: "PrinterOutputModel"):
self.setTargetBedTemperature(printer, temperature=0)
self._preheat_bed_timer.stop()
printer.updateIsPreheating(False)
def _onPreheatBedTimerFinished(self):
self.setTargetBedTemperature(self._preheat_printer, 0)
self._preheat_printer.updateIsPreheating(False)
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: int):
self._output_device.sendCommand("M104 S%s T%s" % (temperature, position))
def _onTargetHotendTemperatureChanged(self):
if not self._preheat_hotends_timer.isActive():
return
for extruder in self._active_printer.extruders:
if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0:
extruder.updateIsPreheating(False)
self._preheat_hotends.remove(extruder)
if not self._preheat_hotends:
self._preheat_hotends_timer.stop()
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
position = extruder.getPosition()
number_of_extruders = len(extruder.getPrinter().extruders)
if position >= number_of_extruders:
return # Got invalid extruder nr, can't pre-heat.
try:
temperature = round(temperature) # The API doesn't allow floating point.
duration = round(duration)
except ValueError:
return # Got invalid values, can't pre-heat.
self.setTargetHotendTemperature(extruder.getPrinter(), position, temperature=temperature)
self._preheat_hotends_timer.setInterval(duration * 1000)
self._preheat_hotends_timer.start()
self._preheat_hotends.add(extruder)
extruder.updateIsPreheating(True)
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0)
if extruder in self._preheat_hotends:
extruder.updateIsPreheating(False)
self._preheat_hotends.remove(extruder)
if not self._preheat_hotends and self._preheat_hotends_timer.isActive():
self._preheat_hotends_timer.stop()
def _onPreheatHotendsTimerFinished(self):
for extruder in self._preheat_hotends:
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
self._preheat_hotends = set()

View File

@ -3,6 +3,8 @@
from UM.Application import Application from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.CuraApplication import CuraApplication
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
@ -254,6 +256,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._last_manager_create_time = time() self._last_manager_create_time = time()
self._manager.authenticationRequired.connect(self._onAuthenticationRequired) self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
machine_manager = CuraApplication.getInstance().getMachineManager()
machine_manager.checkCorrectGroupName(self.getId(), self.name)
def _registerOnFinishedCallback(self, reply: QNetworkReply, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: def _registerOnFinishedCallback(self, reply: QNetworkReply, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None:
if onFinished is not None: if onFinished is not None:
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished

View File

@ -15,6 +15,7 @@ class PrinterOutputController:
self.can_pause = True self.can_pause = True
self.can_abort = True self.can_abort = True
self.can_pre_heat_bed = True self.can_pre_heat_bed = True
self.can_pre_heat_hotends = True
self.can_control_manually = True self.can_control_manually = True
self._output_device = output_device self._output_device = output_device
@ -33,6 +34,12 @@ class PrinterOutputController:
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration): def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
Logger.log("w", "Preheat bed not implemented in controller") Logger.log("w", "Preheat bed not implemented in controller")
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
Logger.log("w", "Cancel preheat hotend not implemented in controller")
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
Logger.log("w", "Preheat hotend not implemented in controller")
def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed): def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed):
Logger.log("w", "Set head position not implemented in controller") Logger.log("w", "Set head position not implemented in controller")

View File

@ -238,6 +238,13 @@ class PrinterOutputModel(QObject):
return self._controller.can_pre_heat_bed return self._controller.can_pre_heat_bed
return False return False
# Does the printer support pre-heating the bed at all
@pyqtProperty(bool, constant=True)
def canPreHeatHotends(self):
if self._controller:
return self._controller.can_pre_heat_hotends
return False
# Does the printer support pause at all # Does the printer support pause at all
@pyqtProperty(bool, constant=True) @pyqtProperty(bool, constant=True)
def canPause(self): def canPause(self):

View File

@ -1,6 +1,8 @@
# Copyright (c) 2016 Ultimaker B.V. # Copyright (c) 2016 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
from UM.Application import Application from UM.Application import Application
from UM.Math.Polygon import Polygon from UM.Math.Polygon import Polygon
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
@ -22,6 +24,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._global_stack = None self._global_stack = None
# Make sure the timer is created on the main thread
self._recompute_convex_hull_timer = None
Application.getInstance().callLater(self.createRecomputeConvexHullTimer)
self._raft_thickness = 0.0 self._raft_thickness = 0.0
# For raft thickness, DRY # For raft thickness, DRY
self._build_volume = Application.getInstance().getBuildVolume() self._build_volume = Application.getInstance().getBuildVolume()
@ -33,6 +39,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._onGlobalStackChanged() self._onGlobalStackChanged()
def createRecomputeConvexHullTimer(self):
self._recompute_convex_hull_timer = QTimer()
self._recompute_convex_hull_timer.setInterval(200)
self._recompute_convex_hull_timer.setSingleShot(True)
self._recompute_convex_hull_timer.timeout.connect(self.recomputeConvexHull)
def setNode(self, node): def setNode(self, node):
previous_node = self._node previous_node = self._node
# Disconnect from previous node signals # Disconnect from previous node signals
@ -99,6 +111,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
return self._compute2DConvexHull() return self._compute2DConvexHull()
return None return None
def recomputeConvexHullDelayed(self):
if self._recompute_convex_hull_timer is not None:
self._recompute_convex_hull_timer.start()
else:
self.recomputeConvexHull()
def recomputeConvexHull(self): def recomputeConvexHull(self):
controller = Application.getInstance().getController() controller = Application.getInstance().getController()
root = controller.getScene().getRoot() root = controller.getScene().getRoot()
@ -279,7 +297,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
def _onChanged(self, *args): def _onChanged(self, *args):
self._raft_thickness = self._build_volume.getRaftThickness() self._raft_thickness = self._build_volume.getRaftThickness()
self.recomputeConvexHull() if not args or args[0] == self._node:
self.recomputeConvexHullDelayed()
def _onGlobalStackChanged(self): def _onGlobalStackChanged(self):
if self._global_stack: if self._global_stack:

View File

@ -348,15 +348,18 @@ class ContainerManager(QObject):
# #
# \param material_id \type{str} the id of the material for which to get the linked materials. # \param material_id \type{str} the id of the material for which to get the linked materials.
# \return \type{list} a list of names of materials with the same GUID # \return \type{list} a list of names of materials with the same GUID
@pyqtSlot("QVariant", result = "QStringList") @pyqtSlot("QVariant", bool, result = "QStringList")
def getLinkedMaterials(self, material_node): def getLinkedMaterials(self, material_node, exclude_self = False):
guid = material_node.metadata["GUID"] guid = material_node.metadata["GUID"]
self_root_material_id = material_node.metadata["base_file"]
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid) material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
linked_material_names = [] linked_material_names = []
if material_group_list: if material_group_list:
for material_group in material_group_list: for material_group in material_group_list:
if exclude_self and material_group.name == self_root_material_id:
continue
linked_material_names.append(material_group.root_material_node.metadata["name"]) linked_material_names.append(material_group.root_material_node.metadata["name"])
return linked_material_names return linked_material_names

View File

@ -173,12 +173,13 @@ class CuraContainerRegistry(ContainerRegistry):
plugin_registry = PluginRegistry.getInstance() plugin_registry = PluginRegistry.getInstance()
extension = file_name.split(".")[-1] extension = file_name.split(".")[-1]
global_container_stack = Application.getInstance().getGlobalContainerStack() global_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack: if not global_stack:
return return
machine_extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) machine_extruders = []
machine_extruders.sort(key = lambda k: k.getMetaDataEntry("position")) for position in sorted(global_stack.extruders):
machine_extruders.append(global_stack.extruders[position])
for plugin_id, meta_data in self._getIOPlugins("profile_reader"): for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
if meta_data["profile_reader"][0]["extension"] != extension: if meta_data["profile_reader"][0]["extension"] != extension:
@ -200,13 +201,18 @@ class CuraContainerRegistry(ContainerRegistry):
# First check if this profile is suitable for this machine # First check if this profile is suitable for this machine
global_profile = None global_profile = None
extruder_profiles = []
if len(profile_or_list) == 1: if len(profile_or_list) == 1:
global_profile = profile_or_list[0] global_profile = profile_or_list[0]
else: else:
for profile in profile_or_list: for profile in profile_or_list:
if not profile.getMetaDataEntry("position"): if not profile.getMetaDataEntry("position"):
global_profile = profile global_profile = profile
break else:
extruder_profiles.append(profile)
extruder_profiles = sorted(extruder_profiles, key = lambda x: int(x.getMetaDataEntry("position")))
profile_or_list = [global_profile] + extruder_profiles
if not global_profile: if not global_profile:
Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name) Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name)
return { "status": "error", return { "status": "error",
@ -227,7 +233,7 @@ class CuraContainerRegistry(ContainerRegistry):
# Get the expected machine definition. # Get the expected machine definition.
# i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode... # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode...
profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition) profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition)
expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_container_stack.definition) expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition)
# And check if the profile_definition matches either one (showing error if not): # And check if the profile_definition matches either one (showing error if not):
if profile_definition != expected_machine_definition: if profile_definition != expected_machine_definition:
@ -251,8 +257,8 @@ class CuraContainerRegistry(ContainerRegistry):
if len(profile_or_list) == 1: if len(profile_or_list) == 1:
global_profile = profile_or_list[0] global_profile = profile_or_list[0]
extruder_profiles = [] extruder_profiles = []
for idx, extruder in enumerate(global_container_stack.extruders.values()): for idx, extruder in enumerate(global_stack.extruders.values()):
profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1)) profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
profile = InstanceContainer(profile_id) profile = InstanceContainer(profile_id)
profile.setName(quality_name) profile.setName(quality_name)
profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
@ -264,12 +270,12 @@ class CuraContainerRegistry(ContainerRegistry):
if idx == 0: if idx == 0:
# move all per-extruder settings to the first extruder's quality_changes # move all per-extruder settings to the first extruder's quality_changes
for qc_setting_key in global_profile.getAllKeys(): for qc_setting_key in global_profile.getAllKeys():
settable_per_extruder = global_container_stack.getProperty(qc_setting_key, settable_per_extruder = global_stack.getProperty(qc_setting_key,
"settable_per_extruder") "settable_per_extruder")
if settable_per_extruder: if settable_per_extruder:
setting_value = global_profile.getProperty(qc_setting_key, "value") setting_value = global_profile.getProperty(qc_setting_key, "value")
setting_definition = global_container_stack.getSettingDefinition(qc_setting_key) setting_definition = global_stack.getSettingDefinition(qc_setting_key)
new_instance = SettingInstance(setting_definition, profile) new_instance = SettingInstance(setting_definition, profile)
new_instance.setProperty("value", setting_value) new_instance.setProperty("value", setting_value)
new_instance.resetState() # Ensure that the state is not seen as a user state. new_instance.resetState() # Ensure that the state is not seen as a user state.
@ -286,7 +292,7 @@ class CuraContainerRegistry(ContainerRegistry):
for profile_index, profile in enumerate(profile_or_list): for profile_index, profile in enumerate(profile_or_list):
if profile_index == 0: if profile_index == 0:
# This is assumed to be the global profile # This is assumed to be the global profile
profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") profile_id = (global_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
elif profile_index < len(machine_extruders) + 1: elif profile_index < len(machine_extruders) + 1:
# This is assumed to be an extruder profile # This is assumed to be an extruder profile

View File

@ -210,6 +210,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
item = { item = {
"id": "", "id": "",
"name": catalog.i18nc("@menuitem", "Not overridden"), "name": catalog.i18nc("@menuitem", "Not overridden"),
"enabled": True,
"color": "#ffffff", "color": "#ffffff",
"index": -1, "index": -1,
"definition": "" "definition": ""

View File

@ -10,7 +10,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Signal import Signal from UM.Signal import Signal
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
import UM.FlameProfiler
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
from UM import Util from UM import Util
@ -24,7 +23,6 @@ from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique from UM.Signal import postponeSignals, CompressTechnique
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.Machines.VariantManager import VariantType
from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
@ -147,6 +145,7 @@ class MachineManager(QObject):
activeStackValueChanged = pyqtSignal() # Emitted whenever a value inside the active stack is changed. activeStackValueChanged = pyqtSignal() # Emitted whenever a value inside the active stack is changed.
activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed
stacksValidationChanged = pyqtSignal() # Emitted whenever a validation is changed stacksValidationChanged = pyqtSignal() # Emitted whenever a validation is changed
numberExtrudersEnabledChanged = pyqtSignal() # Emitted when the number of extruders that are enabled changed
blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
@ -467,16 +466,16 @@ class MachineManager(QObject):
return self._global_container_stack.getId() return self._global_container_stack.getId()
return "" return ""
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = outputDevicesChanged)
def activeMachineNetworkKey(self) -> str: def activeMachineNetworkKey(self) -> str:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("um_network_key") return self._global_container_stack.getMetaDataEntry("um_network_key", "")
return "" return ""
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = outputDevicesChanged)
def activeMachineNetworkGroupName(self) -> str: def activeMachineNetworkGroupName(self) -> str:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("connect_group_name") return self._global_container_stack.getMetaDataEntry("connect_group_name", "")
return "" return ""
@pyqtProperty(QObject, notify = globalContainerChanged) @pyqtProperty(QObject, notify = globalContainerChanged)
@ -662,12 +661,22 @@ class MachineManager(QObject):
if other_machine_stacks: if other_machine_stacks:
self.setActiveMachine(other_machine_stacks[0]["id"]) self.setActiveMachine(other_machine_stacks[0]["id"])
metadata = ContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
network_key = metadata["um_network_key"] if "um_network_key" in metadata else None
ExtruderManager.getInstance().removeMachineExtruders(machine_id) ExtruderManager.getInstance().removeMachineExtruders(machine_id)
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers: for container in containers:
ContainerRegistry.getInstance().removeContainer(container["id"]) ContainerRegistry.getInstance().removeContainer(container["id"])
ContainerRegistry.getInstance().removeContainer(machine_id) ContainerRegistry.getInstance().removeContainer(machine_id)
# If the printer that is being removed is a network printer, the hidden printers have to be also removed
if network_key:
metadata_filter = {"um_network_key": network_key}
hidden_containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
if hidden_containers:
# This reuses the method and remove all printers recursively
self.removeMachine(hidden_containers[0].getId())
@pyqtProperty(bool, notify = globalContainerChanged) @pyqtProperty(bool, notify = globalContainerChanged)
def hasMaterials(self) -> bool: def hasMaterials(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
@ -729,22 +738,6 @@ class MachineManager(QObject):
return result return result
## Property to indicate if a machine has "specialized" material profiles.
# Some machines have their own material profiles that "override" the default catch all profiles.
@pyqtProperty(bool, notify = globalContainerChanged)
def filterMaterialsByMachine(self) -> bool:
if self._global_container_stack:
return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False))
return False
## Property to indicate if a machine has "specialized" quality profiles.
# Some machines have their own quality profiles that "override" the default catch all profiles.
@pyqtProperty(bool, notify = globalContainerChanged)
def filterQualityByMachine(self) -> bool:
if self._global_container_stack:
return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False))
return False
## Get the Definition ID of a machine (specified by ID) ## Get the Definition ID of a machine (specified by ID)
# \param machine_id string machine id to get the definition ID of # \param machine_id string machine id to get the definition ID of
# \returns DefinitionID (string) if found, None otherwise # \returns DefinitionID (string) if found, None otherwise
@ -872,7 +865,13 @@ class MachineManager(QObject):
for position, extruder in self._global_container_stack.extruders.items(): for position, extruder in self._global_container_stack.extruders.items():
if extruder.isEnabled: if extruder.isEnabled:
extruder_count += 1 extruder_count += 1
if self.numberExtrudersEnabled != extruder_count:
definition_changes_container.setProperty("extruders_enabled_count", "value", extruder_count) definition_changes_container.setProperty("extruders_enabled_count", "value", extruder_count)
self.numberExtrudersEnabledChanged.emit()
@pyqtProperty(int, notify = numberExtrudersEnabledChanged)
def numberExtrudersEnabled(self):
return self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value")
@pyqtProperty(str, notify = extruderChanged) @pyqtProperty(str, notify = extruderChanged)
def defaultExtruderPosition(self): def defaultExtruderPosition(self):
@ -1110,7 +1109,7 @@ class MachineManager(QObject):
from cura.Settings.CuraContainerStack import _ContainerIndexes from cura.Settings.CuraContainerStack import _ContainerIndexes
context = PropertyEvaluationContext(extruder) context = PropertyEvaluationContext(extruder)
context.context["evaluate_from_container_index"] = _ContainerIndexes.DefinitionChanges context.context["evaluate_from_container_index"] = _ContainerIndexes.DefinitionChanges
material_diameter = self._global_container_stack.getProperty("material_diameter", "value", context) material_diameter = extruder.getProperty("material_diameter", "value", context)
candidate_materials = self._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,
@ -1193,6 +1192,24 @@ class MachineManager(QObject):
if machine.getMetaDataEntry(key) == value: if machine.getMetaDataEntry(key) == value:
machine.setMetaDataEntry(key, new_value) machine.setMetaDataEntry(key, new_value)
## This method checks if the name of the group stored in the definition container is correct.
# After updating from 3.2 to 3.3 some group names may be temporary. If there is a mismatch in the name of the group
# then all the container stacks are updated, both the current and the hidden ones.
def checkCorrectGroupName(self, device_id: str, group_name: str):
if self._global_container_stack and device_id == self.activeMachineNetworkKey:
# Check if the connect_group_name is correct. If not, update all the containers connected to the same printer
if self.activeMachineNetworkGroupName != group_name:
metadata_filter = {"um_network_key": self.activeMachineNetworkKey}
hidden_containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
for container in hidden_containers:
container.setMetaDataEntry("connect_group_name", group_name)
## This method checks if there is an instance connected to the given network_key
def existNetworkInstances(self, network_key: str) -> bool:
metadata_filter = {"um_network_key": network_key}
containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
return bool(containers)
@pyqtSlot("QVariant") @pyqtSlot("QVariant")
def setGlobalVariant(self, container_node): def setGlobalVariant(self, container_node):
self.blurSettings.emit() self.blurSettings.emit()
@ -1242,6 +1259,13 @@ class MachineManager(QObject):
if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges() self._application.discardOrKeepProfileChanges()
@pyqtSlot()
def resetToUseDefaultQuality(self):
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._setQualityGroup(self._current_quality_group)
for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
stack.userChanges.clear()
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged) @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
def activeQualityChangesGroup(self): def activeQualityChangesGroup(self):
return self._current_quality_changes_group return self._current_quality_changes_group

View File

@ -1,136 +0,0 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
import urllib
from configparser import ConfigParser
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot, QUrl
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
import cura.CuraApplication
class SettingVisibilityPresetsModel(ListModel):
IdRole = Qt.UserRole + 1
NameRole = Qt.UserRole + 2
SettingsRole = Qt.UserRole + 4
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.SettingsRole, "settings")
self._populate()
self._preferences = Preferences.getInstance()
self._preferences.addPreference("cura/active_setting_visibility_preset", "custom") # Preference to store which preset is currently selected
self._preferences.addPreference("cura/custom_visible_settings", "") # Preference that stores the "custom" set so it can always be restored (even after a restart)
self._preferences.preferenceChanged.connect(self._onPreferencesChanged)
self._active_preset = self._preferences.getValue("cura/active_setting_visibility_preset")
if self.find("id", self._active_preset) < 0:
self._active_preset = "custom"
self.activePresetChanged.emit()
def _populate(self):
items = []
for item in Resources.getAllResourcesOfType(cura.CuraApplication.CuraApplication.ResourceTypes.SettingVisibilityPreset):
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(item)
except MimeTypeNotFoundError:
Logger.log("e", "Could not determine mime type of file %s", item)
continue
id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(item)))
if not os.path.isfile(item):
continue
parser = ConfigParser(allow_no_value=True) # accept options without any value,
try:
parser.read([item])
if not parser.has_option("general", "name") and not parser.has_option("general", "weight"):
continue
settings = []
for section in parser.sections():
if section == 'general':
continue
settings.append(section)
for option in parser[section].keys():
settings.append(option)
items.append({
"id": id,
"name": parser["general"]["name"],
"weight": parser["general"]["weight"],
"settings": settings
})
except Exception as e:
Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e))
items.sort(key = lambda k: (k["weight"], k["id"]))
self.setItems(items)
@pyqtSlot(str)
def setActivePreset(self, preset_id):
if preset_id != "custom" and self.find("id", preset_id) == -1:
Logger.log("w", "Tried to set active preset to unknown id %s", preset_id)
return
if preset_id == "custom" and self._active_preset == "custom":
# Copy current visibility set to custom visibility set preference so it can be restored later
visibility_string = self._preferences.getValue("general/visible_settings")
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
self._preferences.setValue("cura/active_setting_visibility_preset", preset_id)
self._active_preset = preset_id
self.activePresetChanged.emit()
activePresetChanged = pyqtSignal()
@pyqtProperty(str, notify = activePresetChanged)
def activePreset(self):
return self._active_preset
def _onPreferencesChanged(self, name):
if name != "general/visible_settings":
return
if self._active_preset != "custom":
return
# Copy current visibility set to custom visibility set preference so it can be restored later
visibility_string = self._preferences.getValue("general/visible_settings")
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
# Factory function, used by QML
@staticmethod
def createSettingVisibilityPresetsModel(engine, js_engine):
return SettingVisibilityPresetsModel.getInstance()
## Get the singleton instance for this class.
@classmethod
def getInstance(cls) -> "SettingVisibilityPresetsModel":
# Note: Explicit use of class name to prevent issues with inheritance.
if not SettingVisibilityPresetsModel.__instance:
SettingVisibilityPresetsModel.__instance = cls()
return SettingVisibilityPresetsModel.__instance
__instance = None # type: "SettingVisibilityPresetsModel"

View File

@ -16,7 +16,8 @@ class SimpleModeSettingsManager(QObject):
self._is_profile_user_created = False # True when profile was custom created by user self._is_profile_user_created = False # True when profile was custom created by user
self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized) self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized)
self._machine_manager.activeQualityChanged.connect(self._updateIsProfileUserCreated) self._machine_manager.activeQualityGroupChanged.connect(self._updateIsProfileUserCreated)
self._machine_manager.activeQualityChangesGroupChanged.connect(self._updateIsProfileUserCreated)
# update on create as the activeQualityChanged signal is emitted before this manager is created when Cura starts # update on create as the activeQualityChanged signal is emitted before this manager is created when Cura starts
self._updateIsProfileCustomized() self._updateIsProfileCustomized()

View File

@ -280,6 +280,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Check if any quality_changes instance container is in conflict. # Check if any quality_changes instance container is in conflict.
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
quality_name = "" quality_name = ""
custom_quality_name = ""
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
num_user_settings = 0 num_user_settings = 0
quality_changes_conflict = False quality_changes_conflict = False
@ -292,7 +293,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
container_id = self._stripFileToId(instance_container_file_name) container_id = self._stripFileToId(instance_container_file_name)
serialized = archive.open(instance_container_file_name).read().decode("utf-8") serialized = archive.open(instance_container_file_name).read().decode("utf-8")
# Qualities and variants don't have upgrades, so don't upgrade them
parser = ConfigParser(interpolation = None)
parser.read_string(serialized)
container_type = parser["metadata"]["type"]
if container_type not in ("quality", "variant"):
serialized = InstanceContainer._updateSerialized(serialized, instance_container_file_name) serialized = InstanceContainer._updateSerialized(serialized, instance_container_file_name)
parser = ConfigParser(interpolation = None) parser = ConfigParser(interpolation = None)
parser.read_string(serialized) parser.read_string(serialized)
container_info = ContainerInfo(instance_container_file_name, serialized, parser) container_info = ContainerInfo(instance_container_file_name, serialized, parser)
@ -309,7 +317,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
position = parser["metadata"]["position"] position = parser["metadata"]["position"]
self._machine_info.quality_changes_info.extruder_info_dict[position] = container_info self._machine_info.quality_changes_info.extruder_info_dict[position] = container_info
quality_name = parser["general"]["name"] custom_quality_name = parser["general"]["name"]
values = parser["values"] if parser.has_section("values") else dict() values = parser["values"] if parser.has_section("values") else dict()
num_settings_overriden_by_quality_changes += len(values) num_settings_overriden_by_quality_changes += len(values)
# Check if quality changes already exists. # Check if quality changes already exists.
@ -473,6 +481,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruders = num_extruders * [""] extruders = num_extruders * [""]
quality_name = custom_quality_name if custom_quality_name else quality_name
self._machine_info.container_id = global_stack_id self._machine_info.container_id = global_stack_id
self._machine_info.name = machine_name self._machine_info.name = machine_name
self._machine_info.definition_id = machine_definition_id self._machine_info.definition_id = machine_definition_id

View File

@ -1,17 +1,17 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 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 re # For escaping characters in the settings.
import json
import copy
from UM.Mesh.MeshWriter import MeshWriter from UM.Mesh.MeshWriter import MeshWriter
from UM.Logger import Logger from UM.Logger import Logger
from UM.Application import Application from UM.Application import Application
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Util import parseBool
from cura.Settings.ExtruderManager import ExtruderManager from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
import re #For escaping characters in the settings.
import json
import copy
## Writes g-code to a file. ## Writes g-code to a file.
# #
@ -45,6 +45,8 @@ class GCodeWriter(MeshWriter):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._application = Application.getInstance()
## Writes the g-code for the entire scene to a stream. ## Writes the g-code for the entire scene to a stream.
# #
# Note that even though the function accepts a collection of nodes, the # Note that even though the function accepts a collection of nodes, the
@ -94,7 +96,6 @@ class GCodeWriter(MeshWriter):
return flat_container return flat_container
## Serialises a container stack to prepare it for writing at the end of the ## Serialises a container stack to prepare it for writing at the end of the
# g-code. # g-code.
# #
@ -104,15 +105,20 @@ class GCodeWriter(MeshWriter):
# \param settings A container stack to serialise. # \param settings A container stack to serialise.
# \return A serialised string of the settings. # \return A serialised string of the settings.
def _serialiseSettings(self, stack): def _serialiseSettings(self, stack):
container_registry = self._application.getContainerRegistry()
quality_manager = self._application.getQualityManager()
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line. prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
prefix_length = len(prefix) prefix_length = len(prefix)
quality_type = stack.quality.getMetaDataEntry("quality_type")
container_with_profile = stack.qualityChanges container_with_profile = stack.qualityChanges
if container_with_profile.getId() == "empty_quality_changes": if container_with_profile.getId() == "empty_quality_changes":
Logger.log("e", "No valid quality profile found, not writing settings to g-code!") # If the global quality changes is empty, create a new one
return "" quality_name = container_registry.uniqueName(stack.quality.getName())
container_with_profile = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile) flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
# If the quality changes is not set, we need to set type manually # If the quality changes is not set, we need to set type manually
if flat_global_container.getMetaDataEntry("type", None) is None: if flat_global_container.getMetaDataEntry("type", None) is None:
flat_global_container.addMetaDataEntry("type", "quality_changes") flat_global_container.addMetaDataEntry("type", "quality_changes")
@ -121,41 +127,47 @@ class GCodeWriter(MeshWriter):
if flat_global_container.getMetaDataEntry("quality_type", None) is None: if flat_global_container.getMetaDataEntry("quality_type", None) is None:
flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal")) flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
# Change the default defintion # Get the machine definition ID for quality profiles
default_machine_definition = "fdmprinter" machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition)
if parseBool(stack.getMetaDataEntry("has_machine_quality", "False")): flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
default_machine_definition = stack.getMetaDataEntry("quality_definition")
if not default_machine_definition:
default_machine_definition = stack.definition.getId()
flat_global_container.setMetaDataEntry("definition", default_machine_definition)
serialized = flat_global_container.serialize() serialized = flat_global_container.serialize()
data = {"global_quality": serialized} data = {"global_quality": serialized}
for extruder in sorted(stack.extruders.values(), key = lambda k: k.getMetaDataEntry("position")): all_setting_keys = set(flat_global_container.getAllKeys())
for extruder in sorted(stack.extruders.values(), key = lambda k: int(k.getMetaDataEntry("position"))):
extruder_quality = extruder.qualityChanges extruder_quality = extruder.qualityChanges
if extruder_quality.getId() == "empty_quality_changes": if extruder_quality.getId() == "empty_quality_changes":
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId()) # Same story, if quality changes is empty, create a new one
continue quality_name = container_registry.uniqueName(stack.quality.getName())
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality) extruder_quality = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
# If the quality changes is not set, we need to set type manually # If the quality changes is not set, we need to set type manually
if flat_extruder_quality.getMetaDataEntry("type", None) is None: if flat_extruder_quality.getMetaDataEntry("type", None) is None:
flat_extruder_quality.addMetaDataEntry("type", "quality_changes") flat_extruder_quality.addMetaDataEntry("type", "quality_changes")
# Ensure that extruder is set. (Can happen if we have empty quality changes). # Ensure that extruder is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("extruder", None) is None: if flat_extruder_quality.getMetaDataEntry("position", None) is None:
flat_extruder_quality.addMetaDataEntry("extruder", extruder.getBottom().getId()) flat_extruder_quality.addMetaDataEntry("position", extruder.getMetaDataEntry("position"))
# Ensure that quality_type is set. (Can happen if we have empty quality changes). # Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None: if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal")) flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal"))
# Change the default defintion # Change the default definition
flat_extruder_quality.setMetaDataEntry("definition", default_machine_definition) flat_extruder_quality.setMetaDataEntry("definition", machine_definition_id_for_quality)
extruder_serialized = flat_extruder_quality.serialize() extruder_serialized = flat_extruder_quality.serialize()
data.setdefault("extruder_quality", []).append(extruder_serialized) data.setdefault("extruder_quality", []).append(extruder_serialized)
all_setting_keys.update(set(flat_extruder_quality.getAllKeys()))
# Check if there is any profiles
if not all_setting_keys:
Logger.log("i", "No custom settings found, not writing settings to g-code.")
return ""
json_string = json.dumps(data) json_string = json.dumps(data)
# Escape characters that have a special meaning in g-code comments. # Escape characters that have a special meaning in g-code comments.

View File

@ -163,7 +163,16 @@ Item {
id: addedSettingsModel; id: addedSettingsModel;
containerId: Cura.MachineManager.activeDefinitionId containerId: Cura.MachineManager.activeDefinitionId
expanded: [ "*" ] expanded: [ "*" ]
exclude: { filter:
{
if (printSequencePropertyProvider.properties.value == "one_at_a_time")
{
return {"settable_per_meshgroup": true};
}
return {"settable_per_mesh": true};
}
exclude:
{
var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]; var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
if(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh") if(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
@ -375,7 +384,6 @@ Item {
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model") title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
width: screenScaleFactor * 360 width: screenScaleFactor * 360
property string labelFilter: ""
property var additional_excluded_settings property var additional_excluded_settings
onVisibilityChanged: onVisibilityChanged:
@ -386,11 +394,33 @@ Item {
// Set skip setting, it will prevent from resetting selected mesh_type // Set skip setting, it will prevent from resetting selected mesh_type
contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type) contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type)
listview.model.forceUpdate() listview.model.forceUpdate()
updateFilter()
} }
} }
function updateFilter()
{
var new_filter = {};
if (printSequencePropertyProvider.properties.value == "one_at_a_time")
{
new_filter["settable_per_meshgroup"] = true;
}
else
{
new_filter["settable_per_mesh"] = true;
}
if(filterInput.text != "")
{
new_filter["i18n_label"] = "*" + filterInput.text;
}
listview.model.filter = new_filter;
}
TextField { TextField {
id: filter id: filterInput
anchors { anchors {
top: parent.top top: parent.top
@ -401,17 +431,7 @@ Item {
placeholderText: catalog.i18nc("@label:textbox", "Filter..."); placeholderText: catalog.i18nc("@label:textbox", "Filter...");
onTextChanged: onTextChanged: settingPickDialog.updateFilter()
{
if(text != "")
{
listview.model.filter = {"settable_per_mesh": true, "i18n_label": "*" + text}
}
else
{
listview.model.filter = {"settable_per_mesh": true}
}
}
} }
CheckBox CheckBox
@ -437,7 +457,7 @@ Item {
anchors anchors
{ {
top: filter.bottom; top: filterInput.bottom;
left: parent.left; left: parent.left;
right: parent.right; right: parent.right;
bottom: parent.bottom; bottom: parent.bottom;
@ -449,10 +469,6 @@ Item {
{ {
id: definitionsModel; id: definitionsModel;
containerId: Cura.MachineManager.activeDefinitionId containerId: Cura.MachineManager.activeDefinitionId
filter:
{
"settable_per_mesh": true
}
visibilityHandler: UM.SettingPreferenceVisibilityHandler {} visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
expanded: [ "*" ] expanded: [ "*" ]
exclude: exclude:
@ -484,6 +500,7 @@ Item {
} }
} }
} }
Component.onCompleted: settingPickDialog.updateFilter()
} }
} }
@ -507,6 +524,16 @@ Item {
storeIndex: 0 storeIndex: 0
} }
UM.SettingPropertyProvider
{
id: printSequencePropertyProvider
containerStackId: Cura.MachineManager.activeMachineId
key: "print_sequence"
watchedProperties: [ "value" ]
storeIndex: 0
}
SystemPalette { id: palette; } SystemPalette { id: palette; }
Component Component

View File

@ -1,4 +1,4 @@
# Copyright (c) 2016 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
@ -7,7 +7,7 @@ from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.FileHandler.WriteFileJob import WriteFileJob from UM.FileHandler.WriteFileJob import WriteFileJob
from UM.Mesh.MeshWriter import MeshWriter from UM.FileHandler.FileWriter import FileWriter #To check against the write modes (text vs. binary).
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.OutputDevice.OutputDevice import OutputDevice from UM.OutputDevice.OutputDevice import OutputDevice
from UM.OutputDevice import OutputDeviceError from UM.OutputDevice import OutputDeviceError
@ -39,7 +39,7 @@ class RemovableDriveOutputDevice(OutputDevice):
# MIME types available to the currently active machine? # MIME types available to the currently active machine?
# #
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs): def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
filter_by_machine = True # This plugin is indended to be used by machine (regardless of what it was told to do) filter_by_machine = True # This plugin is intended to be used by machine (regardless of what it was told to do)
if self._writing: if self._writing:
raise OutputDeviceError.DeviceBusyError() raise OutputDeviceError.DeviceBusyError()
@ -56,19 +56,21 @@ class RemovableDriveOutputDevice(OutputDevice):
machine_file_formats = [file_type.strip() for file_type in container.getMetaDataEntry("file_formats").split(";")] machine_file_formats = [file_type.strip() for file_type in container.getMetaDataEntry("file_formats").split(";")]
# Take the intersection between file_formats and machine_file_formats. # Take the intersection between file_formats and machine_file_formats.
file_formats = list(filter(lambda file_format: file_format["mime_type"] in machine_file_formats, file_formats)) format_by_mimetype = {format["mime_type"]: format for format in file_formats}
file_formats = [format_by_mimetype[mimetype] for mimetype in machine_file_formats] #Keep them ordered according to the preference in machine_file_formats.
if len(file_formats) == 0: if len(file_formats) == 0:
Logger.log("e", "There are no file formats available to write with!") Logger.log("e", "There are no file formats available to write with!")
raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("There are no file formats available to write with!")) raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("@info:status", "There are no file formats available to write with!"))
preferred_format = file_formats[0]
# Just take the first file format available. # Just take the first file format available.
if file_handler is not None: if file_handler is not None:
writer = file_handler.getWriterByMimeType(file_formats[0]["mime_type"]) writer = file_handler.getWriterByMimeType(preferred_format["mime_type"])
else: else:
writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(file_formats[0]["mime_type"]) writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(preferred_format["mime_type"])
extension = file_formats[0]["extension"] extension = preferred_format["extension"]
if file_name is None: if file_name is None:
file_name = self._automaticFileName(nodes) file_name = self._automaticFileName(nodes)
@ -80,8 +82,11 @@ class RemovableDriveOutputDevice(OutputDevice):
try: try:
Logger.log("d", "Writing to %s", file_name) Logger.log("d", "Writing to %s", file_name)
# Using buffering greatly reduces the write time for many lines of gcode # Using buffering greatly reduces the write time for many lines of gcode
if preferred_format["mode"] == FileWriter.OutputMode.TextMode:
self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8") self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8")
job = WriteFileJob(writer, self._stream, nodes, MeshWriter.OutputMode.TextMode) else: #Binary mode.
self._stream = open(file_name, "wb", buffering = 1)
job = WriteFileJob(writer, self._stream, nodes, preferred_format["mode"])
job.setFileName(file_name) job.setFileName(file_name)
job.progress.connect(self._onProgress) job.progress.connect(self._onProgress)
job.finished.connect(self._onFinished) job.finished.connect(self._onFinished)

View File

@ -158,6 +158,9 @@ class SimulationView(View):
return self._nozzle_node return self._nozzle_node
def _onSceneChanged(self, node): def _onSceneChanged(self, node):
if node.getMeshData() is None:
self.resetLayerData()
else:
self.setActivity(False) self.setActivity(False)
self.calculateMaxLayers() self.calculateMaxLayers()
self.calculateMaxPathsOnLayer(self._current_layer_num) self.calculateMaxPathsOnLayer(self._current_layer_num)

View File

@ -1,39 +1,103 @@
# 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 UM.Math.Vector import Vector
from UM.Tool import Tool
from PyQt5.QtCore import Qt, QUrl
from UM.Application import Application
from UM.Event import Event
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Settings.SettingInstance import SettingInstance
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
import os import os
import os.path import os.path
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QApplication
from UM.Math.Vector import Vector
from UM.Tool import Tool
from UM.Application import Application
from UM.Event import Event, MouseEvent
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.PickingPass import PickingPass
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from cura.Operations.SetParentOperation import SetParentOperation
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from UM.Scene.GroupDecorator import GroupDecorator
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from UM.Settings.SettingInstance import SettingInstance
class SupportEraser(Tool): class SupportEraser(Tool):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._shortcut_key = Qt.Key_G self._shortcut_key = Qt.Key_G
self._controller = Application.getInstance().getController() self._controller = self.getController()
self._selection_pass = None
Application.getInstance().globalContainerStackChanged.connect(self._updateEnabled)
# Note: if the selection is cleared with this tool active, there is no way to switch to
# another tool than to reselect an object (by clicking it) because the tool buttons in the
# toolbar will have been disabled. That is why we need to ignore the first press event
# after the selection has been cleared.
Selection.selectionChanged.connect(self._onSelectionChanged)
self._had_selection = False
self._skip_press = False
self._had_selection_timer = QTimer()
self._had_selection_timer.setInterval(0)
self._had_selection_timer.setSingleShot(True)
self._had_selection_timer.timeout.connect(self._selectionChangeDelay)
def event(self, event): def event(self, event):
super().event(event) super().event(event)
modifiers = QApplication.keyboardModifiers()
ctrl_is_active = modifiers & Qt.ControlModifier
if event.type == Event.ToolActivateEvent: if event.type == Event.MousePressEvent and self._controller.getToolsEnabled():
if ctrl_is_active:
self._controller.setActiveTool("TranslateTool")
return
# Load the remover mesh: if self._skip_press:
self._createEraserMesh() # The selection was previously cleared, do not add/remove an anti-support mesh but
# use this click for selection and reactivating this tool only.
self._skip_press = False
return
# After we load the mesh, deactivate the tool again: if self._selection_pass is None:
self.getController().setActiveTool(None) # The selection renderpass is used to identify objects in the current view
self._selection_pass = Application.getInstance().getRenderer().getRenderPass("selection")
picked_node = self._controller.getScene().findObject(self._selection_pass.getIdAtPosition(event.x, event.y))
if not picked_node:
# There is no slicable object at the picked location
return
def _createEraserMesh(self): node_stack = picked_node.callDecoration("getStack")
if node_stack:
if node_stack.getProperty("anti_overhang_mesh", "value"):
self._removeEraserMesh(picked_node)
return
elif node_stack.getProperty("support_mesh", "value") or node_stack.getProperty("infill_mesh", "value") or node_stack.getProperty("cutting_mesh", "value"):
# Only "normal" meshes can have anti_overhang_meshes added to them
return
# Create a pass for picking a world-space location from the mouse location
active_camera = self._controller.getScene().getActiveCamera()
picking_pass = PickingPass(active_camera.getViewportWidth(), active_camera.getViewportHeight())
picking_pass.render()
picked_position = picking_pass.getPickedPosition(event.x, event.y)
# Add the anti_overhang_mesh cube at the picked location
self._createEraserMesh(picked_node, picked_position)
def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
node = CuraSceneNode() node = CuraSceneNode()
node.setName("Eraser") node.setName("Eraser")
@ -41,9 +105,7 @@ class SupportEraser(Tool):
mesh = MeshBuilder() mesh = MeshBuilder()
mesh.addCube(10,10,10) mesh.addCube(10,10,10)
node.setMeshData(mesh.build()) node.setMeshData(mesh.build())
# Place the cube in the platform. Do it manually so it works if the "automatic drop models" is OFF node.setPosition(position)
move_vector = Vector(0, 5, 0)
node.setPosition(move_vector)
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
@ -51,21 +113,60 @@ class SupportEraser(Tool):
node.addDecorator(BuildPlateDecorator(active_build_plate)) node.addDecorator(BuildPlateDecorator(active_build_plate))
node.addDecorator(SliceableObjectDecorator()) node.addDecorator(SliceableObjectDecorator())
stack = node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway. stack = node.callDecoration("getStack") # created by SettingOverrideDecorator
if not stack:
node.addDecorator(SettingOverrideDecorator())
stack = node.callDecoration("getStack")
settings = stack.getTop() settings = stack.getTop()
if not (settings.getInstance("anti_overhang_mesh") and settings.getProperty("anti_overhang_mesh", "value")):
definition = stack.getSettingDefinition("anti_overhang_mesh") definition = stack.getSettingDefinition("anti_overhang_mesh")
new_instance = SettingInstance(definition, settings) new_instance = SettingInstance(definition, settings)
new_instance.setProperty("value", True) new_instance.setProperty("value", True)
new_instance.resetState() # Ensure that the state is not seen as a user state. new_instance.resetState() # Ensure that the state is not seen as a user state.
settings.addInstance(new_instance) settings.addInstance(new_instance)
scene = self._controller.getScene() root = self._controller.getScene().getRoot()
op = AddSceneNodeOperation(node, scene.getRoot())
op = GroupedOperation()
# First add the node to the scene, so it gets the expected transform
op.addOperation(AddSceneNodeOperation(node, root))
op.addOperation(SetParentOperation(node, parent))
op.push() op.push()
Application.getInstance().getController().getScene().sceneChanged.emit(node) Application.getInstance().getController().getScene().sceneChanged.emit(node)
def _removeEraserMesh(self, node: CuraSceneNode):
parent = node.getParent()
if parent == self._controller.getScene().getRoot():
parent = None
op = RemoveSceneNodeOperation(node)
op.push()
if parent and not Selection.isSelected(parent):
Selection.add(parent)
Application.getInstance().getController().getScene().sceneChanged.emit(node)
def _updateEnabled(self):
plugin_enabled = False
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
plugin_enabled = global_container_stack.getProperty("anti_overhang_mesh", "enabled")
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled)
def _onSelectionChanged(self):
# When selection is passed from one object to another object, first the selection is cleared
# and then it is set to the new object. We are only interested in the change from no selection
# to a selection or vice-versa, not in a change from one object to another. A timer is used to
# "merge" a possible clear/select action in a single frame
if Selection.hasSelection() != self._had_selection:
self._had_selection_timer.start()
def _selectionChangeDelay(self):
has_selection = Selection.hasSelection()
if not has_selection and self._had_selection:
self._skip_press = True
else:
self._skip_press = False
self._had_selection = has_selection

View File

@ -13,6 +13,7 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
def __init__(self, output_device): def __init__(self, output_device):
super().__init__(output_device) super().__init__(output_device)
self.can_pre_heat_bed = False self.can_pre_heat_bed = False
self.can_pre_heat_hotends = False
self.can_control_manually = False self.can_control_manually = False
def setJobState(self, job: "PrintJobOutputModel", state: str): def setJobState(self, job: "PrintJobOutputModel", state: str):

View File

@ -147,6 +147,10 @@ class DiscoverUM3Action(MachineAction):
return "" return ""
@pyqtSlot(str, result = bool)
def existsKey(self, key) -> bool:
return Application.getInstance().getMachineManager().existNetworkInstances(network_key = key)
@pyqtSlot() @pyqtSlot()
def loadConfigurationFromPrinter(self): def loadConfigurationFromPrinter(self):
machine_manager = Application.getInstance().getMachineManager() machine_manager = Application.getInstance().getMachineManager()

View File

@ -5,6 +5,7 @@ import QtQuick 2.2
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Window 2.1 import QtQuick.Window 2.1
import QtQuick.Dialogs 1.2
Cura.MachineAction Cura.MachineAction
{ {
@ -34,13 +35,31 @@ Cura.MachineAction
var printerKey = base.selectedDevice.key var printerKey = base.selectedDevice.key
var printerName = base.selectedDevice.name // TODO To change when the groups have a name var printerName = base.selectedDevice.name // TODO To change when the groups have a name
if (manager.getStoredKey() != printerKey) if (manager.getStoredKey() != printerKey)
{
// Check if there is another instance with the same key
if (!manager.existsKey(printerKey))
{ {
manager.setKey(printerKey) manager.setKey(printerKey)
manager.setGroupName(printerName) // TODO To change when the groups have a name manager.setGroupName(printerName) // TODO To change when the groups have a name
completed() completed()
} }
else
{
existingConnectionDialog.open()
} }
} }
}
}
MessageDialog
{
id: existingConnectionDialog
title: catalog.i18nc("@window:title", "Existing Connection")
icon: StandardIcon.Information
text: catalog.i18nc("@message:text", "This printer/group is already added to Cura. Please select another printer/group.")
standardButtons: StandardButton.Ok
modality: Qt.ApplicationModal
}
Column Column
{ {

View File

@ -82,6 +82,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._zero_conf_browser.cancel() self._zero_conf_browser.cancel()
self._zero_conf_browser = None # Force the old ServiceBrowser to be destroyed. self._zero_conf_browser = None # Force the old ServiceBrowser to be destroyed.
for instance_name in list(self._discovered_devices):
self._onRemoveDevice(instance_name)
self._zero_conf = Zeroconf() self._zero_conf = Zeroconf()
self._zero_conf_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', self._zero_conf_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.',
[self._appendServiceChangedRequest]) [self._appendServiceChangedRequest])

View File

@ -1,68 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from PyQt5.QtCore import QTimer
MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
class USBPrinterOutputController(PrinterOutputController):
def __init__(self, output_device):
super().__init__(output_device)
self._preheat_bed_timer = QTimer()
self._preheat_bed_timer.setSingleShot(True)
self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
self._preheat_printer = None
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
self._output_device.sendCommand("G91")
self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
self._output_device.sendCommand("G90")
def homeHead(self, printer):
self._output_device.sendCommand("G28 X")
self._output_device.sendCommand("G28 Y")
def homeBed(self, printer):
self._output_device.sendCommand("G28 Z")
def setJobState(self, job: "PrintJobOutputModel", state: str):
if state == "pause":
self._output_device.pausePrint()
job.updateState("paused")
elif state == "print":
self._output_device.resumePrint()
job.updateState("printing")
elif state == "abort":
self._output_device.cancelPrint()
pass
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
try:
temperature = round(temperature) # The API doesn't allow floating point.
duration = round(duration)
except ValueError:
return # Got invalid values, can't pre-heat.
self.setTargetBedTemperature(printer, temperature=temperature)
self._preheat_bed_timer.setInterval(duration * 1000)
self._preheat_bed_timer.start()
self._preheat_printer = printer
printer.updateIsPreheating(True)
def cancelPreheatBed(self, printer: "PrinterOutputModel"):
self.preheatBed(printer, temperature=0, duration=0)
self._preheat_bed_timer.stop()
printer.updateIsPreheating(False)
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
self._output_device.sendCommand("M140 S%s" % temperature)
def _onPreheatBedTimerFinished(self):
self.setTargetBedTemperature(self._preheat_printer, 0)
self._preheat_printer.updateIsPreheating(False)

View File

@ -10,9 +10,9 @@ from UM.PluginRegistry import PluginRegistry
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.GenericOutputController import GenericOutputController
from .AutoDetectBaudJob import AutoDetectBaudJob from .AutoDetectBaudJob import AutoDetectBaudJob
from .USBPrinterOutputController import USBPrinterOutputController
from .avr_isp import stk500v2, intelHex from .avr_isp import stk500v2, intelHex
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
@ -240,7 +240,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
container_stack = Application.getInstance().getGlobalContainerStack() container_stack = Application.getInstance().getGlobalContainerStack()
num_extruders = container_stack.getProperty("machine_extruder_count", "value") num_extruders = container_stack.getProperty("machine_extruder_count", "value")
# Ensure that a printer is created. # Ensure that a printer is created.
self._printers = [PrinterOutputModel(output_controller=USBPrinterOutputController(self), number_of_extruders=num_extruders)] self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)]
self._printers[0].updateName(container_stack.getName()) self._printers[0].updateName(container_stack.getName())
self.setConnectionState(ConnectionState.connected) self.setConnectionState(ConnectionState.connected)
self._update_thread.start() self._update_thread.start()
@ -372,7 +372,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
elapsed_time = int(time() - self._print_start_time) elapsed_time = int(time() - self._print_start_time)
print_job = self._printers[0].activePrintJob print_job = self._printers[0].activePrintJob
if print_job is None: if print_job is None:
print_job = PrintJobOutputModel(output_controller = USBPrinterOutputController(self), name= Application.getInstance().getPrintInformation().jobName) print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= Application.getInstance().getPrintInformation().jobName)
print_job.updateState("printing") print_job.updateState("printing")
self._printers[0].updateActivePrintJob(print_job) self._printers[0].updateActivePrintJob(print_job)

View File

@ -153,6 +153,10 @@ class VersionUpgrade26to27(VersionUpgrade):
if new_id is not None: if new_id is not None:
parser.set("containers", key, new_id) parser.set("containers", key, new_id)
if "6" not in parser["containers"]:
parser["containers"]["6"] = parser["containers"]["5"]
parser["containers"]["5"] = "empty"
for each_section in ("general", "metadata"): for each_section in ("general", "metadata"):
if not parser.has_section(each_section): if not parser.has_section(each_section):
parser.add_section(each_section) parser.add_section(each_section)

View File

@ -208,14 +208,9 @@ class XmlMaterialProfile(InstanceContainer):
machine_variant_map = {} machine_variant_map = {}
variant_manager = CuraApplication.getInstance().getVariantManager() variant_manager = CuraApplication.getInstance().getVariantManager()
material_manager = CuraApplication.getInstance().getMaterialManager()
root_material_id = self.getMetaDataEntry("base_file") # if basefile is self.getId, this is a basefile. root_material_id = self.getMetaDataEntry("base_file") # if basefile is self.getId, this is a basefile.
material_group = material_manager.getMaterialGroup(root_material_id) all_containers = registry.findInstanceContainers(base_file = root_material_id)
all_containers = []
for node in [material_group.root_material_node] + material_group.derived_material_node_list:
all_containers.append(node.getContainer())
for container in all_containers: for container in all_containers:
definition_id = container.getMetaDataEntry("definition") definition_id = container.getMetaDataEntry("definition")
@ -242,7 +237,7 @@ class XmlMaterialProfile(InstanceContainer):
for definition_id, container in machine_container_map.items(): for definition_id, container in machine_container_map.items():
definition_id = container.getMetaDataEntry("definition") definition_id = container.getMetaDataEntry("definition")
definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = definition_id)[0] definition_metadata = registry.findDefinitionContainersMetadata(id = definition_id)[0]
product = definition_id product = definition_id
for product_name, product_id_list in product_id_map.items(): for product_name, product_id_list in product_id_map.items():

View File

@ -6,7 +6,7 @@
"visible": true, "visible": true,
"manufacturer": "BQ", "manufacturer": "BQ",
"author": "BQ", "author": "BQ",
"file_formats": "text/x-code", "file_formats": "text/x-gcode",
"platform": "bq_hephestos_platform.stl", "platform": "bq_hephestos_platform.stl",
"platform_offset": [ 0, -82, 0] "platform_offset": [ 0, -82, 0]
}, },

View File

@ -632,6 +632,73 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
}, },
"machine_steps_per_mm_x":
{
"label": "Steps per Millimeter (X)",
"description": "How many steps of the stepper motor will result in one millimeter of movement in the X direction.",
"type": "int",
"default_value": 50,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_steps_per_mm_y":
{
"label": "Steps per Millimeter (Y)",
"description": "How many steps of the stepper motor will result in one millimeter of movement in the Y direction.",
"type": "int",
"default_value": 50,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_steps_per_mm_z":
{
"label": "Steps per Millimeter (Z)",
"description": "How many steps of the stepper motor will result in one millimeter of movement in the Z direction.",
"type": "int",
"default_value": 50,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_steps_per_mm_e":
{
"label": "Steps per Millimeter (E)",
"description": "How many steps of the stepper motors will result in one millimeter of extrusion.",
"type": "int",
"default_value": 1600,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_endstop_positive_direction_x":
{
"label": "X Endstop in Positive Direction",
"description": "Whether the endstop of the X axis is in the positive direction (high X coordinate) or negative (low X coordinate).",
"type": "bool",
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_endstop_positive_direction_y":
{
"label": "Y Endstop in Positive Direction",
"description": "Whether the endstop of the Y axis is in the positive direction (high Y coordinate) or negative (low Y coordinate).",
"type": "bool",
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_endstop_positive_direction_z":
{
"label": "Z Endstop in Positive Direction",
"description": "Whether the endstop of the Z axis is in the positive direction (high Z coordinate) or negative (low Z coordinate).",
"type": "bool",
"default_value": true,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_minimum_feedrate": "machine_minimum_feedrate":
{ {
"label": "Minimum Feedrate", "label": "Minimum Feedrate",
@ -642,6 +709,16 @@
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
},
"machine_feeder_wheel_diameter":
{
"label": "Feeder Wheel Diameter",
"description": "The diameter of the wheel that drives the material in the feeder.",
"unit": "mm",
"type": "float",
"default_value": 10.0,
"settable_per_mesh": false,
"settable_per_extruder": true
} }
} }
}, },

View File

@ -25,8 +25,7 @@
"default_value": true "default_value": true
}, },
"machine_nozzle_size": { "machine_nozzle_size": {
"default_value": 0.4, "default_value": 0.4
"minimum_value": "0.001"
}, },
"machine_head_with_fans_polygon": { "machine_head_with_fans_polygon": {
"default_value": [ "default_value": [
@ -36,6 +35,21 @@
[ 18, 35 ] [ 18, 35 ]
] ]
}, },
"machine_max_feedrate_z": {
"default_value": 400
},
"machine_steps_per_mm_x": {
"default_value": 93
},
"machine_steps_per_mm_y": {
"default_value": 93
},
"machine_steps_per_mm_z": {
"default_value": 1600
},
"machine_steps_per_mm_e": {
"default_value": 92
},
"gantry_height": { "gantry_height": {
"default_value": 55 "default_value": 55
}, },

View File

@ -25,6 +25,7 @@
"machine_nozzle_size": { "default_value": 0.5 }, "machine_nozzle_size": { "default_value": 0.5 },
"machine_shape": { "default_value": "elliptic" }, "machine_shape": { "default_value": "elliptic" },
"machine_width": { "default_value": 290 }, "machine_width": { "default_value": 290 },
"material_diameter": { "default_value": 1.75 },
"relative_extrusion": { "default_value": false }, "relative_extrusion": { "default_value": false },
"retraction_amount": { "default_value": 3.2 }, "retraction_amount": { "default_value": 3.2 },
"retraction_combing": { "default_value": "off" }, "retraction_combing": { "default_value": "off" },

View File

@ -25,6 +25,7 @@
"machine_nozzle_size": { "default_value": 0.5 }, "machine_nozzle_size": { "default_value": 0.5 },
"machine_shape": { "default_value": "elliptic" }, "machine_shape": { "default_value": "elliptic" },
"machine_width": { "default_value": 265 }, "machine_width": { "default_value": 265 },
"material_diameter": { "default_value": 1.75 },
"relative_extrusion": { "default_value": false }, "relative_extrusion": { "default_value": false },
"retraction_amount": { "default_value": 3.2 }, "retraction_amount": { "default_value": 3.2 },
"retraction_combing": { "default_value": "off" }, "retraction_combing": { "default_value": "off" },

View File

@ -217,6 +217,7 @@ UM.MainWindow
text: catalog.i18nc("@action:inmenu", "Disable Extruder") text: catalog.i18nc("@action:inmenu", "Disable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false) onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
visible: Cura.MachineManager.getExtruder(model.index).isEnabled visible: Cura.MachineManager.getExtruder(model.index).isEnabled
enabled: Cura.MachineManager.numberExtrudersEnabled > 1
} }
} }

View File

@ -10,17 +10,22 @@ import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
import "Menus" import "Menus"
ToolButton { ToolButton
{
id: base id: base
property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != "" property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property var printerStatus: Cura.MachineManager.printerOutputDevices.length != 0 ? "connected" : "disconnected" property var printerStatus: Cura.MachineManager.printerOutputDevices.length != 0 ? "connected" : "disconnected"
text: isNetworkPrinter ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName text: isNetworkPrinter ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName
tooltip: Cura.MachineManager.activeMachineName tooltip: Cura.MachineManager.activeMachineName
style: ButtonStyle { style: ButtonStyle
background: Rectangle { {
color: { background: Rectangle
{
color:
{
if (control.pressed) { if (control.pressed) {
return UM.Theme.getColor("sidebar_header_active"); return UM.Theme.getColor("sidebar_header_active");
} }
@ -33,7 +38,8 @@ ToolButton {
} }
Behavior on color { ColorAnimation { duration: 50; } } Behavior on color { ColorAnimation { duration: 50; } }
UM.RecolorImage { UM.RecolorImage
{
id: downArrow id: downArrow
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
@ -46,24 +52,27 @@ ToolButton {
source: UM.Theme.getIcon("arrow_bottom") source: UM.Theme.getIcon("arrow_bottom")
} }
PrinterStatusIcon { PrinterStatusIcon
{
id: printerStatusIcon id: printerStatusIcon
visible: isNetworkPrinter visible: printerConnected || isNetworkPrinter
status: printerStatus status: printerStatus
anchors { anchors
{
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
left: parent.left left: parent.left
leftMargin: UM.Theme.getSize("sidebar_margin").width leftMargin: UM.Theme.getSize("sidebar_margin").width
} }
} }
Label { Label
{
id: sidebarComboBoxLabel id: sidebarComboBoxLabel
color: UM.Theme.getColor("sidebar_header_text_active") color: UM.Theme.getColor("sidebar_header_text_active")
text: control.text; text: control.text;
elide: Text.ElideRight; elide: Text.ElideRight;
anchors.left: isNetworkPrinter ? printerStatusIcon.right : parent.left; anchors.left: printerStatusIcon.visible ? printerStatusIcon.right : parent.left;
anchors.leftMargin: isNetworkPrinter ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width anchors.leftMargin: printerStatusIcon.visible ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width
anchors.right: downArrow.left; anchors.right: downArrow.left;
anchors.rightMargin: control.rightMargin; anchors.rightMargin: control.rightMargin;
anchors.verticalCenter: parent.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
@ -74,14 +83,4 @@ ToolButton {
} }
menu: PrinterMenu { } menu: PrinterMenu { }
// Make the toolbutton react when the outputdevice changes
Connections
{
target: Cura.MachineManager
onOutputDevicesChanged:
{
base.isNetworkPrinter = Cura.MachineManager.activeMachineNetworkKey != ""
}
}
} }

View File

@ -66,6 +66,7 @@ Column
configuration: modelData configuration: modelData
onActivateConfiguration: onActivateConfiguration:
{ {
switchPopupState()
Cura.MachineManager.applyRemoteConfiguration(configuration) Cura.MachineManager.applyRemoteConfiguration(configuration)
} }
} }

View File

@ -13,54 +13,53 @@ Item
id: configurationSelector id: configurationSelector
property var connectedDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null property var connectedDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
property var panelWidth: control.width property var panelWidth: control.width
property var panelVisible: false
SyncButton { function switchPopupState()
onClicked: configurationSelector.state == "open" ? configurationSelector.state = "closed" : configurationSelector.state = "open" {
popup.visible ? popup.close() : popup.open()
}
SyncButton
{
id: syncButton
onClicked: switchPopupState()
outputDevice: connectedDevice outputDevice: connectedDevice
} }
Popup { Popup
{
// TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
id: popup id: popup
clip: true clip: true
closePolicy: Popup.CloseOnPressOutsideParent
y: configurationSelector.height - UM.Theme.getSize("default_lining").height y: configurationSelector.height - UM.Theme.getSize("default_lining").height
x: configurationSelector.width - width x: configurationSelector.width - width
width: panelWidth width: panelWidth
visible: panelVisible && connectedDevice != null visible: false
padding: UM.Theme.getSize("default_lining").width padding: UM.Theme.getSize("default_lining").width
contentItem: ConfigurationListView { transformOrigin: Popup.Top
contentItem: ConfigurationListView
{
id: configList id: configList
width: panelWidth - 2 * popup.padding width: panelWidth - 2 * popup.padding
outputDevice: connectedDevice outputDevice: connectedDevice
} }
background: Rectangle { background: Rectangle
{
color: UM.Theme.getColor("setting_control") color: UM.Theme.getColor("setting_control")
border.color: UM.Theme.getColor("setting_control_border") border.color: UM.Theme.getColor("setting_control_border")
} }
} exit: Transition
{
states: [
// This adds a second state to the container where the rectangle is farther to the right
State {
name: "open"
PropertyChanges {
target: popup
height: configList.computedHeight
}
},
State {
name: "closed"
PropertyChanges {
target: popup
height: 0
}
}
]
transitions: [
// This adds a transition that defaults to applying to all state changes
Transition {
// This applies a default NumberAnimation to any changes a state change makes to x or y properties // This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { properties: "height"; duration: 200; easing.type: Easing.InOutQuad; } NumberAnimation { property: "visible"; duration: 75; }
}
enter: Transition
{
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { property: "visible"; duration: 75; }
}
onClosed: visible = false
onOpened: visible = true
} }
]
} }

View File

@ -68,7 +68,8 @@ Button
color: UM.Theme.getColor("text_emphasis") color: UM.Theme.getColor("text_emphasis")
source: UM.Theme.getIcon("arrow_bottom") source: UM.Theme.getIcon("arrow_bottom")
} }
UM.RecolorImage { UM.RecolorImage
{
id: sidebarComboBoxLabel id: sidebarComboBoxLabel
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.leftMargin: UM.Theme.getSize("default_margin").width
@ -86,22 +87,15 @@ Button
label: Label {} label: Label {}
} }
onClicked: Connections
{ {
panelVisible = !panelVisible
}
Connections {
target: outputDevice target: outputDevice
onUniqueConfigurationsChanged: { onUniqueConfigurationsChanged: updateOnSync()
updateOnSync()
}
} }
Connections { Connections
{
target: Cura.MachineManager target: Cura.MachineManager
onCurrentConfigurationChanged: { onCurrentConfigurationChanged: updateOnSync()
updateOnSync()
}
} }
} }

View File

@ -31,7 +31,7 @@ Menu
MenuItem { MenuItem {
text: "%1: %2 - %3".arg(model.name).arg(model.material).arg(model.variant) text: "%1: %2 - %3".arg(model.name).arg(model.material).arg(model.variant)
visible: base.shouldShowExtruders visible: base.shouldShowExtruders
enabled: UM.Selection.hasSelection enabled: UM.Selection.hasSelection && model.enabled
checkable: true checkable: true
checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1 checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1
onTriggered: CuraActions.setExtruderForSelection(model.id) onTriggered: CuraActions.setExtruderForSelection(model.id)

View File

@ -1,8 +1,8 @@
// 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.
import QtQuick 2.2 import QtQuick 2.7
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
@ -12,44 +12,26 @@ Menu
id: menu id: menu
title: catalog.i18nc("@action:inmenu", "Visible Settings") title: catalog.i18nc("@action:inmenu", "Visible Settings")
property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
property bool showingSearchResults property bool showingSearchResults
property bool showingAllSettings property bool showingAllSettings
signal showAllSettings() signal showAllSettings()
signal showSettingVisibilityProfile() signal showSettingVisibilityProfile()
MenuItem
{
text: catalog.i18nc("@action:inmenu", "Custom selection")
checkable: true
checked: !showingSearchResults && !showingAllSettings && Cura.SettingVisibilityPresetsModel.activePreset == "custom"
exclusiveGroup: group
onTriggered:
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
// Restore custom set from preference
UM.Preferences.setValue("general/visible_settings", UM.Preferences.getValue("cura/custom_visible_settings"));
showSettingVisibilityProfile();
}
}
MenuSeparator { }
Instantiator Instantiator
{ {
model: Cura.SettingVisibilityPresetsModel model: settingVisibilityPresetsModel
MenuItem MenuItem
{ {
text: model.name text: model.name
checkable: true checkable: true
checked: model.id == Cura.SettingVisibilityPresetsModel.activePreset checked: model.id == settingVisibilityPresetsModel.activePreset
exclusiveGroup: group exclusiveGroup: group
onTriggered: onTriggered:
{ {
Cura.SettingVisibilityPresetsModel.setActivePreset(model.id); settingVisibilityPresetsModel.setActivePreset(model.id);
UM.Preferences.setValue("general/visible_settings", model.settings.join(";"));
showSettingVisibilityProfile(); showSettingVisibilityProfile();
} }
} }

View File

@ -36,8 +36,8 @@ TabView
if (!base.containerId || !base.editingEnabled) { if (!base.containerId || !base.editingEnabled) {
return "" return ""
} }
var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode); var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode, true);
if (linkedMaterials.length <= 1) { if (linkedMaterials.length == 0) {
return "" return ""
} }
return linkedMaterials.join(", "); return linkedMaterials.join(", ");
@ -99,6 +99,7 @@ TabView
property var new_diameter_value: null; property var new_diameter_value: null;
property var old_diameter_value: null; property var old_diameter_value: null;
property var old_approximate_diameter_value: null; property var old_approximate_diameter_value: null;
property bool keyPressed: false
onYes: onYes:
{ {
@ -112,6 +113,16 @@ TabView
properties.diameter = old_diameter_value; properties.diameter = old_diameter_value;
diameterSpinBox.value = properties.diameter; diameterSpinBox.value = properties.diameter;
} }
onVisibilityChanged:
{
if (!visible && !keyPressed)
{
// If the user closes this dialog without clicking on any button, it's the same as clicking "No".
no();
}
keyPressed = false;
}
} }
Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Display Name") } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Display Name") }
@ -222,7 +233,7 @@ TabView
var old_diameter = Cura.ContainerManager.getContainerProperty(base.containerId, "material_diameter", "value").toString(); var old_diameter = Cura.ContainerManager.getContainerProperty(base.containerId, "material_diameter", "value").toString();
var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter"); var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter");
var new_approximate_diameter = getApproximateDiameter(value); var new_approximate_diameter = getApproximateDiameter(value);
if (Cura.MachineManager.filterMaterialsByMachine && new_approximate_diameter != Cura.ExtruderManager.getActiveExtruderStack().approximateMaterialDiameter) if (new_approximate_diameter != Cura.ExtruderManager.getActiveExtruderStack().approximateMaterialDiameter)
{ {
confirmDiameterChangeDialog.old_diameter_value = old_diameter; confirmDiameterChangeDialog.old_diameter_value = old_diameter;
confirmDiameterChangeDialog.new_diameter_value = value; confirmDiameterChangeDialog.new_diameter_value = value;

View File

@ -364,6 +364,7 @@ Item
} }
width: true ? (parent.width * 0.4) | 0 : parent.width width: true ? (parent.width * 0.4) | 0 : parent.width
frameVisible: true
ListView ListView
{ {

View File

@ -369,6 +369,7 @@ Item
} }
width: true ? (parent.width * 0.4) | 0 : parent.width width: true ? (parent.width * 0.4) | 0 : parent.width
frameVisible: true
ListView ListView
{ {

View File

@ -34,6 +34,8 @@ Item
anchors.fill: parent anchors.fill: parent
onEditingFinished: base.editingFinished() onEditingFinished: base.editingFinished()
Keys.onEnterPressed: base.editingFinished()
Keys.onReturnPressed: base.editingFinished()
} }
Label Label

View File

@ -29,6 +29,8 @@ Item
anchors.fill: parent anchors.fill: parent
onEditingFinished: base.editingFinished() onEditingFinished: base.editingFinished()
Keys.onEnterPressed: base.editingFinished()
Keys.onReturnPressed: base.editingFinished()
} }
Label Label

View File

@ -13,6 +13,8 @@ UM.PreferencesPage
{ {
title: catalog.i18nc("@title:tab", "Setting Visibility"); title: catalog.i18nc("@title:tab", "Setting Visibility");
property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
property int scrollToIndex: 0 property int scrollToIndex: 0
signal scrollToSection( string key ) signal scrollToSection( string key )
@ -27,8 +29,7 @@ UM.PreferencesPage
// After calling this function update Setting visibility preset combobox. // After calling this function update Setting visibility preset combobox.
// Reset should set default setting preset ("Basic") // Reset should set default setting preset ("Basic")
visibilityPreset.setDefaultPreset() visibilityPreset.currentIndex = 1
} }
resetEnabled: true; resetEnabled: true;
@ -37,8 +38,6 @@ UM.PreferencesPage
id: base; id: base;
anchors.fill: parent; anchors.fill: parent;
property bool inhibitSwitchToCustom: false
CheckBox CheckBox
{ {
id: toggleVisibleSettings id: toggleVisibleSettings
@ -112,11 +111,6 @@ UM.PreferencesPage
ComboBox ComboBox
{ {
function setDefaultPreset()
{
visibilityPreset.currentIndex = 0
}
id: visibilityPreset id: visibilityPreset
width: 150 * screenScaleFactor width: 150 * screenScaleFactor
anchors anchors
@ -125,51 +119,25 @@ UM.PreferencesPage
right: parent.right right: parent.right
} }
model: ListModel model: settingVisibilityPresetsModel
{ textRole: "name"
id: visibilityPresetsModel
Component.onCompleted:
{
visibilityPresetsModel.append({text: catalog.i18nc("@action:inmenu", "Custom selection"), id: "custom"});
var presets = Cura.SettingVisibilityPresetsModel;
for(var i = 0; i < presets.rowCount(); i++)
{
visibilityPresetsModel.append({text: presets.getItem(i)["name"], id: presets.getItem(i)["id"]});
}
}
}
currentIndex: currentIndex:
{ {
// Load previously selected preset. // Load previously selected preset.
var index = Cura.SettingVisibilityPresetsModel.find("id", Cura.SettingVisibilityPresetsModel.activePreset); var index = settingVisibilityPresetsModel.find("id", settingVisibilityPresetsModel.activePreset)
if (index == -1) if (index == -1)
{ {
return 0; return 0
} }
return index + 1; // "Custom selection" entry is added in front, so index is off by 1 return index
} }
onActivated: onActivated:
{ {
base.inhibitSwitchToCustom = true; var preset_id = settingVisibilityPresetsModel.getItem(index).id;
var preset_id = visibilityPresetsModel.get(index).id; settingVisibilityPresetsModel.setActivePreset(preset_id);
Cura.SettingVisibilityPresetsModel.setActivePreset(preset_id);
UM.Preferences.setValue("cura/active_setting_visibility_preset", preset_id);
if (preset_id != "custom")
{
UM.Preferences.setValue("general/visible_settings", Cura.SettingVisibilityPresetsModel.getItem(index - 1).settings.join(";"));
// "Custom selection" entry is added in front, so index is off by 1
}
else
{
// Restore custom set from preference
UM.Preferences.setValue("general/visible_settings", UM.Preferences.getValue("cura/custom_visible_settings"));
}
base.inhibitSwitchToCustom = false;
} }
} }
@ -199,16 +167,7 @@ UM.PreferencesPage
exclude: ["machine_settings", "command_line_settings"] exclude: ["machine_settings", "command_line_settings"]
showAncestors: true showAncestors: true
expanded: ["*"] expanded: ["*"]
visibilityHandler: UM.SettingPreferenceVisibilityHandler visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
{
onVisibilityChanged:
{
if(Cura.SettingVisibilityPresetsModel.activePreset != "" && !base.inhibitSwitchToCustom)
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
}
}
}
} }
delegate: Loader delegate: Loader

View File

@ -12,9 +12,20 @@ Item
property alias color: background.color property alias color: background.color
property var extruderModel property var extruderModel
property var position: index property var position: index
//width: index == machineExtruderCount.properties.value - 1 && index % 2 == 0 ? extrudersGrid.width : Math.floor(extrudersGrid.width / 2 - UM.Theme.getSize("sidebar_lining_thin").width / 2)
implicitWidth: parent.width implicitWidth: parent.width
implicitHeight: UM.Theme.getSize("sidebar_extruder_box").height implicitHeight: UM.Theme.getSize("sidebar_extruder_box").height
UM.SettingPropertyProvider
{
id: extruderTemperature
containerStackId: Cura.ExtruderManager.extruderIds[position]
key: "material_print_temperature"
watchedProperties: ["value", "minimum_value", "maximum_value", "resolve"]
storeIndex: 0
property var resolve: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId ? properties.resolve : "None"
}
Rectangle Rectangle
{ {
id: background id: background
@ -34,12 +45,11 @@ Item
{ {
id: extruderTargetTemperature id: extruderTargetTemperature
text: Math.round(extruderModel.targetHotendTemperature) + "°C" text: Math.round(extruderModel.targetHotendTemperature) + "°C"
//text: (connectedPrinter != null && connectedPrinter.hotendIds[index] != null && connectedPrinter.targetHotendTemperatures[index] != null) ? Math.round(connectedPrinter.targetHotendTemperatures[index]) + "°C" : ""
font: UM.Theme.getFont("small") font: UM.Theme.getFont("small")
color: UM.Theme.getColor("text_inactive") color: UM.Theme.getColor("text_inactive")
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.bottom: extruderTemperature.bottom anchors.bottom: extruderCurrentTemperature.bottom
MouseArea //For tooltip. MouseArea //For tooltip.
{ {
@ -52,7 +62,7 @@ Item
{ {
base.showTooltip( base.showTooltip(
base, base,
{x: 0, y: extruderTargetTemperature.mapToItem(base, 0, -parent.height / 4).y}, {x: 0, y: extruderTargetTemperature.mapToItem(base, 0, Math.floor(-parent.height / 4)).y},
catalog.i18nc("@tooltip", "The target temperature of the hotend. The hotend will heat up or cool down towards this temperature. If this is 0, the hotend heating is turned off.") catalog.i18nc("@tooltip", "The target temperature of the hotend. The hotend will heat up or cool down towards this temperature. If this is 0, the hotend heating is turned off.")
); );
} }
@ -65,9 +75,8 @@ Item
} }
Label //Temperature indication. Label //Temperature indication.
{ {
id: extruderTemperature id: extruderCurrentTemperature
text: Math.round(extruderModel.hotendTemperature) + "°C" text: Math.round(extruderModel.hotendTemperature) + "°C"
//text: (connectedPrinter != null && connectedPrinter.hotendIds[index] != null && connectedPrinter.hotendTemperatures[index] != null) ? Math.round(connectedPrinter.hotendTemperatures[index]) + "°C" : ""
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large")
anchors.right: extruderTargetTemperature.left anchors.right: extruderTargetTemperature.left
@ -76,7 +85,7 @@ Item
MouseArea //For tooltip. MouseArea //For tooltip.
{ {
id: extruderTemperatureTooltipArea id: extruderCurrentTemperatureTooltipArea
hoverEnabled: true hoverEnabled: true
anchors.fill: parent anchors.fill: parent
onHoveredChanged: onHoveredChanged:
@ -85,8 +94,8 @@ Item
{ {
base.showTooltip( base.showTooltip(
base, base,
{x: 0, y: parent.mapToItem(base, 0, -parent.height / 4).y}, {x: 0, y: parent.mapToItem(base, 0, Math.floor(-parent.height / 4)).y},
catalog.i18nc("@tooltip", "The current temperature of this extruder.") catalog.i18nc("@tooltip", "The current temperature of this hotend.")
); );
} }
else else
@ -97,6 +106,272 @@ Item
} }
} }
Rectangle //Input field for pre-heat temperature.
{
id: preheatTemperatureControl
color: !enabled ? UM.Theme.getColor("setting_control_disabled") : showError ? UM.Theme.getColor("setting_validation_error_background") : UM.Theme.getColor("setting_validation_ok")
property var showError:
{
if(extruderTemperature.properties.maximum_value != "None" && extruderTemperature.properties.maximum_value < Math.floor(preheatTemperatureInput.text))
{
return true;
} else
{
return false;
}
}
enabled:
{
if (extruderModel == null)
{
return false; //Can't preheat if not connected.
}
if (!connectedPrinter.acceptsCommands)
{
return false; //Not allowed to do anything.
}
if (connectedPrinter.activePrinter && connectedPrinter.activePrinter.activePrintJob)
{
if((["printing", "pre_print", "resuming", "pausing", "paused", "error", "offline"]).indexOf(connectedPrinter.activePrinter.activePrintJob.state) != -1)
{
return false; //Printer is in a state where it can't react to pre-heating.
}
}
return true;
}
border.width: UM.Theme.getSize("default_lining").width
border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : preheatTemperatureInputMouseArea.containsMouse ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border")
anchors.right: preheatButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.bottom: parent.bottom
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
width: UM.Theme.getSize("monitor_preheat_temperature_control").width
height: UM.Theme.getSize("monitor_preheat_temperature_control").height
visible: extruderModel != null ? enabled && extruderModel.canPreHeatHotends && !extruderModel.isPreheating : true
Rectangle //Highlight of input field.
{
anchors.fill: parent
anchors.margins: UM.Theme.getSize("default_lining").width
color: UM.Theme.getColor("setting_control_highlight")
opacity: preheatTemperatureControl.hovered ? 1.0 : 0
}
MouseArea //Change cursor on hovering.
{
id: preheatTemperatureInputMouseArea
hoverEnabled: true
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onHoveredChanged:
{
if (containsMouse)
{
base.showTooltip(
base,
{x: 0, y: preheatTemperatureInputMouseArea.mapToItem(base, 0, 0).y},
catalog.i18nc("@tooltip of temperature input", "The temperature to pre-heat the hotend to.")
);
}
else
{
base.hideTooltip();
}
}
}
Label
{
id: unit
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("setting_unit_margin").width
anchors.verticalCenter: parent.verticalCenter
text: "°C";
color: UM.Theme.getColor("setting_unit")
font: UM.Theme.getFont("default")
}
TextInput
{
id: preheatTemperatureInput
font: UM.Theme.getFont("default")
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
selectByMouse: true
maximumLength: 5
enabled: parent.enabled
validator: RegExpValidator { regExp: /^-?[0-9]{0,9}[.,]?[0-9]{0,10}$/ } //Floating point regex.
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
anchors.right: unit.left
anchors.verticalCenter: parent.verticalCenter
renderType: Text.NativeRendering
Component.onCompleted:
{
if (!extruderTemperature.properties.value)
{
text = "";
}
else
{
text = extruderTemperature.properties.value;
}
}
}
}
Button //The pre-heat button.
{
id: preheatButton
height: UM.Theme.getSize("setting_control").height
visible: extruderModel != null ? extruderModel.canPreHeatHotends: true
enabled:
{
if (!preheatTemperatureControl.enabled)
{
return false; //Not connected, not authenticated or printer is busy.
}
if (extruderModel.isPreheating)
{
return true;
}
if (extruderTemperature.properties.minimum_value != "None" && Math.floor(preheatTemperatureInput.text) < Math.floor(extruderTemperature.properties.minimum_value))
{
return false; //Target temperature too low.
}
if (extruderTemperature.properties.maximum_value != "None" && Math.floor(preheatTemperatureInput.text) > Math.floor(extruderTemperature.properties.maximum_value))
{
return false; //Target temperature too high.
}
if (Math.floor(preheatTemperatureInput.text) == 0)
{
return false; //Setting the temperature to 0 is not allowed (since that cancels the pre-heating).
}
return true; //Preconditions are met.
}
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: UM.Theme.getSize("default_margin").width
style: ButtonStyle {
background: Rectangle
{
border.width: UM.Theme.getSize("default_lining").width
implicitWidth: actualLabel.contentWidth + (UM.Theme.getSize("default_margin").width * 2)
border.color:
{
if(!control.enabled)
{
return UM.Theme.getColor("action_button_disabled_border");
}
else if(control.pressed)
{
return UM.Theme.getColor("action_button_active_border");
}
else if(control.hovered)
{
return UM.Theme.getColor("action_button_hovered_border");
}
else
{
return UM.Theme.getColor("action_button_border");
}
}
color:
{
if(!control.enabled)
{
return UM.Theme.getColor("action_button_disabled");
}
else if(control.pressed)
{
return UM.Theme.getColor("action_button_active");
}
else if(control.hovered)
{
return UM.Theme.getColor("action_button_hovered");
}
else
{
return UM.Theme.getColor("action_button");
}
}
Behavior on color
{
ColorAnimation
{
duration: 50
}
}
Label
{
id: actualLabel
anchors.centerIn: parent
color:
{
if(!control.enabled)
{
return UM.Theme.getColor("action_button_disabled_text");
}
else if(control.pressed)
{
return UM.Theme.getColor("action_button_active_text");
}
else if(control.hovered)
{
return UM.Theme.getColor("action_button_hovered_text");
}
else
{
return UM.Theme.getColor("action_button_text");
}
}
font: UM.Theme.getFont("action_button")
text:
{
if(extruderModel == null)
{
return ""
}
if(extruderModel.isPreheating )
{
return catalog.i18nc("@button Cancel pre-heating", "Cancel")
} else
{
return catalog.i18nc("@button", "Pre-heat")
}
}
}
}
}
onClicked:
{
if (!extruderModel.isPreheating)
{
extruderModel.preheatHotend(preheatTemperatureInput.text, 900);
}
else
{
extruderModel.cancelPreheatHotend();
}
}
onHoveredChanged:
{
if (hovered)
{
base.showTooltip(
base,
{x: 0, y: preheatButton.mapToItem(base, 0, 0).y},
catalog.i18nc("@tooltip of pre-heat", "Heat the hotend in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the hotend to heat up when you're ready to print.")
);
}
else
{
base.hideTooltip();
}
}
}
Rectangle //Material colour indication. Rectangle //Material colour indication.
{ {
id: materialColor id: materialColor

View File

@ -114,21 +114,24 @@ Item
{ {
return false; //Not allowed to do anything. return false; //Not allowed to do anything.
} }
if (connectedPrinter.jobState == "printing" || connectedPrinter.jobState == "pre_print" || connectedPrinter.jobState == "resuming" || connectedPrinter.jobState == "pausing" || connectedPrinter.jobState == "paused" || connectedPrinter.jobState == "error" || connectedPrinter.jobState == "offline") if (connectedPrinter.activePrinter && connectedPrinter.activePrinter.activePrintJob)
{
if((["printing", "pre_print", "resuming", "pausing", "paused", "error", "offline"]).indexOf(connectedPrinter.activePrinter.activePrintJob.state) != -1)
{ {
return false; //Printer is in a state where it can't react to pre-heating. return false; //Printer is in a state where it can't react to pre-heating.
} }
}
return true; return true;
} }
border.width: UM.Theme.getSize("default_lining").width border.width: UM.Theme.getSize("default_lining").width
border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : preheatTemperatureInputMouseArea.containsMouse ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border") border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : preheatTemperatureInputMouseArea.containsMouse ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border")
anchors.left: parent.left anchors.right: preheatButton.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: UM.Theme.getSize("default_margin").height anchors.bottomMargin: UM.Theme.getSize("default_margin").height
width: UM.Theme.getSize("setting_control").width width: UM.Theme.getSize("monitor_preheat_temperature_control").width
height: UM.Theme.getSize("setting_control").height height: UM.Theme.getSize("monitor_preheat_temperature_control").height
visible: printerModel != null ? printerModel.canPreHeatBed: true visible: printerModel != null ? enabled && printerModel.canPreHeatBed && !printerModel.isPreheating : true
Rectangle //Highlight of input field. Rectangle //Highlight of input field.
{ {
anchors.fill: parent anchors.fill: parent
@ -159,18 +162,29 @@ Item
} }
} }
} }
Label
{
id: unit
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("setting_unit_margin").width
anchors.verticalCenter: parent.verticalCenter
text: "°C";
color: UM.Theme.getColor("setting_unit")
font: UM.Theme.getFont("default")
}
TextInput TextInput
{ {
id: preheatTemperatureInput id: preheatTemperatureInput
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
selectByMouse: true selectByMouse: true
maximumLength: 10 maximumLength: 5
enabled: parent.enabled enabled: parent.enabled
validator: RegExpValidator { regExp: /^-?[0-9]{0,9}[.,]?[0-9]{0,10}$/ } //Floating point regex. validator: RegExpValidator { regExp: /^-?[0-9]{0,9}[.,]?[0-9]{0,10}$/ } //Floating point regex.
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
anchors.right: parent.right anchors.right: unit.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
renderType: Text.NativeRendering renderType: Text.NativeRendering

View File

@ -215,7 +215,8 @@ SettingItem
{ {
text: model.name text: model.name
renderType: Text.NativeRendering renderType: Text.NativeRendering
color: { color:
{
if (model.enabled) { if (model.enabled) {
UM.Theme.getColor("setting_control_text") UM.Theme.getColor("setting_control_text")
} else { } else {

View File

@ -26,9 +26,20 @@ SettingItem
textRole: "name" textRole: "name"
onActivated: onActivated:
{
if (model.getItem(index).enabled)
{ {
forceActiveFocus(); forceActiveFocus();
propertyProvider.setPropertyValue("value", model.getItem(index).index); propertyProvider.setPropertyValue("value", model.getItem(index).index);
} else
{
if (propertyProvider.properties.value == -1)
{
control.currentIndex = model.rowCount() - 1; // we know the last item is "Not overriden"
} else {
control.currentIndex = propertyProvider.properties.value; // revert to the old value
}
}
} }
onActiveFocusChanged: onActiveFocusChanged:
@ -192,7 +203,14 @@ SettingItem
{ {
text: model.name text: model.name
renderType: Text.NativeRendering renderType: Text.NativeRendering
color: UM.Theme.getColor("setting_control_text") color:
{
if (model.enabled) {
UM.Theme.getColor("setting_control_text")
} else {
UM.Theme.getColor("action_button_disabled_text");
}
}
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter

View File

@ -13,12 +13,18 @@ SettingItem
property string textBeforeEdit property string textBeforeEdit
property bool textHasChanged property bool textHasChanged
property bool focusGainedByClick: false
onFocusReceived: onFocusReceived:
{ {
textHasChanged = false; textHasChanged = false;
textBeforeEdit = focusItem.text; textBeforeEdit = focusItem.text;
if(!focusGainedByClick)
{
// select all text when tabbing through fields (but not when selecting a field with the mouse)
focusItem.selectAll(); focusItem.selectAll();
} }
}
contents: Rectangle contents: Rectangle
{ {
@ -93,14 +99,6 @@ SettingItem
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
} }
MouseArea
{
id: mouseArea
anchors.fill: parent;
//hoverEnabled: true;
cursorShape: Qt.IBeamCursor
}
TextInput TextInput
{ {
id: input id: input
@ -142,6 +140,7 @@ SettingItem
{ {
base.focusReceived(); base.focusReceived();
} }
base.focusGainedByClick = false;
} }
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
@ -178,6 +177,22 @@ SettingItem
} }
when: !input.activeFocus when: !input.activeFocus
} }
MouseArea
{
id: mouseArea
anchors.fill: parent;
cursorShape: Qt.IBeamCursor
onPressed: {
if(!input.activeFocus) {
base.focusGainedByClick = true;
input.forceActiveFocus();
}
mouse.accepted = false;
}
}
} }
} }
} }

View File

@ -15,6 +15,7 @@ Item
{ {
id: base; id: base;
property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
property Action configureSettings property Action configureSettings
property bool findingSettings property bool findingSettings
property bool showingAllSettings property bool showingAllSettings
@ -439,6 +440,7 @@ Item
key: model.key ? model.key : "" key: model.key ? model.key : ""
watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ] watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ]
storeIndex: 0 storeIndex: 0
removeUnusedValue: model.resolve == undefined
} }
Connections Connections
@ -562,9 +564,9 @@ Item
{ {
definitionsModel.hide(contextMenu.key); definitionsModel.hide(contextMenu.key);
// visible settings have changed, so we're no longer showing a preset // visible settings have changed, so we're no longer showing a preset
if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings) if (settingVisibilityPresetsModel.activePreset != "" && !showingAllSettings)
{ {
Cura.SettingVisibilityPresetsModel.setActivePreset("custom"); settingVisibilityPresetsModel.setActivePreset("custom");
} }
} }
} }
@ -594,16 +596,16 @@ Item
definitionsModel.show(contextMenu.key); definitionsModel.show(contextMenu.key);
} }
// visible settings have changed, so we're no longer showing a preset // visible settings have changed, so we're no longer showing a preset
if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings) if (settingVisibilityPresetsModel.activePreset != "" && !showingAllSettings)
{ {
Cura.SettingVisibilityPresetsModel.setActivePreset("custom"); settingVisibilityPresetsModel.setActivePreset("custom");
} }
} }
} }
MenuItem MenuItem
{ {
//: Settings context menu action //: Settings context menu action
text: catalog.i18nc("@action:menu", "Configure setting visiblity..."); text: catalog.i18nc("@action:menu", "Configure setting visibility...");
onTriggered: Cura.Actions.configureSettingVisibility.trigger(contextMenu); onTriggered: Cura.Actions.configureSettingVisibility.trigger(contextMenu);
} }

View File

@ -87,7 +87,8 @@ Rectangle
} }
} }
MachineSelection { MachineSelection
{
id: machineSelection id: machineSelection
width: base.width - configSelection.width - separator.width width: base.width - configSelection.width - separator.width
height: UM.Theme.getSize("sidebar_header").height height: UM.Theme.getSize("sidebar_header").height
@ -105,9 +106,10 @@ Rectangle
anchors.left: machineSelection.right anchors.left: machineSelection.right
} }
ConfigurationSelection { ConfigurationSelection
{
id: configSelection id: configSelection
visible: isNetworkPrinter && !sidebar.monitoringPrint && !sidebar.hideSettings visible: isNetworkPrinter && printerConnected
width: visible ? Math.round(base.width * 0.15) : 0 width: visible ? Math.round(base.width * 0.15) : 0
height: UM.Theme.getSize("sidebar_header").height height: UM.Theme.getSize("sidebar_header").height
anchors.top: base.top anchors.top: base.top

View File

@ -178,6 +178,7 @@ Column
text: catalog.i18nc("@action:inmenu", "Disable Extruder") text: catalog.i18nc("@action:inmenu", "Disable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false) onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
visible: extruder_enabled visible: extruder_enabled
enabled: Cura.MachineManager.numberExtrudersEnabled > 1
} }
} }
@ -185,22 +186,34 @@ Column
{ {
background: Item background: Item
{ {
Rectangle function buttonBackgroundColor(index)
{ {
anchors.fill: parent var extruder = Cura.MachineManager.getExtruder(index)
border.width: control.checked ? UM.Theme.getSize("default_lining").width * 2 : UM.Theme.getSize("default_lining").width if (extruder.isEnabled) {
border.color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_border") : return (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") :
control.hovered ? UM.Theme.getColor("action_button_hovered_border") :
UM.Theme.getColor("action_button_border")
color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") :
control.hovered ? UM.Theme.getColor("action_button_hovered") : control.hovered ? UM.Theme.getColor("action_button_hovered") :
UM.Theme.getColor("action_button") UM.Theme.getColor("action_button")
Behavior on color { ColorAnimation { duration: 50; } } } else {
return UM.Theme.getColor("action_button_disabled")
}
}
function buttonBorderColor(index)
{
var extruder = Cura.MachineManager.getExtruder(index)
if (extruder.isEnabled) {
return (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_border") :
control.hovered ? UM.Theme.getColor("action_button_hovered_border") :
UM.Theme.getColor("action_button_border")
} else {
return UM.Theme.getColor("action_button_disabled_border")
}
} }
function buttonColor(index) { function buttonColor(index) {
var extruder = Cura.MachineManager.getExtruder(index); var extruder = Cura.MachineManager.getExtruder(index);
if (extruder.isEnabled) { if (extruder.isEnabled)
{
return ( return (
control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") : control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") :
control.hovered ? UM.Theme.getColor("action_button_hovered_text") : control.hovered ? UM.Theme.getColor("action_button_hovered_text") :
@ -210,10 +223,20 @@ Column
} }
} }
Rectangle
{
anchors.fill: parent
border.width: control.checked ? UM.Theme.getSize("default_lining").width * 2 : UM.Theme.getSize("default_lining").width
border.color: buttonBorderColor(index)
color: buttonBackgroundColor(index)
Behavior on color { ColorAnimation { duration: 50; } }
}
Item Item
{ {
id: extruderButtonFace id: extruderButtonFace
anchors.centerIn: parent anchors.centerIn: parent
width: { width: {
var extruderTextWidth = extruderStaticText.visible ? extruderStaticText.width : 0; var extruderTextWidth = extruderStaticText.visible ? extruderStaticText.width : 0;
var iconWidth = extruderIconItem.width; var iconWidth = extruderIconItem.width;
@ -501,6 +524,8 @@ Column
source: UM.Theme.getIcon("warning") source: UM.Theme.getIcon("warning")
width: UM.Theme.getSize("section_icon").width width: UM.Theme.getSize("section_icon").width
height: UM.Theme.getSize("section_icon").height height: UM.Theme.getSize("section_icon").height
sourceSize.width: width
sourceSize.height: height
color: UM.Theme.getColor("material_compatibility_warning") color: UM.Theme.getColor("material_compatibility_warning")
visible: !Cura.MachineManager.isCurrentSetupSupported visible: !Cura.MachineManager.isCurrentSetupSupported
} }
@ -522,9 +547,7 @@ Column
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: {
// open the material URL with web browser // open the material URL with web browser
var version = UM.Application.version; var url = "https://ultimaker.com/incoming-links/cura/material-compatibilty"
var machineName = Cura.MachineManager.activeMachine.definition.id;
var url = "https://ultimaker.com/materialcompatibility/" + version + "/" + machineName + "?utm_source=cura&utm_medium=software&utm_campaign=resources";
Qt.openUrlExternally(url); Qt.openUrlExternally(url);
} }
onEntered: { onEntered: {

View File

@ -111,7 +111,6 @@ Item
// Set selected value // Set selected value
if (Cura.MachineManager.activeQualityType == qualityItem.quality_type) { if (Cura.MachineManager.activeQualityType == qualityItem.quality_type) {
// set to -1 when switching to user created profile so all ticks are clickable // set to -1 when switching to user created profile so all ticks are clickable
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) { if (Cura.SimpleModeSettingsManager.isProfileUserCreated) {
qualityModel.qualitySliderActiveIndex = -1 qualityModel.qualitySliderActiveIndex = -1
@ -474,18 +473,7 @@ Item
onClicked: onClicked:
{ {
// if the current profile is user-created, switch to a built-in quality // if the current profile is user-created, switch to a built-in quality
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) Cura.MachineManager.resetToUseDefaultQuality()
{
if (Cura.QualityProfilesDropDownMenuModel.rowCount() > 0)
{
var item = Cura.QualityProfilesDropDownMenuModel.getItem(0);
Cura.MachineManager.activeQualityGroup = item.quality_group;
}
}
if (Cura.SimpleModeSettingsManager.isProfileCustomized)
{
discardOrKeepProfileChangesDialog.show()
}
} }
onEntered: onEntered:
{ {
@ -594,7 +582,9 @@ Item
// Update value only if the Recomended mode is Active, // Update value only if the Recomended mode is Active,
// Otherwise if I change the value in the Custom mode the Recomended view will try to repeat // Otherwise if I change the value in the Custom mode the Recomended view will try to repeat
// same operation // same operation
if (UM.Preferences.getValue("cura/active_mode") == 0) { var active_mode = UM.Preferences.getValue("cura/active_mode")
if (active_mode == 0 || active_mode == "simple") {
Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue) Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue)
} }
} }

View File

@ -0,0 +1,83 @@
[shaders]
vertex =
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewProjectionMatrix;
attribute highp vec4 a_vertex;
varying highp vec3 v_vertex;
void main()
{
vec4 world_space_vert = u_modelMatrix * a_vertex;
gl_Position = u_viewProjectionMatrix * world_space_vert;
v_vertex = world_space_vert.xyz;
}
fragment =
uniform highp vec3 u_viewPosition;
varying highp vec3 v_vertex;
void main()
{
highp float distance_to_camera = distance(v_vertex, u_viewPosition) * 1000.; // distance in micron
vec3 encoded; // encode float into 3 8-bit channels; this gives a precision of a micron at a range of up to ~16 meter
encoded.b = floor(distance_to_camera / 65536.0);
encoded.g = floor((distance_to_camera - encoded.b * 65536.0) / 256.0);
encoded.r = floor(distance_to_camera - encoded.b * 65536.0 - encoded.g * 256.0);
gl_FragColor.rgb = encoded / 255.;
gl_FragColor.a = 1.0;
}
vertex41core =
#version 410
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewProjectionMatrix;
in highp vec4 a_vertex;
out highp vec3 v_vertex;
void main()
{
vec4 world_space_vert = u_modelMatrix * a_vertex;
gl_Position = u_viewProjectionMatrix * world_space_vert;
v_vertex = world_space_vert.xyz;
}
fragment41core =
#version 410
uniform highp vec3 u_viewPosition;
in highp vec3 v_vertex;
out vec4 frag_color;
void main()
{
highp float distance_to_camera = distance(v_vertex, u_viewPosition) * 1000.; // distance in micron
vec3 encoded; // encode float into 3 8-bit channels; this gives a precision of a micron at a range of up to ~16 meter
encoded.r = floor(distance_to_camera / 65536.0);
encoded.g = floor((distance_to_camera - encoded.r * 65536.0) / 256.0);
encoded.b = floor(distance_to_camera - encoded.r * 65536.0 - encoded.g * 256.0);
frag_color.rgb = encoded / 255.;
frag_color.a = 1.0;
}
[defaults]
[bindings]
u_modelMatrix = model_matrix
u_viewProjectionMatrix = view_projection_matrix
u_normalMatrix = normal_matrix
u_viewPosition = view_position
[attributes]
a_vertex = vertex

View File

@ -84,16 +84,16 @@
"tab_background": [39, 44, 48, 255], "tab_background": [39, 44, 48, 255],
"action_button": [39, 44, 48, 255], "action_button": [39, 44, 48, 255],
"action_button_text": [255, 255, 255, 101], "action_button_text": [255, 255, 255, 200],
"action_button_border": [255, 255, 255, 30], "action_button_border": [255, 255, 255, 30],
"action_button_hovered": [39, 44, 48, 255], "action_button_hovered": [39, 44, 48, 255],
"action_button_hovered_text": [255, 255, 255, 255], "action_button_hovered_text": [255, 255, 255, 255],
"action_button_hovered_border": [255, 255, 255, 30], "action_button_hovered_border": [255, 255, 255, 30],
"action_button_active": [39, 44, 48, 30], "action_button_active": [39, 44, 48, 30],
"action_button_active_text": [255, 255, 255, 255], "action_button_active_text": [255, 255, 255, 255],
"action_button_active_border": [255, 255, 255, 30], "action_button_active_border": [255, 255, 255, 100],
"action_button_disabled": [39, 44, 48, 255], "action_button_disabled": [39, 44, 48, 255],
"action_button_disabled_text": [255, 255, 255, 101], "action_button_disabled_text": [255, 255, 255, 80],
"action_button_disabled_border": [255, 255, 255, 30], "action_button_disabled_border": [255, 255, 255, 30],
"scrollbar_background": [39, 44, 48, 0], "scrollbar_background": [39, 44, 48, 0],

View File

@ -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],
"monitor_preheat_temperature_control": [4.5, 2.0],
"modal_window_minimum": [60.0, 45], "modal_window_minimum": [60.0, 45],
"license_window_minimum": [45, 45], "license_window_minimum": [45, 45],
"wizard_progress": [10.0, 0.0], "wizard_progress": [10.0, 0.0],