In [1]:
<script src=""></script>

Each Comm

  • every object has a comm to its counterpart model in javascript

Here is an example

  • a python mesh is instantiated, along with geometry and texture objects
  • the python mesh is added to the scene object, and a unique id is assigned to it
  • if the scene is not displayed (i.e., no comm is initialized), do nothing
  • if the scene is displayed, then for the root object (recursively) (could do this top down or bottom-up. we choose bottom-up, which means the parent object needs to add the children)
    • create the comms for each of the subobjects, and render them
    • create the root comm, along with references to the comm ids for each of the children. From the children comm ids, retrieve the children models and store a reference to them. Then render the node (which creates the three.js object)
    • have the scene update its model to include a reference to the new model and update it (which adds the root node to the scene)

In [2]:
require(["notebook/js/widget"], function() {

    var GeometryModel = Backbone.Model.extend({});
    IPython.widget_manager.register_widget_model('GeometryModel', GeometryModel);

    var SphereGeometryModel = Backbone.Model.extend({});
    IPython.widget_manager.register_widget_model('SphereGeometryModel', SphereGeometryModel);

    var MaterialModel = Backbone.Model.extend({});
    IPython.widget_manager.register_widget_model('MaterialModel', MaterialModel);

    var MeshModel = Backbone.AssociatedModel.extend({
          relations: [
      {type: Backbone.One,
        key: 'geometry',
      {type: Backbone.One,
        key: 'material',
    IPython.widget_manager.register_widget_model('MeshModel', MeshModel);
    var CameraModel = Backbone.Model.extend({});
    IPython.widget_manager.register_widget_model('CameraModel', CameraModel);   

    var SceneModel = Backbone.AssociatedModel.extend({
      relations: [
      {type: Backbone.Many,
        key: 'meshes',
    IPython.widget_manager.register_widget_model('SceneModel', SceneModel);

    var RendererModel = IPython.WidgetModel.extend({
        initialize: function() {
            // for each object in the hash
            //initialize the models
            // objects is a hash of every object
      relations: [
      {type: Backbone.One,
        key: 'scene',
      {type: Backbone.One,
        key: 'camera',
    IPython.widget_manager.register_widget_model('RendererModel', RendererModel);        


Traitlet objects in python, and some way to specify the scene graph relationship. Possibly the scene graph is represented separately from the collection of objects, and possibly the objects form a nested scenegraph

Communication with the front end over a single comm link---so the renderer is the only "widget". Changes to each of the objects is communicated through it.

Events on the javascript side

Possible ways to do it

  • have a flat set of objects and a separate scenegraph showing the relationships between the objects.

    • easy to sync changes (each object has an id; changes contain the id number)
    • fits with backbone better, sort of. We just need a heterogeneous collection of models, plus a scenegraph model.
  • nested set of objects

    • more natural to construct
    • harder to specify events---you have to traverse the hierarchy first

In [2]:

In [ ]:

In [2]:
# Import the base Widget class and the traitlets Unicode class.
from IPython.html.widgets.widget import Widget, Widget
from IPython.utils.traitlets import Unicode, Int, Instance, Enum, List, Float

class Geometry(Widget):
    target_name = Unicode('GeometryModel')
    default_view_name = Unicode('GeometryView')

class SphereGeometry(Geometry):
    target_name = Unicode('SphereGeometryModel')
    default_view_name = Unicode('SphereGeometryView')
    _keys = ['radius']
    radius = Int(100)

class Material(Widget):
    target_name = Unicode('MaterialModel')
    default_view_name = Unicode('MaterialView')
    _keys = ['color']
    color = Int(0x00cc00)

class Mesh(Widget):
    target_name = Unicode('MeshModel')
    default_view_name = Unicode('MeshView')
    _keys = []
    _children = ['geometry', 'material']
    geometry = Instance(Geometry)
    material = Instance(Material)

class Camera(Widget):
    target_name = Unicode('CameraModel')
    default_view_name = Unicode('CameraView')
    _keys = ['fov', 'ratio']
    fov = Int(70)
    ratio = Float(600.0/400.0)
class Scene(Widget):
    target_name = Unicode('SceneModel')
    default_view_name = Unicode('SceneView') 
    _keys = []
    _children = ['meshes']
    meshes = List(Instance(Mesh))

class Renderer(Widget):
    target_name = Unicode('RendererModel')
    default_view_name = Unicode('RendererView')
    _keys = ['width', 'height', 'renderer_type']
    _children = ['scene', 'camera']
    width = Int(600)
    height = Int(400)
    renderer_type = Enum(['webgl', 'canvas', 'auto'], 'auto']
    scene = Instance(Scene)
    camera = Instance(Camera)

In [4]:
require(["notebook/js/widget"], function() {
    var RendererView = IPython.WidgetView.extend({
        render : function(){
            var width = this.model.get('width');
            var height = this.model.get('height');
            this.renderer = new THREE.WebGLRenderer();
            this.renderer.setSize( width, height);
            this.$el.empty().append( this.renderer.domElement );
   = new CameraView({model: this.model.get('camera')});
            this.scene = new SceneView({model: this.model.get('scene')});
            console.log('renderer', this.model, this.scene.obj);
        update : function(){
            // TODO: tie into a requestAnimationFrame update (just trigger a frame if one is not already lined up)
            // perhaps we should just listen to the nested-change event
            // and perform the appropriate modificatino ourselves?
            // this assumes that (1) we've stored the three.js hierarchy exactly as the models are set up
            // and (2) all changes to the three.js objects are exactly the same (e.g., setting attributes)
            // regardless, we should probably listen to the nested-change event to trigger a redraw

    IPython.widget_manager.register_widget_view('RendererView', RendererView);
    var NestedView = function(options) {
        this.cid = _.uniqueId('view');
        options || (options = {});
        _.extend(this, _.pick(options, ['model', 'id']));
        this.initialize.apply(this, arguments);
        this.model.on('change', this.update, this);
        this.visible = true;
    _.extend(NestedView.prototype, Backbone.Events, {
        initialize: function(){},
        render: function() {
          return this;
        remove: function() {
          return this;
    NestedView.extend = Backbone.View.extend;
    var CameraView = NestedView.extend({
        render: function() {
            var camera = this.obj = new THREE.PerspectiveCamera( this.model.get('fov'), this.model.get('ratio'), 1, 1000 );
            return camera;
    var SceneView = NestedView.extend({
        render: function() {
            var scene = this.obj = new THREE.Scene();
            var light = new THREE.PointLight(0xffffff);
            var that = this;
            this.meshviews = [];
            this.model.get('meshes').each(function(model) {
                var m = new MeshView({model: model})
            this.model.get('lights').each(function(model) {
                var m = new LightView({model: model})
            return scene;
// TODO: most change events are very similar---just change the three.js object appropriately.
// maybe 
    var GeometryView = NestedView.extend({
        render: function() {
            var geometry = this.obj = new THREE.SphereGeometry(this.model.get('radius'), 32, 16);
            return geometry;
        // update will need to generate a new mesh (to change the radius...)
    var MaterialView = NestedView.extend({
        render: function() {
            var material = this.obj = new THREE.MeshLambertMaterial({color: this.model.get('color')});
            return material;
    var MeshView = NestedView.extend({
        render: function() {
            this.geometryview = new GeometryView({model: this.model.get('geometry')});
            this.materialview = new MaterialView({model: this.model.get('material')});
            var mesh = this.obj = new THREE.Mesh( this.geometryview.render(), this.materialview.render() );
            return mesh

In [5]:
from IPython.display import display
sphere = Mesh(geometry=SphereGeometry(), material=Material())
scene = Scene(meshes=[sphere])
renderer = Renderer(camera=Camera(), scene = scene)

{'width': 600, '_remove_class': [0], '_css': {}, 'scene': {'visible': True, 'meshes': [{'geometry': {'visible': True, 'radius': 100}, 'visible': True, 'material': {'color': 52224, 'visible': True}}]}, 'height': 400, 'visible': True, 'camera': {'visible': True, 'fov': 70, 'ratio': 1.5}, '_add_class': [0], 'renderer_type': 'auto'}

In [ ]: