Skip to content

Commit e491b1b

Browse files
committed
feat: fold per-part edge visibility into the global toggle
1 parent 9844980 commit e491b1b

9 files changed

Lines changed: 54 additions & 52 deletions

File tree

‎src/ansys/visor/viewer/renderer/base.py‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,6 @@ def apply_diffuse_color(
105105
) -> None:
106106
"""Set the actor-property diffuse colour for *node_id*."""
107107

108-
@abstractmethod
109-
def apply_edge_visibility(self, node_id: int, edge_visible: bool) -> None:
110-
"""Toggle edge / wireframe visibility for *node_id*."""
111-
112108
@abstractmethod
113109
def apply_selected(
114110
self, node_id: int, selected: bool, diffuse_rgb: list

‎src/ansys/visor/viewer/renderer/local_renderer.py‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,6 @@ def apply_diffuse_color(
204204
return
205205
pipe.set_diffuse_color(r, g, b)
206206

207-
def apply_edge_visibility(self, node_id: int, edge_visible: bool) -> None:
208-
"""No-op in Story 1.2. Phase 3 populates."""
209-
210207
def apply_selected(
211208
self, node_id: int, selected: bool, diffuse_rgb: list
212209
) -> None:

‎src/ansys/visor/viewer/renderer/null_renderer.py‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,6 @@ def apply_opacity(self, node_id: int, opacity: float) -> None:
7979
def apply_diffuse_color(self, node_id: int, r: float, g: float, b: float) -> None:
8080
pass
8181

82-
def apply_edge_visibility(self, node_id: int, edge_visible: bool) -> None:
83-
pass
84-
8582
def apply_selected(self, node_id: int, selected: bool, diffuse_rgb: list) -> None:
8683
pass
8784

‎src/ansys/visor/visor-client/src/components/ui-panels/Panel_BottomMiddle.tsx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC, RefObject, useEffect, useRef } from 'react';
1+
import { FC, useEffect } from 'react';
22
import { AwcIcons } from '../AwcIcons';
33
import { VisorFrontend } from '../../VisorFrontend.tsx';
44
import { makeTooltip } from '../../utils/Tooltip';
@@ -10,7 +10,6 @@ export const Panel_BottomMiddle: FC<{
1010
visorState: VisorFrontend;
1111
onLoad: (util: Panel_BottomMiddle_Util) => void;
1212
}> = ({ visorState, onLoad }) => {
13-
const edgeVisibility: RefObject<boolean> = useRef(false);
1413
const tableElemId = `tableElem-${crypto.randomUUID()}`;
1514
const selectionModeContainerId = `selMode-${crypto.randomUUID()}`;
1615
useEffect(() => {

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

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ const BOUNDING_BOX_ALGORITHM_ID = 205;
4545
const BOUNDING_BOX_OUTLINE_ID = 206;
4646
const BOUNDING_BOX_AXES_ID = 207;
4747

48+
/** Three actor node ids for the edge fan-out. Hand-written literals. */
49+
const FAN_OUT_ACTOR_IDS = [11, 12, 13];
50+
4851
/** What the wasm camera reports for `GetParallelProjection()`. */
4952
const PARALLEL = 1;
5053
const PERSPECTIVE = 0;
@@ -102,21 +105,23 @@ function makeFakeWasmObjects() {
102105
/**
103106
* A scene-graph stand-in for `attachSceneGraph`.
104107
*
105-
* `EdgesWidget` fans out through the graph node's own
106-
* `setEdgeVisibilityAsync`; `BoundingBoxWidget` enumerates
107-
* `descendantActorNodesOrSelfArray` when recomputing bounds. Neither is the
108-
* subject here -- what is pinned is the *send* that follows the local write.
108+
* `EdgesWidget` enumerates `descendantActorNodesOrSelfArray` and calls the
109+
* renderer's own per-actor `setEdgeVisibilityAsync` for each node's `id`;
110+
* `BoundingBoxWidget` enumerates the same array when recomputing bounds.
111+
* Neither is the subject of the send tests -- what those pin is the *send*
112+
* that follows the local write, and they pass an empty actor list so the
113+
* fan-out reaches nothing.
109114
*/
110-
function makeSceneGraphDouble() {
115+
function makeSceneGraphDouble(actorIds: number[] = []) {
111116
return {
112-
setEdgeVisibilityAsync: jest.fn(async () => undefined),
113-
descendantActorNodesOrSelfArray: [],
117+
descendantActorNodesOrSelfArray: actorIds.map((id) => ({ id })),
114118
};
115119
}
116120

117121
async function makeRenderer(
118122
sender: TrameTriggerSender | null,
119-
parallelProjection: number = PERSPECTIVE
123+
parallelProjection: number = PERSPECTIVE,
124+
actorIds: number[] = []
120125
) {
121126
const objects = makeFakeWasmObjects();
122127
const camera = {
@@ -145,7 +150,7 @@ async function makeRenderer(
145150
makeAnnotation(),
146151
sender
147152
);
148-
const sceneGraph = makeSceneGraphDouble();
153+
const sceneGraph = makeSceneGraphDouble(actorIds);
149154
renderer.attachSceneGraph(sceneGraph as unknown as VisorSceneNodeExtended);
150155
return { renderer, camera, sceneGraph, ...objects };
151156
}
@@ -241,6 +246,32 @@ describe('WasmRenderer widget sends: no sender, and a failing sender', () => {
241246
});
242247
});
243248

249+
describe('WasmRenderer edge fan-out reaches every actor on every call', () => {
250+
test('setEdgeVisibilityGlobalAsync issues one per-actor call per actor on every call, not only when the value changes', async () => {
251+
// The per-node cache that D1 deleted lived on the scene-graph node
252+
// and had no invalidation, so a second toggle to the same value was
253+
// a no-op and three of the six pairs below were absent. This is an
254+
// id set with its value, not a call count: a count would not say
255+
// which actors were reached.
256+
const { renderer } = await makeRenderer(makeSender(), PERSPECTIVE, FAN_OUT_ACTOR_IDS);
257+
const perActor = jest.spyOn(renderer, 'setEdgeVisibilityAsync');
258+
259+
await renderer.setEdgeVisibilityGlobalAsync(true);
260+
await renderer.setEdgeVisibilityGlobalAsync(true);
261+
262+
expect(perActor.mock.calls).toEqual([
263+
[11, true],
264+
[12, true],
265+
[13, true],
266+
[11, true],
267+
[12, true],
268+
[13, true],
269+
]);
270+
271+
perActor.mockRestore();
272+
});
273+
});
274+
244275
describe('WasmRenderer.createAsync seeds the orthographic flag from the wasm camera', () => {
245276
test('isOrthographicEnabled is true when the wasm camera reports parallel, before any setter runs', async () => {
246277
const { renderer } = await makeRenderer(makeSender(), PARALLEL);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class WasmRenderer implements IRenderer {
131131
boxAlgorithmWasmId: this.#boundingBoxBoxAlgorithmWasmId,
132132
outlineQueryId: this.#wasmBoundingBoxActorId,
133133
});
134-
this.#edgesWidget = new EdgesWidget(sceneGraph);
134+
this.#edgesWidget = new EdgesWidget(sceneGraph, this);
135135
}
136136

137137
async renderAsync(): Promise<void> {

‎src/ansys/visor/visor-client/src/state/VisorSceneGraph.tsx‎

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export type VisorSceneNodeExtended = Readonly<{
3131
diffuseRgb: Readonly<number[]>;
3232
visible: boolean;
3333
selected: boolean;
34-
edgeVisibility: boolean;
3534
opacity: number;
3635
descendantsOrSelfArray: VisorSceneNodeExtended[];
3736
descendantsOrSelfDictionary: Record<string, VisorSceneNodeExtended>;
@@ -44,7 +43,6 @@ export type VisorSceneNodeExtended = Readonly<{
4443
setDiffuseColorRgbAsync: (r: number, g: number, b: number) => Promise<void>;
4544
setVisibilityAsync: (visible: boolean) => Promise<void>;
4645
setSelectedAsync: (selected: boolean) => Promise<void>;
47-
setEdgeVisibilityAsync: (edgeVisibility: boolean) => Promise<void>;
4846
setOpacityAsync: (opacity: number) => Promise<void>;
4947
clearColorVariableAsync: () => Promise<void>;
5048
setColorVariableAsync: (id: string, component?: number | null) => Promise<void>;
@@ -99,7 +97,6 @@ export const CreateVisorSceneGraph = (() => {
9997
}
10098
let _visible: boolean = true;
10199
let _selected: boolean = false;
102-
let _edgeVisibility: boolean = false;
103100
let _opacity: number = 1;
104101
let _spectrumId: string | null = null;
105102
let _spectrumComponent: number = -1;
@@ -146,9 +143,6 @@ export const CreateVisorSceneGraph = (() => {
146143
get selected() {
147144
return _selected;
148145
},
149-
get edgeVisibility() {
150-
return _edgeVisibility;
151-
},
152146
get opacity() {
153147
return _opacity;
154148
},
@@ -269,21 +263,6 @@ export const CreateVisorSceneGraph = (() => {
269263
// stored colour from its own record.
270264
await renderer!.sendPartSelectedAsync(nodeId, _selected);
271265
},
272-
async setEdgeVisibilityAsync(edgeVisibility: boolean) {
273-
if (_edgeVisibility === edgeVisibility) {
274-
return;
275-
}
276-
_edgeVisibility = edgeVisibility;
277-
if (node.isGroupNode) {
278-
const promises = [];
279-
for (const n of node.descendantActorNodesOrSelfArray) {
280-
promises.push(n.setEdgeVisibilityAsync(edgeVisibility));
281-
}
282-
await Promise.all(promises);
283-
return;
284-
}
285-
await renderer!.setEdgeVisibilityAsync(nodeId, edgeVisibility);
286-
},
287266
async setOpacityAsync(opacity: number) {
288267
if (_opacity === opacity) {
289268
return;
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { VisorSceneNodeExtended } from '../state/VisorSceneGraph.tsx';
2+
import type { IRenderer } from '../renderer/IRenderer.ts';
23

34
export class EdgesWidget {
4-
constructor(sceneGraph: VisorSceneNodeExtended) {
5+
constructor(sceneGraph: VisorSceneNodeExtended, renderer: IRenderer) {
56
this.#sceneGraph = sceneGraph;
7+
this.#renderer = renderer;
68
}
79

810
#enabled: boolean = false;
@@ -11,9 +13,14 @@ export class EdgesWidget {
1113
}
1214

1315
#sceneGraph: VisorSceneNodeExtended;
16+
#renderer: IRenderer;
1417

1518
setEdgesVisibleAsync = async (enable?: boolean | null): Promise<void> => {
1619
this.#enabled = enable ?? !this.#enabled;
17-
await this.#sceneGraph.setEdgeVisibilityAsync(this.#enabled);
20+
const promises = [];
21+
for (const n of this.#sceneGraph.descendantActorNodesOrSelfArray) {
22+
promises.push(this.#renderer.setEdgeVisibilityAsync(n.id, this.#enabled));
23+
}
24+
await Promise.all(promises);
1825
};
1926
}

‎tests/unit/renderer/test_local_renderer.py‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -436,16 +436,12 @@ def test_deregister_all_empties_registry_and_detaches_all_actors(self, renderer)
436436
# ===========================================================================
437437

438438
class TestPerPartMutations:
439-
"""The two permanently un-implemented methods accept their arguments.
439+
"""The permanently un-implemented method accepts its arguments.
440440
441-
Both stay no-ops beyond this story: edge visibility is a global display
442-
toggle, and the colour-variable range is not held per part.
441+
It stays a no-op beyond this story: the colour-variable range is not
442+
held per part.
443443
"""
444444

445-
def test_apply_edge_visibility(self, renderer):
446-
assert renderer.apply_edge_visibility(1, False) is None
447-
448-
449445
def test_refresh_color_variable_range(self, renderer):
450446
assert (
451447
renderer.refresh_color_variable_range(1, "sp-1", "CELL", "temp", 0)

0 commit comments

Comments
 (0)