diff --git a/README.md b/README.md index 849a0c7..7c5302e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Documentation Status](https://readthedocs.org/projects/chemview/badge/?version=latest)](https://readthedocs.org/projects/chemview/?badge=latest) [![Build Status](https://travis-ci.org/gabrielelanaro/chemview.svg?branch=master)](https://travis-ci.org/gabrielelanaro/chemview) -Version: 0.2 +Version: 0.6 The new generation molecular viewer for the IPython notebook. diff --git a/chemview/__init__.py b/chemview/__init__.py index 6a90d41..139cb39 100644 --- a/chemview/__init__.py +++ b/chemview/__init__.py @@ -1,4 +1,6 @@ from .install import enable_notebook from .widget import RepresentationViewer from .viewer import MolecularViewer, TrajectoryControls -from .trajectory import TrajectoryViewer \ No newline at end of file +from .trajectory import TrajectoryViewer + +__version__ = '0.6' diff --git a/chemview/scene.py b/chemview/scene.py index 339ac2f..42f0395 100644 --- a/chemview/scene.py +++ b/chemview/scene.py @@ -3,11 +3,12 @@ import numpy as np import six + class Validator(object): - + def validate(self, value): pass - + def cast(self, value): pass @@ -16,7 +17,7 @@ def default(self, inp=None): class BoundedScalar(Validator): - + def __init__(self, start, end, type, ginclusive=True, linclusive=True, default=None): self.start = start self.end = end @@ -24,97 +25,106 @@ def __init__(self, start, end, type, ginclusive=True, linclusive=True, default=N self.linclusive = linclusive self.type = type self._default = default - + def validate(self, value): lo = operator.le if self.linclusive else operator.lt go = operator.ge if self.ginclusive else operator.gt - + return go(value, self.start) and lo(value, self.end), 'should be between {} and {}'.format(self.start, self.end) - + def cast(self, value): return self.type(value) + class TypedList(Validator): - + def __init__(self, type, length=None, match_length=None, default_item=None, default=None): self.type = type self.length = length self.match_length = match_length self.default_item = default_item self._default = default - - + def default(self, inp=None): if self._default is not None: return self._default - + if self.match_length is not None and self.default_item is not None: return [self.default_item] * len(inp[self.match_length]) else: raise ValueError("This field is required") - + def validate(self, value): # We check the first element only - length_condition = True if self.length is None else len(value) == self.length - + length_condition = True if self.length is None else len( + value) == self.length + if len(value) == 0: type_condition = True else: type_condition = isinstance(value[0], self.type) - + return length_condition and type_condition, 'should be a list with elements of type {}'.format(self.type) - + def cast(self, value): return [self.type(v) for v in value] class UniqueID(Validator): + def default(self, inp=None): raise ValueError("No default for this field") - + def validate(self, value): return isinstance(value, int), 'UUid not valid' - + def cast(self, value): return value - + + class Array(Validator): + def __init__(self, shape, type): self.shape = shape self.type = type def default(self, inp=None): raise ValueError("No default for this field") - + def validate(self, value): return True, '' - + def cast(self, value): return np.array(value, dtype=self.type) + class Keyword(Validator): + def __init__(self, value): self.value = value - + def default(self, inp=None): raise ValueError("No default for this field") - + def validate(self, value): return value == self.value, 'should be {}'.format(self.value) - + def cast(self, value): return six.u(value) + class Boolean(Validator): + def __init__(self, default=None): self._default = default - + def validate(self, value): return isinstance(value, bool), 'should be a boolean, got {}'.format(value) - + def cast(self, value): return bool(value) + class MatchSchema(object): def __init__(self, matchers, field): @@ -123,26 +133,27 @@ def __init__(self, matchers, field): def match(self, value): return self.matchers[value[self.field]] - + + class ValidationError(Exception): pass from collections import OrderedDict -POINTS_SCHEMA = { +POINTS_SCHEMA = { "rep_id": UniqueID(), "rep_type": Keyword("points"), - "options": + "options": OrderedDict({'coordinates': Array(shape=(-1, 3), type=np.float32), 'colors': TypedList(int, match_length='coordinates', default_item=0xffffff), 'sizes': TypedList(float, match_length='coordinates', default_item=0.1), 'visible': TypedList(float, match_length='coordinates', default_item=1.0)}) } -SPHERES_SCHEMA = { +SPHERES_SCHEMA = { "rep_id": UniqueID(), "rep_type": Keyword("spheres"), - "options" : + "options": OrderedDict({'coordinates': Array(shape=(-1, 3), type=np.float32), 'colors': TypedList(int, match_length='coordinates', default_item=0xffffff), 'radii': TypedList(float, match_length='coordinates', default_item=0.1), @@ -150,10 +161,10 @@ class ValidationError(Exception): } -CYLINDERS_SCHEMA = { +CYLINDERS_SCHEMA = { "rep_id": UniqueID(), "rep_type": Keyword("cylinders"), - "options" : + "options": OrderedDict({'startCoords': Array(shape=(-1, 3), type=np.float32), 'endCoords': Array(shape=(-1, 3), type=np.float32), 'colors': TypedList(int, match_length='startCoords', default_item=0xffffff), @@ -161,20 +172,20 @@ class ValidationError(Exception): 'alpha': TypedList(float, match_length='startCoords', default_item=1.0)}) } -LINES_SCHEMA = { +LINES_SCHEMA = { "rep_id": UniqueID(), "rep_type": Keyword("lines"), - "options" : + "options": OrderedDict({'startCoords': Array(shape=(-1, 3), type=np.float32), 'endCoords': Array(shape=(-1, 3), type=np.float32), 'startColors': TypedList(int, match_length='startCoords', default_item=0xffffff), 'endColors': TypedList(float, match_length='endCoords', default_item=1.0)}) } -SURFACE_SCHEMA = { +SURFACE_SCHEMA = { "rep_id": UniqueID(), "rep_type": Keyword("surface"), - "options" : + "options": OrderedDict({'verts': Array(shape=(-1, 3), type=np.float32), 'faces': Array(shape=(-1, 3), type=np.uint32), 'colors': TypedList(int, match_length='verts', default_item=0xffffff), @@ -184,7 +195,7 @@ class ValidationError(Exception): SMOOTHTUBE_SCHEMA = { "rep_id": UniqueID(), "rep_type": Keyword("smoothtube"), - "options" : + "options": OrderedDict({'coordinates': Array(shape=(-1, 3), type=np.float32), 'radius': BoundedScalar(0.0, float('inf'), float, default=1.0), 'color': BoundedScalar(0, 0xffffff, int, default=0xffffff), @@ -194,7 +205,7 @@ class ValidationError(Exception): RIBBON_SCHEMA = { "rep_id": UniqueID(), "rep_type": Keyword("ribbon"), - "options" : + "options": OrderedDict({'coordinates': Array(shape=(-1, 3), type=np.float32), 'normals': Array(shape=(-1, 3), type=np.float32), 'color': BoundedScalar(0, 0xffffff, int, default=0xffffff), @@ -204,64 +215,67 @@ class ValidationError(Exception): SCHEMA = { - 'camera' : {'aspect': BoundedScalar(0.0, float('inf'), float, default=1.0, ginclusive=False), - 'vfov': BoundedScalar(0.0, 180.0, float, default=90.0), - 'location': TypedList(float, 3, default=[0.0, 0.0, -1.0]), - 'quaternion': TypedList(float, 4, default=[0.0, 0.0, 0.0, 1.0]), - 'target': TypedList(float, 3, default=[0.0, 0.0, 0.0])}, - - 'representations' : MatchSchema([POINTS_SCHEMA, - SPHERES_SCHEMA, - LINES_SCHEMA, - CYLINDERS_SCHEMA, - SURFACE_SCHEMA, - SMOOTHTUBE_SCHEMA], 'rep_type') + 'camera': {'aspect': BoundedScalar(0.0, float('inf'), float, default=1.0, ginclusive=False), + 'vfov': BoundedScalar(0.0, 180.0, float, default=90.0), + 'location': TypedList(float, 3, default=[0.0, 0.0, -1.0]), + 'quaternion': TypedList(float, 4, default=[0.0, 0.0, 0.0, 1.0]), + 'target': TypedList(float, 3, default=[0.0, 0.0, 0.0])}, + + 'representations': MatchSchema([POINTS_SCHEMA, + SPHERES_SCHEMA, + LINES_SCHEMA, + CYLINDERS_SCHEMA, + SURFACE_SCHEMA, + SMOOTHTUBE_SCHEMA], 'rep_type') } - + def validate_schema(value, schema): retval = {} for key, validator in schema.items(): - + if isinstance(validator, dict): inp = value.get(key, {}).copy() validated_inp = validate_schema(inp, validator) retval[key] = validated_inp - + elif isinstance(validator, MatchSchema): - inp = value.get(key, []).copy() - + inp = value.get(key, [])[:] # copy + validated_inp = [] for val in inp: sub_schema = validator.match(val) validated_inp.append(validate_schema(val, sub_schema)) - + retval[key] = validated_inp - - + elif isinstance(validator, Validator): if key not in value: try: retval[key] = validator.default(value) except ValueError: - raise ValidationError('Problem: "{}" is required.'.format(key)) + raise ValidationError( + 'Problem: "{}" is required.'.format(key)) else: try: cast = validator.cast(value[key]) except ValueError: - raise ValidationError('Problem: "{}" is {}, cannot cast'.format(key, value[key])) - + raise ValidationError( + 'Problem: "{}" is {}, cannot cast'.format(key, value[key])) + ok, msg = validator.validate(cast) if not ok: - raise ValidationError('Problem: "{}" is {} but {}'.format(key, value[key], msg)) + raise ValidationError( + 'Problem: "{}" is {} but {}'.format(key, value[key], msg)) retval[key] = cast else: raise ValueError("schema not valid") - + return retval - + + def normalize_scene(scene): """Normalize incomplete scene with sane defaults""" retval = scene.copy() - + return validate_schema(scene, SCHEMA) diff --git a/docs/source/conf.py b/docs/source/conf.py index 5ecd620..f432253 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -59,9 +59,9 @@ # built documents. # # The short X.Y version. -version = '0.1' +version = '0.6' # The full version, including alpha/beta/rc tags. -release = '0.1' +release = '0.6' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index c462a09..2f99183 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,13 @@ from setuptools import setup, find_packages setup(name='chemview', - version='0.5', + version='0.6', description='Interactive molecular viewer for IPython notebook', author='Gabriele Lanaro', + install_requires=['notebook', 'numpy', 'matplotlib', 'vapory'], author_email='gabriele.lanaro@gmail.com', url='https://github.com/gabrielelanaro/chemview', packages=find_packages(), - package_data = {'': ['*.js', "*.css"]}, + package_data={'': ['*.js', "*.css"]}, include_package_data=True )