diff --git a/opossum/src/nodes/beam_splitter/mod.rs b/opossum/src/nodes/beam_splitter/mod.rs
index a77bb672cf5ecc3133fbb9436093d1db2ab80dcc..5feca1812ed03ecedbbb2fec26ff5c4881673460 100644
--- a/opossum/src/nodes/beam_splitter/mod.rs
+++ b/opossum/src/nodes/beam_splitter/mod.rs
@@ -9,7 +9,7 @@ use crate::{
     dottable::Dottable,
     error::{OpmResult, OpossumError},
     lightdata::{DataEnergy, LightData},
-    optic_node::OpticNode,
+    optic_node::{Alignable, OpticNode},
     optic_ports::{OpticPorts, PortType},
     properties::Proptype,
     ray::SplittingConfig,
@@ -316,6 +316,7 @@ impl Dottable for BeamSplitter {
 impl Analyzable for BeamSplitter {}
 impl AnalysisGhostFocus for BeamSplitter {}
 
+impl Alignable for BeamSplitter {}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/nodes/detector.rs b/opossum/src/nodes/detector.rs
index ba0b86de9381ebad6d2a43b2edd762b66be8f9ea..c7f3245d13f2c4eeb27979a3c2000c2c4aa7909f 100644
--- a/opossum/src/nodes/detector.rs
+++ b/opossum/src/nodes/detector.rs
@@ -9,7 +9,7 @@ use crate::{
     error::{OpmResult, OpossumError},
     light_result::LightResult,
     lightdata::LightData,
-    optic_node::{OpticNode, LIDT},
+    optic_node::{Alignable, OpticNode, LIDT},
     optic_ports::{OpticPorts, PortType},
     surface::{OpticalSurface, Plane},
     utils::geom_transformation::Isometry,
@@ -174,6 +174,7 @@ impl AnalysisRayTrace for Detector {
         self.light_data = Some(ld);
     }
 }
+impl Alignable for Detector {}
 
 #[cfg(test)]
 mod test {
diff --git a/opossum/src/nodes/dummy.rs b/opossum/src/nodes/dummy.rs
index ef53480d23e479a0314f1ab78ea9fa0af0cda7e3..3e4b17f62c9f4e59395a4f50316e5d963ff832f8 100644
--- a/opossum/src/nodes/dummy.rs
+++ b/opossum/src/nodes/dummy.rs
@@ -83,6 +83,7 @@ impl AnalysisEnergy for Dummy {
         )
     }
 }
+impl Alignable for Dummy {}
 
 impl AnalysisRayTrace for Dummy {
     fn analyze(
@@ -129,6 +130,219 @@ impl AnalysisRayTrace for Dummy {
             Ok(LightResult::from([(outport.into(), data.clone())]))
         }
     }
+
+    fn calc_node_position(
+        &mut self,
+        incoming_data: LightResult,
+        config: &RayTraceConfig,
+    ) -> OpmResult<LightResult> {
+        AnalysisRayTrace::analyze(self, incoming_data, config)
+    }
+
+    fn enter_through_surface(
+        &mut self,
+        rays_bundle: &mut Vec<crate::rays::Rays>,
+        analyzer_type: &crate::analyzers::AnalyzerType,
+        refri: &crate::refractive_index::RefractiveIndexType,
+        backward: bool,
+        port_name: &str,
+    ) -> OpmResult<()> {
+        let uuid = *self.node_attr().uuid();
+        let Some(iso) = &self.effective_iso() else {
+            return Err(OpossumError::Analysis(
+                "surface has no isometry defined".into(),
+            ));
+        };
+        if backward {
+            for rays in &mut *rays_bundle {
+                if let Some(aperture) = self.ports().aperture(&PortType::Input, port_name) {
+                    rays.apodize(aperture, iso)?;
+                    if let crate::analyzers::AnalyzerType::RayTrace(ref config) = analyzer_type {
+                        rays.invalidate_by_threshold_energy(config.min_energy_per_ray())?;
+                    }
+                } else {
+                    return Err(OpossumError::OpticPort("output aperture not found".into()));
+                };
+                let surf = self.get_surface_mut(port_name);
+                let mut reflected_rear = rays.refract_on_surface(surf, Some(refri))?;
+                reflected_rear.set_node_origin_uuid(uuid);
+
+                if let crate::analyzers::AnalyzerType::GhostFocus(_) = analyzer_type {
+                    surf.evaluate_fluence_of_ray_bundle(rays)?;
+                }
+
+                surf.add_to_forward_rays_cache(reflected_rear);
+            }
+            for rays in self.get_surface_mut(port_name).backwards_rays_cache() {
+                rays_bundle.push(rays.clone());
+            }
+        } else {
+            for rays in &mut *rays_bundle {
+                if let Some(aperture) = self.ports().aperture(&PortType::Input, port_name) {
+                    rays.apodize(aperture, &self.effective_iso().unwrap())?;
+                    if let crate::analyzers::AnalyzerType::RayTrace(ref config) = analyzer_type {
+                        rays.invalidate_by_threshold_energy(config.min_energy_per_ray())?;
+                    }
+                } else {
+                    return Err(OpossumError::OpticPort("input aperture not found".into()));
+                };
+                let surf = self.get_surface_mut(port_name);
+                let mut reflected_front = rays.refract_on_surface(surf, Some(refri))?;
+                reflected_front.set_node_origin_uuid(uuid);
+                if let crate::analyzers::AnalyzerType::GhostFocus(_) = analyzer_type {
+                    surf.evaluate_fluence_of_ray_bundle(rays)?;
+                }
+                surf.add_to_backward_rays_cache(reflected_front);
+            }
+            for rays in self.get_surface_mut(port_name).forward_rays_cache() {
+                rays_bundle.push(rays.clone());
+            }
+        }
+        Ok(())
+    }
+
+    fn exit_through_surface(
+        &mut self,
+        rays_bundle: &mut Vec<crate::rays::Rays>,
+        analyzer_type: &crate::analyzers::AnalyzerType,
+        refri: &crate::refractive_index::RefractiveIndexType,
+        backward: bool,
+        port_name: &str,
+    ) -> OpmResult<()> {
+        let uuid: uuid::Uuid = *self.node_attr().uuid();
+        let Some(iso) = &self.effective_iso() else {
+            return Err(OpossumError::Analysis(
+                "surface has no isometry defined".into(),
+            ));
+        };
+        let surf = self.get_surface_mut(port_name);
+        if backward {
+            for rays in &mut *rays_bundle {
+                let mut reflected_front = rays.refract_on_surface(surf, Some(refri))?;
+                reflected_front.set_node_origin_uuid(uuid);
+
+                if let crate::analyzers::AnalyzerType::GhostFocus(_) = analyzer_type {
+                    surf.evaluate_fluence_of_ray_bundle(rays)?;
+                }
+
+                surf.add_to_forward_rays_cache(reflected_front);
+            }
+            for rays in surf.backwards_rays_cache() {
+                rays_bundle.push(rays.clone());
+            }
+            for rays in &mut *rays_bundle {
+                if let Some(aperture) = self.ports().aperture(&PortType::Output, port_name) {
+                    rays.apodize(aperture, iso)?;
+                    if let crate::analyzers::AnalyzerType::RayTrace(config) = analyzer_type {
+                        rays.invalidate_by_threshold_energy(config.min_energy_per_ray())?;
+                    }
+                } else {
+                    return Err(OpossumError::OpticPort("input aperture not found".into()));
+                };
+            }
+        } else {
+            for rays in &mut *rays_bundle {
+                let mut reflected_rear = rays.refract_on_surface(surf, Some(refri))?;
+                reflected_rear.set_node_origin_uuid(uuid);
+                if let crate::analyzers::AnalyzerType::GhostFocus(_) = analyzer_type {
+                    surf.evaluate_fluence_of_ray_bundle(rays)?;
+                }
+                surf.add_to_backward_rays_cache(reflected_rear);
+            }
+            for rays in surf.forward_rays_cache() {
+                rays_bundle.push(rays.clone());
+            }
+            for rays in &mut *rays_bundle {
+                if let Some(aperture) = self.ports().aperture(&PortType::Output, port_name) {
+                    rays.apodize(aperture, iso)?;
+                    if let crate::analyzers::AnalyzerType::RayTrace(config) = analyzer_type {
+                        rays.invalidate_by_threshold_energy(config.min_energy_per_ray())?;
+                    }
+                } else {
+                    return Err(OpossumError::OpticPort("output aperture not found".into()));
+                };
+            }
+        }
+        Ok(())
+    }
+
+    fn pass_through_inert_surface(
+        &mut self,
+        rays_bundle: &mut Vec<crate::rays::Rays>,
+        analyzer_type: &crate::analyzers::AnalyzerType,
+    ) -> OpmResult<()> {
+        if let Some(iso) = self.effective_iso() {
+            let surf = self.get_surface_mut("");
+            surf.set_isometry(&iso);
+            for rays in &mut *rays_bundle {
+                rays.refract_on_surface(surf, None)?;
+                if let crate::analyzers::AnalyzerType::GhostFocus(_) = analyzer_type {
+                    surf.evaluate_fluence_of_ray_bundle(rays)?;
+                }
+            }
+        } else {
+            return Err(OpossumError::Analysis(
+                "no location for surface defined. Aborting".into(),
+            ));
+        }
+        // merge all rays
+        if let Some(ld) = self.get_light_data_mut() {
+            if let LightData::GhostFocus(rays) = ld {
+                for r in rays_bundle {
+                    rays.push(r.clone());
+                }
+            }
+        } else {
+            self.set_light_data(LightData::GhostFocus(rays_bundle.clone()));
+        }
+        Ok(())
+    }
+
+    fn get_light_data_mut(&mut self) -> Option<&mut LightData> {
+        None
+    }
+
+    fn set_light_data(&mut self, _ld: LightData) {}
+
+    fn get_node_attributes_ray_trace(
+        &self,
+        node_attr: &NodeAttr,
+    ) -> OpmResult<(
+        Isometry,
+        crate::refractive_index::RefractiveIndexType,
+        uom::si::f64::Length,
+        uom::si::f64::Angle,
+    )> {
+        let Some(eff_iso) = self.effective_iso() else {
+            return Err(OpossumError::Analysis(
+                "no location for surface defined".into(),
+            ));
+        };
+        let Ok(crate::properties::Proptype::RefractiveIndex(index_model)) =
+            node_attr.get_property("refractive index")
+        else {
+            return Err(OpossumError::Analysis(
+                "cannot read refractive index".into(),
+            ));
+        };
+        let Ok(crate::properties::Proptype::Length(center_thickness)) =
+            node_attr.get_property("center thickness")
+        else {
+            return Err(OpossumError::Analysis(
+                "cannot read center thickness".into(),
+            ));
+        };
+
+        let angle = if let Ok(crate::properties::Proptype::Angle(wedge)) =
+            node_attr.get_property("wedge")
+        {
+            *wedge
+        } else {
+            crate::degree!(0.)
+        };
+
+        Ok((eff_iso, index_model.value.clone(), *center_thickness, angle))
+    }
 }
 
 impl OpticNode for Dummy {
@@ -145,8 +359,6 @@ impl OpticNode for Dummy {
         &mut self.surface
     }
 }
-
-impl Alignable for Dummy {}
 impl Dottable for Dummy {}
 
 #[cfg(test)]
diff --git a/opossum/src/nodes/energy_meter.rs b/opossum/src/nodes/energy_meter.rs
index 65e163f0393ed80e1a2922d4e6c5d3e596db2718..9ef3ad5f3da8d4ca40bdd3d1f4dfe97e09986d58 100644
--- a/opossum/src/nodes/energy_meter.rs
+++ b/opossum/src/nodes/energy_meter.rs
@@ -9,7 +9,7 @@ use crate::{
     joule,
     light_result::LightResult,
     lightdata::LightData,
-    optic_node::{OpticNode, LIDT},
+    optic_node::{Alignable, OpticNode, LIDT},
     optic_ports::{OpticPorts, PortType},
     properties::{Properties, Proptype},
     reporting::node_report::NodeReport,
@@ -289,6 +289,7 @@ impl AnalysisRayTrace for EnergyMeter {
         self.light_data = Some(ld);
     }
 }
+impl Alignable for EnergyMeter {}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/nodes/fluence_detector.rs b/opossum/src/nodes/fluence_detector.rs
index b6f06d95965e08b63dacad2c7c5c4d55fbf35ee4..22044b0e4dc73fcedb3ee9d34bef08be4b10d868 100644
--- a/opossum/src/nodes/fluence_detector.rs
+++ b/opossum/src/nodes/fluence_detector.rs
@@ -16,7 +16,7 @@ use crate::{
     error::{OpmResult, OpossumError},
     light_result::{LightRays, LightResult},
     lightdata::LightData,
-    optic_node::{OpticNode, LIDT},
+    optic_node::{Alignable, OpticNode, LIDT},
     optic_ports::{OpticPorts, PortType},
     plottable::{PlotArgs, PlotData, PlotParameters, PlotSeries, PlotType, Plottable},
     properties::{Properties, Proptype},
@@ -146,6 +146,7 @@ impl OpticNode for FluenceDetector {
     }
 }
 
+impl Alignable for FluenceDetector {}
 impl Dottable for FluenceDetector {
     fn node_color(&self) -> &str {
         "hotpink"
diff --git a/opossum/src/nodes/ideal_filter.rs b/opossum/src/nodes/ideal_filter.rs
index 24c29fd15114fc66482379aa92251658b0ca0d71..a96dd838592f411464d56579c36f4d03da1e2f36 100644
--- a/opossum/src/nodes/ideal_filter.rs
+++ b/opossum/src/nodes/ideal_filter.rs
@@ -9,7 +9,7 @@ use crate::{
     error::{OpmResult, OpossumError},
     light_result::LightResult,
     lightdata::LightData,
-    optic_node::{OpticNode, LIDT},
+    optic_node::{Alignable, OpticNode, LIDT},
     optic_ports::{OpticPorts, PortType},
     properties::Proptype,
     spectrum::Spectrum,
@@ -185,7 +185,7 @@ impl OpticNode for IdealFilter {
         &mut self.surface
     }
 }
-
+impl Alignable for IdealFilter {}
 impl Dottable for IdealFilter {
     fn node_color(&self) -> &str {
         "darkgray"
diff --git a/opossum/src/nodes/ray_propagation_visualizer.rs b/opossum/src/nodes/ray_propagation_visualizer.rs
index 779f8943c74acef82952aec613094b27db4ffd64..eee0419e714acd89444d0f36fdcc12b5e60036b7 100644
--- a/opossum/src/nodes/ray_propagation_visualizer.rs
+++ b/opossum/src/nodes/ray_propagation_visualizer.rs
@@ -20,7 +20,7 @@ use crate::{
     light_result::{LightRays, LightResult},
     lightdata::LightData,
     millimeter,
-    optic_node::{OpticNode, LIDT},
+    optic_node::{Alignable, OpticNode, LIDT},
     optic_ports::{OpticPorts, PortType},
     plottable::{PlotArgs, PlotData, PlotParameters, PlotSeries, PlotType, Plottable},
     properties::{Properties, Proptype},
@@ -253,6 +253,7 @@ impl AnalysisRayTrace for RayPropagationVisualizer {
         self.light_data = Some(ld);
     }
 }
+impl Alignable for RayPropagationVisualizer {}
 
 /// struct that holds the history of the rays' positions for rays of a specific wavelength
 #[derive(Serialize, Deserialize, Clone, Debug)]
diff --git a/opossum/src/nodes/spectrometer.rs b/opossum/src/nodes/spectrometer.rs
index 08071af3b2f3cbfa445477751e1f05716ee9e0bc..070104bb386228e997c8a08ed554787f6d0d27eb 100644
--- a/opossum/src/nodes/spectrometer.rs
+++ b/opossum/src/nodes/spectrometer.rs
@@ -14,7 +14,7 @@ use crate::{
     light_result::LightResult,
     lightdata::LightData,
     nanometer,
-    optic_node::{OpticNode, LIDT},
+    optic_node::{Alignable, OpticNode, LIDT},
     optic_ports::{OpticPorts, PortType},
     plottable::{PlotArgs, PlotParameters, PlotSeries, PlotType, Plottable},
     properties::{Properties, Proptype},
@@ -217,7 +217,7 @@ impl OpticNode for Spectrometer {
         &mut self.surface
     }
 }
-
+impl Alignable for Spectrometer {}
 impl Debug for Spectrometer {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match &self.light_data {