Source code for dtcc_viewer.opengl.window

import glfw
import imgui
import math
import numpy as np
from OpenGL.GL import *
from imgui.integrations.glfw import GlfwRenderer
from dtcc_viewer.logging import info, warning
from dtcc_viewer.opengl.parameters import GuiParametersGlobal
from dtcc_viewer.opengl.action import Action
from dtcc_viewer.opengl.gl_points import GlPoints
from dtcc_viewer.opengl.gl_lines import GlLines
from dtcc_viewer.opengl.gl_raster import GlRaster
from dtcc_viewer.opengl.gl_object import GlObject
from dtcc_viewer.opengl.gl_grid import GlGrid
from dtcc_viewer.opengl.gl_axes import GlAxes
from dtcc_viewer.opengl.gl_model import GlModel
from dtcc_viewer.opengl.gl_mesh import GlMesh
from dtcc_viewer.opengl.gl_quad import GlQuad
from dtcc_viewer.opengl.scene import Scene
from dtcc_viewer.opengl.gui import Gui

from dtcc_viewer.opengl.wrp_bounds import BoundsWrapper
from dtcc_viewer.opengl.wrp_lines import LinesWrapper
from dtcc_viewer.opengl.wrp_object import ObjectWrapper
from dtcc_viewer.opengl.wrp_geometries import GeometriesWrapper
from dtcc_viewer.opengl.wrp_city import CityWrapper
from dtcc_viewer.opengl.wrp_mesh import MeshWrapper
from dtcc_viewer.opengl.wrp_grid import GridWrapper, VolumeGridWrapper
from dtcc_viewer.opengl.wrp_linestring import LineStringWrapper, MultiLineStringWrapper
from dtcc_viewer.opengl.wrp_pointcloud import PointCloudWrapper
from dtcc_viewer.opengl.wrp_surface import SurfaceWrapper, MultiSurfaceWrapper
from dtcc_viewer.opengl.wrp_raster import RasterWrapper, MultiRasterWrapper
from dtcc_viewer.opengl.wrp_building import BuildingWrapper
from dtcc_viewer.opengl.wrp_volume_mesh import VolumeMeshWrapper
from dtcc_viewer.opengl.wrp_roadnetwork import RoadNetworkWrapper


[docs] class Window: """OpenGL Rendering Window. This class represents an OpenGL rendering window with interactive controls for visualizing meshes and point clouds. The window provides methods for rendering various types of data and handling user interactions. Parameters ---------- width : int The width of the window in pixels. height : int The height of the window in pixels. Attributes ---------- gl_objects : list[GlObject] List of GlObject instances to be rendered in the window. gui : Gui An instance of the GUI for interacting with the rendering. guip : GuiParameters GUI parameters common for the entire window. width : int The width of the window in pixels. height : int The height of the window in pixels. interaction : Interaction An Interaction object managing user input interactions. impl : GlfwRenderer GlfwRenderer instance for rendering ImGui GUI. fps : int Frames per second for the rendering loop. time : float Current time in seconds. time_acum : float Accumulated time for FPS calculation. """ gl_objects: list[GlObject] model: GlModel gl_grid: GlGrid gl_axes: GlAxes gui: Gui guip: GuiParametersGlobal # Gui parameters common for the whole window win_width: int win_height: int action: Action impl: GlfwRenderer fps: int time: float time_acum: float
[docs] def __init__(self, width: int, height: int): """Initialize the OpenGL rendering window and setting up default parameters. Parameters ---------- width : int The width of the window in pixels. height : int The height of the window in pixels. """ self.win_width = width self.win_height = height self.action = Action(width, height) imgui.create_context() self.gui = Gui() self.io = imgui.get_io() if not glfw.init(): raise Exception("glfw can not be initialised!") glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True) glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) self.window = glfw.create_window( self.win_width, self.win_height, "DTCC Viewer", None, None ) # Create window if not self.window: glfw.terminate() raise Exception("glfw window can not be created!") # Calculate screen position for window primary_monitor = glfw.get_primary_monitor() mode = glfw.get_video_mode(primary_monitor) x_pos = (mode.size.width - self.win_width) // 2 y_pos = (mode.size.height - self.win_height) // 2 glfw.set_window_pos(self.window, x_pos, y_pos) # Calls can be made after the contex is made current glfw.make_context_current(self.window) # Callback should be called after the impl has been registered self.impl = GlfwRenderer(self.window) # Register callback functions to enable mouse and keyboard interaction glfw.set_window_size_callback(self.window, self._window_resize_callback) glfw.set_cursor_pos_callback(self.window, self.action.mouse_look_callback) glfw.set_key_callback(self.window, self.action.key_input_callback) glfw.set_mouse_button_callback(self.window, self.action.mouse_input_callback) glfw.set_scroll_callback(self.window, self.action.scroll_input_callback) self._update_window_framebuffer_size()
def _preprocess_model(self, scene: Scene): """Preprocess the scene model. This method processes the scene objects and prepares them for rendering. It converts scene wrappers into OpenGL objects and sets up the rendering model. Parameters ---------- scene : Scene The scene containing objects to be preprocessed. """ self.gl_objects = [] scene.offset_mesh_part_ids() info("Preprocessing scene objects...") info(f"Scene has {len(scene.wrappers)} objects.") for wrapper in scene.wrappers: if isinstance(wrapper, ObjectWrapper): if wrapper.mesh_wrp_1 is not None: self.gl_objects.append(GlMesh(wrapper.mesh_wrp_1)) if wrapper.mesh_wrp_2 is not None: self.gl_objects.append(GlMesh(wrapper.mesh_wrp_2)) if wrapper.lss_wrp is not None: self.gl_objects.append(GlLines(wrapper.lss_wrp)) if wrapper.pc_wrp is not None: self.gl_objects.append(GlPoints(wrapper.pc_wrp)) elif isinstance(wrapper, CityWrapper): if wrapper.mesh_bld is not None: self.gl_objects.append(GlMesh(wrapper.mesh_bld)) if wrapper.mesh_ter is not None: self.gl_objects.append(GlMesh(wrapper.mesh_ter)) for grid_wrp in wrapper.grid_wrps: self.gl_objects.append(GlLines(grid_wrp.lines_wrp)) for vgrid_wrp in wrapper.vgrid_wrps: self.gl_objects.append(GlLines(vgrid_wrp.lines_wrp)) for pc_wrp in wrapper.pc_wrps: self.gl_objects.append(GlPoints(pc_wrp)) elif isinstance(wrapper, GeometriesWrapper): for mesh_w in wrapper.mesh_wrps: self.gl_objects.append(GlMesh(mesh_w)) for srf in wrapper.srf_wrps: self.gl_objects.append(GlMesh(srf.mesh_wrp)) for ms in wrapper.ms_wrps: self.gl_objects.append(GlMesh(ms.mesh_wrp)) for mls_wrp in wrapper.mls_wrps: self.gl_objects.append(GlLines(mls_wrp)) for ls_wrp in wrapper.lss_wrps: self.gl_objects.append(GlLines(ls_wrp)) for bnds_wrp in wrapper.bnds_wrps: self.gl_objects.append(GlLines(bnds_wrp.ls_wrp)) for pc in wrapper.pc_wrps: self.gl_objects.append(GlPoints(pc)) for mesh_wrp in wrapper.vmesh_wrps: self.gl_objects.append(GlMesh(mesh_wrp.mesh_vol_wrp)) self.gl_objects.append(GlMesh(mesh_wrp.mesh_env_wrp)) for grd_wrp in wrapper.grd_wrps: self.gl_objects.append(GlLines(grd_wrp.lines_wrp, False)) for vgrd_wrp in wrapper.vgrd_wrps: self.gl_objects.append(GlLines(vgrd_wrp.lines_wrp, False)) elif isinstance(wrapper, MeshWrapper): self.gl_objects.append(GlMesh(wrapper)) elif isinstance(wrapper, SurfaceWrapper): self.gl_objects.append(GlMesh(wrapper.mesh_wrp)) elif isinstance(wrapper, MultiSurfaceWrapper): self.gl_objects.append(GlMesh(wrapper.mesh_wrp)) elif isinstance(wrapper, BuildingWrapper): self.gl_objects.append(GlMesh(wrapper.mesh_wrp)) elif isinstance(wrapper, MultiLineStringWrapper): self.gl_objects.append(GlLines(wrapper)) elif isinstance(wrapper, LineStringWrapper): self.gl_objects.append(GlLines(wrapper)) elif isinstance(wrapper, LinesWrapper): self.gl_objects.append(GlLines(wrapper)) elif isinstance(wrapper, RoadNetworkWrapper): self.gl_objects.append(GlLines(wrapper.mls_wrp)) elif isinstance(wrapper, BoundsWrapper): self.gl_objects.append(GlLines(wrapper.ls_wrp)) elif isinstance(wrapper, PointCloudWrapper): self.gl_objects.append(GlPoints(wrapper)) elif isinstance(wrapper, GridWrapper): if wrapper.lines_wrp is not None: self.gl_objects.append(GlLines(wrapper.lines_wrp, False)) elif isinstance(wrapper, VolumeGridWrapper): if wrapper.lines_wrp is not None: self.gl_objects.append(GlLines(wrapper.lines_wrp, False)) elif isinstance(wrapper, VolumeMeshWrapper): if wrapper.mesh_vol_wrp is not None: self.gl_objects.append(GlMesh(wrapper.mesh_vol_wrp)) if wrapper.mesh_env_wrp is not None: self.gl_objects.append(GlMesh(wrapper.mesh_env_wrp)) elif isinstance(wrapper, RasterWrapper): self.gl_objects.append(GlRaster(wrapper)) elif isinstance(wrapper, MultiRasterWrapper): for raster_wrp in wrapper.raster_wrappers: self.gl_objects.append(GlRaster(raster_wrp)) else: warning(f"Wrapper type {type(wrapper)} not supported!") if len(self.gl_objects) == 0: warning("No meshes, points or lines added to scene!") return False # Create model from meshes self.model = GlModel(self.gl_objects, scene.bb) # Initialise camera base on bounding box size self.action.initialise_camera(scene.bb) # Create opengl data for all the objects if not self.model.preprocess(): warning("GLModel preprocessing failed!") # Create picking frame buffer object self.model.create_picking_fbo(self.action) # Create grid and coordinate axes self.gl_grid = GlGrid(scene.bb) self.gl_axes = GlAxes(1.0, scene.bb.zmin) return True
[docs] def render(self, scene: Scene): """Render single or multiple objects. This method renders multiple meshes, line collections and and point clouds in the window. It updates the rendering loop, handles user interactions, and displays the GUI elements for each rendered object. Parameters ---------- scene: Scene The scene with objects to be rendered. """ if scene.wrappers is None or len(scene.wrappers) == 0: warning("Scene has no objects to render. Viewer aborted!") return False if not scene.preprocess_drawing(): warning("Scene preprocessing failed. Viewer aborted!") glfw.terminate() return False if not self._preprocess_model(scene): warning("Model preprocessing failed. Viewer aborted!") glfw.terminate() return False glClearColor(0.0, 0.0, 0.0, 1) glEnable(GL_DEPTH_TEST) glEnable(GL_BLEND) glDepthFunc(GL_LESS) info(f"Rendering scene...") while not glfw.window_should_close(self.window): glfw.poll_events() glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) color = self.action.gguip.color glClearColor(color[0], color[1], color[2], color[3]) # Enable clipping planes self._clipping_planes() # Check if the mouse is on the GUI or on the model self.action.set_mouse_on_gui(self.io.want_capture_mouse) # Update camera for selected objects if self.action.update_zoom_selected: self.model.zoom_selected(self.action) # Update camera for selected view if self.action.gguip.update_camera: self.action.update_view() # True if the user has clicked on the model if self.action.picking: self.model.evaluate_picking(self.action) # Render the model if self.model.guip.show: self.model.render(self.action) # Draw grid self.gl_grid.render(self.action) # Draw axes self.gl_axes.render(self.action) # Render the GUI self.gui.render(self.model, self.impl, self.action.gguip) self.action.gguip.calc_fps() glfw.swap_buffers(self.window) glfw.terminate()
def _window_resize_callback(self, window, width, height): """Callback for window resize events. This method is a callback function that gets called when the window is resized. It updates the viewport size and interaction parameters. """ self._update_window_framebuffer_size() self.model.create_picking_fbo(self.action) def _update_window_framebuffer_size(self): """Update the window framebuffer size. This method updates the size of the framebuffer and the viewport, when the user resizes the window and adjusts the action parameters accordingly. """ fbuf_size = glfw.get_framebuffer_size(self.window) win_size = glfw.get_window_size(self.window) fb_width = fbuf_size[0] fb_height = fbuf_size[1] win_width = win_size[0] win_height = win_size[1] glViewport(0, 0, fb_width, fb_height) self.action.update_window_size(fb_width, fb_height, win_width, win_height) def _clipping_planes(self): """Enable or disable clipping planes based on GUI parameters. This method enables or disables the clipping planes based on the settings specified in the GUI parameters. """ if self.action.gguip.clip_bool[0]: glEnable(GL_CLIP_DISTANCE0) else: glDisable(GL_CLIP_DISTANCE0) if self.action.gguip.clip_bool[1]: glEnable(GL_CLIP_DISTANCE1) else: glDisable(GL_CLIP_DISTANCE1) if self.action.gguip.clip_bool[2]: glEnable(GL_CLIP_DISTANCE2) else: glDisable(GL_CLIP_DISTANCE2) # Grid clipping planes if self.action.gguip.show_grid: glEnable(GL_CLIP_DISTANCE3) glEnable(GL_CLIP_DISTANCE4) glEnable(GL_CLIP_DISTANCE5) glEnable(GL_CLIP_DISTANCE6) else: glDisable(GL_CLIP_DISTANCE3) glDisable(GL_CLIP_DISTANCE4) glDisable(GL_CLIP_DISTANCE5) glDisable(GL_CLIP_DISTANCE6)