-
Notifications
You must be signed in to change notification settings - Fork 112
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
base: main
Are you sure you want to change the base?
Scene Data Store #1461
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1461 +/- ##
==========================================
- Coverage 61.94% 61.93% -0.01%
==========================================
Files 208 208
Lines 22365 22401 +36
==========================================
+ Hits 13853 13875 +22
- Misses 8512 8526 +14 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
src/compas/scene/context.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consolidating _get_sceneobject_cls
and get_sceneobject_cls
into one function. Also cleaned up the kwargs. I removed sceneobject_type
parameter because now you can call constructor directly with __new__
removed.
|
||
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 |
There was a problem hiding this comment.
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
@property | ||
def __data__(self): | ||
# type: () -> dict | ||
items = {str(object.item.guid): object.item for object in self.objects if object.item is not None} | ||
return { | ||
"name": self.name, | ||
"root": self.root.__data__, # type: ignore | ||
"items": list(items.values()), | ||
"attributes": self.attributes, | ||
"datastore": self.datastore, | ||
"objectstore": self.objectstore, | ||
"tree": self.tree, | ||
} | ||
|
||
@classmethod | ||
def __from_data__(cls, data): | ||
# type: (dict) -> Scene | ||
scene = cls(data["name"]) | ||
items = {str(item.guid): item for item in data["items"]} | ||
|
||
def add(node, parent, items): | ||
for child_node in node.get("children", []): | ||
settings = child_node["settings"] | ||
if "item" in child_node: | ||
guid = child_node["item"] | ||
sceneobject = parent.add(items[guid], **settings) | ||
else: | ||
sceneobject = parent.add(Group(**settings)) | ||
add(child_node, sceneobject, items) | ||
|
||
add(data["root"], scene, items) | ||
|
||
return scene |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We directly serialize datastore
, objectstore
and tree
here. And during deserialization we can read them as they are without using a custom __from__data__
method.
# type: (str, str | None) -> None | ||
super(Scene, self).__init__(name=name) | ||
super(Scene, self).add(TreeNode(name="ROOT")) | ||
def __init__(self, context=None, datastore=None, objectstore=None, tree=None, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Users can passing their own customs stores and tree, if they know what they are doing.
def item(self): | ||
# type: () -> compas.data.Data | ||
return self._item | ||
return self.scene.datastore[self._item] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
item
now becomes a getter that retrieve the live object from datastore, what we actually store as _item
is the guid
@property | ||
def is_root(self): | ||
# type: () -> bool | ||
return self.node.is_root | ||
|
||
@property | ||
def is_leaf(self): | ||
# type: () -> bool | ||
return self.node.is_leaf | ||
|
||
@property | ||
def is_branch(self): | ||
# type: () -> bool | ||
return self.node.is_branch | ||
|
||
@property | ||
def parentnode(self): | ||
# type: () -> compas.datastructures.Node | None | ||
return self.node.parent | ||
|
||
@property | ||
def childnodes(self): | ||
# type: () -> list[compas.datastructures.Node] | ||
return self.node.children |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"shortcut" properties that allows us to still work with SceneObject
like a TreeNode
@Licini shall we try to finish this? |
|
||
from compas.scene import SceneObject | ||
from compas.scene import sceneobject_factory | ||
|
||
|
||
class CompasToRhinoGeometry(component): | ||
def RunScript(self, cg): | ||
if not cg: | ||
return None | ||
|
||
return SceneObject(item=cg).draw() | ||
return sceneobject_factory(item=cg).draw() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gonzalocasas @chenkasirer Please note here, with the factory pattern, you need call factory function in GH components instead of constructor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change that affects a lot of external code. It needs a major release (compas 3!). Also, I don't like the method name sceneobject_factory
, it feels old, could we add the factory method as a class/static method of SceneObject
, like SceneObject.create()
? the word factory on its own sounds too much java-like to my taste :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change that affects a lot of external code. It needs a major release (compas 3!). Also, I don't like the method name
sceneobject_factory
, it feels old, could we add the factory method as a class/static method ofSceneObject
, likeSceneObject.create()
? the word factory on its own sounds too much java-like to my taste :P
Ok 🤣 I had impression that these are the only places SceneOjbect constructor are called. But sure SceneObject.create()
is a good idea to me too. Regarding the full PR, I have been having more discussions @tomvanmele on potential issues and corresponding reworks for data store, so I will close this one for now and re-submit another one that only concerns the serialization part. We can deal with __new__
in the future in compas 3
Done with the requested changes |
Hey guys a rather large PR, there are several components to this, let me explain:
1. Scene data store
Previously we inherent
Scene
fromTree
andSceneObject
fromTreeNode
. It was nice but the serialization was not very straightforward, because we don't want to serializeData
item directly underSceneObject
since they might be reused. So we had to customize the serialization and deserialization to only storeguid
of item underSceneObject
, then group all the distinct items as a list under scene.This PR basically makes the idea more explicit by reflecting this mechanism directly as the attributes of
Scene
. UnderScene
, we have 3 main aspects:datastore
,objectstore
andtree
. Thedatastore
is a bucket of uniqueData
items, theobjectstore
is a bucket ofSceneObjects
, both keyed by theirguids
. Andtree
stores hierarchy of the scene objects, but nothing more.For
SceneObject
, instead maintaining a live link toData
item, we only actually storeguid
of that item and use the datastore of the scene to retrieve it (with minimal overhead).The benefit is a much more transparent serialization and deserialization process: now both
Scene
andSceneObject
can be json dumped/loaded without any customization using our default encoder/decoder. The structure of two classes directly reflect the serialized data with almost no change.This also makes it easier for further extension of
Scene
for example if you want to use a datastore that contains very large geometry but with lazy loading, you can pass in a customdatastore
yourself when creating the scene, using dict-like object which can be queried with aguid
.For example a minimal serialized scene can look like this now:
2. SceneObjectFactory
We have been using
__new__
for automatically choosing appropriateSceneObject
class. It has been working well most of time. But it becomes annoying if one wants to initialize a customSceneObject
class likeGroup
, where I just want a straightforward initiation without triggering auto class detection. @tomvanmele has been advocating for more explicit Factory method instead__new__
, to make the constructor behavior more predictable and more type-hint friendly. Which I quite agree.This does not change the external APIs, users still use
Scene.add
the same way, which internally calls the factory method now instead of the class constructor.3. Others
I added more tests on
Scene
to make sure it behaves correctly. In summary, although there are a big internal re-organization inScene
andSceneObject
, the user-facing APIs aren't changed, there also shouldn't be any changes needed for all the extendedSceneObjects
. I will drop some in-line comments below