diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 707a5df2a7..cff60e3494 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the AGPLv3 or higher. from cura.Settings.ExtruderManager import ExtruderManager +from UM.Settings.ContainerRegistry import ContainerRegistry from UM.i18n import i18nCatalog from UM.Scene.Platform import Platform from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator @@ -25,9 +26,6 @@ import numpy import copy import math -import UM.Settings.ContainerRegistry - - # Setting for clearance around the prime PRIME_CLEARANCE = 6.5 @@ -796,7 +794,7 @@ class BuildVolume(SceneNode): stack = self._global_container_stack else: extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)] - stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] + stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] value = stack.getProperty(setting_key, property) setting_type = stack.getProperty(setting_key, "type") diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index c4b2fe0337..65c799619a 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -1,13 +1,13 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from UM.Scene.SceneNodeDecorator import SceneNodeDecorator from UM.Application import Application -from cura.Settings.ExtruderManager import ExtruderManager from UM.Math.Polygon import Polygon -from . import ConvexHullNode +from UM.Scene.SceneNodeDecorator import SceneNodeDecorator +from UM.Settings.ContainerRegistry import ContainerRegistry -import UM.Settings.ContainerRegistry +from cura.Settings.ExtruderManager import ExtruderManager +from . import ConvexHullNode import numpy @@ -308,11 +308,11 @@ class ConvexHullDecorator(SceneNodeDecorator): extruder_stack_id = self._node.callDecoration("getActiveExtruder") if not extruder_stack_id: #Decoration doesn't exist. extruder_stack_id = ExtruderManager.getInstance().extruderIds["0"] - extruder_stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] + extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] return extruder_stack.getProperty(setting_key, property) else: #Limit_to_extruder is set. Use that one. extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)] - stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] + stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] return stack.getProperty(setting_key, property) ## Returns true if node is a descendant or the same as the root node. diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index ba8499d4f2..b658f88824 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -12,10 +12,14 @@ from UM.Logger import Logger from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") -try: - from cura.CuraVersion import CuraDebugMode -except ImportError: - CuraDebugMode = False # [CodeStyle: Reflecting imported value] +MYPY = False +if MYPY: + CuraDebugMode = False +else: + try: + from cura.CuraVersion import CuraDebugMode + except ImportError: + CuraDebugMode = False # [CodeStyle: Reflecting imported value] # List of exceptions that should be considered "fatal" and abort the program. # These are primarily some exception types that we simply cannot really recover from diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 94a3994ff4..3275055951 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -28,7 +28,6 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.SetTransformOperation import SetTransformOperation -from UM.Operations.TranslateOperation import TranslateOperation from cura.SetParentOperation import SetParentOperation from cura.SliceableObjectDecorator import SliceableObjectDecorator from cura.BlockSlicingDecorator import BlockSlicingDecorator @@ -36,6 +35,11 @@ from cura.BlockSlicingDecorator import BlockSlicingDecorator from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.SettingFunction import SettingFunction +from cura.Settings.MachineNameValidator import MachineNameValidator +from cura.Settings.ProfilesModel import ProfilesModel +from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel +from cura.Settings.SettingInheritanceManager import SettingInheritanceManager +from cura.Settings.UserProfilesModel import UserProfilesModel from . import PlatformPhysics from . import BuildVolume @@ -47,7 +51,14 @@ from . import CuraSplashScreen from . import CameraImageProvider from . import MachineActionManager -import cura.Settings +from cura.Settings.MachineManager import MachineManager +from cura.Settings.ExtruderManager import ExtruderManager +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry +from cura.Settings.ExtrudersModel import ExtrudersModel +from cura.Settings.ContainerSettingsModel import ContainerSettingsModel +from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler +from cura.Settings.QualitySettingsModel import QualitySettingsModel +from cura.Settings.ContainerManager import ContainerManager from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS from UM.FlameProfiler import pyqtSlot @@ -66,11 +77,13 @@ import json numpy.seterr(all="ignore") -try: - from cura.CuraVersion import CuraVersion, CuraBuildType -except ImportError: - CuraVersion = "master" # [CodeStyle: Reflecting imported value] - CuraBuildType = "" +MYPY = False +if not MYPY: + try: + from cura.CuraVersion import CuraVersion, CuraBuildType + except ImportError: + CuraVersion = "master" # [CodeStyle: Reflecting imported value] + CuraBuildType = "" class CuraApplication(QtApplication): class ResourceTypes: @@ -86,6 +99,7 @@ class CuraApplication(QtApplication): Q_ENUMS(ResourceTypes) def __init__(self): + Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources")) if not hasattr(sys, "frozen"): Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")) @@ -110,9 +124,9 @@ class CuraApplication(QtApplication): SettingDefinition.addSettingType("extruder", None, str, Validator) - SettingFunction.registerOperator("extruderValues", cura.Settings.ExtruderManager.getExtruderValues) - SettingFunction.registerOperator("extruderValue", cura.Settings.ExtruderManager.getExtruderValue) - SettingFunction.registerOperator("resolveOrValue", cura.Settings.ExtruderManager.getResolveOrValue) + SettingFunction.registerOperator("extruderValues", ExtruderManager.getExtruderValues) + SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue) + SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue) ## Add the 4 types of profiles to storage. Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality") @@ -131,13 +145,14 @@ class CuraApplication(QtApplication): ## Initialise the version upgrade manager with Cura's storage paths. import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies. + UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions( { - ("quality", UM.Settings.InstanceContainer.Version): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"), - ("machine_stack", UM.Settings.ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"), - ("extruder_train", UM.Settings.ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"), - ("preferences", UM.Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"), - ("user", UM.Settings.InstanceContainer.Version): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer") + ("quality", UM.Settings.InstanceContainer.InstanceContainer.Version): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"), + ("machine_stack", UM.Settings.ContainerStack.ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"), + ("extruder_train", UM.Settings.ContainerStack.ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"), + ("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"), + ("user", UM.Settings.InstanceContainer.InstanceContainer.Version): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer") } ) @@ -226,6 +241,7 @@ class CuraApplication(QtApplication): Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True) Preferences.getInstance().addPreference("cura/dialog_on_project_save", True) Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False) + Preferences.getInstance().addPreference("view/force_layer_view_compatibility_mode", False) Preferences.getInstance().addPreference("cura/currency", "€") Preferences.getInstance().addPreference("cura/material_settings", "{}") @@ -531,6 +547,7 @@ class CuraApplication(QtApplication): controller = self.getController() controller.setActiveView("SolidView") + controller.setCameraTool("CameraTool") controller.setSelectionTool("SelectionTool") @@ -563,9 +580,11 @@ class CuraApplication(QtApplication): self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface...")) # Initialise extruder so as to listen to global container stack changes before the first global container stack is set. - cura.Settings.ExtruderManager.getInstance() - qmlRegisterSingletonType(cura.Settings.MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager) - qmlRegisterSingletonType(cura.Settings.SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager) + ExtruderManager.getInstance() + qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager) + qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", + self.getSettingInheritanceManager) + qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml")) self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles)) @@ -585,12 +604,12 @@ class CuraApplication(QtApplication): def getMachineManager(self, *args): if self._machine_manager is None: - self._machine_manager = cura.Settings.MachineManager.createMachineManager() + self._machine_manager = MachineManager.createMachineManager() return self._machine_manager def getSettingInheritanceManager(self, *args): if self._setting_inheritance_manager is None: - self._setting_inheritance_manager = cura.Settings.SettingInheritanceManager.createSettingInheritanceManager() + self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager() return self._setting_inheritance_manager ## Get the machine action manager @@ -626,23 +645,23 @@ class CuraApplication(QtApplication): qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") - qmlRegisterType(cura.Settings.ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") + qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") - qmlRegisterType(cura.Settings.ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel") - qmlRegisterSingletonType(cura.Settings.ProfilesModel, "Cura", 1, 0, "ProfilesModel", cura.Settings.ProfilesModel.createProfilesModel) - qmlRegisterType(cura.Settings.QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel") - qmlRegisterType(cura.Settings.UserProfilesModel, "Cura", 1, 0, "UserProfilesModel") - qmlRegisterType(cura.Settings.MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") - qmlRegisterType(cura.Settings.QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") - qmlRegisterType(cura.Settings.MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") + qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel") + qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel) + qmlRegisterType(QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel") + qmlRegisterType(UserProfilesModel, "Cura", 1, 0, "UserProfilesModel") + qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") + qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") + qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") - qmlRegisterSingletonType(cura.Settings.ContainerManager, "Cura", 1, 0, "ContainerManager", cura.Settings.ContainerManager.createContainerManager) + qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) # 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"))) qmlRegisterSingletonType(actions_url, "Cura", 1, 0, "Actions") - engine.rootContext().setContextProperty("ExtruderManager", cura.Settings.ExtruderManager.getInstance()) + engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.getInstance()) for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles): type_name = os.path.splitext(os.path.basename(path))[0] diff --git a/cura/Layer.py b/cura/Layer.py index 4e38a6eba9..869b84ed90 100644 --- a/cura/Layer.py +++ b/cura/Layer.py @@ -49,12 +49,12 @@ class Layer: return result - def build(self, vertex_offset, index_offset, vertices, colors, indices): + def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices): result_vertex_offset = vertex_offset result_index_offset = index_offset self._element_count = 0 for polygon in self._polygons: - polygon.build(result_vertex_offset, result_index_offset, vertices, colors, indices) + polygon.build(result_vertex_offset, result_index_offset, vertices, colors, line_dimensions, extruders, line_types, indices) result_vertex_offset += polygon.lineMeshVertexCount() result_index_offset += polygon.lineMeshElementCount() self._element_count += polygon.elementCount diff --git a/cura/LayerData.py b/cura/LayerData.py index ad5326373e..3fe550c297 100644 --- a/cura/LayerData.py +++ b/cura/LayerData.py @@ -6,9 +6,9 @@ from UM.Mesh.MeshData import MeshData # Immutable, use LayerDataBuilder to create one of these. class LayerData(MeshData): def __init__(self, vertices = None, normals = None, indices = None, colors = None, uvs = None, file_name = None, - center_position = None, layers=None, element_counts=None): + center_position = None, layers=None, element_counts=None, attributes=None): super().__init__(vertices=vertices, normals=normals, indices=indices, colors=colors, uvs=uvs, - file_name=file_name, center_position=center_position) + file_name=file_name, center_position=center_position, attributes=attributes) self._layers = layers self._element_counts = element_counts diff --git a/cura/LayerDataBuilder.py b/cura/LayerDataBuilder.py index 2215ed5f27..428ad4a210 100644 --- a/cura/LayerDataBuilder.py +++ b/cura/LayerDataBuilder.py @@ -48,7 +48,11 @@ class LayerDataBuilder(MeshBuilder): self._layers[layer].setThickness(thickness) - def build(self): + ## Return the layer data as LayerData. + # + # \param material_color_map: [r, g, b, a] for each extruder row. + # \param line_type_brightness: compatibility layer view uses line type brightness of 0.5 + def build(self, material_color_map, line_type_brightness = 1.0): vertex_count = 0 index_count = 0 for layer, data in self._layers.items(): @@ -56,20 +60,56 @@ class LayerDataBuilder(MeshBuilder): index_count += data.lineMeshElementCount() vertices = numpy.empty((vertex_count, 3), numpy.float32) + line_dimensions = numpy.empty((vertex_count, 2), numpy.float32) colors = numpy.empty((vertex_count, 4), numpy.float32) indices = numpy.empty((index_count, 2), numpy.int32) + extruders = numpy.empty((vertex_count), numpy.float32) + line_types = numpy.empty((vertex_count), numpy.float32) vertex_offset = 0 index_offset = 0 for layer, data in self._layers.items(): - ( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, indices) + ( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices) self._element_counts[layer] = data.elementCount self.addVertices(vertices) + colors[:, 0:3] *= line_type_brightness self.addColors(colors) self.addIndices(indices.flatten()) + # Note: we're using numpy indexing here. + # See also: https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html + material_colors = numpy.zeros((line_dimensions.shape[0], 4), dtype=numpy.float32) + for extruder_nr in range(material_color_map.shape[0]): + material_colors[extruders == extruder_nr] = material_color_map[extruder_nr] + # Set material_colors with indices where line_types (also numpy array) == MoveCombingType + material_colors[line_types == LayerPolygon.MoveCombingType] = colors[line_types == LayerPolygon.MoveCombingType] + material_colors[line_types == LayerPolygon.MoveRetractionType] = colors[line_types == LayerPolygon.MoveRetractionType] + + attributes = { + "line_dimensions": { + "value": line_dimensions, + "opengl_name": "a_line_dim", + "opengl_type": "vector2f" + }, + "extruders": { + "value": extruders, + "opengl_name": "a_extruder", + "opengl_type": "float" + }, + "colors": { + "value": material_colors, + "opengl_name": "a_material_color", + "opengl_type": "vector4f" + }, + "line_types": { + "value": line_types, + "opengl_name": "a_line_type", + "opengl_type": "float" + } + } + return LayerData(vertices=self.getVertices(), normals=self.getNormals(), indices=self.getIndices(), colors=self.getColors(), uvs=self.getUVCoordinates(), file_name=self.getFileName(), center_position=self.getCenterPosition(), layers=self._layers, - element_counts=self._element_counts) + element_counts=self._element_counts, attributes=attributes) diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index 70b17cff75..2c527c7c7e 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -1,6 +1,6 @@ from UM.Math.Color import Color from UM.Application import Application - +from typing import Any import numpy @@ -19,13 +19,19 @@ class LayerPolygon: __jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(11) == NoneType, numpy.arange(11) == MoveCombingType), numpy.arange(11) == MoveRetractionType) - def __init__(self, mesh, extruder, line_types, data, line_widths): - self._mesh = mesh + ## LayerPolygon, used in ProcessSlicedLayersJob + # \param extruder + # \param line_types array with line_types + # \param data new_points + # \param line_widths array with line widths + # \param line_thicknesses: array with type as index and thickness as value + def __init__(self, extruder, line_types, data, line_widths, line_thicknesses): self._extruder = extruder self._types = line_types self._data = data self._line_widths = line_widths - + self._line_thicknesses = line_thicknesses + self._vertex_begin = 0 self._vertex_end = 0 self._index_begin = 0 @@ -38,7 +44,7 @@ class LayerPolygon: # Buffering the colors shouldn't be necessary as it is not # re-used and can save alot of memory usage. - self._color_map = LayerPolygon.getColorMap() * [1, 1, 1, self._extruder] # The alpha component is used to store the extruder nr + self._color_map = LayerPolygon.getColorMap() self._colors = self._color_map[self._types] # When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType @@ -50,7 +56,7 @@ class LayerPolygon: def buildCache(self): # For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out. - self._build_cache_line_mesh_mask = numpy.logical_not(numpy.logical_or(self._jump_mask, self._types == LayerPolygon.InfillType )) + self._build_cache_line_mesh_mask = numpy.ones(self._jump_mask.shape, dtype=bool) mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask) self._index_begin = 0 self._index_end = mesh_line_count @@ -60,13 +66,23 @@ class LayerPolygon: self._build_cache_needed_points[1:, 0][:, numpy.newaxis] = self._types[1:] != self._types[:-1] # Mark points as unneeded if they are of types we don't want in the line mesh according to the calculated mask numpy.logical_and(self._build_cache_needed_points, self._build_cache_line_mesh_mask, self._build_cache_needed_points ) - + self._vertex_begin = 0 self._vertex_end = numpy.sum( self._build_cache_needed_points ) - - def build(self, vertex_offset, index_offset, vertices, colors, indices): - if (self._build_cache_line_mesh_mask is None) or (self._build_cache_needed_points is None ): + ## Set all the arrays provided by the function caller, representing the LayerPolygon + # The arrays are either by vertex or by indices. + # + # \param vertex_offset : determines where to start and end filling the arrays + # \param index_offset : determines where to start and end filling the arrays + # \param vertices : vertex numpy array to be filled + # \param colors : vertex numpy array to be filled + # \param line_dimensions : vertex numpy array to be filled + # \param extruders : vertex numpy array to be filled + # \param line_types : vertex numpy array to be filled + # \param indices : index numpy array to be filled + def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices): + if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None: self.buildCache() line_mesh_mask = self._build_cache_line_mesh_mask @@ -83,9 +99,18 @@ class LayerPolygon: # Points are picked based on the index list to get the vertices needed. vertices[self._vertex_begin:self._vertex_end, :] = self._data[index_list, :] + # Create an array with colors for each vertex and remove the color data for the points that has been thrown away. - colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()] - colors[self._vertex_begin:self._vertex_end, :] *= numpy.array([[0.5, 0.5, 0.5, 1.0]], numpy.float32) + colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()] + + # Create an array with line widths for each vertex. + line_dimensions[self._vertex_begin:self._vertex_end, 0] = numpy.tile(self._line_widths, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] + line_dimensions[self._vertex_begin:self._vertex_end, 1] = numpy.tile(self._line_thicknesses, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] + + extruders[self._vertex_begin:self._vertex_end] = self._extruder + + # Convert type per vertex to type per line + line_types[self._vertex_begin:self._vertex_end] = numpy.tile(self._types, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] # The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset. self._index_begin += index_offset @@ -173,7 +198,7 @@ class LayerPolygon: return normals - __color_map = None + __color_map = None # type: numpy.ndarray[Any] ## Gets the instance of the VersionUpgradeManager, or creates one. @classmethod diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index 82eb9c5a64..b88613b0ac 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -7,9 +7,9 @@ from UM.FlameProfiler import pyqtSlot from UM.Application import Application from UM.Qt.Duration import Duration from UM.Preferences import Preferences -from UM.Settings import ContainerRegistry +from UM.Settings.ContainerRegistry import ContainerRegistry -import cura.Settings.ExtruderManager +from cura.Settings.ExtruderManager import ExtruderManager import math import os.path @@ -124,7 +124,7 @@ class PrintInformation(QObject): material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings")) - extruder_stacks = list(cura.Settings.ExtruderManager.getInstance().getMachineExtruders(Application.getInstance().getGlobalContainerStack().getId())) + extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(Application.getInstance().getGlobalContainerStack().getId())) for index, amount in enumerate(self._material_amounts): ## Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some # list comprehension filtering to solve this for us. diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index 6e7305b27b..4a55e8a6d9 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -1,12 +1,15 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + from UM.i18n import i18nCatalog from UM.OutputDevice.OutputDevice import OutputDevice from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from PyQt5.QtWidgets import QMessageBox -import UM.Settings.ContainerRegistry + +from UM.Settings.ContainerRegistry import ContainerRegistry from enum import IntEnum # For the connection state tracking. from UM.Logger import Logger -from UM.Application import Application from UM.Signal import signalemitter i18n_catalog = i18nCatalog("cura") @@ -25,7 +28,7 @@ class PrinterOutputDevice(QObject, OutputDevice): def __init__(self, device_id, parent = None): super().__init__(device_id = device_id, parent = parent) - self._container_registry = UM.Settings.ContainerRegistry.getInstance() + self._container_registry = ContainerRegistry.getInstance() self._target_bed_temperature = 0 self._bed_temperature = 0 self._num_extruders = 1 @@ -45,6 +48,7 @@ class PrinterOutputDevice(QObject, OutputDevice): self._job_name = "" self._error_text = "" self._accepts_commands = True + self._preheat_bed_timeout = 900 #Default time-out for pre-heating the bed, in seconds. self._printer_state = "" self._printer_type = "unknown" @@ -161,6 +165,17 @@ class PrinterOutputDevice(QObject, OutputDevice): self._job_name = name self.jobNameChanged.emit() + ## Gives a human-readable address where the device can be found. + @pyqtProperty(str, constant = True) + def address(self): + Logger.log("w", "address is not implemented by this output device.") + + ## A human-readable name for the device. + @pyqtProperty(str, constant = True) + def name(self): + Logger.log("w", "name is not implemented by this output device.") + return "" + @pyqtProperty(str, notify = errorTextChanged) def errorText(self): return self._error_text @@ -199,6 +214,13 @@ class PrinterOutputDevice(QObject, OutputDevice): self._target_bed_temperature = temperature self.targetBedTemperatureChanged.emit() + ## The duration of the time-out to pre-heat the bed, in seconds. + # + # \return The duration of the time-out to pre-heat the bed, in seconds. + @pyqtProperty(int) + def preheatBedTimeout(self): + return self._preheat_bed_timeout + ## Time the print has been printing. # Note that timeTotal - timeElapsed should give time remaining. @pyqtProperty(float, notify = timeElapsedChanged) @@ -254,6 +276,22 @@ class PrinterOutputDevice(QObject, OutputDevice): def _setTargetBedTemperature(self, temperature): Logger.log("w", "_setTargetBedTemperature is not implemented by this output device") + ## Pre-heats the heated bed of the printer. + # + # \param temperature The temperature to heat the bed to, in degrees + # Celsius. + # \param duration How long the bed should stay warm, in seconds. + @pyqtSlot(float, float) + def preheatBed(self, temperature, duration): + Logger.log("w", "preheatBed is not implemented by this output device.") + + ## Cancels pre-heating the heated bed of the printer. + # + # If the bed is not pre-heated, nothing happens. + @pyqtSlot() + def cancelPreheatBed(self): + Logger.log("w", "cancelPreheatBed is not implemented by this output device.") + ## Protected setter for the current bed temperature. # This simply sets the bed temperature, but ensures that a signal is emitted. # /param temperature temperature of the bed. @@ -323,6 +361,28 @@ class PrinterOutputDevice(QObject, OutputDevice): result.append(i18n_catalog.i18nc("@item:material", "Unknown material")) return result + ## List of the colours of the currently loaded materials. + # + # The list is in order of extruders. If there is no material in an + # extruder, the colour is shown as transparent. + # + # The colours are returned in hex-format AARRGGBB or RRGGBB + # (e.g. #800000ff for transparent blue or #00ff00 for pure green). + @pyqtProperty("QVariantList", notify = materialIdChanged) + def materialColors(self): + result = [] + for material_id in self._material_ids: + if material_id is None: + result.append("#00000000") #No material. + continue + + containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_id) + if containers: + result.append(containers[0].getMetaDataEntry("color_code")) + else: + result.append("#00000000") #Unknown material. + return result + ## Protected setter for the current material id. # /param index Index of the extruder # /param material_id id of the material diff --git a/cura/QualityManager.py b/cura/QualityManager.py index 8986b05033..d7b2c7d705 100644 --- a/cura/QualityManager.py +++ b/cura/QualityManager.py @@ -1,12 +1,16 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -import UM.Application -import cura.Settings.ExtruderManager -import UM.Settings.ContainerRegistry - # This collects a lot of quality and quality changes related code which was split between ContainerManager # and the MachineManager and really needs to usable from both. +from typing import List + +from UM.Application import Application +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.InstanceContainer import InstanceContainer +from cura.Settings.ExtruderManager import ExtruderManager + class QualityManager: @@ -18,7 +22,7 @@ class QualityManager: QualityManager.__instance = cls() return QualityManager.__instance - __instance = None + __instance = None # type: "QualityManager" ## Find a quality by name for a specific machine definition and materials. # @@ -121,14 +125,14 @@ class QualityManager: # # \param machine_definition \type{DefinitionContainer} the machine definition. # \return \type{List[InstanceContainer]} the list of quality changes - def findAllQualityChangesForMachine(self, machine_definition): + def findAllQualityChangesForMachine(self, machine_definition: DefinitionContainer) -> List[InstanceContainer]: if machine_definition.getMetaDataEntry("has_machine_quality"): definition_id = machine_definition.getId() else: definition_id = "fdmprinter" filter_dict = { "type": "quality_changes", "extruder": None, "definition": definition_id } - quality_changes_list = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**filter_dict) + quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict) return quality_changes_list ## Find all usable qualities for a machine and extruders. @@ -177,7 +181,7 @@ class QualityManager: if base_material: # There is a basic material specified criteria = { "type": "material", "name": base_material, "definition": definition_id } - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria) + containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria) containers = [basic_material for basic_material in containers if basic_material.getMetaDataEntry("variant") == material_container.getMetaDataEntry( "variant")] @@ -191,13 +195,13 @@ class QualityManager: def _getFilteredContainersForStack(self, machine_definition=None, material_containers=None, **kwargs): # Fill in any default values. if machine_definition is None: - machine_definition = UM.Application.getInstance().getGlobalContainerStack().getBottom() + machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() quality_definition_id = machine_definition.getMetaDataEntry("quality_definition") if quality_definition_id is not None: - machine_definition = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0] + machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0] if material_containers is None: - active_stacks = cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() + active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() material_containers = [stack.findContainer(type="material") for stack in active_stacks] criteria = kwargs @@ -225,7 +229,7 @@ class QualityManager: material_ids.add(basic_material.getId()) material_ids.add(material_instance.getId()) - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria) + containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria) result = [] for container in containers: @@ -241,8 +245,8 @@ class QualityManager: # an extruder definition. # \return \type{DefinitionContainer} the parent machine definition. If the given machine # definition doesn't have a parent then it is simply returned. - def getParentMachineDefinition(self, machine_definition): - container_registry = UM.Settings.ContainerRegistry.getInstance() + def getParentMachineDefinition(self, machine_definition: DefinitionContainer) -> DefinitionContainer: + container_registry = ContainerRegistry.getInstance() machine_entry = machine_definition.getMetaDataEntry("machine") if machine_entry is None: @@ -277,6 +281,6 @@ class QualityManager: # This already is a 'global' machine definition. return machine_definition else: - container_registry = UM.Settings.ContainerRegistry.getInstance() + container_registry = ContainerRegistry.getInstance() whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0] return whole_machine diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 4646961adb..4e4fc36784 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -8,19 +8,25 @@ from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QUrl, QVariant from UM.FlameProfiler import pyqtSlot from PyQt5.QtWidgets import QMessageBox -import UM.PluginRegistry -import UM.Settings +from UM.PluginRegistry import PluginRegistry import UM.SaveFile import UM.Platform import UM.MimeTypeDatabase -import UM.Logger -import cura.Settings +from UM.Logger import Logger +from UM.Application import Application +from UM.Settings.ContainerStack import ContainerStack +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.InstanceContainer import InstanceContainer from cura.QualityManager import QualityManager from UM.MimeTypeDatabase import MimeTypeNotFoundError +from UM.Settings.ContainerRegistry import ContainerRegistry from UM.i18n import i18nCatalog + +from cura.Settings.ExtruderManager import ExtruderManager + catalog = i18nCatalog("cura") ## Manager class that contains common actions to deal with containers in Cura. @@ -32,9 +38,8 @@ class ContainerManager(QObject): def __init__(self, parent = None): super().__init__(parent) - self._container_registry = UM.Settings.ContainerRegistry.getInstance() - self._machine_manager = UM.Application.getInstance().getMachineManager() - + self._container_registry = ContainerRegistry.getInstance() + self._machine_manager = Application.getInstance().getMachineManager() self._container_name_filters = {} ## Create a duplicate of the specified container @@ -49,7 +54,7 @@ class ContainerManager(QObject): def duplicateContainer(self, container_id): containers = self._container_registry.findContainers(None, id = container_id) if not containers: - UM.Logger.log("w", "Could duplicate container %s because it was not found.", container_id) + Logger.log("w", "Could duplicate container %s because it was not found.", container_id) return "" container = containers[0] @@ -81,7 +86,7 @@ class ContainerManager(QObject): def renameContainer(self, container_id, new_id, new_name): containers = self._container_registry.findContainers(None, id = container_id) if not containers: - UM.Logger.log("w", "Could rename container %s because it was not found.", container_id) + Logger.log("w", "Could rename container %s because it was not found.", container_id) return False container = containers[0] @@ -109,7 +114,7 @@ class ContainerManager(QObject): def removeContainer(self, container_id): containers = self._container_registry.findContainers(None, id = container_id) if not containers: - UM.Logger.log("w", "Could remove container %s because it was not found.", container_id) + Logger.log("w", "Could remove container %s because it was not found.", container_id) return False self._container_registry.removeContainer(containers[0].getId()) @@ -129,20 +134,20 @@ class ContainerManager(QObject): def mergeContainers(self, merge_into_id, merge_id): containers = self._container_registry.findContainers(None, id = merge_into_id) if not containers: - UM.Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id) + Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id) return False merge_into = containers[0] containers = self._container_registry.findContainers(None, id = merge_id) if not containers: - UM.Logger.log("w", "Could not merge container %s because it was not found", merge_id) + Logger.log("w", "Could not merge container %s because it was not found", merge_id) return False merge = containers[0] if not isinstance(merge, type(merge_into)): - UM.Logger.log("w", "Cannot merge two containers of different types") + Logger.log("w", "Cannot merge two containers of different types") return False self._performMerge(merge_into, merge) @@ -158,11 +163,11 @@ class ContainerManager(QObject): def clearContainer(self, container_id): containers = self._container_registry.findContainers(None, id = container_id) if not containers: - UM.Logger.log("w", "Could clear container %s because it was not found.", container_id) + Logger.log("w", "Could clear container %s because it was not found.", container_id) return False if containers[0].isReadOnly(): - UM.Logger.log("w", "Cannot clear read-only container %s", container_id) + Logger.log("w", "Cannot clear read-only container %s", container_id) return False containers[0].clear() @@ -173,7 +178,7 @@ class ContainerManager(QObject): def getContainerMetaDataEntry(self, container_id, entry_name): containers = self._container_registry.findContainers(None, id=container_id) if not containers: - UM.Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id) + Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id) return "" result = containers[0].getMetaDataEntry(entry_name) @@ -198,13 +203,13 @@ class ContainerManager(QObject): def setContainerMetaDataEntry(self, container_id, entry_name, entry_value): containers = self._container_registry.findContainers(None, id = container_id) if not containers: - UM.Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id) + Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id) return False container = containers[0] if container.isReadOnly(): - UM.Logger.log("w", "Cannot set metadata of read-only container %s.", container_id) + Logger.log("w", "Cannot set metadata of read-only container %s.", container_id) return False entries = entry_name.split("/") @@ -232,13 +237,13 @@ class ContainerManager(QObject): def setContainerName(self, container_id, new_name): containers = self._container_registry.findContainers(None, id = container_id) if not containers: - UM.Logger.log("w", "Could not set name of container %s because it was not found.", container_id) + Logger.log("w", "Could not set name of container %s because it was not found.", container_id) return False container = containers[0] if container.isReadOnly(): - UM.Logger.log("w", "Cannot set name of read-only container %s.", container_id) + Logger.log("w", "Cannot set name of read-only container %s.", container_id) return False container.setName(new_name) @@ -262,11 +267,11 @@ class ContainerManager(QObject): @pyqtSlot(str, result = bool) def isContainerUsed(self, container_id): - UM.Logger.log("d", "Checking if container %s is currently used", container_id) + Logger.log("d", "Checking if container %s is currently used", container_id) containers = self._container_registry.findContainerStacks() for stack in containers: if container_id in [child.getId() for child in stack.getContainers()]: - UM.Logger.log("d", "The container is in use by %s", stack.getId()) + Logger.log("d", "The container is in use by %s", stack.getId()) return True return False @@ -382,7 +387,7 @@ class ContainerManager(QObject): except MimeTypeNotFoundError: return { "status": "error", "message": "Could not determine mime type of file" } - container_type = UM.Settings.ContainerRegistry.getContainerForMimeType(mime_type) + container_type = self._container_registry.getContainerForMimeType(mime_type) if not container_type: return { "status": "error", "message": "Could not find a container to handle the specified file."} @@ -411,17 +416,17 @@ class ContainerManager(QObject): # \return \type{bool} True if successful, False if not. @pyqtSlot(result = bool) def updateQualityChanges(self): - global_stack = UM.Application.getInstance().getGlobalContainerStack() + global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return False self._machine_manager.blurSettings.emit() - for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): # Find the quality_changes container for this stack and merge the contents of the top container into it. quality_changes = stack.findContainer(type = "quality_changes") if not quality_changes or quality_changes.isReadOnly(): - UM.Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId()) + Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId()) continue self._performMerge(quality_changes, stack.getTop()) @@ -438,7 +443,7 @@ class ContainerManager(QObject): send_emits_containers = [] # Go through global and extruder stacks and clear their topmost container (the user settings). - for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): container = stack.getTop() container.clear() send_emits_containers.append(container) @@ -455,13 +460,13 @@ class ContainerManager(QObject): # \return \type{bool} True if the operation was successfully, False if not. @pyqtSlot(str, result = bool) def createQualityChanges(self, base_name): - global_stack = UM.Application.getInstance().getGlobalContainerStack() + global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return False active_quality_name = self._machine_manager.activeQualityName if active_quality_name == "": - UM.Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId()) + Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId()) return False self._machine_manager.blurSettings.emit() @@ -470,17 +475,17 @@ class ContainerManager(QObject): unique_name = self._container_registry.uniqueName(base_name) # Go through the active stacks and create quality_changes containers from the user containers. - for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): user_container = stack.getTop() quality_container = stack.findContainer(type = "quality") quality_changes_container = stack.findContainer(type = "quality_changes") if not quality_container or not quality_changes_container: - UM.Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId()) + Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId()) continue extruder_id = None if stack is global_stack else QualityManager.getInstance().getParentMachineDefinition(stack.getBottom()).getId() new_changes = self._createQualityChanges(quality_container, unique_name, - UM.Application.getInstance().getGlobalContainerStack().getBottom(), + Application.getInstance().getGlobalContainerStack().getBottom(), extruder_id) self._performMerge(new_changes, quality_changes_container, clear_settings = False) self._performMerge(new_changes, user_container) @@ -502,7 +507,7 @@ class ContainerManager(QObject): # \return \type{bool} True if successful, False if not. @pyqtSlot(str, result = bool) def removeQualityChanges(self, quality_name): - UM.Logger.log("d", "Attempting to remove the quality change containers with name %s", quality_name) + Logger.log("d", "Attempting to remove the quality change containers with name %s", quality_name) containers_found = False if not quality_name: @@ -512,7 +517,7 @@ class ContainerManager(QObject): activate_quality = quality_name == self._machine_manager.activeQualityName activate_quality_type = None - global_stack = UM.Application.getInstance().getGlobalContainerStack() + global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack or not quality_name: return "" machine_definition = global_stack.getBottom() @@ -524,7 +529,7 @@ class ContainerManager(QObject): self._container_registry.removeContainer(container.getId()) if not containers_found: - UM.Logger.log("d", "Unable to remove quality containers, as we did not find any by the name of %s", quality_name) + Logger.log("d", "Unable to remove quality containers, as we did not find any by the name of %s", quality_name) elif activate_quality: definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId @@ -547,15 +552,15 @@ class ContainerManager(QObject): # \return True if successful, False if not. @pyqtSlot(str, str, result = bool) def renameQualityChanges(self, quality_name, new_name): - UM.Logger.log("d", "User requested QualityChanges container rename of %s to %s", quality_name, new_name) + Logger.log("d", "User requested QualityChanges container rename of %s to %s", quality_name, new_name) if not quality_name or not new_name: return False if quality_name == new_name: - UM.Logger.log("w", "Unable to rename %s to %s, because they are the same.", quality_name, new_name) + Logger.log("w", "Unable to rename %s to %s, because they are the same.", quality_name, new_name) return True - global_stack = UM.Application.getInstance().getGlobalContainerStack() + global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return False @@ -572,7 +577,7 @@ class ContainerManager(QObject): container_registry.renameContainer(container.getId(), new_name, self._createUniqueId(stack_id, new_name)) if not containers_to_rename: - UM.Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name) + Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name) self._machine_manager.activeQualityChanged.emit() return True @@ -588,12 +593,12 @@ class ContainerManager(QObject): # \return A string containing the name of the duplicated containers, or an empty string if it failed. @pyqtSlot(str, str, result = str) def duplicateQualityOrQualityChanges(self, quality_name, base_name): - global_stack = UM.Application.getInstance().getGlobalContainerStack() + global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack or not quality_name: return "" machine_definition = global_stack.getBottom() - active_stacks = cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() + active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() material_containers = [stack.findContainer(type="material") for stack in active_stacks] result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name, @@ -609,16 +614,16 @@ class ContainerManager(QObject): # \param material_instances \type{List[InstanceContainer]} # \return \type{str} the name of the newly created container. def _duplicateQualityOrQualityChangesForMachineType(self, quality_name, base_name, machine_definition, material_instances): - UM.Logger.log("d", "Attempting to duplicate the quality %s", quality_name) + Logger.log("d", "Attempting to duplicate the quality %s", quality_name) if base_name is None: base_name = quality_name # Try to find a Quality with the name. container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_instances) if container: - UM.Logger.log("d", "We found a quality to duplicate.") + Logger.log("d", "We found a quality to duplicate.") return self._duplicateQualityForMachineType(container, base_name, machine_definition) - UM.Logger.log("d", "We found a quality_changes to duplicate.") + Logger.log("d", "We found a quality_changes to duplicate.") # Assume it is a quality changes. return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition) @@ -665,11 +670,11 @@ class ContainerManager(QObject): def duplicateMaterial(self, material_id): containers = self._container_registry.findInstanceContainers(id=material_id) if not containers: - UM.Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id) + Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id) return "" # Ensure all settings are saved. - UM.Application.getInstance().saveSettings() + Application.getInstance().saveSettings() # Create a new ID & container to hold the data. new_id = self._container_registry.uniqueName(material_id) @@ -692,7 +697,7 @@ class ContainerManager(QObject): ContainerManager.__instance = cls() return ContainerManager.__instance - __instance = None + __instance = None # type: "ContainerManager" # Factory function, used by QML @staticmethod @@ -713,14 +718,14 @@ class ContainerManager(QObject): def _updateContainerNameFilters(self): self._container_name_filters = {} - for plugin_id, container_type in UM.Settings.ContainerRegistry.getContainerTypes(): + for plugin_id, container_type in self._container_registry.getContainerTypes(): # Ignore default container types since those are not plugins - if container_type in (UM.Settings.InstanceContainer, UM.Settings.ContainerStack, UM.Settings.DefinitionContainer): + if container_type in (InstanceContainer, ContainerStack, DefinitionContainer): continue serialize_type = "" try: - plugin_metadata = UM.PluginRegistry.getInstance().getMetaData(plugin_id) + plugin_metadata = PluginRegistry.getInstance().getMetaData(plugin_id) if plugin_metadata: serialize_type = plugin_metadata["settings_container"]["type"] else: @@ -728,7 +733,7 @@ class ContainerManager(QObject): except KeyError as e: continue - mime_type = UM.Settings.ContainerRegistry.getMimeTypeForContainer(container_type) + mime_type = self._container_registry.getMimeTypeForContainer(container_type) entry = { "type": serialize_type, @@ -791,7 +796,7 @@ class ContainerManager(QObject): base_id = machine_definition.getId() if extruder_id is None else extruder_id # Create a new quality_changes container for the quality. - quality_changes = UM.Settings.InstanceContainer(self._createUniqueId(base_id, new_name)) + quality_changes = InstanceContainer(self._createUniqueId(base_id, new_name)) quality_changes.setName(new_name) quality_changes.addMetaDataEntry("type", "quality_changes") quality_changes.addMetaDataEntry("quality_type", quality_container.getMetaDataEntry("quality_type")) @@ -826,7 +831,7 @@ class ContainerManager(QObject): if not path.endswith(".curaprofile"): continue - single_result = UM.Settings.ContainerRegistry.getInstance().importProfile(path) + single_result = self._container_registry.importProfile(path) if single_result["status"] == "error": status = "error" results[single_result["status"]].append(single_result["message"]) @@ -843,7 +848,7 @@ class ContainerManager(QObject): path = file_url.toLocalFile() if not path: return - return UM.Settings.ContainerRegistry.getInstance().importProfile(path) + return self._container_registry.importProfile(path) @pyqtSlot("QVariantList", QUrl, str) def exportProfile(self, instance_id, file_url, file_type): @@ -852,4 +857,4 @@ class ContainerManager(QObject): path = file_url.toLocalFile() if not path: return - UM.Settings.ContainerRegistry.getInstance().exportProfile(instance_id, path, file_type) + self._container_registry.exportProfile(instance_id, path, file_type) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 81579f74d0..14106d5804 100644 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -4,13 +4,16 @@ from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt. from UM.FlameProfiler import pyqtSlot -import UM.Application #To get the global container stack to find the current machine. -import UM.Logger -from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator #To find which extruders are used in the scene. -from UM.Scene.SceneNode import SceneNode #To find which extruders are used in the scene. -import UM.Settings.ContainerRegistry #Finding containers by ID. -import UM.Settings.SettingFunction - +from UM.Application import Application #To get the global container stack to find the current machine. +from UM.Logger import Logger +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Scene.SceneNode import SceneNode +from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID. +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.SettingFunction import SettingFunction +from UM.Settings.ContainerStack import ContainerStack +from UM.Settings.DefinitionContainer import DefinitionContainer +from typing import Optional ## Manages all existing extruder stacks. # @@ -31,7 +34,7 @@ class ExtruderManager(QObject): super().__init__(parent) self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. self._active_extruder_index = 0 - UM.Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged) + Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged) self._global_container_stack_definition_id = None self._addCurrentMachineExtruders() @@ -42,34 +45,35 @@ class ExtruderManager(QObject): # # \return The unique ID of the currently active extruder stack. @pyqtProperty(str, notify = activeExtruderChanged) - def activeExtruderStackId(self): - if not UM.Application.getInstance().getGlobalContainerStack(): + def activeExtruderStackId(self) -> Optional[str]: + if not Application.getInstance().getGlobalContainerStack(): return None # No active machine, so no active extruder. try: - return self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId() + return self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId() except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong. return None + ## Return extruder count according to extruder trains. @pyqtProperty(int, notify = extrudersChanged) def extruderCount(self): - if not UM.Application.getInstance().getGlobalContainerStack(): + if not Application.getInstance().getGlobalContainerStack(): return 0 # No active machine, so no extruders. try: - return len(self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()]) + return len(self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]) except KeyError: return 0 @pyqtProperty("QVariantMap", notify=extrudersChanged) def extruderIds(self): map = {} - for position in self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()]: - map[position] = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position].getId() + for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]: + map[position] = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position].getId() return map @pyqtSlot(str, result = str) - def getQualityChangesIdByExtruderStackId(self, id): - for position in self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()]: - extruder = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position] + def getQualityChangesIdByExtruderStackId(self, id: str) -> str: + for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]: + extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position] if extruder.getId() == id: return extruder.findContainer(type = "quality_changes").getId() @@ -86,7 +90,7 @@ class ExtruderManager(QObject): # # \return The extruder manager. @classmethod - def getInstance(cls): + def getInstance(cls) -> "ExtruderManager": if not cls.__instance: cls.__instance = ExtruderManager() return cls.__instance @@ -95,16 +99,27 @@ class ExtruderManager(QObject): # # \param index The index of the new active extruder. @pyqtSlot(int) - def setActiveExtruderIndex(self, index): + def setActiveExtruderIndex(self, index: int) -> None: self._active_extruder_index = index self.activeExtruderChanged.emit() @pyqtProperty(int, notify = activeExtruderChanged) - def activeExtruderIndex(self): + def activeExtruderIndex(self) -> int: return self._active_extruder_index - def getActiveExtruderStack(self): - global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + ## Gets the extruder name of an extruder of the currently active machine. + # + # \param index The index of the extruder whose name to get. + @pyqtSlot(int, result = str) + def getExtruderName(self, index): + try: + return list(self.getActiveExtruderStacks())[index].getName() + except IndexError: + return "" + + def getActiveExtruderStack(self) -> ContainerStack: + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: if global_container_stack.getId() in self._extruder_trains: if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]: @@ -113,7 +128,7 @@ class ExtruderManager(QObject): ## Get an extruder stack by index def getExtruderStack(self, index): - global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: if global_container_stack.getId() in self._extruder_trains: if str(index) in self._extruder_trains[global_container_stack.getId()]: @@ -125,19 +140,19 @@ class ExtruderManager(QObject): # # \param machine_definition The machine definition to add the extruders for. # \param machine_id The machine_id to add the extruders for. - def addMachineExtruders(self, machine_definition, machine_id): + def addMachineExtruders(self, machine_definition: DefinitionContainer, machine_id: str) -> None: changed = False machine_definition_id = machine_definition.getId() if machine_id not in self._extruder_trains: self._extruder_trains[machine_id] = { } changed = True - container_registry = UM.Settings.ContainerRegistry.getInstance() + container_registry = ContainerRegistry.getInstance() if container_registry: # Add the extruder trains that don't exist yet. for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition_id): position = extruder_definition.getMetaDataEntry("position", None) if not position: - UM.Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId()) + Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId()) if not container_registry.findContainerStacks(machine = machine_id, position = position): # Doesn't exist yet. self.createExtruderTrain(extruder_definition, machine_definition, position, machine_id) changed = True @@ -148,7 +163,7 @@ class ExtruderManager(QObject): self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train # regardless of what the next stack is, we have to set it again, because of signal routing. - extruder_train.setNextStack(UM.Application.getInstance().getGlobalContainerStack()) + extruder_train.setNextStack(Application.getInstance().getGlobalContainerStack()) changed = True if changed: self.extrudersChanged.emit(machine_id) @@ -177,14 +192,15 @@ class ExtruderManager(QObject): # \param machine_definition The machine that the extruder train belongs to. # \param position The position of this extruder train in the extruder slots of the machine. # \param machine_id The id of the "global" stack this extruder is linked to. - def createExtruderTrain(self, extruder_definition, machine_definition, position, machine_id): + def createExtruderTrain(self, extruder_definition: DefinitionContainer, machine_definition: DefinitionContainer, + position, machine_id: str) -> None: # Cache some things. - container_registry = UM.Settings.ContainerRegistry.getInstance() - machine_definition_id = UM.Application.getInstance().getMachineManager().getQualityDefinitionId(machine_definition) + container_registry = ContainerRegistry.getInstance() + machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_definition) # Create a container stack for this extruder. extruder_stack_id = container_registry.uniqueName(extruder_definition.getId()) - container_stack = UM.Settings.ContainerStack(extruder_stack_id) + container_stack = ContainerStack(extruder_stack_id) container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with. container_stack.addMetaDataEntry("type", "extruder_train") container_stack.addMetaDataEntry("machine", machine_id) @@ -204,7 +220,7 @@ class ExtruderManager(QObject): if len(preferred_variants) >= 1: variant = preferred_variants[0] else: - UM.Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id) + Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id) # And leave it at the default variant. container_stack.addContainer(variant) @@ -234,7 +250,7 @@ class ExtruderManager(QObject): if len(preferred_materials) >= 1: material = preferred_materials[0] else: - UM.Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id) + Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id) # And leave it at the default material. container_stack.addContainer(material) @@ -253,11 +269,11 @@ class ExtruderManager(QObject): if preferred_quality: search_criteria["id"] = preferred_quality - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) if not containers and preferred_quality: - UM.Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id) + Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id) search_criteria.pop("id", None) - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) if containers: quality = containers[0] @@ -270,7 +286,7 @@ class ExtruderManager(QObject): if user_profile: # There was already a user profile, loaded from settings. user_profile = user_profile[0] else: - user_profile = UM.Settings.InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile. + user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile. user_profile.addMetaDataEntry("type", "user") user_profile.addMetaDataEntry("extruder", extruder_stack_id) user_profile.setDefinition(machine_definition) @@ -278,7 +294,7 @@ class ExtruderManager(QObject): container_stack.addContainer(user_profile) # regardless of what the next stack is, we have to set it again, because of signal routing. - container_stack.setNextStack(UM.Application.getInstance().getGlobalContainerStack()) + container_stack.setNextStack(Application.getInstance().getGlobalContainerStack()) container_registry.addContainer(container_stack) @@ -291,14 +307,14 @@ class ExtruderManager(QObject): # \param property \type{str} The property to get. # \return \type{List} the list of results def getAllExtruderSettings(self, setting_key, property): - global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack.getProperty("machine_extruder_count", "value") <= 1: return [global_container_stack.getProperty(setting_key, property)] result = [] for index in self.extruderIds: extruder_stack_id = self.extruderIds[str(index)] - stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] + stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] result.append(stack.getProperty(setting_key, property)) return result @@ -313,8 +329,8 @@ class ExtruderManager(QObject): # # \return A list of extruder stacks. def getUsedExtruderStacks(self): - global_stack = UM.Application.getInstance().getGlobalContainerStack() - container_registry = UM.Settings.ContainerRegistry.getInstance() + global_stack = Application.getInstance().getGlobalContainerStack() + container_registry = ContainerRegistry.getInstance() if global_stack.getProperty("machine_extruder_count", "value") <= 1: #For single extrusion. return [global_stack] @@ -324,7 +340,7 @@ class ExtruderManager(QObject): #Get the extruders of all meshes in the scene. support_enabled = False support_interface_enabled = False - scene_root = UM.Application.getInstance().getController().getScene().getRoot() + scene_root = Application.getInstance().getController().getScene().getRoot() meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] #Only use the nodes that will be printed. for mesh in meshes: extruder_stack_id = mesh.callDecoration("getActiveExtruder") @@ -355,7 +371,7 @@ class ExtruderManager(QObject): try: return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids] except IndexError: # One or more of the extruders was not found. - UM.Logger.log("e", "Unable to find one or more of the extruders in %s", used_extruder_stack_ids) + Logger.log("e", "Unable to find one or more of the extruders in %s", used_extruder_stack_ids) return [] ## Removes the container stack and user profile for the extruders for a specific machine. @@ -363,27 +379,26 @@ class ExtruderManager(QObject): # \param machine_id The machine to remove the extruders for. def removeMachineExtruders(self, machine_id): for extruder in self.getMachineExtruders(machine_id): - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "user", extruder = extruder.getId()) + containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", extruder = extruder.getId()) for container in containers: - UM.Settings.ContainerRegistry.getInstance().removeContainer(container.getId()) - UM.Settings.ContainerRegistry.getInstance().removeContainer(extruder.getId()) + ContainerRegistry.getInstance().removeContainer(container.getId()) + ContainerRegistry.getInstance().removeContainer(extruder.getId()) ## Returns extruders for a specific machine. # # \param machine_id The machine to get the extruders of. def getMachineExtruders(self, machine_id): if machine_id not in self._extruder_trains: - UM.Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id) - return - for name in self._extruder_trains[machine_id]: - yield self._extruder_trains[machine_id][name] + Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id) + return [] + return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]] ## Returns a list containing the global stack and active extruder stacks. # # The first element is the global container stack, followed by any extruder stacks. # \return \type{List[ContainerStack]} def getActiveGlobalAndExtruderStacks(self): - global_stack = UM.Application.getInstance().getGlobalContainerStack() + global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return None @@ -395,7 +410,7 @@ class ExtruderManager(QObject): # # \return \type{List[ContainerStack]} a list of def getActiveExtruderStacks(self): - global_stack = UM.Application.getInstance().getGlobalContainerStack() + global_stack = Application.getInstance().getGlobalContainerStack() result = [] if global_stack: @@ -403,17 +418,17 @@ class ExtruderManager(QObject): result.append(self._extruder_trains[global_stack.getId()][extruder]) return result - def __globalContainerStackChanged(self): + def __globalContainerStackChanged(self) -> None: self._addCurrentMachineExtruders() - global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id: self._global_container_stack_definition_id = global_container_stack.getBottom().getId() self.globalContainerStackDefinitionChanged.emit() self.activeExtruderChanged.emit() ## Adds the extruders of the currently active machine. - def _addCurrentMachineExtruders(self): - global_stack = UM.Application.getInstance().getGlobalContainerStack() + def _addCurrentMachineExtruders(self) -> None: + global_stack = Application.getInstance().getGlobalContainerStack() if global_stack and global_stack.getBottom(): self.addMachineExtruders(global_stack.getBottom(), global_stack.getId()) @@ -427,7 +442,7 @@ class ExtruderManager(QObject): # If no extruder has the value, the list will contain the global value. @staticmethod def getExtruderValues(key): - global_stack = UM.Application.getInstance().getGlobalContainerStack() + global_stack = Application.getInstance().getGlobalContainerStack() result = [] for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): @@ -436,7 +451,7 @@ class ExtruderManager(QObject): if value is None: continue - if isinstance(value, UM.Settings.SettingFunction): + if isinstance(value, SettingFunction): value = value(extruder) result.append(value) @@ -472,10 +487,10 @@ class ExtruderManager(QObject): if extruder: value = extruder.getRawProperty(key, "value") - if isinstance(value, UM.Settings.SettingFunction): + if isinstance(value, SettingFunction): value = value(extruder) else: #Just a value from global. - value = UM.Application.getInstance().getGlobalContainerStack().getProperty(key, "value") + value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value") return value @@ -488,7 +503,7 @@ class ExtruderManager(QObject): # \return The effective value @staticmethod def getResolveOrValue(key): - global_stack = UM.Application.getInstance().getGlobalContainerStack() + global_stack = Application.getInstance().getGlobalContainerStack() resolved_value = global_stack.getProperty(key, "resolve") if resolved_value is not None: diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 6681cc23f0..7f4a77eb5f 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -4,8 +4,9 @@ from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty import UM.Qt.ListModel +from UM.Application import Application -from . import ExtruderManager +from cura.Settings.ExtruderManager import ExtruderManager ## Model that holds extruders. # @@ -55,7 +56,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self._active_extruder_stack = None #Listen to changes. - UM.Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders) + Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders) manager = ExtruderManager.getInstance() self._updateExtruders() @@ -120,7 +121,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): items = [] - global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: if self._add_global: material = global_container_stack.findContainer({ "type": "material" }) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 2a11ddb56d..ce42854d43 100644 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1,5 +1,6 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. +from typing import Union from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal from UM.FlameProfiler import pyqtSlot @@ -10,13 +11,18 @@ from UM.Application import Application from UM.Preferences import Preferences from UM.Logger import Logger from UM.Message import Message -from UM.Signal import postponeSignals -import UM.Settings +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.ContainerStack import ContainerStack +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.SettingDefinition import SettingDefinition +from UM.Settings.SettingFunction import SettingFunction +from UM.Settings.Validator import ValidatorState +from UM.Signal import postponeSignals from cura.QualityManager import QualityManager from cura.PrinterOutputDevice import PrinterOutputDevice -from . import ExtruderManager +from cura.Settings.ExtruderManager import ExtruderManager from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -27,8 +33,8 @@ class MachineManager(QObject): def __init__(self, parent = None): super().__init__(parent) - self._active_container_stack = None - self._global_container_stack = None + self._active_container_stack = None # type: ContainerStack + self._global_container_stack = None # type: ContainerStack Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) ## When the global container is changed, active material probably needs to be updated. @@ -37,10 +43,10 @@ class MachineManager(QObject): self.globalContainerChanged.connect(self.activeQualityChanged) self._stacks_have_errors = None - self._empty_variant_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id="empty_variant")[0] - self._empty_material_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id="empty_material")[0] - self._empty_quality_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality")[0] - self._empty_quality_changes_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality_changes")[0] + self._empty_variant_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_variant")[0] + self._empty_material_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_material")[0] + self._empty_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality")[0] + self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality_changes")[0] self._onGlobalContainerChanged() ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) @@ -92,7 +98,7 @@ class MachineManager(QObject): outputDevicesChanged = pyqtSignal() - def _onOutputDevicesChanged(self): + def _onOutputDevicesChanged(self) -> None: for printer_output_device in self._printer_output_devices: printer_output_device.hotendIdChanged.disconnect(self._onHotendIdChanged) printer_output_device.materialIdChanged.disconnect(self._onMaterialIdChanged) @@ -113,16 +119,17 @@ class MachineManager(QObject): @pyqtProperty(int, constant=True) def totalNumberOfSettings(self): - return len(UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys()) + return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys()) - def _onHotendIdChanged(self, index, hotend_id): + def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None: if not self._global_container_stack: return - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type="variant", definition=self._global_container_stack.getBottom().getId(), name=hotend_id) + containers = ContainerRegistry.getInstance().findInstanceContainers(type="variant", definition=self._global_container_stack.getBottom().getId(), name=hotend_id) if containers: # New material ID is known extruder_manager = ExtruderManager.getInstance() - extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId)) + machine_id = self.activeMachineId + extruders = extruder_manager.getMachineExtruders(machine_id) matching_extruder = None for extruder in extruders: if str(index) == extruder.getMetaDataEntry("position"): @@ -143,7 +150,7 @@ class MachineManager(QObject): if self._global_container_stack.getMetaDataEntry("has_machine_materials", False): definition_id = self.activeQualityDefinitionId extruder_manager = ExtruderManager.getInstance() - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material", definition = definition_id, GUID = material_id) + containers = ContainerRegistry.getInstance().findInstanceContainers(type = "material", definition = definition_id, GUID = material_id) if containers: # New material ID is known extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId)) matching_extruder = None @@ -316,11 +323,11 @@ class MachineManager(QObject): # We do need to validate it, because a setting defintions value can be set by a function, which could # be an invalid setting. definition = self._active_container_stack.getSettingDefinition(key) - validator_type = UM.Settings.SettingDefinition.getValidatorForType(definition.type) + validator_type = SettingDefinition.getValidatorForType(definition.type) if validator_type: validator = validator_type(key) changed_validation_state = validator(self._active_container_stack) - if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError): + if changed_validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError): self._stacks_have_errors = True self.stacksValidationChanged.emit() else: @@ -328,20 +335,20 @@ class MachineManager(QObject): self._updateStacksHaveErrors() @pyqtSlot(str) - def setActiveMachine(self, stack_id): + def setActiveMachine(self, stack_id: str) -> None: self.blurSettings.emit() # Ensure no-one has focus. - containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = stack_id) + containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id) if containers: Application.getInstance().setGlobalContainerStack(containers[0]) @pyqtSlot(str, str) - def addMachine(self, name, definition_id): - container_registry = UM.Settings.ContainerRegistry.getInstance() + def addMachine(self, name: str, definition_id: str) -> None: + container_registry = ContainerRegistry.getInstance() definitions = container_registry.findDefinitionContainers(id = definition_id) if definitions: definition = definitions[0] name = self._createUniqueName("machine", "", name, definition.getName()) - new_global_stack = UM.Settings.ContainerStack(name) + new_global_stack = ContainerStack(name) new_global_stack.addMetaDataEntry("type", "machine") container_registry.addContainer(new_global_stack) @@ -349,7 +356,7 @@ class MachineManager(QObject): material_instance_container = self._updateMaterialContainer(definition, variant_instance_container) quality_instance_container = self._updateQualityContainer(definition, variant_instance_container, material_instance_container) - current_settings_instance_container = UM.Settings.InstanceContainer(name + "_current_settings") + current_settings_instance_container = InstanceContainer(name + "_current_settings") current_settings_instance_container.addMetaDataEntry("machine", name) current_settings_instance_container.addMetaDataEntry("type", "user") current_settings_instance_container.setDefinition(definitions[0]) @@ -377,8 +384,8 @@ class MachineManager(QObject): # \param new_name \type{string} Base name, which may not be unique # \param fallback_name \type{string} Name to use when (stripped) new_name is empty # \return \type{string} Name that is unique for the specified type and name/id - def _createUniqueName(self, container_type, current_name, new_name, fallback_name): - return UM.Settings.ContainerRegistry.getInstance().createUniqueName(container_type, current_name, new_name, fallback_name) + def _createUniqueName(self, container_type: str, current_name: str, new_name: str, fallback_name: str) -> str: + return ContainerRegistry.getInstance().createUniqueName(container_type, current_name, new_name, fallback_name) def _checkStacksHaveErrors(self): if self._global_container_stack is None: #No active machine. @@ -468,35 +475,35 @@ class MachineManager(QObject): return bool(self._stacks_have_errors) @pyqtProperty(str, notify = activeStackChanged) - def activeUserProfileId(self): + def activeUserProfileId(self) -> str: if self._active_container_stack: return self._active_container_stack.getTop().getId() return "" @pyqtProperty(str, notify = globalContainerChanged) - def activeMachineName(self): + def activeMachineName(self) -> str: if self._global_container_stack: return self._global_container_stack.getName() return "" @pyqtProperty(str, notify = globalContainerChanged) - def activeMachineId(self): + def activeMachineId(self) -> str: if self._global_container_stack: return self._global_container_stack.getId() return "" @pyqtProperty(str, notify = activeStackChanged) - def activeStackId(self): + def activeStackId(self) -> str: if self._active_container_stack: return self._active_container_stack.getId() return "" @pyqtProperty(str, notify = activeMaterialChanged) - def activeMaterialName(self): + def activeMaterialName(self) -> str: if self._active_container_stack: material = self._active_container_stack.findContainer({"type":"material"}) if material: @@ -526,7 +533,7 @@ class MachineManager(QObject): return result @pyqtProperty(str, notify=activeMaterialChanged) - def activeMaterialId(self): + def activeMaterialId(self) -> str: if self._active_container_stack: material = self._active_container_stack.findContainer({"type": "material"}) if material: @@ -564,13 +571,13 @@ class MachineManager(QObject): quality_changes = self._global_container_stack.findContainer({"type": "quality_changes"}) if quality_changes: value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality_changes.getId()) - if isinstance(value, UM.Settings.SettingFunction): + if isinstance(value, SettingFunction): value = value(self._global_container_stack) return value quality = self._global_container_stack.findContainer({"type": "quality"}) if quality: value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality.getId()) - if isinstance(value, UM.Settings.SettingFunction): + if isinstance(value, SettingFunction): value = value(self._global_container_stack) return value @@ -579,7 +586,7 @@ class MachineManager(QObject): ## Get the Material ID associated with the currently active material # \returns MaterialID (string) if found, empty string otherwise @pyqtProperty(str, notify=activeQualityChanged) - def activeQualityMaterialId(self): + def activeQualityMaterialId(self) -> str: if self._active_container_stack: quality = self._active_container_stack.findContainer({"type": "quality"}) if quality: @@ -669,8 +676,8 @@ class MachineManager(QObject): ## Check if a container is read_only @pyqtSlot(str, result = bool) - def isReadOnly(self, container_id): - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id) + def isReadOnly(self, container_id) -> bool: + containers = ContainerRegistry.getInstance().findInstanceContainers(id = container_id) if not containers or not self._active_container_stack: return True return containers[0].isReadOnly() @@ -693,7 +700,7 @@ class MachineManager(QObject): @pyqtSlot(str) def setActiveMaterial(self, material_id): with postponeSignals(*self._getContainerChangedSignals(), compress = True): - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = material_id) + containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id) if not containers or not self._active_container_stack: return material_container = containers[0] @@ -752,7 +759,7 @@ class MachineManager(QObject): @pyqtSlot(str) def setActiveVariant(self, variant_id): with postponeSignals(*self._getContainerChangedSignals(), compress = True): - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = variant_id) + containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id) if not containers or not self._active_container_stack: return Logger.log("d", "Attempting to change the active variant to %s", variant_id) @@ -778,7 +785,7 @@ class MachineManager(QObject): with postponeSignals(*self._getContainerChangedSignals(), compress = True): self.blurSettings.emit() - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = quality_id) + containers = ContainerRegistry.getInstance().findInstanceContainers(id = quality_id) if not containers or not self._global_container_stack: return @@ -1065,7 +1072,7 @@ class MachineManager(QObject): @pyqtSlot(str, str) def renameMachine(self, machine_id, new_name): - containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = machine_id) + containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) if containers: new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName()) containers[0].setName(new_name) @@ -1078,13 +1085,13 @@ class MachineManager(QObject): ExtruderManager.getInstance().removeMachineExtruders(machine_id) - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id) + containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id) for container in containers: - UM.Settings.ContainerRegistry.getInstance().removeContainer(container.getId()) - UM.Settings.ContainerRegistry.getInstance().removeContainer(machine_id) + ContainerRegistry.getInstance().removeContainer(container.getId()) + ContainerRegistry.getInstance().removeContainer(machine_id) if activate_new_machine: - stacks = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(type = "machine") + stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine") if stacks: Application.getInstance().setGlobalContainerStack(stacks[0]) @@ -1125,7 +1132,7 @@ class MachineManager(QObject): # \returns DefinitionID (string) if found, None otherwise @pyqtSlot(str, result = str) def getDefinitionByMachineId(self, machine_id): - containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id=machine_id) + containers = ContainerRegistry.getInstance().findContainerStacks(id=machine_id) if containers: return containers[0].getBottom().getId() @@ -1136,13 +1143,13 @@ class MachineManager(QObject): def _updateVariantContainer(self, definition): if not definition.getMetaDataEntry("has_variants"): return self._empty_variant_container - machine_definition_id = UM.Application.getInstance().getMachineManager().getQualityDefinitionId(definition) + machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(definition) containers = [] preferred_variant = definition.getMetaDataEntry("preferred_variant") if preferred_variant: - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id, id = preferred_variant) + containers = ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id, id = preferred_variant) if not containers: - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id) + containers = ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id) if containers: return containers[0] @@ -1170,23 +1177,23 @@ class MachineManager(QObject): if preferred_material: search_criteria["id"] = preferred_material - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) if containers: return containers[0] - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) if "variant" in search_criteria or "id" in search_criteria: # If a material by this name can not be found, try a wider set of search criteria search_criteria.pop("variant", None) search_criteria.pop("id", None) - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + + containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) if containers: return containers[0] Logger.log("w", "Unable to find a material container with provided criteria, returning an empty one instead.") return self._empty_material_container def _updateQualityContainer(self, definition, variant_container, material_container = None, preferred_quality_name = None): - container_registry = UM.Settings.ContainerRegistry.getInstance() + container_registry = ContainerRegistry.getInstance() search_criteria = { "type": "quality" } if definition.getMetaDataEntry("has_machine_quality"): @@ -1269,7 +1276,7 @@ class MachineManager(QObject): # \param preferred_quality_changes_name The name of the quality-changes to # pick, if any such quality-changes profile is available. def _updateQualityChangesContainer(self, quality_type, preferred_quality_changes_name = None): - container_registry = UM.Settings.ContainerRegistry.getInstance() # Cache. + container_registry = ContainerRegistry.getInstance() # Cache. search_criteria = { "type": "quality_changes" } search_criteria["quality"] = quality_type diff --git a/cura/Settings/MachineNameValidator.py b/cura/Settings/MachineNameValidator.py index 34b6351144..68782a2148 100644 --- a/cura/Settings/MachineNameValidator.py +++ b/cura/Settings/MachineNameValidator.py @@ -7,8 +7,8 @@ import os #For statvfs. import urllib #To escape machine names for how they're saved to file. import UM.Resources -import UM.Settings.ContainerRegistry -import UM.Settings.InstanceContainer +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.InstanceContainer import InstanceContainer ## Are machine names valid? # @@ -22,7 +22,7 @@ class MachineNameValidator(QObject): filename_max_length = os.statvfs(UM.Resources.getDataStoragePath()).f_namemax except AttributeError: #Doesn't support statvfs. Probably because it's not a Unix system. filename_max_length = 255 #Assume it's Windows on NTFS. - machine_name_max_length = filename_max_length - len("_current_settings.") - len(UM.Settings.ContainerRegistry.getMimeTypeForContainer(UM.Settings.InstanceContainer).preferredSuffix) + machine_name_max_length = filename_max_length - len("_current_settings.") - len(ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix) # Characters that urllib.parse.quote_plus escapes count for 12! So now # we must devise a regex that allows only 12 normal characters or 1 # special character, and that up to [machine_name_max_length / 12] times. @@ -45,7 +45,7 @@ class MachineNameValidator(QObject): except AttributeError: #Doesn't support statvfs. Probably because it's not a Unix system. filename_max_length = 255 #Assume it's Windows on NTFS. escaped_name = urllib.parse.quote_plus(name) - current_settings_filename = escaped_name + "_current_settings." + UM.Settings.ContainerRegistry.getMimeTypeForContainer(UM.Settings.InstanceContainer).preferredSuffix + current_settings_filename = escaped_name + "_current_settings." + ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix if len(current_settings_filename) > filename_max_length: return QValidator.Invalid diff --git a/cura/Settings/MaterialSettingsVisibilityHandler.py b/cura/Settings/MaterialSettingsVisibilityHandler.py index 43e5cf2b0a..5aa5cc02ab 100644 --- a/cura/Settings/MaterialSettingsVisibilityHandler.py +++ b/cura/Settings/MaterialSettingsVisibilityHandler.py @@ -1,9 +1,9 @@ # Copyright (c) 2016 Ultimaker B.V. # Uranium is released under the terms of the AGPLv3 or higher. -import UM.Settings.Models +from UM.Settings.Models.SettingVisibilityHandler import SettingVisibilityHandler -class MaterialSettingsVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler): +class MaterialSettingsVisibilityHandler(SettingVisibilityHandler): def __init__(self, parent = None, *args, **kwargs): super().__init__(parent = parent, *args, **kwargs) diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index c03e074053..404bb569a5 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -38,7 +38,7 @@ class ProfilesModel(InstanceContainersModel): ProfilesModel.__instance = cls() return ProfilesModel.__instance - __instance = None + __instance = None # type: "ProfilesModel" ## Fetch the list of containers to display. # diff --git a/cura/Settings/QualitySettingsModel.py b/cura/Settings/QualitySettingsModel.py index e6499ae8d0..a3dd6925e5 100644 --- a/cura/Settings/QualitySettingsModel.py +++ b/cura/Settings/QualitySettingsModel.py @@ -5,15 +5,14 @@ import collections from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt -import UM.Application import UM.Logger import UM.Qt -import UM.Settings -from UM.i18n import i18nCatalog +from UM.Application import Application from UM.Settings.ContainerRegistry import ContainerRegistry - import os +from UM.i18n import i18nCatalog + class QualitySettingsModel(UM.Qt.ListModel.ListModel): KeyRole = Qt.UserRole + 1 @@ -27,7 +26,7 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): def __init__(self, parent = None): super().__init__(parent = parent) - self._container_registry = UM.Settings.ContainerRegistry.getInstance() + self._container_registry = ContainerRegistry.getInstance() self._extruder_id = None self._extruder_definition_id = None @@ -94,7 +93,7 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): items = [] settings = collections.OrderedDict() - definition_container = UM.Application.getInstance().getGlobalContainerStack().getBottom() + definition_container = Application.getInstance().getGlobalContainerStack().getBottom() containers = self._container_registry.findInstanceContainers(id = self._quality_id) if not containers: @@ -122,7 +121,7 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): quality_container = quality_container[0] quality_type = quality_container.getMetaDataEntry("quality_type") - definition_id = UM.Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition()) + definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition()) definition = quality_container.getDefinition() # Check if the definition container has a translation file. @@ -169,7 +168,7 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): if self._extruder_definition_id != "": extruder_definitions = self._container_registry.findDefinitionContainers(id = self._extruder_definition_id) if extruder_definitions: - criteria["extruder"] = UM.Application.getInstance().getMachineManager().getQualityDefinitionId(extruder_definitions[0]) + criteria["extruder"] = Application.getInstance().getMachineManager().getQualityDefinitionId(extruder_definitions[0]) criteria["name"] = quality_changes_container.getName() else: criteria["extruder"] = None @@ -178,7 +177,7 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): if changes: containers.extend(changes) - global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + global_container_stack = Application.getInstance().getGlobalContainerStack() is_multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 current_category = "" diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index 2b1201fb37..2f81526813 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -3,9 +3,7 @@ from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal from UM.FlameProfiler import pyqtSlot -import UM.Settings from UM.Application import Application -import cura.Settings from UM.Logger import Logger @@ -14,6 +12,12 @@ from UM.Logger import Logger # because some profiles tend to have 'hardcoded' values that break our inheritance. A good example of that are the # speed settings. If all the children of print_speed have a single value override, changing the speed won't # actually do anything, as only the 'leaf' settings are used by the engine. +from UM.Settings.ContainerStack import ContainerStack +from UM.Settings.SettingFunction import SettingFunction +from UM.Settings.SettingInstance import InstanceState + +from cura.Settings.ExtruderManager import ExtruderManager + class SettingInheritanceManager(QObject): def __init__(self, parent = None): super().__init__(parent) @@ -23,7 +27,7 @@ class SettingInheritanceManager(QObject): self._active_container_stack = None self._onGlobalContainerChanged() - cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) + ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) self._onActiveExtruderChanged() settingsWithIntheritanceChanged = pyqtSignal() @@ -46,7 +50,7 @@ class SettingInheritanceManager(QObject): multi_extrusion = self._global_container_stack.getProperty("machine_extruder_count", "value") > 1 if not multi_extrusion: return self._settings_with_inheritance_warning - extruder = cura.Settings.ExtruderManager.getInstance().getExtruderStack(extruder_index) + extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) if not extruder: Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index) return [] @@ -73,7 +77,7 @@ class SettingInheritanceManager(QObject): self._update() def _onActiveExtruderChanged(self): - new_active_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack() + new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack() if not new_active_stack: new_active_stack = self._global_container_stack @@ -139,14 +143,14 @@ class SettingInheritanceManager(QObject): return self._settings_with_inheritance_warning ## Check if a setting has an inheritance function that is overwritten - def _settingIsOverwritingInheritance(self, key, stack = None): + def _settingIsOverwritingInheritance(self, key: str, stack: ContainerStack = None) -> bool: has_setting_function = False if not stack: stack = self._active_container_stack containers = [] ## Check if the setting has a user state. If not, it is never overwritten. - has_user_state = stack.getProperty(key, "state") == UM.Settings.InstanceState.User + has_user_state = stack.getProperty(key, "state") == InstanceState.User if not has_user_state: return False @@ -155,7 +159,7 @@ class SettingInheritanceManager(QObject): return False ## Also check if the top container is not a setting function (this happens if the inheritance is restored). - if isinstance(stack.getTop().getProperty(key, "value"), UM.Settings.SettingFunction): + if isinstance(stack.getTop().getProperty(key, "value"), SettingFunction): return False ## Mash all containers for all the stacks together. @@ -170,7 +174,7 @@ class SettingInheritanceManager(QObject): continue if value is not None: # If a setting doesn't use any keys, it won't change it's value, so treat it as if it's a fixed value - has_setting_function = isinstance(value, UM.Settings.SettingFunction) + has_setting_function = isinstance(value, SettingFunction) if has_setting_function: for setting_key in value.getUsedSettingKeys(): if setting_key in self._active_container_stack.getAllKeys(): diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index d38dac565b..d5f4ef7b14 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -10,10 +10,10 @@ from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry import UM.Logger -import cura.Settings - from UM.Application import Application +from cura.Settings.ExtruderManager import ExtruderManager + ## A decorator that adds a container stack to a Node. This stack should be queried for all settings regarding # the linked node. The Stack in question will refer to the global stack (so that settings that are not defined by # this stack still resolve. @@ -29,8 +29,8 @@ class SettingOverrideDecorator(SceneNodeDecorator): self._instance = InstanceContainer(container_id = "SettingOverrideInstanceContainer") self._stack.addContainer(self._instance) - if cura.Settings.ExtruderManager.getInstance().extruderCount > 1: - self._extruder_stack = cura.Settings.ExtruderManager.getInstance().getExtruderStack(0).getId() + if ExtruderManager.getInstance().extruderCount > 1: + self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() else: self._extruder_stack = None diff --git a/cura/Settings/__init__.py b/cura/Settings/__init__.py index 0298ac886d..c00ee15ebf 100644 --- a/cura/Settings/__init__.py +++ b/cura/Settings/__init__.py @@ -1,18 +1,2 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. - -from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler -from .ContainerManager import ContainerManager -from .ContainerSettingsModel import ContainerSettingsModel -from .CuraContainerRegistry import CuraContainerRegistry -from .ExtruderManager import ExtruderManager -from .ExtrudersModel import ExtrudersModel -from .MachineManager import MachineManager -from .MachineNameValidator import MachineNameValidator -from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler -from .SettingOverrideDecorator import SettingOverrideDecorator -from .QualitySettingsModel import QualitySettingsModel -from .SettingInheritanceManager import SettingInheritanceManager -from .ProfilesModel import ProfilesModel -from .QualityAndUserProfilesModel import QualityAndUserProfilesModel -from .UserProfilesModel import UserProfilesModel diff --git a/cura_app.py b/cura_app.py index 653f56d34d..633110eff8 100755 --- a/cura_app.py +++ b/cura_app.py @@ -55,7 +55,7 @@ if Platform.isWindows() and hasattr(sys, "frozen"): sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w") # Force an instance of CuraContainerRegistry to be created and reused later. -cura.Settings.CuraContainerRegistry.getInstance() +cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance() # This prestart up check is needed to determine if we should start the application at all. if not cura.CuraApplication.CuraApplication.preStartUp(): diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 0f4ab532fa..fde4d6ab21 100644 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -11,15 +11,16 @@ from UM.Math.Vector import Vector from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshReader import MeshReader from UM.Scene.GroupDecorator import GroupDecorator -import UM.Application from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator from UM.Application import Application from cura.Settings.ExtruderManager import ExtruderManager from cura.QualityManager import QualityManager from UM.Scene.SceneNode import SceneNode +MYPY = False try: - import xml.etree.cElementTree as ET + if not MYPY: + import xml.etree.cElementTree as ET except ImportError: Logger.log("w", "Unable to load cElementTree, switching to slower version") import xml.etree.ElementTree as ET @@ -201,7 +202,7 @@ class ThreeMFReader(MeshReader): build_item_node.setTransformation(transform_matrix) - global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + global_container_stack = Application.getInstance().getGlobalContainerStack() # Create a transformation Matrix to convert from 3mf worldspace into ours. # First step: flip the y and z axis. diff --git a/plugins/3MFReader/__init__.py b/plugins/3MFReader/__init__.py index 3e05cb8dc7..cb4f9b9761 100644 --- a/plugins/3MFReader/__init__.py +++ b/plugins/3MFReader/__init__.py @@ -1,16 +1,16 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. +from typing import Dict from . import ThreeMFReader from . import ThreeMFWorkspaceReader from UM.i18n import i18nCatalog -import UM.Platform +from UM.Platform import Platform catalog = i18nCatalog("cura") - -def getMetaData(): +def getMetaData() -> Dict: # Workarround for osx not supporting double file extensions correclty. - if UM.Platform.isOSX(): + if Platform.isOSX(): workspace_extension = "3mf" else: workspace_extension = "curaproject.3mf" diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 764c73b1f0..eb065cecdc 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -7,8 +7,10 @@ from UM.Logger import Logger from UM.Math.Matrix import Matrix from UM.Application import Application +MYPY = False try: - import xml.etree.cElementTree as ET + if not MYPY: + import xml.etree.cElementTree as ET except ImportError: Logger.log("w", "Unable to load cElementTree, switching to slower version") import xml.etree.ElementTree as ET diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index a6c544c943..a185763848 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -15,12 +15,8 @@ from UM.Platform import Platform from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator -import cura.Settings - -from cura.OneAtATimeIterator import OneAtATimeIterator from cura.Settings.ExtruderManager import ExtruderManager from . import ProcessSlicedLayersJob -from . import ProcessGCodeJob from . import StartSliceJob import os @@ -84,7 +80,7 @@ class CuraEngineBackend(Backend): self._onGlobalStackChanged() self._active_extruder_stack = None - cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) + ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) self._onActiveExtruderChanged() # Listeners for receiving messages from the back-end. @@ -570,7 +566,7 @@ class CuraEngineBackend(Backend): if self._active_extruder_stack: self._active_extruder_stack.containersChanged.disconnect(self._onChanged) - self._active_extruder_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack() + self._active_extruder_stack = ExtruderManager.getInstance().getActiveExtruderStack() if self._active_extruder_stack: self._active_extruder_stack.containersChanged.connect(self._onChanged) diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index c4e9554b2c..0d706f59b8 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -8,6 +8,8 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.Application import Application from UM.Mesh.MeshData import MeshData +from UM.Preferences import Preferences +from UM.View.GL.OpenGLContext import OpenGLContext from UM.Message import Message from UM.i18n import i18nCatalog @@ -15,6 +17,7 @@ from UM.Logger import Logger from UM.Math.Vector import Vector +from cura.Settings.ExtruderManager import ExtruderManager from cura import LayerDataBuilder from cura import LayerDataDecorator from cura import LayerPolygon @@ -24,6 +27,17 @@ from time import time catalog = i18nCatalog("cura") +## Return a 4-tuple with floats 0-1 representing the html color code +# +# \param color_code html color code, i.e. "#FF0000" -> red +def colorCodeToRGBA(color_code): + return [ + int(color_code[1:3], 16) / 255, + int(color_code[3:5], 16) / 255, + int(color_code[5:7], 16) / 255, + 1.0] + + class ProcessSlicedLayersJob(Job): def __init__(self, layers): super().__init__() @@ -92,7 +106,6 @@ class ProcessSlicedLayersJob(Job): layer_data.addLayer(abs_layer_number) this_layer = layer_data.getLayer(abs_layer_number) layer_data.setLayerHeight(abs_layer_number, layer.height) - layer_data.setLayerThickness(abs_layer_number, layer.thickness) for p in range(layer.repeatedMessageCount("path_segment")): polygon = layer.getRepeatedMessage("path_segment", p) @@ -110,23 +123,28 @@ class ProcessSlicedLayersJob(Job): line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. - + + # In the future, line_thicknesses should be given by CuraEngine as well. + # Currently the infill layer thickness also translates to line width + line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4") + line_thicknesses[:] = layer.thickness / 1000 # from micrometer to millimeter + # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) if polygon.point_type == 0: # Point2D new_points[:, 0] = points[:, 0] - new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation + new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation new_points[:, 2] = -points[:, 1] else: # Point3D new_points[:, 0] = points[:, 0] new_points[:, 1] = points[:, 2] new_points[:, 2] = -points[:, 1] - this_poly = LayerPolygon.LayerPolygon(layer_data, extruder, line_types, new_points, line_widths) + this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses) this_poly.buildCache() - + this_layer.polygons.append(this_poly) Job.yieldThread() @@ -144,7 +162,35 @@ class ProcessSlicedLayersJob(Job): self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data - layer_mesh = layer_data.build() + + # Find out colors per extruder + global_container_stack = Application.getInstance().getGlobalContainerStack() + manager = ExtruderManager.getInstance() + extruders = list(manager.getMachineExtruders(global_container_stack.getId())) + if extruders: + material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32) + for extruder in extruders: + material = extruder.findContainer({"type": "material"}) + position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position + color_code = material.getMetaDataEntry("color_code") + color = colorCodeToRGBA(color_code) + material_color_map[position, :] = color + else: + # Single extruder via global stack. + material_color_map = numpy.zeros((1, 4), dtype=numpy.float32) + material = global_container_stack.findContainer({"type": "material"}) + color_code = material.getMetaDataEntry("color_code") + if color_code is None: # not all stacks have a material color + color_code = "#e0e000" + color = colorCodeToRGBA(color_code) + material_color_map[0, :] = color + + # We have to scale the colors for compatibility mode + if OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")): + line_type_brightness = 0.5 # for compatibility mode + else: + line_type_brightness = 1.0 + layer_mesh = layer_data.build(material_color_map, line_type_brightness) if self._abort_requested: if self._progress: diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index ade86e231d..c8cbbe8040 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -16,8 +16,7 @@ from UM.Settings.Validator import ValidatorState from UM.Settings.SettingRelation import RelationType from cura.OneAtATimeIterator import OneAtATimeIterator - -import cura.Settings +from cura.Settings.ExtruderManager import ExtruderManager class StartJobResult(IntEnum): Finished = 1 @@ -84,7 +83,7 @@ class StartSliceJob(Job): self.setResult(StartJobResult.BuildPlateError) return - for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()): + for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): material = extruder_stack.findContainer({"type": "material"}) if material: if material.getMetaDataEntry("compatible") == False: @@ -149,7 +148,7 @@ class StartSliceJob(Job): self._buildGlobalSettingsMessage(stack) self._buildGlobalInheritsStackMessage(stack) - for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()): + for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): self._buildExtruderMessage(extruder_stack) for group in object_groups: diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index 34ea91a727..290b66343e 100644 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -99,8 +99,11 @@ class GCodeReader(MeshReader): count = len(path) line_types = numpy.empty((count - 1, 1), numpy.int32) line_widths = numpy.empty((count - 1, 1), numpy.float32) + line_thicknesses = numpy.empty((count - 1, 1), numpy.float32) # TODO: need to calculate actual line width based on E values line_widths[:, 0] = 0.4 + # TODO: need to calculate actual line heights + line_thicknesses[:, 0] = 0.2 points = numpy.empty((count, 3), numpy.float32) i = 0 for point in path: @@ -113,7 +116,7 @@ class GCodeReader(MeshReader): line_widths[i - 1] = 0.2 i += 1 - this_poly = LayerPolygon(self._layer_data_builder, self._extruder, line_types, points, line_widths) + this_poly = LayerPolygon(self._extruder, line_types, points, line_widths, line_thicknesses) this_poly.buildCache() this_layer.polygons.append(this_poly) @@ -276,7 +279,10 @@ class GCodeReader(MeshReader): self._layer += 1 current_path.clear() - layer_mesh = self._layer_data_builder.build() + material_color_map = numpy.zeros((10, 4), dtype = numpy.float32) + material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0] + material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0] + layer_mesh = self._layer_data_builder.build(material_color_map) decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) scene_node.addDecorator(decorator) diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index d503f547b0..162738f073 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -4,13 +4,10 @@ from UM.Mesh.MeshWriter import MeshWriter from UM.Logger import Logger from UM.Application import Application -import UM.Settings.ContainerRegistry - -from cura.CuraApplication import CuraApplication -from cura.Settings.ExtruderManager import ExtruderManager - from UM.Settings.InstanceContainer import InstanceContainer +from cura.Settings.ExtruderManager import ExtruderManager + import re #For escaping characters in the settings. import json import copy diff --git a/plugins/LayerView/LayerPass.py b/plugins/LayerView/LayerPass.py index 9bc67efc58..9ba245489a 100644 --- a/plugins/LayerView/LayerPass.py +++ b/plugins/LayerView/LayerPass.py @@ -14,6 +14,7 @@ from UM.View.GL.OpenGL import OpenGL from cura.Settings.ExtruderManager import ExtruderManager + import os.path ## RenderPass used to display g-code paths. @@ -28,15 +29,39 @@ class LayerPass(RenderPass): self._extruder_manager = ExtruderManager.getInstance() self._layer_view = None + self._compatibility_mode = None def setLayerView(self, layerview): - self._layerview = layerview + self._layer_view = layerview + self._compatibility_mode = layerview.getCompatibilityMode() def render(self): if not self._layer_shader: - self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), "layers.shader")) + if self._compatibility_mode: + shader_filename = "layers.shader" + else: + shader_filename = "layers3d.shader" + self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), shader_filename)) # Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers) self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex))) + if self._layer_view: + self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getLayerViewType()) + self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities()) + self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves()) + self._layer_shader.setUniformValue("u_show_support", self._layer_view.getShowSupport()) + self._layer_shader.setUniformValue("u_show_adhesion", self._layer_view.getShowAdhesion()) + self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin()) + self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill()) + else: + #defaults + self._layer_shader.setUniformValue("u_layer_view_type", 1) + self._layer_shader.setUniformValue("u_extruder_opacity", [1, 1, 1, 1]) + self._layer_shader.setUniformValue("u_show_travel_moves", 0) + self._layer_shader.setUniformValue("u_show_support", 1) + self._layer_shader.setUniformValue("u_show_adhesion", 1) + self._layer_shader.setUniformValue("u_show_skin", 1) + self._layer_shader.setUniformValue("u_show_infill", 1) + if not self._tool_handle_shader: self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader")) @@ -55,13 +80,15 @@ class LayerPass(RenderPass): continue # Render all layers below a certain number as line mesh instead of vertices. - if self._layerview._current_layer_num - self._layerview._solid_layers > -1 and not self._layerview._only_show_top_layers: + if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())): start = 0 end = 0 element_counts = layer_data.getElementCounts() for layer, counts in element_counts.items(): - if layer + self._layerview._solid_layers > self._layerview._current_layer_num: + if layer > self._layer_view._current_layer_num: break + if self._layer_view._minimum_layer_num > layer: + start += counts end += counts # This uses glDrawRangeElements internally to only draw a certain range of lines. @@ -72,11 +99,11 @@ class LayerPass(RenderPass): # Create a new batch that is not range-limited batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid) - if self._layerview._current_layer_mesh: - batch.addItem(node.getWorldTransformation(), self._layerview._current_layer_mesh) + if self._layer_view._current_layer_mesh: + batch.addItem(node.getWorldTransformation(), self._layer_view._current_layer_mesh) - if self._layerview._current_layer_jumps: - batch.addItem(node.getWorldTransformation(), self._layerview._current_layer_jumps) + if self._layer_view._current_layer_jumps: + batch.addItem(node.getWorldTransformation(), self._layer_view._current_layer_jumps) if len(batch.items) > 0: batch.render(self._scene.getActiveCamera()) diff --git a/plugins/LayerView/LayerView.py b/plugins/LayerView/LayerView.py index df0c92f87e..77c17a0aea 100644 --- a/plugins/LayerView/LayerView.py +++ b/plugins/LayerView/LayerView.py @@ -13,15 +13,15 @@ from UM.Mesh.MeshBuilder import MeshBuilder from UM.Job import Job from UM.Preferences import Preferences from UM.Logger import Logger -from UM.Scene.SceneNode import SceneNode -from UM.View.RenderBatch import RenderBatch from UM.View.GL.OpenGL import OpenGL from UM.Message import Message from UM.Application import Application +from UM.View.GL.OpenGLContext import OpenGLContext from cura.ConvexHullNode import ConvexHullNode +from cura.Settings.ExtruderManager import ExtruderManager -from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication from . import LayerViewProxy @@ -36,11 +36,16 @@ import os.path ## View used to display g-code paths. class LayerView(View): + # Must match LayerView.qml + LAYER_VIEW_TYPE_MATERIAL_TYPE = 0 + LAYER_VIEW_TYPE_LINE_TYPE = 1 + def __init__(self): super().__init__() self._max_layers = 0 self._current_layer_num = 0 + self._minimum_layer_num = 0 self._current_layer_mesh = None self._current_layer_jumps = None self._top_layers_job = None @@ -60,17 +65,30 @@ class LayerView(View): self._proxy = LayerViewProxy.LayerViewProxy() self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) + self._resetSettings() self._legend_items = None Preferences.getInstance().addPreference("view/top_layer_count", 5) Preferences.getInstance().addPreference("view/only_show_top_layers", False) + Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers")) + self._compatibility_mode = True # for safety self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled")) + def _resetSettings(self): + self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed + self._extruder_count = 0 + self._extruder_opacity = [1.0, 1.0, 1.0, 1.0] + self._show_travel_moves = 0 + self._show_support = 1 + self._show_adhesion = 1 + self._show_skin = 1 + self._show_infill = 1 + def getActivity(self): return self._activity @@ -79,6 +97,7 @@ class LayerView(View): # Currently the RenderPass constructor requires a size > 0 # This should be fixed in RenderPass's constructor. self._layer_pass = LayerPass.LayerPass(1, 1) + self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")) self._layer_pass.setLayerView(self) self.getRenderer().addRenderPass(self._layer_pass) return self._layer_pass @@ -86,6 +105,9 @@ class LayerView(View): def getCurrentLayer(self): return self._current_layer_num + def getMinimumLayer(self): + return self._minimum_layer_num + def _onSceneChanged(self, node): self.calculateMaxLayers() @@ -136,6 +158,79 @@ class LayerView(View): self.currentLayerNumChanged.emit() + def setMinimumLayer(self, value): + if self._minimum_layer_num != value: + self._minimum_layer_num = value + if self._minimum_layer_num < 0: + self._minimum_layer_num = 0 + + self._startUpdateTopLayers() + + self.currentLayerNumChanged.emit() + + ## Set the layer view type + # + # \param layer_view_type integer as in LayerView.qml and this class + def setLayerViewType(self, layer_view_type): + self._layer_view_type = layer_view_type + self.currentLayerNumChanged.emit() + + ## Return the layer view type, integer as in LayerView.qml and this class + def getLayerViewType(self): + return self._layer_view_type + + ## Set the extruder opacity + # + # \param extruder_nr 0..3 + # \param opacity 0.0 .. 1.0 + def setExtruderOpacity(self, extruder_nr, opacity): + self._extruder_opacity[extruder_nr] = opacity + self.currentLayerNumChanged.emit() + + def getExtruderOpacities(self): + return self._extruder_opacity + + def setShowTravelMoves(self, show): + self._show_travel_moves = show + self.currentLayerNumChanged.emit() + + def getShowTravelMoves(self): + return self._show_travel_moves + + def setShowSupport(self, show): + self._show_support = show + self.currentLayerNumChanged.emit() + + def getShowSupport(self): + return self._show_support + + def setShowAdhesion(self, show): + self._show_adhesion = show + self.currentLayerNumChanged.emit() + + def getShowAdhesion(self): + return self._show_adhesion + + def setShowSkin(self, show): + self._show_skin = show + self.currentLayerNumChanged.emit() + + def getShowSkin(self): + return self._show_skin + + def setShowInfill(self, show): + self._show_infill = show + self.currentLayerNumChanged.emit() + + def getShowInfill(self): + return self._show_infill + + def getCompatibilityMode(self): + return self._compatibility_mode + + def getExtruderCount(self): + return self._extruder_count + def calculateMaxLayers(self): scene = self.getController().getScene() self._activity = True @@ -167,6 +262,8 @@ class LayerView(View): maxLayersChanged = Signal() currentLayerNumChanged = Signal() + globalStackChanged = Signal() + preferencesChanged = Signal() ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created # as this caused some issues. @@ -176,6 +273,12 @@ class LayerView(View): def endRendering(self): pass + def enableLegend(self): + Application.getInstance().setViewLegendItems(self._getLegendItems()) + + def disableLegend(self): + Application.getInstance().setViewLegendItems([]) + def event(self, event): modifiers = QApplication.keyboardModifiers() ctrl_is_active = modifiers == Qt.ControlModifier @@ -208,7 +311,8 @@ class LayerView(View): self._old_composite_shader = self._composite_pass.getCompositeShader() self._composite_pass.setCompositeShader(self._layerview_composite_shader) - Application.getInstance().setViewLegendItems(self._getLegendItems()) + if self.getLayerViewType() == self.LAYER_VIEW_TYPE_LINE_TYPE: + self.enableLegend() elif event.type == Event.ViewDeactivateEvent: self._wireprint_warning_message.hide() @@ -219,7 +323,7 @@ class LayerView(View): self._composite_pass.setLayerBindings(self._old_layer_bindings) self._composite_pass.setCompositeShader(self._old_composite_shader) - Application.getInstance().setViewLegendItems([]) + self.disableLegend() def _onGlobalStackChanged(self): if self._global_container_stack: @@ -227,7 +331,9 @@ class LayerView(View): self._global_container_stack = Application.getInstance().getGlobalContainerStack() if self._global_container_stack: self._global_container_stack.propertyChanged.connect(self._onPropertyChanged) + self._extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") self._onPropertyChanged("wireframe_enabled", "value") + self.globalStackChanged.emit() else: self._wireprint_warning_message.hide() @@ -239,6 +345,9 @@ class LayerView(View): self._wireprint_warning_message.hide() def _startUpdateTopLayers(self): + if not self._compatibility_mode: + return + if self._top_layers_job: self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh) self._top_layers_job.cancel() @@ -262,13 +371,16 @@ class LayerView(View): self._top_layers_job = None def _onPreferencesChanged(self, preference): - if preference != "view/top_layer_count" and preference != "view/only_show_top_layers": + if preference not in {"view/top_layer_count", "view/only_show_top_layers", "view/force_layer_view_compatibility_mode"}: return self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers")) + self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool( + Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")) self._startUpdateTopLayers() + self.preferencesChanged.emit() def _getLegendItems(self): if self._legend_items is None: diff --git a/plugins/LayerView/LayerView.qml b/plugins/LayerView/LayerView.qml index 82728d2a1e..7713b796a9 100644 --- a/plugins/LayerView/LayerView.qml +++ b/plugins/LayerView/LayerView.qml @@ -13,13 +13,38 @@ Item width: UM.Theme.getSize("button").width height: UM.Theme.getSize("slider_layerview_size").height + Slider + { + id: sliderMinimumLayer + width: UM.Theme.getSize("slider_layerview_size").width + height: UM.Theme.getSize("slider_layerview_size").height + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("slider_layerview_margin").width * 0.2 + orientation: Qt.Vertical + minimumValue: 0; + maximumValue: UM.LayerView.numLayers-1; + stepSize: 1 + + property real pixelsPerStep: ((height - UM.Theme.getSize("slider_handle").height) / (maximumValue - minimumValue)) * stepSize; + + value: UM.LayerView.minimumLayer + onValueChanged: { + UM.LayerView.setMinimumLayer(value) + if (value > UM.LayerView.currentLayer) { + UM.LayerView.setCurrentLayer(value); + } + } + + style: UM.Theme.styles.slider; + } + Slider { id: slider width: UM.Theme.getSize("slider_layerview_size").width height: UM.Theme.getSize("slider_layerview_size").height anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("slider_layerview_margin").width/2 + anchors.leftMargin: UM.Theme.getSize("slider_layerview_margin").width * 0.8 orientation: Qt.Vertical minimumValue: 0; maximumValue: UM.LayerView.numLayers; @@ -28,7 +53,12 @@ Item property real pixelsPerStep: ((height - UM.Theme.getSize("slider_handle").height) / (maximumValue - minimumValue)) * stepSize; value: UM.LayerView.currentLayer - onValueChanged: UM.LayerView.setCurrentLayer(value) + onValueChanged: { + UM.LayerView.setCurrentLayer(value); + if (value < UM.LayerView.minimumLayer) { + UM.LayerView.setMinimumLayer(value); + } + } style: UM.Theme.styles.slider; @@ -96,6 +126,7 @@ Item } Rectangle { + id: slider_background anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter z: slider.z - 1 @@ -114,4 +145,139 @@ Item } } } + + Rectangle { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.top: slider_background.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + width: UM.Theme.getSize("slider_layerview_background").width * 3 + height: slider.height + UM.Theme.getSize("default_margin").height * 2 + color: UM.Theme.getColor("tool_panel_background"); + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + + ListModel // matches LayerView.py + { + id: layerViewTypes + ListElement { + text: "Material color" + type_id: 0 + } + ListElement { + text: "Line type" + type_id: 1 // these ids match the switching in the shader + } + } + + ComboBox + { + id: layerTypeCombobox + anchors.top: parent.top + anchors.left: parent.left + model: layerViewTypes + visible: !UM.LayerView.compatibilityMode + onActivated: { + var type_id = layerViewTypes.get(index).type_id; + UM.LayerView.setLayerViewType(type_id); + if (type_id == 1) { + // Line type + UM.LayerView.enableLegend(); + } else { + UM.LayerView.disableLegend(); + } + } + onModelChanged: { + currentIndex = UM.LayerView.getLayerViewType(); + } + } + + Label + { + id: compatibilityModeLabel + anchors.top: parent.top + anchors.left: parent.left + text: catalog.i18nc("@label","Compatibility mode") + visible: UM.LayerView.compatibilityMode + } + + ColumnLayout { + id: view_settings + anchors.top: UM.LayerView.compatibilityMode ? compatibilityModeLabel.bottom : layerTypeCombobox.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + + CheckBox { + checked: true + onClicked: { + UM.LayerView.setExtruderOpacity(0, checked ? 1.0 : 0.0); + } + text: "Extruder 1" + visible: !UM.LayerView.compatibilityMode && (UM.LayerView.getExtruderCount >= 1) + } + CheckBox { + checked: true + onClicked: { + UM.LayerView.setExtruderOpacity(1, checked ? 1.0 : 0.0); + } + text: "Extruder 2" + visible: !UM.LayerView.compatibilityMode && (UM.LayerView.getExtruderCount >= 2) + } + CheckBox { + checked: true + onClicked: { + UM.LayerView.setExtruderOpacity(2, checked ? 1.0 : 0.0); + } + text: "Extruder 3" + visible: !UM.LayerView.compatibilityMode && (UM.LayerView.getExtruderCount >= 3) + } + CheckBox { + checked: true + onClicked: { + UM.LayerView.setExtruderOpacity(3, checked ? 1.0 : 0.0); + } + text: "Extruder 4" + visible: !UM.LayerView.compatibilityMode && (UM.LayerView.getExtruderCount >= 4) + } + Label { + text: "Other extruders always visible" + visible: !UM.LayerView.compatibilityMode && (UM.LayerView.getExtruderCount >= 5) + } + CheckBox { + onClicked: { + UM.LayerView.setShowTravelMoves(checked ? 1 : 0); + } + text: "Show travel moves" + } + CheckBox { + checked: true + onClicked: { + UM.LayerView.setShowSupport(checked ? 1 : 0); + } + text: "Show support" + } + CheckBox { + checked: true + onClicked: { + UM.LayerView.setShowAdhesion(checked ? 1 : 0); + } + text: "Show adhesion" + } + CheckBox { + checked: true + onClicked: { + UM.LayerView.setShowSkin(checked ? 1 : 0); + } + text: "Show skin" + } + CheckBox { + checked: true + onClicked: { + UM.LayerView.setShowInfill(checked ? 1 : 0); + } + text: "Show infill" + } + } + } } diff --git a/plugins/LayerView/LayerViewProxy.py b/plugins/LayerView/LayerViewProxy.py index dd5ba5e2e0..b3a1cca87d 100644 --- a/plugins/LayerView/LayerViewProxy.py +++ b/plugins/LayerView/LayerViewProxy.py @@ -16,6 +16,8 @@ class LayerViewProxy(QObject): currentLayerChanged = pyqtSignal() maxLayersChanged = pyqtSignal() activityChanged = pyqtSignal() + globalStackChanged = pyqtSignal() + preferencesChanged = pyqtSignal() @pyqtProperty(bool, notify = activityChanged) def getLayerActivity(self): @@ -36,6 +38,12 @@ class LayerViewProxy(QObject): if type(active_view) == LayerView.LayerView.LayerView: return active_view.getCurrentLayer() + @pyqtProperty(int, notify = currentLayerChanged) + def minimumLayer(self): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + return active_view.getMinimumLayer() + busyChanged = pyqtSignal() @pyqtProperty(bool, notify = busyChanged) def busy(self): @@ -44,13 +52,96 @@ class LayerViewProxy(QObject): return active_view.isBusy() return False - + + @pyqtProperty(bool, notify = preferencesChanged) + def compatibilityMode(self): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + return active_view.getCompatibilityMode() + + return False + @pyqtSlot(int) def setCurrentLayer(self, layer_num): active_view = self._controller.getActiveView() if type(active_view) == LayerView.LayerView.LayerView: active_view.setLayer(layer_num) + @pyqtSlot(int) + def setMinimumLayer(self, layer_num): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.setMinimumLayer(layer_num) + + @pyqtSlot(int) + def setLayerViewType(self, layer_view_type): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.setLayerViewType(layer_view_type) + + @pyqtProperty(bool) + def getLayerViewType(self): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + return active_view.getLayerViewType() + return 0 + + # Opacity 0..1 + @pyqtSlot(int, float) + def setExtruderOpacity(self, extruder_nr, opacity): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.setExtruderOpacity(extruder_nr, opacity) + + @pyqtSlot(int) + def setShowTravelMoves(self, show): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.setShowTravelMoves(show) + + @pyqtSlot(int) + def setShowSupport(self, show): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.setShowSupport(show) + + @pyqtSlot(int) + def setShowAdhesion(self, show): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.setShowAdhesion(show) + + @pyqtSlot(int) + def setShowSkin(self, show): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.setShowSkin(show) + + @pyqtSlot(int) + def setShowInfill(self, show): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.setShowInfill(show) + + @pyqtProperty(int, notify = globalStackChanged) + def getExtruderCount(self): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + return active_view.getExtruderCount() + return 0 + + @pyqtSlot() + def enableLegend(self): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.enableLegend() + + @pyqtSlot() + def disableLegend(self): + active_view = self._controller.getActiveView() + if type(active_view) == LayerView.LayerView.LayerView: + active_view.disableLegend() + def _layerActivityChanged(self): self.activityChanged.emit() @@ -63,10 +154,18 @@ class LayerViewProxy(QObject): def _onBusyChanged(self): self.busyChanged.emit() - + + def _onGlobalStackChanged(self): + self.globalStackChanged.emit() + + def _onPreferencesChanged(self): + self.preferencesChanged.emit() + def _onActiveViewChanged(self): active_view = self._controller.getActiveView() if type(active_view) == LayerView.LayerView.LayerView: active_view.currentLayerNumChanged.connect(self._onLayerChanged) active_view.maxLayersChanged.connect(self._onMaxLayersChanged) active_view.busyChanged.connect(self._onBusyChanged) + active_view.globalStackChanged.connect(self._onGlobalStackChanged) + active_view.preferencesChanged.connect(self._onPreferencesChanged) diff --git a/plugins/LayerView/layers.shader b/plugins/LayerView/layers.shader index 0e1f767e23..840c3f25ba 100644 --- a/plugins/LayerView/layers.shader +++ b/plugins/LayerView/layers.shader @@ -3,29 +3,158 @@ vertex = uniform highp mat4 u_modelViewProjectionMatrix; uniform lowp float u_active_extruder; uniform lowp float u_shade_factor; + uniform highp int u_layer_view_type; + attribute highp float a_extruder; + attribute highp float a_line_type; attribute highp vec4 a_vertex; attribute lowp vec4 a_color; + attribute lowp vec4 a_material_color; + varying lowp vec4 v_color; + varying float v_line_type; + void main() { gl_Position = u_modelViewProjectionMatrix * a_vertex; - // shade the color depending on the extruder index stored in the alpha component of the color - v_color = (a_color.a == u_active_extruder) ? a_color : a_color * u_shade_factor; - v_color.a = 1.0; + // shade the color depending on the extruder index + v_color = a_color; + // 8 and 9 are travel moves + if ((a_line_type != 8.0) && (a_line_type != 9.0)) { + v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a); + } + + v_line_type = a_line_type; } fragment = varying lowp vec4 v_color; + varying float v_line_type; + + uniform int u_show_travel_moves; + uniform int u_show_support; + uniform int u_show_adhesion; + uniform int u_show_skin; + uniform int u_show_infill; void main() { + if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // discard movements + discard; + } + // support: 4, 7, 10 + if ((u_show_support == 0) && ( + ((v_line_type >= 3.5) && (v_line_type <= 4.5)) || + ((v_line_type >= 6.5) && (v_line_type <= 7.5)) || + ((v_line_type >= 9.5) && (v_line_type <= 10.5)) + )) { + discard; + } + // skin: 1, 2, 3 + if ((u_show_skin == 0) && ( + (v_line_type >= 0.5) && (v_line_type <= 3.5) + )) { + discard; + } + // adhesion: + if ((u_show_adhesion == 0) && (v_line_type >= 4.5) && (v_line_type <= 5.5)) { + // discard movements + discard; + } + // infill: + if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) { + // discard movements + discard; + } + gl_FragColor = v_color; } +vertex41core = + #version 410 + uniform highp mat4 u_modelViewProjectionMatrix; + uniform lowp float u_active_extruder; + uniform lowp float u_shade_factor; + uniform highp int u_layer_view_type; + + in highp float a_extruder; + in highp float a_line_type; + in highp vec4 a_vertex; + in lowp vec4 a_color; + in lowp vec4 a_material_color; + + out lowp vec4 v_color; + out float v_line_type; + + void main() + { + gl_Position = u_modelViewProjectionMatrix * a_vertex; + v_color = a_color; + if ((a_line_type != 8) && (a_line_type != 9)) { + v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a); + } + + v_line_type = a_line_type; + } + +fragment41core = + #version 410 + in lowp vec4 v_color; + in float v_line_type; + out vec4 frag_color; + + uniform int u_show_travel_moves; + uniform int u_show_support; + uniform int u_show_adhesion; + uniform int u_show_skin; + uniform int u_show_infill; + + void main() + { + if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // discard movements + discard; + } + // support: 4, 7, 10 + if ((u_show_support == 0) && ( + ((v_line_type >= 3.5) && (v_line_type <= 4.5)) || + ((v_line_type >= 6.5) && (v_line_type <= 7.5)) || + ((v_line_type >= 9.5) && (v_line_type <= 10.5)) + )) { + discard; + } + // skin: 1, 2, 3 + if ((u_show_skin == 0) && ( + (v_line_type >= 0.5) && (v_line_type <= 3.5) + )) { + discard; + } + // adhesion: + if ((u_show_adhesion == 0) && (v_line_type >= 4.5) && (v_line_type <= 5.5)) { + // discard movements + discard; + } + // infill: + if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) { + // discard movements + discard; + } + + frag_color = v_color; + } + [defaults] u_active_extruder = 0.0 u_shade_factor = 0.60 +u_layer_view_type = 0 +u_extruder_opacity = [1.0, 1.0, 1.0, 1.0] + +u_show_travel_moves = 0 +u_show_support = 1 +u_show_adhesion = 1 +u_show_skin = 1 +u_show_infill = 1 [bindings] u_modelViewProjectionMatrix = model_view_projection_matrix @@ -33,3 +162,6 @@ u_modelViewProjectionMatrix = model_view_projection_matrix [attributes] a_vertex = vertex a_color = color +a_extruder = extruder +a_line_type = line_type +a_material_color = material_color diff --git a/plugins/LayerView/layers3d.shader b/plugins/LayerView/layers3d.shader new file mode 100644 index 0000000000..d968852c71 --- /dev/null +++ b/plugins/LayerView/layers3d.shader @@ -0,0 +1,256 @@ +[shaders] +vertex41core = + #version 410 + uniform highp mat4 u_modelViewProjectionMatrix; + + uniform highp mat4 u_modelMatrix; + uniform highp mat4 u_viewProjectionMatrix; + uniform lowp float u_active_extruder; + uniform lowp int u_layer_view_type; + uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible + + uniform highp mat4 u_normalMatrix; + + in highp vec4 a_vertex; + in lowp vec4 a_color; + in lowp vec4 a_material_color; + in highp vec4 a_normal; + in highp vec2 a_line_dim; // line width and thickness + in highp int a_extruder; // Note: cannot use this in compatibility, int is only available in newer OpenGL. + in highp float a_line_type; + + out lowp vec4 v_color; + + out highp vec3 v_vertex; + out highp vec3 v_normal; + out lowp vec2 v_line_dim; + out highp int v_extruder; + out highp vec4 v_extruder_opacity; + out float v_line_type; + + out lowp vec4 f_color; + out highp vec3 f_vertex; + out highp vec3 f_normal; + + void main() + { + vec4 v1_vertex = a_vertex; + v1_vertex.y -= a_line_dim.y / 2; // half layer down + + vec4 world_space_vert = u_modelMatrix * v1_vertex; + gl_Position = world_space_vert; + // shade the color depending on the extruder index stored in the alpha component of the color + + switch (u_layer_view_type) { + case 0: // "Material color" + v_color = a_material_color; + break; + case 1: // "Line type" + v_color = a_color; + break; + } + + v_vertex = world_space_vert.xyz; + v_normal = (u_normalMatrix * normalize(a_normal)).xyz; + v_line_dim = a_line_dim; + v_extruder = a_extruder; + v_line_type = a_line_type; + v_extruder_opacity = u_extruder_opacity; + + // for testing without geometry shader + f_color = v_color; + f_vertex = v_vertex; + f_normal = v_normal; + } + +geometry41core = + #version 410 + + uniform highp mat4 u_viewProjectionMatrix; + uniform int u_show_travel_moves; + uniform int u_show_support; + uniform int u_show_adhesion; + uniform int u_show_skin; + uniform int u_show_infill; + + layout(lines) in; + layout(triangle_strip, max_vertices = 26) out; + + in vec4 v_color[]; + in vec3 v_vertex[]; + in vec3 v_normal[]; + in vec2 v_line_dim[]; + in int v_extruder[]; + in vec4 v_extruder_opacity[]; + in float v_line_type[]; + + out vec4 f_color; + out vec3 f_normal; + out vec3 f_vertex; + + // Set the set of variables and EmitVertex + void myEmitVertex(vec3 vertex, vec4 color, vec3 normal, vec4 pos) { + f_vertex = vertex; + f_color = color; + f_normal = normal; + gl_Position = pos; + EmitVertex(); + } + + void main() + { + vec4 g_vertex_delta; + vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers + vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position + vec3 g_vertex_normal_vert; + vec4 g_vertex_offset_vert; + vec3 g_vertex_normal_horz_head; + vec4 g_vertex_offset_horz_head; + + float size_x; + float size_y; + + if ((v_extruder_opacity[0][v_extruder[0]] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) { + return; + } + // See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType + if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) { + return; + } + if ((u_show_support == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) { + return; + } + if ((u_show_adhesion == 0) && (v_line_type[0] == 5)) { + return; + } + if ((u_show_skin == 0) && ((v_line_type[0] == 1) || (v_line_type[0] == 2) || (v_line_type[0] == 3))) { + return; + } + if ((u_show_infill == 0) && (v_line_type[0] == 6)) { + return; + } + + if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { + // fixed size for movements + size_x = 0.1; + size_y = 0.1; + } else { + size_x = v_line_dim[0].x / 2 + 0.01; // radius, and make it nicely overlapping + size_y = v_line_dim[0].y / 2 + 0.01; + } + + g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position; + g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z)); + g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); + + g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x)); + + g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //size * g_vertex_normal_horz; + g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); + g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); + + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert)); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert)); + myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz)); + myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz)); + + EndPrimitive(); + + // left side + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head)); + myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz)); + + EndPrimitive(); + + myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz)); + myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); + + EndPrimitive(); + + // right side + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz)); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz)); + + EndPrimitive(); + + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head)); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz)); + + EndPrimitive(); + } + +fragment41core = + #version 410 + in lowp vec4 f_color; + in lowp vec3 f_normal; + in lowp vec3 f_vertex; + + out vec4 frag_color; + + uniform mediump vec4 u_ambientColor; + uniform highp vec3 u_lightPosition; + + void main() + { + mediump vec4 finalColor = vec4(0.0); + float alpha = f_color.a; + + finalColor.rgb += f_color.rgb * 0.3; + + highp vec3 normal = normalize(f_normal); + highp vec3 light_dir = normalize(u_lightPosition - f_vertex); + + // Diffuse Component + highp float NdotL = clamp(dot(normal, light_dir), 0.0, 1.0); + finalColor += (NdotL * f_color); + finalColor.a = alpha; // Do not change alpha in any way + + frag_color = finalColor; + } + + +[defaults] +u_active_extruder = 0.0 +u_layer_view_type = 0 +u_extruder_opacity = [1.0, 1.0, 1.0, 1.0] + +u_specularColor = [0.4, 0.4, 0.4, 1.0] +u_ambientColor = [0.3, 0.3, 0.3, 0.0] +u_diffuseColor = [1.0, 0.79, 0.14, 1.0] +u_shininess = 20.0 + +u_show_travel_moves = 0 +u_show_support = 1 +u_show_adhesion = 1 +u_show_skin = 1 +u_show_infill = 1 + +[bindings] +u_modelViewProjectionMatrix = model_view_projection_matrix +u_modelMatrix = model_matrix +u_viewProjectionMatrix = view_projection_matrix +u_normalMatrix = normal_matrix +u_lightPosition = light_0_position + +[attributes] +a_vertex = vertex +a_color = color +a_normal = normal +a_line_dim = line_dim +a_extruder = extruder +a_material_color = material_color +a_line_type = line_type diff --git a/plugins/LayerView/layerview_composite.shader b/plugins/LayerView/layerview_composite.shader index 61d61bb901..dcc02acc84 100644 --- a/plugins/LayerView/layerview_composite.shader +++ b/plugins/LayerView/layerview_composite.shader @@ -33,6 +33,7 @@ fragment = void main() { + // blur kernel kernel[0] = 0.0; kernel[1] = 1.0; kernel[2] = 0.0; kernel[3] = 1.0; kernel[4] = -4.0; kernel[5] = 1.0; kernel[6] = 0.0; kernel[7] = 1.0; kernel[8] = 0.0; @@ -63,6 +64,75 @@ fragment = } } +vertex41core = + #version 410 + uniform highp mat4 u_modelViewProjectionMatrix; + in highp vec4 a_vertex; + in highp vec2 a_uvs; + + out highp vec2 v_uvs; + + void main() + { + gl_Position = u_modelViewProjectionMatrix * a_vertex; + v_uvs = a_uvs; + } + +fragment41core = + #version 410 + uniform sampler2D u_layer0; + uniform sampler2D u_layer1; + uniform sampler2D u_layer2; + + uniform vec2 u_offset[9]; + + uniform vec4 u_background_color; + uniform float u_outline_strength; + uniform vec4 u_outline_color; + + in vec2 v_uvs; + + float kernel[9]; + + const vec3 x_axis = vec3(1.0, 0.0, 0.0); + const vec3 y_axis = vec3(0.0, 1.0, 0.0); + const vec3 z_axis = vec3(0.0, 0.0, 1.0); + + out vec4 frag_color; + + void main() + { + // blur kernel + kernel[0] = 0.0; kernel[1] = 1.0; kernel[2] = 0.0; + kernel[3] = 1.0; kernel[4] = -4.0; kernel[5] = 1.0; + kernel[6] = 0.0; kernel[7] = 1.0; kernel[8] = 0.0; + + vec4 result = u_background_color; + + vec4 main_layer = texture(u_layer0, v_uvs); + vec4 selection_layer = texture(u_layer1, v_uvs); + vec4 layerview_layer = texture(u_layer2, v_uvs); + + result = main_layer * main_layer.a + result * (1.0 - main_layer.a); + result = layerview_layer * layerview_layer.a + result * (1.0 - layerview_layer.a); + + vec4 sum = vec4(0.0); + for (int i = 0; i < 9; i++) + { + vec4 color = vec4(texture(u_layer1, v_uvs.xy + u_offset[i]).a); + sum += color * (kernel[i] / u_outline_strength); + } + + if((selection_layer.rgb == x_axis || selection_layer.rgb == y_axis || selection_layer.rgb == z_axis)) + { + frag_color = result; + } + else + { + frag_color = mix(result, u_outline_color, abs(sum.a)); + } + } + [defaults] u_layer0 = 0 u_layer1 = 1 diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index 7141099416..c27f8db4a6 100644 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -6,11 +6,13 @@ from UM.FlameProfiler import pyqtSlot from cura.MachineAction import MachineAction -import UM.Application -import UM.Settings.InstanceContainer -import UM.Settings.DefinitionContainer -import UM.Settings.ContainerRegistry -import UM.Logger +from UM.Application import Application +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Logger import Logger + +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry import UM.i18n catalog = UM.i18n.i18nCatalog("cura") @@ -25,11 +27,11 @@ class MachineSettingsAction(MachineAction): self._container_index = 0 - self._container_registry = UM.Settings.ContainerRegistry.getInstance() + self._container_registry = ContainerRegistry.getInstance() self._container_registry.containerAdded.connect(self._onContainerAdded) def _reset(self): - global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + global_container_stack = Application.getInstance().getGlobalContainerStack() if not global_container_stack: return @@ -45,7 +47,7 @@ class MachineSettingsAction(MachineAction): self.containerIndexChanged.emit() def _createDefinitionChangesContainer(self, global_container_stack, container_index = None): - definition_changes_container = UM.Settings.InstanceContainer(global_container_stack.getName() + "_settings") + definition_changes_container = InstanceContainer(global_container_stack.getName() + "_settings") definition = global_container_stack.getBottom() definition_changes_container.setDefinition(definition) definition_changes_container.addMetaDataEntry("type", "definition_changes") @@ -64,24 +66,24 @@ class MachineSettingsAction(MachineAction): def _onContainerAdded(self, container): # Add this action as a supported action to all machine definitions - if isinstance(container, UM.Settings.DefinitionContainer) and container.getMetaDataEntry("type") == "machine": + if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": if container.getProperty("machine_extruder_count", "value") > 1: # Multiextruder printers are not currently supported - UM.Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId()) + Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId()) return - UM.Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) + Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) @pyqtSlot() def forceUpdate(self): # Force rebuilding the build volume by reloading the global container stack. # This is a bit of a hack, but it seems quick enough. - UM.Application.getInstance().globalContainerStackChanged.emit() + Application.getInstance().globalContainerStackChanged.emit() @pyqtSlot() def updateHasMaterialsMetadata(self): # Updates the has_materials metadata flag after switching gcode flavor - global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: definition = global_container_stack.getBottom() if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False): @@ -111,4 +113,4 @@ class MachineSettingsAction(MachineAction): empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] global_container_stack.replaceContainer(material_index, empty_material) - UM.Application.getInstance().globalContainerStackChanged.emit() \ No newline at end of file + Application.getInstance().globalContainerStackChanged.emit() diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py index 6810ab82aa..b283608cb0 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py @@ -4,16 +4,17 @@ from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal from UM.Application import Application +from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.SettingInstance import SettingInstance from UM.Logger import Logger -import UM.Settings.Models +import UM.Settings.Models.SettingVisibilityHandler from cura.Settings.ExtruderManager import ExtruderManager #To get global-inherits-stack setting values from different extruders. from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator ## The per object setting visibility handler ensures that only setting # definitions that have a matching instance Container are returned as visible. -class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler): +class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler): def __init__(self, parent = None, *args, **kwargs): super().__init__(parent = parent, *args, **kwargs) @@ -72,7 +73,7 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand # Use the found stack number to get the right stack to copy the value from. if stack_nr in ExtruderManager.getInstance().extruderIds: - stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0] + stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0] # Use the raw property to set the value (so the inheritance doesn't break) if stack is not None: diff --git a/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py b/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py index 14a4681bc3..42f3935f65 100644 --- a/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py +++ b/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py @@ -8,7 +8,7 @@ catalog = i18nCatalog("cura") from . import RemovableDrivePlugin import string -import ctypes +import ctypes # type: ignore from ctypes import wintypes # Using ctypes.wintypes in the code below does not seem to work from UM.i18n import i18nCatalog diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 4f39fd4818..05f7c0e6f5 100644 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -1,5 +1,6 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. +from typing import Any from cura.CuraApplication import CuraApplication @@ -26,8 +27,8 @@ import json catalog = i18nCatalog("cura") class SliceInfoJob(Job): - data = None - url = None + data = None # type: Any + url = None # type: str def __init__(self, url, data): super().__init__() diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index c1ae4c6f77..60c01670b1 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -12,12 +12,13 @@ from UM.Settings.Validator import ValidatorState from UM.Math.Color import Color from UM.View.GL.OpenGL import OpenGL -import cura.Settings from cura.Settings.ExtruderManager import ExtruderManager +from cura.Settings.ExtrudersModel import ExtrudersModel import math ## Standard view for mesh models. + class SolidView(View): def __init__(self): super().__init__() @@ -27,7 +28,7 @@ class SolidView(View): self._enabled_shader = None self._disabled_shader = None - self._extruders_model = cura.Settings.ExtrudersModel() + self._extruders_model = ExtrudersModel() def beginRendering(self): scene = self.getController().getScene() diff --git a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py index 47b1068b08..fbe8eb884d 100644 --- a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from UM.i18n import i18nCatalog @@ -8,7 +8,8 @@ from UM.Signal import signalemitter from UM.Message import Message -import UM.Settings +import UM.Settings.ContainerRegistry +import UM.Version #To compare firmware version numbers. from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState import cura.Settings.ExtruderManager @@ -97,6 +98,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._material_ids = [""] * self._num_extruders self._hotend_ids = [""] * self._num_extruders + self._target_bed_temperature = 0 self.setPriority(2) # Make sure the output device gets selected above local file output self.setName(key) @@ -220,12 +222,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def getKey(self): return self._key - ## Name of the printer (as returned from the zeroConf properties) + ## The IP address of the printer. + @pyqtProperty(str, constant = True) + def address(self): + return self._properties.get(b"address", b"").decode("utf-8") + + ## Name of the printer (as returned from the ZeroConf properties) @pyqtProperty(str, constant = True) def name(self): return self._properties.get(b"name", b"").decode("utf-8") - ## Firmware version (as returned from the zeroConf properties) + ## Firmware version (as returned from the ZeroConf properties) @pyqtProperty(str, constant=True) def firmwareVersion(self): return self._properties.get(b"firmware_version", b"").decode("utf-8") @@ -235,6 +242,49 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def ipAddress(self): return self._address + ## Pre-heats the heated bed of the printer. + # + # \param temperature The temperature to heat the bed to, in degrees + # Celsius. + # \param duration How long the bed should stay warm, in seconds. + @pyqtSlot(float, float) + def preheatBed(self, temperature, duration): + temperature = round(temperature) #The API doesn't allow floating point. + duration = round(duration) + if UM.Version.Version(self.firmwareVersion) < UM.Version.Version("3.5.92"): #Real bed pre-heating support is implemented from 3.5.92 and up. + self.setTargetBedTemperature(temperature = temperature) #No firmware-side duration support then. + return + url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/pre_heat") + if duration > 0: + data = """{"temperature": "%i", "timeout": "%i"}""" % (temperature, duration) + else: + data = """{"temperature": "%i"}""" % temperature + put_request = QNetworkRequest(url) + put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + self._manager.put(put_request, data.encode()) + + ## Cancels pre-heating the heated bed of the printer. + # + # If the bed is not pre-heated, nothing happens. + @pyqtSlot() + def cancelPreheatBed(self): + self.preheatBed(temperature = 0, duration = 0) + + ## Changes the target bed temperature on the printer. + # + # /param temperature The new target temperature of the bed. + def _setTargetBedTemperature(self, temperature): + if self._target_bed_temperature == temperature: + return + self._target_bed_temperature = temperature + self.targetBedTemperatureChanged.emit() + + url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/temperature/target") + data = str(temperature) + put_request = QNetworkRequest(url) + put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + self._manager.put(put_request, data.encode()) + def _stopCamera(self): self._camera_timer.stop() if self._image_reply: @@ -271,14 +321,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if auth_state == AuthState.AuthenticationRequested: Logger.log("d", "Authentication state changed to authentication requested.") self.setAcceptsCommands(False) - self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}. Please approve the access request on the printer.").format(self.name)) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network. Please approve the access request on the printer.")) self._authentication_requested_message.show() self._authentication_request_active = True self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: Logger.log("d", "Authentication state changed to authenticated") self.setAcceptsCommands(True) - self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}.").format(self.name)) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network.")) self._authentication_requested_message.hide() if self._authentication_request_active: self._authentication_succeeded_message.show() @@ -291,7 +341,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.sendMaterialProfiles() elif auth_state == AuthState.AuthenticationDenied: self.setAcceptsCommands(False) - self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}. No access to control the printer.").format(self.name)) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network. No access to control the printer.")) self._authentication_requested_message.hide() if self._authentication_request_active: if self._authentication_timer.remainingTime() > 0: @@ -466,6 +516,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): bed_temperature = self._json_printer_state["bed"]["temperature"]["current"] self._setBedTemperature(bed_temperature) + target_bed_temperature = self._json_printer_state["bed"]["temperature"]["target"] + self._setTargetBedTemperature(target_bed_temperature) head_x = self._json_printer_state["heads"][0]["position"]["x"] head_y = self._json_printer_state["heads"][0]["position"]["y"] @@ -581,7 +633,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): remote_material_guid, material.getMetaDataEntry("GUID")) - remote_materials = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material", GUID = remote_material_guid, read_only = True) + remote_materials = UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainers(type = "material", GUID = remote_material_guid, read_only = True) remote_material_name = "Unknown" if remote_materials: remote_material_name = remote_materials[0].getName() @@ -772,7 +824,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Send all material profiles to the printer. def sendMaterialProfiles(self): - for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material"): + for container in UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainers(type = "material"): try: xml_data = container.serialize() if xml_data == "" or xml_data is None: @@ -974,10 +1026,20 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message.hide() elif reply.operation() == QNetworkAccessManager.PutOperation: - if status_code == 204: + if status_code in [200, 201, 202, 204]: pass # Request was successful! else: - Logger.log("d", "Something went wrong when trying to update data of API (%s). Message: %s Statuscode: %s", reply_url, reply.readAll(), status_code) + operation_type = "Unknown" + if reply.operation() == QNetworkAccessManager.GetOperation: + operation_type = "Get" + elif reply.operation() == QNetworkAccessManager.PutOperation: + operation_type = "Put" + elif reply.operation() == QNetworkAccessManager.PostOperation: + operation_type = "Post" + elif reply.operation() == QNetworkAccessManager.DeleteOperation: + operation_type = "Delete" + + Logger.log("d", "Something went wrong when trying to update data of API (%s). Message: %s Statuscode: %s, operation: %s", reply_url, reply.readAll(), status_code, operation_type) else: Logger.log("d", "NetworkPrinterOutputDevice got an unhandled operation %s", reply.operation()) diff --git a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py index 8722c5361e..84fb82a22b 100644 --- a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py @@ -1,7 +1,10 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from . import NetworkPrinterOutputDevice -from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo +from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo # type: ignore from UM.Logger import Logger from UM.Signal import Signal, signalemitter from UM.Application import Application @@ -75,9 +78,13 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._manual_instances.append(address) self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances)) - name = address instance_name = "manual:%s" % address - properties = { b"name": name.encode("utf-8"), b"manual": b"true", b"incomplete": b"true" } + properties = { + b"name": address.encode("utf-8"), + b"address": address.encode("utf-8"), + b"manual": b"true", + b"incomplete": b"true" + } if instance_name not in self._printers: # Add a preliminary printer instance @@ -112,10 +119,14 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if status_code == 200: system_info = json.loads(bytes(reply.readAll()).decode("utf-8")) address = reply.url().host() - name = ("%s (%s)" % (system_info["name"], address)) instance_name = "manual:%s" % address - properties = { b"name": name.encode("utf-8"), b"firmware_version": system_info["firmware"].encode("utf-8"), b"manual": b"true" } + properties = { + b"name": system_info["name"].encode("utf-8"), + b"address": address.encode("utf-8"), + b"firmware_version": system_info["firmware"].encode("utf-8"), + b"manual": b"true" + } if instance_name in self._printers: # Only replace the printer if it is still in the list of (manual) printers self.removePrinter(instance_name) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index e344caee1d..56482755df 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the AGPLv3 or higher. from .avr_isp import stk500v2, ispBase, intelHex -import serial +import serial # type: ignore import threading import time import queue @@ -124,6 +124,16 @@ class USBPrinterOutputDevice(PrinterOutputDevice): def _homeBed(self): self._sendCommand("G28 Z") + ## A name for the device. + @pyqtProperty(str, constant = True) + def name(self): + return self.getName() + + ## The address of the device. + @pyqtProperty(str, constant = True) + def address(self): + return self._serial_port + def startPrint(self): self.writeStarted.emit(self) gcode_list = getattr( Application.getInstance().getController().getScene(), "gcode_list") @@ -631,3 +641,20 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._update_firmware_thread.daemon = True self.connect() + + ## Pre-heats the heated bed of the printer, if it has one. + # + # \param temperature The temperature to heat the bed to, in degrees + # Celsius. + # \param duration How long the bed should stay warm, in seconds. This is + # ignored because there is no g-code to set this. + @pyqtSlot(float, float) + def preheatBed(self, temperature, duration): + self._setTargetBedTemperature(temperature) + + ## Cancels pre-heating the heated bed of the printer. + # + # If the bed is not pre-heated, nothing happens. + @pyqtSlot() + def cancelPreheatBed(self): + self._setTargetBedTemperature(0) \ No newline at end of file diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index 67e21e1abe..eb0193c0c0 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -254,7 +254,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): def getSerialPortList(self, only_list_usb = False): base_list = [] if platform.system() == "Windows": - import winreg #@UnresolvedImport + import winreg # type: ignore @UnresolvedImport try: key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") i = 0 @@ -273,4 +273,4 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): base_list = base_list + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/cu.*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/tty.wchusb*") + glob.glob("/dev/cu.wchusb*") + glob.glob("/dev/rfcomm*") + glob.glob("/dev/serial/by-id/*") return list(base_list) - _instance = None + _instance = None # type: "USBPrinterOutputDeviceManager" diff --git a/plugins/USBPrinting/avr_isp/stk500v2.py b/plugins/USBPrinting/avr_isp/stk500v2.py index 91bef53875..dbfc8dc756 100644 --- a/plugins/USBPrinting/avr_isp/stk500v2.py +++ b/plugins/USBPrinting/avr_isp/stk500v2.py @@ -7,7 +7,7 @@ import struct import sys import time -from serial import Serial +from serial import Serial # type: ignore from serial import SerialException from serial import SerialTimeoutException from UM.Logger import Logger @@ -184,7 +184,7 @@ class Stk500v2(ispBase.IspBase): def portList(): ret = [] - import _winreg + import _winreg # type: ignore key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") #@UndefinedVariable i=0 while True: diff --git a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py index a9148e0c7b..238a13bf61 100644 --- a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py +++ b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py @@ -1,3 +1,8 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.InstanceContainer import InstanceContainer from cura.MachineAction import MachineAction from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty @@ -45,7 +50,7 @@ class UMOUpgradeSelection(MachineAction): definition_changes_container.setDefinition(definition) definition_changes_container.addMetaDataEntry("type", "definition_changes") - UM.Settings.ContainerRegistry.getInstance().addContainer(definition_changes_container) + UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().addContainer(definition_changes_container) # Insert definition_changes between the definition and the variant global_container_stack.insertContainer(-1, definition_changes_container) diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py index 828411cc8a..1f0e640f04 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py @@ -1,18 +1,19 @@ +from UM.Application import Application +from UM.Settings.DefinitionContainer import DefinitionContainer from cura.MachineAction import MachineAction from UM.i18n import i18nCatalog -import cura.Settings.CuraContainerRegistry -import UM.Settings.DefinitionContainer -catalog = i18nCatalog("cura") +from UM.Settings.ContainerRegistry import ContainerRegistry +catalog = i18nCatalog("cura") ## Upgrade the firmware of a machine by USB with this action. class UpgradeFirmwareMachineAction(MachineAction): def __init__(self): super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware")) self._qml_url = "UpgradeFirmwareMachineAction.qml" - cura.Settings.CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) + ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) def _onContainerAdded(self, container): # Add this action as a supported action to all machine definitions if they support USB connection - if isinstance(container, UM.Settings.DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"): - UM.Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) + if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"): + Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py b/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py index 8be5a63151..3bff7c1bf5 100644 --- a/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py +++ b/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py @@ -26,7 +26,7 @@ class Profile: # # \param serialised A string with the contents of a profile. # \param filename The supposed filename of the profile, without extension. - def __init__(self, serialised, filename): + def __init__(self, serialised: str, filename: str) -> None: self._filename = filename parser = configparser.ConfigParser(interpolation = None) @@ -58,17 +58,17 @@ class Profile: self._material_name = None # Parse the settings. - self._settings = {} + self._settings = {} # type: Dict[str,str] if parser.has_section("settings"): for key, value in parser["settings"].items(): self._settings[key] = value # Parse the defaults and the disabled defaults. - self._changed_settings_defaults = {} + self._changed_settings_defaults = {} # type: Dict[str,str] if parser.has_section("defaults"): for key, value in parser["defaults"].items(): self._changed_settings_defaults[key] = value - self._disabled_settings_defaults = [] + self._disabled_settings_defaults = [] # type: List[str] if parser.has_section("disabled_defaults"): disabled_defaults_string = parser.get("disabled_defaults", "values") self._disabled_settings_defaults = [item for item in disabled_defaults_string.split(",") if item != ""] # Split by comma. diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py index 1dd6a504ee..b28124f16b 100644 --- a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py +++ b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py @@ -6,7 +6,7 @@ import os import os.path import io -from UM import Resources +from UM.Resources import Resources from UM.VersionUpgrade import VersionUpgrade # Superclass of the plugin. import UM.VersionUpgrade diff --git a/plugins/X3DReader/X3DReader.py b/plugins/X3DReader/X3DReader.py index 0a81e98d0d..f78023dfab 100644 --- a/plugins/X3DReader/X3DReader.py +++ b/plugins/X3DReader/X3DReader.py @@ -13,8 +13,10 @@ from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshReader import MeshReader from UM.Scene.SceneNode import SceneNode +MYPY = False try: - import xml.etree.cElementTree as ET + if not MYPY: + import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET diff --git a/plugins/XRayView/xray.shader b/plugins/XRayView/xray.shader index b42b3e056a..41b00154ea 100644 --- a/plugins/XRayView/xray.shader +++ b/plugins/XRayView/xray.shader @@ -17,6 +17,28 @@ fragment = gl_FragColor = u_color; } +vertex41core = + #version 410 + uniform highp mat4 u_modelViewProjectionMatrix; + + in highp vec4 a_vertex; + + void main() + { + gl_Position = u_modelViewProjectionMatrix * a_vertex; + } + +fragment41core = + #version 410 + uniform lowp vec4 u_color; + + out vec4 frag_color; + + void main() + { + frag_color = u_color; + } + [defaults] u_color = [0.02, 0.02, 0.02, 1.0] diff --git a/plugins/XRayView/xray_composite.shader b/plugins/XRayView/xray_composite.shader index e7a38950bf..82dca52cf9 100644 --- a/plugins/XRayView/xray_composite.shader +++ b/plugins/XRayView/xray_composite.shader @@ -67,6 +67,77 @@ fragment = } } +vertex41core = + #version 410 + uniform highp mat4 u_modelViewProjectionMatrix; + in highp vec4 a_vertex; + in highp vec2 a_uvs; + + out highp vec2 v_uvs; + + void main() + { + gl_Position = u_modelViewProjectionMatrix * a_vertex; + v_uvs = a_uvs; + } + +fragment41core = + #version 410 + uniform sampler2D u_layer0; + uniform sampler2D u_layer1; + uniform sampler2D u_layer2; + + uniform vec2 u_offset[9]; + + uniform float u_outline_strength; + uniform vec4 u_outline_color; + uniform vec4 u_error_color; + uniform vec4 u_background_color; + + const vec3 x_axis = vec3(1.0, 0.0, 0.0); + const vec3 y_axis = vec3(0.0, 1.0, 0.0); + const vec3 z_axis = vec3(0.0, 0.0, 1.0); + + in vec2 v_uvs; + out vec4 frag_color; + + float kernel[9]; + + void main() + { + kernel[0] = 0.0; kernel[1] = 1.0; kernel[2] = 0.0; + kernel[3] = 1.0; kernel[4] = -4.0; kernel[5] = 1.0; + kernel[6] = 0.0; kernel[7] = 1.0; kernel[8] = 0.0; + + vec4 result = u_background_color; + vec4 layer0 = texture(u_layer0, v_uvs); + + result = layer0 * layer0.a + result * (1.0 - layer0.a); + + float intersection_count = (texture(u_layer2, v_uvs).r * 255.0) / 5.0; + if(mod(intersection_count, 2.0) == 1.0) + { + result = u_error_color; + } + + vec4 sum = vec4(0.0); + for (int i = 0; i < 9; i++) + { + vec4 color = vec4(texture(u_layer1, v_uvs.xy + u_offset[i]).a); + sum += color * (kernel[i] / u_outline_strength); + } + + vec4 layer1 = texture(u_layer1, v_uvs); + if((layer1.rgb == x_axis || layer1.rgb == y_axis || layer1.rgb == z_axis)) + { + frag_color = result; + } + else + { + frag_color = mix(result, vec4(abs(sum.a)) * u_outline_color, abs(sum.a)); + } + } + [defaults] u_layer0 = 0 u_layer1 = 1 diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 4364f8c423..ccc0dad08e 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -1,11 +1,9 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -import math import copy import io import xml.etree.ElementTree as ET -import uuid from UM.Resources import Resources from UM.Logger import Logger @@ -13,11 +11,11 @@ from UM.Util import parseBool from cura.CuraApplication import CuraApplication import UM.Dictionary - -import UM.Settings +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.ContainerRegistry import ContainerRegistry ## Handles serializing and deserializing material containers from an XML file -class XmlMaterialProfile(UM.Settings.InstanceContainer): +class XmlMaterialProfile(InstanceContainer): def __init__(self, container_id, *args, **kwargs): super().__init__(container_id, *args, **kwargs) self._inherited_files = [] @@ -30,7 +28,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): super().setReadOnly(read_only) basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile. - for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): + for container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): container._read_only = read_only # prevent loop instead of calling setReadOnly ## Overridden from InstanceContainer @@ -46,7 +44,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): basefile = self.getMetaDataEntry("base_file", self._id) #if basefile is self.id, this is a basefile. # Update all containers that share GUID and basefile - for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): + for container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): container.setMetaDataEntry(key, value) ## Overridden from InstanceContainer, similar to setMetaDataEntry. @@ -65,7 +63,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile. # Update the basefile as well, this is actually what we're trying to do # Update all containers that share GUID and basefile - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile) + containers = ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile) for container in containers: container.setName(new_name) @@ -74,7 +72,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): super().setDirty(dirty) base_file = self.getMetaDataEntry("base_file", None) if base_file is not None and base_file != self._id: - containers = UM.Settings.ContainerRegistry.getInstance().findContainers(id=base_file) + containers = ContainerRegistry.getInstance().findContainers(id=base_file) if containers: base_container = containers[0] if not base_container.isReadOnly(): @@ -88,7 +86,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): # super().setProperty(key, property_name, property_value) # # basefile = self.getMetaDataEntry("base_file", self._id) #if basefile is self.id, this is a basefile. - # for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): + # for container in UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): # if not container.isReadOnly(): # container.setDirty(True) @@ -96,7 +94,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): # base file: global settings + supported machines # machine / variant combination: only changes for itself. def serialize(self): - registry = UM.Settings.ContainerRegistry.getInstance() + registry = ContainerRegistry.getInstance() base_file = self.getMetaDataEntry("base_file", "") if base_file and self.id != base_file: @@ -422,7 +420,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): density = float(property_values.get("density", 1.3)) # In g/cm3 meta_data["properties"] = property_values - self.setDefinition(UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0]) + self.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0]) global_compatibility = True global_setting_values = {} @@ -467,7 +465,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): # Lets try again with some naive heuristics. machine_id = identifier.get("product").replace(" ", "").lower() - definitions = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id) + definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id) if not definitions: Logger.log("w", "No definition found for machine ID %s", machine_id) continue @@ -490,7 +488,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): new_material._dirty = False - UM.Settings.ContainerRegistry.getInstance().addContainer(new_material) + ContainerRegistry.getInstance().addContainer(new_material) hotends = machine.iterfind("./um:hotend", self.__namespaces) for hotend in hotends: @@ -498,10 +496,10 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): if hotend_id is None: continue - variant_containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = hotend_id) + variant_containers = ContainerRegistry.getInstance().findInstanceContainers(id = hotend_id) if not variant_containers: # It is not really properly defined what "ID" is so also search for variants by name. - variant_containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(definition = definition.id, name = hotend_id) + variant_containers = ContainerRegistry.getInstance().findInstanceContainers(definition = definition.id, name = hotend_id) if not variant_containers: Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id) @@ -539,7 +537,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): new_hotend_material._dirty = False - UM.Settings.ContainerRegistry.getInstance().addContainer(new_hotend_material) + ContainerRegistry.getInstance().addContainer(new_hotend_material) def _addSettingElement(self, builder, instance): try: diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index ac7beff6ca..5835d6b761 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -260,7 +260,7 @@ UM.PreferencesPage CheckBox { id: topLayerCountCheckbox - text: catalog.i18nc("@action:button","Display five top layers in layer view"); + text: catalog.i18nc("@action:button","Display five top layers in layer view compatibility mode"); checked: UM.Preferences.getValue("view/top_layer_count") == 5 onClicked: { @@ -275,6 +275,7 @@ UM.PreferencesPage } } } + UM.TooltipArea { width: childrenRect.width height: childrenRect.height @@ -283,12 +284,26 @@ UM.PreferencesPage CheckBox { id: topLayersOnlyCheckbox - text: catalog.i18nc("@option:check", "Only display top layer(s) in layer view") + text: catalog.i18nc("@option:check", "Only display top layer(s) in layer view compatibility mode") checked: boolCheck(UM.Preferences.getValue("view/only_show_top_layers")) onCheckedChanged: UM.Preferences.setValue("view/only_show_top_layers", checked) } } + UM.TooltipArea { + width: childrenRect.width + height: childrenRect.height + text: catalog.i18nc("@info:tooltip", "Should layer be forced into compatibility mode?") + + CheckBox + { + id: forceLayerViewCompatibilityModeCheckbox + text: catalog.i18nc("@option:check", "Force layer view compatibility mode (restart required)") + checked: boolCheck(UM.Preferences.getValue("view/force_layer_view_compatibility_mode")) + onCheckedChanged: UM.Preferences.setValue("view/force_layer_view_compatibility_mode", checked) + } + } + Item { //: Spacer diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml index cd2f2a7376..6fffa0f902 100644 --- a/resources/qml/PrintMonitor.qml +++ b/resources/qml/PrintMonitor.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Ultimaker B.V. +// Copyright (c) 2017 Ultimaker B.V. // Cura is released under the terms of the AGPLv3 or higher. import QtQuick 2.2 @@ -12,7 +12,7 @@ import Cura 1.0 as Cura Column { id: printMonitor - property var connectedPrinter: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null + property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null Cura.ExtrudersModel { @@ -20,47 +20,476 @@ Column simpleNames: true } - Item + Rectangle { - width: base.width - 2 * UM.Theme.getSize("default_margin").width - height: childrenRect.height + UM.Theme.getSize("default_margin").height - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width + id: connectedPrinterHeader + width: parent.width + height: childrenRect.height + UM.Theme.getSize("default_margin").height * 2 + color: UM.Theme.getColor("setting_category") Label { - text: printerConnected ? connectedPrinter.connectionText : catalog.i18nc("@info:status", "The printer is not connected.") - color: printerConnected && printerAcceptsCommands ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text") - font: UM.Theme.getFont("default") + id: connectedPrinterNameLabel + text: connectedPrinter != null ? connectedPrinter.name : catalog.i18nc("@info:status", "No printer connected") + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + anchors.left: parent.left + anchors.top: parent.top + anchors.margins: UM.Theme.getSize("default_margin").width + } + Label + { + id: connectedPrinterAddressLabel + text: (connectedPrinter != null && connectedPrinter.address != null) ? connectedPrinter.address : "" + font: UM.Theme.getFont("small") + color: UM.Theme.getColor("text_inactive") + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: UM.Theme.getSize("default_margin").width + } + Label + { + text: connectedPrinter != null ? connectedPrinter.connectionText : catalog.i18nc("@info:status", "The printer is not connected.") + color: connectedPrinter != null && connectedPrinter.acceptsCommands ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text") + font: UM.Theme.getFont("very_small") wrapMode: Text.WordWrap - width: parent.width + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.right: parent.right + anchors.rightMargin: UM.Theme.getSize("default_margin").width + anchors.top: connectedPrinterNameLabel.bottom } } - Loader + Rectangle { - sourceComponent: monitorSection - property string label: catalog.i18nc("@label", "Temperatures") - } - Repeater - { - model: machineExtruderCount.properties.value - delegate: Loader + color: UM.Theme.getColor("sidebar_lining") + width: parent.width + height: childrenRect.height + + Flow { - sourceComponent: monitorItem - property string label: machineExtruderCount.properties.value > 1 ? extrudersModel.getItem(index).name : catalog.i18nc("@label", "Hotend") - property string value: printerConnected ? Math.round(connectedPrinter.hotendTemperatures[index]) + "°C" : "" + id: extrudersGrid + spacing: UM.Theme.getSize("sidebar_lining_thin").width + width: parent.width + + Repeater + { + id: extrudersRepeater + model: machineExtruderCount.properties.value + + delegate: Rectangle + { + id: extruderRectangle + color: UM.Theme.getColor("sidebar") + width: index == machineExtruderCount.properties.value - 1 && index % 2 == 0 ? extrudersGrid.width : extrudersGrid.width / 2 - UM.Theme.getSize("sidebar_lining_thin").width / 2 + height: UM.Theme.getSize("sidebar_extruder_box").height + + Label //Extruder name. + { + text: ExtruderManager.getExtruderName(index) != "" ? ExtruderManager.getExtruderName(index) : catalog.i18nc("@label", "Hotend") + color: UM.Theme.getColor("text") + font: UM.Theme.getFont("default") + anchors.left: parent.left + anchors.top: parent.top + anchors.margins: UM.Theme.getSize("default_margin").width + } + Label //Temperature indication. + { + text: (connectedPrinter != null && connectedPrinter.hotendTemperatures[index] != null) ? Math.round(connectedPrinter.hotendTemperatures[index]) + "°C" : "" + color: UM.Theme.getColor("text") + font: UM.Theme.getFont("large") + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: UM.Theme.getSize("default_margin").width + } + Rectangle //Material colour indication. + { + id: materialColor + width: materialName.height * 0.75 + height: materialName.height * 0.75 + color: (connectedPrinter != null && connectedPrinter.materialColors[index] != null && connectedPrinter.materialIds[index] != "") ? connectedPrinter.materialColors[index] : "#00000000" + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + visible: connectedPrinter != null && connectedPrinter.materialColors[index] != null && connectedPrinter.materialIds[index] != "" + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.verticalCenter: materialName.verticalCenter + } + Label //Material name. + { + id: materialName + text: (connectedPrinter != null && connectedPrinter.materialNames[index] != null && connectedPrinter.materialIds[index] != "") ? connectedPrinter.materialNames[index] : "" + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + anchors.left: materialColor.right + anchors.bottom: parent.bottom + anchors.margins: UM.Theme.getSize("default_margin").width + } + Label //Variant name. + { + text: (connectedPrinter != null && connectedPrinter.hotendIds[index] != null) ? connectedPrinter.hotendIds[index] : "" + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: UM.Theme.getSize("default_margin").width + } + } + } } } - Repeater + + Rectangle { - model: machineHeatedBed.properties.value == "True" ? 1 : 0 - delegate: Loader + color: UM.Theme.getColor("sidebar_lining") + width: parent.width + height: UM.Theme.getSize("sidebar_lining_thin").width + } + + Rectangle + { + color: UM.Theme.getColor("sidebar") + width: parent.width + height: machineHeatedBed.properties.value == "True" ? UM.Theme.getSize("sidebar_extruder_box").height : 0 + visible: machineHeatedBed.properties.value == "True" + + Label //Build plate label. { - sourceComponent: monitorItem - property string label: catalog.i18nc("@label", "Build plate") - property string value: printerConnected ? Math.round(connectedPrinter.bedTemperature) + "°C" : "" + text: catalog.i18nc("@label", "Build plate") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + anchors.left: parent.left + anchors.top: parent.top + anchors.margins: UM.Theme.getSize("default_margin").width } + Label //Target temperature. + { + id: bedTargetTemperature + text: connectedPrinter != null ? connectedPrinter.targetBedTemperature + "°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: bedCurrentTemperature.bottom + } + Label //Current temperature. + { + id: bedCurrentTemperature + text: connectedPrinter != null ? connectedPrinter.bedTemperature + "°C" : "" + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + anchors.right: bedTargetTemperature.left + anchors.top: parent.top + anchors.margins: UM.Theme.getSize("default_margin").width + } + Rectangle //Input field for pre-heat temperature. + { + id: preheatTemperatureControl + color: UM.Theme.getColor("setting_validation_ok") + border.width: UM.Theme.getSize("default_lining").width + border.color: mouseArea.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.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 + + 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 + } + Label //Maximum temperature indication. + { + text: (bedTemperature.properties.maximum_value != "None" ? bedTemperature.properties.maximum_value : "") + "°C" + color: UM.Theme.getColor("setting_unit") + font: UM.Theme.getFont("default") + anchors.right: parent.right + anchors.rightMargin: UM.Theme.getSize("setting_unit_margin").width + anchors.verticalCenter: parent.verticalCenter + } + MouseArea //Change cursor on hovering. + { + id: mouseArea + hoverEnabled: true + anchors.fill: parent + cursorShape: Qt.IBeamCursor + } + TextInput + { + id: preheatTemperatureInput + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("setting_control_text") + selectByMouse: true + maximumLength: 10 + 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.verticalCenter: parent.verticalCenter + + Binding + { + target: preheatTemperatureInput + property: "text" + value: + { + // Stacklevels + // 0: user -> unsaved change + // 1: quality changes -> saved change + // 2: quality + // 3: material -> user changed material in materialspage + // 4: variant + // 5: machine_changes + // 6: machine + if ((bedTemperature.resolve != "None" && bedTemperature.resolve) && (bedTemperature.stackLevels[0] != 0) && (bedTemperature.stackLevels[0] != 1)) + { + // We have a resolve function. Indicates that the setting is not settable per extruder and that + // we have to choose between the resolved value (default) and the global value + // (if user has explicitly set this). + return bedTemperature.resolve; + } + else + { + return bedTemperature.properties.value; + } + } + when: !preheatTemperatureInput.activeFocus + } + } + } + + UM.RecolorImage + { + id: preheatCountdownIcon + width: UM.Theme.getSize("save_button_specs_icons").width + height: UM.Theme.getSize("save_button_specs_icons").height + sourceSize.width: width + sourceSize.height: height + color: UM.Theme.getColor("text") + visible: preheatCountdown.visible + source: UM.Theme.getIcon("print_time") + anchors.right: preheatCountdown.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width / 2 + anchors.verticalCenter: preheatCountdown.verticalCenter + } + + Timer + { + id: preheatCountdownTimer + interval: 100 //Update every 100ms. You want to update every 1s, but then you have one timer for the updating running out of sync with the actual date timer and you might skip seconds. + running: false + repeat: true + onTriggered: update() + property var endTime: new Date() //Set initial endTime to be the current date, so that the endTime has initially already passed and the timer text becomes invisible if you were to update. + function update() + { + var now = new Date(); + if (now.getTime() < endTime.getTime()) + { + var remaining = endTime - now; //This is in milliseconds. + var minutes = Math.floor(remaining / 60 / 1000); + var seconds = Math.floor((remaining / 1000) % 60); + preheatCountdown.text = minutes + ":" + (seconds < 10 ? "0" + seconds : seconds); + preheatCountdown.visible = true; + } + else + { + preheatCountdown.visible = false; + running = false; + if (connectedPrinter != null) + { + connectedPrinter.cancelPreheatBed() + } + } + } + } + Label + { + id: preheatCountdown + text: "0:00" + visible: false //It only becomes visible when the timer is running. + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + anchors.right: preheatButton.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width + anchors.verticalCenter: preheatButton.verticalCenter + } + + Button //The pre-heat button. + { + id: preheatButton + height: UM.Theme.getSize("setting_control").height + enabled: + { + if (connectedPrinter == null) + { + return false; //Can't preheat if not connected. + } + if (!connectedPrinter.acceptsCommands) + { + return false; //Not allowed to do anything. + } + if (connectedPrinter.jobState == "printing" || connectedPrinter.jobState == "pre_print" || connectedPrinter.jobState == "resuming" || connectedPrinter.jobState == "error" || connectedPrinter.jobState == "offline") + { + return false; //Printer is in a state where it can't react to pre-heating. + } + if (preheatCountdownTimer.running) + { + return true; //Can always cancel if the timer is running. + } + if (bedTemperature.properties.minimum_value != "None" && parseInt(preheatTemperatureInput.text) < parseInt(bedTemperature.properties.minimum_value)) + { + return false; //Target temperature too low. + } + if (bedTemperature.properties.maximum_value != "None" && parseInt(preheatTemperatureInput.text) > parseInt(bedTemperature.properties.maximum_value)) + { + return false; //Target temperature too high. + } + 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: preheatCountdownTimer.running ? catalog.i18nc("@button Cancel pre-heating", "Cancel") : catalog.i18nc("@button", "Pre-heat") + } + } + } + + onClicked: + { + if (!preheatCountdownTimer.running) + { + connectedPrinter.preheatBed(preheatTemperatureInput.text, connectedPrinter.preheatBedTimeout); + var now = new Date(); + var end_time = new Date(); + end_time.setTime(now.getTime() + connectedPrinter.preheatBedTimeout * 1000); //*1000 because time is in milliseconds here. + preheatCountdownTimer.endTime = end_time; + preheatCountdownTimer.start(); + preheatCountdownTimer.update(); //Update once before the first timer is triggered. + } + else + { + connectedPrinter.cancelPreheatBed(); + preheatCountdownTimer.endTime = new Date(); + preheatCountdownTimer.update(); + } + } + + onHoveredChanged: + { + if (hovered) + { + base.showTooltip( + base, + {x: 0, y: preheatButton.mapToItem(base, 0, 0).y}, + catalog.i18nc("@tooltip of pre-heat", "Heat the bed in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the bed to heat up when you're ready to print.") + ); + } + else + { + base.hideTooltip(); + } + } + } + } + + UM.SettingPropertyProvider + { + id: bedTemperature + containerStackId: Cura.MachineManager.activeMachineId + key: "material_bed_temperature" + watchedProperties: ["value", "minimum_value", "maximum_value", "resolve"] + storeIndex: 0 + + property var resolve: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId ? properties.resolve : "None" + } + + UM.SettingPropertyProvider + { + id: machineExtruderCount + containerStackId: Cura.MachineManager.activeMachineId + key: "machine_extruder_count" + watchedProperties: ["value"] } Loader @@ -72,19 +501,19 @@ Column { sourceComponent: monitorItem property string label: catalog.i18nc("@label", "Job Name") - property string value: printerConnected ? connectedPrinter.jobName : "" + property string value: connectedPrinter != null ? connectedPrinter.jobName : "" } Loader { sourceComponent: monitorItem property string label: catalog.i18nc("@label", "Printing Time") - property string value: printerConnected ? getPrettyTime(connectedPrinter.timeTotal) : "" + property string value: connectedPrinter != null ? getPrettyTime(connectedPrinter.timeTotal) : "" } Loader { sourceComponent: monitorItem property string label: catalog.i18nc("@label", "Estimated time left") - property string value: printerConnected ? getPrettyTime(connectedPrinter.timeTotal - connectedPrinter.timeElapsed) : "" + property string value: connectedPrinter != null ? getPrettyTime(connectedPrinter.timeTotal - connectedPrinter.timeElapsed) : "" } Component @@ -103,7 +532,7 @@ Column width: parent.width * 0.4 anchors.verticalCenter: parent.verticalCenter text: label - color: printerConnected && printerAcceptsCommands ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text") + color: connectedPrinter != null && connectedPrinter.acceptsCommands ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text") font: UM.Theme.getFont("default") elide: Text.ElideRight } @@ -112,7 +541,7 @@ Column width: parent.width * 0.6 anchors.verticalCenter: parent.verticalCenter text: value - color: printerConnected && printerAcceptsCommands ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text") + color: connectedPrinter != null && connectedPrinter.acceptsCommands ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text") font: UM.Theme.getFont("default") elide: Text.ElideRight } @@ -125,7 +554,7 @@ Column Rectangle { color: UM.Theme.getColor("setting_category") - width: base.width - 2 * UM.Theme.getSize("default_margin").width + width: base.width height: UM.Theme.getSize("section").height Label diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index 86570a8d85..b8085a29b1 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Ultimaker B.V. +// Copyright (c) 2017 Ultimaker B.V. // Cura is released under the terms of the AGPLv3 or higher. import QtQuick 2.2 @@ -456,19 +456,6 @@ Rectangle } } - Label { - id: monitorLabel - text: catalog.i18nc("@label","Printer Monitor"); - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width; - anchors.top: headerSeparator.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width * 0.45 - font: UM.Theme.getFont("large") - color: UM.Theme.getColor("text") - visible: monitoringPrint - } - StackView { id: sidebarContents @@ -512,10 +499,8 @@ Rectangle Loader { anchors.bottom: footerSeparator.top - anchors.top: monitorLabel.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.top: headerSeparator.bottom anchors.left: base.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.right: base.right source: monitoringPrint ? "PrintMonitor.qml": "SidebarContents.qml" } diff --git a/resources/shaders/grid.shader b/resources/shaders/grid.shader index c05b9ba15c..74eed544fd 100644 --- a/resources/shaders/grid.shader +++ b/resources/shaders/grid.shader @@ -27,6 +27,37 @@ fragment = gl_FragColor = u_gridColor1; } +vertex41core = + #version 410 + uniform highp mat4 u_modelViewProjectionMatrix; + + in highp vec4 a_vertex; + in lowp vec2 a_uvs; + + out lowp vec2 v_uvs; + + void main() + { + gl_Position = u_modelViewProjectionMatrix * a_vertex; + v_uvs = a_uvs; + } + +fragment41core = + #version 410 + uniform lowp vec4 u_gridColor0; + uniform lowp vec4 u_gridColor1; + + in lowp vec2 v_uvs; + out vec4 frag_color; + + void main() + { + if (mod(floor(v_uvs.x / 10.0) - floor(v_uvs.y / 10.0), 2.0) < 1.0) + frag_color = u_gridColor0; + else + frag_color = u_gridColor1; + } + [defaults] u_gridColor0 = [0.96, 0.96, 0.96, 1.0] u_gridColor1 = [0.8, 0.8, 0.8, 1.0] diff --git a/resources/shaders/overhang.shader b/resources/shaders/overhang.shader index 99cbdf913d..b9cf53f8b7 100644 --- a/resources/shaders/overhang.shader +++ b/resources/shaders/overhang.shader @@ -8,16 +8,16 @@ vertex = attribute highp vec4 a_normal; attribute highp vec2 a_uvs; - varying highp vec3 v_vertex; - varying highp vec3 v_normal; + varying highp vec3 f_vertex; + varying highp vec3 f_normal; void main() { vec4 world_space_vert = u_modelMatrix * a_vertex; gl_Position = u_viewProjectionMatrix * world_space_vert; - v_vertex = world_space_vert.xyz; - v_normal = (u_normalMatrix * normalize(a_normal)).xyz; + f_vertex = world_space_vert.xyz; + f_normal = (u_normalMatrix * normalize(a_normal)).xyz; } fragment = @@ -31,27 +31,28 @@ fragment = uniform lowp float u_overhangAngle; uniform lowp vec4 u_overhangColor; - varying highp vec3 v_vertex; - varying highp vec3 v_normal; + varying highp vec3 f_vertex; + varying highp vec3 f_normal; void main() { + mediump vec4 finalColor = vec4(0.0); - /* Ambient Component */ + // Ambient Component finalColor += u_ambientColor; - highp vec3 normal = normalize(v_normal); - highp vec3 lightDir = normalize(u_lightPosition - v_vertex); + highp vec3 normal = normalize(f_normal); + highp vec3 lightDir = normalize(u_lightPosition - f_vertex); - /* Diffuse Component */ + // Diffuse Component highp float NdotL = clamp(abs(dot(normal, lightDir)), 0.0, 1.0); finalColor += (NdotL * u_diffuseColor); - /* Specular Component */ - /* TODO: We should not do specularity for fragments facing away from the light.*/ + // Specular Component + // TODO: We should not do specularity for fragments facing away from the light. highp vec3 reflectedLight = reflect(-lightDir, normal); - highp vec3 viewVector = normalize(u_viewPosition - v_vertex); + highp vec3 viewVector = normalize(u_viewPosition - f_vertex); highp float NdotR = clamp(dot(viewVector, reflectedLight), 0.0, 1.0); finalColor += pow(NdotR, u_shininess) * u_specularColor; @@ -61,6 +62,73 @@ fragment = gl_FragColor.a = 1.0; } +vertex41core = + #version 410 + uniform highp mat4 u_modelMatrix; + uniform highp mat4 u_viewProjectionMatrix; + uniform highp mat4 u_normalMatrix; + + in highp vec4 a_vertex; + in highp vec4 a_normal; + in highp vec2 a_uvs; + + out highp vec3 f_vertex; + out highp vec3 f_normal; + + void main() + { + vec4 world_space_vert = u_modelMatrix * a_vertex; + gl_Position = u_viewProjectionMatrix * world_space_vert; + + f_vertex = world_space_vert.xyz; + f_normal = (u_normalMatrix * normalize(a_normal)).xyz; + } + +fragment41core = + #version 410 + uniform mediump vec4 u_ambientColor; + uniform mediump vec4 u_diffuseColor; + uniform mediump vec4 u_specularColor; + uniform highp vec3 u_lightPosition; + uniform mediump float u_shininess; + uniform highp vec3 u_viewPosition; + + uniform lowp float u_overhangAngle; + uniform lowp vec4 u_overhangColor; + + in highp vec3 f_vertex; + in highp vec3 f_normal; + + out vec4 frag_color; + + void main() + { + + mediump vec4 finalColor = vec4(0.0); + + // Ambient Component + finalColor += u_ambientColor; + + highp vec3 normal = normalize(f_normal); + highp vec3 lightDir = normalize(u_lightPosition - f_vertex); + + // Diffuse Component + highp float NdotL = clamp(abs(dot(normal, lightDir)), 0.0, 1.0); + finalColor += (NdotL * u_diffuseColor); + + // Specular Component + // TODO: We should not do specularity for fragments facing away from the light. + highp vec3 reflectedLight = reflect(-lightDir, normal); + highp vec3 viewVector = normalize(u_viewPosition - f_vertex); + highp float NdotR = clamp(dot(viewVector, reflectedLight), 0.0, 1.0); + finalColor += pow(NdotR, u_shininess) * u_specularColor; + + finalColor = (-normal.y > u_overhangAngle) ? u_overhangColor : finalColor; + + frag_color = finalColor; + frag_color.a = 1.0; + } + [defaults] u_ambientColor = [0.3, 0.3, 0.3, 1.0] u_diffuseColor = [1.0, 0.79, 0.14, 1.0] diff --git a/resources/shaders/striped.shader b/resources/shaders/striped.shader index 0114f0b2cb..ce7d14e39e 100644 --- a/resources/shaders/striped.shader +++ b/resources/shaders/striped.shader @@ -63,6 +63,74 @@ fragment = gl_FragColor.a = 1.0; } +vertex41core = + #version 410 + uniform highp mat4 u_modelMatrix; + uniform highp mat4 u_viewProjectionMatrix; + uniform highp mat4 u_normalMatrix; + + in highp vec4 a_vertex; + in highp vec4 a_normal; + in highp vec2 a_uvs; + + out highp vec3 v_position; + out highp vec3 v_vertex; + out highp vec3 v_normal; + + void main() + { + vec4 world_space_vert = u_modelMatrix * a_vertex; + gl_Position = u_viewProjectionMatrix * world_space_vert; + + v_position = gl_Position.xyz; + v_vertex = world_space_vert.xyz; + v_normal = (u_normalMatrix * normalize(a_normal)).xyz; + } + +fragment41core = + #version 410 + uniform mediump vec4 u_ambientColor; + uniform mediump vec4 u_diffuseColor1; + uniform mediump vec4 u_diffuseColor2; + uniform mediump vec4 u_specularColor; + uniform highp vec3 u_lightPosition; + uniform mediump float u_shininess; + uniform highp vec3 u_viewPosition; + + uniform mediump float u_width; + + in highp vec3 v_position; + in highp vec3 v_vertex; + in highp vec3 v_normal; + + out vec4 frag_color; + + void main() + { + mediump vec4 finalColor = vec4(0.0); + mediump vec4 diffuseColor = (mod((-v_position.x + v_position.y), u_width) < (u_width / 2.)) ? u_diffuseColor1 : u_diffuseColor2; + + /* Ambient Component */ + finalColor += u_ambientColor; + + highp vec3 normal = normalize(v_normal); + highp vec3 lightDir = normalize(u_lightPosition - v_vertex); + + /* Diffuse Component */ + highp float NdotL = clamp(abs(dot(normal, lightDir)), 0.0, 1.0); + finalColor += (NdotL * diffuseColor); + + /* Specular Component */ + /* TODO: We should not do specularity for fragments facing away from the light.*/ + highp vec3 reflectedLight = reflect(-lightDir, normal); + highp vec3 viewVector = normalize(u_viewPosition - v_vertex); + highp float NdotR = clamp(dot(viewVector, reflectedLight), 0.0, 1.0); + finalColor += pow(NdotR, u_shininess) * u_specularColor; + + frag_color = finalColor; + frag_color.a = 1.0; + } + [defaults] u_ambientColor = [0.3, 0.3, 0.3, 1.0] u_diffuseColor1 = [1.0, 0.5, 0.5, 1.0] diff --git a/resources/shaders/transparent_object.shader b/resources/shaders/transparent_object.shader index cd27a40769..faa43bb46c 100644 --- a/resources/shaders/transparent_object.shader +++ b/resources/shaders/transparent_object.shader @@ -48,6 +48,59 @@ fragment = gl_FragColor.a = u_opacity; } +vertex41core = + #version 410 + uniform highp mat4 u_modelMatrix; + uniform highp mat4 u_viewProjectionMatrix; + uniform highp mat4 u_normalMatrix; + + in highp vec4 a_vertex; + in highp vec4 a_normal; + in highp vec2 a_uvs; + + out highp vec3 v_vertex; + out highp vec3 v_normal; + + void main() + { + vec4 world_space_vert = u_modelMatrix * a_vertex; + gl_Position = u_viewProjectionMatrix * world_space_vert; + + v_vertex = world_space_vert.xyz; + v_normal = (u_normalMatrix * normalize(a_normal)).xyz; + } + +fragment41core = + #version 410 + uniform mediump vec4 u_ambientColor; + uniform mediump vec4 u_diffuseColor; + uniform highp vec3 u_lightPosition; + + uniform mediump float u_opacity; + + in highp vec3 v_vertex; + in highp vec3 v_normal; + + out vec4 frag_color; + + void main() + { + mediump vec4 finalColor = vec4(0.0); + + /* Ambient Component */ + finalColor += u_ambientColor; + + highp vec3 normal = normalize(v_normal); + highp vec3 lightDir = normalize(u_lightPosition - v_vertex); + + /* Diffuse Component */ + highp float NdotL = clamp(abs(dot(normal, lightDir)), 0.0, 1.0); + finalColor += (NdotL * u_diffuseColor); + + frag_color = finalColor; + frag_color.a = u_opacity; + } + [defaults] u_ambientColor = [0.1, 0.1, 0.1, 1.0] u_diffuseColor = [0.4, 0.4, 0.4, 1.0] diff --git a/resources/themes/cura/theme.json b/resources/themes/cura/theme.json index 23ebacd7f9..acce27b74e 100644 --- a/resources/themes/cura/theme.json +++ b/resources/themes/cura/theme.json @@ -24,6 +24,10 @@ "bold": true, "family": "Open Sans" }, + "very_small": { + "size": 1.0, + "family": "Open Sans" + }, "button_tooltip": { "size": 1.0, "family": "Open Sans" @@ -247,9 +251,11 @@ "sidebar_header_mode_toggle": [0.0, 2.0], "sidebar_header_mode_tabs": [0.0, 3.0], "sidebar_lining": [0.5, 0.5], + "sidebar_lining_thin": [0.2, 0.2], "sidebar_setup": [0.0, 2.0], "sidebar_tabs": [0.0, 3.5], "sidebar_inputfields": [0.0, 2.0], + "sidebar_extruder_box": [0.0, 6.0], "simple_mode_infill_caption": [0.0, 5.0], "simple_mode_infill_height": [0.0, 8.0], diff --git a/run_mypy.py b/run_mypy.py new file mode 100644 index 0000000000..88adea8db9 --- /dev/null +++ b/run_mypy.py @@ -0,0 +1,59 @@ +#!env python +import os +import sys +import subprocess + +# A quick Python implementation of unix 'where' command. +def where(exeName, searchPath=os.getenv("PATH")): + paths = searchPath.split(";" if sys.platform == "win32" else ":") + for path in paths: + candidatePath = os.path.join(path, exeName) + if os.path.exists(candidatePath): + return candidatePath + return None + +def findModules(path): + result = [] + for entry in os.scandir(path): + if entry.is_dir() and os.path.exists(os.path.join(path, entry.name, "__init__.py")): + result.append(entry.name) + return result + +def main(): + # Find Uranium via the PYTHONPATH var + uraniumUMPath = where("UM", os.getenv("PYTHONPATH")) + if uraniumUMPath is None: + uraniumUMPath = os.path.join("..", "Uranium") + uraniumPath = os.path.dirname(uraniumUMPath) + + mypyPathParts = [".", os.path.join(".", "plugins"), os.path.join(".", "plugins", "VersionUpgrade"), + uraniumPath, os.path.join(uraniumPath, "stubs")] + if sys.platform == "win32": + os.putenv("MYPYPATH", ";".join(mypyPathParts)) + else: + os.putenv("MYPYPATH", ":".join(mypyPathParts)) + + # Mypy really needs to be run via its Python script otherwise it can't find its data files. + mypyExe = where("mypy.bat" if sys.platform == "win32" else "mypy") + mypyModule = os.path.join(os.path.dirname(mypyExe), "mypy") + + plugins = findModules("plugins") + plugins.sort() + + mods = ["cura"] + plugins + findModules("plugins/VersionUpgrade") + + for mod in mods: + print("------------- Checking module {mod}".format(**locals())) + result = subprocess.run([sys.executable, mypyModule, "-p", mod]) + if result.returncode != 0: + print(""" + Module {mod} failed checking. :( + """.format(**locals())) + break + else: + print(""" + + Done checking. All is good. + """) + return 0 +sys.exit(main())