mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-11-02 20:52:20 -07:00
Merge pull request #3 from Ultimaker/feature_manual_instances
Add support for manual instances
This commit is contained in:
commit
de44ac1df9
4 changed files with 203 additions and 12 deletions
|
|
@ -52,6 +52,22 @@ class DiscoverUM3Action(MachineAction):
|
||||||
else:
|
else:
|
||||||
self._network_plugin.startDiscovery()
|
self._network_plugin.startDiscovery()
|
||||||
|
|
||||||
|
@pyqtSlot(str, str)
|
||||||
|
def removeManualPrinter(self, key, address):
|
||||||
|
if not self._network_plugin:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._network_plugin.removeManualPrinter(key, address)
|
||||||
|
|
||||||
|
@pyqtSlot(str, str)
|
||||||
|
def setManualPrinter(self, key, address):
|
||||||
|
if key != "":
|
||||||
|
# This manual printer replaces a current manual printer
|
||||||
|
self._network_plugin.removeManualPrinter(key)
|
||||||
|
|
||||||
|
if address != "":
|
||||||
|
self._network_plugin.addManualPrinter(address)
|
||||||
|
|
||||||
def _onPrinterDiscoveryChanged(self, *args):
|
def _onPrinterDiscoveryChanged(self, *args):
|
||||||
self._last_zeroconf_event_time = time.time()
|
self._last_zeroconf_event_time = time.time()
|
||||||
self.printersChanged.emit()
|
self.printersChanged.emit()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ Cura.MachineAction
|
||||||
id: base
|
id: base
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
property var selectedPrinter: null
|
property var selectedPrinter: null
|
||||||
|
property bool completeProperties: true
|
||||||
property var connectingToPrinter: null
|
property var connectingToPrinter: null
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
|
|
@ -66,13 +67,45 @@ Cura.MachineAction
|
||||||
text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your Ultimaker 3, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your Ultimaker 3 from the list below:")
|
text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your Ultimaker 3, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your Ultimaker 3 from the list below:")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
spacing: UM.Theme.getSize("default_lining").width
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: addButton
|
||||||
|
text: catalog.i18nc("@action:button", "Add");
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
manualPrinterDialog.showDialog("", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: editButton
|
||||||
|
text: catalog.i18nc("@action:button", "Edit")
|
||||||
|
enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true"
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
manualPrinterDialog.showDialog(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: removeButton
|
||||||
|
text: catalog.i18nc("@action:button", "Remove")
|
||||||
|
enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true"
|
||||||
|
onClicked: manager.removeManualPrinter(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress)
|
||||||
|
}
|
||||||
|
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
id: rediscoverButton
|
id: rediscoverButton
|
||||||
text: catalog.i18nc("@title", "Refresh")
|
text: catalog.i18nc("@title", "Refresh")
|
||||||
onClicked: manager.restartDiscovery()
|
onClicked: manager.restartDiscovery()
|
||||||
anchors.right: parent.right
|
}
|
||||||
anchors.rightMargin: parent.width * 0.5
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row
|
Row
|
||||||
|
|
@ -118,7 +151,12 @@ Cura.MachineAction
|
||||||
}
|
}
|
||||||
width: parent.width
|
width: parent.width
|
||||||
currentIndex: -1
|
currentIndex: -1
|
||||||
onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex]
|
onCurrentIndexChanged:
|
||||||
|
{
|
||||||
|
base.selectedPrinter = listview.model[currentIndex];
|
||||||
|
// Only allow connecting if the printer has responded to API query since the last refresh
|
||||||
|
base.completeProperties = base.selectedPrinter != null && base.selectedPrinter.getProperty("incomplete") != "true";
|
||||||
|
}
|
||||||
Component.onCompleted: manager.startDiscovery()
|
Component.onCompleted: manager.startDiscovery()
|
||||||
delegate: Rectangle
|
delegate: Rectangle
|
||||||
{
|
{
|
||||||
|
|
@ -220,10 +258,68 @@ Cura.MachineAction
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@action:button", "Connect")
|
text: catalog.i18nc("@action:button", "Connect")
|
||||||
enabled: base.selectedPrinter ? true : false
|
enabled: (base.selectedPrinter && base.completeProperties) ? true : false
|
||||||
onClicked: connectToPrinter()
|
onClicked: connectToPrinter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UM.Dialog
|
||||||
|
{
|
||||||
|
id: manualPrinterDialog
|
||||||
|
property string printerKey
|
||||||
|
property alias addressText: addressField.text
|
||||||
|
|
||||||
|
title: catalog.i18nc("@label", "IP Address")
|
||||||
|
|
||||||
|
minimumWidth: 400 * Screen.devicePixelRatio
|
||||||
|
minimumHeight: 120 * Screen.devicePixelRatio
|
||||||
|
width: minimumWidth
|
||||||
|
height: minimumHeight
|
||||||
|
|
||||||
|
signal showDialog(string key, string address)
|
||||||
|
onShowDialog:
|
||||||
|
{
|
||||||
|
printerKey = key;
|
||||||
|
|
||||||
|
addressText = address;
|
||||||
|
addressField.selectAll();
|
||||||
|
addressField.focus = true;
|
||||||
|
|
||||||
|
manualPrinterDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted:
|
||||||
|
{
|
||||||
|
manager.setManualPrinter(printerKey, addressText)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: addressField
|
||||||
|
width: parent.width
|
||||||
|
maximumLength: 40
|
||||||
|
validator: RegExpValidator
|
||||||
|
{
|
||||||
|
regExp: /[a-zA-Z0-9\.\-\_]*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rightButtons: [
|
||||||
|
Button {
|
||||||
|
text: catalog.i18nc("@action:button","Cancel")
|
||||||
|
onClicked: manualPrinterDialog.reject()
|
||||||
|
},
|
||||||
|
Button {
|
||||||
|
text: catalog.i18nc("@action:button", "Ok")
|
||||||
|
onClicked: manualPrinterDialog.accept()
|
||||||
|
enabled: manualPrinterDialog.addressText.trim() != ""
|
||||||
|
isDefault: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -36,11 +36,12 @@ class AuthState(IntEnum):
|
||||||
## Network connected (wifi / lan) printer that uses the Ultimaker API
|
## Network connected (wifi / lan) printer that uses the Ultimaker API
|
||||||
@signalemitter
|
@signalemitter
|
||||||
class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
||||||
def __init__(self, key, address, properties):
|
def __init__(self, key, address, properties, api_prefix):
|
||||||
super().__init__(key)
|
super().__init__(key)
|
||||||
self._address = address
|
self._address = address
|
||||||
self._key = key
|
self._key = key
|
||||||
self._properties = properties # Properties dict as provided by zero conf
|
self._properties = properties # Properties dict as provided by zero conf
|
||||||
|
self._api_prefix = api_prefix
|
||||||
|
|
||||||
self._gcode = None
|
self._gcode = None
|
||||||
self._print_finished = True # _print_finsihed == False means we're halfway in a print
|
self._print_finished = True # _print_finsihed == False means we're halfway in a print
|
||||||
|
|
@ -94,8 +95,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self._material_ids = [""] * self._num_extruders
|
self._material_ids = [""] * self._num_extruders
|
||||||
self._hotend_ids = [""] * self._num_extruders
|
self._hotend_ids = [""] * self._num_extruders
|
||||||
|
|
||||||
self._api_version = "1"
|
|
||||||
self._api_prefix = "/api/v" + self._api_version + "/"
|
|
||||||
self.setPriority(2) # Make sure the output device gets selected above local file output
|
self.setPriority(2) # Make sure the output device gets selected above local file output
|
||||||
self.setName(key)
|
self.setName(key)
|
||||||
self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print over network"))
|
self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print over network"))
|
||||||
|
|
@ -187,6 +186,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
||||||
def getProperties(self):
|
def getProperties(self):
|
||||||
return self._properties
|
return self._properties
|
||||||
|
|
||||||
|
@pyqtSlot(str, result = str)
|
||||||
|
def getProperty(self, key):
|
||||||
|
key = key.encode("utf-8")
|
||||||
|
if key in self._properties:
|
||||||
|
return self._properties.get(key, b"").decode("utf-8")
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
## Get the unique key of this machine
|
## Get the unique key of this machine
|
||||||
# \return key String containing the key of the machine.
|
# \return key String containing the key of the machine.
|
||||||
@pyqtSlot(result = str)
|
@pyqtSlot(result = str)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,13 @@ from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Signal import Signal, signalemitter
|
from UM.Signal import Signal, signalemitter
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
from UM.Preferences import Preferences
|
||||||
|
|
||||||
|
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, QNetworkReply
|
||||||
|
from PyQt5.QtCore import QUrl
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
|
## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
|
||||||
# Zero-Conf is used to detect printers, which are saved in a dict.
|
# Zero-Conf is used to detect printers, which are saved in a dict.
|
||||||
|
|
@ -19,6 +24,12 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
|
||||||
self._browser = None
|
self._browser = None
|
||||||
self._printers = {}
|
self._printers = {}
|
||||||
|
|
||||||
|
self._api_version = "1"
|
||||||
|
self._api_prefix = "/api/v" + self._api_version + "/"
|
||||||
|
|
||||||
|
self._network_manager = QNetworkAccessManager()
|
||||||
|
self._network_manager.finished.connect(self._onNetworkRequestFinished)
|
||||||
|
|
||||||
# List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces
|
# List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces
|
||||||
# authentication requests.
|
# authentication requests.
|
||||||
self._old_printers = []
|
self._old_printers = []
|
||||||
|
|
@ -28,6 +39,11 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
|
||||||
self.removePrinterSignal.connect(self.removePrinter)
|
self.removePrinterSignal.connect(self.removePrinter)
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections)
|
Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections)
|
||||||
|
|
||||||
|
# Get list of manual printers from preferences
|
||||||
|
self._preferences = Preferences.getInstance()
|
||||||
|
self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames
|
||||||
|
self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",")
|
||||||
|
|
||||||
addPrinterSignal = Signal()
|
addPrinterSignal = Signal()
|
||||||
removePrinterSignal = Signal()
|
removePrinterSignal = Signal()
|
||||||
printerListChanged = Signal()
|
printerListChanged = Signal()
|
||||||
|
|
@ -49,6 +65,62 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
|
||||||
self._zero_conf = Zeroconf()
|
self._zero_conf = Zeroconf()
|
||||||
self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged])
|
self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged])
|
||||||
|
|
||||||
|
# Look for manual instances from preference
|
||||||
|
for address in self._manual_instances:
|
||||||
|
if address:
|
||||||
|
self.addManualPrinter(address)
|
||||||
|
|
||||||
|
def addManualPrinter(self, address):
|
||||||
|
if address not in self._manual_instances:
|
||||||
|
self._manual_instances.append(address)
|
||||||
|
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
|
||||||
|
|
||||||
|
name = address
|
||||||
|
instance_name = "manual:%s" % address
|
||||||
|
properties = { b"name": name.encode("utf-8"), b"manual": b"true", b"incomplete": b"true" }
|
||||||
|
|
||||||
|
if instance_name not in self._printers:
|
||||||
|
# Add a preliminary printer instance
|
||||||
|
self.addPrinter(instance_name, address, properties)
|
||||||
|
|
||||||
|
self.checkManualPrinter(address)
|
||||||
|
|
||||||
|
def removeManualPrinter(self, key, address = None):
|
||||||
|
if key in self._printers:
|
||||||
|
if not address:
|
||||||
|
address = self._printers[key].ipAddress
|
||||||
|
self.removePrinter(key)
|
||||||
|
|
||||||
|
if address in self._manual_instances:
|
||||||
|
self._manual_instances.remove(address)
|
||||||
|
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
|
||||||
|
|
||||||
|
def checkManualPrinter(self, address):
|
||||||
|
# Check if a printer exists at this address
|
||||||
|
# If a printer responds, it will replace the preliminary printer created above
|
||||||
|
url = QUrl("http://" + address + self._api_prefix + "system")
|
||||||
|
name_request = QNetworkRequest(url)
|
||||||
|
self._network_manager.get(name_request)
|
||||||
|
|
||||||
|
## Handler for all requests that have finished.
|
||||||
|
def _onNetworkRequestFinished(self, reply):
|
||||||
|
reply_url = reply.url().toString()
|
||||||
|
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||||
|
|
||||||
|
if reply.operation() == QNetworkAccessManager.GetOperation:
|
||||||
|
if "system" in reply_url: # Name returned from printer.
|
||||||
|
if status_code == 200:
|
||||||
|
system_info = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||||
|
address = reply.url().host()
|
||||||
|
name = ("%s (%s)" % (system_info["name"], address))
|
||||||
|
|
||||||
|
instance_name = "manual:%s" % address
|
||||||
|
properties = { b"name": name.encode("utf-8"), b"firmware_version": system_info["firmware"].encode("utf-8"), b"manual": b"true" }
|
||||||
|
if instance_name in self._printers:
|
||||||
|
# Only replace the printer if it is still in the list of (manual) printers
|
||||||
|
self.removePrinter(instance_name)
|
||||||
|
self.addPrinter(instance_name, address, properties)
|
||||||
|
|
||||||
## Stop looking for devices on network.
|
## Stop looking for devices on network.
|
||||||
def stop(self):
|
def stop(self):
|
||||||
if self._zero_conf is not None:
|
if self._zero_conf is not None:
|
||||||
|
|
@ -72,7 +144,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
|
||||||
|
|
||||||
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
|
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
|
||||||
def addPrinter(self, name, address, properties):
|
def addPrinter(self, name, address, properties):
|
||||||
printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties)
|
printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties, self._api_prefix)
|
||||||
self._printers[printer.getKey()] = printer
|
self._printers[printer.getKey()] = printer
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"):
|
if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue