Process selected objects
Apply a controlled operation to selected objects and handle empty, mixed and nested selections.
An operation on a selection starts by defining its scope: which types are supported, how to handle groups, and how to avoid modifying a parent and its child twice. In this example, we will move ordinary displays and groups along the local X axis.
The operation and its boundaries
Download the complete selection.js plugin. Install it, restart the editor and open a copy of a test project. Move selection along local X will appear in the dock menu.
The example targets ordinary modeling mode outside a shared session. It accepts Block Display, Item Display, Text Display and ordinary groups. The project root, real block structures and utility objects are outside the operation’s scope.
Direct changes through setPosition in this example do not create an Undo entry.
The plugin explains this before asking for a value. Use a test copy and do not expect
Ctrl+Z to undo the movement. If an exception occurs, the handler attempts to restore
the coordinates of objects it already started modifying. This is local recovery,
not an editor transaction.
The stable editorAPI handles reading the selection and refreshing the interface.
The example also reads the project root, mode flags and collaboration state through
the internal editor, plus type flags and hierarchy information on objects.
This depends on the current implementation and must be checked when updating.
Read the selection
const selected = [...new Set(editorAPI.getSelectedObjects())];The method returns an array of scene objects. An empty array is a valid result.
Taking a snapshot of the array does not freeze the objects themselves: if your operation
uses await, check the project, object existence and operation requirements again afterwards.
This example changes coordinates synchronously.
Do not blindly call transform methods on every element you find. First filter for supported types and check the required methods. Then keep only the top-level objects within the chosen scope:
const accepted = new Set(compatible);const roots = compatible.filter(object => { for (let parent = object.parent; parent; parent = parent.parent) { if (accepted.has(parent) || parent.isStructureGroup) return false; } return true;});compatible in this snippet contains the already filtered objects from the complete example.
If a group and its child display are selected, only the group is moved; the child moves
with it and does not receive a second offset. If only child displays are selected,
they are processed individually. Unsupported elements do not expand the operation’s scope.
For property-based lookup, editorAPI.find(key, value) searches the entire project.
Do not substitute it for reading the selection without changing what the tool is meant to do.
See the objects and selection contracts.
Apply a supported change
The offset is entered in blocks: 0.25 means a quarter of a block along local X.
The local axis belongs to the object’s parent coordinate system. In a rotated or scaled group,
equal local offsets do not produce equal world-space offsets.
Check for nonempty input, a finite number and a value from -16 to 16; reject zero. This range belongs to the example, not to a BDEngine coordinate limit. Prepare the plan for all objects before changing the first one:
const before = {...object.getPosition()};const after = {...before, x: before.x + offset};if (![after.x, after.y, after.z].every(Number.isFinite)) { throw new Error('Invalid coordinates.');}object.setPosition(after);object.updateMatrix();object.updateMatrixWorld(true);getPosition() returns local coordinates. setPosition validates finite values and
rounds them to the object’s step size. Omitted axes are unchanged, but the example
keeps all three coordinates for possible recovery.
For rotation, getRotation and setRotation use degrees. The native Three.js
rotation property uses radians; do not pass values between these interfaces without
converting them. This example does not change rotation or scale.
See object properties and units.
Refresh the editor state
After changing all target objects, call:
editorAPI.update(true);The method refreshes the editor; true also updates the controller’s temporary state
from the current selection. It is not a history command, a general transaction or a way
to send arbitrary changes to other participants in a shared session.
Use object methods and update their matrices so changes display and save correctly.
Assigning object.position.x directly bypasses some of the object’s logic.
For a production tool, design Undo and network behavior separately if needed;
an additional update() call does not provide them.
If your tool deletes objects, ask for confirmation first and check for a nonempty list:
if (targets.length > 0 && confirm(`Delete ${targets.length} objects?`)) { editorAPI.delete(targets);}In the current implementation, delete([]) falls back to the current selection.
Do not pass an empty filtered result expecting it to do nothing.
The downloadable example does not delete anything.
Test errors and history behavior
- Run the tool with nothing selected. It should explain that there are no suitable objects.
- Select an ordinary display and enter
0.25. Only local X should change. - Select a display together with a utility object. Only the supported type should be moved.
- Test a group, a group together with a child, and two individual children. Selecting “group + child” must not move the child twice.
- Cancel the input, then try an empty string, text, 0 and an out-of-range value. The project must remain unchanged.
- Repeat inside a rotated group. Evaluate the result using local coordinates, rather than the direction of the world axis on screen.
- Run the tool again: each successful run adds the chosen offset once more. Make sure the tool does not advertise Ctrl+Z as a way to undo its changes.
After testing, save and reopen the project copy. For repeat runs and error diagnosis, see checking a plugin before release.