Skip to content

Scene Data Store #1461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

* Added `inheritance` field to `__jsondump__` of `compas.datastructures.Datastructure` to allow for deserialization to closest available superclass of custom datastructures.
* Added `compas.scene.Scene.get_sceneobject_node` to get the TreeNode that corresponds to a scene object.
* Added `compas.scene.sceneobject_factory` method to create appropriate scene objects from data.

### Changed

* Changed `compas.scene.Scene` to use underlying `datastore`, `objectstore` and `tree` attributes for more transparent serialization and deserialization processes.
* Changed `compas.scene.SceneObject` to use object `guid` to retrieve the corresponding TreeNode from the scene tree, and use item `guid` to retrieve the corresponding data item from the scene datastore.

### Removed

* Removed `compas.scene.SceneObject.__new__` method, explicitly use `compas.scene.sceneobject_factory` instead.
* Removed `frame` kwarg from `compas.scene.SceneObject` constructor, since it is now computed from the `worldtransformation` attribute.


## [2.11.0] 2025-04-22

Expand Down
21 changes: 15 additions & 6 deletions src/compas/datastructures/tree/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@

"""
for node in self.nodes:
if node.name == name:
if str(node.name) == str(name):
return node

def get_nodes_by_name(self, name):
Expand All @@ -436,7 +436,7 @@
nodes.append(node)
return nodes

def get_hierarchy_string(self, max_depth=None):
def get_hierarchy_string(self, max_depth=None, node_repr=None):
"""
Return string representation for the spatial hierarchy of the tree.

Expand All @@ -445,6 +445,10 @@
max_depth : int, optional
The maximum depth of the hierarchy to print.
Default is ``None``, in which case the entire hierarchy is printed.
node_repr : callable, optional
A callable to represent the node string.
Default is ``None``, in which case the node.__repr__() is used.


Returns
-------
Expand All @@ -455,18 +459,23 @@

hierarchy = []

def traverse(node, hierarchy, prefix="", last=True, depth=0):
def traverse(node, hierarchy, prefix="", last=True, depth=0, node_repr=None):
if max_depth is not None and depth > max_depth:
return

if node_repr is None:
node_string = node.__repr__()

Check warning on line 467 in src/compas/datastructures/tree/tree.py

View check run for this annotation

Codecov / codecov/patch

src/compas/datastructures/tree/tree.py#L467

Added line #L467 was not covered by tests
else:
node_string = node_repr(node)

connector = "└── " if last else "├── "
hierarchy.append("{}{}{}".format(prefix, connector, node))
hierarchy.append("{}{}{}".format(prefix, connector, node_string))
prefix += " " if last else "│ "
for i, child in enumerate(node.children):
traverse(child, hierarchy, prefix, i == len(node.children) - 1, depth + 1)
traverse(child, hierarchy, prefix, i == len(node.children) - 1, depth + 1, node_repr)

if self.root:
traverse(self.root, hierarchy)
traverse(self.root, hierarchy, node_repr=node_repr)

return "\n".join(hierarchy)

Expand Down
2 changes: 2 additions & 0 deletions src/compas/scene/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from .exceptions import SceneObjectNotRegisteredError
from .sceneobject import SceneObject
from .sceneobject import sceneobject_factory
from .meshobject import MeshObject
from .graphobject import GraphObject
from .geometryobject import GeometryObject
Expand Down Expand Up @@ -43,6 +44,7 @@ def register_scene_objects_base():
__all__ = [
"SceneObjectNotRegisteredError",
"SceneObject",
"sceneobject_factory",
"MeshObject",
"GraphObject",
"GeometryObject",
Expand Down
59 changes: 38 additions & 21 deletions src/compas/scene/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,36 +119,53 @@ def detect_current_context():
return None


def _get_sceneobject_cls(data, **kwargs):
# in any case user gets to override the choice
context_name = kwargs.get("context") or detect_current_context()
def get_sceneobject_cls(item, context=None):
"""Get the scene object class for a given item in the current context. If no context is provided, the current context is detected.
If the exact item type is not registered, a closest match in its inheritance hierarchy is used.

dtype = type(data)
cls = None

if "sceneobject_type" in kwargs:
cls = kwargs["sceneobject_type"]
else:
context = ITEM_SCENEOBJECT[context_name]
Parameters
----------
item : :class:`~compas.data.Data`
The item to get the scene object class for.
context : Literal['Viewer', 'Rhino', 'Grasshopper', 'Blender'], optional
The visualization context in which the pair should be registered.

for type_ in inspect.getmro(dtype):
cls = context.get(type_, None)
if cls is not None:
break
Raises
------
ValueError
If the item is None.
SceneObjectNotRegisteredError
If no scene object is registered for the item type in the current context.

if cls is None:
raise SceneObjectNotRegisteredError("No scene object is registered for this data type: {} in this context: {}".format(dtype, context_name))
Returns
-------
:class:`~compas.scene.SceneObject`
The scene object class for the given item.

return cls
"""

if item is None:
raise ValueError("Cannot create a scene object for None. Please ensure you pass a instance of a supported class.")

def get_sceneobject_cls(item, **kwargs):
if not ITEM_SCENEOBJECT:
register_scene_objects()

if item is None:
raise ValueError("Cannot create a scene object for None. Please ensure you pass a instance of a supported class.")
if context is None:
context = detect_current_context()

itemtype = type(item)

context = ITEM_SCENEOBJECT[context]

cls = None

for inheritancetype in inspect.getmro(itemtype):
cls = context.get(inheritancetype, None)
if cls is not None:
break

if cls is None:
raise SceneObjectNotRegisteredError("No scene object is registered for this data type: {} in this context: {}".format(itemtype, context))

cls = _get_sceneobject_cls(item, **kwargs)
PluginValidator.ensure_implementations(cls)
return cls
13 changes: 0 additions & 13 deletions src/compas/scene/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,3 @@ class Group(SceneObject):
└── <GeometryObject: Point>

"""

def __new__(cls, *args, **kwargs):
# overwriting __new__ to revert to the default behavior of normal object, So an instance can be created directly without providing a registered item.
return object.__new__(cls)

@property
def __data__(self):
# type: () -> dict
data = {
"settings": self.settings,
"children": [child.__data__ for child in self.children],
}
return data
Comment on lines -30 to -42
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These customizations are no longer needed anymore, thanks to the more straight forward serialization/deserialization

Loading
Loading