diff --git a/src/compas_viewer/commands.py b/src/compas_viewer/commands.py index 92652d07b3..ca0fe9de08 100644 --- a/src/compas_viewer/commands.py +++ b/src/compas_viewer/commands.py @@ -236,9 +236,6 @@ def select_all(viewer: "Viewer"): if obj.show and not obj.is_locked: obj.is_selected = True - viewer.ui.sidebar.update() - viewer.renderer.update() - select_all_cmd = Command(title="Select All", callback=select_all) @@ -247,9 +244,6 @@ def deselect_all(viewer: "Viewer"): for obj in viewer.scene.objects: obj.is_selected = False - viewer.ui.sidebar.update() - viewer.renderer.update() - deselect_all_cmd = Command(title="DeSelect All", callback=deselect_all) @@ -275,8 +269,6 @@ def select_object(viewer: "Viewer", event: QMouseEvent): if selected_obj: selected_obj.is_selected = True - viewer.ui.sidebar.update() - viewer.renderer.update() @@ -295,7 +287,6 @@ def select_multiple(viewer: "Viewer", event: QMouseEvent): selected_obj = viewer.scene.instance_colors.get(tuple(unique_color[0])) # type: ignore if selected_obj: selected_obj.is_selected = True - viewer.ui.sidebar.update() viewer.renderer.update() @@ -343,8 +334,6 @@ def select_window(viewer: "Viewer", event: QMouseEvent): obj.is_selected = True continue - viewer.ui.sidebar.update() - viewer.renderer.update() @@ -364,8 +353,6 @@ def deselect_object(viewer: "Viewer", event: QMouseEvent): if selected_obj: selected_obj.is_selected = False - viewer.ui.sidebar.update() - viewer.renderer.update() @@ -394,7 +381,6 @@ def delete_selected(): if obj.is_selected: viewer.scene.remove(obj) del obj - viewer.renderer.update() # ============================================================================= @@ -411,9 +397,6 @@ def clear_scene(viewer: "Viewer"): viewer.scene.remove(obj) del obj - viewer.ui.sidebar.update() - viewer.renderer.update() - clear_scene_cmd = Command(title="Clear Scene", callback=clear_scene) diff --git a/src/compas_viewer/components/sceneform.py b/src/compas_viewer/components/sceneform.py index 8335a9f230..1f748b0f44 100644 --- a/src/compas_viewer/components/sceneform.py +++ b/src/compas_viewer/components/sceneform.py @@ -114,8 +114,6 @@ def on_item_clicked(self, item, column): if self.callback and node.is_selected: self.callback(node) - self.viewer.renderer.update() - def on_item_selection_changed(self): for item in self.selectedItems(): if self.callback: diff --git a/src/compas_viewer/observer.py b/src/compas_viewer/observer.py new file mode 100644 index 0000000000..4cd01d9048 --- /dev/null +++ b/src/compas_viewer/observer.py @@ -0,0 +1,34 @@ +from PySide6.QtCore import QTimer + +from compas_viewer.base import Base + + +class Observer(Base): + def __init__(self): + self._observers = set( + ( + self.viewer.renderer, + self.viewer.ui.sidebar, + ) + ) + + self.update_timer = QTimer() + self.update_timer.setSingleShot(True) + self.update_timer.timeout.connect(self.update_observers) + self.debounce_interval = 200 + + def add_observer(self, observer): + if observer not in self._observers: + self._observers.add(observer) + + def remove_observer(self, observer): + if observer in self._observers: + self._observers.remove(observer) + + def request_update(self): + if not self.update_timer.isActive(): + self.update_timer.start(self.debounce_interval) + + def update_observers(self): + for observer in self._observers: + observer.update() diff --git a/src/compas_viewer/scene/scene.py b/src/compas_viewer/scene/scene.py index 1d40140356..001f13a180 100644 --- a/src/compas_viewer/scene/scene.py +++ b/src/compas_viewer/scene/scene.py @@ -9,6 +9,7 @@ from compas.datastructures import Datastructure from compas.geometry import Geometry from compas.scene import Scene +from compas_viewer.observer import Observer from .sceneobject import ViewerSceneObject @@ -68,6 +69,9 @@ class ViewerScene(Scene): def __init__(self, name: str = "ViewerScene", context: str = "Viewer"): super().__init__(name=name, context=context) + # Observer + self._observer = None + # Primitive self.objects: list[ViewerSceneObject] # Selection @@ -75,10 +79,11 @@ def __init__(self, name: str = "ViewerScene", context: str = "Viewer"): self._instance_colors_generator = instance_colors_generator() @property - def viewer(self): - from compas_viewer import Viewer - - return Viewer() + def observer(self): + """Observer: The observer object for the scene.""" + if self._observer is None: + self._observer = Observer() + return self._observer # TODO: These fixed kwargs could be moved to COMPAS core. def add( @@ -183,4 +188,17 @@ def add( u=u, **kwargs, ) + self.observer.request_update() return sceneobject + + def remove(self, obj: ViewerSceneObject) -> None: + """ + Remove an object from the scene. + + Parameters + ---------- + obj : :class:`compas_viewer.scene.ViewerSceneObject` + The object to remove. + """ + super().remove(obj) + self.observer.request_update() diff --git a/src/compas_viewer/scene/sceneobject.py b/src/compas_viewer/scene/sceneobject.py index b8a4567302..10cd7170fe 100644 --- a/src/compas_viewer/scene/sceneobject.py +++ b/src/compas_viewer/scene/sceneobject.py @@ -16,6 +16,7 @@ from compas_viewer.gl import make_vertex_buffer from compas_viewer.gl import update_index_buffer from compas_viewer.gl import update_vertex_buffer +from compas_viewer.observer import Observer from compas_viewer.renderer.shaders import Shader # Type template of point/line/face data for generating the buffers. @@ -101,6 +102,9 @@ def __init__( ): # Basic super().__init__(**kwargs) + + self._observer = None + self.show = show self.show_points = show_points if show_points is not None else False self.show_lines = show_lines if show_lines is not None else True @@ -111,7 +115,7 @@ def __init__( # Selection self._is_locked = is_locked - self.is_selected = not is_locked and is_selected + self._is_selected = False # Visual self.background: bool = False @@ -135,6 +139,23 @@ def __init__( self._inited = False + @property + def observer(self) -> bool: + """bool : Whether the scene is requested to be updated.""" + if self._observer is None: + self._observer = Observer() + return self._observer + + @property + def is_selected(self): + return self._is_selected + + @is_selected.setter + def is_selected(self, value): + if self._is_selected != value: + self._is_selected = value + self.observer.request_update() + @property def is_locked(self): return self._is_locked