123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- # 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.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.
- class SolidView(View):
- def __init__(self):
- super().__init__()
- application = Application.getInstance()
- application.getPreferences().addPreference("view/show_overhang", True)
- application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
- self._enabled_shader = None
- self._disabled_shader = None
- 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:
- if self._global_stack:
- try:
- self._global_stack.propertyChanged.disconnect(self._onPropertyChanged)
- except TypeError:
- pass
- for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
- extruder_stack.propertyChanged.disconnect(self._onPropertyChanged)
- self._global_stack = Application.getInstance().getGlobalContainerStack()
- if self._global_stack:
- self._global_stack.propertyChanged.connect(self._onPropertyChanged)
- for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
- extruder_stack.propertyChanged.connect(self._onPropertyChanged)
- self._onPropertyChanged("support_angle", "value") # Force an re-evaluation
- def _onPropertyChanged(self, key: str, property_name: str) -> None:
- if key != "support_angle" or property_name != "value":
- return
- # As the rendering is called a *lot* we really, dont want to re-evaluate the property every time. So we store em!
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack:
- support_extruder_nr = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
- try:
- support_angle_stack = global_container_stack.extruderList[support_extruder_nr]
- except IndexError:
- pass
- else:
- self._support_angle = support_angle_stack.getProperty("support_angle", "value")
- def _checkSetup(self):
- if not self._extruders_model:
- self._extruders_model = Application.getInstance().getExtrudersModel()
- if not self._theme:
- self._theme = Application.getInstance().getTheme()
- if not self._enabled_shader:
- self._enabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
- self._enabled_shader.setUniformValue("u_overhangColor", Color(*self._theme.getColor("model_overhang").getRgb()))
- if not self._disabled_shader:
- self._disabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
- self._disabled_shader.setUniformValue("u_diffuseColor1", Color(*self._theme.getColor("model_unslicable").getRgb()))
- self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*self._theme.getColor("model_unslicable_alt").getRgb()))
- self._disabled_shader.setUniformValue("u_width", 50.0)
- if not self._non_printing_shader:
- self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
- self._non_printing_shader.setUniformValue("u_diffuseColor", Color(*self._theme.getColor("model_non_printing").getRgb()))
- self._non_printing_shader.setUniformValue("u_opacity", 0.6)
- if not self._support_mesh_shader:
- self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
- self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
- self._support_mesh_shader.setUniformValue("u_width", 5.0)
- def beginRendering(self):
- scene = self.getController().getScene()
- renderer = self.getRenderer()
- 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"):
- # Make sure the overhang angle is valid before passing it to the shader
- if self._support_angle is not None and self._support_angle >= 0 and self._support_angle <= 90:
- self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - self._support_angle)))
- else:
- self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang.
- else:
- self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))
- for node in DepthFirstIterator(scene.getRoot()):
- if not node.render(renderer):
- if node.getMeshData() and node.isVisible() and not node.callDecoration("getLayerData"):
- uniforms = {}
- shade_factor = 1.0
- per_mesh_stack = node.callDecoration("getStack")
- extruder_index = node.callDecoration("getActiveExtruderPosition")
- if extruder_index is None:
- extruder_index = "0"
- extruder_index = int(extruder_index)
- # Use the support extruder instead of the active extruder if this is a support_mesh
- if per_mesh_stack:
- if per_mesh_stack.getProperty("support_mesh", "value"):
- extruder_index = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
- try:
- material_color = self._extruders_model.getItem(extruder_index)["color"]
- except KeyError:
- material_color = self._extruders_model.defaultColors[0]
- if extruder_index != ExtruderManager.getInstance().activeExtruderIndex:
- # Shade objects that are printed with the non-active extruder 25% darker
- shade_factor = 0.6
- try:
- # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
- # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
- uniforms["diffuse_color"] = [
- shade_factor * int(material_color[1:3], 16) / 255,
- shade_factor * int(material_color[3:5], 16) / 255,
- shade_factor * int(material_color[5:7], 16) / 255,
- 1.0
- ]
- # Color the currently selected face-id. (Disable for now.)
- #face = Selection.getHoverFace()
- uniforms["hover_face"] = -1 #if not face or node != face[0] else face[1]
- except ValueError:
- pass
- if node.callDecoration("isNonPrintingMesh"):
- if per_mesh_stack and (per_mesh_stack.getProperty("infill_mesh", "value") or per_mesh_stack.getProperty("cutting_mesh", "value")):
- renderer.queueNode(node, shader = self._non_printing_shader, uniforms = uniforms, transparent = True)
- else:
- renderer.queueNode(node, shader = self._non_printing_shader, transparent = True)
- elif getattr(node, "_outside_buildarea", False):
- renderer.queueNode(node, shader = self._disabled_shader)
- elif per_mesh_stack and per_mesh_stack.getProperty("support_mesh", "value"):
- # Render support meshes with a vertical stripe that is darker
- shade_factor = 0.6
- uniforms["diffuse_color_2"] = [
- uniforms["diffuse_color"][0] * shade_factor,
- uniforms["diffuse_color"][1] * shade_factor,
- uniforms["diffuse_color"][2] * shade_factor,
- 1.0
- ]
- renderer.queueNode(node, shader = self._support_mesh_shader, uniforms = uniforms)
- else:
- renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms)
- 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)
|