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: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "surface-expert",
"version": "2.9.6",
"version": "2.9.7",
"description": "SurfaceExpert - Optical Surface Analysis Desktop Application",
"main": "src/main.js",
"scripts": {
Expand Down
8 changes: 8 additions & 0 deletions src/components/panels/PropertiesPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export const PropertiesPanel = ({
handleNormalizeUnZ,
handleConvertToUnZ,
handleConvertToPoly,
handleFlipX,
handleFlipY,
handleFlipZ,
handleCopyCoefficients,
c,
t
}) => {
Expand Down Expand Up @@ -383,6 +387,10 @@ export const PropertiesPanel = ({
onNormalizeUnZ: handleNormalizeUnZ,
onConvertToUnZ: handleConvertToUnZ,
onConvertToPoly: handleConvertToPoly,
onFlipX: handleFlipX,
onFlipY: handleFlipY,
onFlipZ: handleFlipZ,
onCopyCoefficients: handleCopyCoefficients,
c,
t
}),
Expand Down
37 changes: 35 additions & 2 deletions 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, c, t }) => {
export const SurfaceActionButtons = ({ surface, onInvert, onNormalizeUnZ, onConvertToUnZ, onConvertToPoly, onFlipX, onFlipY, onFlipZ, onCopyCoefficients, c, t }) => {
const buttonStyle = {
padding: '8px 16px',
backgroundColor: c.accent,
Expand Down Expand Up @@ -69,7 +69,40 @@ export const SurfaceActionButtons = ({ surface, onInvert, onNormalizeUnZ, onConv
title: 'Convert this Poly surface to Opal Un Z'
}, t.properties.convertToUnZ),

// Convert to Poly button - shown only for Opal Un Z
// Flip and Copy buttons - shown only for Zernike
surface.type === 'Zernike' && h('button', {
onClick: onFlipX,
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 mirrored about the X-axis (y → -y)'
}, 'Flip around X'),

surface.type === 'Zernike' && h('button', {
onClick: onFlipY,
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 mirrored about the Y-axis (x → -x)'
}, 'Flip around Y'),

surface.type === 'Zernike' && h('button', {
onClick: onFlipZ,
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 180° about the Z-axis (x → -x, y → -y)'
}, 'Flip around Z'),

surface.type === 'Zernike' && h('button', {
onClick: onCopyCoefficients,
style: buttonStyle,
onMouseEnter: (e) => e.target.style.backgroundColor = '#3a7bc8',
onMouseLeave: (e) => e.target.style.backgroundColor = c.accent,
title: 'Copy Z1-Z37 values as tab-separated text for pasting into Excel'
}, 'Copy Coefficients'),

// Convert to Poly button - shown only for Opal Un Z
surface.type === 'Opal Un Z' && h('button', {
onClick: onConvertToPoly,
style: buttonStyle,
Expand Down
26 changes: 25 additions & 1 deletion src/renderer-modular.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ import {
handleNormalizeUnZConfirm as normalizeUnZConfirmHandler,
handleConvertToUnZ as convertToUnZHandler,
handleConvertToPoly as convertToPolyHandler,
handleFastConvertToPoly as fastConvertToPolyHandler
handleFastConvertToPoly as fastConvertToPolyHandler,
handleFlipZernikeX as flipZernikeXHandler,
handleFlipZernikeY as flipZernikeYHandler,
handleFlipZernikeZ as flipZernikeZHandler,
handleCopyZernikeCoefficients as copyZernikeCoefficientsHandler
} from './utils/surfaceOperationHandlers.js';
import { parseNumber } from './utils/numberParsing.js';

Expand Down Expand Up @@ -427,6 +431,22 @@ const OpticalSurfaceAnalyzer = () => {
convertToPolyHandler(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface);
};

const handleFlipX = () => {
flipZernikeXHandler(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface);
};

const handleFlipY = () => {
flipZernikeYHandler(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface);
};

const handleFlipZ = () => {
flipZernikeZHandler(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface);
};

const handleCopyCoefficients = () => {
copyZernikeCoefficientsHandler(selectedSurface);
};

const handleFastConvertToPoly = () => {
fastConvertToPolyHandler(
selectedSurface,
Expand Down Expand Up @@ -1093,6 +1113,10 @@ const OpticalSurfaceAnalyzer = () => {
handleNormalizeUnZ,
handleConvertToUnZ,
handleConvertToPoly,
handleFlipX,
handleFlipY,
handleFlipZ,
handleCopyCoefficients,
c,
t
})
Expand Down
100 changes: 99 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 } from './surfaceTransformations.js';
import { normalizeUnZ, convertPolyToUnZ, convertUnZToPoly, invertSurface, flipZernikeAroundX, flipZernikeAroundY, flipZernikeAroundZ } from './surfaceTransformations.js';
import { parseNumber } from './numberParsing.js';
import { calculateSurfaceValues } from './calculations.js';

Expand Down Expand Up @@ -387,3 +387,101 @@ function calculateMaxDeviation(deviations) {

return maxDev;
}

// ============================================
// Zernike Flip Handlers
// ============================================

const addFlippedZernikeSurface = (selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface, flippedParams, suffix) => {
const newSurface = {
id: Date.now(),
name: `${selectedSurface.name} (${suffix})`,
type: 'Zernike',
color: selectedSurface.color,
parameters: flippedParams
};

const updatedFolders = folders.map(folder => {
if (folder.id === selectedFolder.id) {
return { ...folder, surfaces: [...folder.surfaces, newSurface] };
}
return folder;
});

setFolders(updatedFolders);
setSelectedSurface(newSurface);

if (window.electronAPI && window.electronAPI.saveSurface) {
window.electronAPI.saveSurface(selectedFolder.name, newSurface);
}
};

/**
* Flip Zernike surface around X-axis (y → -y) and create a new surface.
*/
export const handleFlipZernikeX = (selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface) => {
if (!selectedSurface || selectedSurface.type !== 'Zernike' || !selectedFolder) return;
try {
const flipped = flipZernikeAroundX(selectedSurface.parameters);
addFlippedZernikeSurface(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface, flipped, 'flipped around X');
} catch (error) {
alert(`Error flipping surface around X: ${error.message}`);
console.error('Flip X error:', error);
}
};

/**
* Flip Zernike surface around Y-axis (x → -x) and create a new surface.
*/
export const handleFlipZernikeY = (selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface) => {
if (!selectedSurface || selectedSurface.type !== 'Zernike' || !selectedFolder) return;
try {
const flipped = flipZernikeAroundY(selectedSurface.parameters);
addFlippedZernikeSurface(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface, flipped, 'flipped around Y');
} catch (error) {
alert(`Error flipping surface around Y: ${error.message}`);
console.error('Flip Y error:', error);
}
};

/**
* Flip Zernike surface around Z-axis (x → -x, y → -y) and create a new surface.
*/
export const handleFlipZernikeZ = (selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface) => {
if (!selectedSurface || selectedSurface.type !== 'Zernike' || !selectedFolder) return;
try {
const flipped = flipZernikeAroundZ(selectedSurface.parameters);
addFlippedZernikeSurface(selectedSurface, selectedFolder, folders, setFolders, setSelectedSurface, flipped, 'flipped around Z');
} catch (error) {
alert(`Error flipping surface around Z: ${error.message}`);
console.error('Flip Z error:', error);
}
};

/**
* Copy all Zernike coefficients Z1-Z37 to clipboard as tab-separated values.
*/
export const handleCopyZernikeCoefficients = (selectedSurface) => {
if (!selectedSurface || selectedSurface.type !== 'Zernike') return;
const values = Array.from({ length: 37 }, (_, i) => {
const key = `Z${i + 1}`;
return selectedSurface.parameters[key] || '0';
});
const text = values.join('\t');
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).catch(() => fallbackCopy(text));
} else {
fallbackCopy(text);
}
};

function fallbackCopy(text) {
const el = document.createElement('textarea');
el.value = text;
el.style.position = 'fixed';
el.style.opacity = '0';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
68 changes: 68 additions & 0 deletions src/utils/surfaceTransformations.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,71 @@ export const invertSurface = (surfaceType, parameters) => {

return result;
};

/**
* Zernike azimuthal symmetry map:
* m=0 (spherical): Z1,Z4,Z9,Z16,Z25,Z36,Z37
* m=1 cos(X): Z2,Z7,Z14,Z23,Z34 sin(Y): Z3,Z8,Z15,Z24,Z35
* m=2 cos(X): Z5,Z12,Z21,Z32 sin(Y): Z6,Z13,Z22,Z33
* m=3 cos(X): Z10,Z19,Z30 sin(Y): Z11,Z20,Z31
* m=4 cos(X): Z17,Z28 sin(Y): Z18,Z29
* m=5 cos(X): Z26 sin(Y): Z27
*/

/**
* Flip Zernike surface around X-axis (y → -y, θ → -θ).
* Negates all sine (Y) azimuthal terms; cosine (X) and spherical terms unchanged.
* Radius of curvature is NOT modified.
*
* @param {Object} parameters - Current Zernike surface parameters
* @returns {Object} Parameters with flipped Zernike coefficients
*/
export const flipZernikeAroundX = (parameters) => {
const result = { ...parameters };
[3, 6, 8, 11, 13, 15, 18, 20, 22, 24, 27, 29, 31, 33, 35].forEach(n => {
const key = `Z${n}`;
if (result[key] !== undefined) result[key] = changeSign(result[key]);
});
return result;
};

/**
* Flip Zernike surface around Y-axis (x → -x, θ → π-θ).
* Odd-m cosine (X) terms negated; even-m sine (Y) terms negated.
* Radius of curvature is NOT modified.
*
* @param {Object} parameters - Current Zernike surface parameters
* @returns {Object} Parameters with flipped Zernike coefficients
*/
export const flipZernikeAroundY = (parameters) => {
const result = { ...parameters };
// odd-m X terms: m=1(Z2,Z7,Z14,Z23,Z34), m=3(Z10,Z19,Z30), m=5(Z26)
[2, 7, 10, 14, 19, 23, 26, 30, 34].forEach(n => {
const key = `Z${n}`;
if (result[key] !== undefined) result[key] = changeSign(result[key]);
});
// even-m Y terms: m=2(Z6,Z13,Z22,Z33), m=4(Z18,Z29)
[6, 13, 18, 22, 29, 33].forEach(n => {
const key = `Z${n}`;
if (result[key] !== undefined) result[key] = changeSign(result[key]);
});
return result;
};

/**
* Flip Zernike surface around Z-axis (θ → θ+π, x→-x and y→-y simultaneously).
* All odd-m terms (both X and Y) are negated; even-m and spherical terms unchanged.
* Radius of curvature is NOT modified.
*
* @param {Object} parameters - Current Zernike surface parameters
* @returns {Object} Parameters with flipped Zernike coefficients
*/
export const flipZernikeAroundZ = (parameters) => {
const result = { ...parameters };
// odd-m: m=1(Z2,Z3,Z7,Z8,Z14,Z15,Z23,Z24,Z34,Z35), m=3(Z10,Z11,Z19,Z20,Z30,Z31), m=5(Z26,Z27)
[2, 3, 7, 8, 10, 11, 14, 15, 19, 20, 23, 24, 26, 27, 30, 31, 34, 35].forEach(n => {
const key = `Z${n}`;
if (result[key] !== undefined) result[key] = changeSign(result[key]);
});
return result;
};
Loading