Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/138.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Remote rendering 3.3b] fold per-part edge visibility into the global toggle
4 changes: 0 additions & 4 deletions src/ansys/visor/viewer/renderer/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,6 @@ def apply_diffuse_color(
) -> None:
"""Set the actor-property diffuse colour for *node_id*."""

@abstractmethod
def apply_edge_visibility(self, node_id: int, edge_visible: bool) -> None:
"""Toggle edge / wireframe visibility for *node_id*."""

@abstractmethod
def apply_selected(
self, node_id: int, selected: bool, diffuse_rgb: list
Expand Down
3 changes: 0 additions & 3 deletions src/ansys/visor/viewer/renderer/local_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,6 @@ def apply_diffuse_color(
return
pipe.set_diffuse_color(r, g, b)

def apply_edge_visibility(self, node_id: int, edge_visible: bool) -> None:
"""No-op in Story 1.2. Phase 3 populates."""

def apply_selected(
self, node_id: int, selected: bool, diffuse_rgb: list
) -> None:
Expand Down
3 changes: 0 additions & 3 deletions src/ansys/visor/viewer/renderer/null_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,6 @@ def apply_opacity(self, node_id: int, opacity: float) -> None:
def apply_diffuse_color(self, node_id: int, r: float, g: float, b: float) -> None:
pass

def apply_edge_visibility(self, node_id: int, edge_visible: bool) -> None:
pass

def apply_selected(self, node_id: int, selected: bool, diffuse_rgb: list) -> None:
pass

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, RefObject, useEffect, useRef } from 'react';
import { FC, useEffect } from 'react';
import { AwcIcons } from '../AwcIcons';
import { VisorFrontend } from '../../VisorFrontend.tsx';
import { makeTooltip } from '../../utils/Tooltip';
Expand All @@ -10,7 +10,6 @@ export const Panel_BottomMiddle: FC<{
visorState: VisorFrontend;
onLoad: (util: Panel_BottomMiddle_Util) => void;
}> = ({ visorState, onLoad }) => {
const edgeVisibility: RefObject<boolean> = useRef(false);
const tableElemId = `tableElem-${crypto.randomUUID()}`;
const selectionModeContainerId = `selMode-${crypto.randomUUID()}`;
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ const BOUNDING_BOX_ALGORITHM_ID = 205;
const BOUNDING_BOX_OUTLINE_ID = 206;
const BOUNDING_BOX_AXES_ID = 207;

/** Three actor node ids for the edge fan-out. Hand-written literals. */
const FAN_OUT_ACTOR_IDS = [11, 12, 13];

/** What the wasm camera reports for `GetParallelProjection()`. */
const PARALLEL = 1;
const PERSPECTIVE = 0;
Expand Down Expand Up @@ -102,21 +105,23 @@ function makeFakeWasmObjects() {
/**
* A scene-graph stand-in for `attachSceneGraph`.
*
* `EdgesWidget` fans out through the graph node's own
* `setEdgeVisibilityAsync`; `BoundingBoxWidget` enumerates
* `descendantActorNodesOrSelfArray` when recomputing bounds. Neither is the
* subject here -- what is pinned is the *send* that follows the local write.
* `EdgesWidget` enumerates `descendantActorNodesOrSelfArray` and calls the
* renderer's own per-actor `setEdgeVisibilityAsync` for each node's `id`;
* `BoundingBoxWidget` enumerates the same array when recomputing bounds.
* Neither is the subject of the send tests -- what those pin is the *send*
* that follows the local write, and they pass an empty actor list so the
* fan-out reaches nothing.
*/
function makeSceneGraphDouble() {
function makeSceneGraphDouble(actorIds: number[] = []) {
return {
setEdgeVisibilityAsync: jest.fn(async () => undefined),
descendantActorNodesOrSelfArray: [],
descendantActorNodesOrSelfArray: actorIds.map((id) => ({ id })),
};
}

async function makeRenderer(
sender: TrameTriggerSender | null,
parallelProjection: number = PERSPECTIVE
parallelProjection: number = PERSPECTIVE,
actorIds: number[] = []
) {
const objects = makeFakeWasmObjects();
const camera = {
Expand Down Expand Up @@ -145,7 +150,7 @@ async function makeRenderer(
makeAnnotation(),
sender
);
const sceneGraph = makeSceneGraphDouble();
const sceneGraph = makeSceneGraphDouble(actorIds);
renderer.attachSceneGraph(sceneGraph as unknown as VisorSceneNodeExtended);
return { renderer, camera, sceneGraph, ...objects };
}
Expand Down Expand Up @@ -241,6 +246,32 @@ describe('WasmRenderer widget sends: no sender, and a failing sender', () => {
});
});

describe('WasmRenderer edge fan-out reaches every actor on every call', () => {
test('setEdgeVisibilityGlobalAsync issues one per-actor call per actor on every call, not only when the value changes', async () => {
// The per-node cache that D1 deleted lived on the scene-graph node
// and had no invalidation, so a second toggle to the same value was
// a no-op and three of the six pairs below were absent. This is an
// id set with its value, not a call count: a count would not say
// which actors were reached.
const { renderer } = await makeRenderer(makeSender(), PERSPECTIVE, FAN_OUT_ACTOR_IDS);
const perActor = jest.spyOn(renderer, 'setEdgeVisibilityAsync');

await renderer.setEdgeVisibilityGlobalAsync(true);
await renderer.setEdgeVisibilityGlobalAsync(true);

expect(perActor.mock.calls).toEqual([
[11, true],
[12, true],
[13, true],
[11, true],
[12, true],
[13, true],
]);

perActor.mockRestore();
});
});

describe('WasmRenderer.createAsync seeds the orthographic flag from the wasm camera', () => {
test('isOrthographicEnabled is true when the wasm camera reports parallel, before any setter runs', async () => {
const { renderer } = await makeRenderer(makeSender(), PARALLEL);
Expand Down
2 changes: 1 addition & 1 deletion src/ansys/visor/visor-client/src/renderer/WasmRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export class WasmRenderer implements IRenderer {
boxAlgorithmWasmId: this.#boundingBoxBoxAlgorithmWasmId,
outlineQueryId: this.#wasmBoundingBoxActorId,
});
this.#edgesWidget = new EdgesWidget(sceneGraph);
this.#edgesWidget = new EdgesWidget(sceneGraph, this);
}

async renderAsync(): Promise<void> {
Expand Down
21 changes: 0 additions & 21 deletions src/ansys/visor/visor-client/src/state/VisorSceneGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export type VisorSceneNodeExtended = Readonly<{
diffuseRgb: Readonly<number[]>;
visible: boolean;
selected: boolean;
edgeVisibility: boolean;
opacity: number;
descendantsOrSelfArray: VisorSceneNodeExtended[];
descendantsOrSelfDictionary: Record<string, VisorSceneNodeExtended>;
Expand All @@ -44,7 +43,6 @@ export type VisorSceneNodeExtended = Readonly<{
setDiffuseColorRgbAsync: (r: number, g: number, b: number) => Promise<void>;
setVisibilityAsync: (visible: boolean) => Promise<void>;
setSelectedAsync: (selected: boolean) => Promise<void>;
setEdgeVisibilityAsync: (edgeVisibility: boolean) => Promise<void>;
setOpacityAsync: (opacity: number) => Promise<void>;
clearColorVariableAsync: () => Promise<void>;
setColorVariableAsync: (id: string, component?: number | null) => Promise<void>;
Expand Down Expand Up @@ -99,7 +97,6 @@ export const CreateVisorSceneGraph = (() => {
}
let _visible: boolean = true;
let _selected: boolean = false;
let _edgeVisibility: boolean = false;
let _opacity: number = 1;
let _spectrumId: string | null = null;
let _spectrumComponent: number = -1;
Expand Down Expand Up @@ -146,9 +143,6 @@ export const CreateVisorSceneGraph = (() => {
get selected() {
return _selected;
},
get edgeVisibility() {
return _edgeVisibility;
},
get opacity() {
return _opacity;
},
Expand Down Expand Up @@ -269,21 +263,6 @@ export const CreateVisorSceneGraph = (() => {
// stored colour from its own record.
await renderer!.sendPartSelectedAsync(nodeId, _selected);
},
async setEdgeVisibilityAsync(edgeVisibility: boolean) {
if (_edgeVisibility === edgeVisibility) {
return;
}
_edgeVisibility = edgeVisibility;
if (node.isGroupNode) {
const promises = [];
for (const n of node.descendantActorNodesOrSelfArray) {
promises.push(n.setEdgeVisibilityAsync(edgeVisibility));
}
await Promise.all(promises);
return;
}
await renderer!.setEdgeVisibilityAsync(nodeId, edgeVisibility);
},
async setOpacityAsync(opacity: number) {
if (_opacity === opacity) {
return;
Expand Down
11 changes: 9 additions & 2 deletions src/ansys/visor/visor-client/src/widgets/edgesWidget.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { VisorSceneNodeExtended } from '../state/VisorSceneGraph.tsx';
import type { IRenderer } from '../renderer/IRenderer.ts';

export class EdgesWidget {
constructor(sceneGraph: VisorSceneNodeExtended) {
constructor(sceneGraph: VisorSceneNodeExtended, renderer: IRenderer) {
this.#sceneGraph = sceneGraph;
this.#renderer = renderer;
}

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

#sceneGraph: VisorSceneNodeExtended;
#renderer: IRenderer;

setEdgesVisibleAsync = async (enable?: boolean | null): Promise<void> => {
this.#enabled = enable ?? !this.#enabled;
await this.#sceneGraph.setEdgeVisibilityAsync(this.#enabled);
const promises = [];
for (const n of this.#sceneGraph.descendantActorNodesOrSelfArray) {
promises.push(this.#renderer.setEdgeVisibilityAsync(n.id, this.#enabled));
}
await Promise.all(promises);
};
}
10 changes: 3 additions & 7 deletions tests/unit/renderer/test_local_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,16 +436,12 @@ def test_deregister_all_empties_registry_and_detaches_all_actors(self, renderer)
# ===========================================================================

class TestPerPartMutations:
"""The two permanently un-implemented methods accept their arguments.
"""The permanently un-implemented method accepts its arguments.

Both stay no-ops beyond this story: edge visibility is a global display
toggle, and the colour-variable range is not held per part.
It stays a no-op beyond this story: the colour-variable range is not
held per part.
"""

def test_apply_edge_visibility(self, renderer):
assert renderer.apply_edge_visibility(1, False) is None


def test_refresh_color_variable_range(self, renderer):
assert (
renderer.refresh_color_variable_range(1, "sp-1", "CELL", "temp", 0)
Expand Down
Loading