Data
The data package provides a base class (compas.data.Data
) for all data objects in the COMPAS framework (see Inheritance Diagrams),
the mechanism for serialization of data to JSON format,
and the base infrastructure for validation of the data of COMPAS objects in both the original Python and serialized JSON formats.
>>> from compas.data import Data
>>> from compas.geometry import Point, Box, Rotation
>>> from compas.datastructures import Mesh
>>> from compas.robots import RobotModel
>>> issubclass(Point, Data)
True
>>> issubclass(Box, Data)
True
>>> issubclass(Rotation, Data)
True
>>> issubclass(Mesh, Data)
True
>>> issubclass(RobotModel, Data)
True
Note
This tutorial is loosely based on the COMPAS exchange meeting about compas.data
that is available here
COMPAS exchange: data
Interface
The base data class defines a common data interface for all objects.
Among other things, this interface provides a read-only GUID (compas.data.Data.guid
),
a modifiable object name (compas.data.Data.name
) that defaults to the class name,
a read-only data type (compas.data.Data.dtype
),
and, most importantly, an attribute containing the underlying data of the object (compas.data.Data.data
).
>>> from compas.geometry import Point
>>> point = Point(0, 0, 0)
>>> point.guid
UUID('48613a5b-4c9b-4d7c-8c88-59c28297fd75')
>>> point.name
'Point'
>>> point.dtype
'compas.geometry/Point'
>>> point.data
[0.0, 0.0, 0.0]
JSON Serialization
All objects inheriting the data interface, can be serialized to a JSON string or file.
>>> from compas.geometry import Point
>>> point = Point(0, 0, 0)
>>> point.to_jsonstring()
'[0.0, 0.0, 0.0]'
>>> point.to_json('point.json')
>>> from compas.geometry import Frame
>>> frame = Frame.worldXY()
>>> frame.to_jsonstring()
'{"point": [0.0, 0.0, 0.0], "xaxis": [1.0, 0.0, 0.0], "yaxis": [0.0, 1.0, 0.0]}'
>>> frame.to_json('frame.json')
Conversely, COMPAS data objects can be reconstructed from a compatible JSON string or file.
>>> from compas.geometry import Frame, Box
>>> box = Box(Frame.worldXY(), 1, 1, 1)
>>> jsonstring = box.to_jsonstring()
>>> other = Box.from_jsonstring(jsonstring)
>>> box == other
True
>>> from compas.datastructures import Mesh
>>> mesh = Mesh.from_obj('faces.obj')
>>> mesh.to_json('mesh.json')
>>> other = Mesh.from_json('mesh.json')
The serialization mechanism applies recursively to nested structures of objects as well.
>>> from compas.datastructures import Network, Mesh
>>> from compas.geometry import Point, Transformation, Box, Frame
>>> point = Point(0, 0, 0)
>>> xform = Transformation()
>>> mesh = Mesh.from_shape(Box(Frame.worldXY(), 1, 1, 1))
>>> network = Network()
>>> a = network.add_node(point=point)
>>> b = network.add_node(transformation=xform)
>>> c = network.add_node(box=mesh)
>>> network.to_json('network.json')
>>> other = Network.from_json('network.json')
>>> other.node_attribute(a, 'point') == network.node_attribute(a, 'point')
True
>>> other.node_attribute(b, 'transformation') == network.node_attribute(b, 'transformation')
True
Working Sessions
One of the most useful features of the serialization meshanisms provided by the data package is the ability to store and load entire COMPAS working sessions.
# script A
import compas
from compas.datastructures import Mesh
from compas.geometry import Pointcloud, Box
box = Box.from_width_height_depth(1, 1, 1)
mesh = Mesh.from_poyhedron(12)
boxes = []
for point in Pointcloud.from_bounds(10, 10, 10, 100):
boxcopy = box.copy()
boxcopy.frame.point = point
session = {'mesh': mesh, 'boxes': boxes}
compas.json_dump(session, 'session.json')
# script B
import compas
session = compas.json_load('session.json')
mesh = session['mesh']
boxes = session['boxes']
Note that if you are working in Python 3.6 or higher, you could add some type information to script B such that your editor knows what kind of objects have been loaded, which will help with IntelliSense and code completion.
# script B
from typing import List
import compas
from compas.datastructures import Mesh
from compas.geometry import Box
session = compas.json_load('session.json')
mesh: Mesh = session['mesh']
boxes: List[Box] = session['boxes']
Validation
A somewhat experimental feature of the data package is data validation.
The base data class defines two unimplemented attributes compas.data.Data.JSONSCHEMA
and compas.data.Data.DATASCHEMA
.
The former is meant to define the name of the json schema in the schema
folder of compas.data
,
and the latter a Python schema using schema.Schema
.
If a deriving class implements those attributes, data sources can be validated against the two schemas to verify compatibility of the available data with the object type.
>>> from compas.data import validate_data
>>> from compas.geometry import Frame
>>> data = {'point': [0.0, 0.0, 0.0], 'xaxis': [1.0, 0.0, 0.0], 'zaxis': [0.0, 0.0, 1.0]}
>>> validate_data(data, Frame)
Validation against the JSON schema of this object failed.
Traceback (most recent call last):
...
jsonschema.exceptions.ValidationError: 'yaxis' is a required property
Failed validating 'required' in schema:
{'$compas': '1.7.1',
'$id': 'frame.json',
'$schema': 'http://json-schema.org/draft-07/schema#',
'properties': {'point': {'$ref': 'compas.json#/definitions/point'},
'xaxis': {'$ref': 'compas.json#/definitions/vector'},
'yaxis': {'$ref': 'compas.json#/definitions/vector'}},
'required': ['point', 'xaxis', 'yaxis'],
'type': 'object'}
On instance:
{'point': [0.0, 0.0, 0.0],
'xaxis': [1.0, 0.0, 0.0],
'zaxis': [0.0, 0.0, 1.0]}
Custom Objects
To add a new object class that implements the data interface, only a few attributes have to be implemented.
class MyObject(Data):
def __init__(self, a, b, **kwargs):
super(MyObject, self).__init__(**kwargs)
self.a = a
self.b = b
@property
def data(self):
"""dict : The data dictionary that represents the data of the object."""
return {'a': self.a, 'b': self.b}
@data.setter
def data(self, data):
self.a = data['a']
self.b = data['b']
@classmethod
def from_data(cls, data):
return cls(data['a'], data['b'])
GH Components
Coming soon…