Skip to content

Commit e3ddc21

Browse files
authored
Merge pull request #876 from compas-dev/data-tutorial
Data tutorial based on exchange meeting
2 parents 9b07e30 + b6ea637 commit e3ddc21

File tree

4 files changed

+269
-103
lines changed

4 files changed

+269
-103
lines changed

docs/tutorial.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Tutorial
55
:maxdepth: 1
66
:titlesonly:
77

8+
tutorial/data
89
tutorial/geometry
910
tutorial/networks
1011
tutorial/meshes
@@ -13,5 +14,4 @@ Tutorial
1314
tutorial/numericaldata
1415
tutorial/rpc
1516
tutorial/geomaps
16-
tutorial/serialization
1717
tutorial/plotters

docs/tutorial/data.rst

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
**************
2+
Data
3+
**************
4+
5+
.. rst-class:: lead
6+
7+
The data package provides a base class (:class:`compas.data.Data`) for all data objects in the COMPAS framework (see :ref:`Inheritance Diagrams`),
8+
the mechanism for serialization of data to JSON format,
9+
and the base infrastructure for validation of the data of COMPAS objects in both the original Python and serialized JSON formats.
10+
11+
::
12+
13+
>>> from compas.data import Data
14+
>>> from compas.geometry import Point, Box, Rotation
15+
>>> from compas.datastructures import Mesh
16+
>>> from compas.robots import RobotModel
17+
18+
::
19+
20+
>>> issubclass(Point, Data)
21+
True
22+
>>> issubclass(Box, Data)
23+
True
24+
>>> issubclass(Rotation, Data)
25+
True
26+
>>> issubclass(Mesh, Data)
27+
True
28+
>>> issubclass(RobotModel, Data)
29+
True
30+
31+
32+
.. note::
33+
34+
This tutorial is loosely based on the COMPAS exchange meeting about :mod:`compas.data` that is available here
35+
`COMPAS exchange: data <https://github.com/compas-dev/compas-exchange>`_
36+
37+
Interface
38+
=========
39+
40+
The base data class defines a common data interface for all objects.
41+
Among other things, this interface provides a read-only GUID (:attr:`compas.data.Data.guid`),
42+
a modifiable object name (:attr:`compas.data.Data.name`) that defaults to the class name,
43+
a read-only data type (:attr:`compas.data.Data.dtype`),
44+
and, most importantly, an attribute containing the underlying data of the object (:attr:`compas.data.Data.data`).
45+
46+
::
47+
48+
>>> from compas.geometry import Point
49+
>>> point = Point(0, 0, 0)
50+
>>> point.guid
51+
UUID('48613a5b-4c9b-4d7c-8c88-59c28297fd75')
52+
>>> point.name
53+
'Point'
54+
>>> point.dtype
55+
'compas.geometry/Point'
56+
>>> point.data
57+
[0.0, 0.0, 0.0]
58+
59+
60+
JSON Serialization
61+
==================
62+
63+
All objects inheriting the data interface, can be serialized to a JSON string or file.
64+
65+
::
66+
67+
>>> from compas.geometry import Point
68+
>>> point = Point(0, 0, 0)
69+
>>> point.to_jsonstring()
70+
'[0.0, 0.0, 0.0]'
71+
>>> point.to_json('point.json')
72+
73+
::
74+
75+
>>> from compas.geometry import Frame
76+
>>> frame = Frame.worldXY()
77+
>>> frame.to_jsonstring()
78+
'{"point": [0.0, 0.0, 0.0], "xaxis": [1.0, 0.0, 0.0], "yaxis": [0.0, 1.0, 0.0]}'
79+
>>> frame.to_json('frame.json')
80+
81+
Conversely, COMPAS data objects can be reconstructed from a compatible JSON string or file.
82+
83+
::
84+
85+
>>> from compas.geometry import Frame, Box
86+
>>> box = Box(Frame.worldXY(), 1, 1, 1)
87+
>>> jsonstring = box.to_jsonstring()
88+
>>> other = Box.from_jsonstring(jsonstring)
89+
>>> box == other
90+
True
91+
92+
::
93+
94+
>>> from compas.datastructures import Mesh
95+
>>> mesh = Mesh.from_obj('faces.obj')
96+
>>> mesh.to_json('mesh.json')
97+
>>> other = Mesh.from_json('mesh.json')
98+
99+
The serialization mechanism applies recursively to nested structures of objects as well.
100+
101+
::
102+
103+
>>> from compas.datastructures import Network, Mesh
104+
>>> from compas.geometry import Point, Transformation, Box, Frame
105+
>>> point = Point(0, 0, 0)
106+
>>> xform = Transformation()
107+
>>> mesh = Mesh.from_shape(Box(Frame.worldXY(), 1, 1, 1))
108+
>>> network = Network()
109+
>>> a = network.add_node(point=point)
110+
>>> b = network.add_node(transformation=xform)
111+
>>> c = network.add_node(box=mesh)
112+
>>> network.to_json('network.json')
113+
114+
::
115+
116+
>>> other = Network.from_json('network.json')
117+
>>> other.node_attribute(a, 'point') == network.node_attribute(a, 'point')
118+
True
119+
>>> other.node_attribute(b, 'transformation') == network.node_attribute(b, 'transformation')
120+
True
121+
122+
123+
Working Sessions
124+
================
125+
126+
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.
127+
128+
.. code-block:: python
129+
130+
# script A
131+
132+
import compas
133+
from compas.datastructures import Mesh
134+
from compas.geometry import Pointcloud, Box
135+
136+
box = Box.from_width_height_depth(1, 1, 1)
137+
mesh = Mesh.from_poyhedron(12)
138+
139+
boxes = []
140+
for point in Pointcloud.from_bounds(10, 10, 10, 100):
141+
boxcopy = box.copy()
142+
boxcopy.frame.point = point
143+
144+
session = {'mesh': mesh, 'boxes': boxes}
145+
compas.json_dump(session, 'session.json')
146+
147+
.. code-block:: python
148+
149+
# script B
150+
151+
import compas
152+
153+
session = compas.json_load('session.json')
154+
mesh = session['mesh']
155+
boxes = session['boxes']
156+
157+
Note that if you are working in Python 3.6 or higher, you could add some type information to script B
158+
such that your editor knows what kind of objects have been loaded,
159+
which will help with IntelliSense and code completion.
160+
161+
.. code-block:: python
162+
163+
# script B
164+
165+
from typing import List
166+
import compas
167+
from compas.datastructures import Mesh
168+
from compas.geometry import Box
169+
170+
session = compas.json_load('session.json')
171+
mesh: Mesh = session['mesh']
172+
boxes: List[Box] = session['boxes']
173+
174+
175+
Validation
176+
==========
177+
178+
A somewhat experimental feature of the data package is data validation.
179+
The base data class defines two unimplemented attributes :attr:`compas.data.Data.JSONSCHEMA` and :attr:`compas.data.Data.DATASCHEMA`.
180+
The former is meant to define the name of the json schema in the ``schema`` folder of :mod:`compas.data`,
181+
and the latter a Python schema using :mod:`schema.Schema`.
182+
183+
If a deriving class implements those attributes, data sources can be validated against the two schemas to verify compatibility
184+
of the available data with the object type.
185+
186+
::
187+
188+
>>> from compas.data import validate_data
189+
>>> from compas.geometry import Frame
190+
>>> data = {'point': [0.0, 0.0, 0.0], 'xaxis': [1.0, 0.0, 0.0], 'zaxis': [0.0, 0.0, 1.0]}
191+
>>> validate_data(data, Frame)
192+
Validation against the JSON schema of this object failed.
193+
Traceback (most recent call last):
194+
...
195+
196+
jsonschema.exceptions.ValidationError: 'yaxis' is a required property
197+
198+
Failed validating 'required' in schema:
199+
{'$compas': '1.7.1',
200+
'$id': 'frame.json',
201+
'$schema': 'http://json-schema.org/draft-07/schema#',
202+
'properties': {'point': {'$ref': 'compas.json#/definitions/point'},
203+
'xaxis': {'$ref': 'compas.json#/definitions/vector'},
204+
'yaxis': {'$ref': 'compas.json#/definitions/vector'}},
205+
'required': ['point', 'xaxis', 'yaxis'],
206+
'type': 'object'}
207+
208+
On instance:
209+
{'point': [0.0, 0.0, 0.0],
210+
'xaxis': [1.0, 0.0, 0.0],
211+
'zaxis': [0.0, 0.0, 1.0]}
212+
213+
214+
Custom Objects
215+
==============
216+
217+
To add a new object class that implements the data interface, only a few attributes have to be implemented.
218+
219+
.. code-block:: python
220+
221+
class MyObject(Data):
222+
223+
def __init__(self, a, b, **kwargs):
224+
super(MyObject, self).__init__(**kwargs)
225+
self.a = a
226+
self.b = b
227+
228+
@property
229+
def data(self):
230+
"""dict : The data dictionary that represents the data of the object."""
231+
return {'a': self.a, 'b': self.b}
232+
233+
@data.setter
234+
def data(self, data):
235+
self.a = data['a']
236+
self.b = data['b']
237+
238+
@classmethod
239+
def from_data(cls, data):
240+
return cls(data['a'], data['b'])
241+
242+
243+
GH Components
244+
=============
245+
246+
*Coming soon...*
247+
248+
Inheritance Diagrams
249+
====================
250+
251+
.. currentmodule:: compas.geometry
252+
253+
.. inheritance-diagram:: Bezier Circle Ellipse Frame Line Plane Point Polygon Polyline Quaternion Vector Box Capsule Cone Cylinder Polyhedron Sphere Torus Projection Reflection Rotation Shear Transformation Translation
254+
:parts: 1
255+
256+
.. currentmodule:: compas.datastructures
257+
258+
.. inheritance-diagram:: Mesh Network VolMesh
259+
:parts: 1
260+
261+
.. currentmodule:: compas.robots
262+
263+
.. inheritance-diagram:: RobotModel Joint Link ToolModel Configuration
264+
:parts: 1

docs/tutorial/serialization.rst

Lines changed: 0 additions & 101 deletions
This file was deleted.

0 commit comments

Comments
 (0)