Skip to content

Commit

Permalink
Merge pull request #1394 from compas-dev/docs-devguids-data
Browse files Browse the repository at this point in the history
Guide for creating new data types
  • Loading branch information
tomvanmele authored Oct 20, 2024
2 parents 040a902 + 7d07e1a commit 3d91705
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Added instructions for creating new data types to the dev guide.

### Changed

* Fixed `RuntimeError` when using `compas_rhino.unload_modules` in CPython`.
Expand All @@ -26,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

* Fixed support for `compas_gpython` in Rhino 8 Grasshopper CPython components.
* Changed installation instructions for Rhino 8 in the user guide.
* Fixed `Graph.from_edges` always returning `None`.

### Removed
Expand Down
182 changes: 182 additions & 0 deletions docs/devguide/dtypes.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,184 @@
Implementing a New Data Type
============================

COMPAS data types are classes that are based on :class:`compas.data.Data`.

Data types can be serialized to JSON with

* :func:`compas.json_dump`
* :func:`compas.json_dumps`
* :func:`compas.json_dumpz`

and deserialized with the corresponding "load" functions

* :func:`compas.json_load`
* :func:`compas.json_loads`
* :func:`compas.json_loadz`

All geometry objects and data structures,
and also, for example, the visualization scene,
are serializable data types.


Creating a new data type
========================

In most cases, it is sufficient to implement the ``__data__`` property when creating your custom `Data` class.

.. code-block:: python
class SomeThing(Data):
def __init__(self, a, b)
super().__init__()
# note that if the code needs to be compatible with IronPython
# you should write the following:
# super(SomeThing, self).__init__()
self.a = a
self.b = b
@property
def __data__(self):
return {
"a": self.a,
"b": self.b,
}
>>> custom = SomeThing(a=1, b=2)
>>> compas.json_dump(custom, "custom.json")
>>> result = compas.json_load("custom.json")
>>> isinstance(result, SomeThing)
True
>>> result.a
1
>>> result.b
2

If the attributes stored in the data dictionary defined by the ``__data__`` property
are different from the initialization parameters of the class,
you must also customize the ``__from_data__`` class method to compensate for the difference.

.. code-block:: python
class SomeThing(Data):
def __init__(self)
super().__init__()
# note that if the code needs to be compatible with IronPython
# you should write the following:
# super(SomeThing, self).__init__()
self.items = []
@property
def __data__(self):
return {
"items": self.items,
}
@classmethod
def __from_data__(cls, data):
custom = cls()
for item in data['items']:
custom.add(item)
return custom
def add(self, item):
self.items.append(item)
>>> custom = SomeThing()
>>> custom.add(1)
>>> custom.add(2)
>>> compas.json_dump(custom, "custom.json")
>>> result = compas.json_load("custom.json")
>>> isinstance(result, SomeThing)
True
>>> result.items
[1, 2]


Attribute types
===============

Any attribute that is an instance of a Python base type or a serializable COMPAS data object
can be included in the data dict created by the ``__data__`` property without further processing.
The serialization process will recursively serialize all these attributes.

.. code-block:: python
class SomeThing(Data):
def __init__(self, point, frame, mesh):
super().__init__()
# note that if the code needs to be compatible with IronPython
# you should write the following:
# super(SomeThing, self).__init__()
self.point = point
self.frame = frame
self.mesh = mesh
@property
def __data__(self):
return {
"point": self.point,
"frame": self.frame,
"mesh": self.mesh,
}
>>> import compas
>>> from compas.geometry import Point, Frame
>>> from compas.datastructures import Mesh
>>> point = Point(1, 2, 3)
>>> frame = Frame()
>>> mesh = Mesh.from_meshgrid(10, 10)
>>> custom = SomeThing(point, frame, mesh)
>>> compas.json_dump(custom, "custom.json")
>>> result = compas.json_load("custom.json")
>>> isinstance(result.point, Point)
True
>>> isinstance(result.frame, Frame)
True
>>> isinstance(result.mesh, Mesh)
True
>>> result.point == point
True
>>> result.point is point
False


Note that the the automatic serialization process will incur overhead information
that increases the size of the resulting JSON file.
The performance impact may be significant when many of these instances are serialized.

To avoid this, anticipated conversions can be included explicitly in `__data__` and `__from_data__`.

.. code-block:: python
class SomeThing(Data):
def __init__(self, point, frame, mesh):
super().__init__()
# note that if the code needs to be compatible with IronPython
# you should write the following:
# super(SomeThing, self).__init__()
self.point = point
self.frame = frame
self.mesh = mesh
@property
def __data__(self):
return {
"point": self.point.__data__,
"frame": self.frame.__data__,
"mesh": self.mesh.__data__,
}
@classmethod
def __from_data__(cls, data):
return cls(
Point.__from_data__(data['point']),
Frame.__from_data__(data['frame']),
Mesh.__from_data__(data['mesh']),
)

0 comments on commit 3d91705

Please sign in to comment.