mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-07 23:17:32 -06:00
Merge branch 'master' of https://github.com/Ultimaker/Cura
This commit is contained in:
commit
d683a6b77b
5 changed files with 67 additions and 50 deletions
|
@ -79,6 +79,7 @@ class CuraApplication(QtApplication):
|
||||||
self._platform_activity = False
|
self._platform_activity = False
|
||||||
|
|
||||||
self.activeMachineChanged.connect(self._onActiveMachineChanged)
|
self.activeMachineChanged.connect(self._onActiveMachineChanged)
|
||||||
|
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
|
||||||
|
|
||||||
Preferences.getInstance().addPreference("cura/active_machine", "")
|
Preferences.getInstance().addPreference("cura/active_machine", "")
|
||||||
Preferences.getInstance().addPreference("cura/active_mode", "simple")
|
Preferences.getInstance().addPreference("cura/active_mode", "simple")
|
||||||
|
@ -214,24 +215,15 @@ class CuraApplication(QtApplication):
|
||||||
def getPlatformActivity(self):
|
def getPlatformActivity(self):
|
||||||
return self._platform_activity
|
return self._platform_activity
|
||||||
|
|
||||||
@pyqtSlot(bool)
|
def updatePlatformActivity(self, node = None):
|
||||||
def setPlatformActivity(self, activity):
|
count = 0
|
||||||
##Sets the _platform_activity variable on true or false depending on whether there is a mesh on the platform
|
|
||||||
if activity == True:
|
|
||||||
self._platform_activity = activity
|
|
||||||
elif activity == False:
|
|
||||||
nodes = []
|
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode or not node.getMeshData():
|
if type(node) is not SceneNode or not node.getMeshData():
|
||||||
continue
|
continue
|
||||||
nodes.append(node)
|
|
||||||
i = 0
|
count += 1
|
||||||
for node in nodes:
|
|
||||||
if not node.getMeshData():
|
self._platform_activity = True if count > 0 else False
|
||||||
continue
|
|
||||||
i += 1
|
|
||||||
if i <= 1: ## i == 0 when the meshes are removed using the deleteAll function; i == 1 when the last remaining mesh is removed using the deleteObject function
|
|
||||||
self._platform_activity = activity
|
|
||||||
self.activityChanged.emit()
|
self.activityChanged.emit()
|
||||||
|
|
||||||
## Remove an object from the scene
|
## Remove an object from the scene
|
||||||
|
@ -252,7 +244,6 @@ class CuraApplication(QtApplication):
|
||||||
group_node = group_node.getParent()
|
group_node = group_node.getParent()
|
||||||
op = RemoveSceneNodeOperation(group_node)
|
op = RemoveSceneNodeOperation(group_node)
|
||||||
op.push()
|
op.push()
|
||||||
self.setPlatformActivity(False)
|
|
||||||
|
|
||||||
## Create a number of copies of existing object.
|
## Create a number of copies of existing object.
|
||||||
@pyqtSlot("quint64", int)
|
@pyqtSlot("quint64", int)
|
||||||
|
@ -300,7 +291,6 @@ class CuraApplication(QtApplication):
|
||||||
op.addOperation(RemoveSceneNodeOperation(node))
|
op.addOperation(RemoveSceneNodeOperation(node))
|
||||||
|
|
||||||
op.push()
|
op.push()
|
||||||
self.setPlatformActivity(False)
|
|
||||||
|
|
||||||
## Reset all translation on nodes with mesh data.
|
## Reset all translation on nodes with mesh data.
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
|
|
|
@ -15,9 +15,9 @@ import os
|
||||||
import struct
|
import struct
|
||||||
import math
|
import math
|
||||||
from os import listdir
|
from os import listdir
|
||||||
import untangle
|
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes!
|
## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes!
|
||||||
class ThreeMFReader(MeshReader):
|
class ThreeMFReader(MeshReader):
|
||||||
|
@ -25,6 +25,11 @@ class ThreeMFReader(MeshReader):
|
||||||
super(ThreeMFReader, self).__init__()
|
super(ThreeMFReader, self).__init__()
|
||||||
self._supported_extension = ".3mf"
|
self._supported_extension = ".3mf"
|
||||||
|
|
||||||
|
self._namespaces = {
|
||||||
|
"3mf": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02",
|
||||||
|
"cura": "http://software.ultimaker.com/xml/cura/3mf/2015/10"
|
||||||
|
}
|
||||||
|
|
||||||
def read(self, file_name):
|
def read(self, file_name):
|
||||||
result = None
|
result = None
|
||||||
extension = os.path.splitext(file_name)[1]
|
extension = os.path.splitext(file_name)[1]
|
||||||
|
@ -33,32 +38,39 @@ class ThreeMFReader(MeshReader):
|
||||||
# The base object of 3mf is a zipped archive.
|
# The base object of 3mf is a zipped archive.
|
||||||
archive = zipfile.ZipFile(file_name, 'r')
|
archive = zipfile.ZipFile(file_name, 'r')
|
||||||
try:
|
try:
|
||||||
# The model is always stored in this place.
|
root = ET.parse(archive.open("3D/3dmodel.model"))
|
||||||
root = untangle.parse(archive.read("3D/3dmodel.model").decode("utf-8"))
|
|
||||||
for object in root.model.resources.object: # There can be multiple objects, try to load all of them.
|
# There can be multiple objects, try to load all of them.
|
||||||
|
objects = root.findall("./3mf:resources/3mf:object", self._namespaces)
|
||||||
|
for object in objects:
|
||||||
mesh = MeshData()
|
mesh = MeshData()
|
||||||
node = SceneNode()
|
node = SceneNode()
|
||||||
vertex_list = []
|
vertex_list = []
|
||||||
for vertex in object.mesh.vertices.vertex:
|
#for vertex in object.mesh.vertices.vertex:
|
||||||
vertex_list.append([vertex['x'],vertex['y'],vertex['z']])
|
for vertex in object.findall(".//3mf:vertex", self._namespaces):
|
||||||
|
vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
|
||||||
|
|
||||||
mesh.reserveFaceCount(len(object.mesh.triangles.triangle))
|
triangles = object.findall(".//3mf:triangle", self._namespaces)
|
||||||
|
|
||||||
for triangle in object.mesh.triangles.triangle:
|
mesh.reserveFaceCount(len(triangles))
|
||||||
v1 = int(triangle["v1"])
|
|
||||||
v2 = int(triangle["v2"])
|
#for triangle in object.mesh.triangles.triangle:
|
||||||
v3 = int(triangle["v3"])
|
for triangle in triangles:
|
||||||
|
v1 = int(triangle.get("v1"))
|
||||||
|
v2 = int(triangle.get("v2"))
|
||||||
|
v3 = int(triangle.get("v3"))
|
||||||
mesh.addFace(vertex_list[v1][0],vertex_list[v1][1],vertex_list[v1][2],vertex_list[v2][0],vertex_list[v2][1],vertex_list[v2][2],vertex_list[v3][0],vertex_list[v3][1],vertex_list[v3][2])
|
mesh.addFace(vertex_list[v1][0],vertex_list[v1][1],vertex_list[v1][2],vertex_list[v2][0],vertex_list[v2][1],vertex_list[v2][2],vertex_list[v3][0],vertex_list[v3][1],vertex_list[v3][2])
|
||||||
#TODO: We currently do not check for normals and simply recalculate them.
|
#TODO: We currently do not check for normals and simply recalculate them.
|
||||||
mesh.calculateNormals()
|
mesh.calculateNormals()
|
||||||
node.setMeshData(mesh)
|
node.setMeshData(mesh)
|
||||||
node.setSelectable(True)
|
node.setSelectable(True)
|
||||||
|
|
||||||
# Magical python comprehension; looks for the matching transformation
|
transformation = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(object.get("id")), self._namespaces)
|
||||||
transformation = next((x for x in root.model.build.item if x["objectid"] == object["id"]), None)
|
if transformation:
|
||||||
|
transformation = transformation[0]
|
||||||
|
|
||||||
if transformation["transform"]:
|
if transformation.get("transform"):
|
||||||
splitted_transformation = transformation["transform"].split()
|
splitted_transformation = transformation.get("transform").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
|
||||||
|
@ -102,7 +114,7 @@ class ThreeMFReader(MeshReader):
|
||||||
|
|
||||||
#If there is more then one object, group them.
|
#If there is more then one object, group them.
|
||||||
try:
|
try:
|
||||||
if len(root.model.resources.object) > 1:
|
if len(objects) > 1:
|
||||||
group_decorator = GroupDecorator()
|
group_decorator = GroupDecorator()
|
||||||
result.addDecorator(group_decorator)
|
result.addDecorator(group_decorator)
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -17,8 +17,8 @@ class RemovableDriveOutputDevice(OutputDevice):
|
||||||
super().__init__(device_id)
|
super().__init__(device_id)
|
||||||
|
|
||||||
self.setName(device_name)
|
self.setName(device_name)
|
||||||
self.setShortDescription(catalog.i18nc("", "Save to Removable Drive"))
|
self.setShortDescription(catalog.i18nc("@action:button", "Save to Removable Drive"))
|
||||||
self.setDescription(catalog.i18nc("", "Save to Removable Drive {0}").format(device_name))
|
self.setDescription(catalog.i18nc("@info:tooltip", "Save to Removable Drive {0}").format(device_name))
|
||||||
self.setIconName("save_sd")
|
self.setIconName("save_sd")
|
||||||
self.setPriority(1)
|
self.setPriority(1)
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class RemovableDriveOutputDevice(OutputDevice):
|
||||||
job.progress.connect(self._onProgress)
|
job.progress.connect(self._onProgress)
|
||||||
job.finished.connect(self._onFinished)
|
job.finished.connect(self._onFinished)
|
||||||
|
|
||||||
message = Message(catalog.i18nc("", "Saving to Removable Drive {0}").format(self.getName()), 0, False, -1)
|
message = Message(catalog.i18nc("@info:status", "Saving to Removable Drive <filename>{0}</filename>").format(self.getName()), 0, False, -1)
|
||||||
message.show()
|
message.show()
|
||||||
|
|
||||||
job._message = message
|
job._message = message
|
||||||
|
|
|
@ -63,7 +63,7 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter):
|
||||||
self._listen_thread.daemon = True
|
self._listen_thread.daemon = True
|
||||||
|
|
||||||
self._update_firmware_thread = threading.Thread(target= self._updateFirmware)
|
self._update_firmware_thread = threading.Thread(target= self._updateFirmware)
|
||||||
self._update_firmware_thread.demon = True
|
self._update_firmware_thread.deamon = True
|
||||||
|
|
||||||
self._heatup_wait_start_time = time.time()
|
self._heatup_wait_start_time = time.time()
|
||||||
|
|
||||||
|
@ -222,6 +222,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter):
|
||||||
|
|
||||||
self.setProgress(100, 100)
|
self.setProgress(100, 100)
|
||||||
|
|
||||||
|
self.firmwareUpdateComplete.emit()
|
||||||
|
|
||||||
## Upload new firmware to machine
|
## Upload new firmware to machine
|
||||||
# \param filename full path of firmware file to be uploaded
|
# \param filename full path of firmware file to be uploaded
|
||||||
def updateFirmware(self, file_name):
|
def updateFirmware(self, file_name):
|
||||||
|
@ -277,9 +279,9 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter):
|
||||||
if line is None:
|
if line is None:
|
||||||
self.setIsConnected(False) # Something went wrong with reading, could be that close was called.
|
self.setIsConnected(False) # Something went wrong with reading, could be that close was called.
|
||||||
return
|
return
|
||||||
|
|
||||||
if b"T:" in line:
|
if b"T:" in line:
|
||||||
self._serial.timeout = 0.5
|
self._serial.timeout = 0.5
|
||||||
self._sendCommand("M105")
|
|
||||||
sucesfull_responses += 1
|
sucesfull_responses += 1
|
||||||
if sucesfull_responses >= self._required_responses_auto_baud:
|
if sucesfull_responses >= self._required_responses_auto_baud:
|
||||||
self._serial.timeout = 2 #Reset serial timeout
|
self._serial.timeout = 2 #Reset serial timeout
|
||||||
|
@ -287,6 +289,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter):
|
||||||
Logger.log("i", "Established printer connection on port %s" % self._serial_port)
|
Logger.log("i", "Established printer connection on port %s" % self._serial_port)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._sendCommand("M105") # Send M105 as long as we are listening, otherwise we end up in an undefined state
|
||||||
|
|
||||||
Logger.log("e", "Baud rate detection for %s failed", self._serial_port)
|
Logger.log("e", "Baud rate detection for %s failed", self._serial_port)
|
||||||
self.close() # Unable to connect, wrap up.
|
self.close() # Unable to connect, wrap up.
|
||||||
self.setIsConnected(False)
|
self.setIsConnected(False)
|
||||||
|
@ -319,6 +323,9 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass # This should work, but it does fail sometimes for some reason
|
pass # This should work, but it does fail sometimes for some reason
|
||||||
|
|
||||||
|
self._connect_thread = threading.Thread(target=self._connect)
|
||||||
|
self._connect_thread.daemon = True
|
||||||
|
|
||||||
if self._serial is not None:
|
if self._serial is not None:
|
||||||
self.setIsConnected(False)
|
self.setIsConnected(False)
|
||||||
try:
|
try:
|
||||||
|
@ -327,6 +334,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter):
|
||||||
pass
|
pass
|
||||||
self._serial.close()
|
self._serial.close()
|
||||||
|
|
||||||
|
self._listen_thread = threading.Thread(target=self._listen)
|
||||||
|
self._listen_thread.daemon = True
|
||||||
self._serial = None
|
self._serial = None
|
||||||
|
|
||||||
def isConnected(self):
|
def isConnected(self):
|
||||||
|
@ -579,3 +588,10 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter):
|
||||||
def _getBaudrateList(self):
|
def _getBaudrateList(self):
|
||||||
ret = [250000, 230400, 115200, 57600, 38400, 19200, 9600]
|
ret = [250000, 230400, 115200, 57600, 38400, 19200, 9600]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def _onFirmwareUpdateComplete(self):
|
||||||
|
self._update_firmware_thread.join()
|
||||||
|
self._update_firmware_thread = threading.Thread(target= self._updateFirmware)
|
||||||
|
self._update_firmware_thread.deamon = True
|
||||||
|
|
||||||
|
self.connect()
|
||||||
|
|
|
@ -481,7 +481,6 @@ UM.MainWindow {
|
||||||
onAccepted:
|
onAccepted:
|
||||||
{
|
{
|
||||||
UM.MeshFileHandler.readLocalFile(fileUrl)
|
UM.MeshFileHandler.readLocalFile(fileUrl)
|
||||||
Printer.setPlatformActivity(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue