Source code for bibliopixel.project.fill

"""
Fill in missing parts of a project and cleans up its sections
so they have exactly the right constructor.
"""

import copy
from . import aliases, construct, merge
from .. import layout
from .. util import deprecated, exception, log
from .. colors import make, palettes, tables
from .. animation.strip import Strip

DEFAULT_DRIVERS = [construct.to_type('simpixel')]


[docs]def fill(desc): desc = _fill_aliases(desc) desc = _fill_colors(desc) desc = _fill_palettes(desc) desc = _fill_animation(desc) desc = _fill_drivers(desc) desc = _fill_shape(desc) # Must come after fill_drivers desc = _fill_numbers(desc) desc = _fill_controls(desc) return desc
[docs]def fill_layout(animation): # Try to fill in the layout if it's missing. # This is called from Project.__init__ - TODO: fix that. datatype = animation['datatype'] args = getattr(datatype, 'LAYOUT_ARGS', Strip.LAYOUT_ARGS) layout_cl = getattr(datatype, 'LAYOUT_CLASS', Strip.LAYOUT_CLASS) args = {k: animation[k] for k in args if k in animation} return dict(args, datatype=layout_cl)
def _fill_aliases(desc): def unalias(k): if any(k.startswith(m) for m in aliases.ALIAS_MARKERS): return k[1:] return k al = desc.pop('aliases', {}) aliases.PROJECT_ALIASES = {unalias(k): v for k, v in al.items()} return desc def _fill_colors(desc): tables.set_user_colors(desc.pop('colors', {})) return desc def _fill_palettes(desc): p = desc.pop('palettes', None) if p: default = p.pop('default', None) with exception.add('Error in "palettes" Section'): pp = {k: make.colors(v) for k, v in p.items()} palettes.PROJECT_PALETTES = pp if default: try: palettes.set_default(default) except: log.error('Unable to set default palette to be %s', default) return desc def _fill_animation(desc): da = desc['animation'] or {'typename': 'animation'} da = construct.to_type_constructor(da, 'bibliopixel.animation') datatype = da.get('datatype') if not datatype: e = da.get('_exception') raise e or ValueError('Missing "datatype" in "animation" Section') da.setdefault('name', datatype.__name__) from .. animation import sequence if not issubclass(datatype, sequence.Sequence): # Magic here to allow this to work for non-sequences. length = da.pop('length', []) if length: desc.setdefault('run', {})['seconds'] = length[0] desc = merge.merge(getattr(datatype, 'PROJECT', {}), desc) da['run'] = dict(desc.pop('run', {}), **da.get('run', {})) desc['animation'] = da return desc def _fill_drivers(desc): driver = construct.to_type(desc.pop('driver', {})) drivers = [construct.to_type(d) for d in desc['drivers']] if driver: drivers = [dict(driver, **d) for d in drivers or [{}]] desc['drivers'] = drivers or DEFAULT_DRIVERS return desc def _fill_shape(desc): # TODO: this copy can't be deleted AND this can't be moved even one step # earlier without causing a test failure! desc = copy.deepcopy(desc) dimensions = desc.pop('dimensions', None) if dimensions: deprecated.deprecated('Project Section "dimensions"') shape = desc.pop('shape', None) or dimensions if not shape: return desc if len(desc['drivers']) != 1: raise ValueError('Cannot use dimensions with more than one driver') if isinstance(shape, int): shape = [shape] elif isinstance(shape, str): shape = shape.strip() if shape.startswith('(') and shape.endswith(')'): shape = shape[1:-1] try: shape = [int(s) for s in shape.split(',')] except: raise ValueError('Cannot parse shape %s' % shape) elif not isinstance(shape, (list, tuple)): raise ValueError('`shape` must be a number or a list, was "%s" (%s)' % (shape, type(shape))) ldesc = construct.to_type_constructor(desc.get('layout') or {}, python_path='bibliopixel.layout') driver = desc['drivers'][0] if len(shape) == 1: driver['num'] = shape[0] ldesc.setdefault('datatype', layout.strip.Strip) elif len(shape) == 2: driver['num'] = shape[0] * shape[1] ldesc.setdefault('datatype', layout.matrix.Matrix) ldesc.update(width=shape[0], height=shape[1]) elif len(shape) == 3: driver['num'] = shape[0] * shape[1] * shape[2] ldesc.setdefault('datatype', layout.cube.Cube) ldesc.update(x=shape[0], y=shape[1], z=shape[2]) else: raise ValueError('Dimension %s > 3' % len(shape)) desc['layout'] = ldesc return desc def _fill_numbers(desc): numbers = desc.pop('numbers', None) or 'python' if numbers != 'python': desc.setdefault('maker', {})['numpy_dtype'] = numbers return desc def _fill_controls(desc): controls = desc.get('controls', None) if isinstance(controls, (str, dict)): desc['controls'] = [controls] return desc