mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-10-10 07:17:52 -06:00
Merge branch 'master' of github.com:ultimaker/Cura into feature_material_editing
* 'master' of github.com:ultimaker/Cura: (76 commits) Added UMO upgrade selection Added stubs for UMO upgrade selection Machine action labels are now translatable Code style CURA-1676 Using the correct placeholder SplashScreen: Using system-default fontfamily USBPrinting: Let's "Print via USB" BQ Hephestos2 - Preheating temperature fix When starting a print with the "custom" GCode by BQ, the printer resets the nozzle temperature to 210°C when printing via SD card (no matter what you set on Cura) and only preheats the nozzle to 180°C when printing via USB. So currently the printer begins to print via USB at 180°C and reaches the correct temperature eg.g while printing the brim. Fix warning about missing color in theme Automatically show the Print Monitor when starting a print Fix USBPrinterOutputDevice to work with the Print Monitor Properly prevent warning when no printer is connected. ZOffset decorator is now correctly copied Force focus instead of requesting it. Fix two "critical errors" on startup Minor check machine action GUI improvements Prevent warning when no printer is connected. Fix empty material/quality profiles when switching variants Disable monitor buttons when there is no job running Move message stack "above" the viewport overlay ...
This commit is contained in:
commit
7008e047f6
40 changed files with 1878 additions and 5629 deletions
18
cura/CameraImageProvider.py
Normal file
18
cura/CameraImageProvider.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from PyQt5.QtGui import QImage
|
||||
from PyQt5.QtQuick import QQuickImageProvider
|
||||
from PyQt5.QtCore import QSize
|
||||
|
||||
from UM.Application import Application
|
||||
|
||||
class CameraImageProvider(QQuickImageProvider):
|
||||
def __init__(self):
|
||||
QQuickImageProvider.__init__(self, QQuickImageProvider.Image)
|
||||
|
||||
## Request a new image.
|
||||
def requestImage(self, id, size):
|
||||
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
|
||||
try:
|
||||
return output_device.getCameraImage(), QSize(15, 15)
|
||||
except AttributeError:
|
||||
pass
|
||||
return QImage(), QSize(15, 15)
|
|
@ -144,6 +144,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
|
||||
else:
|
||||
rounded_hull = None
|
||||
mesh = None
|
||||
world_transform = None
|
||||
if self._node.getMeshData():
|
||||
mesh = self._node.getMeshData()
|
||||
world_transform = self._node.getWorldTransformation()
|
||||
|
|
|
@ -44,6 +44,7 @@ from . import ZOffsetDecorator
|
|||
from . import CuraSplashScreen
|
||||
from . import MachineManagerModel
|
||||
from . import ContainerSettingsModel
|
||||
from . import CameraImageProvider
|
||||
from . import MachineActionManager
|
||||
from . import ContainerManager
|
||||
|
||||
|
@ -124,7 +125,6 @@ class CuraApplication(QtApplication):
|
|||
self._platform = None
|
||||
self._output_devices = {}
|
||||
self._print_information = None
|
||||
self._i18n_catalog = None
|
||||
self._previous_active_tool = None
|
||||
self._platform_activity = False
|
||||
self._scene_bounding_box = AxisAlignedBox.Null
|
||||
|
@ -135,12 +135,16 @@ class CuraApplication(QtApplication):
|
|||
self._cura_actions = None
|
||||
self._started = False
|
||||
|
||||
self._i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
|
||||
self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
|
||||
|
||||
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
|
||||
Resources.addType(self.ResourceTypes.Firmware, "firmware")
|
||||
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading machines..."))
|
||||
|
||||
## Add the 4 types of profiles to storage.
|
||||
Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality")
|
||||
Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants")
|
||||
|
@ -229,7 +233,7 @@ class CuraApplication(QtApplication):
|
|||
JobQueue.getInstance().jobFinished.connect(self._onJobFinished)
|
||||
|
||||
self.applicationShuttingDown.connect(self.saveSettings)
|
||||
|
||||
self.engineCreatedSignal.connect(self._onEngineCreated)
|
||||
self._recent_files = []
|
||||
files = Preferences.getInstance().getValue("cura/recent_files").split(";")
|
||||
for f in files:
|
||||
|
@ -238,6 +242,11 @@ class CuraApplication(QtApplication):
|
|||
|
||||
self._recent_files.append(QUrl.fromLocalFile(f))
|
||||
|
||||
def _onEngineCreated(self):
|
||||
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||
|
||||
showPrintMonitor = pyqtSignal(bool, arguments = ["show"])
|
||||
|
||||
## Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
|
||||
#
|
||||
# Note that the AutoSave plugin also calls this method.
|
||||
|
@ -327,13 +336,6 @@ class CuraApplication(QtApplication):
|
|||
parser.add_argument("--debug", dest="debug-mode", action="store_true", default=False, help="Enable detailed crash reports.")
|
||||
|
||||
def run(self):
|
||||
self._i18n_catalog = i18nCatalog("cura");
|
||||
|
||||
i18nCatalog.setTagReplacements({
|
||||
"filename": "font color=\"black\"",
|
||||
"message": "font color=UM.Theme.colors.message_text;",
|
||||
})
|
||||
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
|
||||
|
||||
controller = self.getController()
|
||||
|
@ -554,12 +556,12 @@ class CuraApplication(QtApplication):
|
|||
for _ in range(count):
|
||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||
new_node = copy.deepcopy(node.getParent()) #Copy the group node.
|
||||
new_node.callDecoration("setConvexHull",None)
|
||||
new_node.callDecoration("recomputeConvexHull")
|
||||
|
||||
op.addOperation(AddSceneNodeOperation(new_node,node.getParent().getParent()))
|
||||
else:
|
||||
new_node = copy.deepcopy(node)
|
||||
new_node.callDecoration("setConvexHull", None)
|
||||
new_node.callDecoration("recomputeConvexHull")
|
||||
op.addOperation(AddSceneNodeOperation(new_node, node.getParent()))
|
||||
|
||||
op.push()
|
||||
|
@ -817,3 +819,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
def _addProfileWriter(self, profile_writer):
|
||||
pass
|
||||
|
||||
@pyqtSlot("QSize")
|
||||
def setMinimumWindowSize(self, size):
|
||||
self.getMainWindow().setMinimumSize(size)
|
||||
|
|
|
@ -25,10 +25,13 @@ class CuraSplashScreen(QSplashScreen):
|
|||
if buildtype:
|
||||
version[0] += " (%s)" %(buildtype)
|
||||
|
||||
painter.setFont(QFont("Proxima Nova Rg", 20 ))
|
||||
font = QFont() # Using system-default font here
|
||||
font.setPointSize(20)
|
||||
painter.setFont(font)
|
||||
painter.drawText(0, 0, 330 * self._scale, 230 * self._scale, Qt.AlignHCenter | Qt.AlignBottom, version[0])
|
||||
if len(version) > 1:
|
||||
painter.setFont(QFont("Proxima Nova Rg", 12 ))
|
||||
font.setPointSize(12)
|
||||
painter.setFont(font)
|
||||
painter.drawText(0, 0, 330 * self._scale, 255 * self._scale, Qt.AlignHCenter | Qt.AlignBottom, version[1])
|
||||
|
||||
painter.restore()
|
||||
|
|
|
@ -84,8 +84,8 @@ class MachineManagerModel(QObject):
|
|||
self._global_stack_valid = False
|
||||
self.globalValidationChanged.emit()
|
||||
else:
|
||||
new_validation_state = self._checkStackForErrors(self._active_container_stack)
|
||||
if new_validation_state:
|
||||
has_errors = self._checkStackForErrors(self._active_container_stack)
|
||||
if not has_errors:
|
||||
self._global_stack_valid = True
|
||||
self.globalValidationChanged.emit()
|
||||
|
||||
|
@ -104,6 +104,7 @@ class MachineManagerModel(QObject):
|
|||
self._global_stack_valid = not self._checkStackForErrors(self._global_container_stack)
|
||||
|
||||
def _onActiveExtruderStackChanged(self):
|
||||
self.blurSettings.emit() # Ensure no-one has focus.
|
||||
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
|
||||
self._active_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
|
||||
self._active_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
|
||||
|
@ -202,7 +203,7 @@ class MachineManagerModel(QObject):
|
|||
@pyqtProperty(bool, notify = activeStackChanged)
|
||||
def hasUserSettings(self):
|
||||
if not self._active_container_stack:
|
||||
return
|
||||
return False
|
||||
|
||||
user_settings = self._active_container_stack.getTop().findInstances(**{})
|
||||
return len(user_settings) != 0
|
||||
|
@ -212,7 +213,7 @@ class MachineManagerModel(QObject):
|
|||
# Calling _checkStackForErrors on every change is simply too expensive
|
||||
@pyqtProperty(bool, notify = globalValidationChanged)
|
||||
def isGlobalStackValid(self):
|
||||
return self._global_stack_valid
|
||||
return bool(self._global_stack_valid)
|
||||
|
||||
@pyqtProperty(str, notify = activeStackChanged)
|
||||
def activeUserProfileId(self):
|
||||
|
@ -285,7 +286,7 @@ class MachineManagerModel(QObject):
|
|||
self.blurSettings.emit()
|
||||
self.setActiveQuality(new_container_id)
|
||||
self.updateQualityContainerFromUserContainer()
|
||||
|
||||
return new_container_id
|
||||
|
||||
@pyqtSlot(str, result=str)
|
||||
def duplicateContainer(self, container_id):
|
||||
|
@ -357,7 +358,6 @@ class MachineManagerModel(QObject):
|
|||
self.setActiveQuality(containers[0].getId())
|
||||
self.activeQualityChanged.emit()
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def updateQualityContainerFromUserContainer(self):
|
||||
if not self._active_container_stack:
|
||||
|
@ -401,9 +401,9 @@ class MachineManagerModel(QObject):
|
|||
|
||||
preferred_material = None
|
||||
if old_material:
|
||||
preferred_material = old_material.getId()
|
||||
preferred_material_name = old_material.getName()
|
||||
|
||||
self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material).id)
|
||||
self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material_name).id)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveQuality(self, quality_id):
|
||||
|
@ -496,6 +496,12 @@ class MachineManagerModel(QObject):
|
|||
|
||||
return False
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def getDefinitionByMachineId(self, machine_id):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id=machine_id)
|
||||
if containers:
|
||||
return containers[0].getBottom().getId()
|
||||
|
||||
def _updateVariantContainer(self, definition):
|
||||
if not definition.getMetaDataEntry("has_variants"):
|
||||
return self._empty_variant_container
|
||||
|
@ -513,7 +519,7 @@ class MachineManagerModel(QObject):
|
|||
|
||||
return self._empty_variant_container
|
||||
|
||||
def _updateMaterialContainer(self, definition, variant_container = None, preferred_material = None):
|
||||
def _updateMaterialContainer(self, definition, variant_container = None, preferred_material_name = None):
|
||||
if not definition.getMetaDataEntry("has_materials"):
|
||||
return self._empty_material_container
|
||||
|
||||
|
@ -527,15 +533,26 @@ class MachineManagerModel(QObject):
|
|||
else:
|
||||
search_criteria["definition"] = "fdmprinter"
|
||||
|
||||
if not preferred_material:
|
||||
if preferred_material_name:
|
||||
search_criteria["name"] = preferred_material_name
|
||||
else:
|
||||
preferred_material = definition.getMetaDataEntry("preferred_material")
|
||||
if preferred_material:
|
||||
search_criteria["id"] = preferred_material
|
||||
if preferred_material:
|
||||
search_criteria["id"] = preferred_material
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
return containers[0]
|
||||
|
||||
if "name" in search_criteria or "id" in search_criteria:
|
||||
# If a material by this name can not be found, try a wider set of search criteria
|
||||
search_criteria.pop("name", None)
|
||||
search_criteria.pop("id", None)
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
return containers[0]
|
||||
|
||||
return self._empty_material_container
|
||||
|
||||
def _updateQualityContainer(self, definition, material_container = None, preferred_quality_name = None):
|
||||
|
@ -560,6 +577,15 @@ class MachineManagerModel(QObject):
|
|||
if containers:
|
||||
return containers[0]
|
||||
|
||||
if "name" in search_criteria or "id" in search_criteria:
|
||||
# If a quality by this name can not be found, try a wider set of search criteria
|
||||
search_criteria.pop("name", None)
|
||||
search_criteria.pop("id", None)
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
return containers[0]
|
||||
|
||||
return self._empty_quality_container
|
||||
|
||||
def createMachineManagerModel(engine, script_engine):
|
||||
|
|
|
@ -7,7 +7,6 @@ from UM.Scene.SceneNode import SceneNode
|
|||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Application import Application
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Preferences import Preferences
|
||||
|
||||
|
@ -16,8 +15,6 @@ from cura.ConvexHullDecorator import ConvexHullDecorator
|
|||
from . import PlatformPhysicsOperation
|
||||
from . import ZOffsetDecorator
|
||||
|
||||
import copy
|
||||
|
||||
class PlatformPhysics:
|
||||
def __init__(self, controller, volume):
|
||||
super().__init__()
|
||||
|
@ -100,18 +97,15 @@ class PlatformPhysics:
|
|||
# continue
|
||||
|
||||
# Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
|
||||
try:
|
||||
head_hull = node.callDecoration("getConvexHullHead")
|
||||
if head_hull:
|
||||
overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull"))
|
||||
if not overlap:
|
||||
other_head_hull = other_node.callDecoration("getConvexHullHead")
|
||||
if other_head_hull:
|
||||
overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull)
|
||||
else:
|
||||
overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_node.callDecoration("getConvexHull"))
|
||||
except:
|
||||
overlap = None #It can sometimes occur that the calculated convex hull has no size, in which case there is no overlap.
|
||||
head_hull = node.callDecoration("getConvexHullHead")
|
||||
if head_hull:
|
||||
overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull"))
|
||||
if not overlap:
|
||||
other_head_hull = other_node.callDecoration("getConvexHullHead")
|
||||
if other_head_hull:
|
||||
overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull)
|
||||
else:
|
||||
overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_node.callDecoration("getConvexHull"))
|
||||
|
||||
if overlap is None:
|
||||
continue
|
||||
|
|
|
@ -29,6 +29,10 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
self._head_y = 0
|
||||
self._head_z = 0
|
||||
self._connection_state = ConnectionState.closed
|
||||
self._time_elapsed = 0
|
||||
self._time_total = 0
|
||||
self._job_state = ""
|
||||
self._job_name = ""
|
||||
|
||||
def requestWrite(self, node, file_name = None, filter_by_machine = False):
|
||||
raise NotImplementedError("requestWrite needs to be implemented")
|
||||
|
@ -57,6 +61,39 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
# it also sends it's own device_id (for convenience sake)
|
||||
connectionStateChanged = pyqtSignal(str)
|
||||
|
||||
timeElapsedChanged = pyqtSignal()
|
||||
|
||||
timeTotalChanged = pyqtSignal()
|
||||
|
||||
jobStateChanged = pyqtSignal()
|
||||
|
||||
jobNameChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(str, notify = jobStateChanged)
|
||||
def jobState(self):
|
||||
return self._job_state
|
||||
|
||||
def _updateJobState(self, job_state):
|
||||
if self._job_state != job_state:
|
||||
self._job_state = job_state
|
||||
self.jobStateChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setJobState(self, job_state):
|
||||
self._setJobState(job_state)
|
||||
|
||||
def _setJobState(self, job_state):
|
||||
Logger.log("w", "_setJobState is not implemented by this output device")
|
||||
|
||||
@pyqtProperty(str, notify = jobNameChanged)
|
||||
def jobName(self):
|
||||
return self._job_name
|
||||
|
||||
def setJobName(self, name):
|
||||
if self._job_name != name:
|
||||
self._job_name = name
|
||||
self.jobNameChanged.emit()
|
||||
|
||||
## Get the bed temperature of the bed (if any)
|
||||
# This function is "final" (do not re-implement)
|
||||
# /sa _getBedTemperature implementation function
|
||||
|
@ -74,6 +111,30 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
self._target_bed_temperature = temperature
|
||||
self.targetBedTemperatureChanged.emit()
|
||||
|
||||
## Time the print has been printing.
|
||||
# Note that timeTotal - timeElapsed should give time remaining.
|
||||
@pyqtProperty(float, notify = timeElapsedChanged)
|
||||
def timeElapsed(self):
|
||||
return self._time_elapsed
|
||||
|
||||
## Total time of the print
|
||||
# Note that timeTotal - timeElapsed should give time remaining.
|
||||
@pyqtProperty(float, notify=timeTotalChanged)
|
||||
def timeTotal(self):
|
||||
return self._time_total
|
||||
|
||||
@pyqtSlot(float)
|
||||
def setTimeTotal(self, new_total):
|
||||
if self._time_total != new_total:
|
||||
self._time_total = new_total
|
||||
self.timeTotalChanged.emit()
|
||||
|
||||
@pyqtSlot(float)
|
||||
def setTimeElapsed(self, time_elapsed):
|
||||
if self._time_elapsed != time_elapsed:
|
||||
self._time_elapsed = time_elapsed
|
||||
self.timeElapsedChanged.emit()
|
||||
|
||||
## Home the head of the connected printer
|
||||
# This function is "final" (do not re-implement)
|
||||
# /sa _homeHead implementation function
|
||||
|
|
|
@ -10,3 +10,8 @@ class ZOffsetDecorator(SceneNodeDecorator):
|
|||
|
||||
def getZOffset(self):
|
||||
return self._z_offset
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
copied_decorator = ZOffsetDecorator()
|
||||
copied_decorator.setZOffset(self.getZOffset())
|
||||
return copied_decorator
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue