Skip to content

Commit

Permalink
Merge branch 'main' into feature/robot
Browse files Browse the repository at this point in the history
  • Loading branch information
PingHsunTsai committed Jul 12, 2024
2 parents 6dc64f3 + b768d60 commit 4b85701
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 89 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

### Changed

### Removed


## [1.2.4] 2024-07-12

### Added

* 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`.
* Added `robot.py` example.

### Changed

* Fixed `opacity` bug with `BufferObject`.
* Updated `SceneForm` to avoid completely reload when scene objects not changed.

### Removed

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ doctest_optionflags = [
# ============================================================================

[tool.bumpversion]
current_version = "1.2.3"
current_version = "1.2.4"
message = "Bump version to {new_version}"
commit = true
tag = true
Expand Down
39 changes: 0 additions & 39 deletions scripts/forms.py

This file was deleted.

20 changes: 20 additions & 0 deletions scripts/unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from compas.colors import Color
from compas.geometry import Box
from compas.geometry import Frame
from compas_viewer.viewer import Viewer
from compas_viewer.config import Config

config = Config()
config.unit = "mm"
viewer = Viewer(config)

for i in range(10):
for j in range(10):
viewer.scene.add(
Box(500, 500, 500, Frame([i * 1000, j * 1000, 0], [1, 0, 0], [0, 1, 0])),
show_lines=True,
surfacecolor=Color(i / 10, j / 10, 0.0),
name=f"Box_{i}_{j}",
)

viewer.show()
2 changes: 1 addition & 1 deletion src/compas_viewer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
__copyright__ = "COMPAS Association"
__license__ = "MIT License"
__email__ = "[email protected]"
__version__ = "1.2.3"
__version__ = "1.2.4"


HERE = os.path.dirname(__file__)
Expand Down
103 changes: 57 additions & 46 deletions src/compas_viewer/components/sceneform.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/compas_viewer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ class UIConfig(ConfigBase):
@dataclass
class Config(ConfigBase):
vectorsize: float = 0.1
unit: Literal["m", "cm", "mm"] = "m"
ui: UIConfig = field(default_factory=UIConfig)
window: WindowConfig = field(default_factory=WindowConfig)
renderer: RendererConfig = field(default_factory=RendererConfig)
Expand Down
12 changes: 12 additions & 0 deletions src/compas_viewer/scene/bufferobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
30 changes: 28 additions & 2 deletions src/compas_viewer/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@

class Viewer(Singleton):
def __init__(self, config: Optional[Config] = None, **kwargs):
self.running = False
self.app = QApplication(sys.argv)
self.app.setApplicationName("COMPAS Viewer")
self.app.setApplicationDisplayName("COMPAS Viewer")
self.app.setWindowIcon(QIcon(os.path.join(HERE, "assets", "icons", "compas_icon_white.png")))

self._scene = None
self._unit = "m"

self.config = config or Config()
self.timer = QTimer()
Expand All @@ -36,8 +38,7 @@ def __init__(self, config: Optional[Config] = None, **kwargs):
# renderer should be part of UI
self.renderer = Renderer(self)
self.ui = UI(self)

self.running = False
self.unit = self.config.unit

@property
def scene(self) -> ViewerScene:
Expand All @@ -52,6 +53,31 @@ def scene(self, scene: Scene):
for obj in self._scene.objects:
obj.init()

@property
def unit(self) -> str:
return self._unit

@unit.setter
def unit(self, unit: str):
if self.running:
raise NotImplementedError("Changing the unit after the viewer is running is not yet supported.")
if unit != self._unit:
previous_scale = self.config.camera.scale
if unit == "m":
self.config.renderer.gridsize = (10.0, 10, 10.0, 10)
self.renderer.camera.scale = 1.0
elif unit == "cm":
self.config.renderer.gridsize = (1000.0, 10, 1000.0, 10)
self.renderer.camera.scale = 100.0
elif unit == "mm":
self.config.renderer.gridsize = (10000.0, 10, 10000.0, 10)
self.renderer.camera.scale = 1000.0
else:
raise ValueError(f"Invalid unit: {unit}. Valid units are 'm', 'cm', 'mm'.")
self.renderer.camera.distance *= self.renderer.camera.scale / previous_scale

self._unit = unit

def show(self):
self.running = True
self.ui.init()
Expand Down

0 comments on commit 4b85701

Please sign in to comment.