diff --git a/CHANGELOG.md b/CHANGELOG.md index 8361774ff..f248685df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `list` to accepted types for `Scene.add`. * Added `list[float]` to accepted types for `Camera.position` and `Camera.target`. * Added `unit` to `Viewer` and `Config`. +* Added `bounding_box` and `_update_bounding_box` to `BufferObject`. ### Changed * Fixed `opacity` bug with `BufferObject`. +* Updated `SceneForm` to avoid completely reload when scene objects not changed. ### Removed diff --git a/src/compas_viewer/components/sceneform.py b/src/compas_viewer/components/sceneform.py index a352d60e6..55dee93de 100644 --- a/src/compas_viewer/components/sceneform.py +++ b/src/compas_viewer/components/sceneform.py @@ -48,6 +48,7 @@ def __init__( self.setColumnCount(len(columns)) self.setHeaderLabels(col["title"] for col in self.columns) self.setHeaderHidden(not show_headers) + self._sceneobjects = [] self.callback = callback @@ -65,52 +66,62 @@ def scene(self): return self.viewer.scene def update(self): - self.clear() # TODO: do not clear when objects are same. - self.checkbox_columns = {} - - for node in self.scene.traverse("breadthfirst"): - if node.is_root: - continue - - strings = [] - - for i, column in enumerate(self.columns): - type = column.get("type", None) - if type == "checkbox": - action = column.get("action") - checked = column.get("checked") - if not action or not checked: - raise ValueError("Both action and checked must be provided for checkbox") - self.checkbox_columns[i] = {"action": action, "checked": checked} - strings.append("") - elif type == "label": - text = column.get("text") - if not text: - raise ValueError("Text must be provided for label") - strings.append(text(node)) - - parent_widget = self if node.parent.is_root else node.parent.attributes["widget"] - widget = QTreeWidgetItem(parent_widget, strings) - widget.node = node - widget.setSelected(node.is_selected) - if node.is_selected: - - def expand(node): - if node.attributes.get("widget"): - node.attributes["widget"].setExpanded(True) - if node.parent and not node.parent.is_root: - expand(node.parent) - - expand(node.parent) - - widget.setFlags(widget.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled) - - for col, col_data in self.checkbox_columns.items(): - widget.setCheckState(col, Qt.Checked if col_data["checked"](node) else Qt.Unchecked) - - node.attributes["widget"] = widget - - self.adjust_column_widths() + if list(self.scene.objects) == self._sceneobjects: + for node in self.scene.traverse("breadthfirst"): + widget = node.attributes.get("widget") + if widget: + widget.setSelected(node.is_selected) + if node.is_selected: + self.expand(node.parent) + + else: + self._sceneobjects = list(self.scene.objects) + + self.clear() + self.checkbox_columns = {} + + for node in self.scene.traverse("breadthfirst"): + if node.is_root: + continue + + strings = [] + + for i, column in enumerate(self.columns): + type = column.get("type", None) + if type == "checkbox": + action = column.get("action") + checked = column.get("checked") + if not action or not checked: + raise ValueError("Both action and checked must be provided for checkbox") + self.checkbox_columns[i] = {"action": action, "checked": checked} + strings.append("") + elif type == "label": + text = column.get("text") + if not text: + raise ValueError("Text must be provided for label") + strings.append(text(node)) + + parent_widget = self if node.parent.is_root else node.parent.attributes["widget"] + widget = QTreeWidgetItem(parent_widget, strings) + widget.node = node + widget.setSelected(node.is_selected) + if node.is_selected: + self.expand(node.parent) + + widget.setFlags(widget.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled) + + for col, col_data in self.checkbox_columns.items(): + widget.setCheckState(col, Qt.Checked if col_data["checked"](node) else Qt.Unchecked) + + node.attributes["widget"] = widget + + self.adjust_column_widths() + + def expand(self, node): + if node.attributes.get("widget"): + node.attributes["widget"].setExpanded(True) + if node.parent and not node.parent.is_root: + self.expand(node.parent) def on_item_clicked(self, item, column): if column in self.checkbox_columns: diff --git a/src/compas_viewer/scene/bufferobject.py b/src/compas_viewer/scene/bufferobject.py index c9cd7c8ec..6db6dada7 100644 --- a/src/compas_viewer/scene/bufferobject.py +++ b/src/compas_viewer/scene/bufferobject.py @@ -211,18 +211,30 @@ def __init__( self.is_selected = False self.background = False self._matrix_buffer = None + self._bounding_box = None self._bounding_box_center = None @property def buffergeometry(self) -> BufferGeometry: return self.item + @property + def bounding_box(self) -> NDArray: + if self._bounding_box is None: + self._bounding_box = np.array([np.min(self.buffergeometry.points, axis=0), np.max(self.buffergeometry.points, axis=0)]) + return self._bounding_box + @property def bounding_box_center(self) -> NDArray: if self._bounding_box_center is None: self._bounding_box_center = np.mean(self.buffergeometry.points.reshape(-1, 3), axis=0) return self._bounding_box_center + def _update_bounding_box(self): + self._bounding_box = None + self._bounding_box_center = None + # Set to None so that they are recalculated next time they are accessed + def init(self): """Initialize the object""" self.instance_color = Color.from_rgb255(*next(self.scene._instance_colors_generator))