Featured post

new redirect for blender.org bpy docs.

http://www.blender.org/api/blender_python_api_current/ As of 10/11 november 2015 we can now link to the current api docs and not be worr...

June 22, 2013

Data Viz Episode 1 (WIP don't share yet)

Creating a Vector overlay for rendered raster images.

I'm of the school that considers typography most effective when rendered in 2d, i.e. I don't want my topography for labels / names / scales / dimensions etc to be renderable 3d geometry. I'd have to set up a constraint to always point the typography at the camera so it doesn't distort. But if I can get the 2d locations of these entities it is possible to create vector graphics overlays. Not just useful for typography but grids and axis ticks too.

Let us construct some json from the cube object. This json will have a readability issue if we use indent=0, but with indent list members each get a new line. I know about it, and so do these guys. Some of this code is the same as the first post, but now it writes to a file in the same location as blender.exe
import bpy
from bpy_extras.object_utils import world_to_camera_view
import json
# output json of verts and edges
scene = bpy.context.scene
# needed to rescale 2d coordinates
render = scene.render
res_x = render.resolution_x
res_y = render.resolution_y
obj = bpy.data.objects['Cube']
cam = bpy.data.objects['Camera']
verts = (vert.co for vert in obj.data.vertices)
coords_2d = [world_to_camera_view(scene, cam, coord) for coord in verts]
# find min max distance, between eye and coordinate.
rnd = lambda i: round(i)
rnd3 = lambda i: round(i, 3)
limit_finder = lambda f: f(coords_2d, key=lambda i: i[2])[2]
limits = limit_finder(min), limit_finder(max)
limits = [rnd3(d) for d in limits]
# limits, 2d coords, 2d distances, edge_keys
print('min, max\n{},{}'.format(*limits))
pixel_coords = []
distances = []
edge_keys = [list(i.key) for i in obj.data.edges] # list might be optional
# x, y, d=distance_to_lens
for x, y, d in coords_2d:
pixel_coords.append([rnd(res_x*x), rnd(res_y*y)])
distances.append(rnd3(d))
print("here")
scene_details = {
"limits": limits,
"pixel_coords": pixel_coords,
"distances": distances,
"edge_keys": edge_keys
}
print("here 2")
with open('blender_geom_data.json', 'w') as wfile:
wfile.write(json.dumps(scene_details, sort_keys=True, indent=4))


This will make a lot more sense with some images now it is possible and easy to turn this:

into this (here live d3.js code on tributary to parse the json into svg)

How to proceed from here

What i've shown so far is both trivial and awesome at the same time. Imagine now that we construct the guides / axis as a edge based mesh object, that never gets rendered as pixels. This object would still be visible in 3d view, but the edges wouldn't be rendered in the final render, instead their information can be deferred to this svg-overlay I keep banging on about. Some of this process can be bound to reusable functions both in python and d3.js. i'll probably write a few as examples and start a github repo and get this ball rolling.

Like:

here's the live link

stacked chart

import bpy
from bpy_extras.object_utils import world_to_camera_view
scene = bpy.context.scene
# needed to rescale 2d coordinates
render = scene.render
res_x = render.resolution_x
res_y = render.resolution_y
cam = bpy.data.objects['Camera']
# short hand utilities
to_screen = lambda v: (v.x * res_x, v.y * res_y, v.z)
coord_to_px = lambda coord: world_to_camera_view(scene, cam, coord)
info = lambda mt: (mt.name, coord_to_px(mt.location))
# getting empties location, names and converting 3d vectors to 3d screen space
empties_info = [info(mt) for mt in bpy.data.objects if mt.type == "EMPTY"]
empties_info_2d = [(name, to_screen(v)) for name, v in empties_info]
import json
scene_details = {'empties': empties_info_2d}
with open('blender_geom_data2.json', 'w') as wfile:
wfile.write(json.dumps(scene_details, sort_keys=True, indent=4))

With a little bit of d3.js overlay here