Git Product home page Git Product logo

sverchok's Introduction

Sverchok Redux

A refactoring of Sverchok

In very early development, put add-on a directory called svrx inside of blenders add-on directory.

https://github.com/Sverchok/Sverchok/wiki

Note that this is still in early development and existing layouts might break at anytime.

sverchok's People

Contributors

ly29 avatar zeffii avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sverchok's Issues

API to simplify mesh creation

In many cases when we want to generate some geometry, it's relatively easy to calculate vertex coordinates; but if there is a lot of vertices, then it's hard to figure out which vertex we need to connect to which. And this part of code can be most unintuitive.

What we can have in zero step is smth like

class VertexHandle(object):
  def __init__(self, index, name=None):
    self.index = index
    self.name = name

class Mesh(object):
  def add_vertex(self, vertex, name=None) -> VertexHandle:
  ...

  def add_edge(self, VertexHandle, VertexHandle):
  ...

  def add_face(self, *VertexHandle):
  ...

  def to_rxdata(self):
  ...

  def to_bmesh(self):
  ...

so that we could write smth like

mesh = Mesh()
a = mesh.add_vertex((1.0, 2.0, 3.0))
b = mesh.add_vertex((2.0, 2.0, 3.0))
c = mesh.add_vertex((3.0, 2.0, 3.0))
mesh.add_face(a,b,c)
...
return mesh.to_rxdata()

The great namning debacle

Looking into svrx.typing one quickly sees reason for concern. Many of the names are pretty generic and clashes with blender ````mathutils```. Names in particular that need replacement.

  • Matrix
  • Vector
  • Color

Doing that starting out decided to just pick natural names and it mostly worked out. But some suggestions for avoid doing things like below.

import mathutils as mu
mat = mu.Matrix()
# or even worse
import svrx.typing as t
@node_func
def transform(vert: t.Vertices = None, mat: t.Matrix) -> t.Vertices:

OpenGL viwers

openglviewer
Did a quick test for drawing OpenGL with bmesh, went beyond expectation using the tessellated faces, then we only need to draw triangles.
All data is preprocessed in __call__ and only what to draw, colors etc is passed on to callback.

Very rough state and cannot control drawing on/off for specific features.

Also need a new callback handler or a refactoring of the current one.

Matrix multipilication broken @

So this worked in OS X but not on windows 10, both blender 2.78a.

@node_func(bl_idname="SvRxMatrixTransform")
def transform(vertices: Vertices = Required,
              matrix: Matrix = Matrix.identity
              ) -> Vertices:
    return vertices @ matrix.T
TypeError: unsupported operand type(s) for @: 'numpy.ndarray' and 'numpy.ndarray'

Dynamic Socket Issues

  • Multisocket
  • Changing socket type

Multisocket is allowing a dynamic amount of inputs into a node, like list join and formula node.

@node_func(...)
def formula_node(x: Float = 1.0, *n: Float)-> Float:
    pass

Changing socket type is basically allowing type information to be carried forward over generic socket links. A kind of generics basically.

def split(size: Int = 1, data: Anytype = Required) -> [Anytype("Data", input=1)]:
    pass

stethoscope v2

i propose not having the draw function inside the callback class, but pass it like this
https://github.com/nortikin/sverchok/blob/5c052118b8b1f42c7d4bc8821ad3ca855d1ddde0/nodes/number/easing.py#L147-L179

        # end early
        nvBGL2.callback_disable(n_id)

        ...
   
        if self.activate:

            x, y = [int(j) for j in (self.location + Vector((self.width + 20, 0)))[:]]
            
            draw_data = {
                'tree_name': self.id_data.name[:],
                'custom_function': simple_grid_xy,
                'loc': (x, y),
                'args': (easing_func, palette)  # or whatever args
            }
            nvBGL2.callback_enable(n_id, draw_data)

vectorization concept

Hi, folks. Using hex grid appear an idea for svrx

  1. All vectorization have to occure in any level (or several levels) of data ;
  2. We can use level integer to choose wich level to operate for any of node ;
  3. It concern not only list nodes, but generators, some modificators (or any), some analyzers (or any) ;

Any suggestions how to implement?
And if we vectorize simulateously several levels, how to do?
Interface will include operation level slider for all nodes maybe and add it in generic node

Error handling

Handling of errors in current sverchok is... well... not very good. To understand why the node goes red you need to switch to console and try to understand the stacktrace saying smth like "index -1 is out of bounds".
I think the big refactoring is good time to think about better way of handling errors.

Multi modal nodes

Many functions that we want to expose fall into logical groups that it doesn't make sense to break apart into multipile nodes even though the function are different and in many cases also the function signatures.

For example math nodes and different sort modes.

One, simple option is below, which I feel might be appriate for nodes that keep the same signature but change mode.

mathnode

from svrx.nodes.node_base import node_func
from svrx.typing import Number, Float, Int, EnumP


mode_list_tmp = [('ADD', 1), ('SUB', 2)]
mode_list = [(t, t, t, idx) for t, idx in mode_list_tmp]


@node_func(bl_idname='SvRxNodeNumberMath')
def math(x: Number = 0.0, y: Number = 0.0,
        mode: EnumP(items=mode_list, default='ADD') = 'ADD')-> Number:
    f = func_lookup[mode]
    return f(x, y)


def add(x, y):
    return x  + y

def sub(x, y):
    return x - y

func_lookup = {'ADD': add,
               'SUB': sub, }

However I don't feel this is good enough so will work a bit on this today.

Types

The type system right now is as seen below. It is meant to provide metadata for the execution system and as basis for conversion between types. Dynamic typing is not allowed, however changing type based on setting in a node is allowed. A kind of generics is allowed.

Type Socket class data description
Color SvRxColorSocket np.float64 shape=(N, 4) RGBA
TopoData SvRxTopoSocket Generic topological data
Edges SvRxTopoSocket np.uint, shape = (N, 2)
Faces SvRxTopoSocket SFaces class see #6
SMesh SvRxMeshSocket SMesh see #6
BMesh SvRxMeshSocket Bmesh
Object SvRxObjectSocket Blender obj reference
Number SvRxFloatSocket Generic number
Matrix SvRxMatrixSocket np 4x4 Matrix
Anytype SvRxAnySocket Generic type
String SvRxStringSocket str
Int SvRxIntSocket np.int shape = (N,)
Float SvRxFloatSocket np.float64 shape = (N,)
Vector SvRxVectorSocket np.float64 shape=(N,4), used for when you want a vector
Vertices SvRxVertexSocket np.float64 shape=(N,4)

Monad of Totem

array_inside_sverchok_004_2017_02_06_11_19.zip

I am making this node setup that generates like a TOTEM of Array. Each part of totem can be replaced randomly.

I made a MONAD out of it and trying to vectorize. But seems to fail. Wondering why...

Is this such a case where MONAD vectorize was not made for it.

.foreach mesh out

For study the Mesh.from_pydata method

  • The SvPolygon format is compatible with this,
  • Some validation should be done
  • Amount of vertices must be adjusted
  • Use compatible binary format, eg float32 - C float gives 14x write speed compared to flat numpy float64
    def from_pydata(self, vertices, edges, faces):
        """
        Make a mesh from a list of vertices/edges/faces

        Until we have a nicer way to make geometry, use this.
        :arg vertices:
           float triplets each representing (X, Y, Z)
           eg: [(0.0, 1.0, 0.5), ...].

        :type vertices: iterable object
        :arg edges:
           int pairs, each pair contains two indices to the
           *vertices* argument. eg: [(1, 2), ...]

        :type edges: iterable object
        :arg faces:
           iterator of faces, each faces contains three or more indices to
           the *vertices* argument. eg: [(5, 6, 8, 9), (1, 2, 3), ...]
        :type faces: iterable object
        .. warning::
           Invalid mesh data
           *(out of range indices, edges with matching indices,
           2 sided faces... etc)* are **not** prevented.
           If the data used for mesh creation isn't known to be valid,
           run :class:`Mesh.validate` after this function.
        """

        from itertools import chain, islice, accumulate

        face_lengths = tuple(map(len, faces))

        self.vertices.add(len(vertices))
        self.edges.add(len(edges))
        self.loops.add(sum(face_lengths))
        self.polygons.add(len(faces))

        self.vertices.foreach_set("co", tuple(chain.from_iterable(vertices)))
        self.edges.foreach_set("vertices", tuple(chain.from_iterable(edges)))

        vertex_indices = tuple(chain.from_iterable(faces))
        loop_starts = tuple(islice(chain([0], accumulate(face_lengths)), len(faces)))

        self.polygons.foreach_set("loop_total", face_lengths)
        self.polygons.foreach_set("loop_start", loop_starts)
        self.polygons.foreach_set("vertices", vertex_indices)

        # if no edges - calculate them
        if faces and (not edges):
            self.update(calc_edges=True)

Automatic type conversions

  • From Vertices/Vector/Point to Matrix, gives a translation matrix per location Vertices -> [Matrix]
  • From Number to Vector, gives for each gives n gives (n, n, n, 0) . Number -> Vertices
  • From Matrix to Vertices, [Matrix] -> Vertices or Matrix -> Vertices, not entirely sure about it
  • From Number to Int, either by truncating or by round.
  • From Object to Matrix/Vertices etc.
  • Other

I don't want the number of conversion to be to big and want the rules to be clear and easy to understand, there as an help. For ambiguous situation is better to require clarity. So point out additional rules would be good or suggest a way for socket to show that it can handle multiple types.

Want I don't want to happen is like the old Matrix In node where there is totally invisible functionality available in only one place that is impossible to predict from the UI. I would be better with some different modes for matrix in providing sensible variations.

BMesh nodes

bmesh_nodes
To play around bit I did this.

A typical node ends up looking something like this.

@node_func(id=1)
@generator(mask=(1, 0))
def solidify(bm: BMesh = Required, thickness: Float = 1.0) -> [BMesh]:
    bm = bm.copy()
    geom_in = bm.verts[:] + bm.edges[:] + bm.faces[:]
    bmesh.ops.solidify(bm, geom_in=geom_in, thickness=thickness)
    return bm

Just the sv_init function is longer.
https://github.com/nortikin/sverchok/blob/master/nodes/modifier_make/solidify.py

Stateful Nodes

In the previous implementation the node was implemented manually not fully working with the same interfaces and simplicity as other nodes. So we introduce a class decorator called @stateful

@stateful
class MeshOut():
    bl_idname = "SvRxNodeMeshOut"
    label = "Mesh out"

    def __init__(self):
        self.start()


    def start(self):
        self.verts = []
        self.edges = []
        self.faces = []


    def stop(self):
        obj_index = 0
        #  using range to limit object number for now during testing
        for idx, verts, edges, faces in zip(range(100), self.verts, self.edges, self.faces):
            obj_index = idx
            make_bmesh_geometry(verts[:, :3], edges, faces, idx=idx, normal_update=False)

        # cleanup
        self.remove_non_updated_objects(obj_index)


    def __call__(self, verts: Vertices = Required, edges: Edges = None, faces: Faces = None):
        self.verts.append(verts)
        self.edges.append(edges)
        self.faces.append(faces)
def stateful(cls):
    func = cls()
    get_signature(func)

    class InnerStateful(cls, Stateful):

        inputs_template = func.inputs_template.copy()
        outputs_template = func.outputs_template.copy()
        properties = func.properties.copy()
        parameters = func.parameters.copy()
        returns = func.returns.copy()

    func_new = InnerStateful()
    class_factory(func_new)
    InnerStateful.node_cls = func_new.cls
    _node_classes[cls.bl_idname] = InnerStateful
    return InnerStateful

Never having done any class decorators before I was little bit surprised about the technique.

Primitives and Other generators

currenty the < 0.6 sverchok has a moderately large list of mesh generators, but they aren't organized as tidy as they could be.

I wonder might we do a similar separate menu like Object Add for primitives, with a similar categorization. (the notable exception would be the Nurbs..which are not exposed very well in bpy)

mesh names

let's go name + str("%04d" % idx) padding to 4 numbers
and

    obj['idx'] = idx
    obj['basename'] = node.basemesh_name

that solves all the tricky finding/removing stuff.

Some refactoring going on

  • generator from geom to function
  • fix bl_idnames a bit
  • dump old np linspace/arrange
  • version bump
  • renamning types, matrix etc

Texture script node

script_texture

Inspired by nortikin/sverchok#1248

import numpy as np
import bpy
from svrx.util.function import array_as

def assign_image(image, size ,buffer):
    np_buff = np.empty(len(image.pixels), dtype=np.float32)
    np_buff.shape = (-1, 4)
    np_buff[:,:] = buffer[:,np.newaxis]
    np_buff[:,3] = 1
    np_buff.shape = -1
    image.pixels[:] = np_buff
    return image

@node_script
def texture(value: Float = 0.5,
            size: Int = 64,
            name: String = "texture"):
    image = bpy.data.images.get(name)
    if not image:
        image = bpy.data.images.new(name, size, size, alpha=False)
        image.pack
    else:
        image.scale(size, size)
    new = array_as(value, (size*size,))
    assign_image(image, size, new)
    image.update_tag()                    
                    
                    

I18n/L10n ?

One of things that current Sverchok lacks is proper internationalization. Afaik Blender allows to use some kind of gettext for everything. Can we use the same for Sverchok? This global refactoring might be a good time to introduce such a feature.

Math Node, VMath, Trigonometry, and Logic

Following the mostly successful multi modal node experiment in #11 I now turn to the basic node set starting with math nodes.
In the old Sverchok I think perhaps math node got to many modes, there are 44 and the menu is somewhat hard to navigate.
I propose to split up trigonometry into a different node directly since it is clear divide to make is somewhat more resonable, for now I will not add all functions directly...

See #25 for the current pullrequest for Math node. And modes which are fully or partially implemented
See #28 for Vector Math node branch / PR.

Int and Float socket can be limited

import bmesh
from svrx.util.geom import vectorize
from svrx.util.mesh import rxdata_from_bm

@vectorize
def make_icosphere(subdiv, diam):
    bm = bmesh.new()
    bmesh.ops.create_icosphere(bm, subdivisions=subdiv, diameter=diam, calc_uvs=False)
    return rxdata_from_bm(bm)

@node_script
def sn_icosphere(subdiv: Int(min=0, max=5) = 3, diam: Float = 1.0) -> ([Vertices], [Edges], [Faces]):
    return list(make_icosphere(min(subdiv, 5), diam))

Like so.

startup error message

svrx\util\transforms.py:1899: UserWarning: failed to import module _transformations
  warnings.warn("failed to import module %s" % name)

torus np scriptnode

verts only... almost

import numpy as np
from svrx.util.geom import generator
from svrx.util.smesh import SvVertices

def make_torus(R, r, N1, N2):

    t1 = np.linspace(0, np.pi * 2 * (N1 - 1 / N1), N1)
    t3 = np.tile(t1, N2)

    r1 = np.sin(np.linspace(0, np.pi * 2 * (N2 - 1 / N2), N2))
    r3 = np.repeat(r1, N1) * r + R
    
    r2 = np.repeat(np.cos(np.linspace(0, np.pi * 2 * (N2 - 1 / N2), N2)), N1)

    circ = np.array([np.cos(t3)*r3, np.sin(t3)*r3, r2*r, np.ones(N1*N2)])
    verts = np.transpose(circ)

    return verts, None, None

@node_script
@generator
def sn_torus(
    R: Float = 2.0, r: Float = 0.6, 
    N1: Int = 22, N2: Int = 15) -> ([Vertices], [Edges], [Faces]):
    return list(make_torus(R, r, N1, N2))

Mesh representation

To have two types SMesh and BMesh which are considered compatible and converted as needed by the system.
SMesh is an Sverchok representation of a mesh using flat numpy storage.

The two types would have the same socket representation but the mesh data would be converted as needed. Perhaps also allow overload for certain functions and have two functions to avoid conversion.

To the user and the nodes this would basically be invisible, the intelligence would be in the base system

A partial sample implementation of a mesh using np storage
https://github.com/nortikin/sverchok/blob/polygon_flat/utils/polygon.py

Todolist

A master todolist, feel free to add things, but this is for general features not specific nodes. Only specific nodes that have consequences for the system.

First steps for version 0.6

  • Basic nodeset
  • Stable generator decorator
  • Mesh out
  • Properties, wire up
  • Properties for nodes/draw
  • F8 reload
  • Menu working properly

Documentation of basic design 0.6.1

  • Sockets/node tree/ types
  • How to write Nodes
  • Advanced nodes...

Requirements to fulfil/ Feature to port from Sverchok 0.6.x

All these things should be reconsidered before inclusion, some are independent and and can be moved mostly without problem, some require deeper thinking

  • Reroute support
  • Type conversion
  • Script node #26
  • Working with data tree
  • IO JSON
  • Data viewer
  • Normalize names and impose some order on project layout
  • Documentation system
  • Error handling #24
  • Timings
  • Monad, node groups
  • Switch/node If node
  • I18n/L10n #23
  • Wifi nodes replacement
  • Multi layout operations
  • Upgrade system - possible independent
  • Git branches/ development info - possible independent
  • 3d view panel
  • animation support - just add frame change handler basically.
  • OpenGL viewer

Nodes

  • Consolidate and reflect on existing nodes
  • Look for missing items

Mesh Out node

at the moment most of it works, but it doesn't display anything unless the input contains edges and meshes .

not sure why, haven't looked deeply.

Linspace and arrange

They should be extended into more useful form at least matching float range and int range from N1.

Node wishlist

Opening the gates for nodes that needs to be implemented, priority will be to the ones forcing new issues to be resolved.

Also of course some nodes might needed to be pushed back until issues are resolved, either by working differently or by resolving, potentially systematic issues.

Please add things here.

Basic generator nodes

  • Torus
  • Plane/grid
  • Cube
  • Sphere
  • Cylinder

Math

  • Formula node

List

  • List Repeat / Tile

Topology

  • a simple flip polygons node (non bm) / with mask
    • using a robust ngon normal calculator that handles irregular shapes well (like bmesh's normal calc)
      or doing a 2d convex hull and geting the normalfrom that convex shape,

The old Sverchok nodes
https://github.com/nortikin/sverchok/blob/master/index.md

New sverchok 0.5.9 nodes that should be remade for Rx.

  • vector rewire
  • exec node (not high priority)

Execution

Data is stored in tree structure which knows its own type.

path4521
The data is described using levels which counts the distance from the leafs with data. The tree right now has an even depth but the structure might be irregular otherwise.

Current implementation of data storage is in core.data_tree
https://github.com/Sverchok/Sverchok/blob/master/core/data_tree.py

skarmavbild 2017-01-27 kl 11 43 09

The node tree is then traversed in order as a acyclic graph. For the image above.

linspace.001, vector_in, linspace, Circle, create_matrix, transform, Mesh out

Here there is the possibility of being a lot more clever, to execute a sequences of nodes on a given level etc.
https://github.com/Sverchok/Sverchok/blob/master/core/execution.py#L52-L88

Each node is mapped to a function with provided metadata that can be called on each part of the data tree. Using a node.compile() method that can return different functions based on the settings in the node.

For each node the data tree describing their input sockets are traversed using an longish recursive function. This is similar to what some old nodes like scalar math node does and some list nodes, most don't go as far.
https://github.com/Sverchok/Sverchok/blob/master/core/execution.py#L91-L143

The nodes themselves act upon the leaves or of the data tree and the output is assigned to data tree that are built during the recursion.

Nodes can be separated into 4 kinds.

Level nodes

def example(v: Vertices = None, s: Float = 1.0) -> Vertices
    pass

Generators
A node that increases level is called an generator, for example the circle node or linear space node. For each matched input it produces a separate output.
So circle(radius=[1.0, 2.0, 3.0], vert_nr=20) creates three circles and a new level in the data tree.

Reducer nodes
An node that decreases the level is called an reducer and takes a list a creates singular output. uv_connect(vert : [Vertices]) produces one Mesh object (also different signatures could be possible for uv_connect

Stateful nodes
A node that needs to be aware of the position of each fragment of the structure is called a stateful node, for example mesh output that needs an index for naming.

For now that is pretty much it.

Basic Node set

The idea here is to provide a sufficient amount of nodes that system is usable and can be evaluated beyond totally technical details. A rough outline below based on templates from sverchok.

  • Mesh out
  • Object input
  • Vector in
  • Vectors Out
  • linspace
  • A range
  • Int/Float input
  • Math
  • Trigonometry
  • Logic
  • MatrixGenNode
  • ListJoinNode
  • List Split
  • MaskListNode
  • SvCircleNode
  • ListLengthNode
  • Random Node
  • Topology creation, started.
  • SvInterpolationNode
  • VectorMathNode
  • RandomVectorNode

Timings

Some timings, non optional in branch right now, prints into SVRX_timings text file in blender

timings

Total exec time:  NodeTree 9.523e-03
DAG build time:  2.859e-04 3.0%
SvRxGeneratorTopology: Topology         1.464e-03   15.4%       
SvRxVirtualNode: VNode<Create Matrix>   1.649e-03   17.3%       
SvRxNumberLinspace: Linspace.004        1.425e-04   1.5%        
SvRxNodeMeshOut: Mesh out               3.677e-03   38.6%       
SvRxMatrixTransform: Transform          2.871e-04   3.0%        
SvRxNodeTrig: Trig                      5.888e-05   0.6%        
SvRxNodeVectorIn: Vector In             8.064e-05   0.8%        
SvRxNodeMath: Math                      5.333e-05   0.6%        
SvRxNodeCircle: Circle                  1.796e-03   18.9%       
Node call time total: 9.208e-03 96.7%
Sincos                        1       8.533e-06   0.1%        
Transform                     20      8.107e-05   0.9%        
Linspace                      1       6.827e-05   0.7%        
Mesh out                      1       2.560e-06   0.0%        
Add                           1       1.067e-05   0.1%        
Cylinder                      1       1.390e-03   14.6%       
Vector In                     1       2.133e-05   0.2%        
Circle                        1       1.677e-03   17.6%       
Create Matrix                 1       1.550e-03   16.3%       
Functon call total time 4.809e-03 50.5%

  • control for ui
  • sort nodes in exec order
  • move out function

Documentation

It is another one of these things that is easier to get started with early, parsed the node functions a bit to generate something for docs. It would be ideal if at least the inputs description could be auto generated from the source, images and examples could be manually added, I don't want to full numpy docstring, at least not yet.

----------
Node: Linear Spline
Inputs:
name : default: type
verts : None : Vertices
t : 0.5 : Float
Outputs:
Vertices : Vertices
----------

All nodes
https://gist.github.com/ly29/08a1c2d5ab4a925183245e7b7e8d937d

I don't think we are quite at that stage yet but want to air ideas.

Script Node

script_baby
So just started working on it.

Barely there but functional in the sense that you can load a script and it works.

Todo: (or what does not work)

  • Reset
  • Editing
  • Reload
  • UI
  • Reload on open
  • Reload from TextEditor
  • First line of text must be empty
  • Text file must be valid python identifier (in other words allow file.py and similar things)

A short comparison

levels
In Sverchok Redux complexity is taken care for you.
skarmavbild 2017-02-02 kl 14 27 41
skarmavbild 2017-02-02 kl 14 34 13

Old Sverchok in comparison. By default only one of the circles get the 3rd level circles. In Redux this could be continued as long as desired, in the old we would have join lists and other things to make it work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.