diff --git a/plugins/SimulationView/LayerSlider.qml b/plugins/SimulationView/LayerSlider.qml
new file mode 100644
index 0000000000..22f9d91340
--- /dev/null
+++ b/plugins/SimulationView/LayerSlider.qml
@@ -0,0 +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 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..24a13eaf7a
--- /dev/null
+++ b/plugins/SimulationView/SimulationPass.py
@@ -0,0 +1,186 @@
+# 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 .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/SimulationView/SimulationSliderLabel.qml b/plugins/SimulationView/SimulationSliderLabel.qml
new file mode 100644
index 0000000000..1c8daf867f
--- /dev/null
+++ b/plugins/SimulationView/SimulationSliderLabel.qml
@@ -0,0 +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
+ 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/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py
new file mode 100644
index 0000000000..2751ea4f60
--- /dev/null
+++ b/plugins/SimulationView/SimulationView.py
@@ -0,0 +1,609 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import sys
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QApplication
+
+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.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.Selection import Selection
+from UM.Signal import Signal
+from UM.View.GL.OpenGL import OpenGL
+from UM.View.GL.OpenGLContext import OpenGLContext
+from UM.View.View import View
+from UM.i18n import i18nCatalog
+from cura.ConvexHullNode import ConvexHullNode
+
+from .NozzleNode import NozzleNode
+from .SimulationPass import SimulationPass
+from .SimulationViewProxy import SimulationViewProxy
+
+catalog = i18nCatalog("cura")
+
+import numpy
+import os.path
+
+## View used to display g-code paths.
+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__()
+
+ self._max_layers = 0
+ self._current_layer_num = 0
+ self._minimum_layer_num = 0
+ self._current_layer_mesh = None
+ self._current_layer_jumps = None
+ self._top_layers_job = None
+ 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._simulationview_composite_shader = None
+ self._old_composite_shader = None
+
+ self._global_container_stack = None
+ self._proxy = 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)
+ Preferences.getInstance().addPreference("view/force_layer_view_compatibility_mode", False)
+
+ Preferences.getInstance().addPreference("layerview/layer_view_type", 0)
+ Preferences.getInstance().addPreference("layerview/extruder_opacities", "")
+
+ Preferences.getInstance().addPreference("layerview/show_travel_moves", False)
+ Preferences.getInstance().addPreference("layerview/show_helpers", True)
+ Preferences.getInstance().addPreference("layerview/show_skin", True)
+ Preferences.getInstance().addPreference("layerview/show_infill", True)
+
+ Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
+ self._updateWithPreferences()
+
+ self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
+ self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
+ 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", "Simulation View"))
+
+ def _resetSettings(self):
+ self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed
+ self._extruder_count = 0
+ self._extruder_opacity = [1.0, 1.0, 1.0, 1.0]
+ self._show_travel_moves = 0
+ self._show_helpers = 1
+ self._show_skin = 1
+ self._show_infill = 1
+ self.resetLayerData()
+
+ def getActivity(self):
+ return self._activity
+
+ 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 = SimulationPass(1, 1)
+ self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
+ self._layer_pass.setSimulationView(self)
+ return self._layer_pass
+
+ def getCurrentLayer(self):
+ return self._current_layer_num
+
+ def getMinimumLayer(self):
+ return self._minimum_layer_num
+
+ def getMaxLayers(self):
+ return self._max_layers
+
+ 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
+
+ def setBusy(self, busy):
+ if busy != self._busy:
+ 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()
+ renderer = self.getRenderer()
+
+ if not self._ghost_shader:
+ self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
+ self._ghost_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_ghost").getRgb()))
+
+ for node in DepthFirstIterator(scene.getRoot()):
+ # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
+ # However, it is somewhat relevant when the node is selected, so do render it then.
+ if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
+ continue
+
+ if not node.render(renderer):
+ if (node.getMeshData()) and node.isVisible():
+ renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
+
+ def setLayer(self, value):
+ if self._current_layer_num != value:
+ self._current_layer_num = value
+ if self._current_layer_num < 0:
+ self._current_layer_num = 0
+ if self._current_layer_num > self._max_layers:
+ self._current_layer_num = self._max_layers
+ if self._current_layer_num < self._minimum_layer_num:
+ self._minimum_layer_num = self._current_layer_num
+
+ self._startUpdateTopLayers()
+
+ self.currentLayerNumChanged.emit()
+
+ def setMinimumLayer(self, value):
+ if self._minimum_layer_num != value:
+ self._minimum_layer_num = value
+ if self._minimum_layer_num < 0:
+ self._minimum_layer_num = 0
+ if self._minimum_layer_num > self._max_layers:
+ self._minimum_layer_num = self._max_layers
+ if self._minimum_layer_num > self._current_layer_num:
+ self._current_layer_num = self._minimum_layer_num
+
+ self._startUpdateTopLayers()
+
+ 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 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 SimulationView.qml and this class
+ def getSimulationViewType(self):
+ return self._layer_view_type
+
+ ## Set the extruder opacity
+ #
+ # \param extruder_nr 0..3
+ # \param opacity 0.0 .. 1.0
+ def setExtruderOpacity(self, extruder_nr, opacity):
+ if 0 <= extruder_nr <= 3:
+ self._extruder_opacity[extruder_nr] = opacity
+ self.currentLayerNumChanged.emit()
+
+ def getExtruderOpacities(self):
+ return self._extruder_opacity
+
+ def setShowTravelMoves(self, show):
+ self._show_travel_moves = show
+ self.currentLayerNumChanged.emit()
+
+ def getShowTravelMoves(self):
+ return self._show_travel_moves
+
+ def setShowHelpers(self, show):
+ self._show_helpers = show
+ self.currentLayerNumChanged.emit()
+
+ def getShowHelpers(self):
+ return self._show_helpers
+
+ def setShowSkin(self, show):
+ self._show_skin = show
+ self.currentLayerNumChanged.emit()
+
+ def getShowSkin(self):
+ return self._show_skin
+
+ def setShowInfill(self, show):
+ self._show_infill = show
+ self.currentLayerNumChanged.emit()
+
+ def getShowInfill(self):
+ return self._show_infill
+
+ def getCompatibilityMode(self):
+ return self._compatibility_mode
+
+ 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._old_max_layers = self._max_layers
+ ## Recalculate num max layers
+ new_max_layers = 0
+ for node in DepthFirstIterator(scene.getRoot()):
+ layer_data = node.callDecoration("getLayerData")
+ 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:
+ min_layer_number = layer_id
+ layer_count = max_layer_number - min_layer_number
+
+ if new_max_layers < layer_count:
+ new_max_layers = layer_count
+
+ if new_max_layers > 0 and new_max_layers != self._old_max_layers:
+ self._max_layers = new_max_layers
+
+ # The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
+ # if it's the largest value. If we don't do this, we can have a slider block outside of the
+ # slider.
+ if new_max_layers > self._current_layer_num:
+ self.maxLayersChanged.emit()
+ self.setLayer(int(self._max_layers))
+ else:
+ self.setLayer(int(self._max_layers))
+ 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.
+ def getProxy(self, engine, script_engine):
+ return self._proxy
+
+ def endRendering(self):
+ pass
+
+ def event(self, event):
+ modifiers = QApplication.keyboardModifiers()
+ ctrl_is_active = modifiers & Qt.ControlModifier
+ shift_is_active = modifiers & Qt.ShiftModifier
+ if event.type == Event.KeyPressEvent and ctrl_is_active:
+ amount = 10 if shift_is_active else 1
+ if event.key == KeyEvent.UpKey:
+ self.setLayer(self._current_layer_num + amount)
+ return True
+ if event.key == KeyEvent.DownKey:
+ self.setLayer(self._current_layer_num - amount)
+ return True
+
+ if event.type == Event.ViewActivateEvent:
+ # 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._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._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("simulationview")
+ self._old_composite_shader = self._composite_pass.getCompositeShader()
+ self._composite_pass.setCompositeShader(self._simulationview_composite_shader)
+
+ elif event.type == Event.ViewDeactivateEvent:
+ self._wireprint_warning_message.hide()
+ Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
+ 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)
+
+ def getCurrentLayerMesh(self):
+ return self._current_layer_mesh
+
+ def getCurrentLayerJumps(self):
+ return self._current_layer_jumps
+
+ def _onGlobalStackChanged(self):
+ if self._global_container_stack:
+ self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
+ self._global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if self._global_container_stack:
+ self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
+ self._extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
+ self._onPropertyChanged("wireframe_enabled", "value")
+ self.globalStackChanged.emit()
+ else:
+ self._wireprint_warning_message.hide()
+
+ def _onPropertyChanged(self, key, property_name):
+ if key == "wireframe_enabled" and property_name == "value":
+ if self._global_container_stack.getProperty("wireframe_enabled", "value"):
+ self._wireprint_warning_message.show()
+ else:
+ self._wireprint_warning_message.hide()
+
+ def _onCurrentLayerNumChanged(self):
+ self.calculateMaxPathsOnLayer(self._current_layer_num)
+
+ def _startUpdateTopLayers(self):
+ if not self._compatibility_mode:
+ return
+
+ if self._top_layers_job:
+ self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh)
+ self._top_layers_job.cancel()
+
+ self.setBusy(True)
+
+ self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
+ self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
+ self._top_layers_job.start()
+
+ def _updateCurrentLayerMesh(self, job):
+ self.setBusy(False)
+
+ if not job.getResult():
+ return
+ self.resetLayerData() # Reset the layer data only when job is done. Doing it now prevents "blinking" data.
+ self._current_layer_mesh = job.getResult().get("layers")
+ if self._show_travel_moves:
+ self._current_layer_jumps = job.getResult().get("jumps")
+ self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
+
+ self._top_layers_job = None
+
+ def _updateWithPreferences(self):
+ self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
+ self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
+ self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(
+ Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
+
+ 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:
+ opacity = float(extruder_opacity)
+ except ValueError:
+ opacity = 1.0
+ self.setExtruderOpacity(extruder_nr, opacity)
+
+ self.setShowTravelMoves(bool(Preferences.getInstance().getValue("layerview/show_travel_moves")))
+ self.setShowHelpers(bool(Preferences.getInstance().getValue("layerview/show_helpers")))
+ self.setShowSkin(bool(Preferences.getInstance().getValue("layerview/show_skin")))
+ self.setShowInfill(bool(Preferences.getInstance().getValue("layerview/show_infill")))
+
+ self._startUpdateTopLayers()
+ self.preferencesChanged.emit()
+
+ def _onPreferencesChanged(self, preference):
+ if preference not in {
+ "view/top_layer_count",
+ "view/only_show_top_layers",
+ "view/force_layer_view_compatibility_mode",
+ "layerview/layer_view_type",
+ "layerview/extruder_opacities",
+ "layerview/show_travel_moves",
+ "layerview/show_helpers",
+ "layerview/show_skin",
+ "layerview/show_infill",
+ }:
+ return
+
+ self._updateWithPreferences()
+
+
+class _CreateTopLayersJob(Job):
+ def __init__(self, scene, layer_number, solid_layers):
+ super().__init__()
+
+ self._scene = scene
+ self._layer_number = layer_number
+ self._solid_layers = solid_layers
+ self._cancel = False
+
+ def run(self):
+ layer_data = None
+ for node in DepthFirstIterator(self._scene.getRoot()):
+ layer_data = node.callDecoration("getLayerData")
+ if layer_data:
+ break
+
+ if self._cancel or not layer_data:
+ return
+
+ layer_mesh = MeshBuilder()
+ for i in range(self._solid_layers):
+ layer_number = self._layer_number - i
+ if layer_number < 0:
+ continue
+
+ try:
+ layer = layer_data.getLayer(layer_number).createMesh()
+ except Exception:
+ Logger.logException("w", "An exception occurred while creating layer mesh.")
+ return
+
+ if not layer or layer.getVertices() is None:
+ continue
+
+ layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices())
+ layer_mesh.addVertices(layer.getVertices())
+
+ # Scale layer color by a brightness factor based on the current layer number
+ # This will result in a range of 0.5 - 1.0 to multiply colors by.
+ brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
+ brightness[0, 3] = 1.0
+ layer_mesh.addColors(layer.getColors() * brightness)
+
+ if self._cancel:
+ return
+
+ Job.yieldThread()
+
+ if self._cancel:
+ return
+
+ Job.yieldThread()
+ jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
+ if not jump_mesh or jump_mesh.getVertices() is None:
+ jump_mesh = None
+
+ self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
+
+ def cancel(self):
+ self._cancel = True
+ super().cancel()
+
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..e144b841e6
--- /dev/null
+++ b/plugins/SimulationView/SimulationViewProxy.py
@@ -0,0 +1,259 @@
+# 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/SimulationView/layers.shader b/plugins/SimulationView/layers.shader
new file mode 100644
index 0000000000..d340773403
--- /dev/null
+++ b/plugins/SimulationView/layers.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 = a_color;
+ // 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 = a_color;
+ 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/SimulationView/layers3d.shader b/plugins/SimulationView/layers3d.shader
new file mode 100644
index 0000000000..f377fca055
--- /dev/null
+++ b/plugins/SimulationView/layers3d.shader
@@ -0,0 +1,293 @@
+[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 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
+
+ uniform highp mat4 u_normalMatrix;
+
+ in highp vec4 a_vertex;
+ in lowp vec4 a_color;
+ 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;
+ in highp float a_feedrate;
+ in highp float a_thickness;
+
+ 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;
+
+ 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;
+ 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
+
+ switch (u_layer_view_type) {
+ case 0: // "Material color"
+ v_color = a_material_color;
+ break;
+ 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;
+ 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_layer_view_type = 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
+
+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
+u_viewProjectionMatrix = view_projection_matrix
+u_normalMatrix = normal_matrix
+u_lightPosition = light_0_position
+
+[attributes]
+a_vertex = vertex
+a_color = color
+a_normal = normal
+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/SimulationView/plugin.json b/plugins/SimulationView/plugin.json
new file mode 100644
index 0000000000..0e7bec0626
--- /dev/null
+++ b/plugins/SimulationView/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Simulation View",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.0",
+ "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/SimulationView/simulationview_composite.shader b/plugins/SimulationView/simulationview_composite.shader
new file mode 100644
index 0000000000..dcc02acc84
--- /dev/null
+++ b/plugins/SimulationView/simulationview_composite.shader
@@ -0,0 +1,148 @@
+[shaders]
+vertex =
+ uniform highp mat4 u_modelViewProjectionMatrix;
+ attribute highp vec4 a_vertex;
+ attribute highp vec2 a_uvs;
+
+ varying highp vec2 v_uvs;
+
+ void main()
+ {
+ gl_Position = u_modelViewProjectionMatrix * a_vertex;
+ v_uvs = a_uvs;
+ }
+
+fragment =
+ uniform sampler2D u_layer0;
+ uniform sampler2D u_layer1;
+ uniform sampler2D u_layer2;
+
+ uniform vec2 u_offset[9];
+
+ uniform vec4 u_background_color;
+ uniform float u_outline_strength;
+ uniform vec4 u_outline_color;
+
+ varying vec2 v_uvs;
+
+ float kernel[9];
+
+ const vec3 x_axis = vec3(1.0, 0.0, 0.0);
+ const vec3 y_axis = vec3(0.0, 1.0, 0.0);
+ const vec3 z_axis = vec3(0.0, 0.0, 1.0);
+
+ void main()
+ {
+ // blur kernel
+ kernel[0] = 0.0; kernel[1] = 1.0; kernel[2] = 0.0;
+ kernel[3] = 1.0; kernel[4] = -4.0; kernel[5] = 1.0;
+ kernel[6] = 0.0; kernel[7] = 1.0; kernel[8] = 0.0;
+
+ vec4 result = u_background_color;
+
+ vec4 main_layer = texture2D(u_layer0, v_uvs);
+ vec4 selection_layer = texture2D(u_layer1, v_uvs);
+ vec4 layerview_layer = texture2D(u_layer2, v_uvs);
+
+ result = main_layer * main_layer.a + result * (1.0 - main_layer.a);
+ result = layerview_layer * layerview_layer.a + result * (1.0 - layerview_layer.a);
+
+ vec4 sum = vec4(0.0);
+ for (int i = 0; i < 9; i++)
+ {
+ vec4 color = vec4(texture2D(u_layer1, v_uvs.xy + u_offset[i]).a);
+ sum += color * (kernel[i] / u_outline_strength);
+ }
+
+ if((selection_layer.rgb == x_axis || selection_layer.rgb == y_axis || selection_layer.rgb == z_axis))
+ {
+ gl_FragColor = result;
+ }
+ else
+ {
+ gl_FragColor = mix(result, u_outline_color, abs(sum.a));
+ }
+ }
+
+vertex41core =
+ #version 410
+ uniform highp mat4 u_modelViewProjectionMatrix;
+ in highp vec4 a_vertex;
+ in highp vec2 a_uvs;
+
+ out highp vec2 v_uvs;
+
+ void main()
+ {
+ gl_Position = u_modelViewProjectionMatrix * a_vertex;
+ v_uvs = a_uvs;
+ }
+
+fragment41core =
+ #version 410
+ uniform sampler2D u_layer0;
+ uniform sampler2D u_layer1;
+ uniform sampler2D u_layer2;
+
+ uniform vec2 u_offset[9];
+
+ uniform vec4 u_background_color;
+ uniform float u_outline_strength;
+ uniform vec4 u_outline_color;
+
+ in vec2 v_uvs;
+
+ float kernel[9];
+
+ const vec3 x_axis = vec3(1.0, 0.0, 0.0);
+ const vec3 y_axis = vec3(0.0, 1.0, 0.0);
+ const vec3 z_axis = vec3(0.0, 0.0, 1.0);
+
+ out vec4 frag_color;
+
+ void main()
+ {
+ // blur kernel
+ kernel[0] = 0.0; kernel[1] = 1.0; kernel[2] = 0.0;
+ kernel[3] = 1.0; kernel[4] = -4.0; kernel[5] = 1.0;
+ kernel[6] = 0.0; kernel[7] = 1.0; kernel[8] = 0.0;
+
+ vec4 result = u_background_color;
+
+ vec4 main_layer = texture(u_layer0, v_uvs);
+ vec4 selection_layer = texture(u_layer1, v_uvs);
+ vec4 layerview_layer = texture(u_layer2, v_uvs);
+
+ result = main_layer * main_layer.a + result * (1.0 - main_layer.a);
+ result = layerview_layer * layerview_layer.a + result * (1.0 - layerview_layer.a);
+
+ vec4 sum = vec4(0.0);
+ for (int i = 0; i < 9; i++)
+ {
+ vec4 color = vec4(texture(u_layer1, v_uvs.xy + u_offset[i]).a);
+ sum += color * (kernel[i] / u_outline_strength);
+ }
+
+ if((selection_layer.rgb == x_axis || selection_layer.rgb == y_axis || selection_layer.rgb == z_axis))
+ {
+ frag_color = result;
+ }
+ else
+ {
+ frag_color = mix(result, u_outline_color, abs(sum.a));
+ }
+ }
+
+[defaults]
+u_layer0 = 0
+u_layer1 = 1
+u_layer2 = 2
+u_background_color = [0.965, 0.965, 0.965, 1.0]
+u_outline_strength = 1.0
+u_outline_color = [0.05, 0.66, 0.89, 1.0]
+
+[bindings]
+
+[attributes]
+a_vertex = vertex
+a_uvs = uv