mirror of
				https://github.com/stevenrobertson/cuburn.git
				synced 2025-11-03 18:00:55 -05:00 
			
		
		
		
	Add blending with lookup. In theory.
This commit is contained in:
		@ -9,14 +9,73 @@ from itertools import izip_longest
 | 
				
			|||||||
from scipy.ndimage.filters import gaussian_filter1d
 | 
					from scipy.ndimage.filters import gaussian_filter1d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import spectypes
 | 
					import spectypes
 | 
				
			||||||
import spec
 | 
					import specs
 | 
				
			||||||
import util
 | 
					from use import Wrapper
 | 
				
			||||||
from util import get
 | 
					from util import get, json_encode, resolve_spec
 | 
				
			||||||
import variations
 | 
					import variations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: move to better place before checkin
 | 
					def node_to_anim(node, half):
 | 
				
			||||||
default_blend_opts = {'nloops': 2, 'duration': 2, 'xform_sort': 'weightflip'}
 | 
					    if half:
 | 
				
			||||||
 | 
					        osrc, odst = -0.25, 0.25
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        osrc, odst = 0, 1
 | 
				
			||||||
 | 
					    src = apply_temporal_offset(node, osrc)
 | 
				
			||||||
 | 
					    dst = apply_temporal_offset(node, odst)
 | 
				
			||||||
 | 
					    edge = dict(blend=dict(duration=odst-osrc, xform_sort='natural'))
 | 
				
			||||||
 | 
					    return blend(src, dst, edge)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def edge_to_anim(gdb, edge):
 | 
				
			||||||
 | 
					    edge = resolve(gdb, edge)
 | 
				
			||||||
 | 
					    src, osrc = _split_ref_id(edge.link.src)
 | 
				
			||||||
 | 
					    dst, odst = _split_ref_id(edge.link.dst)
 | 
				
			||||||
 | 
					    src = apply_temporal_offset(resolve(gdb, src), osrc)
 | 
				
			||||||
 | 
					    dst = apply_temporal_offset(resolve(gdb, dst), odst)
 | 
				
			||||||
 | 
					    return blend(src, dst, edit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def resolve(gdb, item):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Given an item, recursively retrieve its base items, then merge according
 | 
				
			||||||
 | 
					    to type. Returns the merged dict.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    is_edge = (item['type'] == 'edge')
 | 
				
			||||||
 | 
					    spec = specs.toplevels[item['type']]
 | 
				
			||||||
 | 
					    def go(i):
 | 
				
			||||||
 | 
					        if i.get('base') is not None:
 | 
				
			||||||
 | 
					            return go(gdb.get(i['base'])) + [i]
 | 
				
			||||||
 | 
					        return [i]
 | 
				
			||||||
 | 
					    items = map(flatten, go(item))
 | 
				
			||||||
 | 
					    out = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for k in set(ik for i in items for ik in i):
 | 
				
			||||||
 | 
					        sp = _resolve_spec(spec, k)
 | 
				
			||||||
 | 
					        vs = [i.get(k) for i in items if k in i]
 | 
				
			||||||
 | 
					        # TODO: dict and list negation; early-stage removal of negated knots?
 | 
				
			||||||
 | 
					        if is_edge and isinstance(sp, (Spline, List)):
 | 
				
			||||||
 | 
					            r = sum(vs, [])
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            r = vs[-1]
 | 
				
			||||||
 | 
					        out[k] = r
 | 
				
			||||||
 | 
					    return unflatten(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _split_ref_id(s):
 | 
				
			||||||
 | 
					    sp = s.split('@')
 | 
				
			||||||
 | 
					    if len(sp) == 1:
 | 
				
			||||||
 | 
					        return sp, 0
 | 
				
			||||||
 | 
					    return sp[0], float(sp[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def apply_temporal_offset(node, offset=0):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Given a ``node`` dict, return a node with all periodic splines rotated by
 | 
				
			||||||
 | 
					    ``offset * velocity``, with the same velocity.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    class TemporalOffsetWrapper(Wrapper):
 | 
				
			||||||
 | 
					        def wrap_spline(self, path, spec, val):
 | 
				
			||||||
 | 
					            if spec.period is not None and isinstance(val, list) and val[1]:
 | 
				
			||||||
 | 
					                position, velocity = val
 | 
				
			||||||
 | 
					                return [position + offset * velocity, velocity]
 | 
				
			||||||
 | 
					            return val
 | 
				
			||||||
 | 
					    wr = TemporalOffsetWrapper(node)
 | 
				
			||||||
 | 
					    return wr.visit(wr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def blend(src, dst, edit={}):
 | 
					def blend(src, dst, edit={}):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@ -26,19 +85,22 @@ def blend(src, dst, edit={}):
 | 
				
			|||||||
    animation. These should be plain node dicts (hierarchical, pre-merged,
 | 
					    animation. These should be plain node dicts (hierarchical, pre-merged,
 | 
				
			||||||
    and adjusted for loop temporal offset).
 | 
					    and adjusted for loop temporal offset).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ``edit`` is an optional edit dict, also hierarchical and pre-merged.
 | 
					    ``edge`` is an edge dict, also hierarchical and pre-merged. (It can be
 | 
				
			||||||
 | 
					    empty, in violation of the spec, to support rendering straight from nodes
 | 
				
			||||||
 | 
					    without having to insert anything into the genome database.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Returns the animation spec as a plain dict.
 | 
					    Returns the animation spec as a plain dict.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    # By design, the blend element will contain only scalar values (no
 | 
					    # By design, the blend element will contain only scalar values (no
 | 
				
			||||||
    # splines or hierarchy), so this can be done blindly
 | 
					    # splines or hierarchy), so this can be done blindly
 | 
				
			||||||
    opts = dict(default_blend_opts)
 | 
					    opts = {}
 | 
				
			||||||
    for d in src, dst, edit:
 | 
					    for d in src, dst, edit:
 | 
				
			||||||
        opts.update(d.get('blend', {}))
 | 
					        opts.update(d.get('blend', {}))
 | 
				
			||||||
 | 
					    opts = Wrapper(opts, specs.blend)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    blended = merge_nodes(specs.node, src, dst, edit, opts['nloops'])
 | 
					    blended = merge_nodes(specs.node, src, dst, edit, opts.nloops)
 | 
				
			||||||
    name_map = sort_xforms(src['xforms'], dst['xforms'], opts['xform_sort'],
 | 
					    name_map = sort_xforms(src['xforms'], dst['xforms'], opts.xform_sort,
 | 
				
			||||||
                           explicit=zip(*opts.get('xform_map', [])))
 | 
					                           explicit=zip(*opts.xform_map))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    blended['xforms'] = {}
 | 
					    blended['xforms'] = {}
 | 
				
			||||||
    for (sxf_key, dxf_key) in name_map:
 | 
					    for (sxf_key, dxf_key) in name_map:
 | 
				
			||||||
@ -49,7 +111,7 @@ def blend(src, dst, edit={}):
 | 
				
			|||||||
        blended['xforms'][bxf_key] = blend_xform(
 | 
					        blended['xforms'][bxf_key] = blend_xform(
 | 
				
			||||||
                src['xforms'].get(sxf_key),
 | 
					                src['xforms'].get(sxf_key),
 | 
				
			||||||
                dst['xforms'].get(dxf_key),
 | 
					                dst['xforms'].get(dxf_key),
 | 
				
			||||||
                xf_edits, opts['nloops'])
 | 
					                xf_edits, opts.nloops)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if 'final_xform' in src or 'final_xform' in dst:
 | 
					    if 'final_xform' in src or 'final_xform' in dst:
 | 
				
			||||||
        blended['final_xform'] = blend_xform(src.get('final_xform'),
 | 
					        blended['final_xform'] = blend_xform(src.get('final_xform'),
 | 
				
			||||||
@ -58,7 +120,7 @@ def blend(src, dst, edit={}):
 | 
				
			|||||||
    # TODO: write 'info' section
 | 
					    # TODO: write 'info' section
 | 
				
			||||||
    # TODO: palflip
 | 
					    # TODO: palflip
 | 
				
			||||||
    blended['type'] = 'animation'
 | 
					    blended['type'] = 'animation'
 | 
				
			||||||
    blended.setdefault('time', {})['duration'] = opts['duration']
 | 
					    blended.setdefault('time', {})['duration'] = opts.duration
 | 
				
			||||||
    return blended
 | 
					    return blended
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def merge_edits(sv, av, bv):
 | 
					def merge_edits(sv, av, bv):
 | 
				
			||||||
@ -75,16 +137,16 @@ def merge_edits(sv, av, bv):
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return bv if bv is not None else av
 | 
					        return bv if bv is not None else av
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def tospline(spl, src, dst, edit, loops):
 | 
					def split_node_val(spl, val):
 | 
				
			||||||
    def split_node_val(val):
 | 
					 | 
				
			||||||
    if val is None:
 | 
					    if val is None:
 | 
				
			||||||
        return spl.default, 0
 | 
					        return spl.default, 0
 | 
				
			||||||
    if isinstance(val, (int, float)):
 | 
					    if isinstance(val, (int, float)):
 | 
				
			||||||
        return val, 0
 | 
					        return val, 0
 | 
				
			||||||
    return val
 | 
					    return val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sp, sv = split_node_val(src)    # position, velocity
 | 
					def tospline(spl, src, dst, edit, loops):
 | 
				
			||||||
    dp, dv = split_node_val(dst)
 | 
					    sp, sv = split_node_val(spl, src)    # position, velocity
 | 
				
			||||||
 | 
					    dp, dv = split_node_val(spl, dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # For variation parameters, copy missing values instead of using defaults
 | 
					    # For variation parameters, copy missing values instead of using defaults
 | 
				
			||||||
    if spl.var:
 | 
					    if spl.var:
 | 
				
			||||||
@ -285,4 +347,4 @@ def palflip(gnm):
 | 
				
			|||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    import sys, json
 | 
					    import sys, json
 | 
				
			||||||
    a, b, c = [json.load(open(f+'.json')) for f in 'abc']
 | 
					    a, b, c = [json.load(open(f+'.json')) for f in 'abc']
 | 
				
			||||||
    print util.json_encode(blend(a, b, c))
 | 
					    print json_encode(blend(a, b, c))
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,9 @@ import numpy as np
 | 
				
			|||||||
from variations import var_params
 | 
					from variations import var_params
 | 
				
			||||||
import util
 | 
					import util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Re-exported
 | 
				
			||||||
 | 
					from blend import node_to_anim, edge_to_anim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class XMLGenomeParser(object):
 | 
					class XMLGenomeParser(object):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Parse an XML genome into a list of dictionaries.
 | 
					    Parse an XML genome into a list of dictionaries.
 | 
				
			||||||
@ -163,8 +166,10 @@ def apply_structure(struct, src):
 | 
				
			|||||||
            out[l[0]] = v
 | 
					            out[l[0]] = v
 | 
				
			||||||
    return out
 | 
					    return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def convert_flame(flame):
 | 
					def flam3_to_node(flame):
 | 
				
			||||||
    return util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
 | 
					    n = util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
 | 
				
			||||||
 | 
					    n['type'] = 'node'
 | 
				
			||||||
 | 
					    return n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def convert_file(path):
 | 
					def convert_file(path):
 | 
				
			||||||
    """Quick one-shot conversion for an XML genome."""
 | 
					    """Quick one-shot conversion for an XML genome."""
 | 
				
			||||||
@ -173,7 +178,7 @@ def convert_file(path):
 | 
				
			|||||||
        warnings.warn("Lot of flames in this file. Sure it's not a "
 | 
					        warnings.warn("Lot of flames in this file. Sure it's not a "
 | 
				
			||||||
                      "frame-based animation?")
 | 
					                      "frame-based animation?")
 | 
				
			||||||
    for flame in flames:
 | 
					    for flame in flames:
 | 
				
			||||||
        yield convert_flame(flame)
 | 
					        yield flam3_to_node(flame)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    import sys
 | 
					    import sys
 | 
				
			||||||
 | 
				
			|||||||
@ -68,6 +68,13 @@ time = (
 | 
				
			|||||||
  , 'frame_width': scalespline(d='Scale of profile temporal width per frame.')
 | 
					  , 'frame_width': scalespline(d='Scale of profile temporal width per frame.')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					blend = (
 | 
				
			||||||
 | 
					  { 'nloops': scalar(2)
 | 
				
			||||||
 | 
					  , 'duration': scalar(2)
 | 
				
			||||||
 | 
					  , 'xform_sort': enum('weightflip weight natural color')
 | 
				
			||||||
 | 
					  , 'xform_map': list_(list_(String('xfid'), d='A pair of src, dst IDs'))
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
base = (
 | 
					base = (
 | 
				
			||||||
  { 'name': String("Human-readable name of this work")
 | 
					  { 'name': String("Human-readable name of this work")
 | 
				
			||||||
  , 'camera': camera
 | 
					  , 'camera': camera
 | 
				
			||||||
 | 
				
			|||||||
@ -62,6 +62,19 @@ class Wrapper(object):
 | 
				
			|||||||
            return self.spec.type
 | 
					            return self.spec.type
 | 
				
			||||||
        return self.spec[name]
 | 
					        return self.spec[name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def visit(cls, obj):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Visit every node. Note that for simplicity, this function will be
 | 
				
			||||||
 | 
					        called on all elements (i.e. pivoting to a new Wrapper type inside the
 | 
				
			||||||
 | 
					        wrapping function and overriding visit() won't do anything).
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if isinstance(obj, (Wrapper, dict)):
 | 
				
			||||||
 | 
					            return dict((k, cls.visit(obj[k])) for k in obj)
 | 
				
			||||||
 | 
					        elif isinstance(obj, list):
 | 
				
			||||||
 | 
					            return [cls.visit(o) for o in obj]
 | 
				
			||||||
 | 
					        return obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __getattr__(self, name):
 | 
					    def __getattr__(self, name):
 | 
				
			||||||
        return self.wrap(name, self.get_spec(name), self._val.get(name))
 | 
					        return self.wrap(name, self.get_spec(name), self._val.get(name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -155,6 +168,7 @@ class SplineEval(object):
 | 
				
			|||||||
        return times, vals, t, scale
 | 
					        return times, vals, t, scale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __call__(self, itime, deriv=0):
 | 
					    def __call__(self, itime, deriv=0):
 | 
				
			||||||
 | 
					        # TODO: respect 'interp' THIS IS IMPORTANT.
 | 
				
			||||||
        times, vals, t, scale = self.find_knots(itime)
 | 
					        times, vals, t, scale = self.find_knots(itime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        m1 = (vals[2] - vals[0]) / (1.0 - times[0])
 | 
					        m1 = (vals[2] - vals[0]) / (1.0 - times[0])
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import base64
 | 
				
			|||||||
import numpy as np
 | 
					import numpy as np
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from cuburn.code.util import crep
 | 
					from cuburn.code.util import crep
 | 
				
			||||||
 | 
					import spectypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get(dct, default, *keys):
 | 
					def get(dct, default, *keys):
 | 
				
			||||||
    if len(keys) == 1:
 | 
					    if len(keys) == 1:
 | 
				
			||||||
@ -49,6 +50,13 @@ def unflatten(kvlist):
 | 
				
			|||||||
        go(out, k.split('.'), v)
 | 
					        go(out, k.split('.'), v)
 | 
				
			||||||
    return out
 | 
					    return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def resolve_spec(sp, path):
 | 
				
			||||||
 | 
					    for name in path:
 | 
				
			||||||
 | 
					        if isinstance(sp, spectypes.Map):
 | 
				
			||||||
 | 
					            sp = sp.type
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            sp = sp[name]
 | 
				
			||||||
 | 
					    return sp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def palette_decode(datastrs):
 | 
					def palette_decode(datastrs):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user