Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[VTK] add vtkFollower #1451

Merged
merged 3 commits into from
Jun 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions panel/models/vtk/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {linspace} from "@bokehjs/core/util/array"
import {Follower} from "./vtkfollower"

export const ARRAY_TYPES = {
uint8: Uint8Array,
Expand Down Expand Up @@ -27,6 +28,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["FullScreenRenderWindow"] = vtk.Rendering.Misc.vtkFullScreenRenderWindow
vtkns["Glyph3DMapper"] = vtk.Rendering.Core.vtkGlyph3DMapper
vtkns["HttpSceneLoader"] = vtk.IO.Core.vtkHttpSceneLoader
Expand Down Expand Up @@ -80,6 +82,11 @@ if (vtk) {
vtkns.VolumeMapper.newInstance,
vtkObjectManager.oneTimeGenericUpdater
)
vtkObjectManager.setTypeMapping(
"vtkFollower",
Follower.newInstance,
vtkObjectManager.genericUpdater
)
}

declare type RGBnode = {
Expand Down
134 changes: 134 additions & 0 deletions panel/models/vtk/vtkfollower.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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) {
console.log("Hello")
// 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')
}

}
3 changes: 2 additions & 1 deletion panel/pane/vtk/synchronizable_deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"SetUseGradientOpacity": None,
"SetRGBTransferFunction": "SetColor",
}
WRAP_ID_RE = re.compile("instance:\${([^}]+)}")
WRAP_ID_RE = re.compile(r"instance:\${([^}]+)}")
ARRAY_TYPES = {
'Int8Array': vtk.vtkCharArray,
'Uint8Array': vtk.vtkUnsignedCharArray,
Expand Down Expand Up @@ -170,6 +170,7 @@ def make_type_handlers():
'vtkGlyph3DMapper': generic_builder,
'vtkProperty': generic_builder,
'vtkActor': generic_builder,
'vtkFollower': generic_builder,
'vtkColorTransferFunction': color_fun_builder,
'vtkPiecewiseFunction': piecewise_fun_builder,
'vtkTexture': generic_builder,
Expand Down
11 changes: 11 additions & 0 deletions panel/pane/vtk/synchronizable_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ def initializeSerializers():
registerInstanceSerializer('vtkImageSlice', genericProp3DSerializer)
registerInstanceSerializer('vtkVolume', genericProp3DSerializer)
registerInstanceSerializer('vtkOpenGLActor', genericActorSerializer)
registerInstanceSerializer('vtkFollower', genericActorSerializer)
registerInstanceSerializer('vtkPVLODActor', genericActorSerializer)


Expand Down Expand Up @@ -575,6 +576,16 @@ def genericActorSerializer(parent, actor, actorId, context, depth):
'forceOpaque': actor.GetForceOpaque(),
'forceTranslucent': actor.GetForceTranslucent()
})

if actor.IsA('vtkFollower'):
camera = actor.GetCamera()
cameraId = context.getReferenceId(camera)
cameraInstance = serializeInstance(
actor, camera, cameraId, context, depth + 1)
if cameraInstance:
instance['dependencies'].append(cameraInstance)
instance['calls'].append(['setCamera', [wrapId(cameraId)]])

return instance

# -----------------------------------------------------------------------------
Expand Down
46 changes: 30 additions & 16 deletions panel/tests/pane/test_vtk.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,27 @@


def make_render_window():

#cone actor
cone = vtk.vtkConeSource()
coneMapper = vtk.vtkPolyDataMapper()
coneMapper.SetInputConnection(cone.GetOutputPort())
coneActor = vtk.vtkActor()
coneActor.SetMapper(coneMapper)

#text actor following camera
text = vtk.vtkVectorText()
text.SetText("Origin")
textMapper = vtk.vtkPolyDataMapper()
textMapper.SetInputConnection(text.GetOutputPort())
textActor = vtk.vtkFollower()
textActor.SetMapper(textMapper)

ren = vtk.vtkRenderer()
ren.AddActor(coneActor)
ren.AddActor(textActor)
textActor.SetCamera(ren.GetActiveCamera())

renWin = vtk.vtkRenderWindow()
renWin.AddRenderer(ren)
return renWin
Expand All @@ -52,15 +66,15 @@ def pyvista_render_window():
globe = examples.load_globe() #test texture
head = examples.download_head() #test volume
uniform = examples.load_uniform() #test structured grid

scalars=sphere.points[:, 2]
sphere._add_point_array(scalars, 'test', set_active=True) #allow to test scalars

uniform.set_active_scalars("Spatial Cell Data")

#test datasetmapper
threshed = uniform.threshold_percent([0.15, 0.50], invert=True)
bodies = threshed.split_bodies()
bodies = threshed.split_bodies()
mapper = vtk.vtkCompositePolyDataMapper2()
mapper.SetInputDataObject(0, bodies)
multiblock = vtk.vtkActor()
Expand Down Expand Up @@ -126,7 +140,7 @@ def test_vtkjs_pane(document, comm, tmp_path):
assert isinstance(model, VTKJSPlot)
assert pane_from_url._models[model.ref['id']][0] is model
assert isinstance(model.data, string_types)

with BytesIO(base64.b64decode(model.data.encode())) as in_memory:
with ZipFile(in_memory) as zf:
filenames = zf.namelist()
Expand Down Expand Up @@ -158,10 +172,10 @@ def test_vtk_pane_from_renwin(document, comm):

# Check array release when actor are removed from scene
ctx = pane._contexts[model.id]
assert len(ctx.dataArrayCache.keys()) == 3
assert len(ctx.dataArrayCache.keys()) == 5
pane.remove_all_actors()
# Default : 20s before removing arrays
assert len(ctx.dataArrayCache.keys()) == 3
assert len(ctx.dataArrayCache.keys()) == 5
# Force 0s for removing arrays
ctx.checkForArraysToRelease(0)
assert len(ctx.dataArrayCache.keys()) == 0
Expand All @@ -181,14 +195,14 @@ def test_vtk_serialize_on_instantiation(document, comm, tmp_path):
assert isinstance(model, VTKSynchronizedPlot)

pane.param.trigger('object')

# test export to file
tmpfile = os.path.join(*tmp_path.joinpath('scene').parts)
exported_file = pane.export_scene(filename=tmpfile)
assert exported_file.endswith('.synch')

# test import from file
imported_pane = VTK.import_scene(filename=exported_file,
imported_pane = VTK.import_scene(filename=exported_file,
synchronizable=False)
assert isinstance(imported_pane, VTKRenderWindow)

Expand All @@ -197,7 +211,7 @@ def test_vtk_serialize_on_instantiation(document, comm, tmp_path):
def test_vtk_sync_helpers(document, comm):
renWin1 = make_render_window()
renWin2 = make_render_window()

# Create 2 panes to compare each other
pane1 = VTK(renWin1)
pane2 = VTK(renWin2)
Expand All @@ -213,23 +227,23 @@ def test_vtk_sync_helpers(document, comm):
assert isinstance(model2, VTKSynchronizedPlot)

# Actors getter
assert len(pane1.actors) == 1
assert len(pane2.actors) == 1
assert len(pane1.actors) == 2
assert len(pane2.actors) == 2
assert pane1.actors[0] is not pane2.actors[0]

# Actors add
pane1.add_actors(pane2.actors)
assert len(pane1.actors) == 2
assert pane1.actors[1] is pane2.actors[0]
assert len(pane1.actors) == 4
assert pane1.actors[3] is pane2.actors[1]

# Actors remove
save_actor = pane1.actors[0]
pane1.remove_actors([pane1.actors[0]])
assert pane1.actors[0] is pane2.actors[0]
assert pane1.actors[2] is pane2.actors[1]

# Actors remove all
pane1.add_actors([save_actor])
assert len(pane1.actors) == 2
assert len(pane1.actors) == 4
pane1.remove_all_actors()
assert len(pane1.actors) == 0

Expand Down Expand Up @@ -263,7 +277,7 @@ def test_vtk_pane_more_complex(document, comm, tmp_path):
assert pane._models[model.ref['id']][0] is model

colorbars_infered = pane.construct_colorbars().object

assert len(colorbars_infered.below) == 2 # infer only actor color bars
assert all(isinstance(cb, ColorBar) for cb in colorbars_infered.below)

Expand Down Expand Up @@ -293,7 +307,7 @@ def test_vtk_pane_more_complex(document, comm, tmp_path):
# (TODO test if the scene imported is identical to the one exported)
imported_scene = VTK.import_scene(filename=exported_file)
assert isinstance(imported_scene, VTKRenderWindowSynchronized)

# Cleanup
pane._cleanup(model)
assert pane._contexts == {}
Expand Down