diff --git a/CHANGELOG.md b/CHANGELOG.md index 7df90f9dc..95029090f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added + +* Added `viewport` and `camera` in the config * Added the update method for the `textobject`. + ### Changed +* Change the str options in `view_port` into all lower case. +* Change naming `view_mode` to `viewport_mode`, `view_port` to `viewport`. * Doc building using `sphinx_compas2_theme `. * Fix the documentation: title lines, comments. * Fix a bug when camera is looking straight up or down. diff --git a/docs/_images/example_camera_config.jpg b/docs/_images/example_camera_config.jpg new file mode 100644 index 000000000..f264e4c07 Binary files /dev/null and b/docs/_images/example_camera_config.jpg differ diff --git a/docs/_images/example_camera_config_2.jpg b/docs/_images/example_camera_config_2.jpg new file mode 100644 index 000000000..7585ecfaa Binary files /dev/null and b/docs/_images/example_camera_config_2.jpg differ diff --git a/docs/examples/control/example_camera_config.py b/docs/examples/control/example_camera_config.py new file mode 100644 index 000000000..7c72cc555 --- /dev/null +++ b/docs/examples/control/example_camera_config.py @@ -0,0 +1,36 @@ +from compas.geometry import Box +from compas.geometry import Frame +from compas_view2.app import App + +# If you input incomplete configuration, the rest will be filled by the default values. + +config = { + "view": { + "show_grid": True, + "viewmode": "shaded", + "background_color": [1, 1, 1, 1], + "selection_color": [1.0, 1.0, 0.0], + "viewport": "top", + "camera": { + "fov": 45, + "near": 0.1, + "far": 1000, + "position": [-15, -15, 15], + "target": [1,1, 1], + "scale": 1, + }, + } +} + +viewer_default = App() +viewer_custom = App(config=config) + + +box = Box(Frame([0, 0, 0], [1, 0, 0], [0, 1, 0]), 1, 1, 1) + +viewer_default.add(box) +viewer_custom.add(box) + + +viewer_default.run() +viewer_custom.run() diff --git a/docs/examples/control/example_camera_config.rst b/docs/examples/control/example_camera_config.rst new file mode 100644 index 000000000..ea4b86be4 --- /dev/null +++ b/docs/examples/control/example_camera_config.rst @@ -0,0 +1,33 @@ +******************************************************************************* +Camera Config +******************************************************************************* + +.. autosummary:: + :toctree: + :nosignatures: + +Default Camera Config +======================================= + +By default, the camera is configed 45 dregees perspective: + +.. figure:: /_images/example_camera_config.jpg + :figclass: figure + :class: figure-img img-fluid + + + +Custom Camera Config +====================== + +You can customize the camera configreation by passing the dictionary to the viewer: fov, near, fac, position, target, scale. + +.. note:: + The `position` is not editable and would be ingored from the config file in `top`, `front`, `right` modes. + +.. figure:: /_images/example_camera_config_2.jpg + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_camera_config.py + :language: python diff --git a/docs/tutorials/files/config_default.json b/docs/tutorials/files/config_default.json index 42b7a88b5..723ae9ed0 100644 --- a/docs/tutorials/files/config_default.json +++ b/docs/tutorials/files/config_default.json @@ -12,7 +12,7 @@ }, "view": { "show_grid": true, - "view_mode": "shaded", + "viewmode": "shaded", "background_color": [1, 1, 1, 1], "selection_color": [1.0, 1.0, 0.0] }, diff --git a/docs/tutorials/tutorial_basics.rst b/docs/tutorials/tutorial_basics.rst index 640321906..4828e8093 100644 --- a/docs/tutorials/tutorial_basics.rst +++ b/docs/tutorials/tutorial_basics.rst @@ -189,7 +189,8 @@ use the "on" decorator (:meth:`compas_view2.app.App.on`) on a callback function. Zoom, Pan, Rotate, and Select -=============================== +============================================================================== + After launching the viewer, the view can be transformed by zooming, panning, and rotating. Object selection is also possible. diff --git a/docs/tutorials/tutorial_configuration.rst b/docs/tutorials/tutorial_configuration.rst index 0fca4f3e3..4c395ce5a 100644 --- a/docs/tutorials/tutorial_configuration.rst +++ b/docs/tutorials/tutorial_configuration.rst @@ -52,7 +52,7 @@ This is a sustainable and sharable way to customize your viewer. :: >>> from compas_view2.app import App - >>> viewer = App(viewmode="lighted", enable_sceneform=True, enable_propertyform=True, enable_sidebar=True, width=2000, height=1000) + >>> viewer = App(viewmode="lighted", viewport = "top", enable_sceneform=True, enable_propertyform=True, enable_sidebar=True, width=2000, height=1000) :: @@ -69,12 +69,6 @@ Configuration Structure The default configuration file can be downloaded here: :download:`Link `, or can be printed by the following code: -:: - - >>> # This prints the default configuration - >>> from compas_view2 import Info - >>> Info().show_config() - It it the template for creating your own settings, keyboard preferences, etc. Supported Keys diff --git a/src/compas_view2/app/app.py b/src/compas_view2/app/app.py index 9c3c29e0e..2dfc7a1a1 100644 --- a/src/compas_view2/app/app.py +++ b/src/compas_view2/app/app.py @@ -75,6 +75,8 @@ class App: viewmode : {'shaded', 'ghosted', 'wireframe', 'lighted'}, optional The display mode of the OpenGL view. It will override the value in the config file. In `ghosted` mode, all objects have a default opacity of 0.7. + viewport : {'front', 'right', 'top', 'perspective'}, optional + The viewport of the OpenGL view. It will override the value in the config file. show_grid : bool, optional Show the XY plane. It will override the value in the config file. config : dict | filepath, optional @@ -135,6 +137,7 @@ def __init__( width: int = None, height: int = None, viewmode: Literal["wireframe", "shaded", "ghosted", "lighted"] = None, + viewport: Literal["front", "right", "top", "perspective"] = None, show_grid: bool = None, enable_sidebar=None, enable_sidedock1: bool = None, @@ -177,6 +180,8 @@ def __init__( if viewmode is not None: config["view"]["viewmode"] = viewmode + if viewport is not None: + config["view"]["viewport"] = viewport if show_grid is not None: config["view"]["show_grid"] = show_grid diff --git a/src/compas_view2/app/config_default.json b/src/compas_view2/app/config_default.json index 256bfba61..c85e1da6a 100644 --- a/src/compas_view2/app/config_default.json +++ b/src/compas_view2/app/config_default.json @@ -13,9 +13,18 @@ }, "view": { "show_grid": true, - "view_mode": "shaded", + "viewmode": "shaded", + "viewport": "perspective", "background_color": [1, 1, 1, 1], - "selection_color": [1.0, 1.0, 0.0] + "selection_color": [1.0, 1.0, 0.0], + "camera": { + "fov": 45, + "near": 0.1, + "far": 1000, + "position":[-1.5, -1.5, 1.5], + "target": [0, 0, 0], + "scale": 1 + } }, "statusbar": { "texts": "Ready", diff --git a/src/compas_view2/app/controller.py b/src/compas_view2/app/controller.py index 7203d4984..54d1308ab 100644 --- a/src/compas_view2/app/controller.py +++ b/src/compas_view2/app/controller.py @@ -431,7 +431,7 @@ def view_front(self): None """ - self.app.view.current = self.app.view.FRONT + self.app.view.current = self.app.view.VIEWPORTS["front"] self.app.view.camera.reset_position() self.app.view.update_projection() self.app.view.update() @@ -444,7 +444,7 @@ def view_right(self): None """ - self.app.view.current = self.app.view.RIGHT + self.app.view.current = self.app.view.VIEWPORTS["right"] self.app.view.camera.reset_position() self.app.view.update_projection() self.app.view.update() @@ -457,7 +457,7 @@ def view_top(self): None """ - self.app.view.current = self.app.view.TOP + self.app.view.current = self.app.view.VIEWPORTS["top"] self.app.view.camera.reset_position() self.app.view.update_projection() self.app.view.update() @@ -470,7 +470,7 @@ def view_perspective(self): None """ - self.app.view.current = self.app.view.PERSPECTIVE + self.app.view.current = self.app.view.VIEWPORTS["perspective"] self.app.view.camera.reset_position() self.app.view.update_projection() self.app.view.update() diff --git a/src/compas_view2/scene/camera.py b/src/compas_view2/scene/camera.py index 30ba0b43f..f58179a66 100644 --- a/src/compas_view2/scene/camera.py +++ b/src/compas_view2/scene/camera.py @@ -1,18 +1,21 @@ -from compas.geometry import Translation -from compas.geometry import Rotation -from compas.geometry import Vector -from numpy.linalg import norm -from numpy.linalg import det from math import atan2 -from numpy import pi +from typing import List + from numpy import array from numpy import asfortranarray from numpy import dot from numpy import float32 +from numpy import pi +from numpy.linalg import det +from numpy.linalg import norm + +from compas.geometry import Rotation +from compas.geometry import Translation +from compas.geometry import Vector from compas_view2.objects import Object -from typing import List -from .matrices import perspective, ortho +from .matrices import ortho +from .matrices import perspective class Position(Vector): @@ -134,7 +137,7 @@ def __init__(self, view, fov=45, near=0.1, far=1000, position=None, target=None, self.reset_position() if target: self.target = target - if position: + if position and view._current == 4: self.position = position @property @@ -235,13 +238,13 @@ def _on_target_update(self, target): def reset_position(self): """Reset the position of the camera based current view type.""" self.target.set(0, 0, 0) - if self.view.current == self.view.PERSPECTIVE: + if self.view.current == self.view.VIEWPORTS["perspective"]: self.rotation.set(pi / 4, 0, -pi / 4) - if self.view.current == self.view.TOP: + if self.view.current == self.view.VIEWPORTS["top"]: self.rotation.set(0, 0, 0) - if self.view.current == self.view.FRONT: + if self.view.current == self.view.VIEWPORTS["front"]: self.rotation.set(pi / 2, 0, 0) - if self.view.current == self.view.RIGHT: + if self.view.current == self.view.VIEWPORTS["right"]: self.rotation.set(pi / 2, 0, pi / 2) def rotate(self, dx, dy): @@ -260,10 +263,10 @@ def rotate(self, dx, dy): Notes ----- - Camera rotations are only available if the current view is a perspective view (``camera.view.current == camera.view.PERSPECTIVE``). + Camera rotations are only available if the current view is a perspective view (``camera.view.current == camera.view.VIEWPORTS["perspective"]``). """ - if self.view.current == self.view.PERSPECTIVE: + if self.view.current == self.view.VIEWPORTS["perspective"]: self.rotation += [-self.rotation_delta * dy, 0, -self.rotation_delta * dx] def pan(self, dx, dy): @@ -323,7 +326,7 @@ def projection(self, width, height): """ aspect = width / height - if self.view.current == self.view.PERSPECTIVE: + if self.view.current == self.view.VIEWPORTS["perspective"]: P = perspective(self.fov, aspect, self.near * self.scale, self.far * self.scale) else: left = -self.distance diff --git a/src/compas_view2/views/view.py b/src/compas_view2/views/view.py index 7f9eb30d5..bc7330d08 100644 --- a/src/compas_view2/views/view.py +++ b/src/compas_view2/views/view.py @@ -1,11 +1,11 @@ import time -from OpenGL import GL +from OpenGL import GL from qtpy import QtCore from qtpy import QtWidgets -from compas_view2.scene import Camera from compas_view2.objects import GridObject +from compas_view2.scene import Camera class View(QtWidgets.QOpenGLWidget): @@ -19,23 +19,20 @@ class View(QtWidgets.QOpenGLWidget): The view configuration. """ - FRONT = 1 - RIGHT = 2 - TOP = 3 - PERSPECTIVE = 4 + VIEWPORTS = {"front": 1, "right": 2, "top": 3, "perspective": 4} def __init__(self, app, view_config): super().__init__() self.setFocusPolicy(QtCore.Qt.StrongFocus) self._opacity = 1.0 - self._current = View.PERSPECTIVE + self._current = self.VIEWPORTS[view_config["viewport"]] self.shader_model = None self.app = app self.color = view_config["background_color"] - self.mode = view_config["view_mode"] + self.mode = view_config["viewmode"] self.selection_color = view_config["selection_color"] self.show_grid = view_config["show_grid"] - self.camera = Camera(self) + self.camera = Camera(self, **view_config["camera"]) self.grid = GridObject(1, 10, 10) self.objects = {} self.keys = {"shift": False, "control": False, "f": False} diff --git a/src/compas_view2/views/view120.py b/src/compas_view2/views/view120.py index a63cd75e3..c33485f40 100644 --- a/src/compas_view2/views/view120.py +++ b/src/compas_view2/views/view120.py @@ -1,12 +1,9 @@ -from OpenGL import GL - -# from PIL import Image - import os + import numpy as np +from OpenGL import GL from compas.geometry import transform_points_numpy - from compas_view2.objects import BufferObject from compas_view2.objects import TextObject from compas_view2.objects import VectorObject @@ -14,6 +11,8 @@ from .view import View +# from PIL import Image + class View120(View): """View widget for OpenGL version 2.1 and GLSL 120 with a Compatibility Profile.""" @@ -120,7 +119,7 @@ def sort_objects_from_viewworld(self, viewworld): def paint(self): viewworld = self.camera.viewworld() - if self.current != self.PERSPECTIVE: + if self.current != self.VIEWPORTS["perspective"]: self.update_projection() # Draw instance maps