diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 9d2ee2c166..ee86f720d8 100644 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -24,7 +24,7 @@ import xml.etree.ElementTree as ET class ThreeMFReader(MeshReader): def __init__(self): super(ThreeMFReader, self).__init__() - self._supported_extension = ".3mf" + self._supported_extensions = [".3mf"] self._namespaces = { "3mf": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02", @@ -33,102 +33,102 @@ class ThreeMFReader(MeshReader): def read(self, file_name): result = None - extension = os.path.splitext(file_name)[1] - if extension.lower() == self._supported_extension: - result = SceneNode() - # The base object of 3mf is a zipped archive. - archive = zipfile.ZipFile(file_name, "r") - try: - root = ET.parse(archive.open("3D/3dmodel.model")) - # There can be multiple objects, try to load all of them. - objects = root.findall("./3mf:resources/3mf:object", self._namespaces) - if len(objects) == 0: - Logger.log("w", "No objects found in 3MF file %s, either the file is corrupt or you are using an outdated format", file_name) - return None + result = SceneNode() + # The base object of 3mf is a zipped archive. + archive = zipfile.ZipFile(file_name, "r") + try: + root = ET.parse(archive.open("3D/3dmodel.model")) - for object in objects: - mesh = MeshData() - node = SceneNode() - vertex_list = [] - #for vertex in object.mesh.vertices.vertex: - for vertex in object.findall(".//3mf:vertex", self._namespaces): - vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")]) - Job.yieldThread() - - triangles = object.findall(".//3mf:triangle", self._namespaces) - - mesh.reserveFaceCount(len(triangles)) - - #for triangle in object.mesh.triangles.triangle: - 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]) - Job.yieldThread() - - #TODO: We currently do not check for normals and simply recalculate them. - mesh.calculateNormals() - node.setMeshData(mesh) - node.setSelectable(True) - - transformation = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(object.get("id")), self._namespaces) - if transformation: - transformation = transformation[0] - - if transformation.get("transform"): - splitted_transformation = transformation.get("transform").split() - ## Transformation is saved as: - ## M00 M01 M02 0.0 - ## M10 M11 M12 0.0 - ## M20 M21 M22 0.0 - ## M30 M31 M32 1.0 - ## We switch the row & cols as that is how everyone else uses matrices! - temp_mat = Matrix() - # Rotation & Scale - temp_mat._data[0,0] = splitted_transformation[0] - temp_mat._data[1,0] = splitted_transformation[1] - temp_mat._data[2,0] = splitted_transformation[2] - temp_mat._data[0,1] = splitted_transformation[3] - temp_mat._data[1,1] = splitted_transformation[4] - temp_mat._data[2,1] = splitted_transformation[5] - temp_mat._data[0,2] = splitted_transformation[6] - temp_mat._data[1,2] = splitted_transformation[7] - temp_mat._data[2,2] = splitted_transformation[8] - - # Translation - temp_mat._data[0,3] = splitted_transformation[9] - temp_mat._data[1,3] = splitted_transformation[10] - temp_mat._data[2,3] = splitted_transformation[11] - - node.setPosition(Vector(temp_mat.at(0,3), temp_mat.at(1,3), temp_mat.at(2,3))) - - temp_quaternion = Quaternion() - temp_quaternion.setByMatrix(temp_mat) - node.setOrientation(temp_quaternion) - - # Magical scale extraction - scale = temp_mat.getTransposed().multiply(temp_mat) - scale_x = math.sqrt(scale.at(0,0)) - scale_y = math.sqrt(scale.at(1,1)) - scale_z = math.sqrt(scale.at(2,2)) - node.setScale(Vector(scale_x,scale_y,scale_z)) - - # We use a different coordinate frame, so rotate. - #rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0)) - #node.rotate(rotation) - result.addChild(node) + # There can be multiple objects, try to load all of them. + objects = root.findall("./3mf:resources/3mf:object", self._namespaces) + if len(objects) == 0: + Logger.log("w", "No objects found in 3MF file %s, either the file is corrupt or you are using an outdated format", file_name) + return None + for object in objects: + mesh = MeshData() + node = SceneNode() + vertex_list = [] + #for vertex in object.mesh.vertices.vertex: + for vertex in object.findall(".//3mf:vertex", self._namespaces): + vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")]) Job.yieldThread() - #If there is more then one object, group them. - try: - if len(objects) > 1: - group_decorator = GroupDecorator() - result.addDecorator(group_decorator) - except: - pass - except Exception as e: - Logger.log("e" ,"exception occured in 3mf reader: %s" , e) + triangles = object.findall(".//3mf:triangle", self._namespaces) + + mesh.reserveFaceCount(len(triangles)) + + #for triangle in object.mesh.triangles.triangle: + 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]) + Job.yieldThread() + + #TODO: We currently do not check for normals and simply recalculate them. + mesh.calculateNormals() + node.setMeshData(mesh) + node.setSelectable(True) + + transformation = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(object.get("id")), self._namespaces) + if transformation: + transformation = transformation[0] + + if transformation.get("transform"): + splitted_transformation = transformation.get("transform").split() + ## Transformation is saved as: + ## M00 M01 M02 0.0 + ## M10 M11 M12 0.0 + ## M20 M21 M22 0.0 + ## M30 M31 M32 1.0 + ## We switch the row & cols as that is how everyone else uses matrices! + temp_mat = Matrix() + # Rotation & Scale + temp_mat._data[0,0] = splitted_transformation[0] + temp_mat._data[1,0] = splitted_transformation[1] + temp_mat._data[2,0] = splitted_transformation[2] + temp_mat._data[0,1] = splitted_transformation[3] + temp_mat._data[1,1] = splitted_transformation[4] + temp_mat._data[2,1] = splitted_transformation[5] + temp_mat._data[0,2] = splitted_transformation[6] + temp_mat._data[1,2] = splitted_transformation[7] + temp_mat._data[2,2] = splitted_transformation[8] + + # Translation + temp_mat._data[0,3] = splitted_transformation[9] + temp_mat._data[1,3] = splitted_transformation[10] + temp_mat._data[2,3] = splitted_transformation[11] + + node.setPosition(Vector(temp_mat.at(0,3), temp_mat.at(1,3), temp_mat.at(2,3))) + + temp_quaternion = Quaternion() + temp_quaternion.setByMatrix(temp_mat) + node.setOrientation(temp_quaternion) + + # Magical scale extraction + scale = temp_mat.getTransposed().multiply(temp_mat) + scale_x = math.sqrt(scale.at(0,0)) + scale_y = math.sqrt(scale.at(1,1)) + scale_z = math.sqrt(scale.at(2,2)) + node.setScale(Vector(scale_x,scale_y,scale_z)) + + # We use a different coordinate frame, so rotate. + #rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0)) + #node.rotate(rotation) + result.addChild(node) + + Job.yieldThread() + + #If there is more then one object, group them. + try: + if len(objects) > 1: + group_decorator = GroupDecorator() + result.addDecorator(group_decorator) + except: + pass + except Exception as e: + Logger.log("e" ,"exception occured in 3mf reader: %s" , e) + return result diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index c8946e9349..efc98da946 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -31,36 +31,36 @@ UM.Dialog columns: 2 Text { - text: catalog.i18nc("@action:label","Size") + text: catalog.i18nc("@action:label","Size (mm)") Layout.fillWidth:true } TextField { id: size focus: true validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: 1; top: 500;} - text: qsTr("120") + text: "120" onTextChanged: { manager.onSizeChanged(text) } } Text { - text: catalog.i18nc("@action:label","Base Height") + text: catalog.i18nc("@action:label","Base Height (mm)") Layout.fillWidth:true } TextField { id: base_height validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: 0; top: 500;} - text: qsTr("2") + text: "2" onTextChanged: { manager.onBaseHeightChanged(text) } } Text { - text: catalog.i18nc("@action:label","Peak Height") + text: catalog.i18nc("@action:label","Peak Height (mm)") Layout.fillWidth:true } TextField { id: peak_height validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: 0; top: 500;} - text: qsTr("12") + text: "12" onTextChanged: { manager.onPeakHeightChanged(text) } } @@ -68,11 +68,19 @@ UM.Dialog text: catalog.i18nc("@action:label","Smoothing") Layout.fillWidth:true } - TextField { - id: smoothing - validator: IntValidator {bottom: 0; top: 100;} - text: qsTr("1") - onTextChanged: { manager.onSmoothingChanged(text) } + Rectangle { + width: 100 + height: 20 + color: "transparent" + + Slider { + id: smoothing + maximumValue: 100.0 + stepSize: 1.0 + value: 1 + width: 100 + onValueChanged: { manager.onSmoothingChanged(value) } + } } UM.I18nCatalog{id: catalog; name:"ultimaker"} diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index be438e6e1b..e39fd5465e 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -1,5 +1,4 @@ # Copyright (c) 2015 Ultimaker B.V. -# Copyright (c) 2013 David Braam # Cura is released under the terms of the AGPLv3 or higher. import os @@ -21,28 +20,17 @@ class ImageReader(MeshReader): super(ImageReader, self).__init__() self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"] self._ui = ImageReaderUI(self) - self._wait = False - self._canceled = False + + def preRead(self, file_name): + self._ui.showConfigUI() + self._ui.waitForUIToClose() + + if self._ui.getCancelled(): + return MeshReader.PreReadResult.cancelled + return MeshReader.PreReadResult.accepted def read(self, file_name): - extension = os.path.splitext(file_name)[1] - if extension.lower() in self._supported_extensions: - self._ui.showConfigUI() - self._wait = True - self._canceled = True - - while self._wait: - pass - # this causes the config window to not repaint... - # Job.yieldThread() - - result = None - if not self._canceled: - result = self._generateSceneNode(file_name, self._ui.size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512) - - return result - - return None + return self._generateSceneNode(file_name, self._ui.size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512) def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size): mesh = None @@ -99,15 +87,20 @@ class ImageReader(MeshReader): Job.yieldThread() for i in range(0, blur_iterations): - ii = blur_iterations-i - copy = numpy.copy(height_data) + copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode='edge') - height_data += numpy.roll(copy, ii, axis=0) - height_data += numpy.roll(copy, -ii, axis=0) - height_data += numpy.roll(copy, ii, axis=1) - height_data += numpy.roll(copy, -ii, axis=1) + height_data += copy[1:-1, 2:] + height_data += copy[1:-1, :-2] + height_data += copy[2:, 1:-1] + height_data += copy[:-2, 1:-1] + + height_data += copy[2:, 2:] + height_data += copy[:-2, 2:] + height_data += copy[2:, :-2] + height_data += copy[:-2, :-2] + + height_data /= 9 - height_data /= 5 Job.yieldThread() height_data *= scale_vector.y @@ -118,8 +111,9 @@ class ImageReader(MeshReader): mesh.reserveFaceCount(total_face_count) - # initialize to texel space vertex offsets - heightmap_vertices = numpy.zeros(((width - 1) * (height - 1), 6, 3), dtype=numpy.float32) + # initialize to texel space vertex offsets. + # 6 is for 6 vertices for each texel quad. + heightmap_vertices = numpy.zeros((width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32) heightmap_vertices = heightmap_vertices + numpy.array([[ [0, base_height, 0], [0, base_height, texel_height], diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index b317404d7f..e14bd7acda 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -1,8 +1,8 @@ # Copyright (c) 2015 Ultimaker B.V. -# Copyright (c) 2013 David Braam # Cura is released under the terms of the AGPLv3 or higher. import os +import threading from PyQt5.QtCore import Qt, QUrl, pyqtSignal, pyqtSlot, QObject from PyQt5.QtQml import QQmlComponent, QQmlContext @@ -23,12 +23,27 @@ class ImageReaderUI(QObject): self.image_reader = image_reader self._ui_view = None self.show_config_ui_trigger.connect(self._actualShowConfigUI) - self.size = 120 + + # There are corresponding values for these fields in ConfigUI.qml. + # If you change the values here, consider updating ConfigUI.qml as well. + self.size = 120 self.base_height = 2 self.peak_height = 12 self.smoothing = 1 + self._ui_lock = threading.Lock() + self._cancelled = False + + def getCancelled(self): + return self._cancelled + + def waitForUIToClose(self): + self._ui_lock.acquire() + self._ui_lock.release() + def showConfigUI(self): + self._ui_lock.acquire() + self._cancelled = False self.show_config_ui_trigger.emit() def _actualShowConfigUI(self): @@ -49,15 +64,15 @@ class ImageReaderUI(QObject): @pyqtSlot() def onOkButtonClicked(self): - self.image_reader._canceled = False - self.image_reader._wait = False + self._cancelled = False self._ui_view.close() + self._ui_lock.release() @pyqtSlot() def onCancelButtonClicked(self): - self.image_reader._canceled = True - self.image_reader._wait = False + self._cancelled = True self._ui_view.close() + self._ui_lock.release() @pyqtSlot(str) def onSizeChanged(self, value): @@ -80,9 +95,6 @@ class ImageReaderUI(QObject): else: self.peak_height = 0 - @pyqtSlot(str) + @pyqtSlot(float) def onSmoothingChanged(self, value): - if (len(value) > 0): - self.smoothing = int(value) - else: - self.smoothing = 0 + self.smoothing = int(value) diff --git a/plugins/ImageReader/__init__.py b/plugins/ImageReader/__init__.py index afd75d038b..69fc1ddcc3 100644 --- a/plugins/ImageReader/__init__.py +++ b/plugins/ImageReader/__init__.py @@ -4,7 +4,7 @@ from . import ImageReader from UM.i18n import i18nCatalog -i18n_catalog = i18nCatalog("uranium") +i18n_catalog = i18nCatalog("cura") def getMetaData(): return {