Merge branch 'print_simulation_view'

This commit is contained in:
Aleksei S 2017-11-20 14:17:19 +01:00
commit ed3d8266b3
33 changed files with 2642 additions and 1190 deletions

View file

@ -218,7 +218,7 @@ class CuraApplication(QtApplication):
"CuraEngineBackend", "CuraEngineBackend",
"UserAgreement", "UserAgreement",
"SolidView", "SolidView",
"LayerView", "SimulationView",
"STLReader", "STLReader",
"SelectionTool", "SelectionTool",
"CameraTool", "CameraTool",
@ -1386,7 +1386,7 @@ class CuraApplication(QtApplication):
extension = os.path.splitext(filename)[1] extension = os.path.splitext(filename)[1]
if extension.lower() in self._non_sliceable_extensions: if extension.lower() in self._non_sliceable_extensions:
self.getController().setActiveView("LayerView") self.getController().setActiveView("SimulationView")
view = self.getController().getActiveView() view = self.getController().getActiveView()
view.resetLayerData() view.resetLayerData()
view.setLayer(9999999) view.setLayer(9999999)

View file

@ -47,12 +47,12 @@ class Layer:
return result return result
def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices): def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices):
result_vertex_offset = vertex_offset result_vertex_offset = vertex_offset
result_index_offset = index_offset result_index_offset = index_offset
self._element_count = 0 self._element_count = 0
for polygon in self._polygons: for polygon in self._polygons:
polygon.build(result_vertex_offset, result_index_offset, vertices, colors, line_dimensions, extruders, line_types, indices) polygon.build(result_vertex_offset, result_index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices)
result_vertex_offset += polygon.lineMeshVertexCount() result_vertex_offset += polygon.lineMeshVertexCount()
result_index_offset += polygon.lineMeshElementCount() result_index_offset += polygon.lineMeshElementCount()
self._element_count += polygon.elementCount self._element_count += polygon.elementCount

View file

@ -20,11 +20,11 @@ class LayerDataBuilder(MeshBuilder):
if layer not in self._layers: if layer not in self._layers:
self._layers[layer] = Layer(layer) self._layers[layer] = Layer(layer)
def addPolygon(self, layer, polygon_type, data, line_width): def addPolygon(self, layer, polygon_type, data, line_width, line_thickness, line_feedrate):
if layer not in self._layers: if layer not in self._layers:
self.addLayer(layer) self.addLayer(layer)
p = LayerPolygon(self, polygon_type, data, line_width) p = LayerPolygon(self, polygon_type, data, line_width, line_thickness, line_feedrate)
self._layers[layer].polygons.append(p) self._layers[layer].polygons.append(p)
def getLayer(self, layer): def getLayer(self, layer):
@ -64,13 +64,14 @@ class LayerDataBuilder(MeshBuilder):
line_dimensions = numpy.empty((vertex_count, 2), numpy.float32) line_dimensions = numpy.empty((vertex_count, 2), numpy.float32)
colors = numpy.empty((vertex_count, 4), numpy.float32) colors = numpy.empty((vertex_count, 4), numpy.float32)
indices = numpy.empty((index_count, 2), numpy.int32) indices = numpy.empty((index_count, 2), numpy.int32)
feedrates = numpy.empty((vertex_count), numpy.float32)
extruders = numpy.empty((vertex_count), numpy.float32) extruders = numpy.empty((vertex_count), numpy.float32)
line_types = numpy.empty((vertex_count), numpy.float32) line_types = numpy.empty((vertex_count), numpy.float32)
vertex_offset = 0 vertex_offset = 0
index_offset = 0 index_offset = 0
for layer, data in sorted(self._layers.items()): for layer, data in sorted(self._layers.items()):
( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices) ( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices)
self._element_counts[layer] = data.elementCount self._element_counts[layer] = data.elementCount
self.addVertices(vertices) self.addVertices(vertices)
@ -107,6 +108,11 @@ class LayerDataBuilder(MeshBuilder):
"value": line_types, "value": line_types,
"opengl_name": "a_line_type", "opengl_name": "a_line_type",
"opengl_type": "float" "opengl_type": "float"
},
"feedrates": {
"value": feedrates,
"opengl_name": "a_feedrate",
"opengl_type": "float"
} }
} }

View file

@ -28,7 +28,8 @@ class LayerPolygon:
# \param data new_points # \param data new_points
# \param line_widths array with line widths # \param line_widths array with line widths
# \param line_thicknesses: array with type as index and thickness as value # \param line_thicknesses: array with type as index and thickness as value
def __init__(self, extruder, line_types, data, line_widths, line_thicknesses): # \param line_feedrates array with line feedrates
def __init__(self, extruder, line_types, data, line_widths, line_thicknesses, line_feedrates):
self._extruder = extruder self._extruder = extruder
self._types = line_types self._types = line_types
for i in range(len(self._types)): for i in range(len(self._types)):
@ -37,6 +38,7 @@ class LayerPolygon:
self._data = data self._data = data
self._line_widths = line_widths self._line_widths = line_widths
self._line_thicknesses = line_thicknesses self._line_thicknesses = line_thicknesses
self._line_feedrates = line_feedrates
self._vertex_begin = 0 self._vertex_begin = 0
self._vertex_end = 0 self._vertex_end = 0
@ -84,10 +86,11 @@ class LayerPolygon:
# \param vertices : vertex numpy array to be filled # \param vertices : vertex numpy array to be filled
# \param colors : vertex numpy array to be filled # \param colors : vertex numpy array to be filled
# \param line_dimensions : vertex numpy array to be filled # \param line_dimensions : vertex numpy array to be filled
# \param feedrates : vertex numpy array to be filled
# \param extruders : vertex numpy array to be filled # \param extruders : vertex numpy array to be filled
# \param line_types : vertex numpy array to be filled # \param line_types : vertex numpy array to be filled
# \param indices : index numpy array to be filled # \param indices : index numpy array to be filled
def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices): def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices):
if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None: if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None:
self.buildCache() self.buildCache()
@ -109,10 +112,13 @@ class LayerPolygon:
# Create an array with colors for each vertex and remove the color data for the points that has been thrown away. # Create an array with colors for each vertex and remove the color data for the points that has been thrown away.
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.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()]
# Create an array with line widths for each vertex. # Create an array with line widths and thicknesses for each vertex.
line_dimensions[self._vertex_begin:self._vertex_end, 0] = numpy.tile(self._line_widths, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] line_dimensions[self._vertex_begin:self._vertex_end, 0] = numpy.tile(self._line_widths, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
line_dimensions[self._vertex_begin:self._vertex_end, 1] = numpy.tile(self._line_thicknesses, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] line_dimensions[self._vertex_begin:self._vertex_end, 1] = numpy.tile(self._line_thicknesses, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
# Create an array with feedrates for each line
feedrates[self._vertex_begin:self._vertex_end] = numpy.tile(self._line_feedrates, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
extruders[self._vertex_begin:self._vertex_end] = self._extruder extruders[self._vertex_begin:self._vertex_end] = self._extruder
# Convert type per vertex to type per line # Convert type per vertex to type per line
@ -167,6 +173,14 @@ class LayerPolygon:
def lineWidths(self): def lineWidths(self):
return self._line_widths return self._line_widths
@property
def lineThicknesses(self):
return self._line_thicknesses
@property
def lineFeedrates(self):
return self._line_feedrates
@property @property
def jumpMask(self): def jumpMask(self):
return self._jump_mask return self._jump_mask

View file

@ -61,6 +61,8 @@ message Polygon {
Type type = 1; // Type of move 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) 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 float line_width = 3; // The width of the line being laid down
float line_thickness = 4; // The thickness of the line being laid down
float line_feedrate = 5; // The feedrate of the line being laid down
} }
message LayerOptimized { message LayerOptimized {
@ -82,6 +84,8 @@ message PathSegment {
bytes points = 3; // The points defining the line segments, bytes of float[2/3] array of length N+1 bytes points = 3; // The points defining the line segments, bytes of float[2/3] array of length N+1
bytes line_type = 4; // Type of line segment as an unsigned char array of length 1 or N, where N is the number of line segments in this path bytes line_type = 4; // Type of line segment as an unsigned char array of length 1 or N, where N is the number of line segments in this path
bytes line_width = 5; // The widths of the line segments as bytes of a float array of length 1 or N bytes line_width = 5; // The widths of the line segments as bytes of a float array of length 1 or N
bytes line_thickness = 6; // The thickness of the line segments as bytes of a float array of length 1 or N
bytes line_feedrate = 7; // The feedrate of the line segments as bytes of a float array of length 1 or N
} }

View file

@ -608,7 +608,7 @@ class CuraEngineBackend(QObject, Backend):
def _onActiveViewChanged(self): def _onActiveViewChanged(self):
if Application.getInstance().getController().getActiveView(): if Application.getInstance().getController().getActiveView():
view = Application.getInstance().getController().getActiveView() view = Application.getInstance().getController().getActiveView()
if view.getPluginId() == "LayerView": # If switching to layer view, we should process the layers if that hasn't been done yet. if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
self._layer_view_active = True self._layer_view_active = True
# There is data and we're not slicing at the moment # There is data and we're not slicing at the moment
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment. # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.

View file

@ -61,7 +61,7 @@ class ProcessSlicedLayersJob(Job):
def run(self): def run(self):
start_time = time() start_time = time()
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
self._progress_message.show() self._progress_message.show()
Job.yieldThread() Job.yieldThread()
if self._abort_requested: if self._abort_requested:
@ -109,6 +109,7 @@ class ProcessSlicedLayersJob(Job):
layer_data.addLayer(abs_layer_number) layer_data.addLayer(abs_layer_number)
this_layer = layer_data.getLayer(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)
for p in range(layer.repeatedMessageCount("path_segment")): for p in range(layer.repeatedMessageCount("path_segment")):
polygon = layer.getRepeatedMessage("path_segment", p) polygon = layer.getRepeatedMessage("path_segment", p)
@ -127,10 +128,11 @@ class ProcessSlicedLayersJob(Job):
line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # 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. 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.
# In the future, line_thicknesses should be given by CuraEngine as well. line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array
# Currently the infill layer thickness also translates to line width line_thicknesses = line_thicknesses.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4")
line_thicknesses[:] = layer.thickness / 1000 # from micrometer to millimeter line_feedrates = numpy.fromstring(polygon.line_feedrate, dtype="f4") # Convert bytearray to numpy array
line_feedrates = line_feedrates.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
@ -145,7 +147,7 @@ class ProcessSlicedLayersJob(Job):
new_points[:, 1] = points[:, 2] new_points[:, 1] = points[:, 2]
new_points[:, 2] = -points[:, 1] new_points[:, 2] = -points[:, 1]
this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses) this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses, line_feedrates)
this_poly.buildCache() this_poly.buildCache()
this_layer.polygons.append(this_poly) this_layer.polygons.append(this_poly)
@ -219,7 +221,7 @@ class ProcessSlicedLayersJob(Job):
self._progress_message.setProgress(100) self._progress_message.setProgress(100)
view = Application.getInstance().getController().getActiveView() view = Application.getInstance().getController().getActiveView()
if view.getPluginId() == "LayerView": if view.getPluginId() == "SimulationView":
view.resetLayerData() view.resetLayerData()
if self._progress_message: if self._progress_message:
@ -232,7 +234,7 @@ class ProcessSlicedLayersJob(Job):
def _onActiveViewChanged(self): def _onActiveViewChanged(self):
if self.isRunning(): if self.isRunning():
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
if not self._progress_message: if not self._progress_message:
self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0, catalog.i18nc("@info:title", "Information")) self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0, catalog.i18nc("@info:title", "Information"))
if self._progress_message.getProgress() != 100: if self._progress_message.getProgress() != 100:

View file

@ -40,7 +40,8 @@ class GCodeReader(MeshReader):
self._extruder_number = 0 self._extruder_number = 0
self._clearValues() self._clearValues()
self._scene_node = None self._scene_node = None
self._position = namedtuple('Position', ['x', 'y', 'z', 'e']) # X, Y, Z position, F feedrate and E extruder values are stored
self._position = namedtuple('Position', ['x', 'y', 'z', 'f', 'e'])
self._is_layers_in_file = False # Does the Gcode have the layers comment? self._is_layers_in_file = False # Does the Gcode have the layers comment?
self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset] self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
self._current_layer_thickness = 0.2 # default self._current_layer_thickness = 0.2 # default
@ -48,7 +49,9 @@ class GCodeReader(MeshReader):
Preferences.getInstance().addPreference("gcodereader/show_caution", True) Preferences.getInstance().addPreference("gcodereader/show_caution", True)
def _clearValues(self): def _clearValues(self):
self._filament_diameter = 2.85
self._extruder_number = 0 self._extruder_number = 0
self._extrusion_length_offset = [0]
self._layer_type = LayerPolygon.Inset0Type self._layer_type = LayerPolygon.Inset0Type
self._layer_number = 0 self._layer_number = 0
self._previous_z = 0 self._previous_z = 0
@ -97,7 +100,7 @@ class GCodeReader(MeshReader):
def _createPolygon(self, layer_thickness, path, extruder_offsets): def _createPolygon(self, layer_thickness, path, extruder_offsets):
countvalid = 0 countvalid = 0
for point in path: for point in path:
if point[3] > 0: if point[5] > 0:
countvalid += 1 countvalid += 1
if countvalid >= 2: if countvalid >= 2:
# we know what to do now, no need to count further # we know what to do now, no need to count further
@ -115,27 +118,56 @@ class GCodeReader(MeshReader):
line_types = numpy.empty((count - 1, 1), numpy.int32) line_types = numpy.empty((count - 1, 1), numpy.int32)
line_widths = numpy.empty((count - 1, 1), numpy.float32) line_widths = numpy.empty((count - 1, 1), numpy.float32)
line_thicknesses = numpy.empty((count - 1, 1), numpy.float32) line_thicknesses = numpy.empty((count - 1, 1), numpy.float32)
# TODO: need to calculate actual line width based on E values line_feedrates = numpy.empty((count - 1, 1), numpy.float32)
line_widths[:, 0] = 0.35 # Just a guess line_widths[:, 0] = 0.35 # Just a guess
line_thicknesses[:, 0] = layer_thickness line_thicknesses[:, 0] = layer_thickness
points = numpy.empty((count, 3), numpy.float32) points = numpy.empty((count, 3), numpy.float32)
extrusion_values = numpy.empty((count, 1), numpy.float32)
i = 0 i = 0
for point in path: for point in path:
points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]] points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]]
extrusion_values[i] = point[4]
if i > 0: if i > 0:
line_types[i - 1] = point[3] line_feedrates[i - 1] = point[3]
if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]: line_types[i - 1] = point[5]
if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
line_widths[i - 1] = 0.1 line_widths[i - 1] = 0.1
line_thicknesses[i - 1] = 0.0 # Travels are set as zero thickness lines
else:
line_widths[i - 1] = self._calculateLineWidth(points[i], points[i-1], extrusion_values[i], extrusion_values[i-1], layer_thickness)
i += 1 i += 1
this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses) this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses, line_feedrates)
this_poly.buildCache() this_poly.buildCache()
this_layer.polygons.append(this_poly) this_layer.polygons.append(this_poly)
return True return True
def _calculateLineWidth(self, current_point, previous_point, current_extrusion, previous_extrusion, layer_thickness):
# Area of the filament
Af = (self._filament_diameter / 2) ** 2 * numpy.pi
# Length of the extruded filament
de = current_extrusion - previous_extrusion
# Volumne of the extruded filament
dVe = de * Af
# Length of the printed line
dX = numpy.sqrt((current_point[0] - previous_point[0])**2 + (current_point[2] - previous_point[2])**2)
# When the extruder recovers from a retraction, we get zero distance
if dX == 0:
return 0.1
# Area of the printed line. This area is a rectangle
Ae = dVe / dX
# This area is a rectangle with area equal to layer_thickness * layer_width
line_width = Ae / layer_thickness
# A threshold is set to avoid weird paths in the GCode
if line_width > 1.2:
return 0.35
return line_width
def _gCode0(self, position, params, path): def _gCode0(self, position, params, path):
x, y, z, e = position
x, y, z, f, e = position
if self._is_absolute_positioning: if self._is_absolute_positioning:
x = params.x if params.x is not None else x x = params.x if params.x is not None else x
y = params.y if params.y is not None else y y = params.y if params.y is not None else y
@ -145,22 +177,24 @@ class GCodeReader(MeshReader):
y += params.y if params.y is not None else 0 y += params.y if params.y is not None else 0
z += params.z if params.z is not None else 0 z += params.z if params.z is not None else 0
f = params.f if params.f is not None else f
if params.e is not None: if params.e is not None:
new_extrusion_value = params.e if self._is_absolute_positioning else e[self._extruder_number] + params.e new_extrusion_value = params.e if self._is_absolute_positioning else e[self._extruder_number] + params.e
if new_extrusion_value > e[self._extruder_number]: if new_extrusion_value > e[self._extruder_number]:
path.append([x, y, z, self._layer_type]) # extrusion path.append([x, y, z, f, params.e + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion
else: else:
path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction path.append([x, y, z, f, params.e + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction
e[self._extruder_number] = new_extrusion_value e[self._extruder_number] = new_extrusion_value
# Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions # Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions
# Also, 1.5 is a heuristic for any priming or whatsoever, we skip those. # Also, 1.5 is a heuristic for any priming or whatsoever, we skip those.
if z > self._previous_z and (z - self._previous_z < 1.5): if z > self._previous_z and (z - self._previous_z < 1.5):
self._current_layer_thickness = z - self._previous_z + 0.05 # allow a tiny overlap self._current_layer_thickness = z - self._previous_z # allow a tiny overlap
self._previous_z = z self._previous_z = z
else: else:
path.append([x, y, z, LayerPolygon.MoveCombingType]) path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType])
return self._position(x, y, z, e) return self._position(x, y, z, f, e)
# G0 and G1 should be handled exactly the same. # G0 and G1 should be handled exactly the same.
_gCode1 = _gCode0 _gCode1 = _gCode0
@ -171,6 +205,7 @@ class GCodeReader(MeshReader):
params.x if params.x is not None else position.x, params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y, params.y if params.y is not None else position.y,
0, 0,
position.f,
position.e) position.e)
## Set the absolute positioning ## Set the absolute positioning
@ -187,11 +222,14 @@ class GCodeReader(MeshReader):
# For example: G92 X10 will set the X to 10 without any physical motion. # For example: G92 X10 will set the X to 10 without any physical motion.
def _gCode92(self, position, params, path): def _gCode92(self, position, params, path):
if params.e is not None: if params.e is not None:
# Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width
self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e
position.e[self._extruder_number] = params.e position.e[self._extruder_number] = params.e
return self._position( return self._position(
params.x if params.x is not None else position.x, params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y, params.y if params.y is not None else position.y,
params.z if params.z is not None else position.z, params.z if params.z is not None else position.z,
params.f if params.f is not None else position.f,
position.e) position.e)
def _processGCode(self, G, line, position, path): def _processGCode(self, G, line, position, path):
@ -199,7 +237,7 @@ class GCodeReader(MeshReader):
line = line.split(";", 1)[0] # Remove comments (if any) line = line.split(";", 1)[0] # Remove comments (if any)
if func is not None: if func is not None:
s = line.upper().split(" ") s = line.upper().split(" ")
x, y, z, e = None, None, None, None x, y, z, f, e = None, None, None, None, None
for item in s[1:]: for item in s[1:]:
if len(item) <= 1: if len(item) <= 1:
continue continue
@ -211,17 +249,20 @@ class GCodeReader(MeshReader):
y = float(item[1:]) y = float(item[1:])
if item[0] == "Z": if item[0] == "Z":
z = float(item[1:]) z = float(item[1:])
if item[0] == "F":
f = float(item[1:]) / 60
if item[0] == "E": if item[0] == "E":
e = float(item[1:]) e = float(item[1:])
if self._is_absolute_positioning and ((x is not None and x < 0) or (y is not None and y < 0)): if self._is_absolute_positioning and ((x is not None and x < 0) or (y is not None and y < 0)):
self._center_is_zero = True self._center_is_zero = True
params = self._position(x, y, z, e) params = self._position(x, y, z, f, e)
return func(position, params, path) return func(position, params, path)
return position return position
def _processTCode(self, T, line, position, path): def _processTCode(self, T, line, position, path):
self._extruder_number = T self._extruder_number = T
if self._extruder_number + 1 > len(position.e): if self._extruder_number + 1 > len(position.e):
self._extrusion_length_offset.extend([0] * (self._extruder_number - len(position.e) + 1))
position.e.extend([0] * (self._extruder_number - len(position.e) + 1)) position.e.extend([0] * (self._extruder_number - len(position.e) + 1))
return position return position
@ -240,6 +281,8 @@ class GCodeReader(MeshReader):
def read(self, file_name): def read(self, file_name):
Logger.log("d", "Preparing to load %s" % file_name) Logger.log("d", "Preparing to load %s" % file_name)
self._cancelled = False self._cancelled = False
# We obtain the filament diameter from the selected printer to calculate line widths
self._filament_diameter = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value")
scene_node = SceneNode() scene_node = SceneNode()
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no # Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
@ -277,7 +320,7 @@ class GCodeReader(MeshReader):
Logger.log("d", "Parsing %s..." % file_name) Logger.log("d", "Parsing %s..." % file_name)
current_position = self._position(0, 0, 0, [0]) current_position = self._position(0, 0, 0, 0, [0])
current_path = [] current_path = []
for line in file: for line in file:
@ -310,6 +353,7 @@ class GCodeReader(MeshReader):
else: else:
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type) Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
# When the layer change is reached, the polygon is computed so we have just one layer per layer per extruder
if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword: if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
try: try:
layer_number = int(line[len(self._layer_keyword):]) layer_number = int(line[len(self._layer_keyword):])
@ -325,17 +369,12 @@ class GCodeReader(MeshReader):
G = self._getInt(line, "G") G = self._getInt(line, "G")
if G is not None: if G is not None:
# When find a movement, the new posistion is calculated and added to the current_path, but
# don't need to create a polygon until the end of the layer
current_position = self._processGCode(G, line, current_position, current_path) current_position = self._processGCode(G, line, current_position, current_path)
# < 2 is a heuristic for a movement only, that should not be counted as a layer
if current_position.z > last_z and abs(current_position.z - last_z) < 2:
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
current_path.clear()
if not self._is_layers_in_file:
self._layer_number += 1
continue continue
# When changing the extruder, the polygon with the stored paths is computed
if line.startswith("T"): if line.startswith("T"):
T = self._getInt(line, "T") T = self._getInt(line, "T")
if T is not None: if T is not None:
@ -344,8 +383,8 @@ class GCodeReader(MeshReader):
current_position = self._processTCode(T, line, current_position, current_path) current_position = self._processTCode(T, line, current_position, current_path)
# "Flush" leftovers # "Flush" leftovers. Last layer paths are still stored
if not self._is_layers_in_file and len(current_path) > 1: if len(current_path) > 1:
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])): if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
self._layer_number += 1 self._layer_number += 1
current_path.clear() current_path.clear()

View file

@ -1,113 +0,0 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Resources import Resources
from UM.Scene.SceneNode import SceneNode
from UM.Scene.ToolHandle import ToolHandle
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.View.RenderPass import RenderPass
from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
from cura.Settings.ExtruderManager import ExtruderManager
import os.path
## RenderPass used to display g-code paths.
class LayerPass(RenderPass):
def __init__(self, width, height):
super().__init__("layerview", width, height)
self._layer_shader = None
self._tool_handle_shader = None
self._gl = OpenGL.getInstance().getBindingsObject()
self._scene = Application.getInstance().getController().getScene()
self._extruder_manager = ExtruderManager.getInstance()
self._layer_view = None
self._compatibility_mode = None
def setLayerView(self, layerview):
self._layer_view = layerview
self._compatibility_mode = layerview.getCompatibilityMode()
def render(self):
if not self._layer_shader:
if self._compatibility_mode:
shader_filename = "layers.shader"
else:
shader_filename = "layers3d.shader"
self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), shader_filename))
# Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
if self._layer_view:
self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getLayerViewType())
self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
self._layer_shader.setUniformValue("u_show_helpers", self._layer_view.getShowHelpers())
self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin())
self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill())
else:
#defaults
self._layer_shader.setUniformValue("u_layer_view_type", 1)
self._layer_shader.setUniformValue("u_extruder_opacity", [1, 1, 1, 1])
self._layer_shader.setUniformValue("u_show_travel_moves", 0)
self._layer_shader.setUniformValue("u_show_helpers", 1)
self._layer_shader.setUniformValue("u_show_skin", 1)
self._layer_shader.setUniformValue("u_show_infill", 1)
if not self._tool_handle_shader:
self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader"))
self.bind()
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay)
for node in DepthFirstIterator(self._scene.getRoot()):
if isinstance(node, ToolHandle):
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
# Render all layers below a certain number as line mesh instead of vertices.
if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
start = 0
end = 0
element_counts = layer_data.getElementCounts()
for layer in sorted(element_counts.keys()):
if layer > self._layer_view._current_layer_num:
break
if self._layer_view._minimum_layer_num > layer:
start += element_counts[layer]
end += element_counts[layer]
# This uses glDrawRangeElements internally to only draw a certain range of lines.
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end))
batch.addItem(node.getWorldTransformation(), layer_data)
batch.render(self._scene.getActiveCamera())
# Create a new batch that is not range-limited
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
if self._layer_view.getCurrentLayerMesh():
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh())
if self._layer_view.getCurrentLayerJumps():
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps())
if len(batch.items) > 0:
batch.render(self._scene.getActiveCamera())
# Render toolhandles on top of the layerview
if len(tool_handle_batch.items) > 0:
tool_handle_batch.render(self._scene.getActiveCamera())
self.release()

View file

@ -1,388 +0,0 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item
{
id: base
width: {
if (UM.LayerView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
} else {
return UM.Theme.getSize("layerview_menu_size").width;
}
}
height: {
if (UM.LayerView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
} else {
return UM.Theme.getSize("layerview_menu_size").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
}
}
property var buttonTarget: {
if(parent != null)
{
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y)
}
return Qt.point(0,0)
}
visible: parent != null ? !parent.parent.monitoringPrint: true
UM.PointingRectangle {
id: layerViewMenu
anchors.right: parent.right
anchors.top: parent.top
width: parent.width
height: parent.height
z: slider.z - 1
color: UM.Theme.getColor("tool_panel_background")
borderWidth: UM.Theme.getSize("default_lining").width
borderColor: UM.Theme.getColor("lining")
arrowSize: 0 // hide arrow until weird issue with first time rendering is fixed
ColumnLayout {
id: view_settings
property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
// if we are in compatibility mode, we only show the "line type"
property bool show_legend: UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type") == 1
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
spacing: UM.Theme.getSize("layerview_row_spacing").height
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
Label
{
id: layerViewTypesLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Color scheme")
font: UM.Theme.getFont("default");
visible: !UM.LayerView.compatibilityMode
Layout.fillWidth: true
color: UM.Theme.getColor("setting_control_text")
}
ListModel // matches LayerView.py
{
id: layerViewTypes
}
Component.onCompleted:
{
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Material Color"),
type_id: 0
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Line Type"),
type_id: 1 // these ids match the switching in the shader
})
}
ComboBox
{
id: layerTypeCombobox
anchors.left: parent.left
Layout.fillWidth: true
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
model: layerViewTypes
visible: !UM.LayerView.compatibilityMode
style: UM.Theme.styles.combobox
anchors.right: parent.right
anchors.rightMargin: 10 * screenScaleFactor
onActivated:
{
UM.Preferences.setValue("layerview/layer_view_type", index);
}
Component.onCompleted:
{
currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
updateLegends(currentIndex);
}
function updateLegends(type_id)
{
// update visibility of legends
view_settings.show_legend = UM.LayerView.compatibilityMode || (type_id == 1);
}
}
Label
{
id: compatibilityModeLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Compatibility Mode")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
visible: UM.LayerView.compatibilityMode
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
}
Label
{
id: space2Label
anchors.left: parent.left
text: " "
font.pointSize: 0.5
}
Connections {
target: UM.Preferences
onPreferenceChanged:
{
layerTypeCombobox.currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
view_settings.show_skin = UM.Preferences.getValue("layerview/show_skin");
view_settings.show_infill = UM.Preferences.getValue("layerview/show_infill");
view_settings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers");
view_settings.top_layer_count = UM.Preferences.getValue("view/top_layer_count");
}
}
Repeater {
model: Cura.ExtrudersModel{}
CheckBox {
id: extrudersModelCheckBox
checked: view_settings.extruder_opacities[index] > 0.5 || view_settings.extruder_opacities[index] == undefined || view_settings.extruder_opacities[index] == ""
onClicked: {
view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0
UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|"));
}
visible: !UM.LayerView.compatibilityMode
enabled: index + 1 <= 4
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: extrudersModelCheckBox.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: model.color
radius: width / 2
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: !view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: model.name
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
anchors.verticalCenter: parent.verticalCenter
anchors.left: extrudersModelCheckBox.left;
anchors.right: extrudersModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
Repeater {
model: ListModel {
id: typesLegenModel
Component.onCompleted:
{
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Travels"),
initialValue: view_settings.show_travel_moves,
preference: "layerview/show_travel_moves",
colorId: "layerview_move_combing"
});
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Helpers"),
initialValue: view_settings.show_helpers,
preference: "layerview/show_helpers",
colorId: "layerview_support"
});
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Shell"),
initialValue: view_settings.show_skin,
preference: "layerview/show_skin",
colorId: "layerview_inset_0"
});
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Infill"),
initialValue: view_settings.show_infill,
preference: "layerview/show_infill",
colorId: "layerview_infill"
});
}
}
CheckBox {
id: legendModelCheckBox
checked: model.initialValue
onClicked: {
UM.Preferences.setValue(model.preference, checked);
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: legendModelCheckBox.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: label
font: UM.Theme.getFont("default")
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
anchors.verticalCenter: parent.verticalCenter
anchors.left: legendModelCheckBox.left;
anchors.right: legendModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
CheckBox {
checked: view_settings.only_show_top_layers
onClicked: {
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
}
text: catalog.i18nc("@label", "Only Show Top Layers")
visible: UM.LayerView.compatibilityMode
style: UM.Theme.styles.checkbox
}
CheckBox {
checked: view_settings.top_layer_count == 5
onClicked: {
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
}
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
visible: UM.LayerView.compatibilityMode
style: UM.Theme.styles.checkbox
}
Repeater {
model: ListModel {
id: typesLegenModelNoCheck
Component.onCompleted:
{
typesLegenModelNoCheck.append({
label: catalog.i18nc("@label", "Top / Bottom"),
colorId: "layerview_skin",
});
typesLegenModelNoCheck.append({
label: catalog.i18nc("@label", "Inner Wall"),
colorId: "layerview_inset_x",
});
}
}
Label {
text: label
visible: view_settings.show_legend
id: typesLegendModelLabel
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: typesLegendModelLabel.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
}
}
}
LayerSlider {
id: slider
width: UM.Theme.getSize("slider_handle").width
height: UM.Theme.getSize("layerview_menu_size").height
anchors {
top: parent.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").height
right: layerViewMenu.right
rightMargin: UM.Theme.getSize("slider_layerview_margin").width
}
// custom properties
upperValue: UM.LayerView.currentLayer
lowerValue: UM.LayerView.minimumLayer
maximumValue: UM.LayerView.numLayers
handleSize: UM.Theme.getSize("slider_handle").width
trackThickness: UM.Theme.getSize("slider_groove").width
trackColor: UM.Theme.getColor("slider_groove")
trackBorderColor: UM.Theme.getColor("slider_groove_border")
upperHandleColor: UM.Theme.getColor("slider_handle")
lowerHandleColor: UM.Theme.getColor("slider_handle")
rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
layersVisible: UM.LayerView.layerActivity && CuraApplication.platformActivity ? true : false
// update values when layer data changes
Connections {
target: UM.LayerView
onMaxLayersChanged: slider.setUpperValue(UM.LayerView.currentLayer)
onMinimumLayerChanged: slider.setLowerValue(UM.LayerView.minimumLayer)
onCurrentLayerChanged: slider.setUpperValue(UM.LayerView.currentLayer)
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted: {
slider.setLowerValue(UM.LayerView.minimumLayer)
slider.setUpperValue(UM.LayerView.currentLayer)
}
}
}
FontMetrics {
id: fontMetrics
font: UM.Theme.getFont("default")
}
}

View file

@ -1,151 +0,0 @@
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
import LayerView
class LayerViewProxy(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._current_layer = 0
self._controller = Application.getInstance().getController()
self._controller.activeViewChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged()
currentLayerChanged = pyqtSignal()
maxLayersChanged = pyqtSignal()
activityChanged = pyqtSignal()
globalStackChanged = pyqtSignal()
preferencesChanged = pyqtSignal()
busyChanged = pyqtSignal()
@pyqtProperty(bool, notify=activityChanged)
def layerActivity(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getActivity()
@pyqtProperty(int, notify=maxLayersChanged)
def numLayers(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getMaxLayers()
@pyqtProperty(int, notify=currentLayerChanged)
def currentLayer(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getCurrentLayer()
@pyqtProperty(int, notify=currentLayerChanged)
def minimumLayer(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getMinimumLayer()
@pyqtProperty(bool, notify=busyChanged)
def busy(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.isBusy()
return False
@pyqtProperty(bool, notify=preferencesChanged)
def compatibilityMode(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getCompatibilityMode()
return False
@pyqtSlot(int)
def setCurrentLayer(self, layer_num):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setLayer(layer_num)
@pyqtSlot(int)
def setMinimumLayer(self, layer_num):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setMinimumLayer(layer_num)
@pyqtSlot(int)
def setLayerViewType(self, layer_view_type):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setLayerViewType(layer_view_type)
@pyqtSlot(result=int)
def getLayerViewType(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getLayerViewType()
return 0
# Opacity 0..1
@pyqtSlot(int, float)
def setExtruderOpacity(self, extruder_nr, opacity):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setExtruderOpacity(extruder_nr, opacity)
@pyqtSlot(int)
def setShowTravelMoves(self, show):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setShowTravelMoves(show)
@pyqtSlot(int)
def setShowHelpers(self, show):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setShowHelpers(show)
@pyqtSlot(int)
def setShowSkin(self, show):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setShowSkin(show)
@pyqtSlot(int)
def setShowInfill(self, show):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setShowInfill(show)
@pyqtProperty(int, notify=globalStackChanged)
def extruderCount(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getExtruderCount()
return 0
def _layerActivityChanged(self):
self.activityChanged.emit()
def _onLayerChanged(self):
self.currentLayerChanged.emit()
self._layerActivityChanged()
def _onMaxLayersChanged(self):
self.maxLayersChanged.emit()
def _onBusyChanged(self):
self.busyChanged.emit()
def _onGlobalStackChanged(self):
self.globalStackChanged.emit()
def _onPreferencesChanged(self):
self.preferencesChanged.emit()
def _onActiveViewChanged(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.currentLayerNumChanged.connect(self._onLayerChanged)
active_view.maxLayersChanged.connect(self._onMaxLayersChanged)
active_view.busyChanged.connect(self._onBusyChanged)
active_view.globalStackChanged.connect(self._onGlobalStackChanged)
active_view.preferencesChanged.connect(self._onPreferencesChanged)

View file

@ -1,25 +0,0 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import LayerView, LayerViewProxy
from PyQt5.QtQml import qmlRegisterType, qmlRegisterSingletonType
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"view": {
"name": catalog.i18nc("@item:inlistbox", "Layer view"),
"view_panel": "LayerView.qml",
"weight": 2
}
}
def createLayerViewProxy(engine, script_engine):
return LayerViewProxy.LayerViewProxy()
def register(app):
layer_view = LayerView.LayerView()
qmlRegisterSingletonType(LayerViewProxy.LayerViewProxy, "UM", 1, 0, "LayerView", layer_view.getProxy)
return { "view": LayerView.LayerView() }

View file

@ -19,6 +19,7 @@ Item {
property color upperHandleColor: "black" property color upperHandleColor: "black"
property color lowerHandleColor: "black" property color lowerHandleColor: "black"
property color rangeHandleColor: "black" property color rangeHandleColor: "black"
property color handleActiveColor: "white"
property real handleLabelWidth: width property real handleLabelWidth: width
property var activeHandle: upperHandle property var activeHandle: upperHandle
@ -100,8 +101,8 @@ Item {
var lowerValue = sliderRoot.getLowerValueFromSliderHandle() var lowerValue = sliderRoot.getLowerValueFromSliderHandle()
// set both values after moving the handle position // set both values after moving the handle position
UM.LayerView.setCurrentLayer(upperValue) UM.SimulationView.setCurrentLayer(upperValue)
UM.LayerView.setMinimumLayer(lowerValue) UM.SimulationView.setMinimumLayer(lowerValue)
} }
function setValue (value) { function setValue (value) {
@ -109,8 +110,8 @@ Item {
value = Math.min(value, sliderRoot.maximumValue) value = Math.min(value, sliderRoot.maximumValue)
value = Math.max(value, sliderRoot.minimumValue + range) value = Math.max(value, sliderRoot.minimumValue + range)
UM.LayerView.setCurrentLayer(value) UM.SimulationView.setCurrentLayer(value)
UM.LayerView.setMinimumLayer(value - range) UM.SimulationView.setMinimumLayer(value - range)
} }
Rectangle { Rectangle {
@ -134,7 +135,7 @@ Item {
onPressed: sliderRoot.setActiveHandle(rangeHandle) onPressed: sliderRoot.setActiveHandle(rangeHandle)
} }
LayerSliderLabel { SimulationSliderLabel {
id: rangleHandleLabel id: rangleHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -146,7 +147,7 @@ Item {
// custom properties // custom properties
maximumValue: sliderRoot.maximumValue maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue value: sliderRoot.upperValue
busy: UM.LayerView.busy busy: UM.SimulationView.busy
setValue: rangeHandle.setValue // connect callback functions setValue: rangeHandle.setValue // connect callback functions
} }
} }
@ -160,7 +161,7 @@ Item {
height: sliderRoot.handleSize height: sliderRoot.handleSize
anchors.horizontalCenter: sliderRoot.horizontalCenter anchors.horizontalCenter: sliderRoot.horizontalCenter
radius: sliderRoot.handleRadius radius: sliderRoot.handleRadius
color: sliderRoot.upperHandleColor color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
visible: sliderRoot.layersVisible visible: sliderRoot.layersVisible
function onHandleDragged () { function onHandleDragged () {
@ -174,7 +175,7 @@ Item {
sliderRoot.updateRangeHandle() sliderRoot.updateRangeHandle()
// set the new value after moving the handle position // set the new value after moving the handle position
UM.LayerView.setCurrentLayer(getValue()) UM.SimulationView.setCurrentLayer(getValue())
} }
// get the upper value based on the slider position // get the upper value based on the slider position
@ -188,7 +189,7 @@ Item {
// set the slider position based on the upper value // set the slider position based on the upper value
function setValue (value) { function setValue (value) {
UM.LayerView.setCurrentLayer(value) UM.SimulationView.setCurrentLayer(value)
var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue) var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))) var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
@ -198,6 +199,9 @@ Item {
sliderRoot.updateRangeHandle() sliderRoot.updateRangeHandle()
} }
Keys.onUpPressed: upperHandleLabel.setValue(upperHandleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onDownPressed: upperHandleLabel.setValue(upperHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging // dragging
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -210,10 +214,13 @@ Item {
} }
onPositionChanged: parent.onHandleDragged() onPositionChanged: parent.onHandleDragged()
onPressed: sliderRoot.setActiveHandle(upperHandle) onPressed: {
sliderRoot.setActiveHandle(upperHandle)
upperHandleLabel.forceActiveFocus()
}
} }
LayerSliderLabel { SimulationSliderLabel {
id: upperHandleLabel id: upperHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -225,7 +232,7 @@ Item {
// custom properties // custom properties
maximumValue: sliderRoot.maximumValue maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue value: sliderRoot.upperValue
busy: UM.LayerView.busy busy: UM.SimulationView.busy
setValue: upperHandle.setValue // connect callback functions setValue: upperHandle.setValue // connect callback functions
} }
} }
@ -239,9 +246,9 @@ Item {
height: parent.handleSize height: parent.handleSize
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
radius: sliderRoot.handleRadius radius: sliderRoot.handleRadius
color: sliderRoot.lowerHandleColor color: lowerHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.lowerHandleColor
visible: slider.layersVisible visible: sliderRoot.layersVisible
function onHandleDragged () { function onHandleDragged () {
@ -254,7 +261,7 @@ Item {
sliderRoot.updateRangeHandle() sliderRoot.updateRangeHandle()
// set the new value after moving the handle position // set the new value after moving the handle position
UM.LayerView.setMinimumLayer(getValue()) UM.SimulationView.setMinimumLayer(getValue())
} }
// get the lower value from the current slider position // get the lower value from the current slider position
@ -268,7 +275,7 @@ Item {
// set the slider position based on the lower value // set the slider position based on the lower value
function setValue (value) { function setValue (value) {
UM.LayerView.setMinimumLayer(value) UM.SimulationView.setMinimumLayer(value)
var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue) var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
var newLowerYPosition = Math.round((sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) + diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))) var newLowerYPosition = Math.round((sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) + diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
@ -278,6 +285,9 @@ Item {
sliderRoot.updateRangeHandle() sliderRoot.updateRangeHandle()
} }
Keys.onUpPressed: lowerHandleLabel.setValue(lowerHandleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onDownPressed: lowerHandleLabel.setValue(lowerHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging // dragging
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -290,10 +300,13 @@ Item {
} }
onPositionChanged: parent.onHandleDragged() onPositionChanged: parent.onHandleDragged()
onPressed: sliderRoot.setActiveHandle(lowerHandle) onPressed: {
sliderRoot.setActiveHandle(lowerHandle)
lowerHandleLabel.forceActiveFocus()
}
} }
LayerSliderLabel { SimulationSliderLabel {
id: lowerHandleLabel id: lowerHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -305,7 +318,7 @@ Item {
// custom properties // custom properties
maximumValue: sliderRoot.maximumValue maximumValue: sliderRoot.maximumValue
value: sliderRoot.lowerValue value: sliderRoot.lowerValue
busy: UM.LayerView.busy busy: UM.SimulationView.busy
setValue: lowerHandle.setValue // connect callback functions setValue: lowerHandle.setValue // connect callback functions
} }
} }

View file

@ -0,0 +1,49 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Math.Color import Color
from UM.Math.Vector import Vector
from UM.PluginRegistry import PluginRegistry
from UM.Scene.SceneNode import SceneNode
from UM.View.GL.OpenGL import OpenGL
from UM.Resources import Resources
import os
class NozzleNode(SceneNode):
def __init__(self, parent = None):
super().__init__(parent)
self._shader = None
self.setCalculateBoundingBox(False)
self._createNozzleMesh()
def _createNozzleMesh(self):
mesh_file = "resources/nozzle.stl"
try:
path = os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), mesh_file)
except FileNotFoundError:
path = ""
reader = Application.getInstance().getMeshFileHandler().getReaderForFile(path)
node = reader.read(path)
if node.getMeshData():
self.setMeshData(node.getMeshData())
def render(self, renderer):
# Avoid to render if it is not visible
if not self.isVisible():
return False
if not self._shader:
# We now misuse the platform shader, as it actually supports textures
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
self._shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_nozzle").getRgb()))
# Set the opacity to 0, so that the template is in full control.
self._shader.setUniformValue("u_opacity", 0)
if self.getMeshData():
renderer.queueNode(self, shader = self._shader, transparent = True)
return True

View file

@ -0,0 +1,161 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item {
id: sliderRoot
// handle properties
property real handleSize: 10
property real handleRadius: handleSize / 2
property color handleColor: "black"
property color handleActiveColor: "white"
property color rangeColor: "black"
property real handleLabelWidth: width
// track properties
property real trackThickness: 4 // width of the slider track
property real trackRadius: trackThickness / 2
property color trackColor: "white"
property real trackBorderWidth: 1 // width of the slider track border
property color trackBorderColor: "black"
// value properties
property real maximumValue: 100
property bool roundValues: true
property real handleValue: maximumValue
property bool pathsVisible: true
function getHandleValueFromSliderHandle () {
return handle.getValue()
}
function setHandleValue (value) {
handle.setValue(value)
updateRangeHandle()
}
function updateRangeHandle () {
rangeHandle.width = handle.x - sliderRoot.handleSize
}
// slider track
Rectangle {
id: track
width: sliderRoot.width - sliderRoot.handleSize
height: sliderRoot.trackThickness
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
border.width: sliderRoot.trackBorderWidth
border.color: sliderRoot.trackBorderColor
visible: sliderRoot.pathsVisible
}
// Progress indicator
Item {
id: rangeHandle
x: handle.width
height: sliderRoot.handleSize
width: handle.x - sliderRoot.handleSize
anchors.verticalCenter: sliderRoot.verticalCenter
visible: sliderRoot.pathsVisible
Rectangle {
height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
width: parent.width + sliderRoot.handleSize
anchors.centerIn: parent
color: sliderRoot.rangeColor
}
}
// Handle
Rectangle {
id: handle
x: sliderRoot.handleSize
width: sliderRoot.handleSize
height: sliderRoot.handleSize
anchors.verticalCenter: sliderRoot.verticalCenter
radius: sliderRoot.handleRadius
color: handleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.handleColor
visible: sliderRoot.pathsVisible
function onHandleDragged () {
// update the range handle
sliderRoot.updateRangeHandle()
// set the new value after moving the handle position
UM.SimulationView.setCurrentPath(getValue())
}
// get the value based on the slider position
function getValue () {
var result = x / (sliderRoot.width - sliderRoot.handleSize)
result = result * sliderRoot.maximumValue
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
// set the slider position based on the value
function setValue (value) {
UM.SimulationView.setCurrentPath(value)
var diff = value / sliderRoot.maximumValue
var newXPosition = Math.round(diff * (sliderRoot.width - sliderRoot.handleSize))
x = newXPosition
// update the range handle
sliderRoot.updateRangeHandle()
}
Keys.onRightPressed: handleLabel.setValue(handleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onLeftPressed: handleLabel.setValue(handleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging
MouseArea {
anchors.fill: parent
drag {
target: parent
axis: Drag.XAxis
minimumX: 0
maximumX: sliderRoot.width - sliderRoot.handleSize
}
onPressed: {
handleLabel.forceActiveFocus()
}
onPositionChanged: parent.onHandleDragged()
}
SimulationSliderLabel {
id: handleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
y: parent.y + sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
target: Qt.point(x + width / 2, sliderRoot.height)
visible: false
startFrom: 0
// custom properties
maximumValue: sliderRoot.maximumValue
value: sliderRoot.handleValue
busy: UM.SimulationView.busy
setValue: handle.setValue // connect callback functions
}
}
}

View file

@ -0,0 +1,187 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Math.Color import Color
from UM.Math.Vector import Vector
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Resources import Resources
from UM.Scene.SceneNode import SceneNode
from UM.Scene.ToolHandle import ToolHandle
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.View.RenderPass import RenderPass
from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
from cura.Settings.ExtruderManager import ExtruderManager
import os.path
## RenderPass used to display g-code paths.
from plugins.SimulationView.NozzleNode import NozzleNode
class SimulationPass(RenderPass):
def __init__(self, width, height):
super().__init__("simulationview", width, height)
self._layer_shader = None
self._layer_shadow_shader = None
self._current_shader = None # This shader will be the shadow or the normal depending if the user wants to see the paths or the layers
self._tool_handle_shader = None
self._nozzle_shader = None
self._old_current_layer = 0
self._old_current_path = 0
self._gl = OpenGL.getInstance().getBindingsObject()
self._scene = Application.getInstance().getController().getScene()
self._extruder_manager = ExtruderManager.getInstance()
self._layer_view = None
self._compatibility_mode = None
def setSimulationView(self, layerview):
self._layer_view = layerview
self._compatibility_mode = layerview.getCompatibilityMode()
def render(self):
if not self._layer_shader:
if self._compatibility_mode:
shader_filename = "layers.shader"
shadow_shader_filename = "layers_shadow.shader"
else:
shader_filename = "layers3d.shader"
shadow_shader_filename = "layers3d_shadow.shader"
self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shader_filename))
self._layer_shadow_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shadow_shader_filename))
self._current_shader = self._layer_shader
# Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
if self._layer_view:
self._layer_shader.setUniformValue("u_max_feedrate", self._layer_view.getMaxFeedrate())
self._layer_shader.setUniformValue("u_min_feedrate", self._layer_view.getMinFeedrate())
self._layer_shader.setUniformValue("u_max_thickness", self._layer_view.getMaxThickness())
self._layer_shader.setUniformValue("u_min_thickness", self._layer_view.getMinThickness())
self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getSimulationViewType())
self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
self._layer_shader.setUniformValue("u_show_helpers", self._layer_view.getShowHelpers())
self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin())
self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill())
else:
#defaults
self._layer_shader.setUniformValue("u_max_feedrate", 1)
self._layer_shader.setUniformValue("u_min_feedrate", 0)
self._layer_shader.setUniformValue("u_max_thickness", 1)
self._layer_shader.setUniformValue("u_min_thickness", 0)
self._layer_shader.setUniformValue("u_layer_view_type", 1)
self._layer_shader.setUniformValue("u_extruder_opacity", [1, 1, 1, 1])
self._layer_shader.setUniformValue("u_show_travel_moves", 0)
self._layer_shader.setUniformValue("u_show_helpers", 1)
self._layer_shader.setUniformValue("u_show_skin", 1)
self._layer_shader.setUniformValue("u_show_infill", 1)
if not self._tool_handle_shader:
self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader"))
if not self._nozzle_shader:
self._nozzle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
self._nozzle_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_nozzle").getRgb()))
self.bind()
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Solid)
head_position = None # Indicates the current position of the print head
nozzle_node = None
for node in DepthFirstIterator(self._scene.getRoot()):
if isinstance(node, ToolHandle):
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
elif isinstance(node, NozzleNode):
nozzle_node = node
nozzle_node.setVisible(False)
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
# Render all layers below a certain number as line mesh instead of vertices.
if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
start = 0
end = 0
element_counts = layer_data.getElementCounts()
for layer in sorted(element_counts.keys()):
# In the current layer, we show just the indicated paths
if layer == self._layer_view._current_layer_num:
# We look for the position of the head, searching the point of the current path
index = self._layer_view._current_path_num
offset = 0
for polygon in layer_data.getLayer(layer).polygons:
# The size indicates all values in the two-dimension array, and the second dimension is
# always size 3 because we have 3D points.
if index >= polygon.data.size // 3 - offset:
index -= polygon.data.size // 3 - offset
offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
continue
# The head position is calculated and translated
head_position = Vector(polygon.data[index+offset][0], polygon.data[index+offset][1], polygon.data[index+offset][2]) + node.getWorldPosition()
break
break
if self._layer_view._minimum_layer_num > layer:
start += element_counts[layer]
end += element_counts[layer]
# Calculate the range of paths in the last layer
current_layer_start = end
current_layer_end = end + self._layer_view._current_path_num * 2 # Because each point is used twice
# This uses glDrawRangeElements internally to only draw a certain range of lines.
# All the layers but the current selected layer are rendered first
if self._old_current_path != self._layer_view._current_path_num:
self._current_shader = self._layer_shadow_shader
if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view._current_layer_num:
self._current_shader = self._layer_shader
layers_batch = RenderBatch(self._current_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end))
layers_batch.addItem(node.getWorldTransformation(), layer_data)
layers_batch.render(self._scene.getActiveCamera())
# Current selected layer is rendered
current_layer_batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (current_layer_start, current_layer_end))
current_layer_batch.addItem(node.getWorldTransformation(), layer_data)
current_layer_batch.render(self._scene.getActiveCamera())
self._old_current_layer = self._layer_view._current_layer_num
self._old_current_path = self._layer_view._current_path_num
# Create a new batch that is not range-limited
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
if self._layer_view.getCurrentLayerMesh():
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh())
if self._layer_view.getCurrentLayerJumps():
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps())
if len(batch.items) > 0:
batch.render(self._scene.getActiveCamera())
# The nozzle is drawn once we know the correct position
if self._layer_view.getActivity() and nozzle_node is not None:
if head_position is not None:
nozzle_node.setVisible(True)
nozzle_node.setPosition(head_position)
nozzle_batch = RenderBatch(self._nozzle_shader, type = RenderBatch.RenderType.Solid)
nozzle_batch.addItem(nozzle_node.getWorldTransformation(), mesh = nozzle_node.getMeshData())
nozzle_batch.render(self._scene.getActiveCamera())
# Render toolhandles on top of the layerview
if len(tool_handle_batch.items) > 0:
tool_handle_batch.render(self._scene.getActiveCamera())
self.release()

View file

@ -17,6 +17,7 @@ UM.PointingRectangle {
property real value: 0 property real value: 0
property var setValue // Function property var setValue // Function
property bool busy: false property bool busy: false
property int startFrom: 1
target: Qt.point(parent.width, y + height / 2) target: Qt.point(parent.width, y + height / 2)
arrowSize: UM.Theme.getSize("default_arrow").width arrowSize: UM.Theme.getSize("default_arrow").width
@ -53,7 +54,7 @@ UM.PointingRectangle {
} }
width: 40 * screenScaleFactor width: 40 * screenScaleFactor
text: sliderLabelRoot.value + 1 // the current handle value, add 1 because layers is an array text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
horizontalAlignment: TextInput.AlignRight horizontalAlignment: TextInput.AlignRight
// key bindings, work when label is currenctly focused (active handle in LayerSlider) // key bindings, work when label is currenctly focused (active handle in LayerSlider)
@ -74,14 +75,14 @@ UM.PointingRectangle {
cursorPosition = 0 cursorPosition = 0
if (valueLabel.text != "") { if (valueLabel.text != "") {
// -1 because we need to convert back to an array structure // -startFrom because we need to convert back to an array structure
sliderLabelRoot.setValue(parseInt(valueLabel.text) - 1) sliderLabelRoot.setValue(parseInt(valueLabel.text) - startFrom)
} }
} }
validator: IntValidator { validator: IntValidator {
bottom: 1 bottom:startFrom
top: sliderLabelRoot.maximumValue + 1 // +1 because actual layers is an array top: sliderLabelRoot.maximumValue + startFrom // +startFrom because maybe we want to start in a different value rather than 0
} }
} }

View file

@ -1,46 +1,46 @@
# Copyright (c) 2015 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import sys import sys
from UM.PluginRegistry import PluginRegistry
from UM.View.View import View
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Resources import Resources
from UM.Event import Event, KeyEvent
from UM.Signal import Signal
from UM.Scene.Selection import Selection
from UM.Math.Color import Color
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Job import Job
from UM.Preferences import Preferences
from UM.Logger import Logger
from UM.View.GL.OpenGL import OpenGL
from UM.Message import Message
from UM.Application import Application
from UM.View.GL.OpenGLContext import OpenGLContext
from cura.ConvexHullNode import ConvexHullNode
from cura.Settings.ExtruderManager import ExtruderManager
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from . import LayerViewProxy from UM.Application import Application
from UM.Event import Event, KeyEvent
from UM.Job import Job
from UM.Logger import Logger
from UM.Math.Color import Color
from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Message import Message
from UM.PluginRegistry import PluginRegistry
from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
from UM.Signal import Signal
from UM.View.GL.OpenGL import OpenGL
from UM.View.GL.OpenGLContext import OpenGLContext
from UM.View.View import View
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") from cura.ConvexHullNode import ConvexHullNode
from plugins.SimulationView.NozzleNode import NozzleNode
from . import SimulationPass, SimulationViewProxy
from . import LayerPass catalog = i18nCatalog("cura")
import numpy import numpy
import os.path import os.path
## View used to display g-code paths. ## View used to display g-code paths.
class LayerView(View): class SimulationView(View):
# Must match LayerView.qml # Must match SimulationView.qml
LAYER_VIEW_TYPE_MATERIAL_TYPE = 0 LAYER_VIEW_TYPE_MATERIAL_TYPE = 0
LAYER_VIEW_TYPE_LINE_TYPE = 1 LAYER_VIEW_TYPE_LINE_TYPE = 1
LAYER_VIEW_TYPE_FEEDRATE = 2
LAYER_VIEW_TYPE_THICKNESS = 3
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -54,22 +54,29 @@ class LayerView(View):
self._activity = False self._activity = False
self._old_max_layers = 0 self._old_max_layers = 0
self._max_paths = 0
self._current_path_num = 0
self._minimum_path_num = 0
self.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged)
self._busy = False self._busy = False
self._simulation_running = False
self._ghost_shader = None self._ghost_shader = None
self._layer_pass = None self._layer_pass = None
self._composite_pass = None self._composite_pass = None
self._old_layer_bindings = None self._old_layer_bindings = None
self._layerview_composite_shader = None self._simulationview_composite_shader = None
self._old_composite_shader = None self._old_composite_shader = None
self._global_container_stack = None self._global_container_stack = None
self._proxy = LayerViewProxy.LayerViewProxy() self._proxy = SimulationViewProxy.SimulationViewProxy()
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._resetSettings() self._resetSettings()
self._legend_items = None self._legend_items = None
self._show_travel_moves = False self._show_travel_moves = False
self._nozzle_node = None
Preferences.getInstance().addPreference("view/top_layer_count", 5) Preferences.getInstance().addPreference("view/top_layer_count", 5)
Preferences.getInstance().addPreference("view/only_show_top_layers", False) Preferences.getInstance().addPreference("view/only_show_top_layers", False)
@ -91,7 +98,7 @@ class LayerView(View):
self._compatibility_mode = True # for safety self._compatibility_mode = True # for safety
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"), self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
title = catalog.i18nc("@info:title", "Layer View")) title = catalog.i18nc("@info:title", "Simulation View"))
def _resetSettings(self): def _resetSettings(self):
self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed
@ -101,17 +108,24 @@ class LayerView(View):
self._show_helpers = 1 self._show_helpers = 1
self._show_skin = 1 self._show_skin = 1
self._show_infill = 1 self._show_infill = 1
self.resetLayerData()
def getActivity(self): def getActivity(self):
return self._activity return self._activity
def getLayerPass(self): def setActivity(self, activity):
if self._activity == activity:
return
self._activity = activity
self.activityChanged.emit()
def getSimulationPass(self):
if not self._layer_pass: if not self._layer_pass:
# Currently the RenderPass constructor requires a size > 0 # Currently the RenderPass constructor requires a size > 0
# This should be fixed in RenderPass's constructor. # This should be fixed in RenderPass's constructor.
self._layer_pass = LayerPass.LayerPass(1, 1) self._layer_pass = SimulationPass.SimulationPass(1, 1)
self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")) self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
self._layer_pass.setLayerView(self) self._layer_pass.setSimulationView(self)
return self._layer_pass return self._layer_pass
def getCurrentLayer(self): def getCurrentLayer(self):
@ -120,13 +134,26 @@ class LayerView(View):
def getMinimumLayer(self): def getMinimumLayer(self):
return self._minimum_layer_num return self._minimum_layer_num
def _onSceneChanged(self, node):
self.calculateMaxLayers()
def getMaxLayers(self): def getMaxLayers(self):
return self._max_layers return self._max_layers
busyChanged = Signal() def getCurrentPath(self):
return self._current_path_num
def getMinimumPath(self):
return self._minimum_path_num
def getMaxPaths(self):
return self._max_paths
def getNozzleNode(self):
if not self._nozzle_node:
self._nozzle_node = NozzleNode()
return self._nozzle_node
def _onSceneChanged(self, node):
self.setActivity(False)
self.calculateMaxLayers()
def isBusy(self): def isBusy(self):
return self._busy return self._busy
@ -136,9 +163,19 @@ class LayerView(View):
self._busy = busy self._busy = busy
self.busyChanged.emit() self.busyChanged.emit()
def isSimulationRunning(self):
return self._simulation_running
def setSimulationRunning(self, running):
self._simulation_running = running
def resetLayerData(self): def resetLayerData(self):
self._current_layer_mesh = None self._current_layer_mesh = None
self._current_layer_jumps = None self._current_layer_jumps = None
self._max_feedrate = sys.float_info.min
self._min_feedrate = sys.float_info.max
self._max_thickness = sys.float_info.min
self._min_thickness = sys.float_info.max
def beginRendering(self): def beginRendering(self):
scene = self.getController().getScene() scene = self.getController().getScene()
@ -186,15 +223,43 @@ class LayerView(View):
self.currentLayerNumChanged.emit() self.currentLayerNumChanged.emit()
def setPath(self, value):
if self._current_path_num != value:
self._current_path_num = value
if self._current_path_num < 0:
self._current_path_num = 0
if self._current_path_num > self._max_paths:
self._current_path_num = self._max_paths
if self._current_path_num < self._minimum_path_num:
self._minimum_path_num = self._current_path_num
self._startUpdateTopLayers()
self.currentPathNumChanged.emit()
def setMinimumPath(self, value):
if self._minimum_path_num != value:
self._minimum_path_num = value
if self._minimum_path_num < 0:
self._minimum_path_num = 0
if self._minimum_path_num > self._max_layers:
self._minimum_path_num = self._max_layers
if self._minimum_path_num > self._current_path_num:
self._current_path_num = self._minimum_path_num
self._startUpdateTopLayers()
self.currentPathNumChanged.emit()
## Set the layer view type ## Set the layer view type
# #
# \param layer_view_type integer as in LayerView.qml and this class # \param layer_view_type integer as in SimulationView.qml and this class
def setLayerViewType(self, layer_view_type): def setSimulationViewType(self, layer_view_type):
self._layer_view_type = layer_view_type self._layer_view_type = layer_view_type
self.currentLayerNumChanged.emit() self.currentLayerNumChanged.emit()
## Return the layer view type, integer as in LayerView.qml and this class ## Return the layer view type, integer as in SimulationView.qml and this class
def getLayerViewType(self): def getSimulationViewType(self):
return self._layer_view_type return self._layer_view_type
## Set the extruder opacity ## Set the extruder opacity
@ -243,9 +308,20 @@ class LayerView(View):
def getExtruderCount(self): def getExtruderCount(self):
return self._extruder_count return self._extruder_count
def getMinFeedrate(self):
return self._min_feedrate
def getMaxFeedrate(self):
return self._max_feedrate
def getMinThickness(self):
return self._min_thickness
def getMaxThickness(self):
return self._max_thickness
def calculateMaxLayers(self): def calculateMaxLayers(self):
scene = self.getController().getScene() scene = self.getController().getScene()
self._activity = True
self._old_max_layers = self._max_layers self._old_max_layers = self._max_layers
## Recalculate num max layers ## Recalculate num max layers
@ -255,9 +331,16 @@ class LayerView(View):
if not layer_data: if not layer_data:
continue continue
self.setActivity(True)
min_layer_number = sys.maxsize min_layer_number = sys.maxsize
max_layer_number = -sys.maxsize max_layer_number = -sys.maxsize
for layer_id in layer_data.getLayers(): for layer_id in layer_data.getLayers():
# Store the max and min feedrates and thicknesses for display purposes
for p in layer_data.getLayer(layer_id).polygons:
self._max_feedrate = max(float(p.lineFeedrates.max()), self._max_feedrate)
self._min_feedrate = min(float(p.lineFeedrates.min()), self._min_feedrate)
self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness)
self._min_thickness = min(float(p.lineThicknesses.min()), self._min_thickness)
if max_layer_number < layer_id: if max_layer_number < layer_id:
max_layer_number = layer_id max_layer_number = layer_id
if min_layer_number > layer_id: if min_layer_number > layer_id:
@ -281,10 +364,32 @@ class LayerView(View):
self.maxLayersChanged.emit() self.maxLayersChanged.emit()
self._startUpdateTopLayers() self._startUpdateTopLayers()
def calculateMaxPathsOnLayer(self, layer_num):
# Update the currentPath
scene = self.getController().getScene()
for node in DepthFirstIterator(scene.getRoot()):
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
layer = layer_data.getLayer(layer_num)
if layer is None:
return
new_max_paths = layer.lineMeshElementCount()
if new_max_paths > 0 and new_max_paths != self._max_paths:
self._max_paths = new_max_paths
self.maxPathsChanged.emit()
self.setPath(int(new_max_paths))
maxLayersChanged = Signal() maxLayersChanged = Signal()
maxPathsChanged = Signal()
currentLayerNumChanged = Signal() currentLayerNumChanged = Signal()
currentPathNumChanged = Signal()
globalStackChanged = Signal() globalStackChanged = Signal()
preferencesChanged = Signal() preferencesChanged = Signal()
busyChanged = Signal()
activityChanged = Signal()
## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
# as this caused some issues. # as this caused some issues.
@ -308,26 +413,31 @@ class LayerView(View):
return True return True
if event.type == Event.ViewActivateEvent: if event.type == Event.ViewActivateEvent:
# Make sure the LayerPass is created # Make sure the SimulationPass is created
layer_pass = self.getLayerPass() layer_pass = self.getSimulationPass()
self.getRenderer().addRenderPass(layer_pass) self.getRenderer().addRenderPass(layer_pass)
# Make sure the NozzleNode is add to the root
nozzle = self.getNozzleNode()
nozzle.setParent(self.getController().getScene().getRoot())
nozzle.setVisible(False)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged() self._onGlobalStackChanged()
if not self._layerview_composite_shader: if not self._simulationview_composite_shader:
self._layerview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), "layerview_composite.shader")) self._simulationview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), "simulationview_composite.shader"))
theme = Application.getInstance().getTheme() theme = Application.getInstance().getTheme()
self._layerview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb())) self._simulationview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb()))
self._layerview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb())) self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))
if not self._composite_pass: if not self._composite_pass:
self._composite_pass = self.getRenderer().getRenderPass("composite") self._composite_pass = self.getRenderer().getRenderPass("composite")
self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later
self._composite_pass.getLayerBindings().append("layerview") self._composite_pass.getLayerBindings().append("simulationview")
self._old_composite_shader = self._composite_pass.getCompositeShader() self._old_composite_shader = self._composite_pass.getCompositeShader()
self._composite_pass.setCompositeShader(self._layerview_composite_shader) self._composite_pass.setCompositeShader(self._simulationview_composite_shader)
elif event.type == Event.ViewDeactivateEvent: elif event.type == Event.ViewDeactivateEvent:
self._wireprint_warning_message.hide() self._wireprint_warning_message.hide()
@ -335,6 +445,7 @@ class LayerView(View):
if self._global_container_stack: if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
self._nozzle_node.setParent(None)
self.getRenderer().removeRenderPass(self._layer_pass) self.getRenderer().removeRenderPass(self._layer_pass)
self._composite_pass.setLayerBindings(self._old_layer_bindings) self._composite_pass.setLayerBindings(self._old_layer_bindings)
self._composite_pass.setCompositeShader(self._old_composite_shader) self._composite_pass.setCompositeShader(self._old_composite_shader)
@ -364,6 +475,9 @@ class LayerView(View):
else: else:
self._wireprint_warning_message.hide() self._wireprint_warning_message.hide()
def _onCurrentLayerNumChanged(self):
self.calculateMaxPathsOnLayer(self._current_layer_num)
def _startUpdateTopLayers(self): def _startUpdateTopLayers(self):
if not self._compatibility_mode: if not self._compatibility_mode:
return return
@ -397,7 +511,7 @@ class LayerView(View):
self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool( self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(
Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")) Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
self.setLayerViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type")))); self.setSimulationViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type"))));
for extruder_nr, extruder_opacity in enumerate(Preferences.getInstance().getValue("layerview/extruder_opacities").split("|")): for extruder_nr, extruder_opacity in enumerate(Preferences.getInstance().getValue("layerview/extruder_opacities").split("|")):
try: try:

View file

@ -0,0 +1,645 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item
{
id: base
width: {
if (UM.SimulationView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
} else {
return UM.Theme.getSize("layerview_menu_size").width;
}
}
height: {
if (UM.SimulationView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
} else {
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
}
}
property var buttonTarget: {
if(parent != null)
{
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y)
}
return Qt.point(0,0)
}
visible: parent != null ? !parent.parent.monitoringPrint: true
UM.PointingRectangle {
id: layerViewMenu
anchors.right: parent.right
anchors.top: parent.top
width: parent.width
height: parent.height
z: layerSlider.z - 1
color: UM.Theme.getColor("tool_panel_background")
borderWidth: UM.Theme.getSize("default_lining").width
borderColor: UM.Theme.getColor("lining")
arrowSize: 0 // hide arrow until weird issue with first time rendering is fixed
ColumnLayout {
id: view_settings
property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
// if we are in compatibility mode, we only show the "line type"
property bool show_legend: UM.SimulationView.compatibilityMode ? true : UM.Preferences.getValue("layerview/layer_view_type") == 1
property bool show_gradient: UM.SimulationView.compatibilityMode ? false : UM.Preferences.getValue("layerview/layer_view_type") == 2 || UM.Preferences.getValue("layerview/layer_view_type") == 3
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
spacing: UM.Theme.getSize("layerview_row_spacing").height
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
Label
{
id: layerViewTypesLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Color scheme")
font: UM.Theme.getFont("default");
visible: !UM.SimulationView.compatibilityMode
Layout.fillWidth: true
color: UM.Theme.getColor("setting_control_text")
}
ListModel // matches SimulationView.py
{
id: layerViewTypes
}
Component.onCompleted:
{
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Material Color"),
type_id: 0
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Line Type"),
type_id: 1
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Feedrate"),
type_id: 2
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Layer thickness"),
type_id: 3 // these ids match the switching in the shader
})
}
ComboBox
{
id: layerTypeCombobox
anchors.left: parent.left
Layout.fillWidth: true
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
model: layerViewTypes
visible: !UM.SimulationView.compatibilityMode
style: UM.Theme.styles.combobox
anchors.right: parent.right
anchors.rightMargin: 10 * screenScaleFactor
onActivated:
{
UM.Preferences.setValue("layerview/layer_view_type", index);
}
Component.onCompleted:
{
currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
updateLegends(currentIndex);
}
function updateLegends(type_id)
{
// update visibility of legends
view_settings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1);
}
}
Label
{
id: compatibilityModeLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Compatibility Mode")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
visible: UM.SimulationView.compatibilityMode
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
}
Label
{
id: space2Label
anchors.left: parent.left
text: " "
font.pointSize: 0.5
}
Connections {
target: UM.Preferences
onPreferenceChanged:
{
layerTypeCombobox.currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
view_settings.show_skin = UM.Preferences.getValue("layerview/show_skin");
view_settings.show_infill = UM.Preferences.getValue("layerview/show_infill");
view_settings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers");
view_settings.top_layer_count = UM.Preferences.getValue("view/top_layer_count");
}
}
Repeater {
model: Cura.ExtrudersModel{}
CheckBox {
id: extrudersModelCheckBox
checked: view_settings.extruder_opacities[index] > 0.5 || view_settings.extruder_opacities[index] == undefined || view_settings.extruder_opacities[index] == ""
onClicked: {
view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0
UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|"));
}
visible: !UM.SimulationView.compatibilityMode
enabled: index + 1 <= 4
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: extrudersModelCheckBox.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: model.color
radius: width / 2
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: !view_settings.show_legend & !view_settings.show_gradient
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: model.name
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
anchors.verticalCenter: parent.verticalCenter
anchors.left: extrudersModelCheckBox.left;
anchors.right: extrudersModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
Repeater {
model: ListModel {
id: typesLegendModel
Component.onCompleted:
{
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Travels"),
initialValue: view_settings.show_travel_moves,
preference: "layerview/show_travel_moves",
colorId: "layerview_move_combing"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Helpers"),
initialValue: view_settings.show_helpers,
preference: "layerview/show_helpers",
colorId: "layerview_support"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Shell"),
initialValue: view_settings.show_skin,
preference: "layerview/show_skin",
colorId: "layerview_inset_0"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Infill"),
initialValue: view_settings.show_infill,
preference: "layerview/show_infill",
colorId: "layerview_infill"
});
}
}
CheckBox {
id: legendModelCheckBox
checked: model.initialValue
onClicked: {
UM.Preferences.setValue(model.preference, checked);
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: legendModelCheckBox.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: label
font: UM.Theme.getFont("default")
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
anchors.verticalCenter: parent.verticalCenter
anchors.left: legendModelCheckBox.left;
anchors.right: legendModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
CheckBox {
checked: view_settings.only_show_top_layers
onClicked: {
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
}
text: catalog.i18nc("@label", "Only Show Top Layers")
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
}
CheckBox {
checked: view_settings.top_layer_count == 5
onClicked: {
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
}
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
}
Repeater {
model: ListModel {
id: typesLegendModelNoCheck
Component.onCompleted:
{
typesLegendModelNoCheck.append({
label: catalog.i18nc("@label", "Top / Bottom"),
colorId: "layerview_skin",
});
typesLegendModelNoCheck.append({
label: catalog.i18nc("@label", "Inner Wall"),
colorId: "layerview_inset_x",
});
}
}
Label {
text: label
visible: view_settings.show_legend
id: typesLegendModelLabel
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: typesLegendModelLabel.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
}
}
// Text for the minimum, maximum and units for the feedrates and layer thickness
Rectangle {
id: gradientLegend
visible: view_settings.show_gradient
width: parent.width
height: UM.Theme.getSize("layerview_row").height
anchors {
topMargin: UM.Theme.getSize("slider_layerview_margin").height
horizontalCenter: parent.horizontalCenter
}
Label {
text: minText()
anchors.left: parent.left
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function minText() {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
}
}
return catalog.i18nc("@label","min")
}
}
Label {
text: unitsText()
anchors.horizontalCenter: parent.horizontalCenter
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function unitsText() {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
return "mm/s"
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
return "mm"
}
}
return ""
}
}
Label {
text: maxText()
anchors.right: parent.right
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function maxText() {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
}
}
return catalog.i18nc("@label","max")
}
}
}
// Gradient colors for feedrate and thickness
Rectangle { // In QML 5.9 can be changed by LinearGradient
// Invert values because then the bar is rotated 90 degrees
id: gradient
visible: view_settings.show_gradient
anchors.left: parent.right
height: parent.width
width: UM.Theme.getSize("layerview_row").height * 1.5
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
gradient: Gradient {
GradientStop {
position: 0.000
color: Qt.rgba(1, 0, 0, 1)
}
GradientStop {
position: 0.25
color: Qt.rgba(0.75, 0.5, 0.25, 1)
}
GradientStop {
position: 0.5
color: Qt.rgba(0.5, 1, 0.5, 1)
}
GradientStop {
position: 0.75
color: Qt.rgba(0.25, 0.5, 0.75, 1)
}
GradientStop {
position: 1.0
color: Qt.rgba(0, 0, 1, 1)
}
}
}
}
Item {
id: slidersBox
width: parent.width
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
anchors {
top: parent.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").height
left: parent.left
}
PathSlider {
id: pathSlider
width: parent.width
height: UM.Theme.getSize("slider_handle").width
anchors.left: parent.left
visible: !UM.SimulationView.compatibilityMode
// custom properties
handleValue: UM.SimulationView.currentPath
maximumValue: UM.SimulationView.numPaths
handleSize: UM.Theme.getSize("slider_handle").width
trackThickness: UM.Theme.getSize("slider_groove").width
trackColor: UM.Theme.getColor("slider_groove")
trackBorderColor: UM.Theme.getColor("slider_groove_border")
handleColor: UM.Theme.getColor("slider_handle")
handleActiveColor: UM.Theme.getColor("slider_handle_active")
rangeColor: UM.Theme.getColor("slider_groove_fill")
// update values when layer data changes
Connections {
target: UM.SimulationView
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted: {
pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
}
LayerSlider {
id: layerSlider
width: UM.Theme.getSize("slider_handle").width
height: UM.Theme.getSize("layerview_menu_size").height
anchors {
top: pathSlider.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").height
right: parent.right
rightMargin: UM.Theme.getSize("slider_layerview_margin").width
}
// custom properties
upperValue: UM.SimulationView.currentLayer
lowerValue: UM.SimulationView.minimumLayer
maximumValue: UM.SimulationView.numLayers
handleSize: UM.Theme.getSize("slider_handle").width
trackThickness: UM.Theme.getSize("slider_groove").width
trackColor: UM.Theme.getColor("slider_groove")
trackBorderColor: UM.Theme.getColor("slider_groove_border")
upperHandleColor: UM.Theme.getColor("slider_handle")
lowerHandleColor: UM.Theme.getColor("slider_handle")
rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
handleActiveColor: UM.Theme.getColor("slider_handle_active")
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
// update values when layer data changes
Connections {
target: UM.SimulationView
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
onCurrentLayerChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted: {
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
}
// Play simulation button
Button {
id: playButton
implicitWidth: UM.Theme.getSize("button").width * 0.75;
implicitHeight: UM.Theme.getSize("button").height * 0.75;
iconSource: "./resources/simulation_resume.svg"
style: UM.Theme.styles.tool_button
visible: !UM.SimulationView.compatibilityMode
anchors {
horizontalCenter: layerSlider.horizontalCenter
top: layerSlider.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").width
}
property var status: 0 // indicates if it's stopped (0) or playing (1)
onClicked: {
switch(status) {
case 0: {
resumeSimulation()
break
}
case 1: {
pauseSimulation()
break
}
}
}
function pauseSimulation() {
UM.SimulationView.setSimulationRunning(false)
iconSource = "./resources/simulation_resume.svg"
simulationTimer.stop()
status = 0
}
function resumeSimulation() {
UM.SimulationView.setSimulationRunning(true)
iconSource = "./resources/simulation_pause.svg"
simulationTimer.start()
}
}
}
Timer
{
id: simulationTimer
interval: 250
running: false
repeat: true
onTriggered: {
var currentPath = UM.SimulationView.currentPath
var numPaths = UM.SimulationView.numPaths
var currentLayer = UM.SimulationView.currentLayer
var numLayers = UM.SimulationView.numLayers
// When the user plays the simulation, if the path slider is at the end of this layer, we start
// the simulation at the beginning of the current layer.
if (playButton.status == 0)
{
if (currentPath >= numPaths)
{
UM.SimulationView.setCurrentPath(0)
}
else
{
UM.SimulationView.setCurrentPath(currentPath+1)
}
}
// If the simulation is already playing and we reach the end of a layer, then it automatically
// starts at the beginning of the next layer.
else
{
if (currentPath >= numPaths)
{
// At the end of the model, the simulation stops
if (currentLayer >= numLayers)
{
playButton.pauseSimulation()
}
else
{
UM.SimulationView.setCurrentLayer(currentLayer+1)
UM.SimulationView.setCurrentPath(0)
}
}
else
{
UM.SimulationView.setCurrentPath(currentPath+1)
}
}
playButton.status = 1
}
}
}
FontMetrics {
id: fontMetrics
font: UM.Theme.getFont("default")
}
}

View file

@ -0,0 +1,261 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
import SimulationView
class SimulationViewProxy(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._current_layer = 0
self._controller = Application.getInstance().getController()
self._controller.activeViewChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged()
self.is_simulationView_selected = False
currentLayerChanged = pyqtSignal()
currentPathChanged = pyqtSignal()
maxLayersChanged = pyqtSignal()
maxPathsChanged = pyqtSignal()
activityChanged = pyqtSignal()
globalStackChanged = pyqtSignal()
preferencesChanged = pyqtSignal()
busyChanged = pyqtSignal()
@pyqtProperty(bool, notify=activityChanged)
def layerActivity(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getActivity()
return False
@pyqtProperty(int, notify=maxLayersChanged)
def numLayers(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxLayers()
return 0
@pyqtProperty(int, notify=currentLayerChanged)
def currentLayer(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getCurrentLayer()
return 0
@pyqtProperty(int, notify=currentLayerChanged)
def minimumLayer(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinimumLayer()
return 0
@pyqtProperty(int, notify=maxPathsChanged)
def numPaths(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxPaths()
return 0
@pyqtProperty(int, notify=currentPathChanged)
def currentPath(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getCurrentPath()
return 0
@pyqtProperty(int, notify=currentPathChanged)
def minimumPath(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinimumPath()
return 0
@pyqtProperty(bool, notify=busyChanged)
def busy(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.isBusy()
return False
@pyqtProperty(bool, notify=preferencesChanged)
def compatibilityMode(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getCompatibilityMode()
return False
@pyqtSlot(int)
def setCurrentLayer(self, layer_num):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setLayer(layer_num)
@pyqtSlot(int)
def setMinimumLayer(self, layer_num):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setMinimumLayer(layer_num)
@pyqtSlot(int)
def setCurrentPath(self, path_num):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setPath(path_num)
@pyqtSlot(int)
def setMinimumPath(self, path_num):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setMinimumPath(path_num)
@pyqtSlot(int)
def setSimulationViewType(self, layer_view_type):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setSimulationViewisinstance(layer_view_type)
@pyqtSlot(result=int)
def getSimulationViewType(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getSimulationViewType()
return 0
@pyqtSlot(bool)
def setSimulationRunning(self, running):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setSimulationRunning(running)
@pyqtSlot(result=bool)
def getSimulationRunning(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.isSimulationRunning()
return False
@pyqtSlot(result=float)
def getMinFeedrate(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinFeedrate()
return 0
@pyqtSlot(result=float)
def getMaxFeedrate(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxFeedrate()
return 0
@pyqtSlot(result=float)
def getMinThickness(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinThickness()
return 0
@pyqtSlot(result=float)
def getMaxThickness(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxThickness()
return 0
# Opacity 0..1
@pyqtSlot(int, float)
def setExtruderOpacity(self, extruder_nr, opacity):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setExtruderOpacity(extruder_nr, opacity)
@pyqtSlot(int)
def setShowTravelMoves(self, show):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setShowTravelMoves(show)
@pyqtSlot(int)
def setShowHelpers(self, show):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setShowHelpers(show)
@pyqtSlot(int)
def setShowSkin(self, show):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setShowSkin(show)
@pyqtSlot(int)
def setShowInfill(self, show):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setShowInfill(show)
@pyqtProperty(int, notify=globalStackChanged)
def extruderCount(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getExtruderCount()
return 0
def _layerActivityChanged(self):
self.activityChanged.emit()
def _onLayerChanged(self):
self.currentLayerChanged.emit()
self._layerActivityChanged()
def _onPathChanged(self):
self.currentPathChanged.emit()
self._layerActivityChanged()
def _onMaxLayersChanged(self):
self.maxLayersChanged.emit()
def _onMaxPathsChanged(self):
self.maxPathsChanged.emit()
def _onBusyChanged(self):
self.busyChanged.emit()
def _onActivityChanged(self):
self.activityChanged.emit()
def _onGlobalStackChanged(self):
self.globalStackChanged.emit()
def _onPreferencesChanged(self):
self.preferencesChanged.emit()
def _onActiveViewChanged(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
# remove other connection if once the SimulationView was created.
if self.is_simulationView_selected:
active_view.currentLayerNumChanged.disconnect(self._onLayerChanged)
active_view.currentPathNumChanged.disconnect(self._onPathChanged)
active_view.maxLayersChanged.disconnect(self._onMaxLayersChanged)
active_view.maxPathsChanged.disconnect(self._onMaxPathsChanged)
active_view.busyChanged.disconnect(self._onBusyChanged)
active_view.activityChanged.disconnect(self._onActivityChanged)
active_view.globalStackChanged.disconnect(self._onGlobalStackChanged)
active_view.preferencesChanged.disconnect(self._onPreferencesChanged)
self.is_simulationView_selected = True
active_view.currentLayerNumChanged.connect(self._onLayerChanged)
active_view.currentPathNumChanged.connect(self._onPathChanged)
active_view.maxLayersChanged.connect(self._onMaxLayersChanged)
active_view.maxPathsChanged.connect(self._onMaxPathsChanged)
active_view.busyChanged.connect(self._onBusyChanged)
active_view.activityChanged.connect(self._onActivityChanged)
active_view.globalStackChanged.connect(self._onGlobalStackChanged)
active_view.preferencesChanged.connect(self._onPreferencesChanged)

View file

@ -0,0 +1,26 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtQml import qmlRegisterSingletonType
from UM.i18n import i18nCatalog
from . import SimulationViewProxy, SimulationView
catalog = i18nCatalog("cura")
def getMetaData():
return {
"view": {
"name": catalog.i18nc("@item:inlistbox", "Simulation view"),
"view_panel": "SimulationView.qml",
"weight": 2
}
}
def createSimulationViewProxy(engine, script_engine):
return SimulationViewProxy.SimulatorViewProxy()
def register(app):
simulation_view = SimulationView.SimulationView()
qmlRegisterSingletonType(SimulationViewProxy.SimulationViewProxy, "UM", 1, 0, "SimulationView", simulation_view.getProxy)
return { "view": SimulationView.SimulationView()}

View file

@ -6,6 +6,10 @@ vertex41core =
uniform highp mat4 u_modelMatrix; uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewProjectionMatrix; uniform highp mat4 u_viewProjectionMatrix;
uniform lowp float u_active_extruder; uniform lowp float u_active_extruder;
uniform lowp float u_max_feedrate;
uniform lowp float u_min_feedrate;
uniform lowp float u_max_thickness;
uniform lowp float u_min_thickness;
uniform lowp int u_layer_view_type; uniform lowp int u_layer_view_type;
uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible
@ -18,6 +22,8 @@ vertex41core =
in highp vec2 a_line_dim; // line width and thickness in highp vec2 a_line_dim; // line width and thickness
in highp float a_extruder; in highp float a_extruder;
in highp float a_line_type; in highp float a_line_type;
in highp float a_feedrate;
in highp float a_thickness;
out lowp vec4 v_color; out lowp vec4 v_color;
@ -32,6 +38,15 @@ vertex41core =
out highp vec3 f_vertex; out highp vec3 f_vertex;
out highp vec3 f_normal; out highp vec3 f_normal;
vec4 gradientColor(float abs_value, float min_value, float max_value)
{
float value = (abs_value - min_value)/(max_value - min_value);
float red = value;
float green = 1-abs(1-2*value);
float blue = 1-value;
return vec4(red, green, blue, 1.0);
}
void main() void main()
{ {
vec4 v1_vertex = a_vertex; vec4 v1_vertex = a_vertex;
@ -48,6 +63,12 @@ vertex41core =
case 1: // "Line type" case 1: // "Line type"
v_color = a_color; v_color = a_color;
break; break;
case 2: // "Feedrate"
v_color = gradientColor(a_feedrate, u_min_feedrate, u_max_feedrate);
break;
case 3: // "Layer thickness"
v_color = gradientColor(a_line_dim.y, u_min_thickness, u_max_thickness);
break;
} }
v_vertex = world_space_vert.xyz; v_vertex = world_space_vert.xyz;
@ -247,6 +268,12 @@ u_show_helpers = 1
u_show_skin = 1 u_show_skin = 1
u_show_infill = 1 u_show_infill = 1
u_min_feedrate = 0
u_max_feedrate = 1
u_min_thickness = 0
u_max_thickness = 1
[bindings] [bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix u_modelViewProjectionMatrix = model_view_projection_matrix
u_modelMatrix = model_matrix u_modelMatrix = model_matrix
@ -262,3 +289,5 @@ a_line_dim = line_dim
a_extruder = extruder a_extruder = extruder
a_material_color = material_color a_material_color = material_color
a_line_type = line_type a_line_type = line_type
a_feedrate = feedrate
a_thickness = thickness

View file

@ -0,0 +1,256 @@
[shaders]
vertex41core =
#version 410
uniform highp mat4 u_modelViewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewProjectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible
uniform highp mat4 u_normalMatrix;
in highp vec4 a_vertex;
in lowp vec4 a_color;
in lowp vec4 a_grayColor;
in lowp vec4 a_material_color;
in highp vec4 a_normal;
in highp vec2 a_line_dim; // line width and thickness
in highp float a_extruder;
in highp float a_line_type;
out lowp vec4 v_color;
out highp vec3 v_vertex;
out highp vec3 v_normal;
out lowp vec2 v_line_dim;
out highp int v_extruder;
out highp vec4 v_extruder_opacity;
out float v_line_type;
out lowp vec4 f_color;
out highp vec3 f_vertex;
out highp vec3 f_normal;
void main()
{
vec4 v1_vertex = a_vertex;
v1_vertex.y -= a_line_dim.y / 2; // half layer down
vec4 world_space_vert = u_modelMatrix * v1_vertex;
gl_Position = world_space_vert;
// shade the color depending on the extruder index stored in the alpha component of the color
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer
v_vertex = world_space_vert.xyz;
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
v_line_dim = a_line_dim;
v_extruder = int(a_extruder);
v_line_type = a_line_type;
v_extruder_opacity = u_extruder_opacity;
// for testing without geometry shader
f_color = v_color;
f_vertex = v_vertex;
f_normal = v_normal;
}
geometry41core =
#version 410
uniform highp mat4 u_viewProjectionMatrix;
uniform int u_show_travel_moves;
uniform int u_show_helpers;
uniform int u_show_skin;
uniform int u_show_infill;
layout(lines) in;
layout(triangle_strip, max_vertices = 26) out;
in vec4 v_color[];
in vec3 v_vertex[];
in vec3 v_normal[];
in vec2 v_line_dim[];
in int v_extruder[];
in vec4 v_extruder_opacity[];
in float v_line_type[];
out vec4 f_color;
out vec3 f_normal;
out vec3 f_vertex;
// Set the set of variables and EmitVertex
void myEmitVertex(vec3 vertex, vec4 color, vec3 normal, vec4 pos) {
f_vertex = vertex;
f_color = color;
f_normal = normal;
gl_Position = pos;
EmitVertex();
}
void main()
{
vec4 g_vertex_delta;
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
vec3 g_vertex_normal_vert;
vec4 g_vertex_offset_vert;
vec3 g_vertex_normal_horz_head;
vec4 g_vertex_offset_horz_head;
float size_x;
float size_y;
if ((v_extruder_opacity[0][v_extruder[0]] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) {
return;
}
// See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
return;
}
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) {
return;
}
if ((u_show_skin == 0) && ((v_line_type[0] == 1) || (v_line_type[0] == 2) || (v_line_type[0] == 3))) {
return;
}
if ((u_show_infill == 0) && (v_line_type[0] == 6)) {
return;
}
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
// fixed size for movements
size_x = 0.05;
} else {
size_x = v_line_dim[1].x / 2 + 0.01; // radius, and make it nicely overlapping
}
size_y = v_line_dim[1].y / 2 + 0.01;
g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position;
g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z));
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0);
g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x));
g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //size * g_vertex_normal_horz;
g_vertex_normal_vert = vec3(0.0, 1.0, 0.0);
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
// Travels: flat plane with pointy ends
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert));
EndPrimitive();
} else {
// All normal lines are rendered as 3d tubes.
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
EndPrimitive();
// left side
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
EndPrimitive();
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
EndPrimitive();
// right side
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
EndPrimitive();
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
EndPrimitive();
}
}
fragment41core =
#version 410
in lowp vec4 f_color;
in lowp vec3 f_normal;
in lowp vec3 f_vertex;
out vec4 frag_color;
uniform mediump vec4 u_ambientColor;
uniform highp vec3 u_lightPosition;
void main()
{
mediump vec4 finalColor = vec4(0.0);
float alpha = f_color.a;
finalColor.rgb += f_color.rgb * 0.3;
highp vec3 normal = normalize(f_normal);
highp vec3 light_dir = normalize(u_lightPosition - f_vertex);
// Diffuse Component
highp float NdotL = clamp(dot(normal, light_dir), 0.0, 1.0);
finalColor += (NdotL * f_color);
finalColor.a = alpha; // Do not change alpha in any way
frag_color = finalColor;
}
[defaults]
u_active_extruder = 0.0
u_extruder_opacity = [1.0, 1.0, 1.0, 1.0]
u_specularColor = [0.4, 0.4, 0.4, 1.0]
u_ambientColor = [0.3, 0.3, 0.3, 0.0]
u_diffuseColor = [1.0, 0.79, 0.14, 1.0]
u_shininess = 20.0
u_show_travel_moves = 0
u_show_helpers = 1
u_show_skin = 1
u_show_infill = 1
[bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix
u_modelMatrix = model_matrix
u_viewProjectionMatrix = view_projection_matrix
u_normalMatrix = normal_matrix
u_lightPosition = light_0_position
[attributes]
a_vertex = vertex
a_color = color
a_grayColor = vec4(0.87, 0.12, 0.45, 1.0)
a_normal = normal
a_line_dim = line_dim
a_extruder = extruder
a_material_color = material_color
a_line_type = line_type

View file

@ -0,0 +1,156 @@
[shaders]
vertex =
uniform highp mat4 u_modelViewProjectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp float u_shade_factor;
uniform highp int u_layer_view_type;
attribute highp float a_extruder;
attribute highp float a_line_type;
attribute highp vec4 a_vertex;
attribute lowp vec4 a_color;
attribute lowp vec4 a_material_color;
varying lowp vec4 v_color;
varying float v_line_type;
void main()
{
gl_Position = u_modelViewProjectionMatrix * a_vertex;
// shade the color depending on the extruder index
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer;
// 8 and 9 are travel moves
// if ((a_line_type != 8.0) && (a_line_type != 9.0)) {
// v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
// }
v_line_type = a_line_type;
}
fragment =
varying lowp vec4 v_color;
varying float v_line_type;
uniform int u_show_travel_moves;
uniform int u_show_helpers;
uniform int u_show_skin;
uniform int u_show_infill;
void main()
{
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
// discard movements
discard;
}
// support: 4, 5, 7, 10
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
)) {
discard;
}
// skin: 1, 2, 3
if ((u_show_skin == 0) && (
(v_line_type >= 0.5) && (v_line_type <= 3.5)
)) {
discard;
}
// infill:
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) {
// discard movements
discard;
}
gl_FragColor = v_color;
}
vertex41core =
#version 410
uniform highp mat4 u_modelViewProjectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp float u_shade_factor;
uniform highp int u_layer_view_type;
in highp float a_extruder;
in highp float a_line_type;
in highp vec4 a_vertex;
in lowp vec4 a_color;
in lowp vec4 a_material_color;
out lowp vec4 v_color;
out float v_line_type;
void main()
{
gl_Position = u_modelViewProjectionMatrix * a_vertex;
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer
// if ((a_line_type != 8) && (a_line_type != 9)) {
// v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
// }
v_line_type = a_line_type;
}
fragment41core =
#version 410
in lowp vec4 v_color;
in float v_line_type;
out vec4 frag_color;
uniform int u_show_travel_moves;
uniform int u_show_helpers;
uniform int u_show_skin;
uniform int u_show_infill;
void main()
{
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
// discard movements
discard;
}
// helpers: 4, 5, 7, 10
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
)) {
discard;
}
// skin: 1, 2, 3
if ((u_show_skin == 0) && (
(v_line_type >= 0.5) && (v_line_type <= 3.5)
)) {
discard;
}
// infill:
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) {
// discard movements
discard;
}
frag_color = v_color;
}
[defaults]
u_active_extruder = 0.0
u_shade_factor = 0.60
u_layer_view_type = 0
u_extruder_opacity = [1.0, 1.0, 1.0, 1.0]
u_show_travel_moves = 0
u_show_helpers = 1
u_show_skin = 1
u_show_infill = 1
[bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix
[attributes]
a_vertex = vertex
a_color = color
a_extruder = extruder
a_line_type = line_type
a_material_color = material_color

View file

@ -1,8 +1,8 @@
{ {
"name": "Layer View", "name": "Simulation View",
"author": "Ultimaker B.V.", "author": "Ultimaker B.V.",
"version": "1.0.0", "version": "1.0.0",
"description": "Provides the Layer view.", "description": "Provides the Simulation view.",
"api": 4, "api": 4,
"i18n-catalog": "cura" "i18n-catalog": "cura"
} }

Binary file not shown.

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 30 30"
version="1.1"
id="svg4620"
sodipodi:docname="simulation_pause.svg"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)">
<metadata
id="metadata4626">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4624" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1137"
id="namedview4622"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="22.250293"
inkscape:cx="8.1879003"
inkscape:cy="12.643765"
inkscape:window-x="2872"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4620">
<sodipodi:guide
position="15,15"
orientation="1,0"
id="guide4628"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="15,15"
orientation="0,1"
id="guide4630"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
</sodipodi:namedview>
<rect
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5192"
width="2"
height="20"
x="19"
y="5" />
<rect
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5192-5"
width="2"
height="20"
x="9"
y="5" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 30 30"
version="1.1"
id="svg3765"
sodipodi:docname="simulation_resume.svg"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)">
<metadata
id="metadata3771">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3769" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1137"
id="namedview3767"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="23.327047"
inkscape:cx="10.788646"
inkscape:cy="14.67951"
inkscape:window-x="2872"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg3765">
<sodipodi:guide
position="15,15"
orientation="0,1"
id="guide4592"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="15,15"
orientation="1,0"
id="guide4594"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
</sodipodi:namedview>
<path
sodipodi:type="star"
id="path3783"
sodipodi:sides="3"
sodipodi:cx="12.732001"
sodipodi:cy="14.695877"
sodipodi:r1="13.891838"
sodipodi:r2="6.945919"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 26.623839,14.695877 -20.8377567,12.030685 0,-24.0613696 z"
inkscape:transform-center-x="-2.9211205"
style="fill:none;stroke:#000000;stroke-width:2.32790732;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
transform="matrix(0.84110413,0,0,0.87756418,1.775541,2.1034247)" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -133,6 +133,7 @@
"slider_groove_fill": [245, 245, 245, 255], "slider_groove_fill": [245, 245, 245, 255],
"slider_handle": [255, 255, 255, 255], "slider_handle": [255, 255, 255, 255],
"slider_handle_hover": [77, 182, 226, 255], "slider_handle_hover": [77, 182, 226, 255],
"slider_handle_active": [68, 192, 255, 255],
"slider_handle_border": [39, 44, 48, 255], "slider_handle_border": [39, 44, 48, 255],
"slider_text_background": [255, 255, 255, 255], "slider_text_background": [255, 255, 255, 255],
@ -194,6 +195,7 @@
"layerview_move_combing": [0, 0, 255, 255], "layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 255, 255], "layerview_move_retraction": [128, 128, 255, 255],
"layerview_support_interface": [64, 192, 255, 255], "layerview_support_interface": [64, 192, 255, 255],
"layerview_nozzle": [181, 166, 66, 120],
"material_compatibility_warning": [255, 255, 255, 255], "material_compatibility_warning": [255, 255, 255, 255],

View file

@ -269,6 +269,7 @@ QtObject {
arrowSize: Theme.getSize("button_tooltip_arrow").width arrowSize: Theme.getSize("button_tooltip_arrow").width
color: Theme.getColor("button_tooltip") color: Theme.getColor("button_tooltip")
opacity: control.hovered ? 1.0 : 0.0; opacity: control.hovered ? 1.0 : 0.0;
visible: control.text != ""
width: control.hovered ? button_tip.width + Theme.getSize("button_tooltip").width : 0 width: control.hovered ? button_tip.width + Theme.getSize("button_tooltip").width : 0
height: Theme.getSize("button_tooltip").height height: Theme.getSize("button_tooltip").height

View file

@ -183,6 +183,7 @@
"slider_groove_fill": [127, 127, 127, 255], "slider_groove_fill": [127, 127, 127, 255],
"slider_handle": [0, 0, 0, 255], "slider_handle": [0, 0, 0, 255],
"slider_handle_hover": [77, 182, 226, 255], "slider_handle_hover": [77, 182, 226, 255],
"slider_handle_active": [68, 192, 255, 255],
"slider_handle_border": [39, 44, 48, 255], "slider_handle_border": [39, 44, 48, 255],
"slider_text_background": [255, 255, 255, 255], "slider_text_background": [255, 255, 255, 255],
@ -271,7 +272,8 @@
"layerview_support_infill": [0, 255, 255, 255], "layerview_support_infill": [0, 255, 255, 255],
"layerview_move_combing": [0, 0, 255, 255], "layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 255, 255], "layerview_move_retraction": [128, 128, 255, 255],
"layerview_support_interface": [64, 192, 255, 255] "layerview_support_interface": [64, 192, 255, 255],
"layerview_nozzle": [181, 166, 66, 50]
}, },
"sizes": { "sizes": {