InteractiveOrientationWidget

Methods

getRepresentationForViewType

Argument Type Required Description
viewType ViewTypes Yes

setBounds

Set the widget bounds

Argument Type Required Description
bounds Bounds Yes The widget bounds

Source

behavior.js
import macro from 'vtk.js/Sources/macros';

export default function widgetBehavior(publicAPI, model) {
model.classHierarchy.push('vtkInteractiveOrientationWidgetProp');
macro.event(publicAPI, model, 'OrientationChange');

// --------------------------------------------------------------------------
// Right click: Delete handle
// --------------------------------------------------------------------------

publicAPI.handleRightButtonPress = (e) => {
if (
!model.activeState ||
!model.activeState.getActive() ||
!model.pickable
) {
return macro.VOID;
}
publicAPI.invokeOrientationChange({
action: 'rightPress',
event: e,
...model.activeState.get('up', 'right', 'direction'),
});
return macro.EVENT_ABORT;
};

// --------------------------------------------------------------------------
// Left press: Select handle to drag
// --------------------------------------------------------------------------

publicAPI.handleLeftButtonPress = (e) => {
if (
!model.activeState ||
!model.activeState.getActive() ||
!model.pickable
) {
return macro.VOID;
}
publicAPI.invokeOrientationChange({
action: 'leftPress',
event: e,
...model.activeState.get('up', 'right', 'direction'),
});
return macro.EVENT_ABORT;
};
}
helpers.d.ts
import vtkAbstractWidget from '../../../Widgets/Core/AbstractWidget';
import vtkCamera from '../../../Rendering/Core/Camera';
import vtkInteractiveOrientationWidget from '../InteractiveOrientationWidget';
import vtkOrientationMarkerWidget from '../../../Interaction/Widgets/OrientationMarkerWidget';
import vtkRenderer from '../../../Rendering/Core/Renderer';
import vtkRenderWindowInteractor from '../../../Rendering/Core/RenderWindowInteractor';
import vtkWidgetManager from '../../../Widgets/Core/WidgetManager';
import { vtkSubscription } from '../../../interfaces';
import { Bounds, Vector3 } from '../../../types';

export function majorAxis(
vec3: Vector3,
idxA: number,
idxB: number
): [number, number, number];

/**
* Create a new vtkOrientationMarkerWidget instance from the provided interactor and parentRenderer and sensible defaults.
*
* @param {vtkRenderWindowInteractor} interactor
* @param {vtkRenderer} parentRenderer
* @returns {vtkOrientationMarkerWidget}
*/
export function createOrientationMarkerWidget(
interactor: vtkRenderWindowInteractor,
parentRenderer: vtkRenderer
): vtkOrientationMarkerWidget;

/**
* Create a new vtkInteractiveOrientationWidget instance and place it at the given bounds.
*
* @param {Bounds} bounds
* @returns {vtkInteractiveOrientationWidget}
*/
export function createInteractiveOrientationWidget(
bounds: Bounds
): vtkInteractiveOrientationWidget;

/**
* Create a new vtkOrientationMarkerWidget alongside with a new vtkInteractiveOrientationWidget with sensible defaults.
*
* @param {vtkWidgetManager} widgetManager
* @param {vtkRenderWindowInteractor} interactor
* @param {vtkRenderer} mainRenderer
* @returns {Object} the constructed widget instances
*/
export function createInteractiveOrientationMarkerWidget(
widgetManager: vtkWidgetManager,
interactor: vtkRenderWindowInteractor,
mainRenderer: vtkRenderer
): {
interactiveOrientationWidget: vtkInteractiveOrientationWidget;
orientationMarkerWidget: vtkOrientationMarkerWidget;
};

/**
* Listen to OrientationChange events on the given view widget.
* The event handler will align the provided camera and update the provided vtkOrientationMarkerWidget instance.
*
* @param {vtkAbstractWidget} viewWidget Must be a vtkInteractiveOrientationWidget view widget
* @param {vtkCamera} camera The camera instance to upate when orientation changes
* @param {vtkOrientationMarkerWidget} orientationMarkerWidget The instance to update when orientation changes
* @param {vtkWidgetManager} widgetManager
* @param {Function} render A callback that should render the view
* @returns {vtkSubscription} the corresponding event subscription, can be used to unsubscribe from the event
*/
export function alignCameraOnViewWidgetOrientationChange(
viewWidget: vtkAbstractWidget,
camera: vtkCamera,
orientationMarkerWidget: vtkOrientationMarkerWidget,
widgetManager: vtkWidgetManager,
render: () => void
): vtkSubscription;
helpers.js
import * as vtkMath from '@kitware/vtk.js/Common/Core/Math';
import vtkOrientationMarkerWidget from '@kitware/vtk.js/Interaction/Widgets/OrientationMarkerWidget';
import vtkAxesActor from '@kitware/vtk.js/Rendering/Core/AxesActor';
import vtkInteractiveOrientationWidget from '@kitware/vtk.js/Widgets/Widgets3D/InteractiveOrientationWidget';

export function majorAxis(vec3, idxA, idxB) {
const axis = [0, 0, 0];
const idx = Math.abs(vec3[idxA]) > Math.abs(vec3[idxB]) ? idxA : idxB;
const value = vec3[idx] > 0 ? 1 : -1;
axis[idx] = value;
return axis;
}

export function createOrientationMarkerWidget(interactor, parentRenderer) {
const axes = vtkAxesActor.newInstance();

const orientationWidget = vtkOrientationMarkerWidget.newInstance({
actor: axes,
interactor,
interactiveRenderer: true,
viewportSize: 0.1,
minPixelSize: 100,
maxPixelSize: 300,
parentRenderer,
});
orientationWidget.setEnabled(true);
orientationWidget.setViewportCorner(
vtkOrientationMarkerWidget.Corners.BOTTOM_LEFT
);

return orientationWidget;
}

export function createInteractiveOrientationWidget(bounds) {
const widget = vtkInteractiveOrientationWidget.newInstance();
widget.placeWidget(bounds);
widget.setBounds(bounds.map((v) => v * 0.45));

return widget;
}

export function createInteractiveOrientationMarkerWidget(
widgetManager,
interactor,
mainRenderer
) {
const orientationMarkerWidget = createOrientationMarkerWidget(
interactor,
mainRenderer
);
interactor.getInteractorStyle().setFocusedRenderer(mainRenderer);

widgetManager.setRenderer(orientationMarkerWidget.getRenderer());

const widget = createInteractiveOrientationWidget(
orientationMarkerWidget.getActor().getBounds()
);

return {
interactiveOrientationWidget: widget,
orientationMarkerWidget,
};
}

export function alignCameraOnViewWidgetOrientationChange(
viewWidget,
camera,
orientationMarkerWidget,
widgetManager,
render
) {
return viewWidget.onOrientationChange(({ up, direction, action, event }) => {
const focalPoint = camera.getFocalPoint();
const position = camera.getPosition();
const viewUp = camera.getViewUp();

const distance = Math.sqrt(
vtkMath.distance2BetweenPoints(position, focalPoint)
);
camera.setPosition(
focalPoint[0] + direction[0] * distance,
focalPoint[1] + direction[1] * distance,
focalPoint[2] + direction[2] * distance
);

if (direction[0]) {
camera.setViewUp(majorAxis(viewUp, 1, 2));
}
if (direction[1]) {
camera.setViewUp(majorAxis(viewUp, 0, 2));
}
if (direction[2]) {
camera.setViewUp(majorAxis(viewUp, 0, 1));
}

orientationMarkerWidget.updateMarkerOrientation();
render();
});
}
index.d.ts
import {
vtkAbstractWidgetFactory,
IAbstractWidgetFactoryInitialValues,
} from '../../Core/AbstractWidgetFactory';
import vtkAbstractWidget from '../../Core/AbstractWidget';
import { Bounds } from '../../../types';
import { ViewTypes } from '../../Core/WidgetManager/Constants';

export interface vtkInteractiveOrientationWidget<
WidgetInstance extends vtkAbstractWidget = vtkAbstractWidget
> extends vtkAbstractWidgetFactory<WidgetInstance> {
/**
* Set the widget bounds
*
* @param {Bounds} bounds The widget bounds
*/
setBounds(bounds: Bounds): void;

/**
* @param {ViewTypes} viewType
*/
getRepresentationForViewType(viewType: ViewTypes): unknown;
}

export interface IInteractiveOrientationWidgetInitialValues<
WidgetInstance extends vtkAbstractWidget
> extends IAbstractWidgetFactoryInitialValues<WidgetInstance> {}

/**
* Method use to decorate a given object (publicAPI+model) with vtkInteractiveOrientationWidget characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {object} [initialValues] (default: {})
*/
export function extend<WidgetInstance extends vtkAbstractWidget>(
publicAPI: object,
model: object,
initialValues?: IInteractiveOrientationWidgetInitialValues<WidgetInstance>
): void;

/**
* Creates a new instance of vtkInteractiveOrientationWidget
*
* @param {object} [initialValues] for pre-setting some of its content
*/
export function newInstance<
WidgetInstance extends vtkAbstractWidget = vtkAbstractWidget
>(
initialValues?: IInteractiveOrientationWidgetInitialValues<WidgetInstance>
): vtkInteractiveOrientationWidget<WidgetInstance>;

export declare const vtkInteractiveOrientationWidget: {
newInstance: typeof newInstance;
extend: typeof extend;
};

export default vtkInteractiveOrientationWidget;
index.js
import macro from 'vtk.js/Sources/macros';
import vtkAbstractWidgetFactory from 'vtk.js/Sources/Widgets/Core/AbstractWidgetFactory';
import vtkConvexFaceContextRepresentation from 'vtk.js/Sources/Widgets/Representations/ConvexFaceContextRepresentation';

import widgetBehavior from 'vtk.js/Sources/Widgets/Widgets3D/InteractiveOrientationWidget/behavior';
import {
INITIAL_POINTS,
generateState,
} from 'vtk.js/Sources/Widgets/Widgets3D/InteractiveOrientationWidget/state';

import { Behavior } from 'vtk.js/Sources/Widgets/Representations/WidgetRepresentation/Constants';
import { ViewTypes } from 'vtk.js/Sources/Widgets/Core/WidgetManager/Constants';

// ----------------------------------------------------------------------------
// Factory
// ----------------------------------------------------------------------------

function vtkInteractiveOrientationWidget(publicAPI, model) {
model.classHierarchy.push('vtkInteractiveOrientationWidget');

// --- Widget Requirement ---------------------------------------------------

model.methodsToLink = [
'closePolyLine',
'activeScaleFactor',
'activeColor',
'useActiveColor',
'glyphResolution',
'defaultScale',
];

publicAPI.setBounds = (bounds) => {
const handles = model.widgetState.getStatesWithLabel('handles');
for (let i = 0; i < handles.length; i++) {
const xyz = INITIAL_POINTS[i];
const x = xyz[0] > 0 ? bounds[1] : bounds[0];
const y = xyz[1] > 0 ? bounds[3] : bounds[2];
const z = xyz[2] > 0 ? bounds[5] : bounds[4];
handles[i].setOrigin(x, y, z);
}
};

publicAPI.getRepresentationsForViewType = (viewType) => {
switch (viewType) {
case ViewTypes.DEFAULT:
case ViewTypes.GEOMETRY:
case ViewTypes.SLICE:
case ViewTypes.VOLUME:
default:
return [
{
builder: vtkConvexFaceContextRepresentation,
labels: ['---', '--+', '-++', '-+-'],
initialValues: {
behavior: Behavior.HANDLE,
pickable: true,
activeScaleFactor: 1.2,
activeColor: 1,
useActiveColor: true,
name: 'Face 1',
},
},
{
builder: vtkConvexFaceContextRepresentation,
labels: ['---', '+--', '+-+', '--+'],
initialValues: {
behavior: Behavior.HANDLE,
pickable: true,
activeScaleFactor: 1.2,
activeColor: 1,
useActiveColor: true,
name: 'Face 2',
},
},
{
builder: vtkConvexFaceContextRepresentation,
labels: ['+--', '++-', '+++', '+-+'],
initialValues: {
behavior: Behavior.HANDLE,
pickable: true,
activeScaleFactor: 1.2,
activeColor: 1,
useActiveColor: true,
name: 'Face 3',
},
},
{
builder: vtkConvexFaceContextRepresentation,
labels: ['++-', '-+-', '-++', '+++'],
initialValues: {
behavior: Behavior.HANDLE,
pickable: true,
activeScaleFactor: 1.2,
activeColor: 1,
useActiveColor: true,
name: 'Face 4',
},
},
{
builder: vtkConvexFaceContextRepresentation,
labels: ['-++', '--+', '+-+', '+++'],
initialValues: {
behavior: Behavior.HANDLE,
pickable: true,
activeScaleFactor: 1.2,
activeColor: 1,
useActiveColor: true,
name: 'Face 5',
},
},
{
builder: vtkConvexFaceContextRepresentation,
labels: ['-+-', '++-', '+--', '---'],
initialValues: {
behavior: Behavior.HANDLE,
pickable: true,
activeScaleFactor: 1.2,
activeColor: 1,
useActiveColor: true,
name: 'Face 6',
},
},
];
}
};
}

// ----------------------------------------------------------------------------

const defaultValues = (initialValues) => ({
behavior: widgetBehavior,
widgetState: generateState(),
...initialValues,
});

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, defaultValues(initialValues));

vtkAbstractWidgetFactory.extend(publicAPI, model, initialValues);

vtkInteractiveOrientationWidget(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(
extend,
'vtkInteractiveOrientationWidget'
);

// ----------------------------------------------------------------------------

export default { newInstance, extend };
state.js
import vtkStateBuilder from 'vtk.js/Sources/Widgets/Core/StateBuilder';

export const INITIAL_POINTS = [
[-1, -1, -1], // 0
[-1, 1, -1], // 1
[1, -1, -1], // 2
[1, 1, -1], // 3
[-1, -1, 1], // 4
[-1, 1, 1], // 5
[1, -1, 1], // 6
[1, 1, 1], // 7
];

export function generateState() {
return vtkStateBuilder
.createBuilder()
.addStateFromMixin({
labels: ['handles', '---'],
mixins: ['origin'],
name: 'handle',
initialValues: {
origin: INITIAL_POINTS[0],
},
})
.addStateFromMixin({
labels: ['handles', '-+-'],
mixins: ['origin'],
name: 'handle',
initialValues: {
origin: INITIAL_POINTS[1],
},
})
.addStateFromMixin({
labels: ['handles', '+--'],
mixins: ['origin'],
name: 'handle',
initialValues: {
origin: INITIAL_POINTS[2],
},
})
.addStateFromMixin({
labels: ['handles', '++-'],
mixins: ['origin'],
name: 'handle',
initialValues: {
origin: INITIAL_POINTS[3],
},
})
.addStateFromMixin({
labels: ['handles', '--+'],
mixins: ['origin'],
name: 'handle',
initialValues: {
origin: INITIAL_POINTS[4],
},
})
.addStateFromMixin({
labels: ['handles', '-++'],
mixins: ['origin'],
name: 'handle',
initialValues: {
origin: INITIAL_POINTS[5],
},
})
.addStateFromMixin({
labels: ['handles', '+-+'],
mixins: ['origin'],
name: 'handle',
initialValues: {
origin: INITIAL_POINTS[6],
},
})
.addStateFromMixin({
labels: ['handles', '+++'],
mixins: ['origin'],
name: 'handle',
initialValues: {
origin: INITIAL_POINTS[7],
},
})
.build('orientation', 'name');
}

export default generateState;