Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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