From f61f1b8e6758b3a30670f1bf08e4d8646a9b23d0 Mon Sep 17 00:00:00 2001 From: Laura Kasian Date: Wed, 7 Oct 2026 06:58:31 -0700 Subject: [PATCH 1/4] fix: fix scalar variables not applying after re-selecting a part colored by a vector component --- .../src/aggregate/AggregateVariableInfo.tsx | 45 +++++++++--- .../jest-tests/AggregateVariableInfo.test.tsx | 8 +-- .../VariableRecordProjection.test.tsx | 68 +++++++++++++++++++ 3 files changed, 108 insertions(+), 13 deletions(-) diff --git a/src/ansys/visor/visor-client/src/aggregate/AggregateVariableInfo.tsx b/src/ansys/visor/visor-client/src/aggregate/AggregateVariableInfo.tsx index 003b6dea..0443e5f6 100644 --- a/src/ansys/visor/visor-client/src/aggregate/AggregateVariableInfo.tsx +++ b/src/ansys/visor/visor-client/src/aggregate/AggregateVariableInfo.tsx @@ -84,15 +84,11 @@ export class AggregateVariableInfo { for (let i = 0; i < actorNodes.length; i++) { const node = actorNodes[i]; - const variable = node.variableCollection.getVariable(variableInfo.id); - let thisComponentId: number | null = node.variableComponent; - - if (variable != null) { - if (variable.getRangeInfo(node.variableComponent) == null) { - // Default to the first component option. - thisComponentId = variable.componentOptions[0].id; - } - } + const thisComponentId = AggregateVariableInfo.#resolveNodeComponentId( + node, + variableInfo, + obj.#componentOptions + ); if (i === 0) { obj.#displayComponentId = thisComponentId; @@ -117,6 +113,37 @@ export class AggregateVariableInfo { return obj; } + /** + * The node's component when it names an option of this variable and has a range, else the variable's first option. + * + * A node holds one component, belonging to the variable it is coloured by, + * and it is read here for every variable in its collection. A component + * with a range but no option (component 0 of a one-component variable, + * whose only option is the magnitude) is therefore replaced too. + * + * @param node - Scene node whose configured component is read. + * @param variableInfo - Variable the component is resolved for. + * @param componentOptions - The variable's options, keyed by component ID string. + * @returns The component ID to display for this node. + */ + static #resolveNodeComponentId( + node: VisorSceneNodeExtended, + variableInfo: VisorVariableInfo, + componentOptions: ReadonlyMap + ): number | null { + const componentId: number | null = node.variableComponent; + const variable = node.variableCollection.getVariable(variableInfo.id); + const firstOptionId = (variable ?? variableInfo).componentOptions[0].id; + + if (componentId == null || !componentOptions.has(componentId.toString())) { + return firstOptionId; + } + if (variable != null && variable.getRangeInfo(componentId) == null) { + return firstOptionId; + } + return componentId; + } + /** * Gets the variable's unique identifier. * diff --git a/src/ansys/visor/visor-client/src/jest-tests/AggregateVariableInfo.test.tsx b/src/ansys/visor/visor-client/src/jest-tests/AggregateVariableInfo.test.tsx index 4d2ac383..9984ced9 100644 --- a/src/ansys/visor/visor-client/src/jest-tests/AggregateVariableInfo.test.tsx +++ b/src/ansys/visor/visor-client/src/jest-tests/AggregateVariableInfo.test.tsx @@ -255,7 +255,7 @@ describe('AggregateVariableInfo', () => { expect(component.displayVariableMax).toBe(5); }); - test('warns when a displayed component has range data but no component option', async () => { + test('falls back to the first option when a displayed component has range data but no component option', async () => { const warningSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); const variable = createVariable({ @@ -280,9 +280,9 @@ describe('AggregateVariableInfo', () => { const result = selection.currentVariableInfo!; - expect(result.displayComponentId).toBe(999); - expect(result.currentComponentInfo).toBeUndefined(); - expect(warningSpy).toHaveBeenCalledWith("invalid componentId: '999'"); + expect(result.displayComponentId).toBe(-1); + expect(result.currentComponentInfo).toBe(result.componentOptions.get('-1')); + expect(warningSpy).not.toHaveBeenCalled(); }); test('setDisplayComponentId selects a component by number', async () => { diff --git a/src/ansys/visor/visor-client/src/jest-tests/VariableRecordProjection.test.tsx b/src/ansys/visor/visor-client/src/jest-tests/VariableRecordProjection.test.tsx index 584b40e0..2990e161 100644 --- a/src/ansys/visor/visor-client/src/jest-tests/VariableRecordProjection.test.tsx +++ b/src/ansys/visor/visor-client/src/jest-tests/VariableRecordProjection.test.tsx @@ -670,3 +670,71 @@ describe('the range trigger is sent for an edit and never for a delivery', () => expect(rangeSends(triggerSender)).toEqual([]); }); }); + +/** Sets a panel `` holding an option with the given value. */ +function selectHolding(container: HTMLElement, value: string): HTMLSelectElement { + return container.querySelector(`select option[value="${value}"]`)! + .parentElement as HTMLSelectElement; +} + +describe('re-selection carries the node component only where it names an option', () => { + test('a scalar is applied after re-selecting a part coloured by a vector component X', async () => { + const { frontend, renderer } = makeFrontend(); + const part = frontend.sceneGraph.descendantActorNodesOrSelfDictionary[PART_A_ID]; + frontend.setTreeViewUtil(makeTreeViewUtilDouble([part])); + await frontend.setAppStateAsync( + { + scene: { + variableStates: { + [VELOCITY_ID]: velocityRecord(), + [PRESSURE_ID]: pressureRecord(), + }, + }, + }, + false + ); + await part.setColorVariableAsync(VELOCITY_ID, -1); + + let container: HTMLElement = null!; + await act(async () => { + container = render( {}} />).container; + }); + const util = await frontend.panelTopRightUtilPromise; + const warningSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + try { + await chooseOption(selectHolding(container, '0'), '0'); + await act(async () => { + await util.refreshSelectionAsync(); + }); + renderer.sendPartColorVariableAsync.mockClear(); + + await chooseOption(selectHolding(container, PRESSURE_ID), PRESSURE_ID); + + expect(renderer.sendPartColorVariableAsync.mock.calls).toEqual([ + [ + 1, + { + variableId: 'POINT::pressure::1', + variableType: 'POINT', + variableName: 'pressure', + component: -1, + min: 2, + max: 8, + }, + ], + ]); + expect(warningSpy).not.toHaveBeenCalledWith("invalid componentId: '0'"); + } finally { + warningSpy.mockRestore(); + } + }); +}); From 217addf63a5ef47a6ef0dc028e58fdc7b7c2b07b Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 7 Oct 2026 17:10:14 +0000 Subject: [PATCH 2/4] chore: adding changelog file 162.added.md [dependabot-skip] --- doc/changelog.d/162.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/162.added.md diff --git a/doc/changelog.d/162.added.md b/doc/changelog.d/162.added.md new file mode 100644 index 00000000..74b48729 --- /dev/null +++ b/doc/changelog.d/162.added.md @@ -0,0 +1 @@ +[Remote rendering 3.5d] Fix scalar variables not applying after re-selecting a part colored by a vector component From 462818cd56e07de5125cdfca68d1178e04e0afcf Mon Sep 17 00:00:00 2001 From: Laura Kasian Date: Wed, 7 Oct 2026 10:49:46 -0700 Subject: [PATCH 3/4] prettier fix --- .../src/jest-tests/VariableRecordProjection.test.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ansys/visor/visor-client/src/jest-tests/VariableRecordProjection.test.tsx b/src/ansys/visor/visor-client/src/jest-tests/VariableRecordProjection.test.tsx index 2990e161..3b031c32 100644 --- a/src/ansys/visor/visor-client/src/jest-tests/VariableRecordProjection.test.tsx +++ b/src/ansys/visor/visor-client/src/jest-tests/VariableRecordProjection.test.tsx @@ -706,7 +706,9 @@ describe('re-selection carries the node component only where it names an option' let container: HTMLElement = null!; await act(async () => { - container = render( {}} />).container; + container = render( + {}} /> + ).container; }); const util = await frontend.panelTopRightUtilPromise; const warningSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); From 77a32cdd148fc0d8602ce1818714af6860307527 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 7 Oct 2026 21:35:44 +0000 Subject: [PATCH 4/4] chore: adding changelog file 162.fixed.md [dependabot-skip] --- doc/changelog.d/162.added.md | 1 - doc/changelog.d/162.fixed.md | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 doc/changelog.d/162.added.md create mode 100644 doc/changelog.d/162.fixed.md diff --git a/doc/changelog.d/162.added.md b/doc/changelog.d/162.added.md deleted file mode 100644 index 74b48729..00000000 --- a/doc/changelog.d/162.added.md +++ /dev/null @@ -1 +0,0 @@ -[Remote rendering 3.5d] Fix scalar variables not applying after re-selecting a part colored by a vector component diff --git a/doc/changelog.d/162.fixed.md b/doc/changelog.d/162.fixed.md new file mode 100644 index 00000000..12b2e654 --- /dev/null +++ b/doc/changelog.d/162.fixed.md @@ -0,0 +1 @@ +Fix scalar variables not applying after re-selecting a part colored by a vector component