This commit is contained in:
ChrisTerBeke 2017-12-08 09:52:06 +01:00
commit 7a6330fd08
20 changed files with 317 additions and 191 deletions

View File

@ -1,6 +1,5 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket
@ -32,6 +31,7 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.SetTransformOperation import SetTransformOperation
from cura.Arrange import Arrange
from cura.ShapeArray import ShapeArray
from cura.ConvexHullDecorator import ConvexHullDecorator
@ -128,6 +128,7 @@ class CuraApplication(QtApplication):
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
def __init__(self):
# this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
Resources.addExpectedDirNameInData(dir_name)
@ -156,7 +157,6 @@ class CuraApplication(QtApplication):
SettingDefinition.addSettingType("extruder", None, str, Validator)
SettingDefinition.addSettingType("optional_extruder", None, str, None)
SettingDefinition.addSettingType("[int]", None, str, None)
SettingFunction.registerOperator("extruderValues", ExtruderManager.getExtruderValues)
@ -181,7 +181,8 @@ class CuraApplication(QtApplication):
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer)
## Initialise the version upgrade manager with Cura's storage paths.
import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies.
# Needs to be here to prevent circular dependencies.
import UM.VersionUpgradeManager
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
{
@ -227,7 +228,9 @@ class CuraApplication(QtApplication):
"TranslateTool",
"FileLogger",
"XmlMaterialProfile",
"PluginBrowser"
"PluginBrowser",
"PrepareStage",
"MonitorStage"
])
self._physics = None
self._volume = None
@ -388,7 +391,6 @@ class CuraApplication(QtApplication):
def needToShowUserAgreement(self):
return self._need_to_show_user_agreement
def setNeedToShowUserAgreement(self, set_value = True):
self._need_to_show_user_agreement = set_value
@ -666,8 +668,8 @@ class CuraApplication(QtApplication):
controller = self.getController()
controller.setActiveStage("PrepareStage")
controller.setActiveView("SolidView")
controller.setCameraTool("CameraTool")
controller.setSelectionTool("SelectionTool")
@ -717,6 +719,7 @@ class CuraApplication(QtApplication):
run_headless = self.getCommandLineOption("headless", False)
if not run_headless:
self.initializeEngine()
controller.setActiveStage("PrepareStage")
if run_headless or self._engine.rootObjects:
self.closeSplash()

22
cura/Stages/CuraStage.py Normal file
View File

@ -0,0 +1,22 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, QUrl, QObject
from UM.Stage import Stage
class CuraStage(Stage):
def __init__(self, parent = None):
super().__init__(parent)
@pyqtProperty(str, constant = True)
def stageId(self):
return self.getPluginId()
@pyqtProperty(QUrl, constant = True)
def mainComponent(self):
return self.getDisplayComponent("main")
@pyqtProperty(QUrl, constant = True)
def sidebarComponent(self):
return self.getDisplayComponent("sidebar")

2
cura/Stages/__init__.py Normal file
View File

@ -0,0 +1,2 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.

View File

@ -22,7 +22,8 @@ if Platform.isLinux(): # Needed for platform.linux_distribution, which is not av
if Platform.isWindows() and hasattr(sys, "frozen"):
try:
del os.environ["PYTHONPATH"]
except KeyError: pass
except KeyError:
pass
# WORKAROUND: GITHUB-704 GITHUB-708
# It looks like setuptools creates a .pth file in
@ -45,6 +46,7 @@ def exceptHook(hook_type, value, traceback):
_crash_handler = CrashHandler(hook_type, value, traceback)
_crash_handler.show()
sys.excepthook = exceptHook
# Workaround for a race condition on certain systems where there
@ -75,7 +77,7 @@ faulthandler.enable()
# Force an instance of CuraContainerRegistry to be created and reused later.
cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance()
# This prestart up check is needed to determine if we should start the application at all.
# This pre-start up check is needed to determine if we should start the application at all.
if not cura.CuraApplication.CuraApplication.preStartUp():
sys.exit(0)

View File

@ -0,0 +1,39 @@
// Copyright (c) 2017 Ultimaker B.V.
import QtQuick 2.2
import QtQuick.Controls 1.1
import UM 1.3 as UM
import Cura 1.0 as Cura
Item
{
// We show a nice overlay on the 3D viewer when the current output device has no monitor view
Rectangle
{
id: viewportOverlay
color: UM.Theme.getColor("viewport_overlay")
width: parent.width
height: parent.height
visible: monitorViewComponent.sourceComponent == null ? 1 : 0
MouseArea
{
anchors.fill: parent
acceptedButtons: Qt.AllButtons
onWheel: wheel.accepted = true
}
}
Loader
{
id: monitorViewComponent
property real maximumWidth: parent.width
property real maximumHeight: parent.height
sourceComponent: Cura.MachineManager.printerOutputDevices.length > 0 ? Cura.MachineManager.printerOutputDevices[0].monitorItem: null
visible: sourceComponent != null
}
}

View File

@ -0,0 +1,73 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources
from cura.Stages.CuraStage import CuraStage
## Stage for monitoring a 3D printing while it's printing.
class MonitorStage(CuraStage):
def __init__(self, parent = None):
super().__init__(parent)
# Wait until QML engine is created, otherwise creating the new QML components will fail
Application.getInstance().engineCreatedSignal.connect(self._setComponents)
# Update the status icon when the output device is changed
Application.getInstance().getOutputDeviceManager().activeDeviceChanged.connect(self._setIconSource)
def _setComponents(self):
self._setMainOverlay()
self._setSidebar()
self._setIconSource()
def _setMainOverlay(self):
main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("MonitorStage"), "MonitorMainView.qml")
self.addDisplayComponent("main", main_component_path)
def _setSidebar(self):
# TODO: currently the sidebar component for prepare and monitor stages is the same, this will change with the printer output device refactor!
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles), "Sidebar.qml")
self.addDisplayComponent("sidebar", sidebar_component_path)
def _setIconSource(self):
if Application.getInstance().getTheme() is not None:
icon_name = self._getActiveOutputDeviceStatusIcon()
self.setIconSource(Application.getInstance().getTheme().getIcon(icon_name))
## Find the correct status icon depending on the active output device state
def _getActiveOutputDeviceStatusIcon(self):
output_device = Application.getInstance().getOutputDeviceManager().getActiveDevice()
if not output_device:
return "tab_status_unknown"
if hasattr(output_device, "acceptsCommands") and not output_device.acceptsCommands:
return "tab_status_unknown"
if not hasattr(output_device, "printerState") or not hasattr(output_device, "jobState"):
return "tab_status_unknown"
# TODO: refactor to use enum instead of hardcoded strings?
if output_device.printerState == "maintenance":
return "tab_status_busy"
if output_device.jobState in ["printing", "pre_print", "pausing", "resuming"]:
return "tab_status_busy"
if output_device.jobState == "wait_cleanup":
return "tab_status_finished"
if output_device.jobState in ["ready", ""]:
return "tab_status_connected"
if output_device.jobState == "paused":
return "tab_status_paused"
if output_device.jobState == "error":
return "tab_status_stopped"
return "tab_status_unknown"

View File

@ -0,0 +1,20 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import MonitorStage
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"stage": {
"name": i18n_catalog.i18nc("@item:inmenu", "Monitor"),
"weight": 1
}
}
def register(app):
return {
"stage": MonitorStage.MonitorStage()
}

View File

@ -0,0 +1,8 @@
{
"name": "Monitor Stage",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides a monitor stage in Cura.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -0,0 +1,18 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
from UM.Application import Application
from UM.Resources import Resources
from cura.Stages.CuraStage import CuraStage
## Stage for preparing model (slicing).
class PrepareStage(CuraStage):
def __init__(self, parent = None):
super().__init__(parent)
Application.getInstance().engineCreatedSignal.connect(self._engineCreated)
def _engineCreated(self):
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles), "Sidebar.qml")
self.addDisplayComponent("sidebar", sidebar_component_path)

View File

@ -0,0 +1,20 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import PrepareStage
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"stage": {
"name": i18n_catalog.i18nc("@item:inmenu", "Prepare"),
"weight": 0
}
}
def register(app):
return {
"stage": PrepareStage.PrepareStage()
}

View File

@ -0,0 +1,8 @@
{
"name": "Prepare Stage",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides a prepare stage in Cura.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -698,7 +698,7 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
if self._reply:
self._reply.abort()
self._stage = OutputStage.ready
Application.getInstance().showPrintMonitor.emit(False)
Application.getInstance().getController().setActiveStage("PrepareStage")
@pyqtSlot(int, result=str)
def formatDuration(self, seconds):

View File

@ -672,7 +672,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
Logger.log("d", "Attempting to perform an action without authentication for printer %s. Auth state is %s", self._key, self._authentication_state)
return
Application.getInstance().showPrintMonitor.emit(True)
Application.getInstance().getController().setActiveStage("MonitorStage")
self._print_finished = True
self.writeStarted.emit(self)
self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
@ -767,7 +767,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
if button == QMessageBox.Yes:
self.startPrint()
else:
Application.getInstance().showPrintMonitor.emit(False)
Application.getInstance().getController().setActiveStage("PrepareStage")
# For some unknown reason Cura on OSX will hang if we do the call back code
# immediately without first returning and leaving QML's event system.
QTimer.singleShot(100, delayedCallback)
@ -850,7 +850,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._write_finished = True # post_reply does not always exist, so make sure we unblock writing
if self._post_reply:
self._finalizePostReply()
Application.getInstance().showPrintMonitor.emit(False)
Application.getInstance().getController().setActiveStage("PrepareStage")
## Attempt to start a new print.
# This function can fail to actually start a print due to not being authenticated or another print already

View File

@ -490,7 +490,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self.setJobName(file_name)
self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
Application.getInstance().showPrintMonitor.emit(True)
Application.getInstance().getController().setActiveStage("MonitorStage")
self.startPrint()
def _setEndstopState(self, endstop_key, value):
@ -698,7 +698,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._is_printing = False
self._is_paused = False
self._updateJobState("ready")
Application.getInstance().showPrintMonitor.emit(False)
Application.getInstance().getController().setActiveStage("PrepareStage")
## Check if the process did not encounter an error yet.
def hasError(self):

View File

@ -20,14 +20,16 @@ UM.MainWindow
viewportRect: Qt.rect(0, 0, (base.width - sidebar.width) / base.width, 1.0)
property bool showPrintMonitor: false
// This connection is here to support legacy printer output devices that use the showPrintMonitor signal on Application to switch to the monitor stage
// It should be phased out in newer plugin versions.
Connections
{
target: Printer
onShowPrintMonitor: {
if (show) {
topbar.startMonitoringPrint()
UM.Controller.setActiveStage("MonitorStage")
} else {
topbar.stopMonitoringPrint()
UM.Controller.setActiveStage("PrepareStage")
}
}
}
@ -331,7 +333,7 @@ UM.MainWindow
text: catalog.i18nc("@action:button","Open File");
iconSource: UM.Theme.getIcon("load")
style: UM.Theme.styles.tool_button
tooltip: '';
tooltip: ""
anchors
{
top: topbar.bottom;
@ -361,31 +363,29 @@ UM.MainWindow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
monitoringPrint: base.showPrintMonitor
onStartMonitoringPrint: base.showPrintMonitor = true
onStopMonitoringPrint: base.showPrintMonitor = false
}
Sidebar
Loader
{
id: sidebar;
id: sidebar
anchors
{
top: topbar.bottom;
bottom: parent.bottom;
right: parent.right;
top: topbar.bottom
bottom: parent.bottom
right: parent.right
}
width: UM.Theme.getSize("sidebar").width
z: 1
width: UM.Theme.getSize("sidebar").width;
monitoringPrint: base.showPrintMonitor
source: UM.Controller.activeStage.sidebarComponent
}
Rectangle
Loader
{
id: viewportOverlay
id: main
color: UM.Theme.getColor("viewport_overlay")
anchors
{
top: topbar.bottom
@ -393,27 +393,16 @@ UM.MainWindow
left: parent.left
right: sidebar.left
}
visible: opacity > 0
opacity: base.showPrintMonitor ? 1 : 0
MouseArea {
MouseArea
{
visible: UM.Controller.activeStage.mainComponent != ""
anchors.fill: parent
acceptedButtons: Qt.AllButtons
onWheel: wheel.accepted = true
}
}
Loader
{
sourceComponent: Cura.MachineManager.printerOutputDevices.length > 0 ? Cura.MachineManager.printerOutputDevices[0].monitorItem: null
visible: base.showPrintMonitor
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenterOffset: - UM.Theme.getSize("sidebar").width / 2
anchors.verticalCenterOffset: UM.Theme.getSize("sidebar_header").height / 2
property real maximumWidth: viewportOverlay.width
property real maximumHeight: viewportOverlay.height
source: UM.Controller.activeStage.mainComponent
}
UM.MessageStack

View File

@ -12,21 +12,23 @@ Menu
title: catalog.i18nc("@title:menu menubar:toplevel", "&View");
id: menu
enabled: !PrintInformation.preSliced
// main views
Instantiator
{
model: UM.ViewModel{}
MenuItem
{
text: model.name;
checkable: true;
checked: model.active;
exclusiveGroup: group;
onTriggered: UM.Controller.setActiveView(model.id);
text: model.name
checkable: true
checked: model.active
exclusiveGroup: group
onTriggered: UM.Controller.setActiveView(model.id)
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
ExclusiveGroup { id: group; }
ExclusiveGroup { id: group }
MenuSeparator {}
MenuItem { action: Cura.Actions.homeCamera; }

View File

@ -46,7 +46,7 @@ Item {
}
function sliceOrStopSlicing() {
if ([1, 5].indexOf(UM.Backend.state) != -1) {
if (backend != "undefined" && [1, 5].indexOf(UM.Backend.state) != -1) {
backend.forceSlice();
} else {
backend.stopSlicing();

View File

@ -24,7 +24,7 @@ Rectangle
property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
property int backendState: UM.Backend.state
property bool monitoringPrint: false
property bool monitoringPrint: UM.Controller.activeStage.stageId == "MonitorStage"
property variant printDuration: PrintInformation.currentPrintTime
property variant printMaterialLengths: PrintInformation.materialLengths
@ -263,7 +263,7 @@ Rectangle
{
id: controlItem
anchors.bottom: footerSeparator.top
anchors.top: headerSeparator.bottom
anchors.top: monitoringPrint ? base.top : headerSeparator.bottom
anchors.left: base.left
anchors.right: base.right
sourceComponent:
@ -282,7 +282,7 @@ Rectangle
Loader
{
anchors.bottom: footerSeparator.top
anchors.top: headerSeparator.bottom
anchors.top: monitoringPrint ? base.top : headerSeparator.bottom
anchors.left: base.left
anchors.right: base.right
source:

View File

@ -403,8 +403,6 @@ Item
}
}
//
// Infill
//
@ -568,16 +566,18 @@ Item
model: infillModel
anchors.fill: parent
property int activeIndex: {
function activeIndex () {
for (var i = 0; i < infillModel.count; i++) {
var density = parseInt(infillDensity.properties.value)
var steps = parseInt(infillSteps.properties.value)
var infillModelItem = infillModel.get(i)
if (density >= infillModelItem.percentageMin
if (infillModelItem != "undefined"
&& density >= infillModelItem.percentageMin
&& density <= infillModelItem.percentageMax
&& steps >= infillModelItem.stepsMin
&& steps <= infillModelItem.stepsMax){
&& steps <= infillModelItem.stepsMax
){
return i
}
}
@ -587,7 +587,7 @@ Item
Rectangle
{
anchors.fill: parent
visible: infillIconList.activeIndex == index
visible: infillIconList.activeIndex() == index
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("quality_slider_unavailable")

View File

@ -6,7 +6,7 @@ import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import UM 1.2 as UM
import UM 1.4 as UM
import Cura 1.0 as Cura
import "Menus"
@ -16,27 +16,10 @@ Rectangle
anchors.left: parent.left
anchors.right: parent.right
height: UM.Theme.getSize("sidebar_header").height
color: base.monitoringPrint ? UM.Theme.getColor("topbar_background_color_monitoring") : UM.Theme.getColor("topbar_background_color")
color: UM.Controller.activeStage.stageId == "MonitorStage" ? UM.Theme.getColor("topbar_background_color_monitoring") : UM.Theme.getColor("topbar_background_color")
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
property bool monitoringPrint: false
// outgoing signal
signal startMonitoringPrint()
signal stopMonitoringPrint()
// update monitoring status when event was triggered outside topbar
Component.onCompleted: {
startMonitoringPrint.connect(function () {
base.monitoringPrint = true
UM.Controller.disableModelRendering()
})
stopMonitoringPrint.connect(function () {
base.monitoringPrint = false
UM.Controller.enableModelRendering()
})
}
UM.I18nCatalog
{
@ -67,92 +50,30 @@ Rectangle
anchors.rightMargin: UM.Theme.getSize("default_margin").width
spacing: UM.Theme.getSize("default_margin").width
Button
// The topbar is dynamically filled with all available stages
Repeater
{
id: showSettings
height: UM.Theme.getSize("sidebar_header").height
text: catalog.i18nc("@title:tab", "Prepare")
id: stagesMenu
model: UM.StageModel{}
delegate: Button
{
text: model.name
checkable: true
checked: isChecked()
exclusiveGroup: sidebarHeaderBarGroup
style: UM.Theme.styles.topbar_header_tab
// We use a Qt.binding to re-bind the checkbox state after manually setting it
// https://stackoverflow.com/questions/38798450/qt-5-7-qml-why-are-my-checkbox-property-bindings-disappearing
onClicked: {
base.stopMonitoringPrint()
checked = Qt.binding(isChecked)
}
function isChecked () {
return !base.monitoringPrint
}
checked: model.active
exclusiveGroup: topbarMenuGroup
style: (model.stage.iconSource != "") ? UM.Theme.styles.topbar_header_tab_no_overlay : UM.Theme.styles.topbar_header_tab
height: UM.Theme.getSize("sidebar_header").height
onClicked: UM.Controller.setActiveStage(model.id)
iconSource: model.stage.iconSource
property color overlayColor: "transparent"
property string overlayIconSource: ""
}
Button
{
id: showMonitor
width: UM.Theme.getSize("topbar_button").width
height: UM.Theme.getSize("sidebar_header").height
text: catalog.i18nc("@title:tab", "Monitor")
checkable: true
checked: isChecked()
exclusiveGroup: sidebarHeaderBarGroup
style: UM.Theme.styles.topbar_header_tab_no_overlay
// We use a Qt.binding to re-bind the checkbox state after manually setting it
// https://stackoverflow.com/questions/38798450/qt-5-7-qml-why-are-my-checkbox-property-bindings-disappearing
onClicked: {
base.startMonitoringPrint()
checked = Qt.binding(isChecked)
}
function isChecked () {
return base.monitoringPrint
}
property string iconSource:
{
if (!printerConnected)
{
return UM.Theme.getIcon("tab_status_unknown");
}
else if (!printerAcceptsCommands)
{
return UM.Theme.getIcon("tab_status_unknown");
}
if (Cura.MachineManager.printerOutputDevices[0].printerState == "maintenance")
{
return UM.Theme.getIcon("tab_status_busy");
}
switch (Cura.MachineManager.printerOutputDevices[0].jobState)
{
case "printing":
case "pre_print":
case "pausing":
case "resuming":
return UM.Theme.getIcon("tab_status_busy");
case "wait_cleanup":
return UM.Theme.getIcon("tab_status_finished");
case "ready":
case "":
return UM.Theme.getIcon("tab_status_connected")
case "paused":
return UM.Theme.getIcon("tab_status_paused")
case "error":
return UM.Theme.getIcon("tab_status_stopped")
default:
return UM.Theme.getIcon("tab_status_unknown")
}
}
}
ExclusiveGroup { id: sidebarHeaderBarGroup }
ExclusiveGroup { id: topbarMenuGroup }
}
ToolButton
@ -225,12 +146,11 @@ Rectangle
{
id: viewOrientationControl
height: 30
spacing: 2
visible: UM.Controller.activeStage.stageId != "MonitorStage"
visible: !base.monitoringPrint
anchors {
anchors
{
verticalCenter: base.verticalCenter
right: viewModeButton.right
rightMargin: UM.Theme.getSize("default_margin").width + viewModeButton.width
@ -308,7 +228,7 @@ Rectangle
}
style: UM.Theme.styles.combobox
visible: !base.monitoringPrint
visible: UM.Controller.activeStage.stageId != "MonitorStage"
model: UM.ViewModel { }
textRole: "name"