Browse Source

Formulate layerview logic using numpy to speed up. Also changed layer data packets from engine to make it possible.

Johan K 8 years ago
parent
commit
f184baadf0

+ 70 - 44
cura/Layer.py

@@ -35,24 +35,31 @@ class Layer:
     def setThickness(self, thickness):
         self._thickness = thickness
 
-    def vertexCount(self):
+    def lineMeshVertexCount(self):
         result = 0
         for polygon in self._polygons:
-            result += polygon.vertexCount()
+            result += polygon.lineMeshVertexCount()
 
         return result
 
-    def build(self, offset, vertices, colors, indices):
-        result = offset
+    def lineMeshElementCount(self):
+        result = 0
         for polygon in self._polygons:
-            if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType:
-                continue
+            result += polygon.lineMeshElementCount()
+
+        return result
 
-            polygon.build(result, vertices, colors, indices)
-            result += polygon.vertexCount()
+    def build(self, vertex_offset, index_offset, vertices, colors, indices):
+        result_vertex_offset = vertex_offset
+        result_index_offset = index_offset
+        self._element_count = 0
+        for polygon in self._polygons:
+            polygon.build(result_vertex_offset, result_index_offset, vertices, colors, indices)
+            result_vertex_offset += polygon.lineMeshVertexCount()
+            result_index_offset += polygon.lineMeshElementCount()
             self._element_count += polygon.elementCount
 
-        return result
+        return (result_vertex_offset,result_index_offset)
 
     def createMesh(self):
         return self.createMeshOrJumps(True)
@@ -61,39 +68,58 @@ class Layer:
         return self.createMeshOrJumps(False)
 
     def createMeshOrJumps(self, make_mesh):
-        builder = MeshBuilder()
-
+        builder = MeshBuilder() # This is never really used, only the mesh_data inside
+        index_pattern = numpy.array([[0,3,2,0,1,3]],dtype = numpy.int32 )
+        
+        line_count = 0
+        if make_mesh:
+            for polygon in self._polygons:
+                line_count += polygon._mesh_line_count
+        else:
+            for polygon in self._polygons:
+                line_count += polygon._jump_count
+            
+        
+        # Reserve the neccesary space for the data upfront
+        builder.reserveFaceAndVerticeCount( 2*line_count, 4*line_count )
+        
         for polygon in self._polygons:
-            if make_mesh and (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
-                continue
-            if not make_mesh and not (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
-                continue
-
-            poly_color = polygon.getColor()
-
-            points = numpy.copy(polygon.data)
-            if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.SkinType or polygon.type == LayerPolygon.SupportInfillType:
-                points[:,1] -= 0.01
-            if polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType:
-                points[:,1] += 0.01
-
-            normals = polygon.getNormals()
-
-            # Scale all by the line width of the polygon so we can easily offset.
-            normals *= (polygon.lineWidth / 2)
-
-            #TODO: Use numpy magic to perform the vertex creation to speed up things.
-            for i in range(len(points)):
-                start = points[i - 1]
-                end = points[i]
-
-                normal = normals[i - 1]
-
-                point1 = Vector(data = start - normal)
-                point2 = Vector(data = start + normal)
-                point3 = Vector(data = end + normal)
-                point4 = Vector(data = end - normal)
-
-                builder.addQuad(point1, point2, point3, point4, color = poly_color)
-
-        return builder.build()
+            #if make_mesh and (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
+            #    continue
+            #if not make_mesh and not (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
+            #    continue
+            
+            index_mask = numpy.logical_not(polygon._jump_mask) if make_mesh else polygon._jump_mask
+
+            # Create an array with rows [p p+1] and only save those we whant to draw based on make_mesh
+            points = numpy.concatenate((polygon.data[:-1],polygon.data[1:]),1)[index_mask.ravel()]
+            # Line types of the points we want to draw
+            line_types = polygon._types[index_mask]
+            
+          
+            #if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.SkinType or polygon.type == LayerPolygon.SupportInfillType:
+            #    points[:,1] -= 0.01
+            #if polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType:
+            #    points[:,1] += 0.01
+            # Shift the z-axis according to previous implementation. 
+            if make_mesh:
+                points[polygon._orInfillSkin[line_types],1::3] -= 0.01
+            else:
+                points[:,1::3] += 0.01
+
+            # Create an array with normals and tile 2 copies to match size of points variable
+            normals = numpy.tile( polygon.getNormals()[index_mask.ravel()], (1,2))
+
+            # Scale all normals by the line width of the current line so we can easily offset.
+            normals *= (polygon.lineWidths[index_mask.ravel()] / 2)
+
+            # Create 4 points to draw each line segment, points +- normals results in 2 points each. Reshape to one point per line
+            f_points = numpy.concatenate((points-normals,points+normals),1).reshape((-1,3))
+            # index_pattern defines which points to use to draw the two faces for each lines egment, the following linesegment is offset by 4 
+            f_indices = ( index_pattern + numpy.arange(0,4*len(normals),4,dtype=numpy.int32).reshape((-1,1)) ).reshape((-1,3))
+            f_colors = numpy.repeat(polygon._color_map[line_types], 4, 0)
+
+            builder.addFacesWithColor(f_points, f_indices, f_colors)
+
+        
+        return builder.build()

+ 8 - 4
cura/LayerDataBuilder.py

@@ -50,18 +50,22 @@ class LayerDataBuilder(MeshBuilder):
 
     def build(self):
         vertex_count = 0
+        index_count = 0
         for layer, data in self._layers.items():
-            vertex_count += data.vertexCount()
+            vertex_count += data.lineMeshVertexCount()
+            index_count += data.lineMeshElementCount()
 
         vertices = numpy.empty((vertex_count, 3), numpy.float32)
         colors = numpy.empty((vertex_count, 4), numpy.float32)
-        indices = numpy.empty((vertex_count, 2), numpy.int32)
+        indices = numpy.empty((index_count, 2), numpy.int32)
 
-        offset = 0
+        vertex_offset = 0
+        index_offset = 0
         for layer, data in self._layers.items():
-            offset = data.build(offset, vertices, colors, indices)
+            ( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, indices)
             self._element_counts[layer] = data.elementCount
 
+        self.clear()
         self.addVertices(vertices)
         self.addColors(colors)
         self.addIndices(indices.flatten())

+ 105 - 35
cura/LayerPolygon.py

@@ -14,40 +14,96 @@ class LayerPolygon:
     SupportInfillType = 7
     MoveCombingType = 8
     MoveRetractionType = 9
-
-    def __init__(self, mesh, polygon_type, data, line_width):
+    
+    __jump_map = numpy.logical_or( numpy.arange(10) == NoneType, numpy.arange(10) >= MoveCombingType )
+    
+    def __init__(self, mesh, line_types, data, line_widths):
         self._mesh = mesh
-        self._type = polygon_type
+        self._types = line_types
         self._data = data
-        self._line_width = line_width / 1000
-        self._begin = 0
-        self._end = 0
-
-        self._color = self.__color_map[polygon_type]
-
-    def build(self, offset, vertices, colors, indices):
-        self._begin = offset
-        self._end = self._begin + len(self._data) - 1
-
-        vertices[self._begin:self._end + 1, :] = self._data[:, :]
-        colors[self._begin:self._end + 1, :] = numpy.array([self._color.r * 0.5, self._color.g * 0.5, self._color.b * 0.5, self._color.a], numpy.float32)
-
-        for i in range(self._begin, self._end):
-            indices[i, 0] = i
-            indices[i, 1] = i + 1
-
-        indices[self._end, 0] = self._end
-        indices[self._end, 1] = self._begin
-
-    def getColor(self):
-        return self._color
-
-    def vertexCount(self):
-        return len(self._data)
+        self._line_widths = line_widths / 1000
+        
+        self._vertex_begin = 0
+        self._vertex_end = 0
+        self._index_begin = 0
+        self._index_end = 0
+        
+        self._jump_mask = self.__jump_map[self._types]
+        self._jump_count = numpy.sum(self._jump_mask)
+        self._mesh_line_count = len(self._types)-self._jump_count
+        self._vertex_count = self._mesh_line_count + numpy.sum( self._types[1:] == self._types[:-1])
+
+        # Buffering the colors shouldn't be necessary as it is not 
+        # re-used and can save alot of memory usage.
+        self._colors = self.__color_map[self._types]
+        self._color_map = self.__color_map
+        
+        # type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
+        # Should be generated in better way, not hardcoded.
+        self._orInfillSkin = numpy.array([0,0,0,1,0,0,1,1,0,0],dtype=numpy.bool)
+        
+        self._build_cache_line_mesh_mask = None
+        self._build_cache_needed_points = None
+        
+    def build_cache(self):
+        #if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType:
+        #    continue
+        self._build_cache_line_mesh_mask = numpy.logical_not(numpy.logical_or(self._jump_mask,self._types == LayerPolygon.InfillType ))
+        mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask)
+        self._index_begin = 0
+        self._index_end = mesh_line_count
+        
+        self._build_cache_needed_points = numpy.ones((len(self._types),2), dtype=numpy.bool)
+        # Only if the type of line segment changes do we need to add an extra vertex to change colors
+        self._build_cache_needed_points[1:,0][:,numpy.newaxis] = self._types[1:] != self._types[:-1]
+        # Remove points of types we don't want in the line mesh
+        numpy.logical_and(self._build_cache_needed_points, self._build_cache_line_mesh_mask, self._build_cache_needed_points )
+        
+        self._vertex_begin = 0
+        self._vertex_end = numpy.sum( self._build_cache_needed_points )
+        
+
+    def build(self, vertex_offset, index_offset, vertices, colors, indices):
+        if (self._build_cache_line_mesh_mask == None) or (self._build_cache_needed_points == None ):
+            self.build_cache()
+            
+        line_mesh_mask = self._build_cache_line_mesh_mask
+        needed_points_list = self._build_cache_needed_points
+            
+        index_list = ( numpy.arange(len(self._types)).reshape((-1,1)) + numpy.array([[0,1]]) ).reshape((-1,1))[needed_points_list.reshape((-1,1))]
+        
+        self._vertex_begin += vertex_offset
+        self._vertex_end += vertex_offset
+        
+        vertices[self._vertex_begin:self._vertex_end, :] = self._data[index_list, :]
+        colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors,(1,2)).reshape((-1,4))[needed_points_list.ravel()] 
+        colors[self._vertex_begin:self._vertex_end, :] *= numpy.array([[0.5, 0.5, 0.5, 1.0]], numpy.float32)
+
+        self._index_begin += index_offset
+        self._index_end += index_offset
+        
+        indices[self._index_begin:self._index_end,:] = numpy.arange(self._index_end-self._index_begin, dtype=numpy.int32).reshape((-1,1))
+        # When the line type changes the index needs to be increased by 2.
+        indices[self._index_begin:self._index_end,:] += numpy.cumsum(needed_points_list[line_mesh_mask.ravel(),0],dtype=numpy.int32).reshape((-1,1))
+        # Each line segment goes from it's starting point p to p+1, offset by the vertex index. 
+        # The -1 is to compensate for the neccecarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
+        indices[self._index_begin:self._index_end,:] += numpy.array([self._vertex_begin - 1,self._vertex_begin])
+        
+        self._build_cache_line_mesh_mask = None
+        self._build_cache_needed_points = None
+
+    def getColors(self):
+        return self._colors
+
+    def lineMeshVertexCount(self):
+        return (self._vertex_end - self._vertex_begin)
+
+    def lineMeshElementCount(self):
+        return (self._index_end - self._index_begin)
 
     @property
-    def type(self):
-        return self._type
+    def types(self):
+        return self._types
 
     @property
     def data(self):
@@ -55,11 +111,11 @@ class LayerPolygon:
 
     @property
     def elementCount(self):
-        return ((self._end - self._begin) + 1) * 2  # The range of vertices multiplied by 2 since each vertex is used twice
+        return (self._index_end - self._index_begin) * 2  # The range of vertices multiplied by 2 since each vertex is used twice
 
     @property
-    def lineWidth(self):
-        return self._line_width
+    def lineWidths(self):
+        return self._line_widths
 
     # Calculate normals for the entire polygon using numpy.
     def getNormals(self):
@@ -71,7 +127,8 @@ class LayerPolygon:
         # we end up subtracting each next point from the current, wrapping
         # around. This gives us the edges from the next point to the current
         # point.
-        normals[:] = normals[:] - numpy.roll(normals, -1, axis = 0)
+        normals = numpy.diff(normals, 1, 0)
+
         # Calculate the length of each edge using standard Pythagoras
         lengths = numpy.sqrt(normals[:, 0] ** 2 + normals[:, 2] ** 2)
         # The normal of a 2D vector is equal to its x and y coordinates swapped
@@ -85,7 +142,7 @@ class LayerPolygon:
 
         return normals
 
-    __color_map = {
+    __color_mapping = {
         NoneType: Color(1.0, 1.0, 1.0, 1.0),
         Inset0Type: Color(1.0, 0.0, 0.0, 1.0),
         InsetXType: Color(0.0, 1.0, 0.0, 1.0),
@@ -97,3 +154,16 @@ class LayerPolygon:
         MoveCombingType: Color(0.0, 0.0, 1.0, 1.0),
         MoveRetractionType: Color(0.5, 0.5, 1.0, 1.0),
     }
+
+    # Should be generated in better way, not hardcoded.
+    __color_map = numpy.array([
+        [1.0, 1.0, 1.0, 1.0],
+        [1.0, 0.0, 0.0, 1.0],
+        [0.0, 1.0, 0.0, 1.0],
+        [1.0, 1.0, 0.0, 1.0],
+        [0.0, 1.0, 1.0, 1.0],
+        [0.0, 1.0, 1.0, 1.0],
+        [1.0, 0.74, 0.0, 1.0],
+        [0.0, 1.0, 1.0, 1.0],
+        [0.0, 0.0, 1.0, 1.0],
+        [0.5, 0.5, 1.0, 1.0]])

+ 4 - 16
plugins/CuraEngineBackend/Cura.proto

@@ -44,21 +44,9 @@ message Layer {
 }
 
 message Polygon {
-    enum Type {
-        NoneType = 0;
-        Inset0Type = 1;
-        InsetXType = 2;
-        SkinType = 3;
-        SupportType = 4;
-        SkirtType = 5;
-        InfillType = 6;
-        SupportInfillType = 7;
-        MoveCombingType = 8;
-        MoveRetractionType = 9;
-    }
-    Type type = 1; // Type of move
-    bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
-    float line_width = 3; // The width of the line being laid down
+    bytes line_type = 1;
+    bytes points = 2;
+    bytes line_width = 3;
 }
 
 message GCodeLayer {
@@ -86,4 +74,4 @@ message GCodePrefix {
 }
 
 message SlicingFinished {
-}
+}

+ 19 - 1
plugins/CuraEngineBackend/ProcessSlicedLayersJob.py

@@ -14,6 +14,7 @@ from UM.Math.Vector import Vector
 
 from cura import LayerDataBuilder
 from cura import LayerDataDecorator
+from cura import LayerPolygon
 
 import numpy
 
@@ -38,6 +39,12 @@ class ProcessSlicedLayersJob(Job):
         self._abort_requested = True
 
     def run(self):
+        # This is to prevent small models layer data to be cleared by extra invocation of engine
+        # Possibly adds an extra bug of layerdata not being removed if platform is cleared.
+        #TODO: remove need for this check 
+        if len(self._layers) == 0:
+            return
+
         if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
             self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
             self._progress.show()
@@ -80,15 +87,22 @@ class ProcessSlicedLayersJob(Job):
             abs_layer_number = layer.id + abs(min_layer_number)
 
             layer_data.addLayer(abs_layer_number)
+            this_layer = layer_data.getLayer(abs_layer_number)
             layer_data.setLayerHeight(abs_layer_number, layer.height)
             layer_data.setLayerThickness(abs_layer_number, layer.thickness)
 
             for p in range(layer.repeatedMessageCount("polygons")):
                 polygon = layer.getRepeatedMessage("polygons", p)
 
+                line_types = numpy.fromstring(polygon.line_type, dtype="u1")  # Convert bytearray to numpy array
+                line_types = line_types.reshape((-1,1))
+
                 points = numpy.fromstring(polygon.points, dtype="i8")  # Convert bytearray to numpy array
                 points = points.reshape((-1,2))  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
 
+                line_widths = numpy.fromstring(polygon.line_width, dtype="i4")  # Convert bytearray to numpy array
+                line_widths = line_widths.reshape((-1,1))  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
+                
                 # Create a new 3D-array, copy the 2D points over and insert the right height.
                 # This uses manual array creation + copy rather than numpy.insert since this is
                 # faster.
@@ -99,7 +113,11 @@ class ProcessSlicedLayersJob(Job):
 
                 new_points /= 1000
 
-                layer_data.addPolygon(abs_layer_number, polygon.type, new_points, polygon.line_width)
+                this_poly = LayerPolygon.LayerPolygon(layer_data, line_types, new_points, line_widths)
+                this_poly.build_cache()
+                
+                this_layer.polygons.append(this_poly)
+
                 Job.yieldThread()
             Job.yieldThread()
             current_layer += 1

+ 8 - 4
plugins/LayerView/LayerView.py

@@ -25,6 +25,8 @@ from . import LayerViewProxy
 from UM.i18n import i18nCatalog
 catalog = i18nCatalog("cura")
 
+import numpy
+
 ## View used to display g-code paths.
 class LayerView(View):
     def __init__(self):
@@ -42,7 +44,7 @@ class LayerView(View):
         self._top_layers_job = None
         self._activity = False
 
-        Preferences.getInstance().addPreference("view/top_layer_count", 1)
+        Preferences.getInstance().addPreference("view/top_layer_count", 5)
         Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
 
         self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
@@ -255,12 +257,14 @@ class _CreateTopLayersJob(Job):
             if not layer or layer.getVertices() is None:
                 continue
 
+            layer_mesh.addIndices(layer_mesh._vertex_count+layer.getIndices())
             layer_mesh.addVertices(layer.getVertices())
-
+            
             # Scale layer color by a brightness factor based on the current layer number
             # This will result in a range of 0.5 - 1.0 to multiply colors by.
-            brightness = (2.0 - (i / self._solid_layers)) / 2.0
-            layer_mesh.addColors(layer.getColors() * brightness)
+            brightness = numpy.ones((1,4),dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
+            brightness[0,3] = 1.0;
+            layer_mesh.addColors(layer.getColors() * brightness )
 
             if self._cancel:
                 return