diff --git a/.gitignore b/.gitignore index c6a7c4d7..e095772f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build/ *.py[cod] node_modules/ .vscode +.venv # Compiled javascript pythreejs/static/ diff --git a/js/src/_base/Renderable.js b/js/src/_base/Renderable.js index 2329891d..2656bb74 100644 --- a/js/src/_base/Renderable.js +++ b/js/src/_base/Renderable.js @@ -2,6 +2,7 @@ var _ = require('underscore'); var widgets = require('@jupyter-widgets/base'); var $ = require('jquery'); var Promise = require('bluebird'); +var THREE = require('three'); var pkgName = require('../../package.json').name; var EXTENSION_SPEC_VERSION = require('../version').EXTENSION_SPEC_VERSION; @@ -55,6 +56,7 @@ var RenderableModel = widgets.DOMWidgetModel.extend({ this.createPropertiesArrays(); ThreeModel.prototype.setupListeners.call(this); + }, createPropertiesArrays: function() { @@ -185,6 +187,9 @@ var RenderableView = widgets.DOMWidgetView.extend({ this.isFrozen = true; this.id = Math.floor(Math.random() * 1000000); this._ticking = false; + if (this.model.get('autoResize')) { + window.addEventListener('resize', this.resize.bind(this), false); + } }, remove: function() { @@ -196,6 +201,7 @@ var RenderableView = widgets.DOMWidgetView.extend({ } }, + // This allows dynamic messages from jupyterlab processPhosphorMessage: function(msg) { widgets.DOMWidgetView.prototype.processPhosphorMessage.call(this, msg); switch (msg.type) { @@ -205,6 +211,11 @@ var RenderableView = widgets.DOMWidgetView.extend({ case 'before-detach': this.el.removeEventListener('contextmenu', this, true); break; + case 'resize': + if (this.model.get('autoResize')) { + this.resize(); + } + break; } }, @@ -240,7 +251,6 @@ var RenderableView = widgets.DOMWidgetView.extend({ doRender: function() { this.el.className = 'jupyter-widget jupyter-threejs'; - this.unfreeze(); this.lazyRendererSetup(); @@ -454,6 +464,38 @@ var RenderableView = widgets.DOMWidgetView.extend({ }, + // Get the size of the container, or if that doesn't exist, the window + getSize: function() { + try { + const { width, height } = this.el.getBoundingClientRect(); + console.log("bounding client size"); + return [width, height]; + } catch (error) { + // Not using a container for the renderer + console.log("Window size"); + return [window.innerWidth, window.innerHeight] + } + }, + + // resize the renderer + resize: function() { + let size = this.getSize(); + const width = size[0], + height = size[1]; + + if (width === 0){ + return; + } + this.renderer.setSize(width, height - 4.4); // Seems to grow by 4.4 px on each resize + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + + // finally render if not frozen + if (!this.isFrozen) { + this.render() + } + }, + teardownViewer: function() { this.$renderer.off('mouseenter'); @@ -494,11 +536,15 @@ var RenderableView = widgets.DOMWidgetView.extend({ }); }, + // Permit custom calls from pythreejs onCustomMessage: function(content, buffers) { switch(content.type) { case 'freeze': this.freeze(); break; + case 'resize': + this.resize(); + break; default: } }, diff --git a/pythreejs/_base/renderable.py b/pythreejs/_base/renderable.py index 303cca19..99da6960 100644 --- a/pythreejs/_base/renderable.py +++ b/pythreejs/_base/renderable.py @@ -23,6 +23,7 @@ class RenderableWidget(DOMWidget): _alpha = Bool(False).tag(sync=True) _webgl_version = Int(2).tag(sync=True) + autoResize = Bool(False).tag(sync=True) autoClear = Bool(True).tag(sync=True) autoClearColor = Bool(True).tag(sync=True) autoClearDepth = Bool(True).tag(sync=True) diff --git a/pythreejs/core/Renderer.py b/pythreejs/core/Renderer.py index 050dc2e0..c6aef25f 100644 --- a/pythreejs/core/Renderer.py +++ b/pythreejs/core/Renderer.py @@ -5,7 +5,7 @@ from contextlib import contextmanager from ipywidgets import widget_serialization from traitlets import ( - Unicode, CInt, Instance, Float, Tuple, Undefined, link) + Unicode, CInt, Instance, Float, Tuple, Undefined, link, Bool) from ..traits import * @@ -27,6 +27,7 @@ class Renderer(RenderableWidget): width = CInt(200) height = CInt(200) + scene = Instance(Scene).tag(sync=True, **widget_serialization) camera = Instance(Camera).tag(sync=True, **widget_serialization) controls = List(Instance(Controls)).tag(sync=True, **widget_serialization) @@ -34,7 +35,7 @@ class Renderer(RenderableWidget): background = Color('black', allow_none=True).tag(sync=True) background_opacity = Float(1.0, min=0.0, max=1.0).tag(sync=True) - def __init__(self, scene, camera, controls=None, antialias=False, alpha=False, webgl_version=2, **kwargs): + def __init__(self, scene, camera, controls=None, antialias=False, alpha=False, webgl_version=2, auto_resize=False, **kwargs): super(Renderer, self).__init__( scene=scene, camera=camera, @@ -42,9 +43,11 @@ def __init__(self, scene, camera, controls=None, antialias=False, alpha=False, w _antialias=antialias, _alpha=alpha, _webgl_version=webgl_version, + auto_resize=False, **kwargs) link((self, 'width'), (self, '_width')) link((self, 'height'), (self, '_height')) + self.autoResize = auto_resize def render(self, scene, camera): content = { diff --git a/pythreejs/renderers/WebGLRenderer.py b/pythreejs/renderers/WebGLRenderer.py index 1aa25cf2..ccede28e 100644 --- a/pythreejs/renderers/WebGLRenderer.py +++ b/pythreejs/renderers/WebGLRenderer.py @@ -1,5 +1,5 @@ from ipywidgets import widget_serialization -from traitlets import Unicode, CInt, link +from traitlets import Unicode, CInt, link, Bool from .._base.renderable import RenderableWidget @@ -18,6 +18,7 @@ class WebGLRenderer(RenderableWidget): width = CInt(200) height = CInt(200) + autoResize = Bool(False) def __init__(self, antialias=False, alpha=False, webgl_version=2, **kwargs): super(WebGLRenderer, self).__init__( @@ -41,4 +42,3 @@ def freeze(self): "type": "freeze" } self.send(content) - diff --git a/setup.py b/setup.py index 6de136eb..9bc422ce 100644 --- a/setup.py +++ b/setup.py @@ -72,6 +72,9 @@ 'nbval', 'pytest-check-links', 'numpy>=1.14', + 'matplotlib', + 'ipywebrtc', + 'scikit-image', ], 'examples': [ 'scipy',