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

This commit is contained in:
Johan K 2016-06-14 18:08:35 +02:00 committed by Johan Kristensen
parent ac0f743855
commit f184baadf0
6 changed files with 202 additions and 92 deletions

View file

@ -35,24 +35,31 @@ class Layer:
def setThickness(self, thickness): def setThickness(self, thickness):
self._thickness = thickness self._thickness = thickness
def vertexCount(self): def lineMeshVertexCount(self):
result = 0 result = 0
for polygon in self._polygons: for polygon in self._polygons:
result += polygon.vertexCount() result += polygon.lineMeshVertexCount()
return result return result
def build(self, offset, vertices, colors, indices): def lineMeshElementCount(self):
result = offset result = 0
for polygon in self._polygons: for polygon in self._polygons:
if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType: result += polygon.lineMeshElementCount()
continue
polygon.build(result, vertices, colors, indices) return result
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 self._element_count += polygon.elementCount
return result return (result_vertex_offset,result_index_offset)
def createMesh(self): def createMesh(self):
return self.createMeshOrJumps(True) return self.createMeshOrJumps(True)
@ -61,39 +68,58 @@ class Layer:
return self.createMeshOrJumps(False) return self.createMeshOrJumps(False)
def createMeshOrJumps(self, make_mesh): 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: for polygon in self._polygons:
if make_mesh and (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType): #if make_mesh and (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
continue # continue
if not make_mesh and not (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType): #if not make_mesh and not (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
continue # 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) # Create an array with normals and tile 2 copies to match size of points variable
if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.SkinType or polygon.type == LayerPolygon.SupportInfillType: normals = numpy.tile( polygon.getNormals()[index_mask.ravel()], (1,2))
points[:,1] -= 0.01
if polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType:
points[:,1] += 0.01
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. # Create 4 points to draw each line segment, points +- normals results in 2 points each. Reshape to one point per line
normals *= (polygon.lineWidth / 2) 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. builder.addFacesWithColor(f_points, f_indices, f_colors)
for i in range(len(points)):
start = points[i - 1]
end = points[i]
normal = normals[i - 1]
return builder.build()
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()

View file

@ -50,18 +50,22 @@ class LayerDataBuilder(MeshBuilder):
def build(self): def build(self):
vertex_count = 0 vertex_count = 0
index_count = 0
for layer, data in self._layers.items(): 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) vertices = numpy.empty((vertex_count, 3), numpy.float32)
colors = numpy.empty((vertex_count, 4), 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(): 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._element_counts[layer] = data.elementCount
self.clear()
self.addVertices(vertices) self.addVertices(vertices)
self.addColors(colors) self.addColors(colors)
self.addIndices(indices.flatten()) self.addIndices(indices.flatten())

View file

@ -14,40 +14,96 @@ class LayerPolygon:
SupportInfillType = 7 SupportInfillType = 7
MoveCombingType = 8 MoveCombingType = 8
MoveRetractionType = 9 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._mesh = mesh
self._type = polygon_type self._types = line_types
self._data = data self._data = data
self._line_width = line_width / 1000 self._line_widths = line_widths / 1000
self._begin = 0
self._end = 0 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): def build(self, vertex_offset, index_offset, vertices, colors, indices):
self._begin = offset if (self._build_cache_line_mesh_mask == None) or (self._build_cache_needed_points == None ):
self._end = self._begin + len(self._data) - 1 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[:, :] self._index_begin += index_offset
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_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): def getColors(self):
indices[i, 0] = i return self._colors
indices[i, 1] = i + 1
indices[self._end, 0] = self._end def lineMeshVertexCount(self):
indices[self._end, 1] = self._begin return (self._vertex_end - self._vertex_begin)
def getColor(self): def lineMeshElementCount(self):
return self._color return (self._index_end - self._index_begin)
def vertexCount(self):
return len(self._data)
@property @property
def type(self): def types(self):
return self._type return self._types
@property @property
def data(self): def data(self):
@ -55,11 +111,11 @@ class LayerPolygon:
@property @property
def elementCount(self): 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 @property
def lineWidth(self): def lineWidths(self):
return self._line_width return self._line_widths
# Calculate normals for the entire polygon using numpy. # Calculate normals for the entire polygon using numpy.
def getNormals(self): def getNormals(self):
@ -71,7 +127,8 @@ class LayerPolygon:
# we end up subtracting each next point from the current, wrapping # we end up subtracting each next point from the current, wrapping
# around. This gives us the edges from the next point to the current # around. This gives us the edges from the next point to the current
# point. # point.
normals[:] = normals[:] - numpy.roll(normals, -1, axis = 0) normals = numpy.diff(normals, 1, 0)
# Calculate the length of each edge using standard Pythagoras # Calculate the length of each edge using standard Pythagoras
lengths = numpy.sqrt(normals[:, 0] ** 2 + normals[:, 2] ** 2) lengths = numpy.sqrt(normals[:, 0] ** 2 + normals[:, 2] ** 2)
# The normal of a 2D vector is equal to its x and y coordinates swapped # The normal of a 2D vector is equal to its x and y coordinates swapped
@ -85,7 +142,7 @@ class LayerPolygon:
return normals return normals
__color_map = { __color_mapping = {
NoneType: Color(1.0, 1.0, 1.0, 1.0), NoneType: Color(1.0, 1.0, 1.0, 1.0),
Inset0Type: Color(1.0, 0.0, 0.0, 1.0), Inset0Type: Color(1.0, 0.0, 0.0, 1.0),
InsetXType: Color(0.0, 1.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), MoveCombingType: Color(0.0, 0.0, 1.0, 1.0),
MoveRetractionType: Color(0.5, 0.5, 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]])

View file

@ -44,21 +44,9 @@ message Layer {
} }
message Polygon { message Polygon {
enum Type { bytes line_type = 1;
NoneType = 0; bytes points = 2;
Inset0Type = 1; bytes line_width = 3;
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
} }
message GCodeLayer { message GCodeLayer {
@ -86,4 +74,4 @@ message GCodePrefix {
} }
message SlicingFinished { message SlicingFinished {
} }

View file

@ -14,6 +14,7 @@ from UM.Math.Vector import Vector
from cura import LayerDataBuilder from cura import LayerDataBuilder
from cura import LayerDataDecorator from cura import LayerDataDecorator
from cura import LayerPolygon
import numpy import numpy
@ -38,6 +39,12 @@ class ProcessSlicedLayersJob(Job):
self._abort_requested = True self._abort_requested = True
def run(self): 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": if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
self._progress.show() self._progress.show()
@ -80,15 +87,22 @@ class ProcessSlicedLayersJob(Job):
abs_layer_number = layer.id + abs(min_layer_number) abs_layer_number = layer.id + abs(min_layer_number)
layer_data.addLayer(abs_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.setLayerHeight(abs_layer_number, layer.height)
layer_data.setLayerThickness(abs_layer_number, layer.thickness) layer_data.setLayerThickness(abs_layer_number, layer.thickness)
for p in range(layer.repeatedMessageCount("polygons")): for p in range(layer.repeatedMessageCount("polygons")):
polygon = layer.getRepeatedMessage("polygons", p) 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 = 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. 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. # 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 # This uses manual array creation + copy rather than numpy.insert since this is
# faster. # faster.
@ -99,7 +113,11 @@ class ProcessSlicedLayersJob(Job):
new_points /= 1000 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()
Job.yieldThread() Job.yieldThread()
current_layer += 1 current_layer += 1

View file

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