Skip to content

Custom objects and global dependencies

THREE, Selectable, transform methods, and persistence and export boundaries for custom plugin objects.

A plugin can create custom geometry and explore the editor’s internals. Use the stable editorAPI for built-in operations. The global editor and editor classes provide deeper access, but their implementation may change between versions.

A visible Three.js object, an editable BDEngine object, and a Minecraft entity have different contracts. Adding a mesh to the scene addresses only the first.

THREE and GLTFExporter

The following globals are available when the plugin system initializes:

Global value Purpose
window.editorAPI Public plugin API
window.editor Current editor instance and its internal subsystems
window.THREE Three.js from the editor build
window.THREE.GLTFExporter glTF exporter class added to THREE
window.Selectable Base class for editor objects, extending THREE.Group
window.libs.JSZip JSZip provided by the editor
gT Retrieves translated text by key

Here, GLTFExporter is specifically available as THREE.GLTFExporter; a separate global GLTFExporter is not created. Use the editor’s Three.js instance so that objects, materials, and the exporter use the same version.

console.log('Three.js revision:', THREE.REVISION);
const exporter = new THREE.GLTFExporter();
const zip = new window.libs.JSZip();

Library versions are tied to the BDEngine build. Do not extend editorAPI’s stability guarantees to every internal class or dependency method. Available globals do not necessarily mean that the UI is ready: see Plugin startup order. Dictionary registration and gT are covered in the Translations reference.

Selectable and a custom class

The new Selectable(editor) constructor creates a group with transform methods, selection state, and a link to the editor. It does not create a Minecraft display, load a model, or assign a serializer to a custom type.

The following example creates a temporary visual marker. It is custom Three.js geometry for the current session, not an exportable Minecraft block.

class PreviewMarker extends Selectable {
constructor(editor) {
super(editor);
this.name = 'Plugin marker';
this.markerMesh = new THREE.Mesh(
new THREE.BoxGeometry(0.25, 0.25, 0.25),
new THREE.MeshBasicMaterial({ color: 0xffaa00 })
);
this.add(this.markerMesh);
}
disposePreview() {
if (this.parent) this.delete();
this.markerMesh.geometry.dispose();
this.markerMesh.material.dispose();
}
}

Selectable provides basic selection, but does not include every feature of a built-in type: a custom group does not automatically get an entry in the object panel, Minecraft export, or support from every tool. For that integration, explore the relevant editor subsystem and verify its contract.

Transform methods and selected

The setters and getters below are synchronous. Position, rotation, and scale are local to the parent. By default, setters mark changed geometry, selection frames, and raycast data for an update, but do not create an Undo command, broadcast the transform to Share Party, or update every panel.

The setters have a skipDirty = false parameter. Keep the default in a typical plugin. Setting it to true skips internal change tracking and requires you to manage updates yourself.

setPosition, setRotation, and setScale accept a partial set of axes: an omitted axis or one set to undefined remains unchanged. Supplied values must be finite numbers. Strings, NaN, and infinities throw a ProjectValidationError; invalid input does not apply a partial set of changes.

setPosition

object.setPosition({ x, y, z }, skipDirty = false) changes the local position. Values use BDEngine scene units; for regular displays, one unit corresponds to one Minecraft block. Values are rounded to object.stepPosition (default 0.000625). Returns undefined.

object.setPosition({ x: 2, z: -1 }); // Y stays unchanged.

getPosition

object.getPosition() returns a new plain { x, y, z } object containing rounded local coordinates. Changing this result does not change the original position. The getter itself does not overwrite the position.

const position = object.getPosition();
object.setPosition({ y: position.y + 1 });

setRotation

object.setRotation({ x, y, z }, skipDirty = false) takes degrees, rounds to object.stepRotation (default 0.01), and updates the corresponding Three.js angles. Returns undefined.

object.setRotation({ y: 90 });

By contrast, the standard Three.js object.rotation property contains radians. Do not assign degrees directly to object.rotation.y.

getRotation

object.getRotation() returns { x, y, z } in degrees. If the internal representation has not been created yet, it is calculated from the current rotation.

Unlike getPosition(), this returns a reference to the internal customRotation. Copy it when you need a snapshot. Manually changing this reference does not recalculate the Three.js rotation or notify the editor of a change:

const rotation = { ...object.getRotation() };
object.setRotation({ y: rotation.y + 15 });

Angles may retain accumulated turns. Do not assume that they are always normalized to the range from -180 to 180.

setQuaternionRotation

object.setQuaternionRotation(quaternion, skipDirty = false) copies the quaternion and updates the rotation representation in degrees using the change relative to the previous quaternion. Returns undefined; an empty argument does not change anything.

Pass a normalized THREE.Quaternion. Validation requires finite x/y/z/w components and a nonzero length, but the method does not automatically normalize the assigned quaternion. An invalid quaternion throws a ProjectValidationError.

const quaternion = new THREE.Quaternion().setFromAxisAngle(
new THREE.Vector3(0, 1, 0),
Math.PI / 2
);
object.setQuaternionRotation(quaternion);

The angle in setFromAxisAngle is in radians, while getRotation() still returns degrees. A quaternion describes orientation, but does not itself store the number of complete turns, so do not use it as a replacement for animation keys with multiple revolutions.

setScale

object.setScale({ x, y, z }, skipDirty = false) sets scale factors. Values are rounded to stepScale (default 0.0001) and clamped to a minimum of minScale (default 0.0011). Returns undefined. For a particle, it also updates the scale of its gizmo.

object.setScale({ x: 2, y: 2, z: 2 });

A zero or negative scale does not create a zero size or a reflection: it is raised to the minimum positive value. Supported objects use the separate setMirrorX method for reflection.

getScale

object.getScale() returns a new { x, y, z } object. The getter may change the original object: if the current scale does not meet the rounding or minimum constraints, it calls setScale and updates the local and world matrices. It is therefore not always a read without side effects.

selected

object.selected is the selection state property. Assign a boolean. Enabling it creates a visual frame; disabling it removes that frame. The editor also updates its selection revision.

object.selected = true;
editorAPI.update(true);

The assignment does not deselect other objects, create selection history, or perform the controller’s full selection workflow on its own. After a batch of state changes, use editorAPI.update(true).

Shear, reflection, and matrices

Affine transforms are enabled for objects whose isDisplay or isCollection is true. They are not enabled for a plain custom Selectable subclass. Do not assign these flags just to get the effect: other editor systems expect the corresponding built-in type.

Method Contract
supportsAffineTransform() Returns a boolean indicating whether shear and reflection are supported
getShear() Returns a copy of { xy, xz, yz }, with angles in radians
setShear({ xy, xz, yz }, skipDirty = false, updateMatrix = true) Updates the supplied components. Each must be a finite angle strictly between -Math.PI / 2 and Math.PI / 2; an invalid angle returns false without a change, success returns true
getMirrorX() Returns a boolean indicating reflection along local X
setMirrorX(value, skipDirty = false, updateMatrix = true) Converts the value to a boolean and updates the matrix and internal state according to the flags. Returns undefined
setFromAffineMatrix(matrix, options = {}) Decomposes a THREE.Matrix4 into components and returns the object itself
applyMatrix4(matrix) Premultiplies the local matrix by the supplied matrix and returns the object itself
updateMatrix() Rebuilds the local matrix, including supported shear and reflection; returns undefined

setShear accepts a partial set of axes. Values very close to the boundary are additionally clamped to a magnitude of Math.PI / 2 - 0.000001. setFromAffineMatrix accepts referenceRotation = null (a reference THREE.Euler), skipDirty = false, and updateWorld = true. For an object without affine support, the matrix is decomposed into standard position, quaternion, and scale.

if (object.supportsAffineTransform()) {
object.setShear({ xy: THREE.MathUtils.degToRad(10) });
object.setMirrorX(true);
editorAPI.update(true);
}

Add an object to the scene

Add the instance through editorAPI.addObject. This example uses the PreviewMarker class above:

const marker = new PreviewMarker(editor);
marker.setPosition({ x: 0, y: 1, z: 0 });
editorAPI.addObject(marker);
marker.selected = true;
editorAPI.update(true);
// When the preview is no longer needed:
// marker.disposePreview();
// editorAPI.update(true);

Selectable itself has add(...objects) and remove(...objects), inherited from the group and extended to update the editor’s internal indices. They change the hierarchy and return the parent object, but do not create history. object.delete() deselects descendants and detaches the object from its parent; it returns undefined and requires the parent to exist.

Direct removal in this example is appropriate for a temporary preview. For standard project objects, use the editorAPI deletion operations.

Persistence, export, and resources

The project saves types known to the editor using their own representation. Extending Selectable is not enough to serialize an arbitrary class, load it again, or restore it through Undo. In particular, addObject does not register a format or add a history command.

THREE.Mesh geometry does not automatically become a Minecraft display either. For a custom export format, you can obtain a mesh snapshot and process it with your own logic. Fully integrating a type into the project requires additional editor mechanisms and separate checks for saving and restoring it.

The plugin is responsible for disposing of resources it creates: geometry, materials, its own textures, event handlers, and timers. Removing a DOM window or detaching a Three.js object does not clean up all of them. Dispose only of resources you own; shared editor textures and materials may still be used by other objects.

When to use a built-in object

If the result must remain an editable display and export to Minecraft, start with editorAPI.add for BlockDisplay, ItemDisplay, or TextDisplay. The editor already implements their loading and standard serialization.

Custom geometry is useful for previews, visual tools, and other plugin features where persistence and in-game export are handled separately. For a practical approach, see Create a generator of built-in objects.

Put it into practice