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:
Arjen Hiemstra 2016-07-04 11:45:59 +02:00
commit 7008e047f6
40 changed files with 1878 additions and 5629 deletions

View 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)

View file

@ -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()

View file

@ -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)

View file

@ -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()

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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