Skip to content

Application context and focus

Application tags tell Rotatrix what the application is doing, so the user’s profile can select suitable input behavior. A change of workspace, tool or edit operation can change those tags. This applies to Navigation and Axis Streaming.

The demos add object-interaction tags during an edit and remove them when it ends. Their integrations publish application state through OpenAxisConnectionManager.

Tags travel in a metadata snapshot alongside capabilities and focus. Tags describe available interactions; capabilities identify supported protocol features; focus helps Rotatrix select the active recipient. Each update replaces the complete tag set, so persistent tags remain in every snapshot.

The demos use these tag sets:

Application stateComplete tag set
Camera navigation, including a selected objectdemo-3d-services
Active object editdemo-3d-services, interaction.object.rotate, interaction.object.translate
Edit accepted or cancelleddemo-3d-services

Press F in any viewer to add navigation.hint.free_camera to the current tag set, or remove it to return to orbit. The demo-3d-services tag and any edit tags remain present; both camera modes share the same object bindings. See Free camera movement.

The interaction tags authorize rotation and translation; the user’s profile chooses between them. Selection alone does not start an edit. See Object manipulation for native operation ownership.

The metadata provider supplies the latest snapshot on connection and refresh. Application state is read on the thread where the application’s API is valid.

The manager calls MyOpenAxisIntegration._metadata() on startup/reconnect and refresh. It uses self.app to inspect the active edit and focus; _tags() builds the complete tag list for that snapshot:

def _tags(self):
camera_tags = ['navigation.hint.free_camera'] if self.app.free_camera else []
return ['demo-3d-services'] + camera_tags + (['interaction.object.rotate', 'interaction.object.translate'] if self.app.operation else [])
def _metadata(self):
# This viewer runs its GUI and networking on the same asyncio thread.
return ConnectionMetadata(tags=tuple(self._tags()), capabilities=('navigation',),
focused=self.app.has_focus())

The Python demo’s GUI and networking share one asyncio thread, so the provider reads application state directly.

An operation-change event first invalidates navigation bound to the previous edit, then requests a metadata refresh. Focus changes use the same refresh path without changing the operation identity.

During MyOpenAxisIntegration.start(), the existing client and session are retained and update-task tracking is initialized as described in the connection recipe. These hooks are registered before networking starts:

app.on_navigation_changed = self._metadata_changed
app.on_focus_changed = self._metadata_changed
app.on_operation_changed = self._operation_changed

An edit transition calls _operation_changed; a focus transition calls _metadata_changed. Both are bound methods on that integration instance. _metadata_sent is a task completion callback that observes any send error:

def _operation_changed(self):
self.session.context_changed()
self._metadata_changed()
def _metadata_changed(self):
if self.connection.state != ConnectionManagerState.READY:
return # Startup/reconnect reads the latest application state itself.
task = asyncio.create_task(self.connection.refresh_metadata())
self._metadata_tasks.add(task)
task.add_done_callback(self._metadata_sent)
def _metadata_sent(self, task):
self._metadata_tasks.discard(task)
if not task.cancelled() and task.exception() is not None:
# The connection may close during a send; reconnect replays current facts.
logging.getLogger('openaxis.viewer').debug('Metadata refresh interrupted: %s', task.exception())

Application events request refreshes. While offline, the latest application state is retained for the next connection. While connected, OpenAxisConnectionManager serializes announcements. The integration observes refresh failures so a disconnect during an update is reported; reconnect publishes current state.

Focus identifies whether the application is receiving user interaction. It is a separate metadata field, updated when window focus changes. Unrelated events, such as a resize, do not require a metadata refresh.

The application emits on_focus_changed when its window gains or loses focus. The integration routes that event to _metadata_changed(); the provider reads app.has_focus() when producing the snapshot.

OpenAxisConnectionManager reads a fresh snapshot after each successful connection. If the user changes tools or ends an edit while offline, reconnect announces that current state without replaying obsolete transitions.

In C++, setters update desired metadata and the scheduler delivers connection changes.

The other SDKs’ refresh APIs are refresh_metadata() in Python, RefreshMetadataAsync() in C#, and refreshMetadata() in TypeScript. They publish the complete snapshot when ready and do nothing offline. Refresh failures return to the caller.

The connection and shutdown recipe shows how event callbacks and pending updates are cleaned up before the application closes.

  • Start an edit and confirm both interaction tags appear; accept or cancel and confirm they disappear while the profile tag remains.
  • Switch focus and confirm only actual focus changes send a focus update.
  • Leave the application idle and confirm no metadata updates are scheduled.
  • Disconnect, change state, and reconnect. The first snapshot must describe new state.
  • Shut down with an update pending. It must finish or be cancelled before native resources disappear, and detached events must schedule no work.

For automated commands and coverage, use the demo READMEs: Python, C#, TypeScript, and C++.