Skip to content

Commit 6c15e9f

Browse files
committed
sync orientation widget to server
1 parent b359c8f commit 6c15e9f

6 files changed

Lines changed: 106 additions & 2 deletions

File tree

‎doc/changelog.d/143.added.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Remote rendering 3.3d] sync camera to server on orientation widget

‎src/ansys/visor/visor-client/src/jest-tests/CameraGestureTracker.test.js‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,23 @@ describe('CameraGestureTracker', () => {
211211
expect(onSettled).toHaveBeenCalledWith('programmatic');
212212
});
213213

214+
// ---- the orientation widget's mark --------------------------------------
215+
216+
test('a camera event followed by the widget mark within 300 ms reports gesture', () => {
217+
// The widget's mark can arrive after the camera events it belongs
218+
// to, so this exercises the retroactive branch of noteWidgetGesture.
219+
// The mark-first order is covered by the wheel test above, via the
220+
// same #markImpulse code path.
221+
tracker.noteCameraEvent();
222+
jest.advanceTimersByTime(299);
223+
224+
tracker.noteWidgetGesture();
225+
jest.advanceTimersByTime(300);
226+
227+
expect(onSettled).toHaveBeenCalledTimes(1);
228+
expect(onSettled).toHaveBeenCalledWith('gesture');
229+
});
230+
214231
// ---- listener management and teardown ----------------------------------
215232

216233
test('the remover returned by addSettledListener stops reports', () => {

‎src/ansys/visor/visor-client/src/jest-tests/WasmRendererWidgetTriggers.test.tsx‎

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,14 @@ function makeFakeWasmObjects() {
103103
SetOrigin: jest.fn(async () => undefined),
104104
SetNormal: jest.fn(async () => undefined),
105105
};
106-
return { actor, property, widget };
106+
// The orientation widget is its own double, not the shared `widget`
107+
// above. Both are observed on `EndInteractionEvent`, and one double for
108+
// both cannot tell those two registrations apart: the plane's own test
109+
// asserts exactly one such registration, and would see two.
110+
const orientationWidget = {
111+
observe: jest.fn(),
112+
};
113+
return { actor, property, widget, orientationWidget };
107114
}
108115

109116
/**
@@ -137,13 +144,19 @@ async function makeRenderer(
137144
canvasDiv: document.createElement('div'),
138145
render: jest.fn(),
139146
clearObserversAndEventListeners: jest.fn(),
147+
// The tracker itself is `#private` to VtkScene, so what the renderer
148+
// can reach is this one passthrough, and this is what the orientation
149+
// registration is pinned against.
150+
noteWidgetGesture: jest.fn(),
140151
camera,
141152
getVtkObject: (wasmId: number) => {
142153
switch (wasmId) {
143154
case ACTOR_ID:
144155
return objects.actor;
145156
case PROPERTY_ID:
146157
return objects.property;
158+
case ORIENTATION_WIDGET_ID:
159+
return objects.orientationWidget;
147160
default:
148161
return objects.widget;
149162
}
@@ -156,7 +169,7 @@ async function makeRenderer(
156169
);
157170
const sceneGraph = makeSceneGraphDouble(actorIds);
158171
renderer.attachSceneGraph(sceneGraph as unknown as VisorSceneNodeExtended);
159-
return { renderer, camera, sceneGraph, ...objects };
172+
return { renderer, camera, scene, sceneGraph, ...objects };
160173
}
161174

162175
/** A sender that records its calls and resolves. */
@@ -336,3 +349,28 @@ describe('WasmRenderer reports the cross-section plane on the end-of-drag event'
336349
expect(sender).not.toHaveBeenCalled();
337350
});
338351
});
352+
353+
describe('WasmRenderer marks an orientation-widget move as a gesture', () => {
354+
// As above, the event does not exist under jsdom, so what is pinned is
355+
// the *registration*: which event the mark is bound to, that there is
356+
// exactly one of it on the orientation widget, and that the callback
357+
// marks and sends nothing. Whether the wasm widget invokes that event at
358+
// all is MC-I6's subject and no gate reaches it.
359+
test('the orientation widget is observed once on EndInteractionEvent and the callback marks a widget gesture', async () => {
360+
const sender = makeSender();
361+
const { scene, orientationWidget } = await makeRenderer(sender);
362+
363+
const endCalls = orientationWidget.observe.mock.calls.filter(
364+
(call) => call[0] === 'EndInteractionEvent'
365+
);
366+
expect(endCalls).toHaveLength(1);
367+
expect(scene.noteWidgetGesture).not.toHaveBeenCalled();
368+
369+
await endCalls[0][1]();
370+
371+
expect(scene.noteWidgetGesture).toHaveBeenCalledTimes(1);
372+
// The mark carries no payload and triggers no send: the report stays
373+
// the settle's, through the unchanged sync_camera path.
374+
expect(sender).not.toHaveBeenCalled();
375+
});
376+
});

‎src/ansys/visor/visor-client/src/renderer/WasmRenderer.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,22 @@ export class WasmRenderer implements IRenderer {
9696
});
9797
});
9898

99+
/**
100+
* Attribute an orientation-widget camera move to the user.
101+
*
102+
* Clicking a face of the cube moves the camera entirely inside wasm,
103+
* with no DOM input `CameraGestureTracker` can see, so without this
104+
* mark the move settles as `programmatic` and the server drops it.
105+
* `EndInteractionEvent` (not `InteractionEvent`) fires once per
106+
* interaction rather than per animation frame. The callback only
107+
* marks the gesture; the report itself is still the settle's,
108+
* unchanged, via `sync_camera`.
109+
*/
110+
const orientationWidget = vtkScene.getVtkObject(annotation.widgets.orientationWidgetId);
111+
orientationWidget.observe('EndInteractionEvent', () => {
112+
vtkScene.noteWidgetGesture();
113+
});
114+
99115
// Bounding-box ids are stashed for attachSceneGraph, which is the
100116
// point at which the live sceneGraph (needed by BoundingBoxWidget)
101117
// becomes available.

‎src/ansys/visor/visor-client/src/wasm/CameraGestureTracker.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
* the camera event can arrive before the tracker has noticed the input.
2626
* To cover that, a wheel event or a z/r keydown arriving while a report is
2727
* pending marks that report `gesture` as well.
28+
*
29+
* - The orientation widget is not a DOM input at all: its face click reaches
30+
* the camera inside wasm. It is marked explicitly, through
31+
* `noteWidgetGesture`, from the observer `WasmRenderer` registers on the
32+
* widget's `EndInteractionEvent`.
2833
*/
2934

3035
/**
@@ -190,6 +195,22 @@ export default class CameraGestureTracker {
190195
this.#settleTimer = setTimeout(this.#reportSettled, CAMERA_SETTLE_MS);
191196
};
192197

198+
/**
199+
* The orientation widget's end-of-interaction mark, called via
200+
* `VtkScene.noteWidgetGesture` from the `EndInteractionEvent` observer in
201+
* `WasmRenderer`. A face click involves no button, wheel or z/r key, so
202+
* without this mark the move settles as `programmatic`.
203+
*
204+
* Delegates to `#markImpulse` to reuse the same window and retroactive
205+
* stickiness as a wheel notch or z/r press, so a mark landing before or
206+
* after the camera events it belongs to is still caught.
207+
*
208+
* @return {void}
209+
*/
210+
noteWidgetGesture = () => {
211+
this.#markImpulse();
212+
};
213+
193214
/**
194215
* @return {void}
195216
*/

‎src/ansys/visor/visor-client/src/wasm/VtkScene.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@ export default class VtkScene {
159159
addCameraSettledListener = (handler) => {
160160
return this.#cameraGestureTracker.addSettledListener(handler);
161161
};
162+
/**
163+
* Mark the settle window now open as a user gesture, on behalf of a wasm
164+
* widget (the orientation widget) whose interaction is not a DOM input
165+
* this scene can see. `WasmRenderer` calls this from its
166+
* `EndInteractionEvent` observer, since `#cameraGestureTracker` is private.
167+
*
168+
* @return {void}
169+
*/
170+
noteWidgetGesture = () => {
171+
this.#cameraGestureTracker?.noteWidgetGesture();
172+
};
162173
/**
163174
* @param {(actorId:number,ctrlKey:boolean,shiftKey:boolean,normX:number,normY:number)=>void} handler
164175
* @return {()=>void}

0 commit comments

Comments
 (0)