diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py
index 8c1ee8fc36..caa39cc703 100755
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -218,7 +218,7 @@ class CuraApplication(QtApplication):
"CuraEngineBackend",
"UserAgreement",
"SolidView",
- "LayerView",
+ "SimulationView",
"STLReader",
"SelectionTool",
"CameraTool",
@@ -1386,7 +1386,7 @@ class CuraApplication(QtApplication):
extension = os.path.splitext(filename)[1]
if extension.lower() in self._non_sliceable_extensions:
- self.getController().setActiveView("LayerView")
+ self.getController().setActiveView("SimulationView")
view = self.getController().getActiveView()
view.resetLayerData()
view.setLayer(9999999)
diff --git a/cura/Layer.py b/cura/Layer.py
index d5ef5c9bb4..9cd45380fc 100644
--- a/cura/Layer.py
+++ b/cura/Layer.py
@@ -47,12 +47,12 @@ class Layer:
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_index_offset = index_offset
self._element_count = 0
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_index_offset += polygon.lineMeshElementCount()
self._element_count += polygon.elementCount
diff --git a/cura/LayerDataBuilder.py b/cura/LayerDataBuilder.py
index 6e50611e64..d6cc81a4e9 100755
--- a/cura/LayerDataBuilder.py
+++ b/cura/LayerDataBuilder.py
@@ -20,11 +20,11 @@ class LayerDataBuilder(MeshBuilder):
if layer not in self._layers:
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:
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)
def getLayer(self, layer):
@@ -64,13 +64,14 @@ class LayerDataBuilder(MeshBuilder):
line_dimensions = numpy.empty((vertex_count, 2), numpy.float32)
colors = numpy.empty((vertex_count, 4), numpy.float32)
indices = numpy.empty((index_count, 2), numpy.int32)
+ feedrates = numpy.empty((vertex_count), numpy.float32)
extruders = numpy.empty((vertex_count), numpy.float32)
line_types = numpy.empty((vertex_count), numpy.float32)
vertex_offset = 0
index_offset = 0
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.addVertices(vertices)
@@ -107,6 +108,11 @@ class LayerDataBuilder(MeshBuilder):
"value": line_types,
"opengl_name": "a_line_type",
"opengl_type": "float"
+ },
+ "feedrates": {
+ "value": feedrates,
+ "opengl_name": "a_feedrate",
+ "opengl_type": "float"
}
}
diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py
index 7f41351b7f..9766e0c82a 100644
--- a/cura/LayerPolygon.py
+++ b/cura/LayerPolygon.py
@@ -28,7 +28,8 @@ class LayerPolygon:
# \param data new_points
# \param line_widths array with line widths
# \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._types = line_types
for i in range(len(self._types)):
@@ -37,6 +38,7 @@ class LayerPolygon:
self._data = data
self._line_widths = line_widths
self._line_thicknesses = line_thicknesses
+ self._line_feedrates = line_feedrates
self._vertex_begin = 0
self._vertex_end = 0
@@ -84,10 +86,11 @@ class LayerPolygon:
# \param vertices : vertex numpy array to be filled
# \param colors : 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 line_types : vertex 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:
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.
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, 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
# Convert type per vertex to type per line
@@ -166,6 +172,14 @@ class LayerPolygon:
@property
def lineWidths(self):
return self._line_widths
+
+ @property
+ def lineThicknesses(self):
+ return self._line_thicknesses
+
+ @property
+ def lineFeedrates(self):
+ return self._line_feedrates
@property
def jumpMask(self):
diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto
index c2e4e5bb5f..69612210ec 100644
--- a/plugins/CuraEngineBackend/Cura.proto
+++ b/plugins/CuraEngineBackend/Cura.proto
@@ -61,6 +61,8 @@ message Polygon {
Type type = 1; // Type of move
bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
float line_width = 3; // The width of the line being laid down
+ 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 {
@@ -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 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_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
}
diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py
index 14c1c10b90..d35df967b2 100755
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -608,7 +608,7 @@ class CuraEngineBackend(QObject, Backend):
def _onActiveViewChanged(self):
if 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
# 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.
diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
index a352564bc2..14646cbac1 100644
--- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
+++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
@@ -61,7 +61,7 @@ class ProcessSlicedLayersJob(Job):
def run(self):
start_time = time()
- if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
+ if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
self._progress_message.show()
Job.yieldThread()
if self._abort_requested:
@@ -109,6 +109,7 @@ class ProcessSlicedLayersJob(Job):
layer_data.addLayer(abs_layer_number)
this_layer = layer_data.getLayer(abs_layer_number)
layer_data.setLayerHeight(abs_layer_number, layer.height)
+ layer_data.setLayerThickness(abs_layer_number, layer.thickness)
for p in range(layer.repeatedMessageCount("path_segment")):
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 = 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.
- # Currently the infill layer thickness also translates to line width
- line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4")
- line_thicknesses[:] = layer.thickness / 1000 # from micrometer to millimeter
+ line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array
+ 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_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.
# 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[:, 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_layer.polygons.append(this_poly)
@@ -219,7 +221,7 @@ class ProcessSlicedLayersJob(Job):
self._progress_message.setProgress(100)
view = Application.getInstance().getController().getActiveView()
- if view.getPluginId() == "LayerView":
+ if view.getPluginId() == "SimulationView":
view.resetLayerData()
if self._progress_message:
@@ -232,7 +234,7 @@ class ProcessSlicedLayersJob(Job):
def _onActiveViewChanged(self):
if self.isRunning():
- if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
+ if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
if not self._progress_message:
self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0, catalog.i18nc("@info:title", "Information"))
if self._progress_message.getProgress() != 100:
diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py
index 1b2795800e..6f0ce0408d 100755
--- a/plugins/GCodeReader/GCodeReader.py
+++ b/plugins/GCodeReader/GCodeReader.py
@@ -40,7 +40,8 @@ class GCodeReader(MeshReader):
self._extruder_number = 0
self._clearValues()
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._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
self._current_layer_thickness = 0.2 # default
@@ -48,7 +49,9 @@ class GCodeReader(MeshReader):
Preferences.getInstance().addPreference("gcodereader/show_caution", True)
def _clearValues(self):
+ self._filament_diameter = 2.85
self._extruder_number = 0
+ self._extrusion_length_offset = [0]
self._layer_type = LayerPolygon.Inset0Type
self._layer_number = 0
self._previous_z = 0
@@ -97,7 +100,7 @@ class GCodeReader(MeshReader):
def _createPolygon(self, layer_thickness, path, extruder_offsets):
countvalid = 0
for point in path:
- if point[3] > 0:
+ if point[5] > 0:
countvalid += 1
if countvalid >= 2:
# 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_widths = 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_thicknesses[:, 0] = layer_thickness
points = numpy.empty((count, 3), numpy.float32)
+ extrusion_values = numpy.empty((count, 1), numpy.float32)
i = 0
for point in path:
points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]]
+ extrusion_values[i] = point[4]
if i > 0:
- line_types[i - 1] = point[3]
- if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
+ line_feedrates[i - 1] = point[3]
+ line_types[i - 1] = point[5]
+ if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
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
- 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_layer.polygons.append(this_poly)
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):
- x, y, z, e = position
+
+ x, y, z, f, e = position
if self._is_absolute_positioning:
x = params.x if params.x is not None else x
y = params.y if params.y is not None else y
@@ -144,23 +176,25 @@ class GCodeReader(MeshReader):
x += params.x if params.x 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
+
+ f = params.f if params.f is not None else f
if params.e is not None:
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]:
- 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:
- 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
# 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.
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
else:
- path.append([x, y, z, LayerPolygon.MoveCombingType])
- return self._position(x, y, z, e)
+ 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, f, e)
# G0 and G1 should be handled exactly the same.
_gCode1 = _gCode0
@@ -171,6 +205,7 @@ class GCodeReader(MeshReader):
params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y,
0,
+ position.f,
position.e)
## 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.
def _gCode92(self, position, params, path):
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
return self._position(
params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y,
params.z if params.z is not None else position.z,
+ params.f if params.f is not None else position.f,
position.e)
def _processGCode(self, G, line, position, path):
@@ -199,7 +237,7 @@ class GCodeReader(MeshReader):
line = line.split(";", 1)[0] # Remove comments (if any)
if func is not None:
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:]:
if len(item) <= 1:
continue
@@ -211,17 +249,20 @@ class GCodeReader(MeshReader):
y = float(item[1:])
if item[0] == "Z":
z = float(item[1:])
+ if item[0] == "F":
+ f = float(item[1:]) / 60
if item[0] == "E":
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)):
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 position
def _processTCode(self, T, line, position, path):
self._extruder_number = T
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))
return position
@@ -240,6 +281,8 @@ class GCodeReader(MeshReader):
def read(self, file_name):
Logger.log("d", "Preparing to load %s" % file_name)
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()
# 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)
- current_position = self._position(0, 0, 0, [0])
+ current_position = self._position(0, 0, 0, 0, [0])
current_path = []
for line in file:
@@ -310,6 +353,7 @@ class GCodeReader(MeshReader):
else:
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:
try:
layer_number = int(line[len(self._layer_keyword):])
@@ -325,17 +369,12 @@ class GCodeReader(MeshReader):
G = self._getInt(line, "G")
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)
-
- # < 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
+ # When changing the extruder, the polygon with the stored paths is computed
if line.startswith("T"):
T = self._getInt(line, "T")
if T is not None:
@@ -344,8 +383,8 @@ class GCodeReader(MeshReader):
current_position = self._processTCode(T, line, current_position, current_path)
- # "Flush" leftovers
- if not self._is_layers_in_file and len(current_path) > 1:
+ # "Flush" leftovers. Last layer paths are still stored
+ if len(current_path) > 1:
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
self._layer_number += 1
current_path.clear()
diff --git a/plugins/LayerView/LayerPass.py b/plugins/LayerView/LayerPass.py
deleted file mode 100755
index 963c8c75c8..0000000000
--- a/plugins/LayerView/LayerPass.py
+++ /dev/null
@@ -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()
diff --git a/plugins/LayerView/LayerView.qml b/plugins/LayerView/LayerView.qml
deleted file mode 100755
index 7261926bc5..0000000000
--- a/plugins/LayerView/LayerView.qml
+++ /dev/null
@@ -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")
- }
-}
diff --git a/plugins/LayerView/LayerViewProxy.py b/plugins/LayerView/LayerViewProxy.py
deleted file mode 100644
index 4cf84117da..0000000000
--- a/plugins/LayerView/LayerViewProxy.py
+++ /dev/null
@@ -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)
diff --git a/plugins/LayerView/__init__.py b/plugins/LayerView/__init__.py
deleted file mode 100644
index da1a5aed19..0000000000
--- a/plugins/LayerView/__init__.py
+++ /dev/null
@@ -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() }
diff --git a/plugins/LayerView/LayerSlider.qml b/plugins/SimulationView/LayerSlider.qml
similarity index 85%
rename from plugins/LayerView/LayerSlider.qml
rename to plugins/SimulationView/LayerSlider.qml
index 9abeb01148..22f9d91340 100644
--- a/plugins/LayerView/LayerSlider.qml
+++ b/plugins/SimulationView/LayerSlider.qml
@@ -1,312 +1,325 @@
-// 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 real minimumRangeHandleSize: handleSize / 2
- property color upperHandleColor: "black"
- property color lowerHandleColor: "black"
- property color rangeHandleColor: "black"
- property real handleLabelWidth: width
- property var activeHandle: upperHandle
-
- // 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 real minimumValue: 0
- property real minimumRange: 0 // minimum range allowed between min and max values
- property bool roundValues: true
- property real upperValue: maximumValue
- property real lowerValue: minimumValue
-
- property bool layersVisible: true
-
- function getUpperValueFromSliderHandle () {
- return upperHandle.getValue()
- }
-
- function setUpperValue (value) {
- upperHandle.setValue(value)
- updateRangeHandle()
- }
-
- function getLowerValueFromSliderHandle () {
- return lowerHandle.getValue()
- }
-
- function setLowerValue (value) {
- lowerHandle.setValue(value)
- updateRangeHandle()
- }
-
- function updateRangeHandle () {
- rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
- }
-
- // set the active handle to show only one label at a time
- function setActiveHandle (handle) {
- activeHandle = handle
- }
-
- // slider track
- Rectangle {
- id: track
-
- width: sliderRoot.trackThickness
- height: sliderRoot.height - sliderRoot.handleSize
- radius: sliderRoot.trackRadius
- anchors.centerIn: sliderRoot
- color: sliderRoot.trackColor
- border.width: sliderRoot.trackBorderWidth
- border.color: sliderRoot.trackBorderColor
- visible: sliderRoot.layersVisible
- }
-
- // Range handle
- Item {
- id: rangeHandle
-
- y: upperHandle.y + upperHandle.height
- width: sliderRoot.handleSize
- height: sliderRoot.minimumRangeHandleSize
- anchors.horizontalCenter: sliderRoot.horizontalCenter
- visible: sliderRoot.layersVisible
-
- // set the new value when dragging
- function onHandleDragged () {
-
- upperHandle.y = y - upperHandle.height
- lowerHandle.y = y + height
-
- var upperValue = sliderRoot.getUpperValueFromSliderHandle()
- var lowerValue = sliderRoot.getLowerValueFromSliderHandle()
-
- // set both values after moving the handle position
- UM.LayerView.setCurrentLayer(upperValue)
- UM.LayerView.setMinimumLayer(lowerValue)
- }
-
- function setValue (value) {
- var range = sliderRoot.upperValue - sliderRoot.lowerValue
- value = Math.min(value, sliderRoot.maximumValue)
- value = Math.max(value, sliderRoot.minimumValue + range)
-
- UM.LayerView.setCurrentLayer(value)
- UM.LayerView.setMinimumLayer(value - range)
- }
-
- Rectangle {
- width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
- height: parent.height + sliderRoot.handleSize
- anchors.centerIn: parent
- color: sliderRoot.rangeHandleColor
- }
-
- MouseArea {
- anchors.fill: parent
-
- drag {
- target: parent
- axis: Drag.YAxis
- minimumY: upperHandle.height
- maximumY: sliderRoot.height - (rangeHandle.height + lowerHandle.height)
- }
-
- onPositionChanged: parent.onHandleDragged()
- onPressed: sliderRoot.setActiveHandle(rangeHandle)
- }
-
- LayerSliderLabel {
- id: rangleHandleLabel
-
- height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
- x: parent.x - width - UM.Theme.getSize("default_margin").width
- anchors.verticalCenter: parent.verticalCenter
- target: Qt.point(sliderRoot.width, y + height / 2)
- visible: sliderRoot.activeHandle == parent
-
- // custom properties
- maximumValue: sliderRoot.maximumValue
- value: sliderRoot.upperValue
- busy: UM.LayerView.busy
- setValue: rangeHandle.setValue // connect callback functions
- }
- }
-
- // Upper handle
- Rectangle {
- id: upperHandle
-
- y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
- width: sliderRoot.handleSize
- height: sliderRoot.handleSize
- anchors.horizontalCenter: sliderRoot.horizontalCenter
- radius: sliderRoot.handleRadius
- color: sliderRoot.upperHandleColor
- visible: sliderRoot.layersVisible
-
- function onHandleDragged () {
-
- // don't allow the lower handle to be heigher than the upper handle
- if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) {
- lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
- }
-
- // update the range handle
- sliderRoot.updateRangeHandle()
-
- // set the new value after moving the handle position
- UM.LayerView.setCurrentLayer(getValue())
- }
-
- // get the upper value based on the slider position
- function getValue () {
- var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
- result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
- result = sliderRoot.roundValues ? Math.round(result) : result
- return result
- }
-
- // set the slider position based on the upper value
- function setValue (value) {
-
- UM.LayerView.setCurrentLayer(value)
-
- var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
- var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
- y = newUpperYPosition
-
- // update the range handle
- sliderRoot.updateRangeHandle()
- }
-
- // dragging
- MouseArea {
- anchors.fill: parent
-
- drag {
- target: parent
- axis: Drag.YAxis
- minimumY: 0
- maximumY: sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)
- }
-
- onPositionChanged: parent.onHandleDragged()
- onPressed: sliderRoot.setActiveHandle(upperHandle)
- }
-
- LayerSliderLabel {
- id: upperHandleLabel
-
- height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
- x: parent.x - width - UM.Theme.getSize("default_margin").width
- anchors.verticalCenter: parent.verticalCenter
- target: Qt.point(sliderRoot.width, y + height / 2)
- visible: sliderRoot.activeHandle == parent
-
- // custom properties
- maximumValue: sliderRoot.maximumValue
- value: sliderRoot.upperValue
- busy: UM.LayerView.busy
- setValue: upperHandle.setValue // connect callback functions
- }
- }
-
- // Lower handle
- Rectangle {
- id: lowerHandle
-
- y: sliderRoot.height - sliderRoot.handleSize
- width: parent.handleSize
- height: parent.handleSize
- anchors.horizontalCenter: parent.horizontalCenter
- radius: sliderRoot.handleRadius
- color: sliderRoot.lowerHandleColor
-
- visible: slider.layersVisible
-
- function onHandleDragged () {
-
- // don't allow the upper handle to be lower than the lower handle
- if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) {
- upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
- }
-
- // update the range handle
- sliderRoot.updateRangeHandle()
-
- // set the new value after moving the handle position
- UM.LayerView.setMinimumLayer(getValue())
- }
-
- // get the lower value from the current slider position
- function getValue () {
- var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
- result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
- result = sliderRoot.roundValues ? Math.round(result) : result
- return result
- }
-
- // set the slider position based on the lower value
- function setValue (value) {
-
- UM.LayerView.setMinimumLayer(value)
-
- 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)))
- y = newLowerYPosition
-
- // update the range handle
- sliderRoot.updateRangeHandle()
- }
-
- // dragging
- MouseArea {
- anchors.fill: parent
-
- drag {
- target: parent
- axis: Drag.YAxis
- minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
- maximumY: sliderRoot.height - parent.height
- }
-
- onPositionChanged: parent.onHandleDragged()
- onPressed: sliderRoot.setActiveHandle(lowerHandle)
- }
-
- LayerSliderLabel {
- id: lowerHandleLabel
-
- height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
- x: parent.x - width - UM.Theme.getSize("default_margin").width
- anchors.verticalCenter: parent.verticalCenter
- target: Qt.point(sliderRoot.width, y + height / 2)
- visible: sliderRoot.activeHandle == parent
-
- // custom properties
- maximumValue: sliderRoot.maximumValue
- value: sliderRoot.lowerValue
- busy: UM.LayerView.busy
- setValue: lowerHandle.setValue // connect callback functions
- }
- }
-}
+// 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 real minimumRangeHandleSize: handleSize / 2
+ property color upperHandleColor: "black"
+ property color lowerHandleColor: "black"
+ property color rangeHandleColor: "black"
+ property color handleActiveColor: "white"
+ property real handleLabelWidth: width
+ property var activeHandle: upperHandle
+
+ // 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 real minimumValue: 0
+ property real minimumRange: 0 // minimum range allowed between min and max values
+ property bool roundValues: true
+ property real upperValue: maximumValue
+ property real lowerValue: minimumValue
+
+ property bool layersVisible: true
+
+ function getUpperValueFromSliderHandle () {
+ return upperHandle.getValue()
+ }
+
+ function setUpperValue (value) {
+ upperHandle.setValue(value)
+ updateRangeHandle()
+ }
+
+ function getLowerValueFromSliderHandle () {
+ return lowerHandle.getValue()
+ }
+
+ function setLowerValue (value) {
+ lowerHandle.setValue(value)
+ updateRangeHandle()
+ }
+
+ function updateRangeHandle () {
+ rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
+ }
+
+ // set the active handle to show only one label at a time
+ function setActiveHandle (handle) {
+ activeHandle = handle
+ }
+
+ // slider track
+ Rectangle {
+ id: track
+
+ width: sliderRoot.trackThickness
+ height: sliderRoot.height - sliderRoot.handleSize
+ radius: sliderRoot.trackRadius
+ anchors.centerIn: sliderRoot
+ color: sliderRoot.trackColor
+ border.width: sliderRoot.trackBorderWidth
+ border.color: sliderRoot.trackBorderColor
+ visible: sliderRoot.layersVisible
+ }
+
+ // Range handle
+ Item {
+ id: rangeHandle
+
+ y: upperHandle.y + upperHandle.height
+ width: sliderRoot.handleSize
+ height: sliderRoot.minimumRangeHandleSize
+ anchors.horizontalCenter: sliderRoot.horizontalCenter
+ visible: sliderRoot.layersVisible
+
+ // set the new value when dragging
+ function onHandleDragged () {
+
+ upperHandle.y = y - upperHandle.height
+ lowerHandle.y = y + height
+
+ var upperValue = sliderRoot.getUpperValueFromSliderHandle()
+ var lowerValue = sliderRoot.getLowerValueFromSliderHandle()
+
+ // set both values after moving the handle position
+ UM.SimulationView.setCurrentLayer(upperValue)
+ UM.SimulationView.setMinimumLayer(lowerValue)
+ }
+
+ function setValue (value) {
+ var range = sliderRoot.upperValue - sliderRoot.lowerValue
+ value = Math.min(value, sliderRoot.maximumValue)
+ value = Math.max(value, sliderRoot.minimumValue + range)
+
+ UM.SimulationView.setCurrentLayer(value)
+ UM.SimulationView.setMinimumLayer(value - range)
+ }
+
+ Rectangle {
+ width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
+ height: parent.height + sliderRoot.handleSize
+ anchors.centerIn: parent
+ color: sliderRoot.rangeHandleColor
+ }
+
+ MouseArea {
+ anchors.fill: parent
+
+ drag {
+ target: parent
+ axis: Drag.YAxis
+ minimumY: upperHandle.height
+ maximumY: sliderRoot.height - (rangeHandle.height + lowerHandle.height)
+ }
+
+ onPositionChanged: parent.onHandleDragged()
+ onPressed: sliderRoot.setActiveHandle(rangeHandle)
+ }
+
+ SimulationSliderLabel {
+ id: rangleHandleLabel
+
+ height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
+ x: parent.x - width - UM.Theme.getSize("default_margin").width
+ anchors.verticalCenter: parent.verticalCenter
+ target: Qt.point(sliderRoot.width, y + height / 2)
+ visible: sliderRoot.activeHandle == parent
+
+ // custom properties
+ maximumValue: sliderRoot.maximumValue
+ value: sliderRoot.upperValue
+ busy: UM.SimulationView.busy
+ setValue: rangeHandle.setValue // connect callback functions
+ }
+ }
+
+ // Upper handle
+ Rectangle {
+ id: upperHandle
+
+ y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
+ width: sliderRoot.handleSize
+ height: sliderRoot.handleSize
+ anchors.horizontalCenter: sliderRoot.horizontalCenter
+ radius: sliderRoot.handleRadius
+ color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
+ visible: sliderRoot.layersVisible
+
+ function onHandleDragged () {
+
+ // don't allow the lower handle to be heigher than the upper handle
+ if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) {
+ lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
+ }
+
+ // update the range handle
+ sliderRoot.updateRangeHandle()
+
+ // set the new value after moving the handle position
+ UM.SimulationView.setCurrentLayer(getValue())
+ }
+
+ // get the upper value based on the slider position
+ function getValue () {
+ var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
+ result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
+ result = sliderRoot.roundValues ? Math.round(result) : result
+ return result
+ }
+
+ // set the slider position based on the upper value
+ function setValue (value) {
+
+ UM.SimulationView.setCurrentLayer(value)
+
+ var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
+ var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
+ y = newUpperYPosition
+
+ // update the range handle
+ 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
+ MouseArea {
+ anchors.fill: parent
+
+ drag {
+ target: parent
+ axis: Drag.YAxis
+ minimumY: 0
+ maximumY: sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)
+ }
+
+ onPositionChanged: parent.onHandleDragged()
+ onPressed: {
+ sliderRoot.setActiveHandle(upperHandle)
+ upperHandleLabel.forceActiveFocus()
+ }
+ }
+
+ SimulationSliderLabel {
+ id: upperHandleLabel
+
+ height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
+ x: parent.x - width - UM.Theme.getSize("default_margin").width
+ anchors.verticalCenter: parent.verticalCenter
+ target: Qt.point(sliderRoot.width, y + height / 2)
+ visible: sliderRoot.activeHandle == parent
+
+ // custom properties
+ maximumValue: sliderRoot.maximumValue
+ value: sliderRoot.upperValue
+ busy: UM.SimulationView.busy
+ setValue: upperHandle.setValue // connect callback functions
+ }
+ }
+
+ // Lower handle
+ Rectangle {
+ id: lowerHandle
+
+ y: sliderRoot.height - sliderRoot.handleSize
+ width: parent.handleSize
+ height: parent.handleSize
+ anchors.horizontalCenter: parent.horizontalCenter
+ radius: sliderRoot.handleRadius
+ color: lowerHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.lowerHandleColor
+
+ visible: sliderRoot.layersVisible
+
+ function onHandleDragged () {
+
+ // don't allow the upper handle to be lower than the lower handle
+ if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) {
+ upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
+ }
+
+ // update the range handle
+ sliderRoot.updateRangeHandle()
+
+ // set the new value after moving the handle position
+ UM.SimulationView.setMinimumLayer(getValue())
+ }
+
+ // get the lower value from the current slider position
+ function getValue () {
+ var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
+ result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
+ result = sliderRoot.roundValues ? Math.round(result) : result
+ return result
+ }
+
+ // set the slider position based on the lower value
+ function setValue (value) {
+
+ UM.SimulationView.setMinimumLayer(value)
+
+ 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)))
+ y = newLowerYPosition
+
+ // update the range handle
+ 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
+ MouseArea {
+ anchors.fill: parent
+
+ drag {
+ target: parent
+ axis: Drag.YAxis
+ minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
+ maximumY: sliderRoot.height - parent.height
+ }
+
+ onPositionChanged: parent.onHandleDragged()
+ onPressed: {
+ sliderRoot.setActiveHandle(lowerHandle)
+ lowerHandleLabel.forceActiveFocus()
+ }
+ }
+
+ SimulationSliderLabel {
+ id: lowerHandleLabel
+
+ height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
+ x: parent.x - width - UM.Theme.getSize("default_margin").width
+ anchors.verticalCenter: parent.verticalCenter
+ target: Qt.point(sliderRoot.width, y + height / 2)
+ visible: sliderRoot.activeHandle == parent
+
+ // custom properties
+ maximumValue: sliderRoot.maximumValue
+ value: sliderRoot.lowerValue
+ busy: UM.SimulationView.busy
+ setValue: lowerHandle.setValue // connect callback functions
+ }
+ }
+}
diff --git a/plugins/SimulationView/NozzleNode.py b/plugins/SimulationView/NozzleNode.py
new file mode 100644
index 0000000000..8a29871775
--- /dev/null
+++ b/plugins/SimulationView/NozzleNode.py
@@ -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
diff --git a/plugins/SimulationView/PathSlider.qml b/plugins/SimulationView/PathSlider.qml
new file mode 100644
index 0000000000..0a4af904aa
--- /dev/null
+++ b/plugins/SimulationView/PathSlider.qml
@@ -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
+ }
+ }
+}
diff --git a/plugins/SimulationView/SimulationPass.py b/plugins/SimulationView/SimulationPass.py
new file mode 100644
index 0000000000..4963568935
--- /dev/null
+++ b/plugins/SimulationView/SimulationPass.py
@@ -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()
diff --git a/plugins/LayerView/LayerSliderLabel.qml b/plugins/SimulationView/SimulationSliderLabel.qml
similarity index 88%
rename from plugins/LayerView/LayerSliderLabel.qml
rename to plugins/SimulationView/SimulationSliderLabel.qml
index c989679285..1c8daf867f 100644
--- a/plugins/LayerView/LayerSliderLabel.qml
+++ b/plugins/SimulationView/SimulationSliderLabel.qml
@@ -1,103 +1,104 @@
-// 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
-
-UM.PointingRectangle {
- id: sliderLabelRoot
-
- // custom properties
- property real maximumValue: 100
- property real value: 0
- property var setValue // Function
- property bool busy: false
-
- target: Qt.point(parent.width, y + height / 2)
- arrowSize: UM.Theme.getSize("default_arrow").width
- height: parent.height
- width: valueLabel.width + UM.Theme.getSize("default_margin").width
- visible: false
-
- // make sure the text field is focussed when pressing the parent handle
- // needed to connect the key bindings when switching active handle
- onVisibleChanged: if (visible) valueLabel.forceActiveFocus()
-
- color: UM.Theme.getColor("tool_panel_background")
- borderColor: UM.Theme.getColor("lining")
- borderWidth: UM.Theme.getSize("default_lining").width
-
- Behavior on height {
- NumberAnimation {
- duration: 50
- }
- }
-
- // catch all mouse events so they're not handled by underlying 3D scene
- MouseArea {
- anchors.fill: parent
- }
-
- TextField {
- id: valueLabel
-
- anchors {
- left: parent.left
- leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
- verticalCenter: parent.verticalCenter
- }
-
- width: 40 * screenScaleFactor
- text: sliderLabelRoot.value + 1 // the current handle value, add 1 because layers is an array
- horizontalAlignment: TextInput.AlignRight
-
- // key bindings, work when label is currenctly focused (active handle in LayerSlider)
- Keys.onUpPressed: sliderLabelRoot.setValue(sliderLabelRoot.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
- Keys.onDownPressed: sliderLabelRoot.setValue(sliderLabelRoot.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
-
- style: TextFieldStyle {
- textColor: UM.Theme.getColor("setting_control_text")
- font: UM.Theme.getFont("default")
- background: Item { }
- }
-
- onEditingFinished: {
-
- // Ensure that the cursor is at the first position. On some systems the text isn't fully visible
- // Seems to have to do something with different dpi densities that QML doesn't quite handle.
- // Another option would be to increase the size even further, but that gives pretty ugly results.
- cursorPosition = 0
-
- if (valueLabel.text != "") {
- // -1 because we need to convert back to an array structure
- sliderLabelRoot.setValue(parseInt(valueLabel.text) - 1)
- }
- }
-
- validator: IntValidator {
- bottom: 1
- top: sliderLabelRoot.maximumValue + 1 // +1 because actual layers is an array
- }
- }
-
- BusyIndicator {
- id: busyIndicator
-
- anchors {
- left: parent.right
- leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
- verticalCenter: parent.verticalCenter
- }
-
- width: sliderLabelRoot.height
- height: width
-
- visible: sliderLabelRoot.busy
- running: sliderLabelRoot.busy
- }
-}
+// 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
+
+UM.PointingRectangle {
+ id: sliderLabelRoot
+
+ // custom properties
+ property real maximumValue: 100
+ property real value: 0
+ property var setValue // Function
+ property bool busy: false
+ property int startFrom: 1
+
+ target: Qt.point(parent.width, y + height / 2)
+ arrowSize: UM.Theme.getSize("default_arrow").width
+ height: parent.height
+ width: valueLabel.width + UM.Theme.getSize("default_margin").width
+ visible: false
+
+ // make sure the text field is focussed when pressing the parent handle
+ // needed to connect the key bindings when switching active handle
+ onVisibleChanged: if (visible) valueLabel.forceActiveFocus()
+
+ color: UM.Theme.getColor("tool_panel_background")
+ borderColor: UM.Theme.getColor("lining")
+ borderWidth: UM.Theme.getSize("default_lining").width
+
+ Behavior on height {
+ NumberAnimation {
+ duration: 50
+ }
+ }
+
+ // catch all mouse events so they're not handled by underlying 3D scene
+ MouseArea {
+ anchors.fill: parent
+ }
+
+ TextField {
+ id: valueLabel
+
+ anchors {
+ left: parent.left
+ leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
+ verticalCenter: parent.verticalCenter
+ }
+
+ width: 40 * screenScaleFactor
+ text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
+ horizontalAlignment: TextInput.AlignRight
+
+ // key bindings, work when label is currenctly focused (active handle in LayerSlider)
+ Keys.onUpPressed: sliderLabelRoot.setValue(sliderLabelRoot.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
+ Keys.onDownPressed: sliderLabelRoot.setValue(sliderLabelRoot.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
+
+ style: TextFieldStyle {
+ textColor: UM.Theme.getColor("setting_control_text")
+ font: UM.Theme.getFont("default")
+ background: Item { }
+ }
+
+ onEditingFinished: {
+
+ // Ensure that the cursor is at the first position. On some systems the text isn't fully visible
+ // Seems to have to do something with different dpi densities that QML doesn't quite handle.
+ // Another option would be to increase the size even further, but that gives pretty ugly results.
+ cursorPosition = 0
+
+ if (valueLabel.text != "") {
+ // -startFrom because we need to convert back to an array structure
+ sliderLabelRoot.setValue(parseInt(valueLabel.text) - startFrom)
+ }
+ }
+
+ validator: IntValidator {
+ bottom:startFrom
+ top: sliderLabelRoot.maximumValue + startFrom // +startFrom because maybe we want to start in a different value rather than 0
+ }
+ }
+
+ BusyIndicator {
+ id: busyIndicator
+
+ anchors {
+ left: parent.right
+ leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
+ verticalCenter: parent.verticalCenter
+ }
+
+ width: sliderLabelRoot.height
+ height: width
+
+ visible: sliderLabelRoot.busy
+ running: sliderLabelRoot.busy
+ }
+}
diff --git a/plugins/LayerView/LayerView.py b/plugins/SimulationView/SimulationView.py
old mode 100755
new mode 100644
similarity index 74%
rename from plugins/LayerView/LayerView.py
rename to plugins/SimulationView/SimulationView.py
index 04be97b747..90f64a8224
--- a/plugins/LayerView/LayerView.py
+++ b/plugins/SimulationView/SimulationView.py
@@ -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.
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.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
-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 os.path
## View used to display g-code paths.
-class LayerView(View):
- # Must match LayerView.qml
+class SimulationView(View):
+ # Must match SimulationView.qml
LAYER_VIEW_TYPE_MATERIAL_TYPE = 0
LAYER_VIEW_TYPE_LINE_TYPE = 1
+ LAYER_VIEW_TYPE_FEEDRATE = 2
+ LAYER_VIEW_TYPE_THICKNESS = 3
def __init__(self):
super().__init__()
@@ -54,22 +54,29 @@ class LayerView(View):
self._activity = False
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._simulation_running = False
self._ghost_shader = None
self._layer_pass = None
self._composite_pass = None
self._old_layer_bindings = None
- self._layerview_composite_shader = None
+ self._simulationview_composite_shader = None
self._old_composite_shader = None
self._global_container_stack = None
- self._proxy = LayerViewProxy.LayerViewProxy()
+ self._proxy = SimulationViewProxy.SimulationViewProxy()
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._resetSettings()
self._legend_items = None
self._show_travel_moves = False
+ self._nozzle_node = None
Preferences.getInstance().addPreference("view/top_layer_count", 5)
Preferences.getInstance().addPreference("view/only_show_top_layers", False)
@@ -91,7 +98,7 @@ class LayerView(View):
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"),
- title = catalog.i18nc("@info:title", "Layer View"))
+ title = catalog.i18nc("@info:title", "Simulation View"))
def _resetSettings(self):
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_skin = 1
self._show_infill = 1
+ self.resetLayerData()
def getActivity(self):
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:
# Currently the RenderPass constructor requires a size > 0
# 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._layer_pass.setLayerView(self)
+ self._layer_pass.setSimulationView(self)
return self._layer_pass
def getCurrentLayer(self):
@@ -120,13 +134,26 @@ class LayerView(View):
def getMinimumLayer(self):
return self._minimum_layer_num
- def _onSceneChanged(self, node):
- self.calculateMaxLayers()
-
def getMaxLayers(self):
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):
return self._busy
@@ -136,9 +163,19 @@ class LayerView(View):
self._busy = busy
self.busyChanged.emit()
+ def isSimulationRunning(self):
+ return self._simulation_running
+
+ def setSimulationRunning(self, running):
+ self._simulation_running = running
+
def resetLayerData(self):
self._current_layer_mesh = 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):
scene = self.getController().getScene()
@@ -186,15 +223,43 @@ class LayerView(View):
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
#
- # \param layer_view_type integer as in LayerView.qml and this class
- def setLayerViewType(self, layer_view_type):
+ # \param layer_view_type integer as in SimulationView.qml and this class
+ def setSimulationViewType(self, layer_view_type):
self._layer_view_type = layer_view_type
self.currentLayerNumChanged.emit()
- ## Return the layer view type, integer as in LayerView.qml and this class
- def getLayerViewType(self):
+ ## Return the layer view type, integer as in SimulationView.qml and this class
+ def getSimulationViewType(self):
return self._layer_view_type
## Set the extruder opacity
@@ -243,9 +308,20 @@ class LayerView(View):
def getExtruderCount(self):
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):
scene = self.getController().getScene()
- self._activity = True
self._old_max_layers = self._max_layers
## Recalculate num max layers
@@ -255,9 +331,16 @@ class LayerView(View):
if not layer_data:
continue
+ self.setActivity(True)
min_layer_number = sys.maxsize
max_layer_number = -sys.maxsize
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:
max_layer_number = layer_id
if min_layer_number > layer_id:
@@ -281,10 +364,32 @@ class LayerView(View):
self.maxLayersChanged.emit()
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()
+ maxPathsChanged = Signal()
currentLayerNumChanged = Signal()
+ currentPathNumChanged = Signal()
globalStackChanged = 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
# as this caused some issues.
@@ -308,26 +413,31 @@ class LayerView(View):
return True
if event.type == Event.ViewActivateEvent:
- # Make sure the LayerPass is created
- layer_pass = self.getLayerPass()
+ # Make sure the SimulationPass is created
+ layer_pass = self.getSimulationPass()
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)
self._onGlobalStackChanged()
- if not self._layerview_composite_shader:
- self._layerview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), "layerview_composite.shader"))
+ if not self._simulationview_composite_shader:
+ self._simulationview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), "simulationview_composite.shader"))
theme = Application.getInstance().getTheme()
- self._layerview_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_background_color", Color(*theme.getColor("viewport_background").getRgb()))
+ self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))
if not self._composite_pass:
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._composite_pass.getLayerBindings().append("layerview")
+ self._composite_pass.getLayerBindings().append("simulationview")
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:
self._wireprint_warning_message.hide()
@@ -335,6 +445,7 @@ class LayerView(View):
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
+ self._nozzle_node.setParent(None)
self.getRenderer().removeRenderPass(self._layer_pass)
self._composite_pass.setLayerBindings(self._old_layer_bindings)
self._composite_pass.setCompositeShader(self._old_composite_shader)
@@ -364,6 +475,9 @@ class LayerView(View):
else:
self._wireprint_warning_message.hide()
+ def _onCurrentLayerNumChanged(self):
+ self.calculateMaxPathsOnLayer(self._current_layer_num)
+
def _startUpdateTopLayers(self):
if not self._compatibility_mode:
return
@@ -397,7 +511,7 @@ class LayerView(View):
self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(
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("|")):
try:
diff --git a/plugins/SimulationView/SimulationView.qml b/plugins/SimulationView/SimulationView.qml
new file mode 100644
index 0000000000..4c7d99deec
--- /dev/null
+++ b/plugins/SimulationView/SimulationView.qml
@@ -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")
+ }
+}
diff --git a/plugins/SimulationView/SimulationViewProxy.py b/plugins/SimulationView/SimulationViewProxy.py
new file mode 100644
index 0000000000..2bd707293f
--- /dev/null
+++ b/plugins/SimulationView/SimulationViewProxy.py
@@ -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)
diff --git a/plugins/SimulationView/__init__.py b/plugins/SimulationView/__init__.py
new file mode 100644
index 0000000000..f7ccf41acc
--- /dev/null
+++ b/plugins/SimulationView/__init__.py
@@ -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()}
diff --git a/plugins/LayerView/layers.shader b/plugins/SimulationView/layers.shader
old mode 100755
new mode 100644
similarity index 100%
rename from plugins/LayerView/layers.shader
rename to plugins/SimulationView/layers.shader
diff --git a/plugins/LayerView/layers3d.shader b/plugins/SimulationView/layers3d.shader
old mode 100755
new mode 100644
similarity index 92%
rename from plugins/LayerView/layers3d.shader
rename to plugins/SimulationView/layers3d.shader
index e8fe425c70..f377fca055
--- a/plugins/LayerView/layers3d.shader
+++ b/plugins/SimulationView/layers3d.shader
@@ -6,6 +6,10 @@ vertex41core =
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewProjectionMatrix;
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 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 float a_extruder;
in highp float a_line_type;
+ in highp float a_feedrate;
+ in highp float a_thickness;
out lowp vec4 v_color;
@@ -32,6 +38,15 @@ vertex41core =
out highp vec3 f_vertex;
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()
{
vec4 v1_vertex = a_vertex;
@@ -48,6 +63,12 @@ vertex41core =
case 1: // "Line type"
v_color = a_color;
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;
@@ -247,6 +268,12 @@ u_show_helpers = 1
u_show_skin = 1
u_show_infill = 1
+u_min_feedrate = 0
+u_max_feedrate = 1
+
+u_min_thickness = 0
+u_max_thickness = 1
+
[bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix
u_modelMatrix = model_matrix
@@ -262,3 +289,5 @@ a_line_dim = line_dim
a_extruder = extruder
a_material_color = material_color
a_line_type = line_type
+a_feedrate = feedrate
+a_thickness = thickness
diff --git a/plugins/SimulationView/layers3d_shadow.shader b/plugins/SimulationView/layers3d_shadow.shader
new file mode 100644
index 0000000000..ad75fcf9d0
--- /dev/null
+++ b/plugins/SimulationView/layers3d_shadow.shader
@@ -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
diff --git a/plugins/SimulationView/layers_shadow.shader b/plugins/SimulationView/layers_shadow.shader
new file mode 100644
index 0000000000..972f18c921
--- /dev/null
+++ b/plugins/SimulationView/layers_shadow.shader
@@ -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
diff --git a/plugins/LayerView/plugin.json b/plugins/SimulationView/plugin.json
similarity index 54%
rename from plugins/LayerView/plugin.json
rename to plugins/SimulationView/plugin.json
index 232fe9c361..0e7bec0626 100644
--- a/plugins/LayerView/plugin.json
+++ b/plugins/SimulationView/plugin.json
@@ -1,8 +1,8 @@
{
- "name": "Layer View",
+ "name": "Simulation View",
"author": "Ultimaker B.V.",
"version": "1.0.0",
- "description": "Provides the Layer view.",
+ "description": "Provides the Simulation view.",
"api": 4,
"i18n-catalog": "cura"
}
diff --git a/plugins/SimulationView/resources/nozzle.stl b/plugins/SimulationView/resources/nozzle.stl
new file mode 100644
index 0000000000..7f4b22804a
Binary files /dev/null and b/plugins/SimulationView/resources/nozzle.stl differ
diff --git a/plugins/SimulationView/resources/simulation_pause.svg b/plugins/SimulationView/resources/simulation_pause.svg
new file mode 100644
index 0000000000..67f7deea5d
--- /dev/null
+++ b/plugins/SimulationView/resources/simulation_pause.svg
@@ -0,0 +1,79 @@
+
+
diff --git a/plugins/SimulationView/resources/simulation_resume.svg b/plugins/SimulationView/resources/simulation_resume.svg
new file mode 100644
index 0000000000..a8ed8e79a3
--- /dev/null
+++ b/plugins/SimulationView/resources/simulation_resume.svg
@@ -0,0 +1,82 @@
+
+
diff --git a/plugins/LayerView/layerview_composite.shader b/plugins/SimulationView/simulationview_composite.shader
similarity index 100%
rename from plugins/LayerView/layerview_composite.shader
rename to plugins/SimulationView/simulationview_composite.shader
diff --git a/resources/themes/cura-dark/theme.json b/resources/themes/cura-dark/theme.json
index e61c48bffd..5cfed426e5 100644
--- a/resources/themes/cura-dark/theme.json
+++ b/resources/themes/cura-dark/theme.json
@@ -133,6 +133,7 @@
"slider_groove_fill": [245, 245, 245, 255],
"slider_handle": [255, 255, 255, 255],
"slider_handle_hover": [77, 182, 226, 255],
+ "slider_handle_active": [68, 192, 255, 255],
"slider_handle_border": [39, 44, 48, 255],
"slider_text_background": [255, 255, 255, 255],
@@ -194,6 +195,7 @@
"layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 255, 255],
"layerview_support_interface": [64, 192, 255, 255],
+ "layerview_nozzle": [181, 166, 66, 120],
"material_compatibility_warning": [255, 255, 255, 255],
diff --git a/resources/themes/cura-light/styles.qml b/resources/themes/cura-light/styles.qml
index 6d991c5541..ea9d184926 100755
--- a/resources/themes/cura-light/styles.qml
+++ b/resources/themes/cura-light/styles.qml
@@ -269,6 +269,7 @@ QtObject {
arrowSize: Theme.getSize("button_tooltip_arrow").width
color: Theme.getColor("button_tooltip")
opacity: control.hovered ? 1.0 : 0.0;
+ visible: control.text != ""
width: control.hovered ? button_tip.width + Theme.getSize("button_tooltip").width : 0
height: Theme.getSize("button_tooltip").height
diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json
index f084e87da2..4197285dd8 100644
--- a/resources/themes/cura-light/theme.json
+++ b/resources/themes/cura-light/theme.json
@@ -183,6 +183,7 @@
"slider_groove_fill": [127, 127, 127, 255],
"slider_handle": [0, 0, 0, 255],
"slider_handle_hover": [77, 182, 226, 255],
+ "slider_handle_active": [68, 192, 255, 255],
"slider_handle_border": [39, 44, 48, 255],
"slider_text_background": [255, 255, 255, 255],
@@ -271,7 +272,8 @@
"layerview_support_infill": [0, 255, 255, 255],
"layerview_move_combing": [0, 0, 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": {