PaintWidget

Source

behavior.js
import macro from 'vtk.js/Sources/macros';
import { vec3 } from 'gl-matrix';

export default function widgetBehavior(publicAPI, model) {
model.painting = model._factory.getPainting();

publicAPI.handleLeftButtonPress = (callData) => {
if (!model.activeState || !model.activeState.getActive()) {
return macro.VOID;
}

model.painting = true;
const trailCircle = model.widgetState.addTrail();
trailCircle.set(
model.activeState.get('origin', 'up', 'right', 'direction', 'scale1')
);
publicAPI.invokeStartInteractionEvent();
return macro.EVENT_ABORT;
};

publicAPI.handleMouseMove = (callData) => publicAPI.handleEvent(callData);

publicAPI.handleLeftButtonRelease = () => {
if (model.painting) {
publicAPI.invokeEndInteractionEvent();
model.widgetState.clearTrailList();
}
model.painting = false;
return model.hasFocus ? macro.EVENT_ABORT : macro.VOID;
};

publicAPI.handleEvent = (callData) => {
const manipulator =
model.activeState?.getManipulator?.() ?? model.manipulator;
if (manipulator && model.activeState && model.activeState.getActive()) {
const normal = model._camera.getDirectionOfProjection();
const up = model._camera.getViewUp();
const right = [];
vec3.cross(right, up, normal);
model.activeState.setUp(...up);
model.activeState.setRight(...right);
model.activeState.setDirection(...normal);

const worldCoords = manipulator.handleEvent(
callData,
model._apiSpecificRenderWindow
);

if (worldCoords.length) {
model.widgetState.setTrueOrigin(...worldCoords);
model.activeState.setOrigin(...worldCoords);

if (model.painting) {
const trailCircle = model.widgetState.addTrail();
trailCircle.set(
model.activeState.get(
'origin',
'up',
'right',
'direction',
'scale1'
)
);
}
}

publicAPI.invokeInteractionEvent();
return macro.EVENT_ABORT;
}
return macro.VOID;
};

publicAPI.grabFocus = () => {
if (!model.hasFocus) {
model.activeState = model.widgetState.getHandle();
model.activeState.activate();
model._interactor.requestAnimation(publicAPI);

const canvas = model._apiSpecificRenderWindow.getCanvas();
canvas.onmouseenter = () => {
if (
model.hasFocus &&
model.activeState === model.widgetState.getHandle()
) {
model.activeState.setVisible(true);
}
};
canvas.onmouseleave = () => {
if (
model.hasFocus &&
model.activeState === model.widgetState.getHandle()
) {
model.activeState.setVisible(false);
}
};
}
model.hasFocus = true;
};

publicAPI.loseFocus = () => {
if (model.hasFocus) {
model._interactor.cancelAnimation(publicAPI);
}
model.widgetState.deactivate();
model.widgetState.getHandle().deactivate();
model.activeState = null;
model.hasFocus = false;
};

macro.get(publicAPI, model, ['painting']);
}
index.js
import macro from 'vtk.js/Sources/macros';
import vtkAbstractWidgetFactory from 'vtk.js/Sources/Widgets/Core/AbstractWidgetFactory';
import vtkCircleContextRepresentation from 'vtk.js/Sources/Widgets/Representations/CircleContextRepresentation';
import vtkPlaneManipulator from 'vtk.js/Sources/Widgets/Manipulators/PlaneManipulator';
import vtkSphereHandleRepresentation from 'vtk.js/Sources/Widgets/Representations/SphereHandleRepresentation';

import widgetBehavior from 'vtk.js/Sources/Widgets/Widgets3D/PaintWidget/behavior';
import stateGenerator from 'vtk.js/Sources/Widgets/Widgets3D/PaintWidget/state';

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

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

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

const superClass = { ...publicAPI };

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

publicAPI.getRepresentationsForViewType = (viewType) => {
switch (viewType) {
case ViewTypes.DEFAULT:
case ViewTypes.GEOMETRY:
case ViewTypes.SLICE:
return [
{
builder: vtkCircleContextRepresentation,
labels: ['handle', 'trail'],
},
];
case ViewTypes.VOLUME:
default:
return [{ builder: vtkSphereHandleRepresentation, labels: ['handle'] }];
}
};

// --- Public methods -------------------------------------------------------

publicAPI.setManipulator = (manipulator) => {
superClass.setManipulator(manipulator);
model.widgetState.getHandle().setManipulator(manipulator);
};

// override
const superSetRadius = publicAPI.setRadius;
publicAPI.setRadius = (r) => {
if (superSetRadius(r)) {
model.widgetState.getHandle().setScale1(r);
}
};

// --------------------------------------------------------------------------
// initialization
// --------------------------------------------------------------------------

// Default manipulator
publicAPI.setManipulator(
model.manipulator ||
vtkPlaneManipulator.newInstance({ useCameraNormal: true })
);
}

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

const defaultValues = (initialValues) => ({
// manipulator: null,
radius: 1,
painting: false,
color: [1],
behavior: widgetBehavior,
widgetState: stateGenerator(initialValues?.radius ?? 1),
...initialValues,
});

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

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

vtkAbstractWidgetFactory.extend(publicAPI, model, initialValues);

macro.get(publicAPI, model, ['painting']);
macro.setGet(publicAPI, model, ['manipulator', 'radius', 'color']);

vtkPaintWidget(publicAPI, model);
}

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

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

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

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

export default function generateState(radius) {
return vtkStateBuilder
.createBuilder()
.addField({
name: 'trueOrigin',
initialValue: [0, 0, 0],
})
.addStateFromMixin({
labels: ['handle'],
mixins: [
'origin',
'color',
'scale1',
'orientation',
'manipulator',
'visible',
],
name: 'handle',
initialValues: {
scale1: radius * 2,
orientation: [1, 0, 0, 0, 1, 0, 0, 0, 1],
},
})
.addDynamicMixinState({
labels: ['trail'],
mixins: ['origin', 'color', 'scale1', 'orientation', 'visible'],
name: 'trail',
initialValues: {
scale1: radius * 2,
orientation: [1, 0, 0, 0, 1, 0, 0, 0, 1],
},
})
.build();
}