diff --git a/examples/reference/panes/VTK.ipynb b/examples/reference/panes/VTK.ipynb
index bd1b95ac2c..04fc54869c 100644
--- a/examples/reference/panes/VTK.ipynb
+++ b/examples/reference/panes/VTK.ipynb
@@ -46,7 +46,9 @@
" \n",
" The mouse must be over the pane to work\n",
"
**Warning**: These keybindings may not work as expected in a notebook context, if they interact with already bound keys\n",
- "* **``orientation_widget``** (bool): A boolean to activate/deactivate the orientation widget in the 3D pane. This widget is clickable and allows to rotate the scene in one of the orthographic projections.\n",
+ "* **``orientation_widget``** (bool): A boolean to activate/deactivate the orientation widget in the 3D pane.\n",
+ "* **``ìnteractive_orientation_widget``** (bool): If True the orientation widget is clickable and allows to rotate the scene in one of the orthographic projections.\n",
+ "
**Warning**: if set to True, synchronization capabilities of `VTKRenderWindowSynchronized` panes could not work.\n",
"* **``object``** (object): Must be a ``vtkRenderWindow`` instance.\n",
"\n",
"#### Properties:\n",
diff --git a/examples/reference/panes/VTKJS.ipynb b/examples/reference/panes/VTKJS.ipynb
index 3e0967e29e..a0a7c704be 100644
--- a/examples/reference/panes/VTKJS.ipynb
+++ b/examples/reference/panes/VTKJS.ipynb
@@ -43,7 +43,8 @@
" \n",
" The mouse must be over the pane to work.\n",
"
**Warning**: These keybindings may not work as expected in a notebook context, if they interact with already bound keys.\n",
- "* **``orientation_widget``** (bool): A boolean to activate/deactivate the orientation widget in the 3D pane. This widget is clickable and allows to rotate the scene in one of the orthographic projections.\n",
+ "* **``orientation_widget``** (bool): A boolean to activate/deactivate the orientation widget in the 3D pane.\n",
+ "* **``ìnteractive_orientation_widget``** (bool): If True the orientation widget is clickable and allows to rotate the scene in one of the orthographic projections.\n",
"* **``object``** (str or object): Can be a string pointing to a local or remote file with a `.vtkjs` extension.\n",
"___"
]
diff --git a/panel/models/vtk.py b/panel/models/vtk.py
index 77c502894d..81b4613da6 100644
--- a/panel/models/vtk.py
+++ b/panel/models/vtk.py
@@ -9,7 +9,7 @@
from bokeh.core.enums import enumeration
from bokeh.models import HTMLBox, Model, ColorMapper
-vtk_cdn = "https://unpkg.com/vtk.js@13.18.0/dist/vtk.js"
+vtk_cdn = "https://unpkg.com/vtk.js@14.16.4/dist/vtk.js"
class VTKAxes(Model):
"""
@@ -64,6 +64,8 @@ class AbstractVTKPlot(HTMLBox):
orientation_widget = Bool(default=False)
+ interactive_orientation_widget = Bool(default=False)
+
width = Override(default=300)
diff --git a/panel/models/vtk/util.ts b/panel/models/vtk/util.ts
index a86c79f54f..f918f30484 100644
--- a/panel/models/vtk/util.ts
+++ b/panel/models/vtk/util.ts
@@ -1,8 +1,5 @@
import {linspace} from "@bokehjs/core/util/array"
-import {Follower} from "./vtkfollower"
-import {RenderWindowInteractor} from "./vtkrenderwindowinteractor"
-
export const ARRAY_TYPES = {
uint8: Uint8Array,
int8: Int8Array,
@@ -30,7 +27,7 @@ if (vtk) {
vtkns["CubeSource"] = vtk.Filters.Sources.vtkCubeSource
vtkns["DataAccessHelper"] = vtk.IO.Core.DataAccessHelper
vtkns["DataArray"] = vtk.Common.Core.vtkDataArray
- vtkns["Follower"] = Follower
+ vtkns["Follower"] = vtk.Rendering.Core.vtkFollower
vtkns["FullScreenRenderWindow"] = vtk.Rendering.Misc.vtkFullScreenRenderWindow
vtkns["Glyph3DMapper"] = vtk.Rendering.Core.vtkGlyph3DMapper
vtkns["HttpSceneLoader"] = vtk.IO.Core.vtkHttpSceneLoader
@@ -60,7 +57,7 @@ if (vtk) {
vtkns["Property"] = vtk.Rendering.Core.vtkProperty
vtkns["Renderer"] = vtk.Rendering.Core.vtkRenderer
vtkns["RenderWindow"] = vtk.Rendering.Core.vtkRenderWindow
- vtkns["RenderWindowInteractor"] = RenderWindowInteractor
+ vtkns["RenderWindowInteractor"] = vtk.Rendering.Core.vtkRenderWindowInteractor
vtkns["SphereMapper"] = vtk.Rendering.Core.vtkSphereMapper
vtkns["SynchronizableRenderWindow"] =
vtk.Rendering.Misc.vtkSynchronizableRenderWindow
@@ -86,7 +83,7 @@ if (vtk) {
)
vtkObjectManager.setTypeMapping(
"vtkFollower",
- Follower.newInstance,
+ vtkns.Follower.newInstance,
vtkObjectManager.genericUpdater
)
}
diff --git a/panel/models/vtk/vtkfollower.ts b/panel/models/vtk/vtkfollower.ts
deleted file mode 100644
index 0a69a19803..0000000000
--- a/panel/models/vtk/vtkfollower.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import { vec3, mat4 } from 'gl-matrix'
-
-
-export let Follower: any
-
-const vtk = (window as any).vtk
-
-if(vtk) {
- const macro = vtk.macro
- const vtkActor = vtk.Rendering.Core.vtkActor
-
- function vtkFollower(publicAPI: any, model: any) {
- // Set our className
- model.classHierarchy.push('vtkFollower')
-
- // Capture 'parentClass' api for internal use
- const superClass = { ...publicAPI }
-
- publicAPI.getMTime = () => {
- let mt = superClass.getMTime()
- if (model.camera !== null) {
- const time = model.camera.getMTime()
- mt = time > mt ? time : mt
- }
-
- return mt;
- };
-
- publicAPI.computeMatrix = () => {
- // check whether or not need to rebuild the matrix
- if (publicAPI.getMTime() > model.matrixMTime.getMTime()) {
- mat4.identity(model.matrix)
- if (model.userMatrix) {
- mat4.multiply(model.matrix, model.matrix, model.userMatrix)
- }
- mat4.translate(model.matrix, model.matrix, model.origin)
- mat4.translate(model.matrix, model.matrix, model.position)
- mat4.multiply(model.matrix, model.matrix, model.rotation)
- mat4.scale(model.matrix, model.matrix, model.scale)
-
- if (model.camera) {
- // first compute our target viewUp
- const vup = vec3.fromValues(model.viewUp[0], model.viewUp[1], model.viewUp[2])
- if (!model.useViewUp) {
- const cvup = model.camera.getViewUp()
- vec3.set(vup, cvup[0], cvup[1], cvup[2])
- }
-
- // compute a vpn
- const vpn = vec3.create();
- if (model.camera.getParallelProjection()) {
- const cvpn = model.camera.getViewPlaneNormal()
- vec3.set(vpn, cvpn[0], cvpn[1], cvpn[2])
- } else {
- vec3.set(vpn, model.position[0], model.position[1], model.position[2])
- const cpos = model.camera.getPosition()
- const tmpv3 = vec3.fromValues(cpos[0], cpos[1], cpos[2])
- vec3.subtract(vpn, tmpv3, vpn)
- vec3.normalize(vpn, vpn)
- }
-
- // compute vright
- const vright = vec3.create();
- vec3.cross(vright, vup, vpn)
- vec3.normalize(vright, vright)
-
- // now recompute the vpn so that it is orthogonal to vup
- vec3.cross(vpn, vright, vup)
- vec3.normalize(vpn, vpn)
-
- model.followerMatrix[0] = vright[0]
- model.followerMatrix[1] = vright[1]
- model.followerMatrix[2] = vright[2]
-
- model.followerMatrix[4] = vup[0]
- model.followerMatrix[5] = vup[1]
- model.followerMatrix[6] = vup[2]
-
- model.followerMatrix[8] = vpn[0]
- model.followerMatrix[9] = vpn[1]
- model.followerMatrix[10] = vpn[2]
-
- mat4.multiply(model.matrix, model.followerMatrix, model.matrix);
- }
-
- mat4.translate(model.matrix, model.matrix, [
- -model.origin[0],
- -model.origin[1],
- -model.origin[2],
- ]);
- mat4.transpose(model.matrix, model.matrix);
-
- // check for identity
- model.isIdentity = false;
- model.matrixMTime.modified();
- }
- }
- }
-
- // ----------------------------------------------------------------------------
- // Object factory
- // ----------------------------------------------------------------------------
-
- const DEFAULT_VALUES = {
- viewUp: [0, 1, 0],
- useViewUp: false,
- camera: null,
- }
-
- // ----------------------------------------------------------------------------
-
- Follower = {
- newInstance: macro.newInstance((publicAPI: any, model: any, initialValues = {}) => {
- Object.assign(model, DEFAULT_VALUES, initialValues)
-
- // Inheritance
- vtkActor.extend(publicAPI, model, initialValues)
-
- model.followerMatrix = mat4.create()
- model.camera = vtk.Rendering.Core.vtkCamera.newInstance()
- mat4.identity(model.followerMatrix)
-
- // Build VTK API
- macro.setGet(publicAPI, model, ['useViewUp', 'camera'])
-
- macro.setGetArray(publicAPI, model, ['viewUp'], 3)
-
- // Object methods
- vtkFollower(publicAPI, model);
- }, 'vtkFollower')
- }
-}
\ No newline at end of file
diff --git a/panel/models/vtk/vtkjs.ts b/panel/models/vtk/vtkjs.ts
index a5cd01ba6e..d2088b8e63 100644
--- a/panel/models/vtk/vtkjs.ts
+++ b/panel/models/vtk/vtkjs.ts
@@ -48,7 +48,7 @@ export class VTKJSPlotView extends AbstractVTKView {
setTimeout(() => {
if (this._axes == null && this.model.axes) this._set_axes()
this._set_camera_state()
- this.model.properties.camera.change.emit()
+ this._get_camera_state()
}, 100),
100
)
diff --git a/panel/models/vtk/vtklayout.ts b/panel/models/vtk/vtklayout.ts
index 2ccb9f5487..381c4c38e1 100644
--- a/panel/models/vtk/vtklayout.ts
+++ b/panel/models/vtk/vtklayout.ts
@@ -10,7 +10,7 @@ import {vtkns, VolumeType, majorAxis, applyStyle, CSSProperties} from "./util"
import {VTKColorBar} from "./vtkcolorbar"
import {VTKAxes} from "./vtkaxes"
-const INFO_DIV_STYLE: CSSProperties = {
+const INFO_DIV_STYLE: CSSProperties = {
padding: "0px 2px 0px 2px",
maxHeight: "150px",
height: "auto",
@@ -35,7 +35,7 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
protected _vtk_container: HTMLDivElement
protected _vtk_renwin: any
protected _widgetManager: any
-
+
initialize(): void {
super.initialize()
this._camera_callbacks = []
@@ -49,7 +49,7 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
if (old_info_div)
this.el.removeChild(old_info_div)
if (this.model.color_mappers.length < 1) return
-
+
const info_div = document.createElement("div")
const expand_width = "350px"
const collapsed_width = "30px"
@@ -110,7 +110,9 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
this.init_vtk_renwin()
set_size(this._vtk_container, this.model)
this.el.appendChild(this._vtk_container)
- this._connect_interactions_to_model()
+ // update camera model state only at the end of the interaction
+ // with the scene (avoid bouncing events and large amount of events)
+ this._vtk_renwin.getInteractor().onEndAnimation(() => this._get_camera_state())
this._remove_default_key_binding()
this._bind_key_events()
this.plot()
@@ -118,7 +120,7 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
this.model.renderer_el = this._vtk_renwin
} else {
set_size(this._vtk_container, this.model)
- // warning if _vtk_renwin contain controllers or other elements
+ // warning if _vtk_renwin contain controllers or other elements
// we must attach them to the new el
this.el.appendChild(this._vtk_container)
}
@@ -146,7 +148,7 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
abstract init_vtk_renwin(): void
abstract plot(): void //here goes the specific implementation pour all concrete model based on vtk-js
-
+
get _vtk_camera_state(): any {
const vtk_camera = this._vtk_renwin.getRenderer().getActiveCamera()
let state: any
@@ -159,13 +161,6 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
delete state.flattenedDepIds
delete state.managedInstanceId
delete state.directionOfProjection
- delete state.projectionMatrix
- delete state.viewMatrix
- delete state.physicalTranslation
- delete state.physicalScale
- delete state.physicalViewUp
- delete state.physicalViewNorth
- delete state.mtime
}
return state
}
@@ -228,19 +223,6 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
})
}
- _connect_interactions_to_model(): void {
- // update camera model state only at the end of the interaction
- // with the scene (avoid bouncing events and large amount of events)
-
- const update_model_camera = () => {
- this._get_camera_state()
- this.model.properties.camera.change.emit()
- }
- const interactor = this._vtk_renwin.getInteractor()
- const event_list = ["LeftButtonRelease", "RightButtonRelease", "EndAnimation"]
- event_list.forEach((event) => interactor['on' + event](update_model_camera))
- }
-
_create_orientation_widget(): void {
const axes = vtkns.AxesActor.newInstance()
@@ -256,10 +238,16 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
this._orientationWidget.setViewportSize(0.15)
this._orientationWidget.setMinPixelSize(75)
this._orientationWidget.setMaxPixelSize(300)
+ if (this.model.interactive_orientation_widget)
+ this._make_orientation_widget_interactive()
+ this._orientation_widget_visibility(this.model.orientation_widget)
+ }
+ _make_orientation_widget_interactive(): void {
this._widgetManager = vtkns.WidgetManager.newInstance()
this._widgetManager.setRenderer(this._orientationWidget.getRenderer())
+ const axes = this._orientationWidget.getActor()
const widget = vtkns.InteractiveOrientationWidget.newInstance()
widget.placeWidget(axes.getBounds())
widget.setBounds(axes.getBounds())
@@ -292,10 +280,11 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
this._vtk_renwin.getRenderer().resetCameraClippingRange()
this._vtk_render()
+ this._get_camera_state()
})
- this._orientation_widget_visibility(this.model.orientation_widget)
}
+
_delete_axes(): void {
if (this._axes) {
Object.keys(this._axes).forEach((key) =>
@@ -323,8 +312,10 @@ export abstract class AbstractVTKView extends PanelHTMLBoxView {
_orientation_widget_visibility(visibility: boolean): void {
this._orientationWidget.setEnabled(visibility)
- if (visibility) this._widgetManager.enablePicking()
- else this._widgetManager.disablePicking()
+ if (this._widgetManager != null){
+ if (visibility) this._widgetManager.enablePicking()
+ else this._widgetManager.disablePicking()
+ }
this._vtk_render()
}
@@ -394,6 +385,7 @@ export namespace AbstractVTKPlot {
enable_keybindings: p.Property
orientation_widget: p.Property
color_mappers: p.Property
+ interactive_orientation_widget: p.Property
}
}
@@ -415,10 +407,11 @@ export abstract class AbstractVTKPlot extends HTMLBox {
static init_AbstractVTKPlot(): void {
this.define({
- axes: [ p.Instance ],
- camera: [ p.Instance ],
- color_mappers: [ p.Array, [] ],
- orientation_widget: [ p.Boolean, false ],
+ axes: [ p.Instance ],
+ camera: [ p.Instance ],
+ color_mappers: [ p.Array, [] ],
+ orientation_widget: [ p.Boolean, false ],
+ interactive_orientation_widget: [ p.Boolean, false ],
})
this.override({
diff --git a/panel/models/vtk/vtkrenderwindowinteractor.ts b/panel/models/vtk/vtkrenderwindowinteractor.ts
deleted file mode 100644
index 18a998e35a..0000000000
--- a/panel/models/vtk/vtkrenderwindowinteractor.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-export let RenderWindowInteractor: any
-
-const vtk = (window as any).vtk
-
-if (vtk) {
- const macro = vtk.macro
- const vtkRenderWindowInteractor = vtk.Rendering.Core.vtkRenderWindowInteractor
- // ----------------------------------------------------------------------------
- // panelRenderWindowInteractor fix findPokeRenderer
- // ----------------------------------------------------------------------------
-
- function panelRenderWindowInteractor(publicAPI: any, model: any) {
- // Set our className
- model.classHierarchy.push("panelRenderWindowInteractor")
-
- publicAPI.findPokedRenderer = (x = 0, y = 0) => {
- if (!model.view) {
- return null
- }
- const rc = model.view.getRenderable().getRenderersByReference()
- rc.sort((a: any, b: any) => a.getLayer() - b.getLayer())
-
- let interactiveren = null
- let viewportren = null
- let currentRenderer = null
-
- let count = rc.length
- while (count--) {
- const aren = rc[count]
- if (model.view.isInViewport(x, y, aren) && aren.getInteractive()) {
- currentRenderer = aren
- break
- }
-
- if (interactiveren === null && aren.getInteractive()) {
- // Save this renderer in case we can't find one in the viewport that
- // is interactive.
- interactiveren = aren
- }
- if (viewportren === null && model.view.isInViewport(x, y, aren)) {
- // Save this renderer in case we can't find one in the viewport that
- // is interactive.
- viewportren = aren
- }
- }
-
- // We must have a value. If we found an interactive renderer before, that's
- // better than a non-interactive renderer.
- if (currentRenderer === null) {
- currentRenderer = interactiveren
- }
-
- // We must have a value. If we found a renderer that is in the viewport,
- // that is better than any old viewport (but not as good as an interactive
- // one).
- if (currentRenderer === null) {
- currentRenderer = viewportren
- }
-
- // We must have a value - take anything.
- if (currentRenderer == null) {
- currentRenderer = rc[0]
- }
-
- return currentRenderer
- }
- }
-
- // ----------------------------------------------------------------------------
- // Object factory
- // ----------------------------------------------------------------------------
-
- RenderWindowInteractor = {
- newInstance: macro.newInstance(
- (publicAPI: any, model: any, initialValues = {}) => {
- vtkRenderWindowInteractor.extend(publicAPI, model, initialValues)
-
- // Object specific methods
- panelRenderWindowInteractor(publicAPI, model)
- },
- "panelRenderWindowInteractor"
- ),
- }
-}
diff --git a/panel/models/vtk/vtksynchronized.ts b/panel/models/vtk/vtksynchronized.ts
index 4e9e77820a..cf32e84f35 100644
--- a/panel/models/vtk/vtksynchronized.ts
+++ b/panel/models/vtk/vtksynchronized.ts
@@ -49,7 +49,7 @@ export class VTKSynchronizedPlotView extends AbstractVTKView {
CONTEXT_NAME
)
}
-
+
connect_signals(): void {
super.connect_signals()
this.connect(this.model.properties.arrays.change, () =>
@@ -64,7 +64,6 @@ export class VTKSynchronizedPlotView extends AbstractVTKView {
Promise.all(this._promises).then(() => {
this._sync_plot(state, () => {
this._on_scene_ready()
- this._connect_interactions_to_model()
})
})
}
@@ -87,7 +86,10 @@ export class VTKSynchronizedPlotView extends AbstractVTKView {
this._decode_arrays()
const state = clone(this.model.scene)
Promise.all(this._promises).then(() => {
- this._sync_plot(state, () => this._on_scene_ready())
+ this._sync_plot(state, () => this._on_scene_ready()).then(() => {
+ this._set_camera_state()
+ this._get_camera_state()
+ })
})
}
@@ -131,10 +133,9 @@ export class VTKSynchronizedPlotView extends AbstractVTKView {
if (!this._axes) this._set_axes()
this._vtk_renwin.resize()
this._vtk_render()
- this._set_camera_state()
}
- _sync_plot(state: any, onSceneReady: CallableFunction): void {
+ _sync_plot(state: any, onSceneReady: CallableFunction): any {
// Need to ensure all promises are resolved before calling this function
this._renderable = false
this._promises = []
@@ -147,8 +148,7 @@ export class VTKSynchronizedPlotView extends AbstractVTKView {
)
if (renderer && !this._vtk_renwin.getRenderer())
this._vtk_renwin.getRenderWindow().addRenderer(renderer)
-
- this._vtk_renwin
+ return this._vtk_renwin
.getRenderWindow()
.synchronize(state).then(onSceneReady)
}
diff --git a/panel/models/vtk/vtkvolume.ts b/panel/models/vtk/vtkvolume.ts
index 1118873aa8..f7846e60b2 100644
--- a/panel/models/vtk/vtkvolume.ts
+++ b/panel/models/vtk/vtkvolume.ts
@@ -111,7 +111,11 @@ export class VTKVolumePlotView extends AbstractVTKView {
super.render()
this._create_orientation_widget()
this._set_axes()
- if (!this.model.camera) this._vtk_renwin.getRenderer().resetCamera()
+ if (!this.model.camera)
+ this._vtk_renwin.getRenderer().resetCamera()
+ else
+ this._set_camera_state()
+ this._get_camera_state()
}
invalidate_render(): void {
diff --git a/panel/pane/vtk/vtk.py b/panel/pane/vtk/vtk.py
index 43f8d617f3..b66cf8252e 100644
--- a/panel/pane/vtk/vtk.py
+++ b/panel/pane/vtk/vtk.py
@@ -66,6 +66,9 @@ class AbstractVTK(PaneBase):
orientation_widget = param.Boolean(default=False, doc="""
Activate/Deactivate the orientation widget display.""")
+ interactive_orientation_widget = param.Boolean(default=True, constant=True, doc="""
+ """)
+
def _process_param_change(self, msg):
msg = super(AbstractVTK, self)._process_param_change(msg)
if 'axes' in msg and msg['axes'] is not None:
@@ -161,6 +164,8 @@ def __new__(self, obj, **params):
if VTKRenderWindow.applies(obj, **params):
return VTKRenderWindow(obj, **params)
else:
+ if params.get('interactive_orientation_widget', False):
+ param.main.param.warning("""Setting interactive_orientation_widget=True will break synchronization capabilities of the pane""")
return VTKRenderWindowSynchronized(obj, **params)
elif VTKJS.applies(obj):
return VTKJS(obj, **params)
@@ -416,6 +421,9 @@ class VTKRenderWindowSynchronized(BaseVTKRenderWindow, SyncHelpers):
with a custom bokeh model on javascript side
"""
+ interactive_orientation_widget = param.Boolean(default=False, constant=True, doc="""
+ """)
+
_one_time_reset = param.Boolean(default=False)
_rename = dict(_one_time_reset='one_time_reset',
@@ -502,12 +510,14 @@ def unlink_camera(self):
old_camera = self.vtk_camera
new_camera = vtk.vtkCamera()
self.vtk_camera = new_camera
+ exclude_properties = ['mtime']
if self.camera is not None:
for k, v in self.camera.items():
- if type(v) is list:
- getattr(new_camera, 'Set' + k[0].capitalize() + k[1:])(*v)
- else:
- getattr(new_camera, 'Set' + k[0].capitalize() + k[1:])(v)
+ if k not in exclude_properties:
+ if type(v) is list:
+ getattr(new_camera, 'Set' + k[0].capitalize() + k[1:])(*v)
+ else:
+ getattr(new_camera, 'Set' + k[0].capitalize() + k[1:])(v)
else:
new_camera.DeepCopy(old_camera)