2D navigation
Use viewspace.2d for a drawing, diagram, image, or canvas whose camera can pan
and zoom but cannot rotate. OpenAxis computes the navigation; your application
provides a camera snapshot and applies the resulting pose.
Try the canvas demo
Section titled “Try the canvas demo”Open 2D View Navigation. It displays a workflow diagram using Canvas 2D. Left-drag to pan, use the wheel to zoom around the cursor, and press R to reset. Fractional high-resolution wheel input is preserved.
With Rotatrix running, the demo selects the user’s shared navigation profile through its navigation capability. Hold Win+Shift on Windows, Ctrl+Shift on macOS, Super+Shift on Linux, or btn4 to activate pan and zoom with the default mappings. The Rotatrix OSD shows the active mappings. The viewport stays flat: it never tilts or rolls.
From the OpenAxis checkout, run:
pnpm installpnpm -C ts/sdk buildpnpm -C examples/demos devVisit http://localhost:5188/demos/viewspace-2d.html.
The implementation is examples/demos/lib/viewspace-2d.ts; its entry page is
examples/demos/viewspace-2d.html. This focused example uses TypeScript; Python,
C# and C++ integrations use the same camera facts and wire contract. C++ maps
them through NavigationAdapter and native scheduler dispatch.
Describe a 2D viewport
Section titled “Describe a 2D viewport”Publish these tags through the connection lifecycle metadata:
metadata: () => ({ tags: ['demo-3d-services', 'viewspace.2d'], capabilities: ['navigation'],})demo-3d-services identifies the demo; Rotatrix selects the user’s navigation
profile from its capabilities and context. Device instructions describe Rotatrix’s
customizable default mappings, not SDK or demo bindings. viewspace.2d describes the
application’s actual viewport restriction, not its workspace name. The current
server’s 2D pan/zoom path uses orbit navigation, so this demo does not publish
the free-camera preference tag.
An orthographic 3D view that can still orbit should use viewspace.3d.
When switching between genuinely 2D and 3D viewports, invalidate the old context
and refresh the complete metadata snapshot. See
Application context and focus.
Supply camera and viewport facts
Section titled “Supply camera and viewport facts”Return ortho_extent, the visible vertical span in drawing units, and
viewport.aspect, the viewport width divided by its height. Do not return fov.
The demo uses world X right, Y up, and a camera looking along negative Z, with
rotation vector [0, 0, 0]. Other fixed orientations are supported: the camera
orientation defines the view plane. No sketch.plane, geometry picking, or
physical drawing-plane depth is required.
Optionally return viewport.cursor in normalized coordinates: X from −1 at the
left to +1 at the right, Y from −1 at the bottom to +1 at the top. Return
UNAVAILABLE outside the viewport. The server anchors zoom at the supplied
cursor, falling back to the viewport center.
Capture pose, aspect, and cursor together so a query sees a consistent snapshot. This adapter comes directly from the runnable demo:
export class CanvasNavigationAdapter implements NavigationAdapter<CanvasView> { constructor(private view: CanvasView) {} captureContext() { return this.view.alive ? this.view : undefined; } isCurrent(context: CanvasView) { return this.view.alive && context === this.view; } beginQuery() { const pose = this.view.read(); const aspect = this.view.width / this.view.height; const cursor = this.view.cursor && { ...this.view.cursor }; return { resolve(name: string): unknown { switch (name) { case 'document.id': return 'workflow-diagram'; case 'world.orientation': return { forward: [0, 0, -1], up: [0, 1, 0], handedness: 'right' }; case 'camera.pose': return pose; case 'viewport.aspect': return aspect; case 'viewport.cursor': return cursor ?? UNAVAILABLE; default: return UNAVAILABLE; } } }; } applyPose(context: CanvasView, pose: CameraPoseValue, _navigation?: NavigationStateMessage, pivot?: Vec3) { if (!this.isCurrent(context) || !context.write(pose, pivot)) return { success: false }; return { success: true, realizedPose: context.read() }; }}Map the pose to a canvas
Section titled “Map the pose to a canvas”For viewport height height, the drawing scale is:
const pixelsPerUnit = height / camera.ortho_extent;const screenX = width / 2 + (worldX - camera.t[0]) * pixelsPerUnit;const screenY = height / 2 - (worldY - camera.t[1]) * pixelsPerUnit;The minus sign converts world Y up to canvas Y down. Use CSS pixels for viewport
and pointer calculations, then scale the backing canvas by devicePixelRatio
for sharp rendering. Do not mix backing-buffer pixels into the aspect or cursor.
The demo limits the vertical span to 2–80000 drawing units and reports the realized pose after that clamp. The SDK can reconcile it with the server and reverse smoothly at a limit. Preserve the camera depth supplied by OpenAxis; for this orthographic renderer it does not change the drawing scale.
Keep native input and lifecycle in sync
Section titled “Keep native input and lifecycle in sync”Mouse pan, wheel zoom, and reset modify the same camera state used by the adapter,
then call session.nativeCameraChanged(). They participate in SDK reconciliation
instead of racing a separate camera.
On resize, invalidate the current context and clear the cached cursor because
the projection changed. On blur, cancel dragging and invalidate navigation.
The shared FocusManager wraps OpenAxisConnectionManager to publish focus,
reconnect, and handle page suspension. On final shutdown, stop that lifecycle
before closing and draining the Navigation session.
See Concurrent input and Connection shutdown for the full patterns.