mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 22:47:29 -06:00
Merge branch 'master' into fix_layer_number_width
This commit is contained in:
commit
a8cb1e25b4
294 changed files with 5983 additions and 2588 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -15,6 +15,7 @@ LC_MESSAGES
|
||||||
.cache
|
.cache
|
||||||
*.qmlc
|
*.qmlc
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
|
.pytest_cache
|
||||||
|
|
||||||
#MacOS
|
#MacOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
@ -25,6 +26,7 @@ LC_MESSAGES
|
||||||
*.lprof
|
*.lprof
|
||||||
*~
|
*~
|
||||||
*.qm
|
*.qm
|
||||||
|
.directory
|
||||||
.idea
|
.idea
|
||||||
cura.desktop
|
cura.desktop
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ from cura.Backups.BackupsManager import BackupsManager
|
||||||
# api = CuraAPI()
|
# api = CuraAPI()
|
||||||
# api.backups.createBackup()
|
# api.backups.createBackup()
|
||||||
# api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})``
|
# api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})``
|
||||||
|
|
||||||
class Backups:
|
class Backups:
|
||||||
manager = BackupsManager() # Re-used instance of the backups manager.
|
manager = BackupsManager() # Re-used instance of the backups manager.
|
||||||
|
|
||||||
|
|
33
cura/API/Interface/Settings.py
Normal file
33
cura/API/Interface/Settings.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
|
## The Interface.Settings API provides a version-proof bridge between Cura's
|
||||||
|
# (currently) sidebar UI and plug-ins that hook into it.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ``from cura.API import CuraAPI
|
||||||
|
# api = CuraAPI()
|
||||||
|
# api.interface.settings.getContextMenuItems()
|
||||||
|
# data = {
|
||||||
|
# "name": "My Plugin Action",
|
||||||
|
# "iconName": "my-plugin-icon",
|
||||||
|
# "actions": my_menu_actions,
|
||||||
|
# "menu_item": MyPluginAction(self)
|
||||||
|
# }
|
||||||
|
# api.interface.settings.addContextMenuItem(data)``
|
||||||
|
|
||||||
|
class Settings:
|
||||||
|
# Re-used instance of Cura:
|
||||||
|
application = CuraApplication.getInstance() # type: CuraApplication
|
||||||
|
|
||||||
|
## Add items to the sidebar context menu.
|
||||||
|
# \param menu_item dict containing the menu item to add.
|
||||||
|
def addContextMenuItem(self, menu_item: dict) -> None:
|
||||||
|
self.application.addSidebarCustomMenuItem(menu_item)
|
||||||
|
|
||||||
|
## Get all custom items currently added to the sidebar context menu.
|
||||||
|
# \return List containing all custom context menu items.
|
||||||
|
def getContextMenuItems(self) -> list:
|
||||||
|
return self.application.getSidebarCustomMenuItems()
|
24
cura/API/Interface/__init__.py
Normal file
24
cura/API/Interface/__init__.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
from cura.API.Interface.Settings import Settings
|
||||||
|
|
||||||
|
## The Interface class serves as a common root for the specific API
|
||||||
|
# methods for each interface element.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ``from cura.API import CuraAPI
|
||||||
|
# api = CuraAPI()
|
||||||
|
# api.interface.settings.addContextMenuItem()
|
||||||
|
# api.interface.viewport.addOverlay() # Not implemented, just a hypothetical
|
||||||
|
# api.interface.toolbar.getToolButtonCount() # Not implemented, just a hypothetical
|
||||||
|
# # etc.``
|
||||||
|
|
||||||
|
class Interface:
|
||||||
|
|
||||||
|
# For now we use the same API version to be consistent.
|
||||||
|
VERSION = PluginRegistry.APIVersion
|
||||||
|
|
||||||
|
# API methods specific to the settings portion of the UI
|
||||||
|
settings = Settings()
|
|
@ -2,6 +2,7 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from cura.API.Backups import Backups
|
from cura.API.Backups import Backups
|
||||||
|
from cura.API.Interface import Interface
|
||||||
|
|
||||||
## The official Cura API that plug-ins can use to interact with Cura.
|
## The official Cura API that plug-ins can use to interact with Cura.
|
||||||
#
|
#
|
||||||
|
@ -9,10 +10,14 @@ from cura.API.Backups import Backups
|
||||||
# this API provides a version-safe interface with proper deprecation warnings
|
# this API provides a version-safe interface with proper deprecation warnings
|
||||||
# etc. Usage of any other methods than the ones provided in this API can cause
|
# etc. Usage of any other methods than the ones provided in this API can cause
|
||||||
# plug-ins to be unstable.
|
# plug-ins to be unstable.
|
||||||
|
|
||||||
class CuraAPI:
|
class CuraAPI:
|
||||||
|
|
||||||
# For now we use the same API version to be consistent.
|
# For now we use the same API version to be consistent.
|
||||||
VERSION = PluginRegistry.APIVersion
|
VERSION = PluginRegistry.APIVersion
|
||||||
|
|
||||||
# Backups API.
|
# Backups API
|
||||||
backups = Backups()
|
backups = Backups()
|
||||||
|
|
||||||
|
# Interface API
|
||||||
|
interface = Interface()
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
from UM.Application import Application #To modify the maximum zoom level.
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Scene.Platform import Platform
|
from UM.Scene.Platform import Platform
|
||||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||||
|
@ -170,6 +171,12 @@ class BuildVolume(SceneNode):
|
||||||
if shape:
|
if shape:
|
||||||
self._shape = shape
|
self._shape = shape
|
||||||
|
|
||||||
|
## Get the length of the 3D diagonal through the build volume.
|
||||||
|
#
|
||||||
|
# This gives a sense of the scale of the build volume in general.
|
||||||
|
def getDiagonalSize(self) -> float:
|
||||||
|
return math.sqrt(self._width * self._width + self._height * self._height + self._depth * self._depth)
|
||||||
|
|
||||||
def getDisallowedAreas(self) -> List[Polygon]:
|
def getDisallowedAreas(self) -> List[Polygon]:
|
||||||
return self._disallowed_areas
|
return self._disallowed_areas
|
||||||
|
|
||||||
|
@ -235,6 +242,8 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
# Mark the node as outside build volume if the set extruder is disabled
|
# Mark the node as outside build volume if the set extruder is disabled
|
||||||
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||||
|
if extruder_position not in self._global_container_stack.extruders:
|
||||||
|
continue
|
||||||
if not self._global_container_stack.extruders[extruder_position].isEnabled:
|
if not self._global_container_stack.extruders[extruder_position].isEnabled:
|
||||||
node.setOutsideBuildArea(True)
|
node.setOutsideBuildArea(True)
|
||||||
continue
|
continue
|
||||||
|
@ -552,6 +561,12 @@ class BuildVolume(SceneNode):
|
||||||
if self._engine_ready:
|
if self._engine_ready:
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
||||||
|
camera = Application.getInstance().getController().getCameraTool()
|
||||||
|
if camera:
|
||||||
|
diagonal = self.getDiagonalSize()
|
||||||
|
if diagonal > 1:
|
||||||
|
camera.setZoomRange(min = 0.1, max = diagonal * 5) #You can zoom out up to 5 times the diagonal. This gives some space around the volume.
|
||||||
|
|
||||||
def _onEngineCreated(self):
|
def _onEngineCreated(self):
|
||||||
self._engine_ready = True
|
self._engine_ready = True
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
|
@ -4,15 +4,20 @@ from PyQt5.QtCore import QSize
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
|
||||||
|
|
||||||
class CameraImageProvider(QQuickImageProvider):
|
class CameraImageProvider(QQuickImageProvider):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QQuickImageProvider.__init__(self, QQuickImageProvider.Image)
|
super().__init__(QQuickImageProvider.Image)
|
||||||
|
|
||||||
## Request a new image.
|
## Request a new image.
|
||||||
def requestImage(self, id, size):
|
def requestImage(self, id, size):
|
||||||
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
|
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
|
||||||
try:
|
try:
|
||||||
return output_device.activePrinter.camera.getImage(), QSize(15, 15)
|
image = output_device.activePrinter.camera.getImage()
|
||||||
|
if image.isNull():
|
||||||
|
image = QImage()
|
||||||
|
|
||||||
|
return image, QSize(15, 15)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
return QImage(), QSize(15, 15)
|
return QImage(), QSize(15, 15)
|
||||||
|
|
|
@ -50,7 +50,8 @@ class CuraActions(QObject):
|
||||||
scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene()
|
scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene()
|
||||||
camera = scene.getActiveCamera()
|
camera = scene.getActiveCamera()
|
||||||
if camera:
|
if camera:
|
||||||
camera.setPosition(Vector(-80, 250, 700))
|
diagonal_size = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getDiagonalSize()
|
||||||
|
camera.setPosition(Vector(-80, 250, 700) * diagonal_size / 375)
|
||||||
camera.setPerspective(True)
|
camera.setPerspective(True)
|
||||||
camera.lookAt(Vector(0, 0, 0))
|
camera.lookAt(Vector(0, 0, 0))
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import copy
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import cast, TYPE_CHECKING, Optional
|
from typing import cast, TYPE_CHECKING
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
|
@ -68,9 +67,9 @@ from cura.Machines.Models.NozzleModel import NozzleModel
|
||||||
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
||||||
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
|
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
|
||||||
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||||
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
|
from cura.Machines.Models.FavoriteMaterialsModel import FavoriteMaterialsModel
|
||||||
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
|
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
|
||||||
from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
|
from cura.Machines.Models.MaterialBrandsModel import MaterialBrandsModel
|
||||||
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
||||||
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
||||||
from cura.Machines.Models.MachineManagementModel import MachineManagementModel
|
from cura.Machines.Models.MachineManagementModel import MachineManagementModel
|
||||||
|
@ -104,6 +103,8 @@ from cura.Settings.UserChangesModel import UserChangesModel
|
||||||
from cura.Settings.ExtrudersModel import ExtrudersModel
|
from cura.Settings.ExtrudersModel import ExtrudersModel
|
||||||
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
||||||
from cura.Settings.ContainerManager import ContainerManager
|
from cura.Settings.ContainerManager import ContainerManager
|
||||||
|
from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
|
||||||
|
import cura.Settings.cura_empty_instance_containers
|
||||||
|
|
||||||
from cura.ObjectsModel import ObjectsModel
|
from cura.ObjectsModel import ObjectsModel
|
||||||
|
|
||||||
|
@ -117,11 +118,12 @@ if TYPE_CHECKING:
|
||||||
numpy.seterr(all = "ignore")
|
numpy.seterr(all = "ignore")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode
|
from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode, CuraSDKVersion
|
||||||
except ImportError:
|
except ImportError:
|
||||||
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
|
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
|
||||||
CuraBuildType = ""
|
CuraBuildType = ""
|
||||||
CuraDebugMode = False
|
CuraDebugMode = False
|
||||||
|
CuraSDKVersion = ""
|
||||||
|
|
||||||
|
|
||||||
class CuraApplication(QtApplication):
|
class CuraApplication(QtApplication):
|
||||||
|
@ -213,7 +215,6 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
self._message_box_callback = None
|
self._message_box_callback = None
|
||||||
self._message_box_callback_arguments = []
|
self._message_box_callback_arguments = []
|
||||||
self._preferred_mimetype = ""
|
|
||||||
self._i18n_catalog = None
|
self._i18n_catalog = None
|
||||||
|
|
||||||
self._currently_loading_files = []
|
self._currently_loading_files = []
|
||||||
|
@ -226,6 +227,10 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
self._need_to_show_user_agreement = True
|
self._need_to_show_user_agreement = True
|
||||||
|
|
||||||
|
self._sidebar_custom_menu_items = [] # type: list # Keeps list of custom menu items for the side bar
|
||||||
|
|
||||||
|
self._plugins_loaded = False
|
||||||
|
|
||||||
# Backups
|
# Backups
|
||||||
self._auto_save = None
|
self._auto_save = None
|
||||||
self._save_data_enabled = True
|
self._save_data_enabled = True
|
||||||
|
@ -362,42 +367,23 @@ class CuraApplication(QtApplication):
|
||||||
# Add empty variant, material and quality containers.
|
# Add empty variant, material and quality containers.
|
||||||
# Since they are empty, they should never be serialized and instead just programmatically created.
|
# Since they are empty, they should never be serialized and instead just programmatically created.
|
||||||
# We need them to simplify the switching between materials.
|
# We need them to simplify the switching between materials.
|
||||||
empty_container = self._container_registry.getEmptyInstanceContainer()
|
self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container
|
||||||
self.empty_container = empty_container
|
|
||||||
|
|
||||||
empty_definition_changes_container = copy.deepcopy(empty_container)
|
self._container_registry.addContainer(
|
||||||
empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
|
cura.Settings.cura_empty_instance_containers.empty_definition_changes_container)
|
||||||
empty_definition_changes_container.setMetaDataEntry("type", "definition_changes")
|
self.empty_definition_changes_container = cura.Settings.cura_empty_instance_containers.empty_definition_changes_container
|
||||||
self._container_registry.addContainer(empty_definition_changes_container)
|
|
||||||
self.empty_definition_changes_container = empty_definition_changes_container
|
|
||||||
|
|
||||||
empty_variant_container = copy.deepcopy(empty_container)
|
self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_variant_container)
|
||||||
empty_variant_container.setMetaDataEntry("id", "empty_variant")
|
self.empty_variant_container = cura.Settings.cura_empty_instance_containers.empty_variant_container
|
||||||
empty_variant_container.setMetaDataEntry("type", "variant")
|
|
||||||
self._container_registry.addContainer(empty_variant_container)
|
|
||||||
self.empty_variant_container = empty_variant_container
|
|
||||||
|
|
||||||
empty_material_container = copy.deepcopy(empty_container)
|
self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_material_container)
|
||||||
empty_material_container.setMetaDataEntry("id", "empty_material")
|
self.empty_material_container = cura.Settings.cura_empty_instance_containers.empty_material_container
|
||||||
empty_material_container.setMetaDataEntry("type", "material")
|
|
||||||
self._container_registry.addContainer(empty_material_container)
|
|
||||||
self.empty_material_container = empty_material_container
|
|
||||||
|
|
||||||
empty_quality_container = copy.deepcopy(empty_container)
|
self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_quality_container)
|
||||||
empty_quality_container.setMetaDataEntry("id", "empty_quality")
|
self.empty_quality_container = cura.Settings.cura_empty_instance_containers.empty_quality_container
|
||||||
empty_quality_container.setName("Not Supported")
|
|
||||||
empty_quality_container.setMetaDataEntry("quality_type", "not_supported")
|
|
||||||
empty_quality_container.setMetaDataEntry("type", "quality")
|
|
||||||
empty_quality_container.setMetaDataEntry("supported", False)
|
|
||||||
self._container_registry.addContainer(empty_quality_container)
|
|
||||||
self.empty_quality_container = empty_quality_container
|
|
||||||
|
|
||||||
empty_quality_changes_container = copy.deepcopy(empty_container)
|
self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_quality_changes_container)
|
||||||
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
|
self.empty_quality_changes_container = cura.Settings.cura_empty_instance_containers.empty_quality_changes_container
|
||||||
empty_quality_changes_container.setMetaDataEntry("type", "quality_changes")
|
|
||||||
empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported")
|
|
||||||
self._container_registry.addContainer(empty_quality_changes_container)
|
|
||||||
self.empty_quality_changes_container = empty_quality_changes_container
|
|
||||||
|
|
||||||
# Initializes the version upgrade manager with by providing the paths for each resource type and the latest
|
# Initializes the version upgrade manager with by providing the paths for each resource type and the latest
|
||||||
# versions.
|
# versions.
|
||||||
|
@ -494,7 +480,9 @@ class CuraApplication(QtApplication):
|
||||||
preferences.addPreference("view/filter_current_build_plate", False)
|
preferences.addPreference("view/filter_current_build_plate", False)
|
||||||
preferences.addPreference("cura/sidebar_collapsed", False)
|
preferences.addPreference("cura/sidebar_collapsed", False)
|
||||||
|
|
||||||
self._need_to_show_user_agreement = not self.getPreferences().getValue("general/accepted_user_agreement")
|
preferences.addPreference("cura/favorite_materials", ";".join([]))
|
||||||
|
|
||||||
|
self._need_to_show_user_agreement = not preferences.getValue("general/accepted_user_agreement")
|
||||||
|
|
||||||
for key in [
|
for key in [
|
||||||
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
|
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
|
||||||
|
@ -508,9 +496,6 @@ class CuraApplication(QtApplication):
|
||||||
self.applicationShuttingDown.connect(self.saveSettings)
|
self.applicationShuttingDown.connect(self.saveSettings)
|
||||||
self.engineCreatedSignal.connect(self._onEngineCreated)
|
self.engineCreatedSignal.connect(self._onEngineCreated)
|
||||||
|
|
||||||
self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
|
||||||
self._onGlobalContainerChanged()
|
|
||||||
|
|
||||||
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
|
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
|
||||||
|
|
||||||
CuraApplication.Created = True
|
CuraApplication.Created = True
|
||||||
|
@ -625,7 +610,7 @@ class CuraApplication(QtApplication):
|
||||||
# Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
|
# Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
|
||||||
def saveSettings(self):
|
def saveSettings(self):
|
||||||
if not self.started or not self._save_data_enabled:
|
if not self.started or not self._save_data_enabled:
|
||||||
# Do not do saving during application start or when data should not be safed on quit.
|
# Do not do saving during application start or when data should not be saved on quit.
|
||||||
return
|
return
|
||||||
ContainerRegistry.getInstance().saveDirtyContainers()
|
ContainerRegistry.getInstance().saveDirtyContainers()
|
||||||
self.savePreferences()
|
self.savePreferences()
|
||||||
|
@ -774,7 +759,10 @@ class CuraApplication(QtApplication):
|
||||||
# Initialize camera
|
# Initialize camera
|
||||||
root = controller.getScene().getRoot()
|
root = controller.getScene().getRoot()
|
||||||
camera = Camera("3d", root)
|
camera = Camera("3d", root)
|
||||||
camera.setPosition(Vector(-80, 250, 700))
|
diagonal = self.getBuildVolume().getDiagonalSize()
|
||||||
|
if diagonal < 1: #No printer added yet. Set a default camera distance for normal-sized printers.
|
||||||
|
diagonal = 375
|
||||||
|
camera.setPosition(Vector(-80, 250, 700) * diagonal / 375)
|
||||||
camera.setPerspective(True)
|
camera.setPerspective(True)
|
||||||
camera.lookAt(Vector(0, 0, 0))
|
camera.lookAt(Vector(0, 0, 0))
|
||||||
controller.getScene().setActiveCamera("3d")
|
controller.getScene().setActiveCamera("3d")
|
||||||
|
@ -908,6 +896,7 @@ class CuraApplication(QtApplication):
|
||||||
engine.rootContext().setContextProperty("CuraApplication", self)
|
engine.rootContext().setContextProperty("CuraApplication", self)
|
||||||
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
||||||
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
|
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
|
||||||
|
engine.rootContext().setContextProperty("CuraSDKVersion", CuraSDKVersion)
|
||||||
|
|
||||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
||||||
|
|
||||||
|
@ -924,9 +913,9 @@ class CuraApplication(QtApplication):
|
||||||
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
||||||
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||||
|
|
||||||
|
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
||||||
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
||||||
qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
|
qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel")
|
||||||
qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
|
|
||||||
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
||||||
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
|
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
|
||||||
|
|
||||||
|
@ -942,6 +931,7 @@ class CuraApplication(QtApplication):
|
||||||
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
||||||
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
|
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
|
||||||
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance)
|
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance)
|
||||||
|
qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel")
|
||||||
|
|
||||||
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
|
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
|
||||||
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
|
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
|
||||||
|
@ -989,30 +979,14 @@ class CuraApplication(QtApplication):
|
||||||
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
|
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
|
||||||
self._camera_animation.start()
|
self._camera_animation.start()
|
||||||
|
|
||||||
def _onGlobalContainerChanged(self):
|
|
||||||
if self._global_container_stack is not None:
|
|
||||||
machine_file_formats = [file_type.strip() for file_type in self._global_container_stack.getMetaDataEntry("file_formats").split(";")]
|
|
||||||
new_preferred_mimetype = ""
|
|
||||||
if machine_file_formats:
|
|
||||||
new_preferred_mimetype = machine_file_formats[0]
|
|
||||||
|
|
||||||
if new_preferred_mimetype != self._preferred_mimetype:
|
|
||||||
self._preferred_mimetype = new_preferred_mimetype
|
|
||||||
self.preferredOutputMimetypeChanged.emit()
|
|
||||||
|
|
||||||
requestAddPrinter = pyqtSignal()
|
requestAddPrinter = pyqtSignal()
|
||||||
activityChanged = pyqtSignal()
|
activityChanged = pyqtSignal()
|
||||||
sceneBoundingBoxChanged = pyqtSignal()
|
sceneBoundingBoxChanged = pyqtSignal()
|
||||||
preferredOutputMimetypeChanged = pyqtSignal()
|
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = activityChanged)
|
@pyqtProperty(bool, notify = activityChanged)
|
||||||
def platformActivity(self):
|
def platformActivity(self):
|
||||||
return self._platform_activity
|
return self._platform_activity
|
||||||
|
|
||||||
@pyqtProperty(str, notify=preferredOutputMimetypeChanged)
|
|
||||||
def preferredOutputMimetype(self):
|
|
||||||
return self._preferred_mimetype
|
|
||||||
|
|
||||||
@pyqtProperty(str, notify = sceneBoundingBoxChanged)
|
@pyqtProperty(str, notify = sceneBoundingBoxChanged)
|
||||||
def getSceneBoundingBoxString(self):
|
def getSceneBoundingBoxString(self):
|
||||||
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
|
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
|
||||||
|
@ -1599,12 +1573,13 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
def _readMeshFinished(self, job):
|
def _readMeshFinished(self, job):
|
||||||
nodes = job.getResult()
|
nodes = job.getResult()
|
||||||
filename = job.getFileName()
|
file_name = job.getFileName()
|
||||||
self._currently_loading_files.remove(filename)
|
file_name_lower = file_name.lower()
|
||||||
|
file_extension = file_name_lower.split(".")[-1]
|
||||||
|
self._currently_loading_files.remove(file_name)
|
||||||
|
|
||||||
self.fileLoaded.emit(filename)
|
self.fileLoaded.emit(file_name)
|
||||||
arrange_objects_on_load = not self.getPreferences().getValue("cura/use_multi_build_plate")
|
target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
||||||
target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
|
|
||||||
|
|
||||||
root = self.getController().getScene().getRoot()
|
root = self.getController().getScene().getRoot()
|
||||||
fixed_nodes = []
|
fixed_nodes = []
|
||||||
|
@ -1635,15 +1610,11 @@ class CuraApplication(QtApplication):
|
||||||
node.scale(original_node.getScale())
|
node.scale(original_node.getScale())
|
||||||
|
|
||||||
node.setSelectable(True)
|
node.setSelectable(True)
|
||||||
node.setName(os.path.basename(filename))
|
node.setName(os.path.basename(file_name))
|
||||||
self.getBuildVolume().checkBoundsAndUpdate(node)
|
self.getBuildVolume().checkBoundsAndUpdate(node)
|
||||||
|
|
||||||
is_non_sliceable = False
|
is_non_sliceable = "." + file_extension in self._non_sliceable_extensions
|
||||||
filename_lower = filename.lower()
|
|
||||||
for extension in self._non_sliceable_extensions:
|
|
||||||
if filename_lower.endswith(extension):
|
|
||||||
is_non_sliceable = True
|
|
||||||
break
|
|
||||||
if is_non_sliceable:
|
if is_non_sliceable:
|
||||||
self.callLater(lambda: self.getController().setActiveView("SimulationView"))
|
self.callLater(lambda: self.getController().setActiveView("SimulationView"))
|
||||||
|
|
||||||
|
@ -1662,7 +1633,7 @@ class CuraApplication(QtApplication):
|
||||||
if not child.getDecorator(ConvexHullDecorator):
|
if not child.getDecorator(ConvexHullDecorator):
|
||||||
child.addDecorator(ConvexHullDecorator())
|
child.addDecorator(ConvexHullDecorator())
|
||||||
|
|
||||||
if arrange_objects_on_load:
|
if file_extension != "3mf":
|
||||||
if node.callDecoration("isSliceable"):
|
if node.callDecoration("isSliceable"):
|
||||||
# Only check position if it's not already blatantly obvious that it won't fit.
|
# Only check position if it's not already blatantly obvious that it won't fit.
|
||||||
if node.getBoundingBox() is None or self._volume.getBoundingBox() is None or node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
if node.getBoundingBox() is None or self._volume.getBoundingBox() is None or node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
||||||
|
@ -1696,7 +1667,7 @@ class CuraApplication(QtApplication):
|
||||||
if select_models_on_load:
|
if select_models_on_load:
|
||||||
Selection.add(node)
|
Selection.add(node)
|
||||||
|
|
||||||
self.fileCompleted.emit(filename)
|
self.fileCompleted.emit(file_name)
|
||||||
|
|
||||||
def addNonSliceableExtension(self, extension):
|
def addNonSliceableExtension(self, extension):
|
||||||
self._non_sliceable_extensions.append(extension)
|
self._non_sliceable_extensions.append(extension)
|
||||||
|
@ -1732,3 +1703,10 @@ class CuraApplication(QtApplication):
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def showMoreInformationDialogForAnonymousDataCollection(self):
|
def showMoreInformationDialogForAnonymousDataCollection(self):
|
||||||
cast(SliceInfo, self._plugin_registry.getPluginObject("SliceInfoPlugin")).showMoreInfoDialog()
|
cast(SliceInfo, self._plugin_registry.getPluginObject("SliceInfoPlugin")).showMoreInfoDialog()
|
||||||
|
|
||||||
|
def addSidebarCustomMenuItem(self, menu_item: dict) -> None:
|
||||||
|
self._sidebar_custom_menu_items.append(menu_item)
|
||||||
|
|
||||||
|
def getSidebarCustomMenuItems(self) -> list:
|
||||||
|
return self._sidebar_custom_menu_items
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ class LayerPolygon:
|
||||||
__number_of_types = 11
|
__number_of_types = 11
|
||||||
|
|
||||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
||||||
|
|
||||||
## LayerPolygon, used in ProcessSlicedLayersJob
|
## LayerPolygon, used in ProcessSlicedLayersJob
|
||||||
# \param extruder
|
# \param extruder
|
||||||
# \param line_types array with line_types
|
# \param line_types array with line_types
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
import copy
|
import copy
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, cast
|
import json
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Dict, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
|
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ from UM.Util import parseBool
|
||||||
|
|
||||||
from .MaterialNode import MaterialNode
|
from .MaterialNode import MaterialNode
|
||||||
from .MaterialGroup import MaterialGroup
|
from .MaterialGroup import MaterialGroup
|
||||||
|
from .VariantType import VariantType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
@ -38,7 +39,8 @@ if TYPE_CHECKING:
|
||||||
#
|
#
|
||||||
class MaterialManager(QObject):
|
class MaterialManager(QObject):
|
||||||
|
|
||||||
materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated.
|
materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated.
|
||||||
|
favoritesUpdated = pyqtSignal() # Emitted whenever the favorites are changed
|
||||||
|
|
||||||
def __init__(self, container_registry, parent = None):
|
def __init__(self, container_registry, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
@ -47,7 +49,7 @@ class MaterialManager(QObject):
|
||||||
|
|
||||||
self._fallback_materials_map = dict() # material_type -> generic material metadata
|
self._fallback_materials_map = dict() # material_type -> generic material metadata
|
||||||
self._material_group_map = dict() # root_material_id -> MaterialGroup
|
self._material_group_map = dict() # root_material_id -> MaterialGroup
|
||||||
self._diameter_machine_variant_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
|
self._diameter_machine_nozzle_buildplate_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
|
||||||
|
|
||||||
# We're using these two maps to convert between the specific diameter material id and the generic material id
|
# We're using these two maps to convert between the specific diameter material id and the generic material id
|
||||||
# because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
|
# because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
|
||||||
|
@ -75,6 +77,8 @@ class MaterialManager(QObject):
|
||||||
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
||||||
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
||||||
|
|
||||||
|
self._favorites = set()
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
# Find all materials and put them in a matrix for quick search.
|
# Find all materials and put them in a matrix for quick search.
|
||||||
material_metadatas = {metadata["id"]: metadata for metadata in
|
material_metadatas = {metadata["id"]: metadata for metadata in
|
||||||
|
@ -187,52 +191,83 @@ class MaterialManager(QObject):
|
||||||
self._diameter_material_map[root_material_id] = default_root_material_id
|
self._diameter_material_map[root_material_id] = default_root_material_id
|
||||||
|
|
||||||
# Map #4
|
# Map #4
|
||||||
# "machine" -> "variant_name" -> "root material ID" -> specific material InstanceContainer
|
# "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
|
||||||
# Construct the "machine" -> "variant" -> "root material ID" -> specific material InstanceContainer
|
self._diameter_machine_nozzle_buildplate_material_map = dict()
|
||||||
self._diameter_machine_variant_material_map = dict()
|
|
||||||
for material_metadata in material_metadatas.values():
|
for material_metadata in material_metadatas.values():
|
||||||
# We don't store empty material in the lookup tables
|
self.__addMaterialMetadataIntoLookupTree(material_metadata)
|
||||||
if material_metadata["id"] == "empty_material":
|
|
||||||
continue
|
|
||||||
|
|
||||||
root_material_id = material_metadata["base_file"]
|
|
||||||
definition = material_metadata["definition"]
|
|
||||||
approximate_diameter = material_metadata["approximate_diameter"]
|
|
||||||
|
|
||||||
if approximate_diameter not in self._diameter_machine_variant_material_map:
|
|
||||||
self._diameter_machine_variant_material_map[approximate_diameter] = {}
|
|
||||||
|
|
||||||
machine_variant_material_map = self._diameter_machine_variant_material_map[approximate_diameter]
|
|
||||||
if definition not in machine_variant_material_map:
|
|
||||||
machine_variant_material_map[definition] = MaterialNode()
|
|
||||||
|
|
||||||
machine_node = machine_variant_material_map[definition]
|
|
||||||
variant_name = material_metadata.get("variant_name")
|
|
||||||
if not variant_name:
|
|
||||||
# if there is no variant, this material is for the machine, so put its metadata in the machine node.
|
|
||||||
machine_node.material_map[root_material_id] = MaterialNode(material_metadata)
|
|
||||||
else:
|
|
||||||
# this material is variant-specific, so we save it in a variant-specific node under the
|
|
||||||
# machine-specific node
|
|
||||||
|
|
||||||
# Check first if the variant exist in the manager
|
|
||||||
existing_variant = self._application.getVariantManager().getVariantNode(definition, variant_name)
|
|
||||||
if existing_variant is not None:
|
|
||||||
if variant_name not in machine_node.children_map:
|
|
||||||
machine_node.children_map[variant_name] = MaterialNode()
|
|
||||||
|
|
||||||
variant_node = machine_node.children_map[variant_name]
|
|
||||||
if root_material_id in variant_node.material_map: # We shouldn't have duplicated variant-specific materials for the same machine.
|
|
||||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id)
|
|
||||||
continue
|
|
||||||
variant_node.material_map[root_material_id] = MaterialNode(material_metadata)
|
|
||||||
else:
|
|
||||||
# Add this container id to the wrong containers list in the registry
|
|
||||||
Logger.log("w", "Not adding {id} to the material manager because the variant does not exist.".format(id = material_metadata["id"]))
|
|
||||||
self._container_registry.addWrongContainerId(material_metadata["id"])
|
|
||||||
|
|
||||||
self.materialsUpdated.emit()
|
self.materialsUpdated.emit()
|
||||||
|
|
||||||
|
favorites = self._application.getPreferences().getValue("cura/favorite_materials")
|
||||||
|
for item in favorites.split(";"):
|
||||||
|
self._favorites.add(item)
|
||||||
|
self.favoritesUpdated.emit()
|
||||||
|
|
||||||
|
def __addMaterialMetadataIntoLookupTree(self, material_metadata: dict) -> None:
|
||||||
|
material_id = material_metadata["id"]
|
||||||
|
|
||||||
|
# We don't store empty material in the lookup tables
|
||||||
|
if material_id == "empty_material":
|
||||||
|
return
|
||||||
|
|
||||||
|
root_material_id = material_metadata["base_file"]
|
||||||
|
definition = material_metadata["definition"]
|
||||||
|
approximate_diameter = material_metadata["approximate_diameter"]
|
||||||
|
|
||||||
|
if approximate_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
||||||
|
self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] = {}
|
||||||
|
|
||||||
|
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[
|
||||||
|
approximate_diameter]
|
||||||
|
if definition not in machine_nozzle_buildplate_material_map:
|
||||||
|
machine_nozzle_buildplate_material_map[definition] = MaterialNode()
|
||||||
|
|
||||||
|
# This is a list of information regarding the intermediate nodes:
|
||||||
|
# nozzle -> buildplate
|
||||||
|
nozzle_name = material_metadata.get("variant_name")
|
||||||
|
buildplate_name = material_metadata.get("buildplate_name")
|
||||||
|
intermediate_node_info_list = [(nozzle_name, VariantType.NOZZLE),
|
||||||
|
(buildplate_name, VariantType.BUILD_PLATE),
|
||||||
|
]
|
||||||
|
|
||||||
|
variant_manager = self._application.getVariantManager()
|
||||||
|
|
||||||
|
machine_node = machine_nozzle_buildplate_material_map[definition]
|
||||||
|
current_node = machine_node
|
||||||
|
current_intermediate_node_info_idx = 0
|
||||||
|
error_message = None # type: Optional[str]
|
||||||
|
while current_intermediate_node_info_idx < len(intermediate_node_info_list):
|
||||||
|
variant_name, variant_type = intermediate_node_info_list[current_intermediate_node_info_idx]
|
||||||
|
if variant_name is not None:
|
||||||
|
# The new material has a specific variant, so it needs to be added to that specific branch in the tree.
|
||||||
|
variant = variant_manager.getVariantNode(definition, variant_name, variant_type)
|
||||||
|
if variant is None:
|
||||||
|
error_message = "Material {id} contains a variant {name} that does not exist.".format(
|
||||||
|
id = material_metadata["id"], name = variant_name)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Update the current node to advance to a more specific branch
|
||||||
|
if variant_name not in current_node.children_map:
|
||||||
|
current_node.children_map[variant_name] = MaterialNode()
|
||||||
|
current_node = current_node.children_map[variant_name]
|
||||||
|
|
||||||
|
current_intermediate_node_info_idx += 1
|
||||||
|
|
||||||
|
if error_message is not None:
|
||||||
|
Logger.log("e", "%s It will not be added into the material lookup tree.", error_message)
|
||||||
|
self._container_registry.addWrongContainerId(material_metadata["id"])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add the material to the current tree node, which is the deepest (the most specific) branch we can find.
|
||||||
|
# Sanity check: Make sure that there is no duplicated materials.
|
||||||
|
if root_material_id in current_node.material_map:
|
||||||
|
Logger.log("e", "Duplicated material [%s] with root ID [%s]. It has already been added.",
|
||||||
|
material_id, root_material_id)
|
||||||
|
ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
current_node.material_map[root_material_id] = MaterialNode(material_metadata)
|
||||||
|
|
||||||
def _updateMaps(self):
|
def _updateMaps(self):
|
||||||
Logger.log("i", "Updating material lookup data ...")
|
Logger.log("i", "Updating material lookup data ...")
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
@ -263,45 +298,52 @@ class MaterialManager(QObject):
|
||||||
#
|
#
|
||||||
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
|
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
|
||||||
#
|
#
|
||||||
def getAvailableMaterials(self, machine_definition: "DefinitionContainer", extruder_variant_name: Optional[str],
|
def getAvailableMaterials(self, machine_definition: "DefinitionContainer", nozzle_name: Optional[str],
|
||||||
diameter: float) -> Dict[str, MaterialNode]:
|
buildplate_name: Optional[str], diameter: float) -> Dict[str, MaterialNode]:
|
||||||
# round the diameter to get the approximate diameter
|
# round the diameter to get the approximate diameter
|
||||||
rounded_diameter = str(round(diameter))
|
rounded_diameter = str(round(diameter))
|
||||||
if rounded_diameter not in self._diameter_machine_variant_material_map:
|
if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
||||||
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
|
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
machine_definition_id = machine_definition.getId()
|
machine_definition_id = machine_definition.getId()
|
||||||
|
|
||||||
# If there are variant materials, get the variant material
|
# If there are nozzle-and-or-buildplate materials, get the nozzle-and-or-buildplate material
|
||||||
machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
|
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
|
||||||
machine_node = machine_variant_material_map.get(machine_definition_id)
|
machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
|
||||||
default_machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
|
default_machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)
|
||||||
variant_node = None
|
nozzle_node = None
|
||||||
if extruder_variant_name is not None and machine_node is not None:
|
buildplate_node = None
|
||||||
variant_node = machine_node.getChildNode(extruder_variant_name)
|
if nozzle_name is not None and machine_node is not None:
|
||||||
|
nozzle_node = machine_node.getChildNode(nozzle_name)
|
||||||
|
# Get buildplate node if possible
|
||||||
|
if nozzle_node is not None and buildplate_name is not None:
|
||||||
|
buildplate_node = nozzle_node.getChildNode(buildplate_name)
|
||||||
|
|
||||||
nodes_to_check = [variant_node, machine_node, default_machine_node]
|
nodes_to_check = [buildplate_node, nozzle_node, machine_node, default_machine_node]
|
||||||
|
|
||||||
# Fallback mechanism of finding materials:
|
# Fallback mechanism of finding materials:
|
||||||
# 1. variant-specific material
|
# 1. buildplate-specific material
|
||||||
# 2. machine-specific material
|
# 2. nozzle-specific material
|
||||||
# 3. generic material (for fdmprinter)
|
# 3. machine-specific material
|
||||||
|
# 4. generic material (for fdmprinter)
|
||||||
machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
|
machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
|
||||||
|
|
||||||
material_id_metadata_dict = dict() # type: Dict[str, MaterialNode]
|
material_id_metadata_dict = dict() # type: Dict[str, MaterialNode]
|
||||||
for node in nodes_to_check:
|
for current_node in nodes_to_check:
|
||||||
if node is not None:
|
if current_node is None:
|
||||||
# Only exclude the materials that are explicitly specified in the "exclude_materials" field.
|
continue
|
||||||
# Do not exclude other materials that are of the same type.
|
|
||||||
for material_id, node in node.material_map.items():
|
|
||||||
if material_id in machine_exclude_materials:
|
|
||||||
Logger.log("d", "Exclude material [%s] for machine [%s]",
|
|
||||||
material_id, machine_definition.getId())
|
|
||||||
continue
|
|
||||||
|
|
||||||
if material_id not in material_id_metadata_dict:
|
# Only exclude the materials that are explicitly specified in the "exclude_materials" field.
|
||||||
material_id_metadata_dict[material_id] = node
|
# Do not exclude other materials that are of the same type.
|
||||||
|
for material_id, node in current_node.material_map.items():
|
||||||
|
if material_id in machine_exclude_materials:
|
||||||
|
Logger.log("d", "Exclude material [%s] for machine [%s]",
|
||||||
|
material_id, machine_definition.getId())
|
||||||
|
continue
|
||||||
|
|
||||||
|
if material_id not in material_id_metadata_dict:
|
||||||
|
material_id_metadata_dict[material_id] = node
|
||||||
|
|
||||||
return material_id_metadata_dict
|
return material_id_metadata_dict
|
||||||
|
|
||||||
|
@ -310,13 +352,14 @@ class MaterialManager(QObject):
|
||||||
#
|
#
|
||||||
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
|
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
|
||||||
extruder_stack: "ExtruderStack") -> Optional[dict]:
|
extruder_stack: "ExtruderStack") -> Optional[dict]:
|
||||||
variant_name = None
|
buildplate_name = machine.getBuildplateName()
|
||||||
|
nozzle_name = None
|
||||||
if extruder_stack.variant.getId() != "empty_variant":
|
if extruder_stack.variant.getId() != "empty_variant":
|
||||||
variant_name = extruder_stack.variant.getName()
|
nozzle_name = extruder_stack.variant.getName()
|
||||||
diameter = extruder_stack.approximateMaterialDiameter
|
diameter = extruder_stack.approximateMaterialDiameter
|
||||||
|
|
||||||
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
|
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
|
||||||
return self.getAvailableMaterials(machine.definition, variant_name, diameter)
|
return self.getAvailableMaterials(machine.definition, nozzle_name, buildplate_name, diameter)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Gets MaterialNode for the given extruder and machine with the given material name.
|
# Gets MaterialNode for the given extruder and machine with the given material name.
|
||||||
|
@ -324,32 +367,36 @@ class MaterialManager(QObject):
|
||||||
# 1. the given machine doesn't have materials;
|
# 1. the given machine doesn't have materials;
|
||||||
# 2. cannot find any material InstanceContainers with the given settings.
|
# 2. cannot find any material InstanceContainers with the given settings.
|
||||||
#
|
#
|
||||||
def getMaterialNode(self, machine_definition_id: str, extruder_variant_name: Optional[str],
|
def getMaterialNode(self, machine_definition_id: str, nozzle_name: Optional[str],
|
||||||
diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
|
buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
|
||||||
# round the diameter to get the approximate diameter
|
# round the diameter to get the approximate diameter
|
||||||
rounded_diameter = str(round(diameter))
|
rounded_diameter = str(round(diameter))
|
||||||
if rounded_diameter not in self._diameter_machine_variant_material_map:
|
if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
||||||
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]",
|
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]",
|
||||||
diameter, rounded_diameter, root_material_id)
|
diameter, rounded_diameter, root_material_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# If there are variant materials, get the variant material
|
# If there are nozzle materials, get the nozzle-specific material
|
||||||
machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
|
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
|
||||||
machine_node = machine_variant_material_map.get(machine_definition_id)
|
machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
|
||||||
variant_node = None
|
nozzle_node = None
|
||||||
|
buildplate_node = None
|
||||||
|
|
||||||
# Fallback for "fdmprinter" if the machine-specific materials cannot be found
|
# Fallback for "fdmprinter" if the machine-specific materials cannot be found
|
||||||
if machine_node is None:
|
if machine_node is None:
|
||||||
machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
|
machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)
|
||||||
if machine_node is not None and extruder_variant_name is not None:
|
if machine_node is not None and nozzle_name is not None:
|
||||||
variant_node = machine_node.getChildNode(extruder_variant_name)
|
nozzle_node = machine_node.getChildNode(nozzle_name)
|
||||||
|
if nozzle_node is not None and buildplate_name is not None:
|
||||||
|
buildplate_node = nozzle_node.getChildNode(buildplate_name)
|
||||||
|
|
||||||
# Fallback mechanism of finding materials:
|
# Fallback mechanism of finding materials:
|
||||||
# 1. variant-specific material
|
# 1. buildplate-specific material
|
||||||
# 2. machine-specific material
|
# 2. nozzle-specific material
|
||||||
# 3. generic material (for fdmprinter)
|
# 3. machine-specific material
|
||||||
nodes_to_check = [variant_node, machine_node,
|
# 4. generic material (for fdmprinter)
|
||||||
machine_variant_material_map.get(self._default_machine_definition_id)]
|
nodes_to_check = [buildplate_node, nozzle_node, machine_node,
|
||||||
|
machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)]
|
||||||
|
|
||||||
material_node = None
|
material_node = None
|
||||||
for node in nodes_to_check:
|
for node in nodes_to_check:
|
||||||
|
@ -366,7 +413,8 @@ class MaterialManager(QObject):
|
||||||
# 1. the given machine doesn't have materials;
|
# 1. the given machine doesn't have materials;
|
||||||
# 2. cannot find any material InstanceContainers with the given settings.
|
# 2. cannot find any material InstanceContainers with the given settings.
|
||||||
#
|
#
|
||||||
def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, extruder_variant_name: str, material_guid: str) -> Optional["MaterialNode"]:
|
def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, nozzle_name: str,
|
||||||
|
buildplate_name: Optional[str], material_guid: str) -> Optional["MaterialNode"]:
|
||||||
node = None
|
node = None
|
||||||
machine_definition = global_stack.definition
|
machine_definition = global_stack.definition
|
||||||
extruder_definition = global_stack.extruders[position].definition
|
extruder_definition = global_stack.extruders[position].definition
|
||||||
|
@ -385,7 +433,7 @@ class MaterialManager(QObject):
|
||||||
Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
|
Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
|
node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name,
|
||||||
material_diameter, root_material_id)
|
material_diameter, root_material_id)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
@ -413,13 +461,17 @@ class MaterialManager(QObject):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
## Get default material for given global stack, extruder position and extruder variant name
|
## Get default material for given global stack, extruder position and extruder nozzle name
|
||||||
# you can provide the extruder_definition and then the position is ignored (useful when building up global stack in CuraStackBuilder)
|
# you can provide the extruder_definition and then the position is ignored (useful when building up global stack in CuraStackBuilder)
|
||||||
def getDefaultMaterial(self, global_stack: "GlobalStack", position: str, extruder_variant_name: Optional[str], extruder_definition: Optional["DefinitionContainer"] = None) -> Optional["MaterialNode"]:
|
def getDefaultMaterial(self, global_stack: "GlobalStack", position: str, nozzle_name: Optional[str],
|
||||||
|
extruder_definition: Optional["DefinitionContainer"] = None) -> Optional["MaterialNode"]:
|
||||||
node = None
|
node = None
|
||||||
|
|
||||||
|
buildplate_name = global_stack.getBuildplateName()
|
||||||
machine_definition = global_stack.definition
|
machine_definition = global_stack.definition
|
||||||
if extruder_definition is None:
|
if extruder_definition is None:
|
||||||
extruder_definition = global_stack.extruders[position].definition
|
extruder_definition = global_stack.extruders[position].definition
|
||||||
|
|
||||||
if extruder_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
|
if extruder_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
|
||||||
# At this point the extruder_definition is not None
|
# At this point the extruder_definition is not None
|
||||||
material_diameter = extruder_definition.getProperty("material_diameter", "value")
|
material_diameter = extruder_definition.getProperty("material_diameter", "value")
|
||||||
|
@ -428,7 +480,7 @@ class MaterialManager(QObject):
|
||||||
approximate_material_diameter = str(round(material_diameter))
|
approximate_material_diameter = str(round(material_diameter))
|
||||||
root_material_id = machine_definition.getMetaDataEntry("preferred_material")
|
root_material_id = machine_definition.getMetaDataEntry("preferred_material")
|
||||||
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter)
|
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter)
|
||||||
node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
|
node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name,
|
||||||
material_diameter, root_material_id)
|
material_diameter, root_material_id)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
@ -515,8 +567,8 @@ class MaterialManager(QObject):
|
||||||
if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
|
if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
|
||||||
new_id += "_" + container_to_copy.getMetaDataEntry("definition")
|
new_id += "_" + container_to_copy.getMetaDataEntry("definition")
|
||||||
if container_to_copy.getMetaDataEntry("variant_name"):
|
if container_to_copy.getMetaDataEntry("variant_name"):
|
||||||
variant_name = container_to_copy.getMetaDataEntry("variant_name")
|
nozzle_name = container_to_copy.getMetaDataEntry("variant_name")
|
||||||
new_id += "_" + variant_name.replace(" ", "_")
|
new_id += "_" + nozzle_name.replace(" ", "_")
|
||||||
|
|
||||||
new_container = copy.deepcopy(container_to_copy)
|
new_container = copy.deepcopy(container_to_copy)
|
||||||
new_container.getMetaData()["id"] = new_id
|
new_container.getMetaData()["id"] = new_id
|
||||||
|
@ -565,3 +617,25 @@ class MaterialManager(QObject):
|
||||||
new_base_id = new_id,
|
new_base_id = new_id,
|
||||||
new_metadata = new_metadata)
|
new_metadata = new_metadata)
|
||||||
return new_id
|
return new_id
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def addFavorite(self, root_material_id: str):
|
||||||
|
self._favorites.add(root_material_id)
|
||||||
|
self.favoritesUpdated.emit()
|
||||||
|
|
||||||
|
# Ensure all settings are saved.
|
||||||
|
self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
|
||||||
|
self._application.saveSettings()
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def removeFavorite(self, root_material_id: str):
|
||||||
|
self._favorites.remove(root_material_id)
|
||||||
|
self.favoritesUpdated.emit()
|
||||||
|
|
||||||
|
# Ensure all settings are saved.
|
||||||
|
self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
|
||||||
|
self._application.saveSettings()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def getFavorites(self):
|
||||||
|
return self._favorites
|
|
@ -2,45 +2,63 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
|
||||||
#
|
## This is the base model class for GenericMaterialsModel and MaterialBrandsModel.
|
||||||
# This is the base model class for GenericMaterialsModel and BrandMaterialsModel
|
# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
|
||||||
# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
|
# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
|
||||||
# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
|
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
|
||||||
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
|
|
||||||
#
|
|
||||||
class BaseMaterialsModel(ListModel):
|
class BaseMaterialsModel(ListModel):
|
||||||
RootMaterialIdRole = Qt.UserRole + 1
|
|
||||||
IdRole = Qt.UserRole + 2
|
|
||||||
NameRole = Qt.UserRole + 3
|
|
||||||
BrandRole = Qt.UserRole + 4
|
|
||||||
MaterialRole = Qt.UserRole + 5
|
|
||||||
ColorRole = Qt.UserRole + 6
|
|
||||||
ContainerNodeRole = Qt.UserRole + 7
|
|
||||||
|
|
||||||
extruderPositionChanged = pyqtSignal()
|
extruderPositionChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._application = Application.getInstance()
|
|
||||||
self._machine_manager = self._application.getMachineManager()
|
|
||||||
|
|
||||||
self.addRoleName(self.RootMaterialIdRole, "root_material_id")
|
from cura.CuraApplication import CuraApplication
|
||||||
self.addRoleName(self.IdRole, "id")
|
|
||||||
self.addRoleName(self.NameRole, "name")
|
self._application = CuraApplication.getInstance()
|
||||||
self.addRoleName(self.BrandRole, "brand")
|
|
||||||
self.addRoleName(self.MaterialRole, "material")
|
# Make these managers available to all material models
|
||||||
self.addRoleName(self.ColorRole, "color_name")
|
self._container_registry = self._application.getInstance().getContainerRegistry()
|
||||||
self.addRoleName(self.ContainerNodeRole, "container_node")
|
self._machine_manager = self._application.getMachineManager()
|
||||||
|
self._material_manager = self._application.getMaterialManager()
|
||||||
|
|
||||||
|
# Update the stack and the model data when the machine changes
|
||||||
|
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
|
||||||
|
|
||||||
|
# Update this model when switching machines
|
||||||
|
self._machine_manager.activeStackChanged.connect(self._update)
|
||||||
|
|
||||||
|
# Update this model when list of materials changes
|
||||||
|
self._material_manager.materialsUpdated.connect(self._update)
|
||||||
|
|
||||||
|
# Update this model when list of favorites changes
|
||||||
|
self._material_manager.favoritesUpdated.connect(self._update)
|
||||||
|
|
||||||
|
self.addRoleName(Qt.UserRole + 1, "root_material_id")
|
||||||
|
self.addRoleName(Qt.UserRole + 2, "id")
|
||||||
|
self.addRoleName(Qt.UserRole + 3, "GUID")
|
||||||
|
self.addRoleName(Qt.UserRole + 4, "name")
|
||||||
|
self.addRoleName(Qt.UserRole + 5, "brand")
|
||||||
|
self.addRoleName(Qt.UserRole + 6, "description")
|
||||||
|
self.addRoleName(Qt.UserRole + 7, "material")
|
||||||
|
self.addRoleName(Qt.UserRole + 8, "color_name")
|
||||||
|
self.addRoleName(Qt.UserRole + 9, "color_code")
|
||||||
|
self.addRoleName(Qt.UserRole + 10, "density")
|
||||||
|
self.addRoleName(Qt.UserRole + 11, "diameter")
|
||||||
|
self.addRoleName(Qt.UserRole + 12, "approximate_diameter")
|
||||||
|
self.addRoleName(Qt.UserRole + 13, "adhesion_info")
|
||||||
|
self.addRoleName(Qt.UserRole + 14, "is_read_only")
|
||||||
|
self.addRoleName(Qt.UserRole + 15, "container_node")
|
||||||
|
self.addRoleName(Qt.UserRole + 16, "is_favorite")
|
||||||
|
|
||||||
self._extruder_position = 0
|
self._extruder_position = 0
|
||||||
self._extruder_stack = None
|
self._extruder_stack = None
|
||||||
# Update the stack and the model data when the machine changes
|
|
||||||
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
|
self._available_materials = None
|
||||||
|
self._favorite_ids = None
|
||||||
|
|
||||||
def _updateExtruderStack(self):
|
def _updateExtruderStack(self):
|
||||||
global_stack = self._machine_manager.activeMachine
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
@ -65,8 +83,55 @@ class BaseMaterialsModel(ListModel):
|
||||||
def extruderPosition(self) -> int:
|
def extruderPosition(self) -> int:
|
||||||
return self._extruder_position
|
return self._extruder_position
|
||||||
|
|
||||||
#
|
## This is an abstract method that needs to be implemented by the specific
|
||||||
# This is an abstract method that needs to be implemented by
|
# models themselves.
|
||||||
#
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
## This method is used by all material models in the beginning of the
|
||||||
|
# _update() method in order to prevent errors. It's the same in all models
|
||||||
|
# so it's placed here for easy access.
|
||||||
|
def _canUpdate(self):
|
||||||
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
|
||||||
|
if global_stack is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
extruder_position = str(self._extruder_position)
|
||||||
|
|
||||||
|
if extruder_position not in global_stack.extruders:
|
||||||
|
return False
|
||||||
|
|
||||||
|
extruder_stack = global_stack.extruders[extruder_position]
|
||||||
|
|
||||||
|
self._available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
|
||||||
|
if self._available_materials is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
## This is another convenience function which is shared by all material
|
||||||
|
# models so it's put here to avoid having so much duplicated code.
|
||||||
|
def _createMaterialItem(self, root_material_id, container_node):
|
||||||
|
metadata = container_node.metadata
|
||||||
|
item = {
|
||||||
|
"root_material_id": root_material_id,
|
||||||
|
"id": metadata["id"],
|
||||||
|
"container_id": metadata["id"], # TODO: Remove duplicate in material manager qml
|
||||||
|
"GUID": metadata["GUID"],
|
||||||
|
"name": metadata["name"],
|
||||||
|
"brand": metadata["brand"],
|
||||||
|
"description": metadata["description"],
|
||||||
|
"material": metadata["material"],
|
||||||
|
"color_name": metadata["color_name"],
|
||||||
|
"color_code": metadata.get("color_code", ""),
|
||||||
|
"density": metadata.get("properties", {}).get("density", ""),
|
||||||
|
"diameter": metadata.get("properties", {}).get("diameter", ""),
|
||||||
|
"approximate_diameter": metadata["approximate_diameter"],
|
||||||
|
"adhesion_info": metadata["adhesion_info"],
|
||||||
|
"is_read_only": self._container_registry.isReadOnly(metadata["id"]),
|
||||||
|
"container_node": container_node,
|
||||||
|
"is_favorite": root_material_id in self._favorite_ids
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
|
@ -1,157 +0,0 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
|
||||||
|
|
||||||
from UM.Qt.ListModel import ListModel
|
|
||||||
from UM.Logger import Logger
|
|
||||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# This is an intermediate model to group materials with different colours for a same brand and type.
|
|
||||||
#
|
|
||||||
class MaterialsModelGroupedByType(ListModel):
|
|
||||||
NameRole = Qt.UserRole + 1
|
|
||||||
ColorsRole = Qt.UserRole + 2
|
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
self.addRoleName(self.NameRole, "name")
|
|
||||||
self.addRoleName(self.ColorsRole, "colors")
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# This model is used to show branded materials in the material drop down menu.
|
|
||||||
# The structure of the menu looks like this:
|
|
||||||
# Brand -> Material Type -> list of materials
|
|
||||||
#
|
|
||||||
# To illustrate, a branded material menu may look like this:
|
|
||||||
# Ultimaker -> PLA -> Yellow PLA
|
|
||||||
# -> Black PLA
|
|
||||||
# -> ...
|
|
||||||
# -> ABS -> White ABS
|
|
||||||
# ...
|
|
||||||
#
|
|
||||||
class BrandMaterialsModel(ListModel):
|
|
||||||
NameRole = Qt.UserRole + 1
|
|
||||||
MaterialsRole = Qt.UserRole + 2
|
|
||||||
|
|
||||||
extruderPositionChanged = pyqtSignal()
|
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
self.addRoleName(self.NameRole, "name")
|
|
||||||
self.addRoleName(self.MaterialsRole, "materials")
|
|
||||||
|
|
||||||
self._extruder_position = 0
|
|
||||||
self._extruder_stack = None
|
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
|
||||||
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
|
||||||
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
|
||||||
self._material_manager = CuraApplication.getInstance().getMaterialManager()
|
|
||||||
|
|
||||||
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
|
|
||||||
self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
|
|
||||||
self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
|
|
||||||
self._update()
|
|
||||||
|
|
||||||
def _updateExtruderStack(self):
|
|
||||||
global_stack = self._machine_manager.activeMachine
|
|
||||||
if global_stack is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._extruder_stack is not None:
|
|
||||||
self._extruder_stack.pyqtContainersChanged.disconnect(self._update)
|
|
||||||
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
|
|
||||||
if self._extruder_stack is not None:
|
|
||||||
self._extruder_stack.pyqtContainersChanged.connect(self._update)
|
|
||||||
# Force update the model when the extruder stack changes
|
|
||||||
self._update()
|
|
||||||
|
|
||||||
def setExtruderPosition(self, position: int):
|
|
||||||
if self._extruder_stack is None or self._extruder_position != position:
|
|
||||||
self._extruder_position = position
|
|
||||||
self._updateExtruderStack()
|
|
||||||
self.extruderPositionChanged.emit()
|
|
||||||
|
|
||||||
@pyqtProperty(int, fset=setExtruderPosition, notify=extruderPositionChanged)
|
|
||||||
def extruderPosition(self) -> int:
|
|
||||||
return self._extruder_position
|
|
||||||
|
|
||||||
def _update(self):
|
|
||||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
|
||||||
global_stack = self._machine_manager.activeMachine
|
|
||||||
if global_stack is None:
|
|
||||||
self.setItems([])
|
|
||||||
return
|
|
||||||
extruder_position = str(self._extruder_position)
|
|
||||||
if extruder_position not in global_stack.extruders:
|
|
||||||
self.setItems([])
|
|
||||||
return
|
|
||||||
extruder_stack = global_stack.extruders[str(self._extruder_position)]
|
|
||||||
|
|
||||||
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
|
|
||||||
extruder_stack)
|
|
||||||
if available_material_dict is None:
|
|
||||||
self.setItems([])
|
|
||||||
return
|
|
||||||
|
|
||||||
brand_item_list = []
|
|
||||||
brand_group_dict = {}
|
|
||||||
for root_material_id, container_node in available_material_dict.items():
|
|
||||||
metadata = container_node.metadata
|
|
||||||
brand = metadata["brand"]
|
|
||||||
# Only add results for generic materials
|
|
||||||
if brand.lower() == "generic":
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Do not include the materials from a to-be-removed package
|
|
||||||
if bool(metadata.get("removed", False)):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if brand not in brand_group_dict:
|
|
||||||
brand_group_dict[brand] = {}
|
|
||||||
|
|
||||||
material_type = metadata["material"]
|
|
||||||
if material_type not in brand_group_dict[brand]:
|
|
||||||
brand_group_dict[brand][material_type] = []
|
|
||||||
|
|
||||||
item = {"root_material_id": root_material_id,
|
|
||||||
"id": metadata["id"],
|
|
||||||
"name": metadata["name"],
|
|
||||||
"brand": metadata["brand"],
|
|
||||||
"material": metadata["material"],
|
|
||||||
"color_name": metadata["color_name"],
|
|
||||||
"container_node": container_node
|
|
||||||
}
|
|
||||||
brand_group_dict[brand][material_type].append(item)
|
|
||||||
|
|
||||||
for brand, material_dict in brand_group_dict.items():
|
|
||||||
brand_item = {"name": brand,
|
|
||||||
"materials": MaterialsModelGroupedByType(self)}
|
|
||||||
|
|
||||||
material_type_item_list = []
|
|
||||||
for material_type, material_list in material_dict.items():
|
|
||||||
material_type_item = {"name": material_type,
|
|
||||||
"colors": BaseMaterialsModel(self)}
|
|
||||||
material_type_item["colors"].clear()
|
|
||||||
|
|
||||||
# Sort materials by name
|
|
||||||
material_list = sorted(material_list, key = lambda x: x["name"].upper())
|
|
||||||
material_type_item["colors"].setItems(material_list)
|
|
||||||
|
|
||||||
material_type_item_list.append(material_type_item)
|
|
||||||
|
|
||||||
# Sort material type by name
|
|
||||||
material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper())
|
|
||||||
brand_item["materials"].setItems(material_type_item_list)
|
|
||||||
|
|
||||||
brand_item_list.append(brand_item)
|
|
||||||
|
|
||||||
# Sort brand by name
|
|
||||||
brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper())
|
|
||||||
self.setItems(brand_item_list)
|
|
|
@ -8,7 +8,7 @@ from UM.Logger import Logger
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
from UM.Util import parseBool
|
from UM.Util import parseBool
|
||||||
|
|
||||||
from cura.Machines.VariantManager import VariantType
|
from cura.Machines.VariantType import VariantType
|
||||||
|
|
||||||
|
|
||||||
class BuildPlateModel(ListModel):
|
class BuildPlateModel(ListModel):
|
||||||
|
|
42
cura/Machines/Models/FavoriteMaterialsModel.py
Normal file
42
cura/Machines/Models/FavoriteMaterialsModel.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
|
class FavoriteMaterialsModel(BaseMaterialsModel):
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
|
||||||
|
# Perform standard check and reset if the check fails
|
||||||
|
if not self._canUpdate():
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get updated list of favorites
|
||||||
|
self._favorite_ids = self._material_manager.getFavorites()
|
||||||
|
|
||||||
|
item_list = []
|
||||||
|
|
||||||
|
for root_material_id, container_node in self._available_materials.items():
|
||||||
|
metadata = container_node.metadata
|
||||||
|
|
||||||
|
# Do not include the materials from a to-be-removed package
|
||||||
|
if bool(metadata.get("removed", False)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Only add results for favorite materials
|
||||||
|
if root_material_id not in self._favorite_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
item = self._createMaterialItem(root_material_id, container_node)
|
||||||
|
item_list.append(item)
|
||||||
|
|
||||||
|
# Sort the item list alphabetically by name
|
||||||
|
item_list = sorted(item_list, key = lambda d: d["brand"].upper())
|
||||||
|
|
||||||
|
self.setItems(item_list)
|
|
@ -4,63 +4,39 @@
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
|
|
||||||
class GenericMaterialsModel(BaseMaterialsModel):
|
class GenericMaterialsModel(BaseMaterialsModel):
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
|
||||||
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
|
||||||
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
|
||||||
self._material_manager = CuraApplication.getInstance().getMaterialManager()
|
|
||||||
|
|
||||||
self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
|
|
||||||
self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
|
|
||||||
self._update()
|
self._update()
|
||||||
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
|
||||||
|
|
||||||
global_stack = self._machine_manager.activeMachine
|
# Perform standard check and reset if the check fails
|
||||||
if global_stack is None:
|
if not self._canUpdate():
|
||||||
self.setItems([])
|
self.setItems([])
|
||||||
return
|
return
|
||||||
extruder_position = str(self._extruder_position)
|
|
||||||
if extruder_position not in global_stack.extruders:
|
|
||||||
self.setItems([])
|
|
||||||
return
|
|
||||||
extruder_stack = global_stack.extruders[extruder_position]
|
|
||||||
|
|
||||||
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
|
# Get updated list of favorites
|
||||||
extruder_stack)
|
self._favorite_ids = self._material_manager.getFavorites()
|
||||||
if available_material_dict is None:
|
|
||||||
self.setItems([])
|
|
||||||
return
|
|
||||||
|
|
||||||
item_list = []
|
item_list = []
|
||||||
for root_material_id, container_node in available_material_dict.items():
|
|
||||||
metadata = container_node.metadata
|
|
||||||
|
|
||||||
# Only add results for generic materials
|
for root_material_id, container_node in self._available_materials.items():
|
||||||
if metadata["brand"].lower() != "generic":
|
metadata = container_node.metadata
|
||||||
continue
|
|
||||||
|
|
||||||
# Do not include the materials from a to-be-removed package
|
# Do not include the materials from a to-be-removed package
|
||||||
if bool(metadata.get("removed", False)):
|
if bool(metadata.get("removed", False)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
item = {"root_material_id": root_material_id,
|
# Only add results for generic materials
|
||||||
"id": metadata["id"],
|
if metadata["brand"].lower() != "generic":
|
||||||
"name": metadata["name"],
|
continue
|
||||||
"brand": metadata["brand"],
|
|
||||||
"material": metadata["material"],
|
item = self._createMaterialItem(root_material_id, container_node)
|
||||||
"color_name": metadata["color_name"],
|
|
||||||
"container_node": container_node
|
|
||||||
}
|
|
||||||
item_list.append(item)
|
item_list.append(item)
|
||||||
|
|
||||||
# Sort the item list by material name alphabetically
|
# Sort the item list alphabetically by name
|
||||||
item_list = sorted(item_list, key = lambda d: d["name"].upper())
|
item_list = sorted(item_list, key = lambda d: d["name"].upper())
|
||||||
|
|
||||||
self.setItems(item_list)
|
self.setItems(item_list)
|
||||||
|
|
107
cura/Machines/Models/MaterialBrandsModel.py
Normal file
107
cura/Machines/Models/MaterialBrandsModel.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
|
class MaterialTypesModel(ListModel):
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(Qt.UserRole + 1, "name")
|
||||||
|
self.addRoleName(Qt.UserRole + 2, "colors")
|
||||||
|
|
||||||
|
class MaterialBrandsModel(BaseMaterialsModel):
|
||||||
|
|
||||||
|
extruderPositionChanged = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(Qt.UserRole + 1, "name")
|
||||||
|
self.addRoleName(Qt.UserRole + 2, "material_types")
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
|
||||||
|
# Perform standard check and reset if the check fails
|
||||||
|
if not self._canUpdate():
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get updated list of favorites
|
||||||
|
self._favorite_ids = self._material_manager.getFavorites()
|
||||||
|
|
||||||
|
brand_item_list = []
|
||||||
|
brand_group_dict = {}
|
||||||
|
|
||||||
|
# Part 1: Generate the entire tree of brands -> material types -> spcific materials
|
||||||
|
for root_material_id, container_node in self._available_materials.items():
|
||||||
|
metadata = container_node.metadata
|
||||||
|
|
||||||
|
# Do not include the materials from a to-be-removed package
|
||||||
|
if bool(metadata.get("removed", False)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add brands we haven't seen yet to the dict, skipping generics
|
||||||
|
brand = metadata["brand"]
|
||||||
|
if brand.lower() == "generic":
|
||||||
|
continue
|
||||||
|
if brand not in brand_group_dict:
|
||||||
|
brand_group_dict[brand] = {}
|
||||||
|
|
||||||
|
# Add material types we haven't seen yet to the dict
|
||||||
|
material_type = metadata["material"]
|
||||||
|
if material_type not in brand_group_dict[brand]:
|
||||||
|
brand_group_dict[brand][material_type] = []
|
||||||
|
|
||||||
|
# Now handle the individual materials
|
||||||
|
item = self._createMaterialItem(root_material_id, container_node)
|
||||||
|
brand_group_dict[brand][material_type].append(item)
|
||||||
|
|
||||||
|
# Part 2: Organize the tree into models
|
||||||
|
#
|
||||||
|
# Normally, the structure of the menu looks like this:
|
||||||
|
# Brand -> Material Type -> Specific Material
|
||||||
|
#
|
||||||
|
# To illustrate, a branded material menu may look like this:
|
||||||
|
# Ultimaker ┳ PLA ┳ Yellow PLA
|
||||||
|
# ┃ ┣ Black PLA
|
||||||
|
# ┃ ┗ ...
|
||||||
|
# ┃
|
||||||
|
# ┗ ABS ┳ White ABS
|
||||||
|
# ┗ ...
|
||||||
|
for brand, material_dict in brand_group_dict.items():
|
||||||
|
|
||||||
|
material_type_item_list = []
|
||||||
|
brand_item = {
|
||||||
|
"name": brand,
|
||||||
|
"material_types": MaterialTypesModel(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
for material_type, material_list in material_dict.items():
|
||||||
|
material_type_item = {
|
||||||
|
"name": material_type,
|
||||||
|
"colors": BaseMaterialsModel(self)
|
||||||
|
}
|
||||||
|
material_type_item["colors"].clear()
|
||||||
|
|
||||||
|
# Sort materials by name
|
||||||
|
material_list = sorted(material_list, key = lambda x: x["name"].upper())
|
||||||
|
material_type_item["colors"].setItems(material_list)
|
||||||
|
|
||||||
|
material_type_item_list.append(material_type_item)
|
||||||
|
|
||||||
|
# Sort material type by name
|
||||||
|
material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper())
|
||||||
|
brand_item["material_types"].setItems(material_type_item_list)
|
||||||
|
|
||||||
|
brand_item_list.append(brand_item)
|
||||||
|
|
||||||
|
# Sort brand by name
|
||||||
|
brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper())
|
||||||
|
self.setItems(brand_item_list)
|
|
@ -1,104 +0,0 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
|
||||||
|
|
||||||
from UM.Logger import Logger
|
|
||||||
from UM.Qt.ListModel import ListModel
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# This model is for the Material management page.
|
|
||||||
#
|
|
||||||
class MaterialManagementModel(ListModel):
|
|
||||||
RootMaterialIdRole = Qt.UserRole + 1
|
|
||||||
DisplayNameRole = Qt.UserRole + 2
|
|
||||||
BrandRole = Qt.UserRole + 3
|
|
||||||
MaterialTypeRole = Qt.UserRole + 4
|
|
||||||
ColorNameRole = Qt.UserRole + 5
|
|
||||||
ColorCodeRole = Qt.UserRole + 6
|
|
||||||
ContainerNodeRole = Qt.UserRole + 7
|
|
||||||
ContainerIdRole = Qt.UserRole + 8
|
|
||||||
|
|
||||||
DescriptionRole = Qt.UserRole + 9
|
|
||||||
AdhesionInfoRole = Qt.UserRole + 10
|
|
||||||
ApproximateDiameterRole = Qt.UserRole + 11
|
|
||||||
GuidRole = Qt.UserRole + 12
|
|
||||||
DensityRole = Qt.UserRole + 13
|
|
||||||
DiameterRole = Qt.UserRole + 14
|
|
||||||
IsReadOnlyRole = Qt.UserRole + 15
|
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
self.addRoleName(self.RootMaterialIdRole, "root_material_id")
|
|
||||||
self.addRoleName(self.DisplayNameRole, "name")
|
|
||||||
self.addRoleName(self.BrandRole, "brand")
|
|
||||||
self.addRoleName(self.MaterialTypeRole, "material")
|
|
||||||
self.addRoleName(self.ColorNameRole, "color_name")
|
|
||||||
self.addRoleName(self.ColorCodeRole, "color_code")
|
|
||||||
self.addRoleName(self.ContainerNodeRole, "container_node")
|
|
||||||
self.addRoleName(self.ContainerIdRole, "container_id")
|
|
||||||
|
|
||||||
self.addRoleName(self.DescriptionRole, "description")
|
|
||||||
self.addRoleName(self.AdhesionInfoRole, "adhesion_info")
|
|
||||||
self.addRoleName(self.ApproximateDiameterRole, "approximate_diameter")
|
|
||||||
self.addRoleName(self.GuidRole, "guid")
|
|
||||||
self.addRoleName(self.DensityRole, "density")
|
|
||||||
self.addRoleName(self.DiameterRole, "diameter")
|
|
||||||
self.addRoleName(self.IsReadOnlyRole, "is_read_only")
|
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
|
||||||
self._container_registry = CuraApplication.getInstance().getContainerRegistry()
|
|
||||||
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
|
||||||
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
|
||||||
self._material_manager = CuraApplication.getInstance().getMaterialManager()
|
|
||||||
|
|
||||||
self._machine_manager.globalContainerChanged.connect(self._update)
|
|
||||||
self._extruder_manager.activeExtruderChanged.connect(self._update)
|
|
||||||
self._material_manager.materialsUpdated.connect(self._update)
|
|
||||||
|
|
||||||
self._update()
|
|
||||||
|
|
||||||
def _update(self):
|
|
||||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
|
||||||
|
|
||||||
global_stack = self._machine_manager.activeMachine
|
|
||||||
if global_stack is None:
|
|
||||||
self.setItems([])
|
|
||||||
return
|
|
||||||
active_extruder_stack = self._machine_manager.activeStack
|
|
||||||
|
|
||||||
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
|
|
||||||
active_extruder_stack)
|
|
||||||
if available_material_dict is None:
|
|
||||||
self.setItems([])
|
|
||||||
return
|
|
||||||
|
|
||||||
material_list = []
|
|
||||||
for root_material_id, container_node in available_material_dict.items():
|
|
||||||
keys_to_fetch = ("name",
|
|
||||||
"brand",
|
|
||||||
"material",
|
|
||||||
"color_name",
|
|
||||||
"color_code",
|
|
||||||
"description",
|
|
||||||
"adhesion_info",
|
|
||||||
"approximate_diameter",)
|
|
||||||
|
|
||||||
item = {"root_material_id": container_node.metadata["base_file"],
|
|
||||||
"container_node": container_node,
|
|
||||||
"guid": container_node.metadata["GUID"],
|
|
||||||
"container_id": container_node.metadata["id"],
|
|
||||||
"density": container_node.metadata.get("properties", {}).get("density", ""),
|
|
||||||
"diameter": container_node.metadata.get("properties", {}).get("diameter", ""),
|
|
||||||
"is_read_only": self._container_registry.isReadOnly(container_node.metadata["id"]),
|
|
||||||
}
|
|
||||||
|
|
||||||
for key in keys_to_fetch:
|
|
||||||
item[key] = container_node.metadata.get(key, "")
|
|
||||||
|
|
||||||
material_list.append(item)
|
|
||||||
|
|
||||||
material_list = sorted(material_list, key = lambda k: (k["brand"].upper(), k["name"].upper()))
|
|
||||||
self.setItems(material_list)
|
|
|
@ -8,6 +8,8 @@ from UM.Logger import Logger
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
from UM.Util import parseBool
|
from UM.Util import parseBool
|
||||||
|
|
||||||
|
from cura.Machines.VariantType import VariantType
|
||||||
|
|
||||||
|
|
||||||
class NozzleModel(ListModel):
|
class NozzleModel(ListModel):
|
||||||
IdRole = Qt.UserRole + 1
|
IdRole = Qt.UserRole + 1
|
||||||
|
@ -43,7 +45,6 @@ class NozzleModel(ListModel):
|
||||||
self.setItems([])
|
self.setItems([])
|
||||||
return
|
return
|
||||||
|
|
||||||
from cura.Machines.VariantManager import VariantType
|
|
||||||
variant_node_dict = self._variant_manager.getVariantNodes(global_stack, VariantType.NOZZLE)
|
variant_node_dict = self._variant_manager.getVariantNodes(global_stack, VariantType.NOZZLE)
|
||||||
if not variant_node_dict:
|
if not variant_node_dict:
|
||||||
self.setItems([])
|
self.setItems([])
|
||||||
|
|
|
@ -45,7 +45,7 @@ class QualityManager(QObject):
|
||||||
self._empty_quality_container = self._application.empty_quality_container
|
self._empty_quality_container = self._application.empty_quality_container
|
||||||
self._empty_quality_changes_container = self._application.empty_quality_changes_container
|
self._empty_quality_changes_container = self._application.empty_quality_changes_container
|
||||||
|
|
||||||
self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup
|
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # for quality lookup
|
||||||
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
|
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
|
||||||
|
|
||||||
self._default_machine_definition_id = "fdmprinter"
|
self._default_machine_definition_id = "fdmprinter"
|
||||||
|
@ -64,10 +64,10 @@ class QualityManager(QObject):
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
# Initialize the lookup tree for quality profiles with following structure:
|
# Initialize the lookup tree for quality profiles with following structure:
|
||||||
# <machine> -> <variant> -> <material>
|
# <machine> -> <nozzle> -> <buildplate> -> <material>
|
||||||
# -> <material>
|
# <machine> -> <material>
|
||||||
|
|
||||||
self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup
|
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # for quality lookup
|
||||||
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
|
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
|
||||||
|
|
||||||
quality_metadata_list = self._container_registry.findContainersMetadata(type = "quality")
|
quality_metadata_list = self._container_registry.findContainersMetadata(type = "quality")
|
||||||
|
@ -79,53 +79,41 @@ class QualityManager(QObject):
|
||||||
quality_type = metadata["quality_type"]
|
quality_type = metadata["quality_type"]
|
||||||
|
|
||||||
root_material_id = metadata.get("material")
|
root_material_id = metadata.get("material")
|
||||||
variant_name = metadata.get("variant")
|
nozzle_name = metadata.get("variant")
|
||||||
|
buildplate_name = metadata.get("buildplate")
|
||||||
is_global_quality = metadata.get("global_quality", False)
|
is_global_quality = metadata.get("global_quality", False)
|
||||||
is_global_quality = is_global_quality or (root_material_id is None and variant_name is None)
|
is_global_quality = is_global_quality or (root_material_id is None and nozzle_name is None and buildplate_name is None)
|
||||||
|
|
||||||
# Sanity check: material+variant and is_global_quality cannot be present at the same time
|
# Sanity check: material+variant and is_global_quality cannot be present at the same time
|
||||||
if is_global_quality and (root_material_id or variant_name):
|
if is_global_quality and (root_material_id or nozzle_name):
|
||||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(metadata["id"])
|
ConfigurationErrorMessage.getInstance().addFaultyContainers(metadata["id"])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if definition_id not in self._machine_variant_material_quality_type_to_quality_dict:
|
if definition_id not in self._machine_nozzle_buildplate_material_quality_type_to_quality_dict:
|
||||||
self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode()
|
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id] = QualityNode()
|
||||||
machine_node = cast(QualityNode, self._machine_variant_material_quality_type_to_quality_dict[definition_id])
|
machine_node = cast(QualityNode, self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id])
|
||||||
|
|
||||||
if is_global_quality:
|
if is_global_quality:
|
||||||
# For global qualities, save data in the machine node
|
# For global qualities, save data in the machine node
|
||||||
machine_node.addQualityMetadata(quality_type, metadata)
|
machine_node.addQualityMetadata(quality_type, metadata)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if variant_name is not None:
|
current_node = machine_node
|
||||||
# If variant_name is specified in the quality/quality_changes profile, check if material is specified,
|
intermediate_node_info_list = [nozzle_name, buildplate_name, root_material_id]
|
||||||
# too.
|
current_intermediate_node_info_idx = 0
|
||||||
if variant_name not in machine_node.children_map:
|
|
||||||
machine_node.children_map[variant_name] = QualityNode()
|
|
||||||
variant_node = cast(QualityNode, machine_node.children_map[variant_name])
|
|
||||||
|
|
||||||
if root_material_id is None:
|
while current_intermediate_node_info_idx < len(intermediate_node_info_list):
|
||||||
# If only variant_name is specified but material is not, add the quality/quality_changes metadata
|
node_name = intermediate_node_info_list[current_intermediate_node_info_idx]
|
||||||
# into the current variant node.
|
if node_name is not None:
|
||||||
variant_node.addQualityMetadata(quality_type, metadata)
|
# There is specific information, update the current node to go deeper so we can add this quality
|
||||||
else:
|
# at the most specific branch in the lookup tree.
|
||||||
# If only variant_name and material are both specified, go one level deeper: create a material node
|
if node_name not in current_node.children_map:
|
||||||
# under the current variant node, and then add the quality/quality_changes metadata into the
|
current_node.children_map[node_name] = QualityNode()
|
||||||
# material node.
|
current_node = cast(QualityNode, current_node.children_map[node_name])
|
||||||
if root_material_id not in variant_node.children_map:
|
|
||||||
variant_node.children_map[root_material_id] = QualityNode()
|
|
||||||
material_node = cast(QualityNode, variant_node.children_map[root_material_id])
|
|
||||||
|
|
||||||
material_node.addQualityMetadata(quality_type, metadata)
|
current_intermediate_node_info_idx += 1
|
||||||
|
|
||||||
else:
|
current_node.addQualityMetadata(quality_type, metadata)
|
||||||
# If variant_name is not specified, check if material is specified.
|
|
||||||
if root_material_id is not None:
|
|
||||||
if root_material_id not in machine_node.children_map:
|
|
||||||
machine_node.children_map[root_material_id] = QualityNode()
|
|
||||||
material_node = cast(QualityNode, machine_node.children_map[root_material_id])
|
|
||||||
|
|
||||||
material_node.addQualityMetadata(quality_type, metadata)
|
|
||||||
|
|
||||||
# Initialize the lookup tree for quality_changes profiles with following structure:
|
# Initialize the lookup tree for quality_changes profiles with following structure:
|
||||||
# <machine> -> <quality_type> -> <name>
|
# <machine> -> <quality_type> -> <name>
|
||||||
|
@ -217,8 +205,8 @@ class QualityManager(QObject):
|
||||||
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||||
# (1) the machine-specific node
|
# (1) the machine-specific node
|
||||||
# (2) the generic node
|
# (2) the generic node
|
||||||
machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
|
machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
|
||||||
default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
|
default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
|
||||||
nodes_to_check = [machine_node, default_machine_node]
|
nodes_to_check = [machine_node, default_machine_node]
|
||||||
|
|
||||||
# Iterate over all quality_types in the machine node
|
# Iterate over all quality_types in the machine node
|
||||||
|
@ -238,16 +226,19 @@ class QualityManager(QObject):
|
||||||
quality_group_dict[quality_type] = quality_group
|
quality_group_dict[quality_type] = quality_group
|
||||||
break
|
break
|
||||||
|
|
||||||
|
buildplate_name = machine.getBuildplateName()
|
||||||
|
|
||||||
# Iterate over all extruders to find quality containers for each extruder
|
# Iterate over all extruders to find quality containers for each extruder
|
||||||
for position, extruder in machine.extruders.items():
|
for position, extruder in machine.extruders.items():
|
||||||
variant_name = None
|
nozzle_name = None
|
||||||
if extruder.variant.getId() != "empty_variant":
|
if extruder.variant.getId() != "empty_variant":
|
||||||
variant_name = extruder.variant.getName()
|
nozzle_name = extruder.variant.getName()
|
||||||
|
|
||||||
# This is a list of root material IDs to use for searching for suitable quality profiles.
|
# This is a list of root material IDs to use for searching for suitable quality profiles.
|
||||||
# The root material IDs in this list are in prioritized order.
|
# The root material IDs in this list are in prioritized order.
|
||||||
root_material_id_list = []
|
root_material_id_list = []
|
||||||
has_material = False # flag indicating whether this extruder has a material assigned
|
has_material = False # flag indicating whether this extruder has a material assigned
|
||||||
|
root_material_id = None
|
||||||
if extruder.material.getId() != "empty_material":
|
if extruder.material.getId() != "empty_material":
|
||||||
has_material = True
|
has_material = True
|
||||||
root_material_id = extruder.material.getMetaDataEntry("base_file")
|
root_material_id = extruder.material.getMetaDataEntry("base_file")
|
||||||
|
@ -264,36 +255,51 @@ class QualityManager(QObject):
|
||||||
# Here we construct a list of nodes we want to look for qualities with the highest priority first.
|
# Here we construct a list of nodes we want to look for qualities with the highest priority first.
|
||||||
# The use case is that, when we look for qualities for a machine, we first want to search in the following
|
# The use case is that, when we look for qualities for a machine, we first want to search in the following
|
||||||
# order:
|
# order:
|
||||||
# 1. machine-variant-and-material-specific qualities if exist
|
# 1. machine-nozzle-buildplate-and-material-specific qualities if exist
|
||||||
# 2. machine-variant-specific qualities if exist
|
# 2. machine-nozzle-and-material-specific qualities if exist
|
||||||
# 3. machine-material-specific qualities if exist
|
# 3. machine-nozzle-specific qualities if exist
|
||||||
# 4. machine-specific qualities if exist
|
# 4. machine-material-specific qualities if exist
|
||||||
# 5. generic qualities if exist
|
# 5. machine-specific global qualities if exist, otherwise generic global qualities
|
||||||
|
# NOTE: We DO NOT fail back to generic global qualities if machine-specific global qualities exist.
|
||||||
|
# This is because when a machine defines its own global qualities such as Normal, Fine, etc.,
|
||||||
|
# it is intended to maintain those specific qualities ONLY. If we still fail back to the generic
|
||||||
|
# global qualities, there can be unimplemented quality types e.g. "coarse", and this is not
|
||||||
|
# correct.
|
||||||
# Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
|
# Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
|
||||||
# the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
|
# the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
|
||||||
# qualities from there.
|
# qualities from there.
|
||||||
|
node_info_list_0 = [nozzle_name, buildplate_name, root_material_id]
|
||||||
nodes_to_check = []
|
nodes_to_check = []
|
||||||
|
|
||||||
if variant_name:
|
# This function tries to recursively find the deepest (the most specific) branch and add those nodes to
|
||||||
# In this case, we have both a specific variant and a specific material
|
# the search list in the order described above. So, by iterating over that search node list, we first look
|
||||||
variant_node = machine_node.getChildNode(variant_name)
|
# in the more specific branches and then the less specific (generic) ones.
|
||||||
if variant_node and has_material:
|
def addNodesToCheck(node, nodes_to_check_list, node_info_list, node_info_idx):
|
||||||
for root_material_id in root_material_id_list:
|
if node_info_idx < len(node_info_list):
|
||||||
material_node = variant_node.getChildNode(root_material_id)
|
node_name = node_info_list[node_info_idx]
|
||||||
|
if node_name is not None:
|
||||||
|
current_node = node.getChildNode(node_name)
|
||||||
|
if current_node is not None and has_material:
|
||||||
|
addNodesToCheck(current_node, nodes_to_check_list, node_info_list, node_info_idx + 1)
|
||||||
|
|
||||||
|
if has_material:
|
||||||
|
for rmid in root_material_id_list:
|
||||||
|
material_node = node.getChildNode(rmid)
|
||||||
if material_node:
|
if material_node:
|
||||||
nodes_to_check.append(material_node)
|
nodes_to_check_list.append(material_node)
|
||||||
break
|
break
|
||||||
nodes_to_check.append(variant_node)
|
|
||||||
|
|
||||||
# In this case, we only have a specific material but NOT a variant
|
nodes_to_check_list.append(node)
|
||||||
if has_material:
|
|
||||||
for root_material_id in root_material_id_list:
|
addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0)
|
||||||
material_node = machine_node.getChildNode(root_material_id)
|
|
||||||
if material_node:
|
# The last fall back will be the global qualities (either from the machine-specific node or the generic
|
||||||
nodes_to_check.append(material_node)
|
# node), but we only use one. For details see the overview comments above.
|
||||||
break
|
if machine_node.quality_type_map:
|
||||||
|
nodes_to_check += [machine_node]
|
||||||
|
else:
|
||||||
|
nodes_to_check += [default_machine_node]
|
||||||
|
|
||||||
nodes_to_check += [machine_node, default_machine_node]
|
|
||||||
for node in nodes_to_check:
|
for node in nodes_to_check:
|
||||||
if node and node.quality_type_map:
|
if node and node.quality_type_map:
|
||||||
if has_variant_materials:
|
if has_variant_materials:
|
||||||
|
@ -309,8 +315,8 @@ class QualityManager(QObject):
|
||||||
quality_group_dict[quality_type] = quality_group
|
quality_group_dict[quality_type] = quality_group
|
||||||
|
|
||||||
quality_group = quality_group_dict[quality_type]
|
quality_group = quality_group_dict[quality_type]
|
||||||
quality_group.nodes_for_extruders[position] = quality_node
|
if position not in quality_group.nodes_for_extruders:
|
||||||
break
|
quality_group.nodes_for_extruders[position] = quality_node
|
||||||
|
|
||||||
# Update availabilities for each quality group
|
# Update availabilities for each quality group
|
||||||
self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
|
self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
|
||||||
|
@ -323,8 +329,8 @@ class QualityManager(QObject):
|
||||||
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||||
# (1) the machine-specific node
|
# (1) the machine-specific node
|
||||||
# (2) the generic node
|
# (2) the generic node
|
||||||
machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
|
machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
|
||||||
default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(
|
default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(
|
||||||
self._default_machine_definition_id)
|
self._default_machine_definition_id)
|
||||||
nodes_to_check = [machine_node, default_machine_node]
|
nodes_to_check = [machine_node, default_machine_node]
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -11,20 +10,13 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Util import parseBool
|
from UM.Util import parseBool
|
||||||
|
|
||||||
from cura.Machines.ContainerNode import ContainerNode
|
from cura.Machines.ContainerNode import ContainerNode
|
||||||
|
from cura.Machines.VariantType import VariantType, ALL_VARIANT_TYPES
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
|
||||||
|
|
||||||
class VariantType(Enum):
|
|
||||||
BUILD_PLATE = "buildplate"
|
|
||||||
NOZZLE = "nozzle"
|
|
||||||
|
|
||||||
|
|
||||||
ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# VariantManager is THE place to look for a specific variant. It maintains two variant lookup tables with the following
|
# VariantManager is THE place to look for a specific variant. It maintains two variant lookup tables with the following
|
||||||
# structure:
|
# structure:
|
||||||
|
|
15
cura/Machines/VariantType.py
Normal file
15
cura/Machines/VariantType.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class VariantType(Enum):
|
||||||
|
BUILD_PLATE = "buildplate"
|
||||||
|
NOZZLE = "nozzle"
|
||||||
|
|
||||||
|
|
||||||
|
ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["VariantType", "ALL_VARIANT_TYPES"]
|
|
@ -1,112 +1,149 @@
|
||||||
# Copyright (c) 2015 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from UM.Scene.Iterator import Iterator
|
import sys
|
||||||
from UM.Scene.SceneNode import SceneNode
|
|
||||||
from functools import cmp_to_key
|
from shapely import affinity
|
||||||
from UM.Application import Application
|
from shapely.geometry import Polygon
|
||||||
|
|
||||||
|
from UM.Scene.Iterator.Iterator import Iterator
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
|
||||||
|
|
||||||
|
# Iterator that determines the object print order when one-at a time mode is enabled.
|
||||||
|
#
|
||||||
|
# In one-at-a-time mode, only one extruder can be enabled to print. In order to maximize the number of objects we can
|
||||||
|
# print, we need to print from the corner that's closest to the extruder that's being used. Here is an illustration:
|
||||||
|
#
|
||||||
|
# +--------------------------------+
|
||||||
|
# | |
|
||||||
|
# | |
|
||||||
|
# | | - Rectangle represents the complete print head including fans, etc.
|
||||||
|
# | X X | y - X's are the nozzles
|
||||||
|
# | (1) (2) | ^
|
||||||
|
# | | |
|
||||||
|
# +--------------------------------+ +--> x
|
||||||
|
#
|
||||||
|
# In this case, the nozzles are symmetric, nozzle (1) is closer to the bottom left corner while (2) is closer to the
|
||||||
|
# bottom right. If we use nozzle (1) to print, then we better off printing from the bottom left corner so the print
|
||||||
|
# head will not collide into an object on its top-right side, which is a very large unused area. Following the same
|
||||||
|
# logic, if we are printing with nozzle (2), then it's better to print from the bottom-right side.
|
||||||
|
#
|
||||||
|
# This iterator determines the print order following the rules above.
|
||||||
|
#
|
||||||
|
class OneAtATimeIterator(Iterator):
|
||||||
|
|
||||||
## Iterator that returns a list of nodes in the order that they need to be printed
|
|
||||||
# If there is no solution an empty list is returned.
|
|
||||||
# Take note that the list of nodes can have children (that may or may not contain mesh data)
|
|
||||||
class OneAtATimeIterator(Iterator.Iterator):
|
|
||||||
def __init__(self, scene_node):
|
def __init__(self, scene_node):
|
||||||
super().__init__(scene_node) # Call super to make multiple inheritence work.
|
from cura.CuraApplication import CuraApplication
|
||||||
self._hit_map = [[]]
|
self._global_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
self._original_node_list = []
|
self._original_node_list = []
|
||||||
|
|
||||||
|
super().__init__(scene_node) # Call super to make multiple inheritance work.
|
||||||
|
|
||||||
|
def getMachineNearestCornerToExtruder(self, global_stack):
|
||||||
|
head_and_fans_coordinates = global_stack.getHeadAndFansCoordinates()
|
||||||
|
|
||||||
|
used_extruder = None
|
||||||
|
for extruder in global_stack.extruders.values():
|
||||||
|
if extruder.isEnabled:
|
||||||
|
used_extruder = extruder
|
||||||
|
break
|
||||||
|
|
||||||
|
extruder_offsets = [used_extruder.getProperty("machine_nozzle_offset_x", "value"),
|
||||||
|
used_extruder.getProperty("machine_nozzle_offset_y", "value")]
|
||||||
|
|
||||||
|
# find the corner that's closest to the origin
|
||||||
|
min_distance2 = sys.maxsize
|
||||||
|
min_coord = None
|
||||||
|
for coord in head_and_fans_coordinates:
|
||||||
|
x = coord[0] - extruder_offsets[0]
|
||||||
|
y = coord[1] - extruder_offsets[1]
|
||||||
|
|
||||||
|
distance2 = x**2 + y**2
|
||||||
|
if distance2 <= min_distance2:
|
||||||
|
min_distance2 = distance2
|
||||||
|
min_coord = coord
|
||||||
|
|
||||||
|
return min_coord
|
||||||
|
|
||||||
|
def _checkForCollisions(self) -> bool:
|
||||||
|
all_nodes = []
|
||||||
|
for node in self._scene_node.getChildren():
|
||||||
|
if not issubclass(type(node), SceneNode):
|
||||||
|
continue
|
||||||
|
convex_hull = node.callDecoration("getConvexHullHead")
|
||||||
|
if not convex_hull:
|
||||||
|
continue
|
||||||
|
|
||||||
|
bounding_box = node.getBoundingBox()
|
||||||
|
if not bounding_box:
|
||||||
|
continue
|
||||||
|
from UM.Math.Polygon import Polygon
|
||||||
|
bounding_box_polygon = Polygon([[bounding_box.left, bounding_box.front],
|
||||||
|
[bounding_box.left, bounding_box.back],
|
||||||
|
[bounding_box.right, bounding_box.back],
|
||||||
|
[bounding_box.right, bounding_box.front]])
|
||||||
|
|
||||||
|
all_nodes.append({"node": node,
|
||||||
|
"bounding_box": bounding_box_polygon,
|
||||||
|
"convex_hull": convex_hull})
|
||||||
|
|
||||||
|
has_collisions = False
|
||||||
|
for i, node_dict in enumerate(all_nodes):
|
||||||
|
for j, other_node_dict in enumerate(all_nodes):
|
||||||
|
if i == j:
|
||||||
|
continue
|
||||||
|
if node_dict["bounding_box"].intersectsPolygon(other_node_dict["convex_hull"]):
|
||||||
|
has_collisions = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if has_collisions:
|
||||||
|
break
|
||||||
|
|
||||||
|
return has_collisions
|
||||||
|
|
||||||
def _fillStack(self):
|
def _fillStack(self):
|
||||||
|
min_coord = self.getMachineNearestCornerToExtruder(self._global_stack)
|
||||||
|
transform_x = -int(round(min_coord[0] / abs(min_coord[0])))
|
||||||
|
transform_y = -int(round(min_coord[1] / abs(min_coord[1])))
|
||||||
|
|
||||||
|
machine_size = [self._global_stack.getProperty("machine_width", "value"),
|
||||||
|
self._global_stack.getProperty("machine_depth", "value")]
|
||||||
|
|
||||||
|
def flip_x(polygon):
|
||||||
|
tm2 = [-1, 0, 0, 1, 0, 0]
|
||||||
|
return affinity.affine_transform(affinity.translate(polygon, xoff = -machine_size[0]), tm2)
|
||||||
|
|
||||||
|
def flip_y(polygon):
|
||||||
|
tm2 = [1, 0, 0, -1, 0, 0]
|
||||||
|
return affinity.affine_transform(affinity.translate(polygon, yoff = -machine_size[1]), tm2)
|
||||||
|
|
||||||
|
if self._checkForCollisions():
|
||||||
|
self._node_stack = []
|
||||||
|
return
|
||||||
|
|
||||||
node_list = []
|
node_list = []
|
||||||
for node in self._scene_node.getChildren():
|
for node in self._scene_node.getChildren():
|
||||||
if not issubclass(type(node), SceneNode):
|
if not issubclass(type(node), SceneNode):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if node.callDecoration("getConvexHull"):
|
convex_hull = node.callDecoration("getConvexHull")
|
||||||
node_list.append(node)
|
if convex_hull:
|
||||||
|
xmin = min(x for x, _ in convex_hull._points)
|
||||||
|
xmax = max(x for x, _ in convex_hull._points)
|
||||||
|
ymin = min(y for _, y in convex_hull._points)
|
||||||
|
ymax = max(y for _, y in convex_hull._points)
|
||||||
|
|
||||||
|
convex_hull_polygon = Polygon.from_bounds(xmin, ymin, xmax, ymax)
|
||||||
|
if transform_x < 0:
|
||||||
|
convex_hull_polygon = flip_x(convex_hull_polygon)
|
||||||
|
if transform_y < 0:
|
||||||
|
convex_hull_polygon = flip_y(convex_hull_polygon)
|
||||||
|
|
||||||
if len(node_list) < 2:
|
node_list.append({"node": node,
|
||||||
self._node_stack = node_list[:]
|
"min_coord": [convex_hull_polygon.bounds[0], convex_hull_polygon.bounds[1]],
|
||||||
return
|
})
|
||||||
|
|
||||||
# Copy the list
|
node_list = sorted(node_list, key = lambda d: d["min_coord"])
|
||||||
self._original_node_list = node_list[:]
|
|
||||||
|
|
||||||
## Initialise the hit map (pre-compute all hits between all objects)
|
|
||||||
self._hit_map = [[self._checkHit(i,j) for i in node_list] for j in node_list]
|
|
||||||
|
|
||||||
# Check if we have to files that block eachother. If this is the case, there is no solution!
|
|
||||||
for a in range(0,len(node_list)):
|
|
||||||
for b in range(0,len(node_list)):
|
|
||||||
if a != b and self._hit_map[a][b] and self._hit_map[b][a]:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Sort the original list so that items that block the most other objects are at the beginning.
|
|
||||||
# This does not decrease the worst case running time, but should improve it in most cases.
|
|
||||||
sorted(node_list, key = cmp_to_key(self._calculateScore))
|
|
||||||
|
|
||||||
todo_node_list = [_ObjectOrder([], node_list)]
|
|
||||||
while len(todo_node_list) > 0:
|
|
||||||
current = todo_node_list.pop()
|
|
||||||
for node in current.todo:
|
|
||||||
# Check if the object can be placed with what we have and still allows for a solution in the future
|
|
||||||
if not self._checkHitMultiple(node, current.order) and not self._checkBlockMultiple(node, current.todo):
|
|
||||||
# We found a possible result. Create new todo & order list.
|
|
||||||
new_todo_list = current.todo[:]
|
|
||||||
new_todo_list.remove(node)
|
|
||||||
new_order = current.order[:] + [node]
|
|
||||||
if len(new_todo_list) == 0:
|
|
||||||
# We have no more nodes to check, so quit looking.
|
|
||||||
todo_node_list = None
|
|
||||||
self._node_stack = new_order
|
|
||||||
|
|
||||||
return
|
|
||||||
todo_node_list.append(_ObjectOrder(new_order, new_todo_list))
|
|
||||||
self._node_stack = [] #No result found!
|
|
||||||
|
|
||||||
|
|
||||||
# Check if first object can be printed before the provided list (using the hit map)
|
|
||||||
def _checkHitMultiple(self, node, other_nodes):
|
|
||||||
node_index = self._original_node_list.index(node)
|
|
||||||
for other_node in other_nodes:
|
|
||||||
other_node_index = self._original_node_list.index(other_node)
|
|
||||||
if self._hit_map[node_index][other_node_index]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _checkBlockMultiple(self, node, other_nodes):
|
|
||||||
node_index = self._original_node_list.index(node)
|
|
||||||
for other_node in other_nodes:
|
|
||||||
other_node_index = self._original_node_list.index(other_node)
|
|
||||||
if self._hit_map[other_node_index][node_index] and node_index != other_node_index:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
## Calculate score simply sums the number of other objects it 'blocks'
|
|
||||||
def _calculateScore(self, a, b):
|
|
||||||
score_a = sum(self._hit_map[self._original_node_list.index(a)])
|
|
||||||
score_b = sum(self._hit_map[self._original_node_list.index(b)])
|
|
||||||
return score_a - score_b
|
|
||||||
|
|
||||||
# Checks if A can be printed before B
|
|
||||||
def _checkHit(self, a, b):
|
|
||||||
if a == b:
|
|
||||||
return False
|
|
||||||
|
|
||||||
overlap = a.callDecoration("getConvexHullBoundary").intersectsPolygon(b.callDecoration("getConvexHullHeadFull"))
|
|
||||||
if overlap:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
## Internal object used to keep track of a possible order in which to print objects.
|
|
||||||
class _ObjectOrder():
|
|
||||||
def __init__(self, order, todo):
|
|
||||||
"""
|
|
||||||
:param order: List of indexes in which to print objects, ordered by printing order.
|
|
||||||
:param todo: List of indexes which are not yet inserted into the order list.
|
|
||||||
"""
|
|
||||||
self.order = order
|
|
||||||
self.todo = todo
|
|
||||||
|
|
||||||
|
self._node_stack = [d["node"] for d in node_list]
|
||||||
|
|
|
@ -267,6 +267,7 @@ class PrintInformation(QObject):
|
||||||
new_active_build_plate = self._multi_build_plate_model.activeBuildPlate
|
new_active_build_plate = self._multi_build_plate_model.activeBuildPlate
|
||||||
if new_active_build_plate != self._active_build_plate:
|
if new_active_build_plate != self._active_build_plate:
|
||||||
self._active_build_plate = new_active_build_plate
|
self._active_build_plate = new_active_build_plate
|
||||||
|
self._updateJobName()
|
||||||
|
|
||||||
self._initVariablesWithBuildPlate(self._active_build_plate)
|
self._initVariablesWithBuildPlate(self._active_build_plate)
|
||||||
|
|
||||||
|
@ -299,7 +300,7 @@ class PrintInformation(QObject):
|
||||||
|
|
||||||
def _updateJobName(self):
|
def _updateJobName(self):
|
||||||
if self._base_name == "":
|
if self._base_name == "":
|
||||||
self._job_name = "unnamed"
|
self._job_name = "Untitled"
|
||||||
self._is_user_specified_job_name = False
|
self._is_user_specified_job_name = False
|
||||||
self.jobNameChanged.emit()
|
self.jobNameChanged.emit()
|
||||||
return
|
return
|
||||||
|
@ -320,6 +321,15 @@ class PrintInformation(QObject):
|
||||||
else:
|
else:
|
||||||
self._job_name = base_name
|
self._job_name = base_name
|
||||||
|
|
||||||
|
# In case there are several buildplates, a suffix is attached
|
||||||
|
if self._multi_build_plate_model.maxBuildPlate > 0:
|
||||||
|
connector = "_#"
|
||||||
|
suffix = connector + str(self._active_build_plate + 1)
|
||||||
|
if connector in self._job_name:
|
||||||
|
self._job_name = self._job_name.split(connector)[0] # get the real name
|
||||||
|
if self._active_build_plate != 0:
|
||||||
|
self._job_name += suffix
|
||||||
|
|
||||||
self.jobNameChanged.emit()
|
self.jobNameChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
|
@ -369,8 +379,9 @@ class PrintInformation(QObject):
|
||||||
def baseName(self):
|
def baseName(self):
|
||||||
return self._base_name
|
return self._base_name
|
||||||
|
|
||||||
## Created an acronymn-like abbreviated machine name from the currently active machine name
|
## Created an acronym-like abbreviated machine name from the currently
|
||||||
# Called each time the global stack is switched
|
# active machine name.
|
||||||
|
# Called each time the global stack is switched.
|
||||||
def _setAbbreviatedMachineName(self):
|
def _setAbbreviatedMachineName(self):
|
||||||
global_container_stack = self._application.getGlobalContainerStack()
|
global_container_stack = self._application.getGlobalContainerStack()
|
||||||
if not global_container_stack:
|
if not global_container_stack:
|
||||||
|
|
|
@ -120,7 +120,7 @@ class PrinterOutputModel(QObject):
|
||||||
|
|
||||||
@pyqtProperty(QVariant, notify = headPositionChanged)
|
@pyqtProperty(QVariant, notify = headPositionChanged)
|
||||||
def headPosition(self):
|
def headPosition(self):
|
||||||
return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position_z}
|
return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position.z}
|
||||||
|
|
||||||
def updateHeadPosition(self, x, y, z):
|
def updateHeadPosition(self, x, y, z):
|
||||||
if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z:
|
if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z:
|
||||||
|
|
|
@ -229,7 +229,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
return offset_hull
|
return offset_hull
|
||||||
|
|
||||||
def _getHeadAndFans(self):
|
def _getHeadAndFans(self):
|
||||||
return Polygon(numpy.array(self._global_stack.getProperty("machine_head_with_fans_polygon", "value"), numpy.float32))
|
return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32))
|
||||||
|
|
||||||
def _compute2DConvexHeadFull(self):
|
def _compute2DConvexHeadFull(self):
|
||||||
return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans())
|
return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans())
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Any, cast, List, Optional, Union
|
from typing import Any, cast, List, Optional
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
@ -13,6 +13,7 @@ from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.Interfaces import ContainerInterface, DefinitionContainerInterface
|
from UM.Settings.Interfaces import ContainerInterface, DefinitionContainerInterface
|
||||||
|
from cura.Settings import cura_empty_instance_containers
|
||||||
|
|
||||||
from . import Exceptions
|
from . import Exceptions
|
||||||
|
|
||||||
|
@ -39,14 +40,12 @@ class CuraContainerStack(ContainerStack):
|
||||||
def __init__(self, container_id: str) -> None:
|
def __init__(self, container_id: str) -> None:
|
||||||
super().__init__(container_id)
|
super().__init__(container_id)
|
||||||
|
|
||||||
self._container_registry = ContainerRegistry.getInstance() #type: ContainerRegistry
|
self._empty_instance_container = cura_empty_instance_containers.empty_container #type: InstanceContainer
|
||||||
|
|
||||||
self._empty_instance_container = self._container_registry.getEmptyInstanceContainer() #type: InstanceContainer
|
self._empty_quality_changes = cura_empty_instance_containers.empty_quality_changes_container #type: InstanceContainer
|
||||||
|
self._empty_quality = cura_empty_instance_containers.empty_quality_container #type: InstanceContainer
|
||||||
self._empty_quality_changes = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0] #type: InstanceContainer
|
self._empty_material = cura_empty_instance_containers.empty_material_container #type: InstanceContainer
|
||||||
self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0] #type: InstanceContainer
|
self._empty_variant = cura_empty_instance_containers.empty_variant_container #type: InstanceContainer
|
||||||
self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] #type: InstanceContainer
|
|
||||||
self._empty_variant = self._container_registry.findInstanceContainers(id = "empty_variant")[0] #type: InstanceContainer
|
|
||||||
|
|
||||||
self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] #type: List[ContainerInterface]
|
self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] #type: List[ContainerInterface]
|
||||||
self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes
|
self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes
|
||||||
|
|
|
@ -8,7 +8,7 @@ from UM.Logger import Logger
|
||||||
from UM.Settings.Interfaces import DefinitionContainerInterface
|
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
|
||||||
from cura.Machines.VariantManager import VariantType
|
from cura.Machines.VariantType import VariantType
|
||||||
from .GlobalStack import GlobalStack
|
from .GlobalStack import GlobalStack
|
||||||
from .ExtruderStack import ExtruderStack
|
from .ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
|
@ -108,16 +108,27 @@ class CuraStackBuilder:
|
||||||
|
|
||||||
preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type")
|
preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type")
|
||||||
quality_group_dict = quality_manager.getQualityGroups(new_global_stack)
|
quality_group_dict = quality_manager.getQualityGroups(new_global_stack)
|
||||||
quality_group = quality_group_dict.get(preferred_quality_type)
|
if not quality_group_dict:
|
||||||
|
# There is no available quality group, set all quality containers to empty.
|
||||||
new_global_stack.quality = quality_group.node_for_global.getContainer()
|
|
||||||
if not new_global_stack.quality:
|
|
||||||
new_global_stack.quality = application.empty_quality_container
|
new_global_stack.quality = application.empty_quality_container
|
||||||
for position, extruder_stack in new_global_stack.extruders.items():
|
for extruder_stack in new_global_stack.extruders.values():
|
||||||
if position in quality_group.nodes_for_extruders and quality_group.nodes_for_extruders[position].getContainer():
|
|
||||||
extruder_stack.quality = quality_group.nodes_for_extruders[position].getContainer()
|
|
||||||
else:
|
|
||||||
extruder_stack.quality = application.empty_quality_container
|
extruder_stack.quality = application.empty_quality_container
|
||||||
|
else:
|
||||||
|
# Set the quality containers to the preferred quality type if available, otherwise use the first quality
|
||||||
|
# type that's available.
|
||||||
|
if preferred_quality_type not in quality_group_dict:
|
||||||
|
Logger.log("w", "The preferred quality {quality_type} doesn't exist for this set-up. Choosing a random one.".format(quality_type = preferred_quality_type))
|
||||||
|
preferred_quality_type = next(iter(quality_group_dict))
|
||||||
|
quality_group = quality_group_dict.get(preferred_quality_type)
|
||||||
|
|
||||||
|
new_global_stack.quality = quality_group.node_for_global.getContainer()
|
||||||
|
if not new_global_stack.quality:
|
||||||
|
new_global_stack.quality = application.empty_quality_container
|
||||||
|
for position, extruder_stack in new_global_stack.extruders.items():
|
||||||
|
if position in quality_group.nodes_for_extruders and quality_group.nodes_for_extruders[position].getContainer():
|
||||||
|
extruder_stack.quality = quality_group.nodes_for_extruders[position].getContainer()
|
||||||
|
else:
|
||||||
|
extruder_stack.quality = application.empty_quality_container
|
||||||
|
|
||||||
# Register the global stack after the extruder stacks are created. This prevents the registry from adding another
|
# Register the global stack after the extruder stacks are created. This prevents the registry from adding another
|
||||||
# extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1).
|
# extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1).
|
||||||
|
|
|
@ -139,9 +139,6 @@ class ExtruderStack(CuraContainerStack):
|
||||||
super().deserialize(contents, file_name)
|
super().deserialize(contents, file_name)
|
||||||
if "enabled" not in self.getMetaData():
|
if "enabled" not in self.getMetaData():
|
||||||
self.setMetaDataEntry("enabled", "True")
|
self.setMetaDataEntry("enabled", "True")
|
||||||
stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", ""))
|
|
||||||
if stacks:
|
|
||||||
self.setNextStack(stacks[0])
|
|
||||||
|
|
||||||
def _onPropertiesChanged(self, key: str, properties: Dict[str, Any]) -> None:
|
def _onPropertiesChanged(self, key: str, properties: Dict[str, Any]) -> None:
|
||||||
# When there is a setting that is not settable per extruder that depends on a value from a setting that is,
|
# When there is a setting that is not settable per extruder that depends on a value from a setting that is,
|
||||||
|
|
|
@ -55,6 +55,16 @@ class GlobalStack(CuraContainerStack):
|
||||||
return "machine_stack"
|
return "machine_stack"
|
||||||
return configuration_type
|
return configuration_type
|
||||||
|
|
||||||
|
def getBuildplateName(self) -> Optional[str]:
|
||||||
|
name = None
|
||||||
|
if self.variant.getId() != "empty_variant":
|
||||||
|
name = self.variant.getName()
|
||||||
|
return name
|
||||||
|
|
||||||
|
@pyqtProperty(str, constant = True)
|
||||||
|
def preferred_output_file_formats(self) -> str:
|
||||||
|
return self.getMetaDataEntry("file_formats")
|
||||||
|
|
||||||
## Add an extruder to the list of extruders of this stack.
|
## Add an extruder to the list of extruders of this stack.
|
||||||
#
|
#
|
||||||
# \param extruder The extruder to add.
|
# \param extruder The extruder to add.
|
||||||
|
@ -96,6 +106,9 @@ class GlobalStack(CuraContainerStack):
|
||||||
|
|
||||||
# Handle the "resolve" property.
|
# Handle the "resolve" property.
|
||||||
#TODO: Why the hell does this involve threading?
|
#TODO: Why the hell does this involve threading?
|
||||||
|
# Answer: Because if multiple threads start resolving properties that have the same underlying properties that's
|
||||||
|
# related, without taking a note of which thread a resolve paths belongs to, they can bump into each other and
|
||||||
|
# generate unexpected behaviours.
|
||||||
if self._shouldResolve(key, property_name, context):
|
if self._shouldResolve(key, property_name, context):
|
||||||
current_thread = threading.current_thread()
|
current_thread = threading.current_thread()
|
||||||
self._resolving_settings[current_thread.name].add(key)
|
self._resolving_settings[current_thread.name].add(key)
|
||||||
|
@ -172,6 +185,9 @@ class GlobalStack(CuraContainerStack):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def getHeadAndFansCoordinates(self):
|
||||||
|
return self.getProperty("machine_head_with_fans_polygon", "value")
|
||||||
|
|
||||||
|
|
||||||
## private:
|
## private:
|
||||||
global_stack_mime = MimeType(
|
global_stack_mime = MimeType(
|
||||||
|
|
|
@ -21,9 +21,6 @@ from UM.Settings.SettingFunction import SettingFunction
|
||||||
from UM.Signal import postponeSignals, CompressTechnique
|
from UM.Signal import postponeSignals, CompressTechnique
|
||||||
|
|
||||||
import cura.CuraApplication
|
import cura.CuraApplication
|
||||||
from cura.Machines.ContainerNode import ContainerNode #For typing.
|
|
||||||
from cura.Machines.QualityChangesGroup import QualityChangesGroup #For typing.
|
|
||||||
from cura.Machines.QualityGroup import QualityGroup #For typing.
|
|
||||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||||
|
@ -44,12 +41,16 @@ if TYPE_CHECKING:
|
||||||
from cura.Machines.MaterialManager import MaterialManager
|
from cura.Machines.MaterialManager import MaterialManager
|
||||||
from cura.Machines.QualityManager import QualityManager
|
from cura.Machines.QualityManager import QualityManager
|
||||||
from cura.Machines.VariantManager import VariantManager
|
from cura.Machines.VariantManager import VariantManager
|
||||||
|
from cura.Machines.ContainerNode import ContainerNode
|
||||||
|
from cura.Machines.QualityChangesGroup import QualityChangesGroup
|
||||||
|
from cura.Machines.QualityGroup import QualityGroup
|
||||||
|
|
||||||
|
|
||||||
class MachineManager(QObject):
|
class MachineManager(QObject):
|
||||||
def __init__(self, parent: QObject = None) -> None:
|
def __init__(self, parent: QObject = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self._active_container_stack = None # type: Optional[ExtruderManager]
|
self._active_container_stack = None # type: Optional[ExtruderStack]
|
||||||
self._global_container_stack = None # type: Optional[GlobalStack]
|
self._global_container_stack = None # type: Optional[GlobalStack]
|
||||||
|
|
||||||
self._current_root_material_id = {} # type: Dict[str, str]
|
self._current_root_material_id = {} # type: Dict[str, str]
|
||||||
|
@ -1087,7 +1088,7 @@ class MachineManager(QObject):
|
||||||
self.activeQualityGroupChanged.emit()
|
self.activeQualityGroupChanged.emit()
|
||||||
self.activeQualityChangesGroupChanged.emit()
|
self.activeQualityChangesGroupChanged.emit()
|
||||||
|
|
||||||
def _setQualityGroup(self, quality_group: Optional[QualityGroup], empty_quality_changes: bool = True) -> None:
|
def _setQualityGroup(self, quality_group: Optional["QualityGroup"], empty_quality_changes: bool = True) -> None:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
if quality_group is None:
|
if quality_group is None:
|
||||||
|
@ -1118,7 +1119,7 @@ class MachineManager(QObject):
|
||||||
self.activeQualityGroupChanged.emit()
|
self.activeQualityGroupChanged.emit()
|
||||||
self.activeQualityChangesGroupChanged.emit()
|
self.activeQualityChangesGroupChanged.emit()
|
||||||
|
|
||||||
def _fixQualityChangesGroupToNotSupported(self, quality_changes_group: QualityChangesGroup) -> None:
|
def _fixQualityChangesGroupToNotSupported(self, quality_changes_group: "QualityChangesGroup") -> None:
|
||||||
nodes = [quality_changes_group.node_for_global] + list(quality_changes_group.nodes_for_extruders.values())
|
nodes = [quality_changes_group.node_for_global] + list(quality_changes_group.nodes_for_extruders.values())
|
||||||
containers = [n.getContainer() for n in nodes if n is not None]
|
containers = [n.getContainer() for n in nodes if n is not None]
|
||||||
for container in containers:
|
for container in containers:
|
||||||
|
@ -1126,7 +1127,7 @@ class MachineManager(QObject):
|
||||||
container.setMetaDataEntry("quality_type", "not_supported")
|
container.setMetaDataEntry("quality_type", "not_supported")
|
||||||
quality_changes_group.quality_type = "not_supported"
|
quality_changes_group.quality_type = "not_supported"
|
||||||
|
|
||||||
def _setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup) -> None:
|
def _setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return #Can't change that.
|
return #Can't change that.
|
||||||
quality_type = quality_changes_group.quality_type
|
quality_type = quality_changes_group.quality_type
|
||||||
|
@ -1170,20 +1171,20 @@ class MachineManager(QObject):
|
||||||
self.activeQualityGroupChanged.emit()
|
self.activeQualityGroupChanged.emit()
|
||||||
self.activeQualityChangesGroupChanged.emit()
|
self.activeQualityChangesGroupChanged.emit()
|
||||||
|
|
||||||
def _setVariantNode(self, position: str, container_node: ContainerNode) -> None:
|
def _setVariantNode(self, position: str, container_node: "ContainerNode") -> None:
|
||||||
if container_node.getContainer() is None or self._global_container_stack is None:
|
if container_node.getContainer() is None or self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
self._global_container_stack.extruders[position].variant = container_node.getContainer()
|
self._global_container_stack.extruders[position].variant = container_node.getContainer()
|
||||||
self.activeVariantChanged.emit()
|
self.activeVariantChanged.emit()
|
||||||
|
|
||||||
def _setGlobalVariant(self, container_node: ContainerNode) -> None:
|
def _setGlobalVariant(self, container_node: "ContainerNode") -> None:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
self._global_container_stack.variant = container_node.getContainer()
|
self._global_container_stack.variant = container_node.getContainer()
|
||||||
if not self._global_container_stack.variant:
|
if not self._global_container_stack.variant:
|
||||||
self._global_container_stack.variant = self._application.empty_variant_container
|
self._global_container_stack.variant = self._application.empty_variant_container
|
||||||
|
|
||||||
def _setMaterial(self, position: str, container_node: ContainerNode = None) -> None:
|
def _setMaterial(self, position: str, container_node: Optional["ContainerNode"] = None) -> None:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
if container_node and container_node.getContainer():
|
if container_node and container_node.getContainer():
|
||||||
|
@ -1256,13 +1257,17 @@ class MachineManager(QObject):
|
||||||
else:
|
else:
|
||||||
position_list = [position]
|
position_list = [position]
|
||||||
|
|
||||||
|
buildplate_name = None
|
||||||
|
if self._global_container_stack.variant.getId() != "empty_variant":
|
||||||
|
buildplate_name = self._global_container_stack.variant.getName()
|
||||||
|
|
||||||
for position_item in position_list:
|
for position_item in position_list:
|
||||||
extruder = self._global_container_stack.extruders[position_item]
|
extruder = self._global_container_stack.extruders[position_item]
|
||||||
|
|
||||||
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
|
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
|
||||||
current_variant_name = None
|
current_nozzle_name = None
|
||||||
if extruder.variant.getId() != self._empty_variant_container.getId():
|
if extruder.variant.getId() != self._empty_variant_container.getId():
|
||||||
current_variant_name = extruder.variant.getMetaDataEntry("name")
|
current_nozzle_name = extruder.variant.getMetaDataEntry("name")
|
||||||
|
|
||||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||||
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
||||||
|
@ -1271,7 +1276,8 @@ class MachineManager(QObject):
|
||||||
material_diameter = extruder.getProperty("material_diameter", "value", context)
|
material_diameter = extruder.getProperty("material_diameter", "value", context)
|
||||||
candidate_materials = self._material_manager.getAvailableMaterials(
|
candidate_materials = self._material_manager.getAvailableMaterials(
|
||||||
self._global_container_stack.definition,
|
self._global_container_stack.definition,
|
||||||
current_variant_name,
|
current_nozzle_name,
|
||||||
|
buildplate_name,
|
||||||
material_diameter)
|
material_diameter)
|
||||||
|
|
||||||
if not candidate_materials:
|
if not candidate_materials:
|
||||||
|
@ -1284,7 +1290,7 @@ class MachineManager(QObject):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# The current material is not available, find the preferred one
|
# The current material is not available, find the preferred one
|
||||||
material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, position_item, current_variant_name)
|
material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, position_item, current_nozzle_name)
|
||||||
if material_node is not None:
|
if material_node is not None:
|
||||||
self._setMaterial(position_item, material_node)
|
self._setMaterial(position_item, material_node)
|
||||||
|
|
||||||
|
@ -1326,7 +1332,12 @@ class MachineManager(QObject):
|
||||||
for extruder_configuration in configuration.extruderConfigurations:
|
for extruder_configuration in configuration.extruderConfigurations:
|
||||||
position = str(extruder_configuration.position)
|
position = str(extruder_configuration.position)
|
||||||
variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), extruder_configuration.hotendID)
|
variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), extruder_configuration.hotendID)
|
||||||
material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack, position, extruder_configuration.hotendID, extruder_configuration.material.guid)
|
material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack,
|
||||||
|
position,
|
||||||
|
extruder_configuration.hotendID,
|
||||||
|
configuration.buildplateConfiguration,
|
||||||
|
extruder_configuration.material.guid)
|
||||||
|
|
||||||
if variant_container_node:
|
if variant_container_node:
|
||||||
self._setVariantNode(position, variant_container_node)
|
self._setVariantNode(position, variant_container_node)
|
||||||
else:
|
else:
|
||||||
|
@ -1378,7 +1389,7 @@ class MachineManager(QObject):
|
||||||
return bool(containers)
|
return bool(containers)
|
||||||
|
|
||||||
@pyqtSlot("QVariant")
|
@pyqtSlot("QVariant")
|
||||||
def setGlobalVariant(self, container_node: ContainerNode) -> None:
|
def setGlobalVariant(self, container_node: "ContainerNode") -> None:
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
self._setGlobalVariant(container_node)
|
self._setGlobalVariant(container_node)
|
||||||
|
@ -1389,12 +1400,17 @@ class MachineManager(QObject):
|
||||||
def setMaterialById(self, position: str, root_material_id: str) -> None:
|
def setMaterialById(self, position: str, root_material_id: str) -> None:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
|
buildplate_name = None
|
||||||
|
if self._global_container_stack.variant.getId() != "empty_variant":
|
||||||
|
buildplate_name = self._global_container_stack.variant.getName()
|
||||||
|
|
||||||
machine_definition_id = self._global_container_stack.definition.id
|
machine_definition_id = self._global_container_stack.definition.id
|
||||||
position = str(position)
|
position = str(position)
|
||||||
extruder_stack = self._global_container_stack.extruders[position]
|
extruder_stack = self._global_container_stack.extruders[position]
|
||||||
variant_name = extruder_stack.variant.getName()
|
nozzle_name = extruder_stack.variant.getName()
|
||||||
material_diameter = extruder_stack.approximateMaterialDiameter
|
material_diameter = extruder_stack.approximateMaterialDiameter
|
||||||
material_node = self._material_manager.getMaterialNode(machine_definition_id, variant_name, material_diameter, root_material_id)
|
material_node = self._material_manager.getMaterialNode(machine_definition_id, nozzle_name, buildplate_name,
|
||||||
|
material_diameter, root_material_id)
|
||||||
self.setMaterial(position, material_node)
|
self.setMaterial(position, material_node)
|
||||||
|
|
||||||
## global_stack: if you want to provide your own global_stack instead of the current active one
|
## global_stack: if you want to provide your own global_stack instead of the current active one
|
||||||
|
@ -1423,7 +1439,7 @@ class MachineManager(QObject):
|
||||||
self.setVariant(position, variant_node)
|
self.setVariant(position, variant_node)
|
||||||
|
|
||||||
@pyqtSlot(str, "QVariant")
|
@pyqtSlot(str, "QVariant")
|
||||||
def setVariant(self, position: str, container_node: ContainerNode) -> None:
|
def setVariant(self, position: str, container_node: "ContainerNode") -> None:
|
||||||
position = str(position)
|
position = str(position)
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
|
@ -1447,7 +1463,7 @@ class MachineManager(QObject):
|
||||||
## Optionally provide global_stack if you want to use your own
|
## Optionally provide global_stack if you want to use your own
|
||||||
# The active global_stack is treated differently.
|
# The active global_stack is treated differently.
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def setQualityGroup(self, quality_group: QualityGroup, no_dialog: bool = False, global_stack: Optional["GlobalStack"] = None) -> None:
|
def setQualityGroup(self, quality_group: "QualityGroup", no_dialog: bool = False, global_stack: Optional["GlobalStack"] = None) -> None:
|
||||||
if global_stack is not None and global_stack != self._global_container_stack:
|
if global_stack is not None and global_stack != self._global_container_stack:
|
||||||
if quality_group is None:
|
if quality_group is None:
|
||||||
Logger.log("e", "Could not set quality group because quality group is None")
|
Logger.log("e", "Could not set quality group because quality group is None")
|
||||||
|
@ -1455,9 +1471,14 @@ class MachineManager(QObject):
|
||||||
if quality_group.node_for_global is None:
|
if quality_group.node_for_global is None:
|
||||||
Logger.log("e", "Could not set quality group [%s] because it has no node_for_global", str(quality_group))
|
Logger.log("e", "Could not set quality group [%s] because it has no node_for_global", str(quality_group))
|
||||||
return
|
return
|
||||||
|
# This is not changing the quality for the active machine !!!!!!!!
|
||||||
global_stack.quality = quality_group.node_for_global.getContainer()
|
global_stack.quality = quality_group.node_for_global.getContainer()
|
||||||
for extruder_nr, extruder_stack in global_stack.extruders.items():
|
for extruder_nr, extruder_stack in global_stack.extruders.items():
|
||||||
extruder_stack.quality = quality_group.nodes_for_extruders[extruder_nr].getContainer()
|
quality_container = self._empty_quality_container
|
||||||
|
if extruder_nr in quality_group.nodes_for_extruders:
|
||||||
|
container = quality_group.nodes_for_extruders[extruder_nr].getContainer()
|
||||||
|
quality_container = container if container is not None else quality_container
|
||||||
|
extruder_stack.quality = quality_container
|
||||||
return
|
return
|
||||||
|
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
|
@ -1469,11 +1490,11 @@ class MachineManager(QObject):
|
||||||
self._application.discardOrKeepProfileChanges()
|
self._application.discardOrKeepProfileChanges()
|
||||||
|
|
||||||
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
|
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
|
||||||
def activeQualityGroup(self) -> Optional[QualityGroup]:
|
def activeQualityGroup(self) -> Optional["QualityGroup"]:
|
||||||
return self._current_quality_group
|
return self._current_quality_group
|
||||||
|
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup, no_dialog: bool = False) -> None:
|
def setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", no_dialog: bool = False) -> None:
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
self._setQualityChangesGroup(quality_changes_group)
|
self._setQualityChangesGroup(quality_changes_group)
|
||||||
|
@ -1492,7 +1513,7 @@ class MachineManager(QObject):
|
||||||
stack.userChanges.clear()
|
stack.userChanges.clear()
|
||||||
|
|
||||||
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
|
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
|
||||||
def activeQualityChangesGroup(self) -> Optional[QualityChangesGroup]:
|
def activeQualityChangesGroup(self) -> Optional["QualityChangesGroup"]:
|
||||||
return self._current_quality_changes_group
|
return self._current_quality_changes_group
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeQualityGroupChanged)
|
@pyqtProperty(str, notify = activeQualityGroupChanged)
|
||||||
|
|
41
cura/Settings/SidebarCustomMenuItemsModel.py
Normal file
41
cura/Settings/SidebarCustomMenuItemsModel.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from PyQt5.QtCore import pyqtSlot, Qt
|
||||||
|
|
||||||
|
|
||||||
|
class SidebarCustomMenuItemsModel(ListModel):
|
||||||
|
name_role = Qt.UserRole + 1
|
||||||
|
actions_role = Qt.UserRole + 2
|
||||||
|
menu_item_role = Qt.UserRole + 3
|
||||||
|
menu_item_icon_name_role = Qt.UserRole + 5
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.addRoleName(self.name_role, "name")
|
||||||
|
self.addRoleName(self.actions_role, "actions")
|
||||||
|
self.addRoleName(self.menu_item_role, "menu_item")
|
||||||
|
self.addRoleName(self.menu_item_icon_name_role, "iconName")
|
||||||
|
self._updateExtensionList()
|
||||||
|
|
||||||
|
def _updateExtensionList(self)-> None:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
for menu_item in CuraApplication.getInstance().getSidebarCustomMenuItems():
|
||||||
|
|
||||||
|
self.appendItem({
|
||||||
|
"name": menu_item["name"],
|
||||||
|
"icon_name": menu_item["icon_name"],
|
||||||
|
"actions": menu_item["actions"],
|
||||||
|
"menu_item": menu_item["menu_item"]
|
||||||
|
})
|
||||||
|
|
||||||
|
@pyqtSlot(str, "QVariantList", "QVariantMap")
|
||||||
|
def callMenuItemMethod(self, menu_item_name: str, menu_item_actions: list, kwargs: Any) -> None:
|
||||||
|
for item in self._items:
|
||||||
|
if menu_item_name == item["name"]:
|
||||||
|
for method in menu_item_actions:
|
||||||
|
getattr(item["menu_item"], method)(kwargs)
|
||||||
|
break
|
56
cura/Settings/cura_empty_instance_containers.py
Normal file
56
cura/Settings/cura_empty_instance_containers.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from UM.Settings.constant_instance_containers import EMPTY_CONTAINER_ID, empty_container
|
||||||
|
|
||||||
|
|
||||||
|
# Empty definition changes
|
||||||
|
EMPTY_DEFINITION_CHANGES_CONTAINER_ID = "empty_definition_changes"
|
||||||
|
empty_definition_changes_container = copy.deepcopy(empty_container)
|
||||||
|
empty_definition_changes_container.setMetaDataEntry("id", EMPTY_DEFINITION_CHANGES_CONTAINER_ID)
|
||||||
|
empty_definition_changes_container.setMetaDataEntry("type", "definition_changes")
|
||||||
|
|
||||||
|
# Empty variant
|
||||||
|
EMPTY_VARIANT_CONTAINER_ID = "empty_variant"
|
||||||
|
empty_variant_container = copy.deepcopy(empty_container)
|
||||||
|
empty_variant_container.setMetaDataEntry("id", EMPTY_VARIANT_CONTAINER_ID)
|
||||||
|
empty_variant_container.setMetaDataEntry("type", "variant")
|
||||||
|
|
||||||
|
# Empty material
|
||||||
|
EMPTY_MATERIAL_CONTAINER_ID = "empty_material"
|
||||||
|
empty_material_container = copy.deepcopy(empty_container)
|
||||||
|
empty_material_container.setMetaDataEntry("id", EMPTY_MATERIAL_CONTAINER_ID)
|
||||||
|
empty_material_container.setMetaDataEntry("type", "material")
|
||||||
|
|
||||||
|
# Empty quality
|
||||||
|
EMPTY_QUALITY_CONTAINER_ID = "empty_quality"
|
||||||
|
empty_quality_container = copy.deepcopy(empty_container)
|
||||||
|
empty_quality_container.setMetaDataEntry("id", EMPTY_QUALITY_CONTAINER_ID)
|
||||||
|
empty_quality_container.setName("Not Supported")
|
||||||
|
empty_quality_container.setMetaDataEntry("quality_type", "not_supported")
|
||||||
|
empty_quality_container.setMetaDataEntry("type", "quality")
|
||||||
|
empty_quality_container.setMetaDataEntry("supported", False)
|
||||||
|
|
||||||
|
# Empty quality changes
|
||||||
|
EMPTY_QUALITY_CHANGES_CONTAINER_ID = "empty_quality_changes"
|
||||||
|
empty_quality_changes_container = copy.deepcopy(empty_container)
|
||||||
|
empty_quality_changes_container.setMetaDataEntry("id", EMPTY_QUALITY_CHANGES_CONTAINER_ID)
|
||||||
|
empty_quality_changes_container.setMetaDataEntry("type", "quality_changes")
|
||||||
|
empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported")
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["EMPTY_CONTAINER_ID",
|
||||||
|
"empty_container", # For convenience
|
||||||
|
"EMPTY_DEFINITION_CHANGES_CONTAINER_ID",
|
||||||
|
"empty_definition_changes_container",
|
||||||
|
"EMPTY_VARIANT_CONTAINER_ID",
|
||||||
|
"empty_variant_container",
|
||||||
|
"EMPTY_MATERIAL_CONTAINER_ID",
|
||||||
|
"empty_material_container",
|
||||||
|
"EMPTY_QUALITY_CHANGES_CONTAINER_ID",
|
||||||
|
"empty_quality_changes_container",
|
||||||
|
"EMPTY_QUALITY_CONTAINER_ID",
|
||||||
|
"empty_quality_container"
|
||||||
|
]
|
|
@ -131,6 +131,7 @@ faulthandler.enable(all_threads = True)
|
||||||
# first seems to prevent Sip from going into a state where it
|
# first seems to prevent Sip from going into a state where it
|
||||||
# tries to create PyQt objects on a non-main thread.
|
# tries to create PyQt objects on a non-main thread.
|
||||||
import Arcus #@UnusedImport
|
import Arcus #@UnusedImport
|
||||||
|
import Savitar #@UnusedImport
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
app = CuraApplication()
|
app = CuraApplication()
|
||||||
|
|
|
@ -59,7 +59,7 @@ class ThreeMFReader(MeshReader):
|
||||||
if transformation == "":
|
if transformation == "":
|
||||||
return Matrix()
|
return Matrix()
|
||||||
|
|
||||||
splitted_transformation = transformation.split()
|
split_transformation = transformation.split()
|
||||||
## Transformation is saved as:
|
## Transformation is saved as:
|
||||||
## M00 M01 M02 0.0
|
## M00 M01 M02 0.0
|
||||||
## M10 M11 M12 0.0
|
## M10 M11 M12 0.0
|
||||||
|
@ -68,20 +68,20 @@ class ThreeMFReader(MeshReader):
|
||||||
## We switch the row & cols as that is how everyone else uses matrices!
|
## We switch the row & cols as that is how everyone else uses matrices!
|
||||||
temp_mat = Matrix()
|
temp_mat = Matrix()
|
||||||
# Rotation & Scale
|
# Rotation & Scale
|
||||||
temp_mat._data[0, 0] = splitted_transformation[0]
|
temp_mat._data[0, 0] = split_transformation[0]
|
||||||
temp_mat._data[1, 0] = splitted_transformation[1]
|
temp_mat._data[1, 0] = split_transformation[1]
|
||||||
temp_mat._data[2, 0] = splitted_transformation[2]
|
temp_mat._data[2, 0] = split_transformation[2]
|
||||||
temp_mat._data[0, 1] = splitted_transformation[3]
|
temp_mat._data[0, 1] = split_transformation[3]
|
||||||
temp_mat._data[1, 1] = splitted_transformation[4]
|
temp_mat._data[1, 1] = split_transformation[4]
|
||||||
temp_mat._data[2, 1] = splitted_transformation[5]
|
temp_mat._data[2, 1] = split_transformation[5]
|
||||||
temp_mat._data[0, 2] = splitted_transformation[6]
|
temp_mat._data[0, 2] = split_transformation[6]
|
||||||
temp_mat._data[1, 2] = splitted_transformation[7]
|
temp_mat._data[1, 2] = split_transformation[7]
|
||||||
temp_mat._data[2, 2] = splitted_transformation[8]
|
temp_mat._data[2, 2] = split_transformation[8]
|
||||||
|
|
||||||
# Translation
|
# Translation
|
||||||
temp_mat._data[0, 3] = splitted_transformation[9]
|
temp_mat._data[0, 3] = split_transformation[9]
|
||||||
temp_mat._data[1, 3] = splitted_transformation[10]
|
temp_mat._data[1, 3] = split_transformation[10]
|
||||||
temp_mat._data[2, 3] = splitted_transformation[11]
|
temp_mat._data[2, 3] = split_transformation[11]
|
||||||
|
|
||||||
return temp_mat
|
return temp_mat
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Preferences import Preferences
|
from UM.Preferences import Preferences
|
||||||
|
|
||||||
|
from cura.Machines.VariantType import VariantType
|
||||||
from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
@ -84,14 +85,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
MimeTypeDatabase.addMimeType(
|
|
||||||
MimeType(
|
|
||||||
name="application/x-curaproject+xml",
|
|
||||||
comment="Cura Project File",
|
|
||||||
suffixes=["curaproject.3mf"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self._supported_extensions = [".3mf"]
|
self._supported_extensions = [".3mf"]
|
||||||
self._dialog = WorkspaceDialog()
|
self._dialog = WorkspaceDialog()
|
||||||
self._3mf_mesh_reader = None
|
self._3mf_mesh_reader = None
|
||||||
|
@ -629,6 +622,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
type = "extruder_train")
|
type = "extruder_train")
|
||||||
extruder_stack_dict = {stack.getMetaDataEntry("position"): stack for stack in extruder_stacks}
|
extruder_stack_dict = {stack.getMetaDataEntry("position"): stack for stack in extruder_stacks}
|
||||||
|
|
||||||
|
# Make sure that those extruders have the global stack as the next stack or later some value evaluation
|
||||||
|
# will fail.
|
||||||
|
for stack in extruder_stacks:
|
||||||
|
stack.setNextStack(global_stack, connect_signals = False)
|
||||||
|
|
||||||
Logger.log("d", "Workspace loading is checking definitions...")
|
Logger.log("d", "Workspace loading is checking definitions...")
|
||||||
# Get all the definition files & check if they exist. If not, add them.
|
# Get all the definition files & check if they exist. If not, add them.
|
||||||
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
||||||
|
@ -720,8 +718,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
nodes = []
|
nodes = []
|
||||||
|
|
||||||
base_file_name = os.path.basename(file_name)
|
base_file_name = os.path.basename(file_name)
|
||||||
if base_file_name.endswith(".curaproject.3mf"):
|
|
||||||
base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
|
|
||||||
self.setWorkspaceName(base_file_name)
|
self.setWorkspaceName(base_file_name)
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
@ -889,7 +885,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
parser = self._machine_info.variant_info.parser
|
parser = self._machine_info.variant_info.parser
|
||||||
variant_name = parser["general"]["name"]
|
variant_name = parser["general"]["name"]
|
||||||
|
|
||||||
from cura.Machines.VariantManager import VariantType
|
|
||||||
variant_type = VariantType.BUILD_PLATE
|
variant_type = VariantType.BUILD_PLATE
|
||||||
|
|
||||||
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
|
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
|
||||||
|
@ -905,7 +900,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
parser = extruder_info.variant_info.parser
|
parser = extruder_info.variant_info.parser
|
||||||
|
|
||||||
variant_name = parser["general"]["name"]
|
variant_name = parser["general"]["name"]
|
||||||
from cura.Machines.VariantManager import VariantType
|
|
||||||
variant_type = VariantType.NOZZLE
|
variant_type = VariantType.NOZZLE
|
||||||
|
|
||||||
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
|
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
|
||||||
|
@ -929,12 +923,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
root_material_id = extruder_info.root_material_id
|
root_material_id = extruder_info.root_material_id
|
||||||
root_material_id = self._old_new_materials.get(root_material_id, root_material_id)
|
root_material_id = self._old_new_materials.get(root_material_id, root_material_id)
|
||||||
|
|
||||||
|
build_plate_id = global_stack.variant.getId()
|
||||||
|
|
||||||
# get material diameter of this extruder
|
# get material diameter of this extruder
|
||||||
machine_material_diameter = extruder_stack.materialDiameter
|
machine_material_diameter = extruder_stack.materialDiameter
|
||||||
material_node = material_manager.getMaterialNode(global_stack.definition.getId(),
|
material_node = material_manager.getMaterialNode(global_stack.definition.getId(),
|
||||||
extruder_stack.variant.getName(),
|
extruder_stack.variant.getName(),
|
||||||
|
build_plate_id,
|
||||||
machine_material_diameter,
|
machine_material_diameter,
|
||||||
root_material_id)
|
root_material_id)
|
||||||
|
|
||||||
if material_node is not None and material_node.getContainer() is not None:
|
if material_node is not None and material_node.getContainer() is not None:
|
||||||
extruder_stack.material = material_node.getContainer()
|
extruder_stack.material = material_node.getContainer()
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,7 @@ catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
def getMetaData() -> Dict:
|
def getMetaData() -> Dict:
|
||||||
# Workaround for osx not supporting double file extensions correctly.
|
workspace_extension = "3mf"
|
||||||
if Platform.isOSX():
|
|
||||||
workspace_extension = "3mf"
|
|
||||||
else:
|
|
||||||
workspace_extension = "curaproject.3mf"
|
|
||||||
|
|
||||||
metaData = {}
|
metaData = {}
|
||||||
if "3MFReader.ThreeMFReader" in sys.modules:
|
if "3MFReader.ThreeMFReader" in sys.modules:
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for reading 3MF files.",
|
"description": "Provides support for reading 3MF files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,9 @@ except ImportError:
|
||||||
import zipfile
|
import zipfile
|
||||||
import UM.Application
|
import UM.Application
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
class ThreeMFWriter(MeshWriter):
|
class ThreeMFWriter(MeshWriter):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -173,6 +176,7 @@ class ThreeMFWriter(MeshWriter):
|
||||||
archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
|
archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.logException("e", "Error writing zip file")
|
Logger.logException("e", "Error writing zip file")
|
||||||
|
self.setInformation(catalog.i18nc("@error:zip", "Error writing 3mf file."))
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
if not self._store_archive:
|
if not self._store_archive:
|
||||||
|
|
|
@ -15,11 +15,7 @@ from UM.Platform import Platform
|
||||||
i18n_catalog = i18nCatalog("uranium")
|
i18n_catalog = i18nCatalog("uranium")
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
# Workarround for osx not supporting double file extensions correctly.
|
workspace_extension = "3mf"
|
||||||
if Platform.isOSX():
|
|
||||||
workspace_extension = "3mf"
|
|
||||||
else:
|
|
||||||
workspace_extension = "curaproject.3mf"
|
|
||||||
|
|
||||||
metaData = {}
|
metaData = {}
|
||||||
|
|
||||||
|
@ -36,7 +32,7 @@ def getMetaData():
|
||||||
"output": [{
|
"output": [{
|
||||||
"extension": workspace_extension,
|
"extension": workspace_extension,
|
||||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
||||||
"mime_type": "application/x-curaproject+xml",
|
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
|
||||||
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for writing 3MF files.",
|
"description": "Provides support for writing 3MF files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Shows changes since latest checked version.",
|
"description": "Shows changes since latest checked version.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import argparse #To run the engine in debug mode if the front-end is in debug mode.
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import os
|
import os
|
||||||
from PyQt5.QtCore import QObject, QTimer, pyqtSlot
|
from PyQt5.QtCore import QObject, QTimer, pyqtSlot
|
||||||
|
@ -179,7 +180,15 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# \return list of commands and args / parameters.
|
# \return list of commands and args / parameters.
|
||||||
def getEngineCommand(self) -> List[str]:
|
def getEngineCommand(self) -> List[str]:
|
||||||
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
|
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
|
||||||
return [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
|
command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
|
||||||
|
parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.")
|
||||||
|
known_args = vars(parser.parse_known_args()[0])
|
||||||
|
if known_args["debug"]:
|
||||||
|
command.append("-vvv")
|
||||||
|
|
||||||
|
return command
|
||||||
|
|
||||||
## Emitted when we get a message containing print duration and material amount.
|
## Emitted when we get a message containing print duration and material amount.
|
||||||
# This also implies the slicing has finished.
|
# This also implies the slicing has finished.
|
||||||
|
@ -541,6 +550,9 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
## Remove old layer data (if any)
|
## Remove old layer data (if any)
|
||||||
def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
|
def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
|
||||||
|
# Clear out any old gcode
|
||||||
|
self._scene.gcode_dict = {} # type: ignore
|
||||||
|
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if node.callDecoration("getLayerData"):
|
if node.callDecoration("getLayerData"):
|
||||||
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
|
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
|
||||||
|
|
|
@ -41,7 +41,7 @@ class StartJobResult(IntEnum):
|
||||||
|
|
||||||
## Formatter class that handles token expansion in start/end gcode
|
## Formatter class that handles token expansion in start/end gcode
|
||||||
class GcodeStartEndFormatter(Formatter):
|
class GcodeStartEndFormatter(Formatter):
|
||||||
def get_value(self, key: str, *args: str, default_extruder_nr: str = "-1", **kwargs) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
|
def get_value(self, key: str, args: str, kwargs: dict, default_extruder_nr: str = "-1") -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
|
||||||
# The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
|
# The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
|
||||||
# and a default_extruder_nr to use when no extruder_nr is specified
|
# and a default_extruder_nr to use when no extruder_nr is specified
|
||||||
|
|
||||||
|
@ -220,8 +220,10 @@ class StartSliceJob(Job):
|
||||||
stack = global_stack
|
stack = global_stack
|
||||||
skip_group = False
|
skip_group = False
|
||||||
for node in group:
|
for node in group:
|
||||||
|
# Only check if the printing extruder is enabled for printing meshes
|
||||||
|
is_non_printing_mesh = node.callDecoration("evaluateIsNonPrintingMesh")
|
||||||
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||||
if not extruders_enabled[extruder_position]:
|
if not is_non_printing_mesh and not extruders_enabled[extruder_position]:
|
||||||
skip_group = True
|
skip_group = True
|
||||||
has_model_with_disabled_extruders = True
|
has_model_with_disabled_extruders = True
|
||||||
associated_disabled_extruders.add(extruder_position)
|
associated_disabled_extruders.add(extruder_position)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "CuraEngine Backend",
|
"name": "CuraEngine Backend",
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Provides the link to the CuraEngine slicing backend.",
|
"description": "Provides the link to the CuraEngine slicing backend.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for importing Cura profiles.",
|
"description": "Provides support for importing Cura profiles.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for exporting Cura profiles.",
|
"description": "Provides support for exporting Cura profiles.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog":"cura"
|
"i18n-catalog":"cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Checks for firmware updates.",
|
"description": "Checks for firmware updates.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Reads g-code from a compressed archive.",
|
"description": "Reads g-code from a compressed archive.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,17 @@ from UM.Mesh.MeshWriter import MeshWriter #The class we're extending/implementin
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Scene.SceneNode import SceneNode #For typing.
|
from UM.Scene.SceneNode import SceneNode #For typing.
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
## A file writer that writes gzipped g-code.
|
## A file writer that writes gzipped g-code.
|
||||||
#
|
#
|
||||||
# If you're zipping g-code, you might as well use gzip!
|
# If you're zipping g-code, you might as well use gzip!
|
||||||
class GCodeGzWriter(MeshWriter):
|
class GCodeGzWriter(MeshWriter):
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__(add_to_recent_files = False)
|
||||||
|
|
||||||
## Writes the gzipped g-code to a stream.
|
## Writes the gzipped g-code to a stream.
|
||||||
#
|
#
|
||||||
# Note that even though the function accepts a collection of nodes, the
|
# Note that even though the function accepts a collection of nodes, the
|
||||||
|
@ -28,12 +35,15 @@ class GCodeGzWriter(MeshWriter):
|
||||||
def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode = MeshWriter.OutputMode.BinaryMode) -> bool:
|
def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode = MeshWriter.OutputMode.BinaryMode) -> bool:
|
||||||
if mode != MeshWriter.OutputMode.BinaryMode:
|
if mode != MeshWriter.OutputMode.BinaryMode:
|
||||||
Logger.log("e", "GCodeGzWriter does not support text mode.")
|
Logger.log("e", "GCodeGzWriter does not support text mode.")
|
||||||
|
self.setInformation(catalog.i18nc("@error:not supported", "GCodeGzWriter does not support text mode."))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#Get the g-code from the g-code writer.
|
#Get the g-code from the g-code writer.
|
||||||
gcode_textio = StringIO() #We have to convert the g-code into bytes.
|
gcode_textio = StringIO() #We have to convert the g-code into bytes.
|
||||||
success = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter")).write(gcode_textio, None)
|
gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
|
||||||
|
success = gcode_writer.write(gcode_textio, None)
|
||||||
if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code.
|
if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code.
|
||||||
|
self.setInformation(gcode_writer.getInformation())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
result = gzip.compress(gcode_textio.getvalue().encode("utf-8"))
|
result = gzip.compress(gcode_textio.getvalue().encode("utf-8"))
|
||||||
|
|
|
@ -16,7 +16,8 @@ def getMetaData():
|
||||||
"extension": file_extension,
|
"extension": file_extension,
|
||||||
"description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"),
|
"description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"),
|
||||||
"mime_type": "application/gzip",
|
"mime_type": "application/gzip",
|
||||||
"mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode
|
"mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode,
|
||||||
|
"hide_in_file_dialog": True,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Writes g-code to a compressed archive.",
|
"description": "Writes g-code to a compressed archive.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for importing profiles from g-code files.",
|
"description": "Provides support for importing profiles from g-code files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Victor Larchenko",
|
"author": "Victor Larchenko",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Allows loading and displaying G-code files.",
|
"description": "Allows loading and displaying G-code files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
|
||||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
## Writes g-code to a file.
|
## Writes g-code to a file.
|
||||||
#
|
#
|
||||||
|
@ -45,7 +47,7 @@ class GCodeWriter(MeshWriter):
|
||||||
_setting_keyword = ";SETTING_"
|
_setting_keyword = ";SETTING_"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__(add_to_recent_files = False)
|
||||||
|
|
||||||
self._application = Application.getInstance()
|
self._application = Application.getInstance()
|
||||||
|
|
||||||
|
@ -62,11 +64,13 @@ class GCodeWriter(MeshWriter):
|
||||||
def write(self, stream, nodes, mode = MeshWriter.OutputMode.TextMode):
|
def write(self, stream, nodes, mode = MeshWriter.OutputMode.TextMode):
|
||||||
if mode != MeshWriter.OutputMode.TextMode:
|
if mode != MeshWriter.OutputMode.TextMode:
|
||||||
Logger.log("e", "GCodeWriter does not support non-text mode.")
|
Logger.log("e", "GCodeWriter does not support non-text mode.")
|
||||||
|
self.setInformation(catalog.i18nc("@error:not supported", "GCodeWriter does not support non-text mode."))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
scene = Application.getInstance().getController().getScene()
|
scene = Application.getInstance().getController().getScene()
|
||||||
if not hasattr(scene, "gcode_dict"):
|
if not hasattr(scene, "gcode_dict"):
|
||||||
|
self.setInformation(catalog.i18nc("@warning:status", "Please generate G-code before saving."))
|
||||||
return False
|
return False
|
||||||
gcode_dict = getattr(scene, "gcode_dict")
|
gcode_dict = getattr(scene, "gcode_dict")
|
||||||
gcode_list = gcode_dict.get(active_build_plate, None)
|
gcode_list = gcode_dict.get(active_build_plate, None)
|
||||||
|
@ -82,6 +86,7 @@ class GCodeWriter(MeshWriter):
|
||||||
stream.write(settings)
|
stream.write(settings)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
self.setInformation(catalog.i18nc("@warning:status", "Please generate G-code before saving."))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
## Create a new container with container 2 as base and container 1 written over it.
|
## Create a new container with container 2 as base and container 1 written over it.
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Writes g-code to a file.",
|
"description": "Writes g-code to a file.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Enables ability to generate printable geometry from 2D image files.",
|
"description": "Enables ability to generate printable geometry from 2D image files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for importing profiles from legacy Cura versions.",
|
"description": "Provides support for importing profiles from legacy Cura versions.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "fieldOfView",
|
"author": "fieldOfView",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Model Checker",
|
"name": "Model Checker",
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides a monitor stage in Cura.",
|
"description": "Provides a monitor stage in Cura.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides the Per Model Settings.",
|
"description": "Provides the Per Model Settings.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
|
# Copyright (c) 2018 Jaime van Kessel, Ultimaker B.V.
|
||||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
@ -260,6 +261,9 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
# Create the plugin dialog component
|
# Create the plugin dialog component
|
||||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
|
path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
|
||||||
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||||
|
if self._view is None:
|
||||||
|
Logger.log("e", "Not creating PostProcessing button near save button because the QML component failed to be created.")
|
||||||
|
return
|
||||||
Logger.log("d", "Post processing view created.")
|
Logger.log("d", "Post processing view created.")
|
||||||
|
|
||||||
# Create the save button component
|
# Create the save button component
|
||||||
|
@ -269,6 +273,9 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
def showPopup(self):
|
def showPopup(self):
|
||||||
if self._view is None:
|
if self._view is None:
|
||||||
self._createView()
|
self._createView()
|
||||||
|
if self._view is None:
|
||||||
|
Logger.log("e", "Not creating PostProcessing window since the QML component failed to be created.")
|
||||||
|
return
|
||||||
self._view.show()
|
self._view.show()
|
||||||
|
|
||||||
## Property changed: trigger re-slice
|
## Property changed: trigger re-slice
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Post Processing",
|
"name": "Post Processing",
|
||||||
"author": "Ultimaker",
|
"author": "Ultimaker",
|
||||||
"version": "2.2",
|
"version": "2.2",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"description": "Extension that allows for user created scripts for post processing",
|
"description": "Extension that allows for user created scripts for post processing",
|
||||||
"catalog": "cura"
|
"catalog": "cura"
|
||||||
}
|
}
|
|
@ -58,10 +58,10 @@ class FilamentChange(Script):
|
||||||
color_change = "M600"
|
color_change = "M600"
|
||||||
|
|
||||||
if initial_retract is not None and initial_retract > 0.:
|
if initial_retract is not None and initial_retract > 0.:
|
||||||
color_change = color_change + (" E%.2f" % initial_retract)
|
color_change = color_change + (" E-%.2f" % initial_retract)
|
||||||
|
|
||||||
if later_retract is not None and later_retract > 0.:
|
if later_retract is not None and later_retract > 0.:
|
||||||
color_change = color_change + (" L%.2f" % later_retract)
|
color_change = color_change + (" L-%.2f" % later_retract)
|
||||||
|
|
||||||
color_change = color_change + " ; Generated by FilamentChange plugin"
|
color_change = color_change + " ; Generated by FilamentChange plugin"
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class PauseAtHeight(Script):
|
||||||
"pause_height":
|
"pause_height":
|
||||||
{
|
{
|
||||||
"label": "Pause Height",
|
"label": "Pause Height",
|
||||||
"description": "At what height should the pause occur",
|
"description": "At what height should the pause occur?",
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 5.0,
|
"default_value": 5.0,
|
||||||
|
@ -39,7 +39,7 @@ class PauseAtHeight(Script):
|
||||||
"pause_layer":
|
"pause_layer":
|
||||||
{
|
{
|
||||||
"label": "Pause Layer",
|
"label": "Pause Layer",
|
||||||
"description": "At what layer should the pause occur",
|
"description": "At what layer should the pause occur?",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"value": "math.floor((pause_height - 0.27) / 0.1) + 1",
|
"value": "math.floor((pause_height - 0.27) / 0.1) + 1",
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
|
@ -142,13 +142,14 @@ class PauseAtHeight(Script):
|
||||||
standby_temperature = self.getSettingValueByKey("standby_temperature")
|
standby_temperature = self.getSettingValueByKey("standby_temperature")
|
||||||
firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value")
|
firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value")
|
||||||
control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value")
|
control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value")
|
||||||
|
initial_layer_height = Application.getInstance().getGlobalContainerStack().getProperty("layer_height_0", "value")
|
||||||
|
|
||||||
is_griffin = False
|
is_griffin = False
|
||||||
|
|
||||||
# T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
|
# T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
|
||||||
|
|
||||||
# use offset to calculate the current height: <current_height> = <current_z> - <layer_0_z>
|
# use offset to calculate the current height: <current_height> = <current_z> - <layer_0_z>
|
||||||
layer_0_z = 0.
|
layer_0_z = 0
|
||||||
current_z = 0
|
current_z = 0
|
||||||
got_first_g_cmd_on_layer_0 = False
|
got_first_g_cmd_on_layer_0 = False
|
||||||
current_t = 0 #Tracks the current extruder for tracking the target temperature.
|
current_t = 0 #Tracks the current extruder for tracking the target temperature.
|
||||||
|
@ -195,11 +196,10 @@ class PauseAtHeight(Script):
|
||||||
# This block is executed once, the first time there is a G
|
# This block is executed once, the first time there is a G
|
||||||
# command, to get the z offset (z for first positive layer)
|
# command, to get the z offset (z for first positive layer)
|
||||||
if not got_first_g_cmd_on_layer_0:
|
if not got_first_g_cmd_on_layer_0:
|
||||||
layer_0_z = current_z
|
layer_0_z = current_z - initial_layer_height
|
||||||
got_first_g_cmd_on_layer_0 = True
|
got_first_g_cmd_on_layer_0 = True
|
||||||
|
|
||||||
current_height = current_z - layer_0_z
|
current_height = current_z - layer_0_z
|
||||||
|
|
||||||
if current_height < pause_height:
|
if current_height < pause_height:
|
||||||
break # Try the next layer.
|
break # Try the next layer.
|
||||||
|
|
||||||
|
|
|
@ -35,25 +35,39 @@ class GCodeStep():
|
||||||
Class to store the current value of each G_Code parameter
|
Class to store the current value of each G_Code parameter
|
||||||
for any G-Code step
|
for any G-Code step
|
||||||
"""
|
"""
|
||||||
def __init__(self, step):
|
def __init__(self, step, in_relative_movement: bool = False):
|
||||||
self.step = step
|
self.step = step
|
||||||
self.step_x = 0
|
self.step_x = 0
|
||||||
self.step_y = 0
|
self.step_y = 0
|
||||||
self.step_z = 0
|
self.step_z = 0
|
||||||
self.step_e = 0
|
self.step_e = 0
|
||||||
self.step_f = 0
|
self.step_f = 0
|
||||||
|
|
||||||
|
self.in_relative_movement = in_relative_movement
|
||||||
|
|
||||||
self.comment = ""
|
self.comment = ""
|
||||||
|
|
||||||
def readStep(self, line):
|
def readStep(self, line):
|
||||||
"""
|
"""
|
||||||
Reads gcode from line into self
|
Reads gcode from line into self
|
||||||
"""
|
"""
|
||||||
self.step_x = _getValue(line, "X", self.step_x)
|
if not self.in_relative_movement:
|
||||||
self.step_y = _getValue(line, "Y", self.step_y)
|
self.step_x = _getValue(line, "X", self.step_x)
|
||||||
self.step_z = _getValue(line, "Z", self.step_z)
|
self.step_y = _getValue(line, "Y", self.step_y)
|
||||||
self.step_e = _getValue(line, "E", self.step_e)
|
self.step_z = _getValue(line, "Z", self.step_z)
|
||||||
self.step_f = _getValue(line, "F", self.step_f)
|
self.step_e = _getValue(line, "E", self.step_e)
|
||||||
return
|
self.step_f = _getValue(line, "F", self.step_f)
|
||||||
|
else:
|
||||||
|
delta_step_x = _getValue(line, "X", 0)
|
||||||
|
delta_step_y = _getValue(line, "Y", 0)
|
||||||
|
delta_step_z = _getValue(line, "Z", 0)
|
||||||
|
delta_step_e = _getValue(line, "E", 0)
|
||||||
|
|
||||||
|
self.step_x += delta_step_x
|
||||||
|
self.step_y += delta_step_y
|
||||||
|
self.step_z += delta_step_z
|
||||||
|
self.step_e += delta_step_e
|
||||||
|
self.step_f = _getValue(line, "F", self.step_f) # the feedrate is not relative
|
||||||
|
|
||||||
def copyPosFrom(self, step):
|
def copyPosFrom(self, step):
|
||||||
"""
|
"""
|
||||||
|
@ -65,7 +79,9 @@ class GCodeStep():
|
||||||
self.step_e = step.step_e
|
self.step_e = step.step_e
|
||||||
self.step_f = step.step_f
|
self.step_f = step.step_f
|
||||||
self.comment = step.comment
|
self.comment = step.comment
|
||||||
return
|
|
||||||
|
def setInRelativeMovement(self, value: bool) -> None:
|
||||||
|
self.in_relative_movement = value
|
||||||
|
|
||||||
|
|
||||||
# Execution part of the stretch plugin
|
# Execution part of the stretch plugin
|
||||||
|
@ -86,6 +102,7 @@ class Stretcher():
|
||||||
# of already deposited material for current layer
|
# of already deposited material for current layer
|
||||||
self.layer_z = 0 # Z position of the extrusion moves of the current layer
|
self.layer_z = 0 # Z position of the extrusion moves of the current layer
|
||||||
self.layergcode = ""
|
self.layergcode = ""
|
||||||
|
self._in_relative_movement = False
|
||||||
|
|
||||||
def execute(self, data):
|
def execute(self, data):
|
||||||
"""
|
"""
|
||||||
|
@ -96,7 +113,8 @@ class Stretcher():
|
||||||
+ " and push wall stretch " + str(self.pw_stretch) + "mm")
|
+ " and push wall stretch " + str(self.pw_stretch) + "mm")
|
||||||
retdata = []
|
retdata = []
|
||||||
layer_steps = []
|
layer_steps = []
|
||||||
current = GCodeStep(0)
|
in_relative_movement = False
|
||||||
|
current = GCodeStep(0, in_relative_movement)
|
||||||
self.layer_z = 0.
|
self.layer_z = 0.
|
||||||
current_e = 0.
|
current_e = 0.
|
||||||
for layer in data:
|
for layer in data:
|
||||||
|
@ -107,20 +125,31 @@ class Stretcher():
|
||||||
current.comment = line[line.find(";"):]
|
current.comment = line[line.find(";"):]
|
||||||
if _getValue(line, "G") == 0:
|
if _getValue(line, "G") == 0:
|
||||||
current.readStep(line)
|
current.readStep(line)
|
||||||
onestep = GCodeStep(0)
|
onestep = GCodeStep(0, in_relative_movement)
|
||||||
onestep.copyPosFrom(current)
|
onestep.copyPosFrom(current)
|
||||||
elif _getValue(line, "G") == 1:
|
elif _getValue(line, "G") == 1:
|
||||||
current.readStep(line)
|
current.readStep(line)
|
||||||
onestep = GCodeStep(1)
|
onestep = GCodeStep(1, in_relative_movement)
|
||||||
onestep.copyPosFrom(current)
|
onestep.copyPosFrom(current)
|
||||||
|
|
||||||
|
# end of relative movement
|
||||||
|
elif _getValue(line, "G") == 90:
|
||||||
|
in_relative_movement = False
|
||||||
|
current.setInRelativeMovement(in_relative_movement)
|
||||||
|
# start of relative movement
|
||||||
|
elif _getValue(line, "G") == 91:
|
||||||
|
in_relative_movement = True
|
||||||
|
current.setInRelativeMovement(in_relative_movement)
|
||||||
|
|
||||||
elif _getValue(line, "G") == 92:
|
elif _getValue(line, "G") == 92:
|
||||||
current.readStep(line)
|
current.readStep(line)
|
||||||
onestep = GCodeStep(-1)
|
onestep = GCodeStep(-1, in_relative_movement)
|
||||||
onestep.copyPosFrom(current)
|
onestep.copyPosFrom(current)
|
||||||
else:
|
else:
|
||||||
onestep = GCodeStep(-1)
|
onestep = GCodeStep(-1, in_relative_movement)
|
||||||
onestep.copyPosFrom(current)
|
onestep.copyPosFrom(current)
|
||||||
onestep.comment = line
|
onestep.comment = line
|
||||||
|
|
||||||
if line.find(";LAYER:") >= 0 and len(layer_steps):
|
if line.find(";LAYER:") >= 0 and len(layer_steps):
|
||||||
# Previous plugin "forgot" to separate two layers...
|
# Previous plugin "forgot" to separate two layers...
|
||||||
Logger.log("d", "Layer Z " + "{:.3f}".format(self.layer_z)
|
Logger.log("d", "Layer Z " + "{:.3f}".format(self.layer_z)
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides a prepare stage in Cura.",
|
"description": "Provides a prepare stage in Cura.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Provides removable drive hotplugging and writing support.",
|
"description": "Provides removable drive hotplugging and writing support.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,33 +40,37 @@ Item {
|
||||||
|
|
||||||
property bool layersVisible: true
|
property bool layersVisible: true
|
||||||
|
|
||||||
function getUpperValueFromSliderHandle () {
|
function getUpperValueFromSliderHandle() {
|
||||||
return upperHandle.getValue()
|
return upperHandle.getValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUpperValue (value) {
|
function setUpperValue(value) {
|
||||||
upperHandle.setValue(value)
|
upperHandle.setValue(value)
|
||||||
updateRangeHandle()
|
updateRangeHandle()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLowerValueFromSliderHandle () {
|
function getLowerValueFromSliderHandle() {
|
||||||
return lowerHandle.getValue()
|
return lowerHandle.getValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLowerValue (value) {
|
function setLowerValue(value) {
|
||||||
lowerHandle.setValue(value)
|
lowerHandle.setValue(value)
|
||||||
updateRangeHandle()
|
updateRangeHandle()
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRangeHandle () {
|
function updateRangeHandle() {
|
||||||
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
|
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the active handle to show only one label at a time
|
// set the active handle to show only one label at a time
|
||||||
function setActiveHandle (handle) {
|
function setActiveHandle(handle) {
|
||||||
activeHandle = handle
|
activeHandle = handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeValue(value) {
|
||||||
|
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
|
||||||
|
}
|
||||||
|
|
||||||
// slider track
|
// slider track
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: track
|
id: track
|
||||||
|
@ -188,6 +192,8 @@ Item {
|
||||||
|
|
||||||
// set the slider position based on the upper value
|
// set the slider position based on the upper value
|
||||||
function setValue (value) {
|
function setValue (value) {
|
||||||
|
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
||||||
|
value = sliderRoot.normalizeValue(value)
|
||||||
|
|
||||||
UM.SimulationView.setCurrentLayer(value)
|
UM.SimulationView.setCurrentLayer(value)
|
||||||
|
|
||||||
|
@ -274,6 +280,8 @@ Item {
|
||||||
|
|
||||||
// set the slider position based on the lower value
|
// set the slider position based on the lower value
|
||||||
function setValue (value) {
|
function setValue (value) {
|
||||||
|
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
||||||
|
value = sliderRoot.normalizeValue(value)
|
||||||
|
|
||||||
UM.SimulationView.setMinimumLayer(value)
|
UM.SimulationView.setMinimumLayer(value)
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ Item {
|
||||||
|
|
||||||
// value properties
|
// value properties
|
||||||
property real maximumValue: 100
|
property real maximumValue: 100
|
||||||
|
property real minimumValue: 0
|
||||||
property bool roundValues: true
|
property bool roundValues: true
|
||||||
property real handleValue: maximumValue
|
property real handleValue: maximumValue
|
||||||
|
|
||||||
|
@ -47,6 +48,10 @@ Item {
|
||||||
rangeHandle.width = handle.x - sliderRoot.handleSize
|
rangeHandle.width = handle.x - sliderRoot.handleSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeValue(value) {
|
||||||
|
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
|
||||||
|
}
|
||||||
|
|
||||||
// slider track
|
// slider track
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: track
|
id: track
|
||||||
|
@ -110,6 +115,8 @@ Item {
|
||||||
|
|
||||||
// set the slider position based on the value
|
// set the slider position based on the value
|
||||||
function setValue (value) {
|
function setValue (value) {
|
||||||
|
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
||||||
|
value = sliderRoot.normalizeValue(value)
|
||||||
|
|
||||||
UM.SimulationView.setCurrentPath(value)
|
UM.SimulationView.setCurrentPath(value)
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,6 @@ UM.PointingRectangle {
|
||||||
width: valueLabel.width + UM.Theme.getSize("default_margin").width
|
width: valueLabel.width + UM.Theme.getSize("default_margin").width
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
// make sure the text field is focussed when pressing the parent handle
|
|
||||||
// needed to connect the key bindings when switching active handle
|
|
||||||
onVisibleChanged: if (visible) valueLabel.forceActiveFocus()
|
|
||||||
|
|
||||||
color: UM.Theme.getColor("tool_panel_background")
|
color: UM.Theme.getColor("tool_panel_background")
|
||||||
borderColor: UM.Theme.getColor("lining")
|
borderColor: UM.Theme.getColor("lining")
|
||||||
borderWidth: UM.Theme.getSize("default_lining").width
|
borderWidth: UM.Theme.getSize("default_lining").width
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2017 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Cura is released under the terms of the LGPLv3 or higher.
|
// Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.4
|
import QtQuick 2.4
|
||||||
|
@ -12,30 +12,43 @@ import Cura 1.0 as Cura
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
id: base
|
id: base
|
||||||
width: {
|
width:
|
||||||
if (UM.SimulationView.compatibilityMode) {
|
{
|
||||||
|
if (UM.SimulationView.compatibilityMode)
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
|
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size").width;
|
return UM.Theme.getSize("layerview_menu_size").width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
height: {
|
height: {
|
||||||
if (viewSettings.collapsed) {
|
if (viewSettings.collapsed)
|
||||||
if (UM.SimulationView.compatibilityMode) {
|
{
|
||||||
|
if (UM.SimulationView.compatibilityMode)
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
|
return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
|
||||||
}
|
}
|
||||||
return UM.Theme.getSize("layerview_menu_size_collapsed").height;
|
return UM.Theme.getSize("layerview_menu_size_collapsed").height;
|
||||||
} else if (UM.SimulationView.compatibilityMode) {
|
}
|
||||||
|
else if (UM.SimulationView.compatibilityMode)
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
|
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
|
||||||
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
|
}
|
||||||
|
else if (UM.Preferences.getValue("layerview/layer_view_type") == 0)
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on height { NumberAnimation { duration: 100 } }
|
Behavior on height { NumberAnimation { duration: 100 } }
|
||||||
|
|
||||||
property var buttonTarget: {
|
property var buttonTarget:
|
||||||
|
{
|
||||||
if(parent != null)
|
if(parent != null)
|
||||||
{
|
{
|
||||||
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
|
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
|
||||||
|
@ -44,7 +57,8 @@ Item
|
||||||
return Qt.point(0,0)
|
return Qt.point(0,0)
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
id: layerViewMenu
|
id: layerViewMenu
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
@ -83,7 +97,8 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout
|
||||||
|
{
|
||||||
id: viewSettings
|
id: viewSettings
|
||||||
|
|
||||||
property bool collapsed: false
|
property bool collapsed: false
|
||||||
|
@ -195,7 +210,8 @@ Item
|
||||||
width: width
|
width: width
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections
|
||||||
|
{
|
||||||
target: UM.Preferences
|
target: UM.Preferences
|
||||||
onPreferenceChanged:
|
onPreferenceChanged:
|
||||||
{
|
{
|
||||||
|
@ -212,18 +228,22 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater
|
||||||
|
{
|
||||||
model: Cura.ExtrudersModel{}
|
model: Cura.ExtrudersModel{}
|
||||||
CheckBox {
|
CheckBox
|
||||||
|
{
|
||||||
id: extrudersModelCheckBox
|
id: extrudersModelCheckBox
|
||||||
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
|
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
|
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
|
||||||
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
|
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
|
||||||
}
|
}
|
||||||
visible: !UM.SimulationView.compatibilityMode
|
visible: !UM.SimulationView.compatibilityMode
|
||||||
enabled: index + 1 <= 4
|
enabled: index + 1 <= 4
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: extrudersModelCheckBox.right
|
anchors.right: extrudersModelCheckBox.right
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
|
@ -253,8 +273,10 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater
|
||||||
model: ListModel {
|
{
|
||||||
|
model: ListModel
|
||||||
|
{
|
||||||
id: typesLegendModel
|
id: typesLegendModel
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
{
|
{
|
||||||
|
@ -285,13 +307,16 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox
|
||||||
|
{
|
||||||
id: legendModelCheckBox
|
id: legendModelCheckBox
|
||||||
checked: model.initialValue
|
checked: model.initialValue
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
UM.Preferences.setValue(model.preference, checked);
|
UM.Preferences.setValue(model.preference, checked);
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: legendModelCheckBox.right
|
anchors.right: legendModelCheckBox.right
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
|
@ -320,18 +345,22 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox
|
||||||
|
{
|
||||||
checked: viewSettings.only_show_top_layers
|
checked: viewSettings.only_show_top_layers
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
|
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Only Show Top Layers")
|
text: catalog.i18nc("@label", "Only Show Top Layers")
|
||||||
visible: UM.SimulationView.compatibilityMode
|
visible: UM.SimulationView.compatibilityMode
|
||||||
style: UM.Theme.styles.checkbox
|
style: UM.Theme.styles.checkbox
|
||||||
}
|
}
|
||||||
CheckBox {
|
CheckBox
|
||||||
|
{
|
||||||
checked: viewSettings.top_layer_count == 5
|
checked: viewSettings.top_layer_count == 5
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
|
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
|
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
|
||||||
|
@ -339,8 +368,10 @@ Item
|
||||||
style: UM.Theme.styles.checkbox
|
style: UM.Theme.styles.checkbox
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater
|
||||||
model: ListModel {
|
{
|
||||||
|
model: ListModel
|
||||||
|
{
|
||||||
id: typesLegendModelNoCheck
|
id: typesLegendModelNoCheck
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
{
|
{
|
||||||
|
@ -355,11 +386,13 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label
|
||||||
|
{
|
||||||
text: label
|
text: label
|
||||||
visible: viewSettings.show_legend
|
visible: viewSettings.show_legend
|
||||||
id: typesLegendModelLabel
|
id: typesLegendModelLabel
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: typesLegendModelLabel.right
|
anchors.right: typesLegendModelLabel.right
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
|
@ -378,30 +411,37 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text for the minimum, maximum and units for the feedrates and layer thickness
|
// Text for the minimum, maximum and units for the feedrates and layer thickness
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
id: gradientLegend
|
id: gradientLegend
|
||||||
visible: viewSettings.show_gradient
|
visible: viewSettings.show_gradient
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: UM.Theme.getSize("layerview_row").height
|
height: UM.Theme.getSize("layerview_row").height
|
||||||
anchors {
|
anchors
|
||||||
|
{
|
||||||
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
||||||
horizontalCenter: parent.horizontalCenter
|
horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label
|
||||||
|
{
|
||||||
text: minText()
|
text: minText()
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
color: UM.Theme.getColor("setting_control_text")
|
color: UM.Theme.getColor("setting_control_text")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
|
||||||
function minText() {
|
function minText()
|
||||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
{
|
||||||
|
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
|
||||||
|
{
|
||||||
// Feedrate selected
|
// Feedrate selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||||
|
{
|
||||||
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
|
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
|
||||||
}
|
}
|
||||||
// Layer thickness selected
|
// Layer thickness selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||||
|
{
|
||||||
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
|
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,20 +449,25 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label
|
||||||
|
{
|
||||||
text: unitsText()
|
text: unitsText()
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
color: UM.Theme.getColor("setting_control_text")
|
color: UM.Theme.getColor("setting_control_text")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
|
||||||
function unitsText() {
|
function unitsText()
|
||||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
{
|
||||||
|
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
|
||||||
|
{
|
||||||
// Feedrate selected
|
// Feedrate selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||||
|
{
|
||||||
return "mm/s"
|
return "mm/s"
|
||||||
}
|
}
|
||||||
// Layer thickness selected
|
// Layer thickness selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||||
|
{
|
||||||
return "mm"
|
return "mm"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -430,20 +475,25 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label
|
||||||
|
{
|
||||||
text: maxText()
|
text: maxText()
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
color: UM.Theme.getColor("setting_control_text")
|
color: UM.Theme.getColor("setting_control_text")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
|
||||||
function maxText() {
|
function maxText()
|
||||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
{
|
||||||
|
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
|
||||||
|
{
|
||||||
// Feedrate selected
|
// Feedrate selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||||
|
{
|
||||||
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
|
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
|
||||||
}
|
}
|
||||||
// Layer thickness selected
|
// Layer thickness selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||||
|
{
|
||||||
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
|
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,7 +503,8 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gradient colors for feedrate
|
// Gradient colors for feedrate
|
||||||
Rectangle { // In QML 5.9 can be changed by LinearGradient
|
Rectangle
|
||||||
|
{ // In QML 5.9 can be changed by LinearGradient
|
||||||
// Invert values because then the bar is rotated 90 degrees
|
// Invert values because then the bar is rotated 90 degrees
|
||||||
id: feedrateGradient
|
id: feedrateGradient
|
||||||
visible: viewSettings.show_feedrate_gradient
|
visible: viewSettings.show_feedrate_gradient
|
||||||
|
@ -463,20 +514,25 @@ Item
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
border.color: UM.Theme.getColor("lining")
|
border.color: UM.Theme.getColor("lining")
|
||||||
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
||||||
gradient: Gradient {
|
gradient: Gradient
|
||||||
GradientStop {
|
{
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.000
|
position: 0.000
|
||||||
color: Qt.rgba(1, 0.5, 0, 1)
|
color: Qt.rgba(1, 0.5, 0, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.625
|
position: 0.625
|
||||||
color: Qt.rgba(0.375, 0.5, 0, 1)
|
color: Qt.rgba(0.375, 0.5, 0, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.75
|
position: 0.75
|
||||||
color: Qt.rgba(0.25, 1, 0, 1)
|
color: Qt.rgba(0.25, 1, 0, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 1.0
|
position: 1.0
|
||||||
color: Qt.rgba(0, 0, 1, 1)
|
color: Qt.rgba(0, 0, 1, 1)
|
||||||
}
|
}
|
||||||
|
@ -484,7 +540,8 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gradient colors for layer thickness (similar to parula colormap)
|
// Gradient colors for layer thickness (similar to parula colormap)
|
||||||
Rectangle { // In QML 5.9 can be changed by LinearGradient
|
Rectangle // In QML 5.9 can be changed by LinearGradient
|
||||||
|
{
|
||||||
// Invert values because then the bar is rotated 90 degrees
|
// Invert values because then the bar is rotated 90 degrees
|
||||||
id: thicknessGradient
|
id: thicknessGradient
|
||||||
visible: viewSettings.show_thickness_gradient
|
visible: viewSettings.show_thickness_gradient
|
||||||
|
@ -494,24 +551,30 @@ Item
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
border.color: UM.Theme.getColor("lining")
|
border.color: UM.Theme.getColor("lining")
|
||||||
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
||||||
gradient: Gradient {
|
gradient: Gradient
|
||||||
GradientStop {
|
{
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.000
|
position: 0.000
|
||||||
color: Qt.rgba(1, 1, 0, 1)
|
color: Qt.rgba(1, 1, 0, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.25
|
position: 0.25
|
||||||
color: Qt.rgba(1, 0.75, 0.25, 1)
|
color: Qt.rgba(1, 0.75, 0.25, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.5
|
position: 0.5
|
||||||
color: Qt.rgba(0, 0.75, 0.5, 1)
|
color: Qt.rgba(0, 0.75, 0.5, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.75
|
position: 0.75
|
||||||
color: Qt.rgba(0, 0.375, 0.75, 1)
|
color: Qt.rgba(0, 0.375, 0.75, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 1.0
|
position: 1.0
|
||||||
color: Qt.rgba(0, 0, 0.5, 1)
|
color: Qt.rgba(0, 0, 0.5, 1)
|
||||||
}
|
}
|
||||||
|
@ -520,19 +583,22 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
id: slidersBox
|
id: slidersBox
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
|
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
|
||||||
|
|
||||||
anchors {
|
anchors
|
||||||
|
{
|
||||||
top: parent.bottom
|
top: parent.bottom
|
||||||
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
||||||
left: parent.left
|
left: parent.left
|
||||||
}
|
}
|
||||||
|
|
||||||
PathSlider {
|
PathSlider
|
||||||
|
{
|
||||||
id: pathSlider
|
id: pathSlider
|
||||||
|
|
||||||
height: UM.Theme.getSize("slider_handle").width
|
height: UM.Theme.getSize("slider_handle").width
|
||||||
|
@ -553,25 +619,29 @@ Item
|
||||||
rangeColor: UM.Theme.getColor("slider_groove_fill")
|
rangeColor: UM.Theme.getColor("slider_groove_fill")
|
||||||
|
|
||||||
// update values when layer data changes
|
// update values when layer data changes
|
||||||
Connections {
|
Connections
|
||||||
|
{
|
||||||
target: UM.SimulationView
|
target: UM.SimulationView
|
||||||
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||||
onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the slider handlers show the correct value after switching views
|
// make sure the slider handlers show the correct value after switching views
|
||||||
Component.onCompleted: {
|
Component.onCompleted:
|
||||||
|
{
|
||||||
pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LayerSlider {
|
LayerSlider
|
||||||
|
{
|
||||||
id: layerSlider
|
id: layerSlider
|
||||||
|
|
||||||
width: UM.Theme.getSize("slider_handle").width
|
width: UM.Theme.getSize("slider_handle").width
|
||||||
height: UM.Theme.getSize("layerview_menu_size").height
|
height: UM.Theme.getSize("layerview_menu_size").height
|
||||||
|
|
||||||
anchors {
|
anchors
|
||||||
|
{
|
||||||
top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
|
top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
|
||||||
topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
|
topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
|
||||||
right: parent.right
|
right: parent.right
|
||||||
|
@ -593,7 +663,8 @@ Item
|
||||||
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
|
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
|
||||||
|
|
||||||
// update values when layer data changes
|
// update values when layer data changes
|
||||||
Connections {
|
Connections
|
||||||
|
{
|
||||||
target: UM.SimulationView
|
target: UM.SimulationView
|
||||||
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||||
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
||||||
|
@ -601,45 +672,54 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the slider handlers show the correct value after switching views
|
// make sure the slider handlers show the correct value after switching views
|
||||||
Component.onCompleted: {
|
Component.onCompleted:
|
||||||
|
{
|
||||||
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
||||||
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play simulation button
|
// Play simulation button
|
||||||
Button {
|
Button
|
||||||
|
{
|
||||||
id: playButton
|
id: playButton
|
||||||
iconSource: "./resources/simulation_resume.svg"
|
iconSource: "./resources/simulation_resume.svg"
|
||||||
style: UM.Theme.styles.small_tool_button
|
style: UM.Theme.styles.small_tool_button
|
||||||
visible: !UM.SimulationView.compatibilityMode
|
visible: !UM.SimulationView.compatibilityMode
|
||||||
anchors {
|
anchors
|
||||||
|
{
|
||||||
verticalCenter: pathSlider.verticalCenter
|
verticalCenter: pathSlider.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
property var status: 0 // indicates if it's stopped (0) or playing (1)
|
property var status: 0 // indicates if it's stopped (0) or playing (1)
|
||||||
|
|
||||||
onClicked: {
|
onClicked:
|
||||||
switch(status) {
|
{
|
||||||
case 0: {
|
switch(status)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
resumeSimulation()
|
resumeSimulation()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1:
|
||||||
|
{
|
||||||
pauseSimulation()
|
pauseSimulation()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pauseSimulation() {
|
function pauseSimulation()
|
||||||
|
{
|
||||||
UM.SimulationView.setSimulationRunning(false)
|
UM.SimulationView.setSimulationRunning(false)
|
||||||
iconSource = "./resources/simulation_resume.svg"
|
iconSource = "./resources/simulation_resume.svg"
|
||||||
simulationTimer.stop()
|
simulationTimer.stop()
|
||||||
status = 0
|
status = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function resumeSimulation() {
|
function resumeSimulation()
|
||||||
|
{
|
||||||
UM.SimulationView.setSimulationRunning(true)
|
UM.SimulationView.setSimulationRunning(true)
|
||||||
iconSource = "./resources/simulation_pause.svg"
|
iconSource = "./resources/simulation_pause.svg"
|
||||||
simulationTimer.start()
|
simulationTimer.start()
|
||||||
|
@ -652,7 +732,8 @@ Item
|
||||||
interval: 100
|
interval: 100
|
||||||
running: false
|
running: false
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered:
|
||||||
|
{
|
||||||
var currentPath = UM.SimulationView.currentPath
|
var currentPath = UM.SimulationView.currentPath
|
||||||
var numPaths = UM.SimulationView.numPaths
|
var numPaths = UM.SimulationView.numPaths
|
||||||
var currentLayer = UM.SimulationView.currentLayer
|
var currentLayer = UM.SimulationView.currentLayer
|
||||||
|
@ -697,7 +778,8 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FontMetrics {
|
FontMetrics
|
||||||
|
{
|
||||||
id: fontMetrics
|
id: fontMetrics
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides the Simulation view.",
|
"description": "Provides the Simulation view.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides a normal solid mesh view.",
|
"description": "Provides a normal solid mesh view.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
|
@ -25,10 +25,12 @@ from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||||
|
|
||||||
from UM.Settings.SettingInstance import SettingInstance
|
from UM.Settings.SettingInstance import SettingInstance
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
class SupportEraser(Tool):
|
class SupportEraser(Tool):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._shortcut_key = Qt.Key_G
|
self._shortcut_key = Qt.Key_E
|
||||||
self._controller = self.getController()
|
self._controller = self.getController()
|
||||||
|
|
||||||
self._selection_pass = None
|
self._selection_pass = None
|
||||||
|
@ -96,8 +98,7 @@ class SupportEraser(Tool):
|
||||||
|
|
||||||
node.setName("Eraser")
|
node.setName("Eraser")
|
||||||
node.setSelectable(True)
|
node.setSelectable(True)
|
||||||
mesh = MeshBuilder()
|
mesh = self._createCube(10)
|
||||||
mesh.addCube(10,10,10)
|
|
||||||
node.setMeshData(mesh.build())
|
node.setMeshData(mesh.build())
|
||||||
|
|
||||||
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
|
@ -160,3 +161,28 @@ class SupportEraser(Tool):
|
||||||
self._skip_press = False
|
self._skip_press = False
|
||||||
|
|
||||||
self._had_selection = has_selection
|
self._had_selection = has_selection
|
||||||
|
|
||||||
|
def _createCube(self, size):
|
||||||
|
mesh = MeshBuilder()
|
||||||
|
|
||||||
|
# Can't use MeshBuilder.addCube() because that does not get per-vertex normals
|
||||||
|
# Per-vertex normals require duplication of vertices
|
||||||
|
s = size / 2
|
||||||
|
verts = [ # 6 faces with 4 corners each
|
||||||
|
[-s, -s, s], [-s, s, s], [ s, s, s], [ s, -s, s],
|
||||||
|
[-s, s, -s], [-s, -s, -s], [ s, -s, -s], [ s, s, -s],
|
||||||
|
[ s, -s, -s], [-s, -s, -s], [-s, -s, s], [ s, -s, s],
|
||||||
|
[-s, s, -s], [ s, s, -s], [ s, s, s], [-s, s, s],
|
||||||
|
[-s, -s, s], [-s, -s, -s], [-s, s, -s], [-s, s, s],
|
||||||
|
[ s, -s, -s], [ s, -s, s], [ s, s, s], [ s, s, -s]
|
||||||
|
]
|
||||||
|
mesh.setVertices(numpy.asarray(verts, dtype=numpy.float32))
|
||||||
|
|
||||||
|
indices = []
|
||||||
|
for i in range(0, 24, 4): # All 6 quads (12 triangles)
|
||||||
|
indices.append([i, i+2, i+1])
|
||||||
|
indices.append([i, i+3, i+2])
|
||||||
|
mesh.setIndices(numpy.asarray(indices, dtype=numpy.int32))
|
||||||
|
|
||||||
|
mesh.calculateNormals()
|
||||||
|
return mesh
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Creates an eraser mesh to block the printing of support in certain places",
|
"description": "Creates an eraser mesh to block the printing of support in certain places",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
"name": "Toolbox",
|
"name": "Toolbox",
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"description": "Find, manage and install new Cura packages."
|
"description": "Find, manage and install new Cura packages."
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,16 @@ Item
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||||
width: parent.width
|
width: parent.width
|
||||||
frameVisible: false
|
frameVisible: false
|
||||||
|
|
||||||
|
// Workaround for scroll issues (QTBUG-49652)
|
||||||
|
flickableItem.interactive: false
|
||||||
|
Component.onCompleted:
|
||||||
|
{
|
||||||
|
for (var i = 0; i < flickableItem.children.length; ++i)
|
||||||
|
{
|
||||||
|
flickableItem.children[i].enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
selectionMode: 0
|
selectionMode: 0
|
||||||
model: packageData.supported_configs
|
model: packageData.supported_configs
|
||||||
headerDelegate: Rectangle
|
headerDelegate: Rectangle
|
||||||
|
|
|
@ -114,7 +114,10 @@ Item
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
toolbox.viewPage = "author"
|
toolbox.viewPage = "author"
|
||||||
toolbox.filterModelByProp("packages", "author_id", model.id)
|
toolbox.setFilters("packages", {
|
||||||
|
"author_id": model.id,
|
||||||
|
"type": "material"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -105,8 +105,21 @@ Rectangle
|
||||||
switch(toolbox.viewCategory)
|
switch(toolbox.viewCategory)
|
||||||
{
|
{
|
||||||
case "material":
|
case "material":
|
||||||
toolbox.viewPage = "author"
|
|
||||||
toolbox.filterModelByProp("packages", "author_name", model.name)
|
// If model has a type, it must be a package
|
||||||
|
if (model.type !== undefined)
|
||||||
|
{
|
||||||
|
toolbox.viewPage = "detail"
|
||||||
|
toolbox.filterModelByProp("packages", "id", model.id)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toolbox.viewPage = "author"
|
||||||
|
toolbox.setFilters("packages", {
|
||||||
|
"author_id": model.id,
|
||||||
|
"type": "material"
|
||||||
|
})
|
||||||
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
toolbox.viewPage = "detail"
|
toolbox.viewPage = "detail"
|
||||||
|
|
|
@ -28,7 +28,7 @@ class PackagesModel(ListModel):
|
||||||
self.addRoleName(Qt.UserRole + 11, "download_url")
|
self.addRoleName(Qt.UserRole + 11, "download_url")
|
||||||
self.addRoleName(Qt.UserRole + 12, "last_updated")
|
self.addRoleName(Qt.UserRole + 12, "last_updated")
|
||||||
self.addRoleName(Qt.UserRole + 13, "is_bundled")
|
self.addRoleName(Qt.UserRole + 13, "is_bundled")
|
||||||
self.addRoleName(Qt.UserRole + 14, "is_enabled")
|
self.addRoleName(Qt.UserRole + 14, "is_active")
|
||||||
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
|
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
|
||||||
self.addRoleName(Qt.UserRole + 16, "has_configs")
|
self.addRoleName(Qt.UserRole + 16, "has_configs")
|
||||||
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
||||||
|
@ -75,7 +75,7 @@ class PackagesModel(ListModel):
|
||||||
"download_url": package["download_url"] if "download_url" in package else None,
|
"download_url": package["download_url"] if "download_url" in package else None,
|
||||||
"last_updated": package["last_updated"] if "last_updated" in package else None,
|
"last_updated": package["last_updated"] if "last_updated" in package else None,
|
||||||
"is_bundled": package["is_bundled"] if "is_bundled" in package else False,
|
"is_bundled": package["is_bundled"] if "is_bundled" in package else False,
|
||||||
"is_enabled": package["is_enabled"] if "is_enabled" in package else False,
|
"is_active": package["is_active"] if "is_active" in package else False,
|
||||||
"is_installed": package["is_installed"] if "is_installed" in package else False,
|
"is_installed": package["is_installed"] if "is_installed" in package else False,
|
||||||
"has_configs": has_configs,
|
"has_configs": has_configs,
|
||||||
"supported_configs": configs_model,
|
"supported_configs": configs_model,
|
||||||
|
|
|
@ -776,17 +776,25 @@ class Toolbox(QObject, Extension):
|
||||||
# Filter Models:
|
# Filter Models:
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
@pyqtSlot(str, str, str)
|
@pyqtSlot(str, str, str)
|
||||||
def filterModelByProp(self, modelType: str, filterType: str, parameter: str):
|
def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
|
||||||
if not self._models[modelType]:
|
if not self._models[model_type]:
|
||||||
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", modelType)
|
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", model_type)
|
||||||
return
|
return
|
||||||
self._models[modelType].setFilter({ filterType: parameter })
|
self._models[model_type].setFilter({filter_type: parameter})
|
||||||
self.filterChanged.emit()
|
self.filterChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot(str, "QVariantMap")
|
||||||
def removeFilters(self, modelType: str):
|
def setFilters(self, model_type: str, filter_dict: dict) -> None:
|
||||||
if not self._models[modelType]:
|
if not self._models[model_type]:
|
||||||
Logger.log("w", "Toolbox: Couldn't remove filters on %s model because it doesn't exist.", modelType)
|
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", model_type)
|
||||||
return
|
return
|
||||||
self._models[modelType].setFilter({})
|
self._models[model_type].setFilter(filter_dict)
|
||||||
|
self.filterChanged.emit()
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def removeFilters(self, model_type: str) -> None:
|
||||||
|
if not self._models[model_type]:
|
||||||
|
Logger.log("w", "Toolbox: Couldn't remove filters on %s model because it doesn't exist.", model_type)
|
||||||
|
return
|
||||||
|
self._models[model_type].setFilter({})
|
||||||
self.filterChanged.emit()
|
self.filterChanged.emit()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#Copyright (c) 2018 Ultimaker B.V.
|
#Copyright (c) 2018 Ultimaker B.V.
|
||||||
#Cura is released under the terms of the LGPLv3 or higher.
|
#Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from Charon.VirtualFile import VirtualFile #To open UFP files.
|
from Charon.VirtualFile import VirtualFile #To open UFP files.
|
||||||
from Charon.OpenMode import OpenMode #To indicate that we want to write to UFP files.
|
from Charon.OpenMode import OpenMode #To indicate that we want to write to UFP files.
|
||||||
|
@ -12,11 +13,15 @@ from UM.PluginRegistry import PluginRegistry #To get the g-code writer.
|
||||||
from PyQt5.QtCore import QBuffer
|
from PyQt5.QtCore import QBuffer
|
||||||
|
|
||||||
from cura.Snapshot import Snapshot
|
from cura.Snapshot import Snapshot
|
||||||
|
from cura.Utils.Threading import call_on_qt_thread
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
class UFPWriter(MeshWriter):
|
class UFPWriter(MeshWriter):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__(add_to_recent_files = False)
|
||||||
self._snapshot = None
|
self._snapshot = None
|
||||||
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._createSnapshot)
|
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._createSnapshot)
|
||||||
|
|
||||||
|
@ -25,6 +30,11 @@ class UFPWriter(MeshWriter):
|
||||||
Logger.log("d", "Creating thumbnail image...")
|
Logger.log("d", "Creating thumbnail image...")
|
||||||
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
|
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
|
||||||
|
|
||||||
|
# This needs to be called on the main thread (Qt thread) because the serialization of material containers can
|
||||||
|
# trigger loading other containers. Because those loaded containers are QtObjects, they must be created on the
|
||||||
|
# Qt thread. The File read/write operations right now are executed on separated threads because they are scheduled
|
||||||
|
# by the Job class.
|
||||||
|
@call_on_qt_thread
|
||||||
def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
|
def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
|
||||||
archive = VirtualFile()
|
archive = VirtualFile()
|
||||||
archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
|
archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
|
||||||
|
@ -32,7 +42,11 @@ class UFPWriter(MeshWriter):
|
||||||
#Store the g-code from the scene.
|
#Store the g-code from the scene.
|
||||||
archive.addContentType(extension = "gcode", mime_type = "text/x-gcode")
|
archive.addContentType(extension = "gcode", mime_type = "text/x-gcode")
|
||||||
gcode_textio = StringIO() #We have to convert the g-code into bytes.
|
gcode_textio = StringIO() #We have to convert the g-code into bytes.
|
||||||
PluginRegistry.getInstance().getPluginObject("GCodeWriter").write(gcode_textio, None)
|
gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
|
||||||
|
success = gcode_writer.write(gcode_textio, None)
|
||||||
|
if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code.
|
||||||
|
self.setInformation(gcode_writer.getInformation())
|
||||||
|
return False
|
||||||
gcode = archive.getStream("/3D/model.gcode")
|
gcode = archive.getStream("/3D/model.gcode")
|
||||||
gcode.write(gcode_textio.getvalue().encode("UTF-8"))
|
gcode.write(gcode_textio.getvalue().encode("UTF-8"))
|
||||||
archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
|
archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
|
||||||
|
@ -52,5 +66,50 @@ class UFPWriter(MeshWriter):
|
||||||
else:
|
else:
|
||||||
Logger.log("d", "Thumbnail not created, cannot save it")
|
Logger.log("d", "Thumbnail not created, cannot save it")
|
||||||
|
|
||||||
|
# Store the material.
|
||||||
|
application = Application.getInstance()
|
||||||
|
machine_manager = application.getMachineManager()
|
||||||
|
material_manager = application.getMaterialManager()
|
||||||
|
global_stack = machine_manager.activeMachine
|
||||||
|
|
||||||
|
material_extension = "xml.fdm_material"
|
||||||
|
material_mime_type = "application/x-ultimaker-material-profile"
|
||||||
|
|
||||||
|
try:
|
||||||
|
archive.addContentType(extension = material_extension, mime_type = material_mime_type)
|
||||||
|
except:
|
||||||
|
Logger.log("w", "The material extension: %s was already added", material_extension)
|
||||||
|
|
||||||
|
added_materials = []
|
||||||
|
for extruder_stack in global_stack.extruders.values():
|
||||||
|
material = extruder_stack.material
|
||||||
|
material_file_name = material.getMetaData()["base_file"] + ".xml.fdm_material"
|
||||||
|
material_file_name = "/Materials/" + material_file_name
|
||||||
|
|
||||||
|
#Same material cannot be added
|
||||||
|
if material_file_name in added_materials:
|
||||||
|
continue
|
||||||
|
|
||||||
|
material_root_id = material.getMetaDataEntry("base_file")
|
||||||
|
material_group = material_manager.getMaterialGroup(material_root_id)
|
||||||
|
if material_group is None:
|
||||||
|
Logger.log("e", "Cannot find material container with root id [%s]", material_root_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
material_container = material_group.root_material_node.getContainer()
|
||||||
|
try:
|
||||||
|
serialized_material = material_container.serialize()
|
||||||
|
except NotImplementedError:
|
||||||
|
Logger.log("e", "Unable serialize material container with root id: %s", material_root_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
material_file = archive.getStream(material_file_name)
|
||||||
|
material_file.write(serialized_material.encode("UTF-8"))
|
||||||
|
archive.addRelation(virtual_path = material_file_name,
|
||||||
|
relation_type = "http://schemas.ultimaker.org/package/2018/relationships/material",
|
||||||
|
origin = "/3D/model.gcode")
|
||||||
|
|
||||||
|
added_materials.append(material_file_name)
|
||||||
|
|
||||||
archive.close()
|
archive.close()
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for writing Ultimaker Format Packages.",
|
"description": "Provides support for writing Ultimaker Format Packages.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
|
@ -30,7 +30,12 @@ Component
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.right:parent.right
|
||||||
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
text: Cura.MachineManager.printerOutputDevices[0].name
|
text: Cura.MachineManager.printerOutputDevices[0].name
|
||||||
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle
|
Rectangle
|
||||||
|
|
|
@ -9,6 +9,7 @@ Component
|
||||||
{
|
{
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
|
id: monitorFrame
|
||||||
width: maximumWidth
|
width: maximumWidth
|
||||||
height: maximumHeight
|
height: maximumHeight
|
||||||
color: UM.Theme.getColor("viewport_background")
|
color: UM.Theme.getColor("viewport_background")
|
||||||
|
@ -103,5 +104,15 @@ Component
|
||||||
visible: OutputDevice.activePrinter != null
|
visible: OutputDevice.activePrinter != null
|
||||||
anchors.fill:parent
|
anchors.fill:parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onVisibleChanged:
|
||||||
|
{
|
||||||
|
if (!monitorFrame.visible)
|
||||||
|
{
|
||||||
|
// After switching the Tab ensure that active printer is Null, the video stream image
|
||||||
|
// might be active
|
||||||
|
OutputDevice.setActivePrinter(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
|
machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
|
||||||
machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
|
machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
|
||||||
#Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
|
#Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
|
||||||
if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"):
|
if "application/x-ufp" not in machine_file_formats and Version(self.firmwareVersion) >= Version("4.4"):
|
||||||
machine_file_formats = ["application/x-ufp"] + machine_file_formats
|
machine_file_formats = ["application/x-ufp"] + machine_file_formats
|
||||||
|
|
||||||
# Take the intersection between file_formats and machine_file_formats.
|
# Take the intersection between file_formats and machine_file_formats.
|
||||||
|
@ -590,4 +590,4 @@ def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: s
|
||||||
for item in list:
|
for item in list:
|
||||||
if item.key == key:
|
if item.key == key:
|
||||||
return item
|
return item
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -19,5 +19,5 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
|
||||||
|
|
||||||
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
||||||
data = "{\"action\": \"%s\"}" % state
|
data = "{\"action\": \"%s\"}" % state
|
||||||
self._output_device.put("print_jobs/%s/action" % job.key, data, onFinished=None)
|
self._output_device.put("print_jobs/%s/action" % job.key, data, on_finished=None)
|
||||||
|
|
||||||
|
|
|
@ -299,11 +299,11 @@ Cura.MachineAction
|
||||||
}
|
}
|
||||||
else if (base.selectedDevice.clusterSize === 0)
|
else if (base.selectedDevice.clusterSize === 0)
|
||||||
{
|
{
|
||||||
return catalog.i18nc("@label", "This printer is not set up to host a group of Ultimaker 3 printers.");
|
return catalog.i18nc("@label", "This printer is not set up to host a group of printers.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return catalog.i18nc("@label", "This printer is the host for a group of %1 Ultimaker 3 printers.".arg(base.selectedDevice.clusterSize));
|
return catalog.i18nc("@label", "This printer is the host for a group of %1 printers.".arg(base.selectedDevice.clusterSize));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,11 +349,6 @@ Cura.MachineAction
|
||||||
addressField.focus = true;
|
addressField.focus = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccepted:
|
|
||||||
{
|
|
||||||
manager.setManualDevice(printerKey, addressText)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: UM.Theme.getSize("default_margin").height
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
|
@ -369,7 +364,6 @@ Cura.MachineAction
|
||||||
{
|
{
|
||||||
id: addressField
|
id: addressField
|
||||||
width: parent.width
|
width: parent.width
|
||||||
maximumLength: 40
|
|
||||||
validator: RegExpValidator
|
validator: RegExpValidator
|
||||||
{
|
{
|
||||||
regExp: /[a-zA-Z0-9\.\-\_]*/
|
regExp: /[a-zA-Z0-9\.\-\_]*/
|
||||||
|
@ -393,7 +387,7 @@ Cura.MachineAction
|
||||||
text: catalog.i18nc("@action:button", "OK")
|
text: catalog.i18nc("@action:button", "OK")
|
||||||
onClicked:
|
onClicked:
|
||||||
{
|
{
|
||||||
manualPrinterDialog.accept()
|
manager.setManualDevice(manualPrinterDialog.printerKey, manualPrinterDialog.addressText)
|
||||||
manualPrinterDialog.hide()
|
manualPrinterDialog.hide()
|
||||||
}
|
}
|
||||||
enabled: manualPrinterDialog.addressText.trim() != ""
|
enabled: manualPrinterDialog.addressText.trim() != ""
|
||||||
|
|
|
@ -165,7 +165,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
file_name = "none.xml"
|
file_name = "none.xml"
|
||||||
|
|
||||||
self.postForm("materials", "form-data; name=\"file\";filename=\"%s\"" % file_name, xml_data.encode(), onFinished=None)
|
self.postForm("materials", "form-data; name=\"file\";filename=\"%s\"" % file_name, xml_data.encode(), on_finished=None)
|
||||||
|
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
# If the material container is not the most "generic" one it can't be serialized an will raise a
|
# If the material container is not the most "generic" one it can't be serialized an will raise a
|
||||||
|
@ -270,7 +270,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
file_name = "%s.gcode.gz" % CuraApplication.getInstance().getPrintInformation().jobName
|
file_name = "%s.gcode.gz" % CuraApplication.getInstance().getPrintInformation().jobName
|
||||||
self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode,
|
self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode,
|
||||||
onFinished=self._onPostPrintJobFinished)
|
on_finished=self._onPostPrintJobFinished)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -381,8 +381,8 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._checkAuthentication()
|
self._checkAuthentication()
|
||||||
|
|
||||||
# We don't need authentication for requesting info, so we can go right ahead with requesting this.
|
# We don't need authentication for requesting info, so we can go right ahead with requesting this.
|
||||||
self.get("printer", onFinished=self._onGetPrinterDataFinished)
|
self.get("printer", on_finished=self._onGetPrinterDataFinished)
|
||||||
self.get("print_job", onFinished=self._onGetPrintJobFinished)
|
self.get("print_job", on_finished=self._onGetPrintJobFinished)
|
||||||
|
|
||||||
def _resetAuthenticationRequestedMessage(self):
|
def _resetAuthenticationRequestedMessage(self):
|
||||||
if self._authentication_requested_message:
|
if self._authentication_requested_message:
|
||||||
|
@ -404,7 +404,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def _verifyAuthentication(self):
|
def _verifyAuthentication(self):
|
||||||
Logger.log("d", "Attempting to verify authentication")
|
Logger.log("d", "Attempting to verify authentication")
|
||||||
# This will ensure that the "_onAuthenticationRequired" is triggered, which will setup the authenticator.
|
# This will ensure that the "_onAuthenticationRequired" is triggered, which will setup the authenticator.
|
||||||
self.get("auth/verify", onFinished=self._onVerifyAuthenticationCompleted)
|
self.get("auth/verify", on_finished=self._onVerifyAuthenticationCompleted)
|
||||||
|
|
||||||
def _onVerifyAuthenticationCompleted(self, reply):
|
def _onVerifyAuthenticationCompleted(self, reply):
|
||||||
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||||
|
@ -426,7 +426,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
def _checkAuthentication(self):
|
def _checkAuthentication(self):
|
||||||
Logger.log("d", "Checking if authentication is correct for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
|
Logger.log("d", "Checking if authentication is correct for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
|
||||||
self.get("auth/check/" + str(self._authentication_id), onFinished=self._onCheckAuthenticationFinished)
|
self.get("auth/check/" + str(self._authentication_id), on_finished=self._onCheckAuthenticationFinished)
|
||||||
|
|
||||||
def _onCheckAuthenticationFinished(self, reply):
|
def _onCheckAuthenticationFinished(self, reply):
|
||||||
if str(self._authentication_id) not in reply.url().toString():
|
if str(self._authentication_id) not in reply.url().toString():
|
||||||
|
@ -502,7 +502,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self.post("auth/request",
|
self.post("auth/request",
|
||||||
json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(),
|
json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(),
|
||||||
"user": self._getUserName()}).encode(),
|
"user": self._getUserName()}).encode(),
|
||||||
onFinished=self._onRequestAuthenticationFinished)
|
on_finished=self._onRequestAuthenticationFinished)
|
||||||
|
|
||||||
self.setAuthenticationState(AuthState.AuthenticationRequested)
|
self.setAuthenticationState(AuthState.AuthenticationRequested)
|
||||||
|
|
||||||
|
|
|
@ -31,11 +31,11 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
|
||||||
|
|
||||||
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
||||||
data = "{\"target\": \"%s\"}" % state
|
data = "{\"target\": \"%s\"}" % state
|
||||||
self._output_device.put("print_job/state", data, onFinished=None)
|
self._output_device.put("print_job/state", data, on_finished=None)
|
||||||
|
|
||||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
|
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
|
||||||
data = str(temperature)
|
data = str(temperature)
|
||||||
self._output_device.put("printer/bed/temperature/target", data, onFinished=self._onPutBedTemperatureCompleted)
|
self._output_device.put("printer/bed/temperature/target", data, on_finished=self._onPutBedTemperatureCompleted)
|
||||||
|
|
||||||
def _onPutBedTemperatureCompleted(self, reply):
|
def _onPutBedTemperatureCompleted(self, reply):
|
||||||
if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"):
|
if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"):
|
||||||
|
@ -51,10 +51,10 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
|
||||||
new_y = head_pos.y + y
|
new_y = head_pos.y + y
|
||||||
new_z = head_pos.z + z
|
new_z = head_pos.z + z
|
||||||
data = "{\n\"x\":%s,\n\"y\":%s,\n\"z\":%s\n}" %(new_x, new_y, new_z)
|
data = "{\n\"x\":%s,\n\"y\":%s,\n\"z\":%s\n}" %(new_x, new_y, new_z)
|
||||||
self._output_device.put("printer/heads/0/position", data, onFinished=None)
|
self._output_device.put("printer/heads/0/position", data, on_finished=None)
|
||||||
|
|
||||||
def homeBed(self, printer):
|
def homeBed(self, printer):
|
||||||
self._output_device.put("printer/heads/0/position/z", "0", onFinished=None)
|
self._output_device.put("printer/heads/0/position/z", "0", on_finished=None)
|
||||||
|
|
||||||
def _onPreheatBedTimerFinished(self):
|
def _onPreheatBedTimerFinished(self):
|
||||||
self.setTargetBedTemperature(self._preheat_printer, 0)
|
self.setTargetBedTemperature(self._preheat_printer, 0)
|
||||||
|
@ -89,7 +89,7 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
|
||||||
printer.updateIsPreheating(True)
|
printer.updateIsPreheating(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
self._output_device.put("printer/bed/pre_heat", data, onFinished = self._onPutPreheatBedCompleted)
|
self._output_device.put("printer/bed/pre_heat", data, on_finished = self._onPutPreheatBedCompleted)
|
||||||
printer.updateIsPreheating(True)
|
printer.updateIsPreheating(True)
|
||||||
self._preheat_request_in_progress = True
|
self._preheat_request_in_progress = True
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,10 @@ UM.Dialog
|
||||||
{
|
{
|
||||||
resetPrintersModel()
|
resetPrintersModel()
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutputDevice.cancelPrintSelection()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
title: catalog.i18nc("@title:window", "Print over network")
|
title: catalog.i18nc("@title:window", "Print over network")
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,9 @@ Item
|
||||||
|
|
||||||
MouseArea
|
MouseArea
|
||||||
{
|
{
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: OutputDevice.setActivePrinter(null)
|
onClicked: OutputDevice.setActivePrinter(null)
|
||||||
z: 0
|
z: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Button
|
Button
|
||||||
|
@ -28,7 +28,7 @@ Item
|
||||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").width
|
anchors.bottomMargin: UM.Theme.getSize("default_margin").width
|
||||||
anchors.right: cameraImage.right
|
anchors.right: cameraImage.right
|
||||||
|
|
||||||
// TODO: Harcoded sizes
|
// TODO: Hardcoded sizes
|
||||||
width: 20 * screenScaleFactor
|
width: 20 * screenScaleFactor
|
||||||
height: 20 * screenScaleFactor
|
height: 20 * screenScaleFactor
|
||||||
|
|
||||||
|
@ -89,9 +89,11 @@ Item
|
||||||
|
|
||||||
MouseArea
|
MouseArea
|
||||||
{
|
{
|
||||||
anchors.fill: cameraImage
|
anchors.fill: cameraImage
|
||||||
onClicked: { /* no-op */ }
|
onClicked:
|
||||||
z: 1
|
{
|
||||||
|
OutputDevice.setActivePrinter(null)
|
||||||
|
}
|
||||||
|
z: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,12 +39,12 @@ class SendMaterialJob(Job):
|
||||||
try:
|
try:
|
||||||
remote_materials_list = json.loads(remote_materials_list)
|
remote_materials_list = json.loads(remote_materials_list)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
Logger.log("e", "Current material storage on printer was a corrupted reply.")
|
Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
remote_materials_by_guid = {material["guid"]: material for material in remote_materials_list} #Index by GUID.
|
remote_materials_by_guid = {material["guid"]: material for material in remote_materials_list} #Index by GUID.
|
||||||
except KeyError:
|
except KeyError:
|
||||||
Logger.log("e", "Current material storage on printer was an invalid reply (missing GUIDs).")
|
Logger.log("e", "Request material storage on printer: Printer's answer was missing GUIDs.")
|
||||||
return
|
return
|
||||||
|
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = ContainerRegistry.getInstance()
|
||||||
|
|
|
@ -198,7 +198,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||||
has_cluster_capable_firmware = Version(system_info["firmware"]) > self._min_cluster_version
|
has_cluster_capable_firmware = Version(system_info["firmware"]) > self._min_cluster_version
|
||||||
instance_name = "manual:%s" % address
|
instance_name = "manual:%s" % address
|
||||||
properties = {
|
properties = {
|
||||||
b"name": system_info["name"].encode("utf-8"),
|
b"name": (system_info["name"] + " (manual)").encode("utf-8"),
|
||||||
b"address": address.encode("utf-8"),
|
b"address": address.encode("utf-8"),
|
||||||
b"firmware_version": system_info["firmware"].encode("utf-8"),
|
b"firmware_version": system_info["firmware"].encode("utf-8"),
|
||||||
b"manual": b"true",
|
b"manual": b"true",
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Manages network connections to Ultimaker 3 printers.",
|
"description": "Manages network connections to Ultimaker 3 printers.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
|
@ -21,7 +21,6 @@ class AutoDetectBaudJob(Job):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
Logger.log("d", "Auto detect baud rate started.")
|
Logger.log("d", "Auto detect baud rate started.")
|
||||||
timeout = 3
|
|
||||||
wait_response_timeouts = [3, 15, 30]
|
wait_response_timeouts = [3, 15, 30]
|
||||||
wait_bootloader_times = [1.5, 5, 15]
|
wait_bootloader_times = [1.5, 5, 15]
|
||||||
write_timeout = 3
|
write_timeout = 3
|
||||||
|
@ -52,7 +51,7 @@ class AutoDetectBaudJob(Job):
|
||||||
if serial is None:
|
if serial is None:
|
||||||
try:
|
try:
|
||||||
serial = Serial(str(self._serial_port), baud_rate, timeout = read_timeout, writeTimeout = write_timeout)
|
serial = Serial(str(self._serial_port), baud_rate, timeout = read_timeout, writeTimeout = write_timeout)
|
||||||
except SerialException as e:
|
except SerialException:
|
||||||
Logger.logException("w", "Unable to create serial")
|
Logger.logException("w", "Unable to create serial")
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
@ -72,7 +71,7 @@ class AutoDetectBaudJob(Job):
|
||||||
|
|
||||||
while timeout_time > time():
|
while timeout_time > time():
|
||||||
line = serial.readline()
|
line = serial.readline()
|
||||||
if b"ok T:" in line:
|
if b"ok " in line and b"T:" in line:
|
||||||
successful_responses += 1
|
successful_responses += 1
|
||||||
if successful_responses >= 3:
|
if successful_responses >= 3:
|
||||||
self.setResult(baud_rate)
|
self.setResult(baud_rate)
|
||||||
|
|
|
@ -15,7 +15,7 @@ from cura.PrinterOutput.GenericOutputController import GenericOutputController
|
||||||
from .AutoDetectBaudJob import AutoDetectBaudJob
|
from .AutoDetectBaudJob import AutoDetectBaudJob
|
||||||
from .avr_isp import stk500v2, intelHex
|
from .avr_isp import stk500v2, intelHex
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
|
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl
|
||||||
|
|
||||||
from serial import Serial, SerialException, SerialTimeoutException
|
from serial import Serial, SerialException, SerialTimeoutException
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event
|
||||||
|
@ -146,8 +146,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def updateFirmware(self, file):
|
def updateFirmware(self, file):
|
||||||
# the file path is qurl encoded.
|
# the file path could be url-encoded.
|
||||||
self._firmware_location = file.replace("file://", "")
|
if file.startswith("file://"):
|
||||||
|
self._firmware_location = QUrl(file).toLocalFile()
|
||||||
|
else:
|
||||||
|
self._firmware_location = file
|
||||||
self.showFirmwareInterface()
|
self.showFirmwareInterface()
|
||||||
self.setFirmwareUpdateState(FirmwareUpdateState.updating)
|
self.setFirmwareUpdateState(FirmwareUpdateState.updating)
|
||||||
self._update_firmware_thread.start()
|
self._update_firmware_thread.start()
|
||||||
|
@ -323,7 +326,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
if self._firmware_name is None:
|
if self._firmware_name is None:
|
||||||
self.sendCommand("M115")
|
self.sendCommand("M115")
|
||||||
|
|
||||||
if b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
|
if (b"ok " in line and b"T:" in line) or b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
|
||||||
extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
|
extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
|
||||||
# Update all temperature values
|
# Update all temperature values
|
||||||
matched_extruder_nrs = []
|
matched_extruder_nrs = []
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue