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/171.maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Remote rendering 3.6d] Deliver server state to the browser without sending it back
2 changes: 1 addition & 1 deletion src/ansys/visor/viewer/app/trame/local_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ def set_panel_top_right_tab_index(self, payload) -> None:
"""Frontend -> Backend: the top-right panel reports its active tab.

The index is forwarded verbatim and is never range-checked here; the
client's ``selectTab`` is the guard, so a future third tab is a UI
client's ``applyTabIndex`` is the guard, so a future third tab is a UI
change rather than a validation failure at this boundary.
"""
api = self._mutation_api("set_panel_top_right_tab_index", payload)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
toggle's.

``tab_index`` is deliberately unbounded: the client only ever sends ``0``
or ``1``, and ``Panel_TopRight_Util.selectTab`` already ignores anything
or ``1``, and ``Panel_TopRight_Util.applyTabIndex`` already ignores anything
else, so the bound lives there, not here.
"""

Expand Down
2 changes: 1 addition & 1 deletion src/ansys/visor/viewer/vtk/scene/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,7 @@ def set_panel_top_right_tab_index(self, tab_index: int) -> None:
"""Record which top-right tab is active.

The value is stored opaquely and is never interpreted here; it is
only ever handed back to the client's ``selectTab``, which is where
only ever handed back to the client's ``applyTabIndex``, which is where
the range guard lives.
"""
with self._vtk_lock:
Expand Down
58 changes: 19 additions & 39 deletions src/ansys/visor/visor-client/src/VisorFrontend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -363,34 +363,22 @@ export class VisorFrontend {
void (async () => {
const panelTopLeft = await panelTopLeftUtilPromise;
if (uiState.panelTopLeftPanelCollapsed !== undefined) {
if (uiState.panelTopLeftPanelCollapsed) {
panelTopLeft.collapsePanel();
} else {
panelTopLeft.expandPanel();
}
panelTopLeft.applyPanelCollapsed(uiState.panelTopLeftPanelCollapsed);
}
})();
void (async () => {
const panelTopRight = await panelTopRightUtilPromise;
if (uiState.panelTopRightPanelCollapsed !== undefined) {
if (uiState.panelTopRightPanelCollapsed) {
panelTopRight.collapsePanel();
} else {
panelTopRight.expandPanel();
}
panelTopRight.applyPanelCollapsed(uiState.panelTopRightPanelCollapsed);
}
if (uiState.panelTopRightLegendCollapsed !== undefined) {
if (uiState.panelTopRightLegendCollapsed) {
panelTopRight.collapseLegend();
} else {
panelTopRight.expandLegend();
}
panelTopRight.applyLegendCollapsed(uiState.panelTopRightLegendCollapsed);
}
if (uiState.panelTopRightTabIndex !== undefined) {
panelTopRight.selectTab(uiState.panelTopRightTabIndex);
panelTopRight.applyTabIndex(uiState.panelTopRightTabIndex);
}
})();
const promises: Promise<void>[] = [];
const promises: Promise<unknown>[] = [];
const sceneState = appState.scene;
if (sceneState.unit !== undefined) {
const unit = (this.#unit = sceneState.unit);
Expand All @@ -403,17 +391,17 @@ export class VisorFrontend {
// projection's sole writer is the camera write below. See
// AppStateProjectionLoad.test.ts for why.
if (sceneState.crossSectionEnabled !== undefined) {
const promise = renderer.setCrossSectionVisibilityAsync(
const promise = renderer.applyCrossSectionVisibilityAsync(
sceneState.crossSectionEnabled
);
promises.push(promise);
}
if (sceneState.edgesEnabled !== undefined) {
const promise = renderer.setEdgeVisibilityGlobalAsync(sceneState.edgesEnabled);
const promise = renderer.applyEdgeVisibilityGlobalAsync(sceneState.edgesEnabled);
promises.push(promise);
}
if (sceneState.boundingBoxEnabled !== undefined) {
const promise = renderer.setBoundingBoxVisibilityAsync(
const promise = renderer.applyBoundingBoxVisibilityAsync(
sceneState.boundingBoxEnabled
);
promises.push(promise);
Expand All @@ -436,9 +424,9 @@ export class VisorFrontend {
promises.push(promise);
}
if (cameraState.parallelProjection !== undefined) {
// Routed through setOrthographicModeAsync, not setCameraParallelProjectionAsync: only
// Routed through applyOrthographicModeAsync, not setCameraParallelProjectionAsync: only
// the former also sets the widget flag, so the latter would leave it stale for the next save.
const promise = renderer.setOrthographicModeAsync(cameraState.parallelProjection);
const promise = renderer.applyOrthographicModeAsync(cameraState.parallelProjection);
promises.push(promise);
}
if (cameraState.viewAngle !== undefined) {
Expand Down Expand Up @@ -484,11 +472,11 @@ export class VisorFrontend {
// write this actor's ambient/diffuse properties with
// different values, so they must be sequenced for this
// node rather than raced as independent promises.
let partPromise: Promise<void> = Promise.resolve();
let partPromise: Promise<unknown> = Promise.resolve();
if (part_state.diffuseRgb !== undefined) {
const [r, g, b] = part_state.diffuseRgb;
partPromise = partPromise.then(() =>
partNode.setDiffuseColorRgbAsync(r, g, b)
partNode.applyDiffuseColorRgbAsync(r, g, b)
);
}
// `selected` is applied to the node, like every other
Expand All @@ -497,30 +485,22 @@ export class VisorFrontend {
if (part_state.selected !== undefined) {
const selected = part_state.selected;
partPromise = partPromise.then(() =>
partNode.setSelectedAsync(selected)
partNode.applySelectedAsync(selected)
);
}
promises.push(partPromise);
if (part_state.opacity !== undefined) {
const promise = node.setOpacityAsync(part_state.opacity);
const promise = node.applyOpacityAsync(part_state.opacity);
promises.push(promise);
}
if (part_state.visible !== undefined) {
const promise = node.setVisibilityAsync(part_state.visible);
const promise = node.applyVisibilityAsync(part_state.visible);
promises.push(promise);
}
// A null reference is delivered without a component.
if (part_state.variableId !== undefined) {
let promise;
if (part_state.variableId !== null) {
promise = node.setColorVariableAsync(
part_state.variableId,
part_state.variableComponent
);
} else {
promise = node.clearColorVariableAsync();
}
promises.push(promise);
// A null reference clears the colouring; an absent one leaves it.
const colorVariable = part_state.colorVariable;
if (colorVariable !== undefined) {
promises.push(node.applyColorVariableAsync(colorVariable));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ export const Panel_TopLeft: FC<{
const treeViewContainer = document.getElementById(treeViewContainerId) as HTMLDivElement;
treeViewUtil.current.addSelectionChangeListener((selectedNodes) => {
(async () => {
// One call per part with its target value; a part whose value
// is unchanged sends nothing.
const selected = new Set(selectedNodes);
for (const node of visorState.sceneGraph.descendantActorNodesOrSelfArray) {
await node.setSelectedAsync(false);
}
for (const node of selectedNodes) {
await node.setSelectedAsync(true);
await node.setSelectedAsync(selected.has(node));
}
await visorState.render();
})();
});
treeViewUtil.current.addVisibilityChangeListener((visibleNodes) => {
(async () => {
// One call per part with its target value; a part whose value
// is unchanged sends nothing.
const visible = new Set(visibleNodes);
for (const node of visorState.sceneGraph.descendantActorNodesOrSelfArray) {
await node.setVisibilityAsync(false);
}
for (const node of visibleNodes) {
await node.setVisibilityAsync(true);
await node.setVisibilityAsync(visible.has(node));
}
// await visorState.updateBoundingBoxBoundsAsync();
await visorState.render();
Expand All @@ -57,46 +57,34 @@ export const Panel_TopLeft: FC<{
'theme-hover-background-3',
]) as HTMLButtonElement;
let isPanelCollapsed = false;
let sendEnabled = false;
collapseButton.onclick = (e) => {
reactComponentContainer.style.width = `${reactComponentContainer.offsetWidth}px`;
treeViewContainer.style.display = 'none';
collapseButton.remove();
collapseButtonContainer.appendChild(expandButton);
isPanelCollapsed = true;
if (sendEnabled) {
void visorState.sendPanelTopLeftPanelCollapsedAsync(isPanelCollapsed);
const applyPanelCollapsed = (collapsed: boolean) => {
if (collapsed) {
reactComponentContainer.style.width = `${reactComponentContainer.offsetWidth}px`;
treeViewContainer.style.display = 'none';
collapseButton.remove();
collapseButtonContainer.appendChild(expandButton);
} else {
reactComponentContainer.style.removeProperty('width');
treeViewContainer.style.removeProperty('display');
expandButton.remove();
collapseButtonContainer.appendChild(collapseButton);
}
isPanelCollapsed = collapsed;
};
collapseButton.onclick = (e) => {
applyPanelCollapsed(true);
void visorState.sendPanelTopLeftPanelCollapsedAsync(isPanelCollapsed);
};
expandButton.onclick = (e) => {
reactComponentContainer.style.removeProperty('width');
treeViewContainer.style.removeProperty('display');
expandButton.remove();
collapseButtonContainer.appendChild(collapseButton);
isPanelCollapsed = false;
if (sendEnabled) {
void visorState.sendPanelTopLeftPanelCollapsedAsync(isPanelCollapsed);
}
applyPanelCollapsed(false);
void visorState.sendPanelTopLeftPanelCollapsedAsync(isPanelCollapsed);
};
expandButton.click();
applyPanelCollapsed(false);
visorState.setTreeViewUtil(treeViewUtil.current);
const util = new Panel_TopLeft_Util(
treeViewUtil.current,
() => {
expandButton.click();
},
() => {
collapseButton.click();
},
() => {
return isPanelCollapsed;
}
);
const util = new Panel_TopLeft_Util(treeViewUtil.current, applyPanelCollapsed, () => {
return isPanelCollapsed;
});
visorState.setPanelTopLeftUtil(util);
// Must stay on the line after the util handoff: it suppresses the mount click above and is
// open before any delivered apply awaiting the util promise can click; scaffolding,
// removed when delivery is separated from mutation.
sendEnabled = true;
onLoad(util);
}, []);
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ import { VisorSceneNodeExtended } from '../../state/VisorSceneGraph.tsx';
export class Panel_TopLeft_Util {
constructor(
treeViewUtil: TreeViewUtil<VisorSceneNodeExtended>,
expandPanel: () => void,
collapsePanel: () => void,
applyPanelCollapsed: (collapsed: boolean) => void,
getIsPanelCollapsed: () => boolean
) {
this.treeViewUtil = treeViewUtil;
this.expandPanel = expandPanel;
this.collapsePanel = collapsePanel;
this.#applyPanelCollapsed = applyPanelCollapsed;
this.#getIsPanelCollapsed = getIsPanelCollapsed;
}

readonly treeViewUtil: TreeViewUtil<VisorSceneNodeExtended>;
readonly expandPanel: () => void;
readonly collapsePanel: () => void;
readonly #applyPanelCollapsed: (collapsed: boolean) => void;
readonly #getIsPanelCollapsed: () => boolean;

/** Collapse or expand the panel; send nothing. */
applyPanelCollapsed(collapsed: boolean): void {
this.#applyPanelCollapsed(collapsed);
}

get isPanelCollapsed() {
return this.#getIsPanelCollapsed();
}
Expand Down
Loading
Loading