StateBuilder

Provides a builder API to create a vtkWidgetState. It is recommended to use the
builder to construct a vtkWidgetState, unless there is use-case not covered by
vtkStateBuilder.

A builder object is created via vtkStateBuilder.createBuilder(). State is
built via builder.build().

When a sub-state is added, a unique name must be provided. This will act as a
state identifier and make the substate accessible via state.get{NAME}().
Optionally, a list of labels may be provided. Labels categorize sub-states and
group them together. All states associated with a given label can be retrieved
via state.getStatesWithLabel(LABEL).

Mixin sub-states are states constructed from a set of specified mixins. The
resultant sub-state will have all the properties and methods exported by each
mixin.’

Sample usage:

const state = vtkStateBuilder
.createBuilder()
.addStateFromMixin({
labels: ['dragHandle'],
mixins: ['origin', 'color', 'scale1', 'visible'],
name: 'draggingHandle',
initialValues: {
scale1: 0.1,
origin: [1, 2, 3],
visible: false,
}
})
.build();

addStateFromMixin({ labels, mixins, name, initialValues })

Creates a sub-state that mixes in the specified set of mixins. Available mixins
can be found in StateBuilder/index.js.

addDynamicMixinState({ labels, mixins, name, initialValues })

Creates a list of sub-states that are all derived from the mixin list.

addStateFromInstance({ labels, name, instance })

Adds a given vtkWidgetState instance as a sub-state under the given name.

addField({ name, initialValue })

Add a field (with initial value) to the top-level state.

build()

Constructs and returns the vtkWidgetState.

Source

boundsMixin.js
import macro from 'vtk.js/Sources/macro';
import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox';

function vtkBoundsMixin(publicAPI, model) {
const sourceBounds = [];
const bbox = vtkBoundingBox.newInstance();

publicAPI.containsPoint = (x, y, z) => {
if (Array.isArray(x)) {
return bbox.containsPoint(x[0], x[1], x[2]);
}
return bbox.containsPoint(x, y, z);
};

publicAPI.placeWidget = (bounds) => {
model.bounds = [];
for (let i = 0; i < 6; i++) {
sourceBounds[i] = bounds[i];
model.bounds[i] = bounds[i] * model.placeFactor;
}
bbox.setBounds(model.bounds);
publicAPI.invokeBoundsChange(model.bounds);
publicAPI.modified();
};

publicAPI.setPlaceFactor = (factor) => {
if (model.placeFactor !== factor) {
model.placeFactor = factor;
model.bounds = [];
for (let i = 0; i < 6; i++) {
model.bounds[i] = sourceBounds[i] * model.placeFactor;
}
bbox.setBounds(model.bounds);
publicAPI.invokeBoundsChange(model.bounds);
publicAPI.modified();
}
};
}

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

const DEFAULT_VALUES = {
bounds: [-1, 1, -1, 1, -1, 1],
placeFactor: 1,
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGetArray(publicAPI, model, ['bounds'], 6);
macro.get(publicAPI, model, ['placeFactor']);
macro.event(publicAPI, model, 'BoundsChange');

model.bounds = model.bounds.slice();
vtkBoundsMixin(publicAPI, model);
}

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

export default { extend };
colorMixin.js
import macro from 'vtk.js/Sources/macro';

const DEFAULT_VALUES = {
color: 0.5,
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGet(publicAPI, model, ['color']);
}

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

export default { extend };
directionMixin.js
import macro from 'vtk.js/Sources/macro';
import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder';

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

function vtkDirectionMixin(publicAPI, model) {
const transform =
model.angleUnit === 'degree'
? vtkMatrixBuilder.buildFromDegree()
: vtkMatrixBuilder.buildFromRadian();

publicAPI.rotateFromDirections = (originDirection, targetDirection) => {
transform
.identity()
.rotateFromDirections(originDirection, targetDirection)
.apply(model.direction);
publicAPI.modified();
};

publicAPI.rotate = (angle, axis) => {
transform
.identity()
.rotate(angle, axis)
.apply(model.direction);
};

publicAPI.rotateX = (angle) => {
transform
.identity()
.rotateX(angle)
.apply(model.direction);
};

publicAPI.rotateY = (angle) => {
transform
.identity()
.rotateY(angle)
.apply(model.direction);
};

publicAPI.rotateZ = (angle) => {
transform
.identity()
.rotateZ(angle)
.apply(model.direction);
};
}

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

const DEFAULT_VALUES = {
direction: [1, 0, 0],
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGetArray(publicAPI, model, ['direction'], 3);
vtkDirectionMixin(publicAPI, model);
}

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

export default { extend };
index.js
import macro from 'vtk.js/Sources/macro';

import vtkWidgetState from 'vtk.js/Sources/Widgets/Core/WidgetState';

import bounds from 'vtk.js/Sources/Widgets/Core/StateBuilder/boundsMixin';
import color from 'vtk.js/Sources/Widgets/Core/StateBuilder/colorMixin';
import direction from 'vtk.js/Sources/Widgets/Core/StateBuilder/directionMixin';
import manipulator from 'vtk.js/Sources/Widgets/Core/StateBuilder/manipulatorMixin';
import name from 'vtk.js/Sources/Widgets/Core/StateBuilder/nameMixin';
import orientation from 'vtk.js/Sources/Widgets/Core/StateBuilder/orientationMixin';
import origin from 'vtk.js/Sources/Widgets/Core/StateBuilder/originMixin';
import scale1 from 'vtk.js/Sources/Widgets/Core/StateBuilder/scale1Mixin';
import scale3 from 'vtk.js/Sources/Widgets/Core/StateBuilder/scale3Mixin';
import visible from 'vtk.js/Sources/Widgets/Core/StateBuilder/visibleMixin';

const { vtkErrorMacro } = macro;

// ----------------------------------------------------------------------------
// Global type lookup map
// ----------------------------------------------------------------------------

const MIXINS = {
bounds,
color,
direction,
manipulator,
name,
orientation,
origin,
scale1,
scale3,
visible,
};

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

function newInstance(
mixins,
initialValues,
publicAPI = {},
model = {},
skipWidgetState = false
) {
if (!skipWidgetState) {
vtkWidgetState.extend(publicAPI, model, initialValues);
}

for (let i = 0; i < mixins.length; i++) {
const mixin = MIXINS[mixins[i]];
if (mixin) {
mixin.extend(publicAPI, model, initialValues);
} else {
vtkErrorMacro('Invalid mixin name:', mixins[i]);
}
}
macro.safeArrays(model);

return Object.freeze(publicAPI);
}

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

class Builder {
constructor() {
this.publicAPI = {};
this.model = {};

vtkWidgetState.extend(this.publicAPI, this.model);
// The root state should always have the bounds/placeWidget/widgetFactor
bounds.extend(this.publicAPI, this.model);
}

/* eslint-disable no-shadow */
addDynamicMixinState({ labels, mixins, name, initialValues }) {
const listName = `${name}List`;
this.model[listName] = [];
// Create new Instance method
this.publicAPI[`add${macro.capitalize(name)}`] = () => {
const instance = newInstance(mixins, initialValues);
this.publicAPI.bindState(instance, labels);
this.model[listName].push(instance);
this.publicAPI.modified();
return instance;
};
this.publicAPI[`remove${macro.capitalize(name)}`] = (instanceOrIndex) => {
let removeIndex = this.model[listName].indexOf(instanceOrIndex);
if (removeIndex === -1 && instanceOrIndex < this.model[listName].length) {
removeIndex = instanceOrIndex;
}
const instance = this.model[listName][removeIndex];
if (instance) {
this.publicAPI.unbindState(instance);
}
this.model[listName].splice(removeIndex, 1);
this.publicAPI.modified();
};
this.publicAPI[`get${macro.capitalize(name)}List`] = () =>
this.model[listName].slice();
this.publicAPI[`clear${macro.capitalize(name)}List`] = () => {
while (this.model[listName].length) {
const instance = this.model[listName].pop();
if (instance) {
this.publicAPI.unbindState(instance);
}
}
this.publicAPI.modified();
};
return this;
}

addStateFromMixin({ labels, mixins, name, initialValues }) {
const instance = newInstance(mixins, initialValues);
this.model[name] = instance;
this.publicAPI.bindState(instance, labels);
macro.setGet(this.publicAPI, this.model, [name]);
return this;
}

addStateFromInstance({ labels, name, instance }) {
this.model[name] = instance;
this.publicAPI.bindState(instance, labels);
macro.setGet(this.publicAPI, this.model, [name]);
return this;
}

addField({ name, initialValue }) {
if (Array.isArray(initialValue)) {
macro.setGetArray(
this.publicAPI,
this.model,
[name],
initialValue.length
);
} else {
macro.setGet(this.publicAPI, this.model, [name]);
}
this.model[name] = initialValue;
return this;
}

build(...mixins) {
return newInstance(mixins, {}, this.publicAPI, this.model, true);
}
}

// ----------------------------------------------------------------------------
// Public API
// ----------------------------------------------------------------------------

export function createBuilder() {
return new Builder();
}

export default {
createBuilder,
};
manipulatorMixin.js
import macro from 'vtk.js/Sources/macro';

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

function vtkManipulatorMixin(publicAPI, model) {
publicAPI.updateManipulator = () => {
if (model.manipulator) {
const { origin, normal, direction } = model;
const {
setOrigin,
setCenter,
setNormal,
setDirection,
} = model.manipulator;

if (origin && setOrigin) {
setOrigin(origin);
} else if (origin && setCenter) {
setCenter(origin);
}

if (direction && setDirection) {
setDirection(direction);
} else if (direction && !normal && setNormal) {
setNormal(direction);
} else if (normal && setDirection) {
setDirection(normal);
}
}
};
}

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

const DEFAULT_VALUES = {
manipulator: null,
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGet(publicAPI, model, ['manipulator']);
vtkManipulatorMixin(publicAPI, model);
}

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

export default { extend };
nameMixin.js
import macro from 'vtk.js/Sources/macro';

const DEFAULT_VALUES = {
name: '',
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGet(publicAPI, model, ['name']);
}

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

export default { extend };
orientationMixin.js
import macro from 'vtk.js/Sources/macro';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';

function eq(v1, v2) {
return (
v1.length === 3 &&
v2.length === 3 &&
v1[0] === v2[0] &&
v1[1] === v2[1] &&
v1[2] === v2[2]
);
}

function isSame(o, p1, p2, before) {
return eq(o, before.o) && eq(p1, before.p1) && eq(p2, before.p2);
}

// function axis(o, p1, p2) {
// if (o[0] === p1[0] && p1[0] === p2[0]) {
// return 'X';
// }
// if (o[1] === p1[1] && p1[1] === p2[1]) {
// return 'Y';
// }
// if (o[2] === p1[2] && p1[2] === p2[2]) {
// return 'Z';
// }
// return '?';
// }

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

function vtkOrientationMixin(publicAPI, model) {
const previousPoints = { o: [], p1: [], p2: [] };

publicAPI.normalize = () => {
vtkMath.normalize(model.up);
vtkMath.normalize(model.right);
vtkMath.normalize(model.direction);
publicAPI.modified();
};

publicAPI.updateFromOriginRightUp = (o, p1, p2) => {
if (isSame(o, p1, p2, previousPoints)) {
return;
}
previousPoints.o = o.slice();
previousPoints.p1 = p1.slice();
previousPoints.p2 = p2.slice();

model.up = [p2[0] - o[0], p2[1] - o[1], p2[2] - o[2]];
model.right = [p1[0] - o[0], p1[1] - o[1], p1[2] - o[2]];
vtkMath.cross(model.up, model.right, model.direction);
vtkMath.cross(model.direction, model.up, model.right);
publicAPI.normalize();
publicAPI.modified();
};
}

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

const DEFAULT_VALUES = {
up: [0, 1, 0],
right: [1, 0, 0],
direction: [0, 0, 1],
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGetArray(publicAPI, model, ['up', 'right', 'direction'], 3);
vtkOrientationMixin(publicAPI, model);
}

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

export default { extend };
originMixin.js
import macro from 'vtk.js/Sources/macro';

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

function vtkOriginMixin(publicAPI, model) {
publicAPI.translate = (dx, dy, dz) => {
const [x, y, z] = publicAPI.getOriginByReference();
publicAPI.setOrigin(x + dx, y + dy, z + dz);
};
}

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

const DEFAULT_VALUES = {
origin: [0, 0, 0],
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGetArray(publicAPI, model, ['origin'], 3);
vtkOriginMixin(publicAPI, model);
}

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

export default { extend };
scale1Mixin.js
import macro from 'vtk.js/Sources/macro';

const DEFAULT_VALUES = {
scale1: 0.5,
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGet(publicAPI, model, ['scale1']);
}

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

export default { extend };
scale3Mixin.js
import macro from 'vtk.js/Sources/macro';

const DEFAULT_VALUES = {
scale3: [1, 1, 1],
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGetArray(publicAPI, model, ['scale3'], 3);
}

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

export default { extend };
visibleMixin.js
import macro from 'vtk.js/Sources/macro';

const DEFAULT_VALUES = {
visible: true,
};

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

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.setGet(publicAPI, model, ['visible']);
publicAPI.isVisible = publicAPI.getVisible;
}

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

export default { extend };