diff --git a/plugins/XRayView/XRayPass.py b/cura/XRayPass.py similarity index 93% rename from plugins/XRayView/XRayPass.py rename to cura/XRayPass.py index a75d393b35..edc20ce62d 100644 --- a/plugins/XRayView/XRayPass.py +++ b/cura/XRayPass.py @@ -3,6 +3,7 @@ import os.path +from UM.Resources import Resources from UM.Application import Application from UM.PluginRegistry import PluginRegistry @@ -23,7 +24,7 @@ class XRayPass(RenderPass): def render(self): if not self._shader: - self._shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("XRayView"), "xray.shader")) + self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "xray.shader")) batch = RenderBatch(self._shader, type = RenderBatch.RenderType.NoType, backface_cull = False, blend_mode = RenderBatch.BlendMode.Additive) for node in DepthFirstIterator(self._scene.getRoot()): diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 4f15bafedb..565a704b0a 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -1,17 +1,29 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import os.path from UM.View.View import View from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Selection import Selection from UM.Resources import Resources +from PyQt5.QtGui import QOpenGLContext + from UM.Application import Application -from UM.View.RenderBatch import RenderBatch +from UM.Logger import Logger from UM.Math.Color import Color +from UM.PluginRegistry import PluginRegistry +from UM.Platform import Platform +from UM.Event import Event + +from UM.View.RenderBatch import RenderBatch from UM.View.GL.OpenGL import OpenGL +from cura.CuraApplication import CuraApplication + from cura.Settings.ExtruderManager import ExtruderManager +from cura import XRayPass + import math ## Standard view for mesh models. @@ -27,12 +39,20 @@ class SolidView(View): self._non_printing_shader = None self._support_mesh_shader = None + self._xray_shader = None + self._xray_pass = None + self._xray_composite_shader = None + self._composite_pass = None + self._extruders_model = None self._theme = None self._support_angle = 90 self._global_stack = None + self._old_composite_shader = None + self._old_layer_bindings = None + Application.getInstance().engineCreatedSignal.connect(self._onGlobalContainerChanged) def _onGlobalContainerChanged(self) -> None: @@ -98,6 +118,32 @@ class SolidView(View): self._checkSetup() + if not self._xray_shader: + self._xray_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "xray.shader")) + self._xray_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("xray").getRgb())) + + if not self._xray_composite_shader: + self._xray_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SolidView"), "xray_composite.shader")) + theme = Application.getInstance().getTheme() + self._xray_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb())) + self._xray_composite_shader.setUniformValue("u_error_color", Color(*theme.getColor("xray_error").getRgb())) + self._xray_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb())) + + if not self.getRenderer().getRenderPass("xray"): + # Currently the RenderPass constructor requires a size > 0 + # This should be fixed in RenderPass's constructor. + self._xray_pass = XRayPass.XRayPass(1, 1) + + self.getRenderer().addRenderPass(self._xray_pass) + + if not self._composite_pass: + self._composite_pass = self.getRenderer().getRenderPass("composite") + + self._old_layer_bindings = self._composite_pass.getLayerBindings() + self._composite_pass.setLayerBindings(["default", "selection", "xray"]) + self._old_composite_shader = self._composite_pass.getCompositeShader() + self._composite_pass.setCompositeShader(self._xray_composite_shader) + global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: if Application.getInstance().getPreferences().getValue("view/show_overhang"): @@ -174,5 +220,31 @@ class SolidView(View): if node.callDecoration("isGroup") and Selection.isSelected(node): renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = RenderBatch.RenderMode.LineLoop) + def endRendering(self): pass + + def event(self, event): + if event.type == Event.ViewActivateEvent: + # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching. + # This can happen when you do the following steps: + # 1. Start Cura + # 2. Load a model + # 3. Switch to Custom mode + # 4. Select the model and click on the per-object tool icon + # 5. Switch view to Layer view or X-Ray + # 6. Cura will very likely crash + # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why. + # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL + # context is None. + if Platform.isOSX(): + if QOpenGLContext.currentContext() is None: + Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later") + CuraApplication.getInstance().callLater(lambda e = event: self.event(e)) + return + + + if event.type == Event.ViewDeactivateEvent: + self.getRenderer().removeRenderPass(self._xray_pass) + self._composite_pass.setLayerBindings(self._old_layer_bindings) + self._composite_pass.setCompositeShader(self._old_composite_shader) diff --git a/plugins/SolidView/xray_composite.shader b/plugins/SolidView/xray_composite.shader new file mode 100644 index 0000000000..e3ac2162a7 --- /dev/null +++ b/plugins/SolidView/xray_composite.shader @@ -0,0 +1,166 @@ +[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 = + #ifdef GL_ES + #ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + #else + precision mediump float; + #endif // GL_FRAGMENT_PRECISION_HIGH + #endif // GL_ES + uniform sampler2D u_layer0; //Default pass. + uniform sampler2D u_layer1; //Selection pass. + uniform sampler2D u_layer2; //X-ray pass. + + uniform vec2 u_offset[9]; + + uniform float u_outline_strength; + uniform vec4 u_outline_color; + uniform vec4 u_error_color; + uniform vec4 u_background_color; + + 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); + + varying vec2 v_uvs; + + float kernel[9]; + + void main() + { + 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 layer0 = texture2D(u_layer0, v_uvs); + + result = layer0 * layer0.a + result * (1.0 - layer0.a); + + float intersection_count = (texture2D(u_layer2, v_uvs).r * 255.0) / 5.0; + if(mod(intersection_count, 2.0) >= 1.0) + { + result = u_error_color; + } + + 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); + } + + vec4 layer1 = texture2D(u_layer1, v_uvs); + if((layer1.rgb == x_axis || layer1.rgb == y_axis || layer1.rgb == z_axis)) + { + gl_FragColor = result; + } + else + { + gl_FragColor = mix(result, u_outline_color, abs(sum.a)); + } + + gl_FragColor.a = gl_FragColor.a > 0.5 ? 1.0 : 0.0; + } + +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; //Default pass. + uniform sampler2D u_layer1; //Selection pass. + uniform sampler2D u_layer2; //X-ray pass. + + uniform vec2 u_offset[9]; + + uniform float u_outline_strength; + uniform vec4 u_outline_color; + uniform vec4 u_error_color; + uniform vec4 u_background_color; + + 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); + + in vec2 v_uvs; + out vec4 frag_color; + + float kernel[9]; + + void main() + { + 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 layer0 = texture(u_layer0, v_uvs); + + result = layer0 * layer0.a + result * (1.0 - layer0.a); + + float intersection_count = (texture(u_layer2, v_uvs).r * 255.0) / 5.0; + if(mod(intersection_count, 2.0) >= 1.0) + { + result = u_error_color; + } + + 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); + } + + vec4 layer1 = texture(u_layer1, v_uvs); + if((layer1.rgb == x_axis || layer1.rgb == y_axis || layer1.rgb == z_axis)) + { + frag_color = result; + } + else + { + frag_color = mix(result, u_outline_color, abs(sum.a)); + } + + frag_color.a = frag_color.a > 0.5 ? 1.0 : 0.0; + } + +[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] +u_error_color = [1.0, 0.0, 0.0, 1.0] + +[bindings] + +[attributes] +a_vertex = vertex +a_uvs = uv + diff --git a/plugins/SolidView/xray_overlay.shader b/plugins/SolidView/xray_overlay.shader new file mode 100755 index 0000000000..ba032b2123 --- /dev/null +++ b/plugins/SolidView/xray_overlay.shader @@ -0,0 +1,50 @@ +[shaders] +vertex = + uniform highp mat4 u_modelViewProjectionMatrix; + + attribute highp vec4 a_vertex; + + void main() + { + gl_Position = u_modelViewProjectionMatrix * a_vertex; + } + +fragment = + uniform lowp vec4 u_xray_error; + + void main() + { + gl_FragColor = u_xray_error; + } + +vertex41core = + #version 410 + uniform highp mat4 u_modelViewProjectionMatrix; + + in highp vec4 a_vertex; + + void main() + { + gl_Position = u_modelViewProjectionMatrix * a_vertex; + } + +fragment41core = + #version 410 + uniform lowp vec4 u_xray_error; + + out vec4 frag_color; + + void main() + { + + frag_color = u_xray_error; + } + +[defaults] +u_xray_error = [1.0, 1.0, 1.0, 1.0] + +[bindings] +u_modelViewProjectionMatrix = model_view_projection_matrix + +[attributes] +a_vertex = vertex diff --git a/plugins/XRayView/XRayView.py b/plugins/XRayView/XRayView.py index 88a5a441b8..d94835c65e 100644 --- a/plugins/XRayView/XRayView.py +++ b/plugins/XRayView/XRayView.py @@ -8,6 +8,7 @@ from UM.Application import Application from UM.Logger import Logger from UM.Math.Color import Color from UM.PluginRegistry import PluginRegistry +from UM.Resources import Resources from UM.Platform import Platform from UM.Event import Event from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator @@ -19,7 +20,8 @@ from cura.CuraApplication import CuraApplication from cura.CuraView import CuraView from cura.Scene.ConvexHullNode import ConvexHullNode -from . import XRayPass +from cura import XRayPass + ## View used to display a see-through version of objects with errors highlighted. class XRayView(CuraView): @@ -38,7 +40,7 @@ class XRayView(CuraView): renderer = self.getRenderer() if not self._xray_shader: - self._xray_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("XRayView"), "xray.shader")) + self._xray_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "xray.shader")) self._xray_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("xray").getRgb())) for node in BreadthFirstIterator(scene.getRoot()): diff --git a/plugins/XRayView/xray.shader b/resources/shaders/xray.shader similarity index 100% rename from plugins/XRayView/xray.shader rename to resources/shaders/xray.shader