Transform interactions
Find public page interfaces for value dragging, preview, commit, and cancellation.
Supported operations and target values
Use
webui.interaction.transform
Provides begin → preview → commit/cancel for position, Euler rotation, and scale. It is not a general undo tool for materials, mesh editing, file writes, or arbitrary operations.
export interface WebUITransformTarget {
targetId: string;
propertyPath: WebUITransformPropertyPath;
}export interface WebUITransformUpdate extends WebUITransformTarget {
value: WebUIVector3;
}targetId is an object ID from current scene state, not a display name or invented identifier. propertyPath accepts only location, rotation_euler, and scale. value is an array of three finite absolute numbers, not a mouse-move delta. Rotation uses radians; convert displayed degrees. Other values use the corresponding Blender property units.
Capture initial values when the gesture starts and calculate subsequent updates from that baseline. Do not repeatedly add to preview snapshots. location is the object's local property; world coordinates are not local coordinates for parented objects. If a target is deleted or unavailable, show failure and reread the scene.
begin: start a transform
export interface WebUITransformAPI {
begin(options: WebUITransformBeginOptions): Promise<WebUITransformInteraction>;
drag(pointerEvent: PointerEvent, options: WebUITransformDragOptions): WebUITransformDragGesture;
isSuppressed(targetId: string, propertyPath: WebUITransformPropertyPath): boolean;
}export interface WebUITransformBeginOptions extends WebUIRequestOptions {
interactionId?: string;
targets: readonly WebUITransformTarget[];
requestOptions?: WebUIRequestOptions;
}targets must be nonempty with unique target/property pairs. interactionId is generated when omitted; custom IDs must be unique per operation and not reused while active. requestOptions configures the begin request, with inner requestOptions taking precedence over outer options.
begin returns a handle asynchronously. Await it before update, commit, or cancel; a Promise is not a handle. Target, connection, or state failures can prevent startup. Do not keep displaying an active drag after failure.
export interface WebUITransformInteraction {
readonly interactionId: string;
readonly targets: readonly WebUITransformTarget[];
readonly beginResult: Record<string, WebUIJSONValue> | null;
readonly active: boolean;
/** Accumulates changed target/property values; each send includes every pair updated in this operation. */
update(updates: readonly WebUITransformUpdate[]): boolean;
/** Requires all begin targets, using the accumulated preview when updates is omitted. */
commit(
updates?: readonly WebUITransformUpdate[],
options?: WebUIRequestOptions,
): Promise<WebUITransformCommitResult>;
cancel(options?: WebUIRequestOptions): Promise<WebUITransformCancelResult>;
dispose(): void;
isSuppressed(targetId: string, propertyPath: WebUITransformPropertyPath): boolean;
}update: send a preview, not a commit
handle.update(updates) Returns a boolean synchronously: false after the interaction ends. Invalid arguments may throw synchronously. true means the update was accepted locally, not that the host preview finished.
Each update may contain only changed target/property pairs. The SDK accumulates their latest values throughout the operation and sends the accumulated snapshot. Frequent updates are coalesced, keeping the latest value for each pair. Only targets and properties declared at begin are allowed; the final commit must still cover every target.
Do not call a normal action, start a new interaction, or create an undo step for every pointer move. Show local previews immediately, then reconcile with host results and state.
commit and cancel: finish explicitly
export interface WebUITransformCommitResult {
interactionId: string;
action: 'commit';
targetCount: number;
previewErrors: number;
writeErrors: number;
undoPushed: boolean;
lastPreviewErrorCode?: string;
lastWriteErrorCode?: string;
undoErrorCode?: string;
[extra: string]: WebUIJSONValue | undefined;
}export interface WebUITransformCancelResult {
interactionId: string;
action: 'cancel';
targetCount?: number;
previewErrors?: number;
writeErrors?: number;
cancelled?: boolean;
lastPreviewErrorCode?: string;
lastWriteErrorCode?: string;
[extra: string]: WebUIJSONValue | undefined;
}| Interface or field | Usage and limitations |
|---|---|
commit(updates?, options?) |
Asynchronously commits. Omitted updates use the accumulated preview values, which must already be valid. The final set must cover every target. |
cancel(options?) |
Asynchronously requests restoration of initial values. After completion, cancel may return cancelled:false and does not undo a committed operation. |
dispose() |
Synchronous cleanup attempts to cancel active interactions but returns no awaitable restoration result. Use await cancel() when you need the result. |
active |
Indicates that the handle can still accept updates. Sending commit ends local updates; it does not prove successful host writes. |
Check previewErrors, writeErrors, and undoErrorCode. undoPushed:false is not necessarily failure, but does not prove an undo step was created. Cancellation may fail to restore deleted objects or after disconnect; do not always display Restored.
function showCommitResult(result, output) {
const failed = result.previewErrors > 0 || result.writeErrors > 0 || Boolean(result.undoErrorCode);
output.textContent = failed
? "变换或撤销有错误,请核对场景。"
: result.undoPushed
? "变换已确认。"
: "操作已结束,没有新增撤销记录,请核对场景。";
}After a rejected or timed-out commit, do not retry automatically or pretend another cancel guarantees rollback. Reread actual state and let the user decide.
drag: manage a pointer gesture
export interface WebUITransformDragOptions {
interactionId?: string;
element?: EventTarget & {
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
setPointerCapture?(pointerId: number): void;
releasePointerCapture?(pointerId: number): void;
};
targets: readonly WebUITransformTarget[];
requestOptions?: WebUIRequestOptions;
beginOptions?: WebUIRequestOptions;
commitOptions?: WebUIRequestOptions;
cancelOptions?: WebUIRequestOptions;
updates(event: PointerEvent, context: WebUITransformDragContext): readonly WebUITransformUpdate[] | WebUIVector3;
onPreview?(updates: readonly WebUITransformUpdate[], meta: WebUITransformPreviewMeta): void;
onCommit?(result: WebUITransformCommitResult, meta: { interactionId: string }): void;
onCancel?(result: WebUITransformCancelResult, meta: { phase: string; interactionId: string }): void;
onError?(error: WebUIError, meta: { phase: string; interactionId: string }): void;
}export interface WebUITransformDragGesture {
readonly interactionId: string;
readonly active: boolean;
readonly ready: Promise<WebUITransformInteraction>;
cancel(): void;
}Call drag synchronously in a real pointerdown handler and pass the original PointerEvent. Do not await before reading currentTarget. element defaults to the initiating control, or can be explicit. Use touch-action:none and avoid overlapping window drag regions.
| Interface or field | Usage and limitations |
|---|---|
targets |
Fixes targets for this gesture; updates must remain within them. |
updates(event, context) |
Compute absolute values from starting values and pointer movement; a single target may return a three-number array. context.phase is start, move, or end. Start and move may return only changed pairs. An explicit end list must cover all targets; an empty array reuses accumulated previews that already cover every target. |
onPreview |
For immediate page previews, possibly before host writes complete. Not a success notification. |
onCommit / onCancel |
Called after asynchronous completion; still check the result. |
onError |
Handles begin, move, commit, and cancel errors with visible feedback. |
requestOptions / beginOptions |
requestOptions takes precedence for begin when present. Use beginOptions for begin-only customization. |
commitOptions / cancelOptions |
Customize the two completion requests separately; otherwise they use requestOptions. |
ready / cancel() |
ready is the begin Promise, not completion of the whole gesture. cancel() returns void; observe results in callbacks. |
The helper manages pointer movement, release-to-commit, pointercancel, Esc cancellation, and listener cleanup. Your code still removes its pointerdown listener and cancels the current gesture on unmount. Do not add a second release-to-commit handler.
Example: drag a control to change X position
This binding function uses an existing control. readTarget supplies the current subscribed
{id, location: [x, y, z]}, or null with no active object. Each press fixes the target and initial position. showCommitResult is from the previous section.
function bindPositionDrag(control, output, readTarget) {
const webui = window.acquireNexusWebUI();
let gesture = null;
let busy = false;
let disposed = false;
function report(error) {
if (!disposed) output.textContent = `${error.code || "变换失败"}:${error.message}`;
}
function start(event) {
if (event.button !== 0 || busy || disposed) return;
try {
const target = readTarget();
if (!target) {
output.textContent = "请先选择一个对象。";
return;
}
const initial = [...target.location];
const startX = event.clientX;
busy = true;
gesture = webui.interaction.transform.drag(event, {
element: control,
targets: [{targetId: target.id, propertyPath: "location"}],
updates(pointer) {
return [initial[0] + (pointer.clientX - startX) * 0.01, initial[1], initial[2]];
},
onCommit(result) {
busy = false;
if (!disposed) showCommitResult(result, output);
},
onCancel(result) {
busy = false;
if (!disposed) output.textContent = result.writeErrors > 0
? "恢复有错误,请核对场景。"
: "取消已处理,请核对当前位置。";
},
onError(error) {
// Keep the control locked until this failed gesture is unmounted.
report(error);
},
});
gesture.ready.catch(report);
} catch (error) {
report(error);
}
}
control.addEventListener("pointerdown", start);
return () => {
disposed = true;
control.removeEventListener("pointerdown", start);
if (gesture?.active) gesture.cancel();
};
}The 0.01 position units per pixel is a chosen sensitivity, not an SDK constant. Keep busy during commit so a second gesture cannot overwrite a pending one. After failure, reread the scene and remount instead of blindly writing again. The example uses only the left button; your controls may choose another input design.
isSuppressed: prevent stale snapshots overwriting drag values
webui.interaction.transform.isSuppressed(targetId,
propertyPath)
Checks whether a target property remains locally suppressed during interaction. It does not stop rendering for you; subscription callbacks must skip actively manipulated fields.
if (!webui.interaction.transform.isSuppressed(item.id, "location")) {
renderLocation(item.location);
}Other objects and unrelated fields can update normally. After commit, reconcile with an authoritative snapshot. Suppression ending does not prove all operations succeeded; check commit results. Do not use isSuppressed as a substitute for permission or target-validity checks.
Cleanup and real-scene verification
Manual begin flows must handle cancellation, unmount, and unmount while begin is pending. Cancel late handles even after the component is gone. Cancelling a drag before begin finishes waits for startup and then follows the cancellation path.
Disable controls after disconnect. dispose is not evidence that the scene was restored. Manage only gestures owned by the component; do not destroy the page SDK on control unmount.
- After release, displayed and actual values agree. Remounting does not duplicate gesture handling.
- Esc, pointer cancellation, and closing the control end the preview.
- Multi-target results are complete. Check actual undo/redo records after commit.
- Deletion or connection failures show errors rather than success.
Verify these operations and drag feel in real Blender. Browser previews cannot replace this check. For the complete workflow, see Transform interaction tutorial.