Skip to content
Merged
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
4 changes: 4 additions & 0 deletions src/components/panels/PropertiesPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export const PropertiesPanel = ({
handleFlipX,
handleFlipY,
handleFlipZ,
handleRotate90CCW,
handleRotate90CW,
handleCopyCoefficients,
c,
t
Expand Down Expand Up @@ -390,6 +392,8 @@ export const PropertiesPanel = ({
onFlipX: handleFlipX,
onFlipY: handleFlipY,
onFlipZ: handleFlipZ,
onRotate90CCW: handleRotate90CCW,
onRotate90CW: handleRotate90CW,
onCopyCoefficients: handleCopyCoefficients,
c,
t
Expand Down
18 changes: 17 additions & 1 deletion src/components/ui/SurfaceActionButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const { createElement: h } = React;
* @param {Function} props.onConvertToPoly - Callback for UnZ → Poly conversion
* @param {Object} props.c - Color scheme object
*/
export const SurfaceActionButtons = ({ surface, onInvert, onNormalizeUnZ, onConvertToUnZ, onConvertToPoly, onFlipX, onFlipY, onFlipZ, onCopyCoefficients, c, t }) => {
export const SurfaceActionButtons = ({ surface, onInvert, onNormalizeUnZ, onConvertToUnZ, onConvertToPoly, onFlipX, onFlipY, onFlipZ, onRotate90CCW, onRotate90CW, onCopyCoefficients, c, t }) => {
const buttonStyle = {
padding: '8px 16px',
backgroundColor: c.accent,
Expand Down Expand Up @@ -94,6 +94,22 @@ export const SurfaceActionButtons = ({ surface, onInvert, onNormalizeUnZ, onConv
title: 'Create a new surface with Zernike coefficients rotated 180° about the Z-axis (x → -x, y → -y)'
}, t.properties.flipZ),

surface.type === 'Zernike' && h('button', {
onClick: onRotate90CCW,
style: buttonStyle,
onMouseEnter: (e) => e.target.style.backgroundColor = '#3a7bc8',
onMouseLeave: (e) => e.target.style.backgroundColor = c.accent,
title: 'Create a new surface with Zernike coefficients rotated 90° counter-clockwise'
}, t.properties.rotate90CCW),

surface.type === 'Zernike' && h('button', {
onClick: onRotate90CW,
style: buttonStyle,
onMouseEnter: (e) => e.target.style.backgroundColor = '#3a7bc8',
onMouseLeave: (e) => e.target.style.backgroundColor = c.accent,
title: 'Create a new surface with Zernike coefficients rotated 90° clockwise'
}, t.properties.rotate90CW),

surface.type === 'Zernike' && h('button', {
onClick: onCopyCoefficients,
style: buttonStyle,
Expand Down
4 changes: 4 additions & 0 deletions src/constants/locales.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export const locales = {
flipX: 'Flip around X',
flipY: 'Flip around Y',
flipZ: 'Flip around Z',
rotate90CCW: 'Rotate 90° CCW',
rotate90CW: 'Rotate 90° CW',
copyCoefficients: 'Copy Coefficients',

// Metrics
Expand Down Expand Up @@ -497,6 +499,8 @@ export const locales = {
flipX: 'Отразить по X',
flipY: 'Отразить по Y',
flipZ: 'Отразить по Z',
rotate90CCW: 'Повернуть 90° ПЧС',
rotate90CW: 'Повернуть 90° ЧС',
copyCoefficients: 'Копировать коэффициенты',

// Метрики
Expand Down
12 changes: 12 additions & 0 deletions src/renderer-modular.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
handleFlipZernikeX as flipZernikeXHandler,
handleFlipZernikeY as flipZernikeYHandler,
handleFlipZernikeZ as flipZernikeZHandler,
handleRotateZernike90CCW as rotateZernike90CCWHandler,
handleRotateZernike90CW as rotateZernike90CWHandler,
handleCopyZernikeCoefficients as copyZernikeCoefficientsHandler
} from './utils/surfaceOperationHandlers.js';
import { parseNumber } from './utils/numberParsing.js';
Expand Down Expand Up @@ -448,6 +450,14 @@ const OpticalSurfaceAnalyzer = () => {
flipZernikeZHandler(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface);
};

const handleRotate90CCW = () => {
rotateZernike90CCWHandler(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface);
};

const handleRotate90CW = () => {
rotateZernike90CWHandler(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface);
};

const handleCopyCoefficients = () => {
copyZernikeCoefficientsHandler(selectedSurface);
};
Expand Down Expand Up @@ -1124,6 +1134,8 @@ const OpticalSurfaceAnalyzer = () => {
handleFlipX,
handleFlipY,
handleFlipZ,
handleRotate90CCW,
handleRotate90CW,
handleCopyCoefficients,
c,
t
Expand Down
30 changes: 29 additions & 1 deletion src/utils/surfaceOperationHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// ============================================
// Business logic for surface transformation operations

import { normalizeUnZ, convertPolyToUnZ, convertUnZToPoly, invertSurface, flipZernikeAroundX, flipZernikeAroundY, flipZernikeAroundZ } from './surfaceTransformations.js';
import { normalizeUnZ, convertPolyToUnZ, convertUnZToPoly, invertSurface, flipZernikeAroundX, flipZernikeAroundY, flipZernikeAroundZ, rotateZernikeBy90CCW, rotateZernikeBy90CW } from './surfaceTransformations.js';
import { parseNumber } from './numberParsing.js';
import { calculateSurfaceValues } from './calculations.js';

Expand Down Expand Up @@ -458,6 +458,34 @@ export const handleFlipZernikeZ = (selectedSurface, selectedFolder, folders, set
}
};

/**
* Rotate Zernike surface 90° CCW and create a new surface.
*/
export const handleRotateZernike90CCW = (selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface) => {
if (!selectedSurface || selectedSurface.type !== 'Zernike' || !selectedFolder) return;
try {
const rotated = rotateZernikeBy90CCW(selectedSurface.parameters);
addFlippedZernikeSurface(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface, rotated, 'rotated 90° CCW');
} catch (error) {
alert(`Error rotating surface 90° CCW: ${error.message}`);
console.error('Rotate 90° CCW error:', error);
}
};

/**
* Rotate Zernike surface 90° CW and create a new surface.
*/
export const handleRotateZernike90CW = (selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface) => {
if (!selectedSurface || selectedSurface.type !== 'Zernike' || !selectedFolder) return;
try {
const rotated = rotateZernikeBy90CW(selectedSurface.parameters);
addFlippedZernikeSurface(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface, rotated, 'rotated 90° CW');
} catch (error) {
alert(`Error rotating surface 90° CW: ${error.message}`);
console.error('Rotate 90° CW error:', error);
}
};

/**
* Copy all Zernike coefficients Z1-Z37 to clipboard as tab-separated values.
*/
Expand Down
92 changes: 92 additions & 0 deletions src/utils/surfaceTransformations.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,95 @@ export const flipZernikeAroundZ = (parameters) => {
});
return result;
};

/**
* Rotate Zernike surface 90° CCW (θ → θ - π/2).
* Each azimuthal order m picks up a phase shift of mπ/2, mixing cos/sin pairs.
*
* m=0: unchanged
* m=1,5 (mπ/2 = π/2): new_cos = -old_sin, new_sin = old_cos
* m=2 (mπ/2 = π): both cos and sin negated
* m=3 (mπ/2 = 3π/2): new_cos = old_sin, new_sin = -old_cos
* m=4 (mπ/2 = 2π): unchanged
*
* @param {Object} parameters - Current Zernike surface parameters
* @returns {Object} Parameters rotated 90° CCW
*/
export const rotateZernikeBy90CCW = (parameters) => {
const result = { ...parameters };

// m=1 pairs: (Z2,Z3),(Z7,Z8),(Z14,Z15),(Z23,Z24),(Z34,Z35) → new_cos=-old_sin, new_sin=old_cos
[[2,3],[7,8],[14,15],[23,24],[34,35]].forEach(([c, s]) => {
const kc = `Z${c}`, ks = `Z${s}`;
const oldCos = result[kc], oldSin = result[ks];
if (oldCos !== undefined) result[kc] = changeSign(oldSin);
if (oldSin !== undefined) result[ks] = oldCos;
});

// m=2: all negated
[5, 6, 12, 13, 21, 22, 32, 33].forEach(n => {
const key = `Z${n}`;
if (result[key] !== undefined) result[key] = changeSign(result[key]);
});

// m=3 pairs: (Z10,Z11),(Z19,Z20),(Z30,Z31) → new_cos=old_sin, new_sin=-old_cos
[[10,11],[19,20],[30,31]].forEach(([c, s]) => {
const kc = `Z${c}`, ks = `Z${s}`;
const oldCos = result[kc], oldSin = result[ks];
if (oldCos !== undefined) result[kc] = oldSin;
if (oldSin !== undefined) result[ks] = changeSign(oldCos);
});

// m=5 pair: (Z26,Z27) → same rule as m=1
const oldZ26 = result['Z26'], oldZ27 = result['Z27'];
if (oldZ26 !== undefined) result['Z26'] = changeSign(oldZ27);
if (oldZ27 !== undefined) result['Z27'] = oldZ26;

return result;
};

/**
* Rotate Zernike surface 90° CW (θ → θ + π/2).
* Inverse of rotateZernikeBy90CCW.
*
* m=0: unchanged
* m=1,5 (mπ/2 = π/2): new_cos = old_sin, new_sin = -old_cos
* m=2 (mπ/2 = π): both cos and sin negated
* m=3 (mπ/2 = 3π/2): new_cos = -old_sin, new_sin = old_cos
* m=4 (mπ/2 = 2π): unchanged
*
* @param {Object} parameters - Current Zernike surface parameters
* @returns {Object} Parameters rotated 90° CW
*/
export const rotateZernikeBy90CW = (parameters) => {
const result = { ...parameters };

// m=1 pairs: new_cos=old_sin, new_sin=-old_cos
[[2,3],[7,8],[14,15],[23,24],[34,35]].forEach(([c, s]) => {
const kc = `Z${c}`, ks = `Z${s}`;
const oldCos = result[kc], oldSin = result[ks];
if (oldCos !== undefined) result[kc] = oldSin;
if (oldSin !== undefined) result[ks] = changeSign(oldCos);
});

// m=2: all negated
[5, 6, 12, 13, 21, 22, 32, 33].forEach(n => {
const key = `Z${n}`;
if (result[key] !== undefined) result[key] = changeSign(result[key]);
});

// m=3 pairs: new_cos=-old_sin, new_sin=old_cos
[[10,11],[19,20],[30,31]].forEach(([c, s]) => {
const kc = `Z${c}`, ks = `Z${s}`;
const oldCos = result[kc], oldSin = result[ks];
if (oldCos !== undefined) result[kc] = changeSign(oldSin);
if (oldSin !== undefined) result[ks] = oldCos;
});

// m=5 pair: same rule as m=1
const oldZ26 = result['Z26'], oldZ27 = result['Z27'];
if (oldZ26 !== undefined) result['Z26'] = oldZ27;
if (oldZ27 !== undefined) result['Z27'] = changeSign(oldZ26);

return result;
};
Loading