|
| 1 | + |
| 2 | +Extending pythreejs |
| 3 | +=================== |
| 4 | + |
| 5 | +While you can do a lot with pythreejs out of the box, you might have |
| 6 | +some custom rendering you want to do, that would be more efficient |
| 7 | +to configure as a separate widget. To be able to integrate such |
| 8 | +objects with pythreejs, the following extension guide can be helpful. |
| 9 | + |
| 10 | + |
| 11 | +Blackbox object |
| 12 | +--------------- |
| 13 | + |
| 14 | +Pythreejs exports a :py:class:`~pythreejs.Blackbox` Widget, |
| 15 | +which inherits :py:class:`~pythreejs.Object3D`. The intention is for |
| 16 | +third-party widget libraries to inherit from it on both the Python |
| 17 | +and JS side. You would add the traits needed to set up your object, |
| 18 | +and have the JS side set up the corresponding three.js object. The |
| 19 | +three.js object itself would not be synced across the wire, which is |
| 20 | +why it is called a blackbox, but you can still manipulate it in a |
| 21 | +scene (transforming, putting it as a child, etc.). This can be |
| 22 | +very efficient e.g. for complex, generated objects, where the |
| 23 | +final three.js data would be prohibitively expensive to synchronize. |
| 24 | + |
| 25 | + |
| 26 | +Example implementation |
| 27 | +********************** |
| 28 | + |
| 29 | +Below is an example implementation for rendering a crystal lattice. |
| 30 | +It takes a basis structure, and then tiles copies of this basis |
| 31 | +in x/y/z, potentially generating thousands of spheres. |
| 32 | + |
| 33 | + |
| 34 | +.. note:: |
| 35 | + |
| 36 | + This example is not a good/optimized crystal structure viewer. It is |
| 37 | + merely used to convey the concept of a widget with a few parameters |
| 38 | + translating to something with potentially hugh amounts of data/objects. |
| 39 | + |
| 40 | + |
| 41 | +Python:: |
| 42 | + |
| 43 | + import traitlets |
| 44 | + import pythreejs |
| 45 | + |
| 46 | + class CubicLattice(pythreejs.Blackbox): |
| 47 | + _model_module = traitlets.Unicode('my_module_name').tag(sync=True) |
| 48 | + |
| 49 | + basis = traitlets.List( |
| 50 | + trait=pythreejs.Vector3(), |
| 51 | + default_value=[[0, 0, 0]], |
| 52 | + max_length=5 |
| 53 | + ).tag(sync=True) |
| 54 | + |
| 55 | + repetitions = traitlets.List( |
| 56 | + trait=traitlets.Int(), |
| 57 | + default_value=[5, 5, 5], |
| 58 | + min_length=3, |
| 59 | + max_length=3 |
| 60 | + ).tag(sync=True) |
| 61 | + |
| 62 | + |
| 63 | +JavaScript: |
| 64 | + |
| 65 | +.. code-block:: javascript |
| 66 | +
|
| 67 | + import * as THREE from "three"; |
| 68 | +
|
| 69 | + import { |
| 70 | + BlackboxModel |
| 71 | + } from 'jupyter-threejs'; |
| 72 | +
|
| 73 | +
|
| 74 | + const atomGeometry = new THREE.SphereBufferGeometry(0.2, 16, 8); |
| 75 | + const atomMaterials = [ |
| 76 | + new THREE.MeshLambertMaterial({color: 'red'}), |
| 77 | + new THREE.MeshLambertMaterial({color: 'green'}), |
| 78 | + new THREE.MeshLambertMaterial({color: 'yellow'}), |
| 79 | + new THREE.MeshLambertMaterial({color: 'blue'}), |
| 80 | + new THREE.MeshLambertMaterial({color: 'cyan'}), |
| 81 | + ]; |
| 82 | +
|
| 83 | + export class CubicLatticeModel extends BlackboxModel { |
| 84 | + defaults() { |
| 85 | + return {...super.defaults(), ...{ |
| 86 | + _model_name: 'CubicLatticeModel', |
| 87 | + _model_module: 'my_module_name', |
| 88 | + basis: [[0, 0, 0]], |
| 89 | + repetitions: [5, 5, 5], |
| 90 | + }}; |
| 91 | + } |
| 92 | +
|
| 93 | + // This method is called to create the three.js object of the model: |
| 94 | + constructThreeObject() { |
| 95 | + const root = new THREE.Group(); |
| 96 | + // Create the children of this group: |
| 97 | + // This is the part that is specific to this example |
| 98 | + this.createLattice(root); |
| 99 | + return root; |
| 100 | + } |
| 101 | +
|
| 102 | + // This method is called whenever the model changes: |
| 103 | + onChange(model, options) { |
| 104 | + super.onChange(model, options); |
| 105 | + // If any of the parameters change, simply rebuild children: |
| 106 | + this.createLattice(); |
| 107 | + } |
| 108 | +
|
| 109 | + // Our custom method to build the lattice: |
| 110 | + createLattice(obj) { |
| 111 | + obj = obj || this.obj; |
| 112 | +
|
| 113 | + // Set up the basis to tile: |
| 114 | + const basisInput = this.get('basis'); |
| 115 | + const basis = new THREE.Group(); |
| 116 | + for (let i=0; i < basisInput.length; ++i) { |
| 117 | + let mesh = new THREE.Mesh(atomGeometry, atomMaterials[i]); |
| 118 | + mesh.position.fromArray(basisInput[i]); |
| 119 | + basis.add(mesh); |
| 120 | + } |
| 121 | +
|
| 122 | + // Tile in x, y, z: |
| 123 | + const [nx, ny, nz] = this.get('repetitions'); |
| 124 | + const children = []; |
| 125 | + for (let x = 0; x < nx; ++x) { |
| 126 | + for (let y = 0; y < ny; ++y) { |
| 127 | + for (let z = 0; z < nz; ++z) { |
| 128 | + let copy = basis.clone(); |
| 129 | + copy.position.set(x, y, z); |
| 130 | + children.push(copy); |
| 131 | + } |
| 132 | + } |
| 133 | + } |
| 134 | +
|
| 135 | + obj.remove(...obj.children); |
| 136 | + obj.add(...children); |
| 137 | + } |
| 138 | + } |
| 139 | +
|
| 140 | +
|
| 141 | +This code should then be wrapped up in a widget extension (see |
| 142 | +documentation from ipywidgets on how to do this). |
| 143 | + |
| 144 | +Usage:: |
| 145 | + |
| 146 | + import pythreejs |
| 147 | + from IPython.display import display |
| 148 | + from my_module import CubicLattice |
| 149 | + |
| 150 | + lattice = CubicLattice(basis=[[0,0,0], [0.5, 0.5, 0.5]]) |
| 151 | + |
| 152 | + # Preview the lattice directly: |
| 153 | + display(lattice) |
| 154 | + |
| 155 | + # Or put it in a scene: |
| 156 | + width=600 |
| 157 | + height=400 |
| 158 | + key_light = pythreejs.DirectionalLight(position=[-5, 5, 3], intensity=0.7) |
| 159 | + ambient_light = pythreejs.AmbientLight(color='#777777') |
| 160 | + |
| 161 | + camera = pythreejs.PerspectiveCamera( |
| 162 | + position=[-5, 0, -5], |
| 163 | + children=[ |
| 164 | + # Have the key light follow the camera: |
| 165 | + key_light |
| 166 | + ], |
| 167 | + aspect=width/height, |
| 168 | + ) |
| 169 | + control = pythreejs.OrbitControls(controlling=camera) |
| 170 | + |
| 171 | + scene = pythreejs.Scene(children=[lattice, camera, ambient_light]) |
| 172 | + |
| 173 | + renderer = pythreejs.Renderer(camera=camera, |
| 174 | + scene=scene, |
| 175 | + controls=[control], |
| 176 | + width=width, height=height) |
| 177 | + |
| 178 | + display(renderer) |
| 179 | + |
| 180 | + |
| 181 | + |
| 182 | +.. figure:: images/extension-example.png |
| 183 | + :alt: rendered output example |
| 184 | + |
| 185 | + Figure: Example view of the rendered lattice object. |
0 commit comments