diff --git a/raytracer/cherry-rs/examples/convexplano_lens.rs b/raytracer/cherry-rs/examples/convexplano_lens.rs index 7c9bbb5b..246483d0 100644 --- a/raytracer/cherry-rs/examples/convexplano_lens.rs +++ b/raytracer/cherry-rs/examples/convexplano_lens.rs @@ -342,7 +342,7 @@ mod test_ri_info { ParaxialView::new(&model, &FIELD_SPECS, false).expect("Could not create paraxial view"); let expected = axial_primary_color(); - let results = view.axial_primary_color(&WAVELENGTHS).unwrap(); + let results = view.axial_primary_color(); assert_eq!(expected.len(), results.len()); for (axis, expected_value) in expected.iter() { diff --git a/raytracer/cherry-rs/src/core/sequential_model.rs b/raytracer/cherry-rs/src/core/sequential_model.rs index e9eddd48..48fa799a 100644 --- a/raytracer/cherry-rs/src/core/sequential_model.rs +++ b/raytracer/cherry-rs/src/core/sequential_model.rs @@ -43,6 +43,7 @@ pub struct Gap { pub struct SequentialModel { surfaces: Vec, submodels: HashMap, + wavelengths: Vec, } /// A submodel of a sequential optical system. @@ -327,6 +328,7 @@ impl SequentialModel { Ok(Self { surfaces, submodels: models, + wavelengths: wavelengths.to_vec(), }) } @@ -356,6 +358,11 @@ impl SequentialModel { .fold(0.0, |acc, x| acc.max(x)) } + /// Returns the wavelengths at which the system is modeled. + pub fn wavelengths(&self) -> &[Float] { + &self.wavelengths + } + /// Computes the unique IDs for each paraxial model. fn calc_model_ids(surfaces: &[Surface], wavelengths: &[Float]) -> Vec { let mut ids = Vec::new(); @@ -490,17 +497,21 @@ impl<'a> SequentialSubModel for SequentialSubModelSlice<'a> { } impl Serialize for SubModelID { - // Serialize as a string like "0:Y" because tuples as map keys are difficult to work with in - // languages like Javascript. + // Serialize as a string like "0:Y" because tuples as map keys are difficult to + // work with in languages like Javascript. fn serialize(&self, serializer: S) -> Result where S: Serializer, { // Serialize as a string like "0:Y" - let key = format!("{}:{}", self.0, match self.1 { - Axis::X => "X", - Axis::Y => "Y", - }); + let key = format!( + "{}:{}", + self.0, + match self.1 { + Axis::X => "X", + Axis::Y => "Y", + } + ); serializer.serialize_str(&key) } } diff --git a/raytracer/cherry-rs/src/lib.rs b/raytracer/cherry-rs/src/lib.rs index b7779249..26670c6a 100644 --- a/raytracer/cherry-rs/src/lib.rs +++ b/raytracer/cherry-rs/src/lib.rs @@ -150,4 +150,5 @@ pub use views::{ }; // Re-exports from dependencies +#[cfg(feature = "ri-info")] pub use lib_ria::Material; diff --git a/raytracer/cherry-rs/src/views/paraxial.rs b/raytracer/cherry-rs/src/views/paraxial.rs index c6698c88..aedbefa1 100644 --- a/raytracer/cherry-rs/src/views/paraxial.rs +++ b/raytracer/cherry-rs/src/views/paraxial.rs @@ -59,6 +59,7 @@ type RayTransferMatrix = Array2; #[derive(Debug)] pub struct ParaxialView { subviews: HashMap, + wavelengths: Vec, } /// A description of a paraxial optical system. @@ -67,6 +68,7 @@ pub struct ParaxialView { #[derive(Debug, Serialize)] pub struct ParaxialViewDescription { subviews: HashMap, + primary_axial_color: HashMap, } /// A paraxial subview of an optical system. @@ -238,6 +240,7 @@ impl ParaxialView { Ok(Self { subviews: subviews?, + wavelengths: sequential_model.wavelengths().to_vec(), }) } @@ -254,6 +257,7 @@ impl ParaxialView { .iter() .map(|(id, subview)| (*id, subview.describe())) .collect(), + primary_axial_color: self.axial_primary_color(), } } @@ -275,34 +279,20 @@ impl ParaxialView { /// enter the wavelengths for the Fraunhofer F and C lines as minimum and /// maximum wavelengths to the underlying sequential model. /// - /// # Arguments - /// * `wavelengths` - The wavelengths to compute the axial primary color. - /// These must match the wavelengths used in the underlying sequential - /// model. - /// /// # Returns /// A HashMap containing the axial primary color for each axis. - /// - /// # Errors - /// An error is returned if the number of wavelengths does not match the - /// number of unique wavelengths in the paraxial view. - pub fn axial_primary_color(&self, wavelengths: &[Float]) -> Result> { - let unique_wavelengths = self.subviews.keys().map(|id| id.0).collect::>(); - if unique_wavelengths.len() != wavelengths.len() { - return Err(anyhow!( - "The number of wavelengths must match the number of unique wavelengths in the system." - )); - } - + pub fn axial_primary_color(&self) -> HashMap { // Find the indexes of the minimum and maximum wavelengths. Return with the // empty axial primary color if there are no wavelengths. - let min_wav_index = wavelengths + let min_wav_index = self + .wavelengths .iter() .enumerate() .min_by(|(_, a), (_, b)| a.total_cmp(b)) .map(|(index, _)| index) .unwrap_or_default(); - let max_wav_index = wavelengths + let max_wav_index = self + .wavelengths .iter() .enumerate() .max_by(|(_, a), (_, b)| a.total_cmp(b)) @@ -333,7 +323,7 @@ impl ParaxialView { } } - Ok(axial_primary_color) + axial_primary_color } } diff --git a/www/js/CHANGELOG.md b/www/js/CHANGELOG.md index 63eee10f..033f51d7 100644 --- a/www/js/CHANGELOG.md +++ b/www/js/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Materials dispersion data is now downloaded and added to the browser's IndexedDB database when the application starts. - Users may now specify materials data to use in a lens design, and this data will be used to automatically compute refractive indexes at each wavelength. -- The results summary window now contains paraxial data computed at multiple wavelengths. +- The results summary window now contains paraxial data computed at multiple wavelengths and pximary axial color information. ## [0.2.0] 2024-12-09 diff --git a/www/js/src/components/SummaryWindow.js b/www/js/src/components/SummaryWindow.js index 9f2f87d1..02c7b421 100644 --- a/www/js/src/components/SummaryWindow.js +++ b/www/js/src/components/SummaryWindow.js @@ -15,40 +15,59 @@ const formatValue = (value) => { // Reusable table component that can be used in both modal and popup const SummaryTable = ({ data, wavelengths, sorted_indexes, appModes }) => ( - - - - - - - - - - - {!appModes.refractiveIndex && ( +
+

Paraxial Data

+
ParameterValue
+ - - {sorted_indexes.map((i) => ( - - ))} + + - )} - - - {Object.entries(data).map(([key, values]) => ( - - - {appModes.refractiveIndex ? ( - - ) : ( - sorted_indexes.map((i) => ( - - )) - )} + + + + + + + +
Wavelength, μm{wavelengths[i]}ParameterValue
{key}{formatValue(values[0])}{formatValue(values[i])}
Primary Axial Color{formatValue(data["Primary Axial Color"])}
+ +

Paraxial Data (wavelength-dependent)

+ + + + + + + + + - ))} - -
ParameterValue
+ {!appModes.refractiveIndex && ( + + Wavelength, μm + {sorted_indexes.map((i) => ( + {wavelengths[i]} + ))} + + )} + + + {Object.entries(data.subviews).map(([key, values]) => ( + + {key} + {appModes.refractiveIndex ? ( + {formatValue(values[0])} + ) : ( + sorted_indexes.map((i) => ( + {formatValue(values[i])} + )) + )} + + ))} + + + ); const Modal = ({ isOpen, onClose, children }) => { @@ -115,8 +134,16 @@ const SummaryWindow = ({ description, isOpen, wavelengths, appModes, onClose }) // Update summary whenever description changes useEffect(() => { if (!description) return; - + + console.log(description.paraxial_view); + + // For now we only deal with the Y axis as we don't support toric surfaces const newSummary = { + "Primary Axial Color": description.paraxial_view.primary_axial_color.get("Y") || 0, + subviews: {} + }; + + const newSubviewSummaries = { "Aperture Stop (surface index)": {}, "Effective Focal Length": {}, "Back Focal Distance": {}, @@ -147,23 +174,24 @@ const SummaryWindow = ({ description, isOpen, wavelengths, appModes, onClose }) // For now we only deal with the Y axis as we don't support toric surfaces if (axis !== "Y") continue; - newSummary["Aperture Stop (surface index)"][wavelength_index] = subview.aperture_stop; - newSummary["Effective Focal Length"][wavelength_index] = subview.effective_focal_length; - newSummary["Back Focal Distance"][wavelength_index] = subview.back_focal_distance; - newSummary["Front Focal Distance"][wavelength_index] = subview.front_focal_distance; - newSummary["Paraxial Image Plane Location"][wavelength_index] = subview.paraxial_image_plane.location; - newSummary["Paraxial Image Plane Semi-Diameter"][wavelength_index] = subview.paraxial_image_plane.semi_diameter; - newSummary["Entrance Pupil Location"][wavelength_index] = subview.entrance_pupil.location; - newSummary["Entrance Pupil Semi-Diameter"][wavelength_index] = subview.entrance_pupil.semi_diameter; - newSummary["Exit Pupil Location"][wavelength_index] = subview.exit_pupil.location; - newSummary["Exit Pupil Semi-Diameter"][wavelength_index] = subview.exit_pupil.semi_diameter; - newSummary["Back Principal Plane"][wavelength_index] = subview.back_principal_plane; - newSummary["Front Principal Plane"][wavelength_index] = subview.front_principal_plane; + newSubviewSummaries["Aperture Stop (surface index)"][wavelength_index] = subview.aperture_stop; + newSubviewSummaries["Effective Focal Length"][wavelength_index] = subview.effective_focal_length; + newSubviewSummaries["Back Focal Distance"][wavelength_index] = subview.back_focal_distance; + newSubviewSummaries["Front Focal Distance"][wavelength_index] = subview.front_focal_distance; + newSubviewSummaries["Paraxial Image Plane Location"][wavelength_index] = subview.paraxial_image_plane.location; + newSubviewSummaries["Paraxial Image Plane Semi-Diameter"][wavelength_index] = subview.paraxial_image_plane.semi_diameter; + newSubviewSummaries["Entrance Pupil Location"][wavelength_index] = subview.entrance_pupil.location; + newSubviewSummaries["Entrance Pupil Semi-Diameter"][wavelength_index] = subview.entrance_pupil.semi_diameter; + newSubviewSummaries["Exit Pupil Location"][wavelength_index] = subview.exit_pupil.location; + newSubviewSummaries["Exit Pupil Semi-Diameter"][wavelength_index] = subview.exit_pupil.semi_diameter; + newSubviewSummaries["Back Principal Plane"][wavelength_index] = subview.back_principal_plane; + newSubviewSummaries["Front Principal Plane"][wavelength_index] = subview.front_principal_plane; // Only extract these values once if appModes is set to refractive index if (appModes.refractiveIndex) break; } - + newSummary.subviews = newSubviewSummaries; + setSummary(newSummary); }, [description]);