Skip to content

Commit ba669cb

Browse files
authored
Merge pull request #1471 from compas-dev/group
Group
2 parents 8527872 + 5e77fb0 commit ba669cb

File tree

5 files changed

+188
-3
lines changed

5 files changed

+188
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
* Added `compas.scene.Scene.add_group()` for adding group.
13+
* Added `compas.scene.Group.add_from_list()` for adding a list of items to a group.
14+
1215
### Changed
1316

1417
* Fixed error in `circle_to_compas` from Rhino.
1518
* Fixed Rhino to Rhino brep serialization.
19+
* Upated `compas.scene.Group.add()` to pass on group kwargs as default for child items.
1620

1721
### Removed
1822

src/compas/scene/group.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- coding: utf-8 -*-
2+
import compas.data # noqa: F401
3+
24
from .sceneobject import SceneObject
35

46

@@ -9,6 +11,13 @@ class Group(SceneObject):
911
----------
1012
name : str, optional
1113
The name of the group.
14+
**kwargs : dict, optional
15+
Additional keyword arguments to pass on to the child sceneobjects as default values.
16+
17+
Attributes
18+
----------
19+
kwargs : dict
20+
The keyword arguments to pass on to the child sceneobjects as default values.
1221
1322
Examples
1423
--------
@@ -32,6 +41,10 @@ def __new__(cls, *args, **kwargs):
3241
# overwriting __new__ to revert to the default behavior of normal object, So an instance can be created directly without providing a registered item.
3342
return object.__new__(cls)
3443

44+
def __init__(self, name, **kwargs):
45+
super(Group, self).__init__(name=name, **kwargs)
46+
self.kwargs = kwargs
47+
3548
@property
3649
def __data__(self):
3750
# type: () -> dict
@@ -40,3 +53,52 @@ def __data__(self):
4053
"children": [child.__data__ for child in self.children],
4154
}
4255
return data
56+
57+
def add(self, item, **kwargs):
58+
# type: (compas.data.Data, dict) -> SceneObject
59+
"""Add a child item to the group, using the group's kwargs as default values for the child sceneobject.
60+
61+
Parameters
62+
----------
63+
item : :class:`compas.data.Data`
64+
The item to add.
65+
**kwargs : dict
66+
Additional keyword arguments to create the scene object for the item.
67+
68+
Returns
69+
-------
70+
:class:`compas.scene.SceneObject`
71+
The scene object associated with the added item.
72+
73+
Raises
74+
------
75+
ValueError
76+
If the scene object does not have an associated scene node.
77+
"""
78+
group_kwargs = self.kwargs.copy()
79+
group_kwargs.update(kwargs)
80+
kwargs = group_kwargs
81+
return super(Group, self).add(item, **kwargs)
82+
83+
def add_from_list(self, items, **kwargs):
84+
# type: (list[compas.data.Data], dict) -> list[SceneObject]
85+
"""Add a list of items to the group, using the group's kwargs as default values for the child sceneobject.
86+
87+
Parameters
88+
----------
89+
items : list[:class:`compas.data.Data`]
90+
The items to add.
91+
**kwargs : dict
92+
Additional keyword arguments to create the scene object for the items.
93+
94+
Returns
95+
-------
96+
list[SceneObject]
97+
The scene objects associated with the added items.
98+
99+
"""
100+
sceneobjects = []
101+
for item in items:
102+
sceneobject = self.add(item, **kwargs)
103+
sceneobjects.append(sceneobject)
104+
return sceneobjects

src/compas/scene/scene.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ def add(node, parent, items):
6161
settings = child_node["settings"]
6262
if "item" in child_node:
6363
guid = child_node["item"]
64-
sceneobject = parent.add(items[guid], **settings)
64+
sceneobject = scene.add(items[guid], parent=parent, **settings)
6565
else:
66-
sceneobject = parent.add(Group(**settings))
66+
sceneobject = scene.add_group(parent=parent, **settings)
6767
add(child_node, sceneobject, items)
6868

69-
add(data["root"], scene, items)
69+
add(data["root"], scene.root, items)
7070

7171
return scene
7272

@@ -117,10 +117,21 @@ def add(self, item, parent=None, **kwargs):
117117
if kwargs["context"] != self.context:
118118
raise Exception("Object context should be the same as scene context: {} != {}".format(kwargs["context"], self.context))
119119
del kwargs["context"] # otherwist the SceneObject receives "context" twice, which results in an error
120+
if isinstance(parent, Group):
121+
# Use the kwargs of the parent group as default values for the child sceneobject
122+
group_kwargs = parent.kwargs.copy()
123+
group_kwargs.update(kwargs)
124+
kwargs = group_kwargs
120125
sceneobject = SceneObject(item=item, context=self.context, **kwargs) # type: ignore
121126
super(Scene, self).add(sceneobject, parent=parent)
122127
return sceneobject
123128

129+
def add_group(self, name, parent=None, **kwargs):
130+
# type: (str, SceneObject | TreeNode | None, dict) -> SceneObject
131+
group = Group(name=name, **kwargs)
132+
self.add(group, parent=parent)
133+
return group
134+
124135
def clear_context(self, guids=None):
125136
# type: (list | None) -> None
126137
"""Clear the visualisation context.

tests/compas/scene/test_scene.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from compas.geometry import Box
1212
from compas.geometry import Frame
1313
from compas.geometry import Translation
14+
from compas.scene import Group
1415

1516
@pytest.fixture(autouse=True)
1617
def reset_sceneobjects_registration():
@@ -114,3 +115,47 @@ def test_scene_clear():
114115
scene.clear(clear_context=False, clear_scene=True)
115116

116117
assert len(scene.objects) == 0
118+
119+
def test_group_creation():
120+
scene = Scene()
121+
group = scene.add_group("TestGroup")
122+
assert isinstance(group, Group)
123+
assert group.name == "TestGroup"
124+
assert group.parent == scene.root
125+
126+
def test_group_add_item():
127+
scene = Scene()
128+
group = scene.add_group("TestGroup")
129+
box = Box()
130+
box_obj = group.add(box)
131+
assert isinstance(box_obj, SceneObject)
132+
assert box_obj.parent is group
133+
assert box_obj.item is box
134+
135+
def test_group_add_from_list():
136+
scene = Scene()
137+
group = scene.add_group("TestGroup")
138+
boxes = [Box() for _ in range(3)]
139+
box_objs = group.add_from_list(boxes)
140+
assert len(box_objs) == 3
141+
for box_obj in box_objs:
142+
assert isinstance(box_obj, SceneObject)
143+
assert box_obj.parent is group
144+
145+
def test_group_kwargs_inheritance():
146+
scene = Scene()
147+
group = scene.add_group("TestGroup", show=False)
148+
box = Box()
149+
box_obj = group.add(box)
150+
assert not box_obj.show # Should inherit show=False from group
151+
152+
def test_group_hierarchy():
153+
scene = Scene()
154+
parent_group = scene.add_group("ParentGroup")
155+
child_group = parent_group.add(Group("ChildGroup"))
156+
box = Box()
157+
box_obj = child_group.add(box)
158+
159+
assert box_obj.parent is child_group
160+
assert child_group.parent is parent_group
161+
assert parent_group.parent is scene.root

tests/compas/scene/test_scene_serialisation.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,66 @@ def test_scene_serialisation_empty():
124124
scene = compas.json_loads(compas.json_dumps(scene))
125125

126126
assert isinstance(scene, Scene)
127+
128+
def test_group_serialisation():
129+
scene1 = Scene()
130+
group = scene1.add_group("TestGroup", show=False)
131+
box = Box.from_width_height_depth(1, 1, 1)
132+
group.add(box)
133+
134+
scene2 = Scene.from_jsonstring(scene1.to_jsonstring())
135+
136+
# Check if group was serialized correctly
137+
group2 = scene2.find_by_name("TestGroup")
138+
assert isinstance(group2, Group)
139+
assert group2.name == "TestGroup"
140+
assert not group2.show
141+
142+
# Check if box was serialized correctly
143+
box2 = group2.children[0]
144+
assert isinstance(box2.item, Box)
145+
assert box2.parent is group2
146+
147+
def test_nested_group_serialisation():
148+
scene1 = Scene()
149+
parent_group = scene1.add_group("ParentGroup", show=False)
150+
child_group = parent_group.add(Group("ChildGroup", show=True))
151+
box = Box.from_width_height_depth(1, 1, 1)
152+
child_group.add(box)
153+
154+
scene2 = Scene.from_jsonstring(scene1.to_jsonstring())
155+
156+
# Check parent group
157+
parent2 = scene2.find_by_name("ParentGroup")
158+
assert isinstance(parent2, Group)
159+
assert not parent2.show
160+
161+
# Check child group
162+
child2 = parent2.children[0]
163+
assert isinstance(child2, Group)
164+
assert child2.name == "ChildGroup"
165+
assert child2.show
166+
assert child2.parent is parent2
167+
168+
# Check box
169+
box2 = child2.children[0]
170+
assert isinstance(box2.item, Box)
171+
assert box2.parent is child2
172+
173+
def test_group_kwargs_serialisation():
174+
scene1 = Scene()
175+
group = scene1.add_group("TestGroup", show=False, opacity=0.5)
176+
box = Box.from_width_height_depth(1, 1, 1)
177+
group.add(box)
178+
179+
scene2 = Scene.from_jsonstring(scene1.to_jsonstring())
180+
181+
# Check if group kwargs were serialized correctly
182+
group2 = scene2.find_by_name("TestGroup")
183+
assert not group2.show
184+
assert group2.opacity == 0.5
185+
186+
# Check if box inherited kwargs correctly
187+
box2 = group2.children[0]
188+
assert not box2.show
189+
assert box2.opacity == 0.5

0 commit comments

Comments
 (0)