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:
node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild)
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._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")
if active_extruder_changed is not None:
active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild)
self._updateDisallowedAreasAndRebuild()
def setWidth(self, width):
if width is not None:

View File

@ -109,10 +109,6 @@ class CuraActions(QObject):
nodes_to_change = []
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 node.callDecoration("isGroup"):
for grouped_node in BreadthFirstIterator(node):
@ -125,6 +121,10 @@ class CuraActions(QObject):
nodes_to_change.append(grouped_node)
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)
if not nodes_to_change:

View File

@ -1,10 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
#Type hinting.
from typing import Dict
from PyQt5.QtCore import QObject
from PyQt5.QtCore import QObject, QTimer
from PyQt5.QtNetwork import QLocalServer
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.MachineManagementModel import MachineManagementModel
from cura.Machines.Models.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
from cura.Machines.MachineErrorChecker import MachineErrorChecker
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.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
from cura.ObjectsModel import ObjectsModel
@ -101,7 +99,6 @@ from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
from configparser import ConfigParser
import sys
import os.path
import numpy
@ -226,6 +223,7 @@ class CuraApplication(QtApplication):
self._object_manager = None
self._build_plate_model = None
self._multi_build_plate_model = None
self._setting_visibility_presets_model = None
self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None
self._cura_scene_controller = None
@ -286,10 +284,15 @@ class CuraApplication(QtApplication):
self._preferred_mimetype = ""
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().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.Firmware, "firmware")
@ -376,10 +379,6 @@ class CuraApplication(QtApplication):
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.engineCreatedSignal.connect(self._onEngineCreated)
@ -452,27 +451,18 @@ class CuraApplication(QtApplication):
@pyqtSlot(str)
def discardOrKeepProfileChangesClosed(self, option):
if option == "discard":
global_stack = self.getGlobalContainerStack()
for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
extruder.getTop().clear()
global_stack.getTop().clear()
if option == "discard":
for extruder in global_stack.extruders.values():
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
# before slicing. To ensure that slicer uses right settings values
elif option == "keep":
global_stack = self.getGlobalContainerStack()
for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
user_extruder_container = extruder.getTop()
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()
for extruder in global_stack.extruders.values():
extruder.userChanges.update()
global_stack.userChanges.update()
@pyqtSlot(int)
def messageBoxClosed(self, button):
@ -682,6 +672,11 @@ class CuraApplication(QtApplication):
self._print_information = PrintInformation.PrintInformation()
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
if self.getCommandLineOption("headless", False):
self.runWithoutGUI()
@ -764,6 +759,10 @@ class CuraApplication(QtApplication):
def hasGui(self):
return self._use_gui
@pyqtSlot(result = QObject)
def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
return self._setting_visibility_presets_model
def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
return self._machine_error_checker
@ -890,11 +889,11 @@ class CuraApplication(QtApplication):
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
qmlRegisterType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel")
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
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.
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
@ -970,6 +969,10 @@ class CuraApplication(QtApplication):
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()}
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
def updatePlatformActivity(self, node = None):
count = 0

View File

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

View File

@ -3,7 +3,7 @@
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.ContainerStack import ContainerStack
@ -11,6 +11,7 @@ from UM.Settings.ContainerStack import ContainerStack
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
#
# This the QML model for the quality management page.
#
@ -39,7 +40,7 @@ class MachineManagementModel(ListModel):
## Handler for container added/removed events from registry
def _onContainerChanged(self, container):
# 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()
## Private convenience function to reset & repopulate the model.
@ -47,7 +48,9 @@ class MachineManagementModel(ListModel):
items = []
# 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.sort(key = lambda i: i.getMetaDataEntry("connect_group_name"))
@ -61,7 +64,7 @@ class MachineManagementModel(ListModel):
"metadata": metadata,
"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}
self._local_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**local_filter_printers)
self._local_container_stacks.sort(key = lambda i: i.getName())

View File

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# 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.Scene.Selection import Selection
@ -21,8 +21,13 @@ class MultiBuildPlateModel(ListModel):
def __init__(self, parent = None):
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.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbersDelayed)
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
self._max_build_plate = 1 # default
@ -45,6 +50,9 @@ class MultiBuildPlateModel(ListModel):
def activeBuildPlate(self):
return self._active_build_plate
def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args):
self._update_timer.start()
def _updateSelectedObjectBuildPlateNumbers(self, *args):
result = set()
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.Qt.ListModel import ListModel
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -14,8 +19,13 @@ class ObjectsModel(ListModel):
def __init__(self):
super().__init__()
Application.getInstance().getController().getScene().sceneChanged.connect(self._update)
Preferences.getInstance().preferenceChanged.connect(self._update)
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed)
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
@ -23,6 +33,9 @@ class ObjectsModel(ListModel):
self._build_plate_number = nr
self._update()
def _updateDelayed(self, *args):
self._update_timer.start()
def _update(self, *args):
nodes = []
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_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
move_vector = move_vector.set(y = -bbox.bottom + z_offset)

View File

@ -18,6 +18,7 @@ class ExtruderOutputModel(QObject):
hotendTemperatureChanged = pyqtSignal()
activeMaterialChanged = pyqtSignal()
extruderConfigurationChanged = pyqtSignal()
isPreheatingChanged = pyqtSignal()
def __init__(self, printer: "PrinterOutputModel", position, parent=None):
super().__init__(parent)
@ -30,6 +31,21 @@ class ExtruderOutputModel(QObject):
self._extruder_configuration = ExtruderConfigurationModel()
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)
def activeMaterial(self) -> "MaterialOutputModel":
return self._active_material
@ -82,3 +98,25 @@ class ExtruderOutputModel(QObject):
if self._extruder_configuration.isValid():
return self._extruder_configuration
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.Logger import Logger
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.CuraApplication import CuraApplication
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
@ -254,6 +256,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._last_manager_create_time = time()
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:
if onFinished is not None:
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished

View File

@ -15,6 +15,7 @@ class PrinterOutputController:
self.can_pause = True
self.can_abort = True
self.can_pre_heat_bed = True
self.can_pre_heat_hotends = True
self.can_control_manually = True
self._output_device = output_device
@ -33,6 +34,12 @@ class PrinterOutputController:
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
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):
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 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
@pyqtProperty(bool, constant=True)
def canPause(self):

View File

@ -1,6 +1,8 @@
# Copyright (c) 2016 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.Math.Polygon import Polygon
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
@ -22,6 +24,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
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
# For raft thickness, DRY
self._build_volume = Application.getInstance().getBuildVolume()
@ -33,6 +39,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
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):
previous_node = self._node
# Disconnect from previous node signals
@ -99,6 +111,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
return self._compute2DConvexHull()
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):
controller = Application.getInstance().getController()
root = controller.getScene().getRoot()
@ -279,7 +297,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
def _onChanged(self, *args):
self._raft_thickness = self._build_volume.getRaftThickness()
self.recomputeConvexHull()
if not args or args[0] == self._node:
self.recomputeConvexHullDelayed()
def _onGlobalStackChanged(self):
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.
# \return \type{list} a list of names of materials with the same GUID
@pyqtSlot("QVariant", result = "QStringList")
def getLinkedMaterials(self, material_node):
@pyqtSlot("QVariant", bool, result = "QStringList")
def getLinkedMaterials(self, material_node, exclude_self = False):
guid = material_node.metadata["GUID"]
self_root_material_id = material_node.metadata["base_file"]
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
linked_material_names = []
if 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"])
return linked_material_names

View File

@ -173,12 +173,13 @@ class CuraContainerRegistry(ContainerRegistry):
plugin_registry = PluginRegistry.getInstance()
extension = file_name.split(".")[-1]
global_container_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack:
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return
machine_extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()))
machine_extruders.sort(key = lambda k: k.getMetaDataEntry("position"))
machine_extruders = []
for position in sorted(global_stack.extruders):
machine_extruders.append(global_stack.extruders[position])
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
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
global_profile = None
extruder_profiles = []
if len(profile_or_list) == 1:
global_profile = profile_or_list[0]
else:
for profile in profile_or_list:
if not profile.getMetaDataEntry("position"):
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:
Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name)
return { "status": "error",
@ -227,7 +233,7 @@ class CuraContainerRegistry(ContainerRegistry):
# Get the expected machine definition.
# i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode...
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):
if profile_definition != expected_machine_definition:
@ -251,8 +257,8 @@ class CuraContainerRegistry(ContainerRegistry):
if len(profile_or_list) == 1:
global_profile = profile_or_list[0]
extruder_profiles = []
for idx, extruder in enumerate(global_container_stack.extruders.values()):
profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1))
for idx, extruder in enumerate(global_stack.extruders.values()):
profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
profile = InstanceContainer(profile_id)
profile.setName(quality_name)
profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
@ -264,12 +270,12 @@ class CuraContainerRegistry(ContainerRegistry):
if idx == 0:
# move all per-extruder settings to the first extruder's quality_changes
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")
if settable_per_extruder:
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.setProperty("value", setting_value)
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):
if profile_index == 0:
# 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:
# This is assumed to be an extruder profile

View File

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

View File

@ -10,7 +10,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Signal import Signal
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
import UM.FlameProfiler
from UM.FlameProfiler import pyqtSlot
from UM import Util
@ -24,7 +23,6 @@ from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.Machines.VariantManager import VariantType
from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
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.
activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container 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
@ -467,16 +466,16 @@ class MachineManager(QObject):
return self._global_container_stack.getId()
return ""
@pyqtProperty(str, notify = globalContainerChanged)
@pyqtProperty(str, notify = outputDevicesChanged)
def activeMachineNetworkKey(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("um_network_key")
return self._global_container_stack.getMetaDataEntry("um_network_key", "")
return ""
@pyqtProperty(str, notify = globalContainerChanged)
@pyqtProperty(str, notify = outputDevicesChanged)
def activeMachineNetworkGroupName(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("connect_group_name")
return self._global_container_stack.getMetaDataEntry("connect_group_name", "")
return ""
@pyqtProperty(QObject, notify = globalContainerChanged)
@ -662,12 +661,22 @@ class MachineManager(QObject):
if other_machine_stacks:
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)
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers:
ContainerRegistry.getInstance().removeContainer(container["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)
def hasMaterials(self) -> bool:
if self._global_container_stack:
@ -729,22 +738,6 @@ class MachineManager(QObject):
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)
# \param machine_id string machine id to get the definition ID of
# \returns DefinitionID (string) if found, None otherwise
@ -872,7 +865,13 @@ class MachineManager(QObject):
for position, extruder in self._global_container_stack.extruders.items():
if extruder.isEnabled:
extruder_count += 1
if self.numberExtrudersEnabled != 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)
def defaultExtruderPosition(self):
@ -1110,7 +1109,7 @@ class MachineManager(QObject):
from cura.Settings.CuraContainerStack import _ContainerIndexes
context = PropertyEvaluationContext(extruder)
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(
self._global_container_stack.definition.getId(),
current_variant_name,
@ -1193,6 +1192,24 @@ class MachineManager(QObject):
if machine.getMetaDataEntry(key) == 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")
def setGlobalVariant(self, container_node):
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:
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)
def activeQualityChangesGroup(self):
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._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
self._updateIsProfileCustomized()

View File

@ -280,6 +280,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# 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)]
quality_name = ""
custom_quality_name = ""
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
num_user_settings = 0
quality_changes_conflict = False
@ -292,7 +293,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
container_id = self._stripFileToId(instance_container_file_name)
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)
parser = ConfigParser(interpolation = None)
parser.read_string(serialized)
container_info = ContainerInfo(instance_container_file_name, serialized, parser)
@ -309,7 +317,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
position = parser["metadata"]["position"]
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()
num_settings_overriden_by_quality_changes += len(values)
# Check if quality changes already exists.
@ -473,6 +481,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
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.name = machine_name
self._machine_info.definition_id = machine_definition_id

View File

@ -1,17 +1,17 @@
# Copyright (c) 2017 Ultimaker B.V.
# 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.Logger import Logger
from UM.Application import Application
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.
#
@ -45,6 +45,8 @@ class GCodeWriter(MeshWriter):
def __init__(self):
super().__init__()
self._application = Application.getInstance()
## Writes the g-code for the entire scene to a stream.
#
# Note that even though the function accepts a collection of nodes, the
@ -94,7 +96,6 @@ class GCodeWriter(MeshWriter):
return flat_container
## Serialises a container stack to prepare it for writing at the end of the
# g-code.
#
@ -104,15 +105,20 @@ class GCodeWriter(MeshWriter):
# \param settings A container stack to serialise.
# \return A serialised string of the settings.
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_length = len(prefix)
quality_type = stack.quality.getMetaDataEntry("quality_type")
container_with_profile = stack.qualityChanges
if container_with_profile.getId() == "empty_quality_changes":
Logger.log("e", "No valid quality profile found, not writing settings to g-code!")
return ""
# If the global quality changes is empty, create a new one
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 flat_global_container.getMetaDataEntry("type", None) is None:
flat_global_container.addMetaDataEntry("type", "quality_changes")
@ -121,41 +127,47 @@ class GCodeWriter(MeshWriter):
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
# Change the default defintion
default_machine_definition = "fdmprinter"
if parseBool(stack.getMetaDataEntry("has_machine_quality", "False")):
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)
# Get the machine definition ID for quality profiles
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition)
flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
serialized = flat_global_container.serialize()
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
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())
continue
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
# Same story, if quality changes is empty, create a new one
quality_name = container_registry.uniqueName(stack.quality.getName())
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 flat_extruder_quality.getMetaDataEntry("type", None) is None:
flat_extruder_quality.addMetaDataEntry("type", "quality_changes")
# Ensure that extruder is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("extruder", None) is None:
flat_extruder_quality.addMetaDataEntry("extruder", extruder.getBottom().getId())
if flat_extruder_quality.getMetaDataEntry("position", None) is None:
flat_extruder_quality.addMetaDataEntry("position", extruder.getMetaDataEntry("position"))
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal"))
# Change the default defintion
flat_extruder_quality.setMetaDataEntry("definition", default_machine_definition)
# Change the default definition
flat_extruder_quality.setMetaDataEntry("definition", machine_definition_id_for_quality)
extruder_serialized = flat_extruder_quality.serialize()
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)
# Escape characters that have a special meaning in g-code comments.
@ -169,5 +181,5 @@ class GCodeWriter(MeshWriter):
# Lines have 80 characters, so the payload of each line is 80 - prefix.
for pos in range(0, len(escaped_string), 80 - prefix_length):
result += prefix + escaped_string[pos : pos + 80 - prefix_length] + "\n"
result += prefix + escaped_string[pos: pos + 80 - prefix_length] + "\n"
return result

View File

@ -163,7 +163,16 @@ Item {
id: addedSettingsModel;
containerId: Cura.MachineManager.activeDefinitionId
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" ];
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")
width: screenScaleFactor * 360
property string labelFilter: ""
property var additional_excluded_settings
onVisibilityChanged:
@ -386,11 +394,33 @@ Item {
// Set skip setting, it will prevent from resetting selected mesh_type
contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type)
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 {
id: filter
id: filterInput
anchors {
top: parent.top
@ -401,17 +431,7 @@ Item {
placeholderText: catalog.i18nc("@label:textbox", "Filter...");
onTextChanged:
{
if(text != "")
{
listview.model.filter = {"settable_per_mesh": true, "i18n_label": "*" + text}
}
else
{
listview.model.filter = {"settable_per_mesh": true}
}
}
onTextChanged: settingPickDialog.updateFilter()
}
CheckBox
@ -437,7 +457,7 @@ Item {
anchors
{
top: filter.bottom;
top: filterInput.bottom;
left: parent.left;
right: parent.right;
bottom: parent.bottom;
@ -449,10 +469,6 @@ Item {
{
id: definitionsModel;
containerId: Cura.MachineManager.activeDefinitionId
filter:
{
"settable_per_mesh": true
}
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
expanded: [ "*" ]
exclude:
@ -484,6 +500,7 @@ Item {
}
}
}
Component.onCompleted: settingPickDialog.updateFilter()
}
}
@ -507,6 +524,16 @@ Item {
storeIndex: 0
}
UM.SettingPropertyProvider
{
id: printSequencePropertyProvider
containerStackId: Cura.MachineManager.activeMachineId
key: "print_sequence"
watchedProperties: [ "value" ]
storeIndex: 0
}
SystemPalette { id: palette; }
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.
import os.path
@ -7,7 +7,7 @@ from UM.Application import Application
from UM.Logger import Logger
from UM.Message import Message
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.OutputDevice.OutputDevice import OutputDevice
from UM.OutputDevice import OutputDeviceError
@ -39,7 +39,7 @@ class RemovableDriveOutputDevice(OutputDevice):
# MIME types available to the currently active machine?
#
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:
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(";")]
# 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:
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.
if file_handler is not None:
writer = file_handler.getWriterByMimeType(file_formats[0]["mime_type"])
writer = file_handler.getWriterByMimeType(preferred_format["mime_type"])
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:
file_name = self._automaticFileName(nodes)
@ -80,8 +82,11 @@ class RemovableDriveOutputDevice(OutputDevice):
try:
Logger.log("d", "Writing to %s", file_name)
# 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")
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.progress.connect(self._onProgress)
job.finished.connect(self._onFinished)

View File

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

View File

@ -1,39 +1,103 @@
# Copyright (c) 2018 Ultimaker B.V.
# 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.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):
def __init__(self):
super().__init__()
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):
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:
self._createEraserMesh()
if self._skip_press:
# 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:
self.getController().setActiveTool(None)
if self._selection_pass is 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.setName("Eraser")
@ -41,9 +105,7 @@ class SupportEraser(Tool):
mesh = MeshBuilder()
mesh.addCube(10,10,10)
node.setMeshData(mesh.build())
# Place the cube in the platform. Do it manually so it works if the "automatic drop models" is OFF
move_vector = Vector(0, 5, 0)
node.setPosition(move_vector)
node.setPosition(position)
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
@ -51,21 +113,60 @@ class SupportEraser(Tool):
node.addDecorator(BuildPlateDecorator(active_build_plate))
node.addDecorator(SliceableObjectDecorator())
stack = node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
if not stack:
node.addDecorator(SettingOverrideDecorator())
stack = node.callDecoration("getStack")
stack = node.callDecoration("getStack") # created by SettingOverrideDecorator
settings = stack.getTop()
if not (settings.getInstance("anti_overhang_mesh") and settings.getProperty("anti_overhang_mesh", "value")):
definition = stack.getSettingDefinition("anti_overhang_mesh")
new_instance = SettingInstance(definition, settings)
new_instance.setProperty("value", True)
new_instance.resetState() # Ensure that the state is not seen as a user state.
settings.addInstance(new_instance)
scene = self._controller.getScene()
op = AddSceneNodeOperation(node, scene.getRoot())
root = self._controller.getScene().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()
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):
super().__init__(output_device)
self.can_pre_heat_bed = False
self.can_pre_heat_hotends = False
self.can_control_manually = False
def setJobState(self, job: "PrintJobOutputModel", state: str):

View File

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

View File

@ -5,6 +5,7 @@ import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
import QtQuick.Dialogs 1.2
Cura.MachineAction
{
@ -33,14 +34,32 @@ Cura.MachineAction
{
var printerKey = base.selectedDevice.key
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.setGroupName(printerName) // TODO To change when the groups have a name
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
{

View File

@ -82,6 +82,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._zero_conf_browser.cancel()
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_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.',
[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.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.GenericOutputController import GenericOutputController
from .AutoDetectBaudJob import AutoDetectBaudJob
from .USBPrinterOutputController import USBPrinterOutputController
from .avr_isp import stk500v2, intelHex
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
@ -240,7 +240,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
container_stack = Application.getInstance().getGlobalContainerStack()
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
# 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.setConnectionState(ConnectionState.connected)
self._update_thread.start()
@ -372,7 +372,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
elapsed_time = int(time() - self._print_start_time)
print_job = self._printers[0].activePrintJob
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")
self._printers[0].updateActivePrintJob(print_job)

View File

@ -153,6 +153,10 @@ class VersionUpgrade26to27(VersionUpgrade):
if new_id is not None:
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"):
if not parser.has_section(each_section):
parser.add_section(each_section)

View File

@ -208,14 +208,9 @@ class XmlMaterialProfile(InstanceContainer):
machine_variant_map = {}
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.
material_group = material_manager.getMaterialGroup(root_material_id)
all_containers = []
for node in [material_group.root_material_node] + material_group.derived_material_node_list:
all_containers.append(node.getContainer())
all_containers = registry.findInstanceContainers(base_file = root_material_id)
for container in all_containers:
definition_id = container.getMetaDataEntry("definition")
@ -242,7 +237,7 @@ class XmlMaterialProfile(InstanceContainer):
for definition_id, container in machine_container_map.items():
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
for product_name, product_id_list in product_id_map.items():

View File

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

View File

@ -632,6 +632,73 @@
"settable_per_extruder": 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":
{
"label": "Minimum Feedrate",
@ -642,6 +709,16 @@
"settable_per_mesh": false,
"settable_per_extruder": 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
},
"machine_nozzle_size": {
"default_value": 0.4,
"minimum_value": "0.001"
"default_value": 0.4
},
"machine_head_with_fans_polygon": {
"default_value": [
@ -36,6 +35,21 @@
[ 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": {
"default_value": 55
},

View File

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

View File

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

View File

@ -217,6 +217,7 @@ UM.MainWindow
text: catalog.i18nc("@action:inmenu", "Disable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
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 "Menus"
ToolButton {
ToolButton
{
id: base
property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property var printerStatus: Cura.MachineManager.printerOutputDevices.length != 0 ? "connected" : "disconnected"
text: isNetworkPrinter ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName
tooltip: Cura.MachineManager.activeMachineName
style: ButtonStyle {
background: Rectangle {
color: {
style: ButtonStyle
{
background: Rectangle
{
color:
{
if (control.pressed) {
return UM.Theme.getColor("sidebar_header_active");
}
@ -33,7 +38,8 @@ ToolButton {
}
Behavior on color { ColorAnimation { duration: 50; } }
UM.RecolorImage {
UM.RecolorImage
{
id: downArrow
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
@ -46,24 +52,27 @@ ToolButton {
source: UM.Theme.getIcon("arrow_bottom")
}
PrinterStatusIcon {
PrinterStatusIcon
{
id: printerStatusIcon
visible: isNetworkPrinter
visible: printerConnected || isNetworkPrinter
status: printerStatus
anchors {
anchors
{
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: UM.Theme.getSize("sidebar_margin").width
}
}
Label {
Label
{
id: sidebarComboBoxLabel
color: UM.Theme.getColor("sidebar_header_text_active")
text: control.text;
elide: Text.ElideRight;
anchors.left: isNetworkPrinter ? printerStatusIcon.right : parent.left;
anchors.leftMargin: isNetworkPrinter ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width
anchors.left: printerStatusIcon.visible ? printerStatusIcon.right : parent.left;
anchors.leftMargin: printerStatusIcon.visible ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width
anchors.right: downArrow.left;
anchors.rightMargin: control.rightMargin;
anchors.verticalCenter: parent.verticalCenter;
@ -74,14 +83,4 @@ ToolButton {
}
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
onActivateConfiguration:
{
switchPopupState()
Cura.MachineManager.applyRemoteConfiguration(configuration)
}
}

View File

@ -13,54 +13,53 @@ Item
id: configurationSelector
property var connectedDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
property var panelWidth: control.width
property var panelVisible: false
SyncButton {
onClicked: configurationSelector.state == "open" ? configurationSelector.state = "closed" : configurationSelector.state = "open"
function switchPopupState()
{
popup.visible ? popup.close() : popup.open()
}
SyncButton
{
id: syncButton
onClicked: switchPopupState()
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
clip: true
closePolicy: Popup.CloseOnPressOutsideParent
y: configurationSelector.height - UM.Theme.getSize("default_lining").height
x: configurationSelector.width - width
width: panelWidth
visible: panelVisible && connectedDevice != null
visible: false
padding: UM.Theme.getSize("default_lining").width
contentItem: ConfigurationListView {
transformOrigin: Popup.Top
contentItem: ConfigurationListView
{
id: configList
width: panelWidth - 2 * popup.padding
outputDevice: connectedDevice
}
background: Rectangle {
background: Rectangle
{
color: UM.Theme.getColor("setting_control")
border.color: UM.Theme.getColor("setting_control_border")
}
}
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 {
exit: Transition
{
// 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")
source: UM.Theme.getIcon("arrow_bottom")
}
UM.RecolorImage {
UM.RecolorImage
{
id: sidebarComboBoxLabel
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
@ -86,22 +87,15 @@ Button
label: Label {}
}
onClicked:
Connections
{
panelVisible = !panelVisible
}
Connections {
target: outputDevice
onUniqueConfigurationsChanged: {
updateOnSync()
}
onUniqueConfigurationsChanged: updateOnSync()
}
Connections {
Connections
{
target: Cura.MachineManager
onCurrentConfigurationChanged: {
updateOnSync()
}
onCurrentConfigurationChanged: updateOnSync()
}
}

View File

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

View File

@ -1,8 +1,8 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick 2.7
import QtQuick.Controls 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
@ -12,44 +12,26 @@ Menu
id: menu
title: catalog.i18nc("@action:inmenu", "Visible Settings")
property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
property bool showingSearchResults
property bool showingAllSettings
signal showAllSettings()
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
{
model: Cura.SettingVisibilityPresetsModel
model: settingVisibilityPresetsModel
MenuItem
{
text: model.name
checkable: true
checked: model.id == Cura.SettingVisibilityPresetsModel.activePreset
checked: model.id == settingVisibilityPresetsModel.activePreset
exclusiveGroup: group
onTriggered:
{
Cura.SettingVisibilityPresetsModel.setActivePreset(model.id);
UM.Preferences.setValue("general/visible_settings", model.settings.join(";"));
settingVisibilityPresetsModel.setActivePreset(model.id);
showSettingVisibilityProfile();
}
}

View File

@ -36,8 +36,8 @@ TabView
if (!base.containerId || !base.editingEnabled) {
return ""
}
var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode);
if (linkedMaterials.length <= 1) {
var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode, true);
if (linkedMaterials.length == 0) {
return ""
}
return linkedMaterials.join(", ");
@ -99,6 +99,7 @@ TabView
property var new_diameter_value: null;
property var old_diameter_value: null;
property var old_approximate_diameter_value: null;
property bool keyPressed: false
onYes:
{
@ -112,6 +113,16 @@ TabView
properties.diameter = old_diameter_value;
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") }
@ -222,7 +233,7 @@ TabView
var old_diameter = Cura.ContainerManager.getContainerProperty(base.containerId, "material_diameter", "value").toString();
var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter");
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.new_diameter_value = value;

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,8 @@ UM.PreferencesPage
{
title: catalog.i18nc("@title:tab", "Setting Visibility");
property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
property int scrollToIndex: 0
signal scrollToSection( string key )
@ -27,8 +29,7 @@ UM.PreferencesPage
// After calling this function update Setting visibility preset combobox.
// Reset should set default setting preset ("Basic")
visibilityPreset.setDefaultPreset()
visibilityPreset.currentIndex = 1
}
resetEnabled: true;
@ -37,8 +38,6 @@ UM.PreferencesPage
id: base;
anchors.fill: parent;
property bool inhibitSwitchToCustom: false
CheckBox
{
id: toggleVisibleSettings
@ -112,11 +111,6 @@ UM.PreferencesPage
ComboBox
{
function setDefaultPreset()
{
visibilityPreset.currentIndex = 0
}
id: visibilityPreset
width: 150 * screenScaleFactor
anchors
@ -125,51 +119,25 @@ UM.PreferencesPage
right: parent.right
}
model: ListModel
{
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"]});
}
}
}
model: settingVisibilityPresetsModel
textRole: "name"
currentIndex:
{
// Load previously selected preset.
var index = Cura.SettingVisibilityPresetsModel.find("id", Cura.SettingVisibilityPresetsModel.activePreset);
if(index == -1)
var index = settingVisibilityPresetsModel.find("id", settingVisibilityPresetsModel.activePreset)
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:
{
base.inhibitSwitchToCustom = true;
var preset_id = visibilityPresetsModel.get(index).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;
var preset_id = settingVisibilityPresetsModel.getItem(index).id;
settingVisibilityPresetsModel.setActivePreset(preset_id);
}
}
@ -199,16 +167,7 @@ UM.PreferencesPage
exclude: ["machine_settings", "command_line_settings"]
showAncestors: true
expanded: ["*"]
visibilityHandler: UM.SettingPreferenceVisibilityHandler
{
onVisibilityChanged:
{
if(Cura.SettingVisibilityPresetsModel.activePreset != "" && !base.inhibitSwitchToCustom)
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
}
}
}
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
}
delegate: Loader

View File

@ -12,9 +12,20 @@ Item
property alias color: background.color
property var extruderModel
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
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
{
id: background
@ -34,12 +45,11 @@ Item
{
id: extruderTargetTemperature
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")
color: UM.Theme.getColor("text_inactive")
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.bottom: extruderTemperature.bottom
anchors.bottom: extruderCurrentTemperature.bottom
MouseArea //For tooltip.
{
@ -52,7 +62,7 @@ Item
{
base.showTooltip(
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.")
);
}
@ -65,9 +75,8 @@ Item
}
Label //Temperature indication.
{
id: extruderTemperature
id: extruderCurrentTemperature
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")
font: UM.Theme.getFont("large")
anchors.right: extruderTargetTemperature.left
@ -76,7 +85,7 @@ Item
MouseArea //For tooltip.
{
id: extruderTemperatureTooltipArea
id: extruderCurrentTemperatureTooltipArea
hoverEnabled: true
anchors.fill: parent
onHoveredChanged:
@ -85,8 +94,8 @@ Item
{
base.showTooltip(
base,
{x: 0, y: parent.mapToItem(base, 0, -parent.height / 4).y},
catalog.i18nc("@tooltip", "The current temperature of this extruder.")
{x: 0, y: parent.mapToItem(base, 0, Math.floor(-parent.height / 4)).y},
catalog.i18nc("@tooltip", "The current temperature of this hotend.")
);
}
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.
{
id: materialColor

View File

@ -114,21 +114,24 @@ Item
{
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 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.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
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("setting_control").width
height: UM.Theme.getSize("setting_control").height
visible: printerModel != null ? printerModel.canPreHeatBed: true
width: UM.Theme.getSize("monitor_preheat_temperature_control").width
height: UM.Theme.getSize("monitor_preheat_temperature_control").height
visible: printerModel != null ? enabled && printerModel.canPreHeatBed && !printerModel.isPreheating : true
Rectangle //Highlight of input field.
{
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
{
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: 10
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: parent.right
anchors.right: unit.left
anchors.verticalCenter: parent.verticalCenter
renderType: Text.NativeRendering

View File

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

View File

@ -26,9 +26,20 @@ SettingItem
textRole: "name"
onActivated:
{
if (model.getItem(index).enabled)
{
forceActiveFocus();
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:
@ -192,7 +203,14 @@ SettingItem
{
text: model.name
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")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter

View File

@ -13,12 +13,18 @@ SettingItem
property string textBeforeEdit
property bool textHasChanged
property bool focusGainedByClick: false
onFocusReceived:
{
textHasChanged = false;
textBeforeEdit = focusItem.text;
if(!focusGainedByClick)
{
// select all text when tabbing through fields (but not when selecting a field with the mouse)
focusItem.selectAll();
}
}
contents: Rectangle
{
@ -93,14 +99,6 @@ SettingItem
font: UM.Theme.getFont("default")
}
MouseArea
{
id: mouseArea
anchors.fill: parent;
//hoverEnabled: true;
cursorShape: Qt.IBeamCursor
}
TextInput
{
id: input
@ -142,6 +140,7 @@ SettingItem
{
base.focusReceived();
}
base.focusGainedByClick = false;
}
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
@ -178,6 +177,22 @@ SettingItem
}
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;
property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
property Action configureSettings
property bool findingSettings
property bool showingAllSettings
@ -439,6 +440,7 @@ Item
key: model.key ? model.key : ""
watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ]
storeIndex: 0
removeUnusedValue: model.resolve == undefined
}
Connections
@ -562,9 +564,9 @@ Item
{
definitionsModel.hide(contextMenu.key);
// 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);
}
// 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
{
//: 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);
}

View File

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

View File

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

View File

@ -111,7 +111,6 @@ Item
// Set selected value
if (Cura.MachineManager.activeQualityType == qualityItem.quality_type) {
// set to -1 when switching to user created profile so all ticks are clickable
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) {
qualityModel.qualitySliderActiveIndex = -1
@ -474,18 +473,7 @@ Item
onClicked:
{
// if the current profile is user-created, switch to a built-in quality
if (Cura.SimpleModeSettingsManager.isProfileUserCreated)
{
if (Cura.QualityProfilesDropDownMenuModel.rowCount() > 0)
{
var item = Cura.QualityProfilesDropDownMenuModel.getItem(0);
Cura.MachineManager.activeQualityGroup = item.quality_group;
}
}
if (Cura.SimpleModeSettingsManager.isProfileCustomized)
{
discardOrKeepProfileChangesDialog.show()
}
Cura.MachineManager.resetToUseDefaultQuality()
}
onEntered:
{
@ -594,7 +582,9 @@ Item
// 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
// 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)
}
}

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],
"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_hovered": [39, 44, 48, 255],
"action_button_hovered_text": [255, 255, 255, 255],
"action_button_hovered_border": [255, 255, 255, 30],
"action_button_active": [39, 44, 48, 30],
"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_text": [255, 255, 255, 101],
"action_button_disabled_text": [255, 255, 255, 80],
"action_button_disabled_border": [255, 255, 255, 30],
"scrollbar_background": [39, 44, 48, 0],

View File

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