diff --git a/cura/Layer.py b/cura/Layer.py index 904e5528a3..cb64d77c2d 100644 --- a/cura/Layer.py +++ b/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() - polygon.build(result, vertices, colors, indices) - result += polygon.vertexCount() + return result + + 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 + #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 - poly_color = polygon.getColor() + # 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 - 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 + # 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)) - normals = polygon.getNormals() + # Scale all normals by the line width of the current line so we can easily offset. + normals *= (polygon.lineWidths[index_mask.ravel()] / 2) - # Scale all by the line width of the polygon so we can easily offset. - normals *= (polygon.lineWidth / 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) - #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] + builder.addFacesWithColor(f_points, f_indices, f_colors) - 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() + + return builder.build() \ No newline at end of file diff --git a/cura/LayerDataBuilder.py b/cura/LayerDataBuilder.py index 7e8e0e636b..85f9a23e7b 100644 --- a/cura/LayerDataBuilder.py +++ b/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()) diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index c4dc5d4954..f2c84e0d57 100644 --- a/cura/LayerPolygon.py +++ b/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._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]) - self._color = self.__color_map[polygon_type] + # 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, offset, vertices, colors, indices): - self._begin = offset - self._end = self._begin + len(self._data) - 1 + 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) - 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) + 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 - for i in range(self._begin, self._end): - indices[i, 0] = i - indices[i, 1] = i + 1 + def getColors(self): + return self._colors - indices[self._end, 0] = self._end - indices[self._end, 1] = self._begin + def lineMeshVertexCount(self): + return (self._vertex_end - self._vertex_begin) - def getColor(self): - return self._color - - def vertexCount(self): - return len(self._data) + 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]]) diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto index 38753fd804..e40678c6a5 100644 --- a/plugins/CuraEngineBackend/Cura.proto +++ b/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 { -} \ No newline at end of file +} diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index d23f71e874..0aa34b8f3f 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/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 diff --git a/plugins/LayerView/LayerView.py b/plugins/LayerView/LayerView.py index cd7a17a357..90927ff39d 100644 --- a/plugins/LayerView/LayerView.py +++ b/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