Browse Source

Made changed from code review and updated MeshReader plugins to support changes made to Uranium branch MeshReaderDialog. This branch now must be paired with that Uranium branch.

Kurt Loeffler 9 years ago
parent
commit
447fdc8fbc

+ 96 - 96
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
-
-                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)
 
+        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
+
+            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  

+ 19 - 11
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"}

+ 23 - 29
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 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)
+    def preRead(self, file_name):
+        self._ui.showConfigUI()
+        self._ui.waitForUIToClose()
 
-            return result
+        if self._ui.getCancelled():
+            return MeshReader.PreReadResult.cancelled
+        return MeshReader.PreReadResult.accepted
 
-        return None
+    def read(self, file_name):
+        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 += 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 += 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 /= 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],

+ 23 - 11
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)

+ 1 - 1
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 {