Skip to main content

nanonis_rs/client/scan/
mod.rs

1mod types;
2pub use types::*;
3
4use super::NanonisClient;
5use crate::error::NanonisError;
6use crate::types::{NanonisValue, Position};
7use std::time::Duration;
8
9impl NanonisClient {
10    /// Start, stop, pause or resume a scan
11    pub fn scan_action(
12        &mut self,
13        scan_action: ScanAction,
14        scan_direction: ScanDirection,
15    ) -> Result<(), NanonisError> {
16        self.quick_send(
17            "Scan.Action",
18            vec![
19                NanonisValue::U16(scan_action.into()),
20                NanonisValue::U32(scan_direction.into()),
21            ],
22            vec!["H", "I"],
23            vec![],
24        )?;
25        Ok(())
26    }
27
28    /// Configure the scan frame parameters
29    pub fn scan_frame_set(&mut self, frame: ScanFrame) -> Result<(), NanonisError> {
30        self.quick_send(
31            "Scan.FrameSet",
32            vec![
33                NanonisValue::F32(frame.center.x as f32),
34                NanonisValue::F32(frame.center.y as f32),
35                NanonisValue::F32(frame.width_m),
36                NanonisValue::F32(frame.height_m),
37                NanonisValue::F32(frame.angle_deg),
38            ],
39            vec!["f", "f", "f", "f", "f"],
40            vec![],
41        )?;
42        Ok(())
43    }
44
45    /// Get the scan frame parameters
46    pub fn scan_frame_get(&mut self) -> Result<ScanFrame, NanonisError> {
47        let result = self.quick_send(
48            "Scan.FrameGet",
49            vec![],
50            vec![],
51            vec!["f", "f", "f", "f", "f"],
52        )?;
53        if result.len() >= 5 {
54            let center_x = result[0].as_f32()? as f64;
55            let center_y = result[1].as_f32()? as f64;
56            let width = result[2].as_f32()?;
57            let height = result[3].as_f32()?;
58            let angle = result[4].as_f32()?;
59
60            Ok(ScanFrame::new(
61                Position::new(center_x, center_y),
62                width,
63                height,
64                angle,
65            ))
66        } else {
67            Err(NanonisError::Protocol(
68                "Invalid scan frame response".to_string(),
69            ))
70        }
71    }
72
73    /// Get the scan buffer parameters
74    /// Returns: (channel_indexes, pixels, lines)
75    pub fn scan_buffer_get(&mut self) -> Result<(Vec<i32>, i32, i32), NanonisError> {
76        let result =
77            self.quick_send("Scan.BufferGet", vec![], vec![], vec!["i", "*i", "i", "i"])?;
78        if result.len() >= 4 {
79            let channel_indexes = result[1].as_i32_array()?.to_vec();
80            let pixels = result[2].as_i32()?;
81            let lines = result[3].as_i32()?;
82            Ok((channel_indexes, pixels, lines))
83        } else {
84            Err(NanonisError::Protocol(
85                "Invalid scan buffer response".to_string(),
86            ))
87        }
88    }
89
90    /// Get the current scan status.
91    ///
92    /// Returns whether a scan is currently running or not.
93    ///
94    /// # Returns
95    /// `true` if scan is running, `false` if scan is not running.
96    ///
97    /// # Errors
98    /// Returns `NanonisError` if communication fails or protocol error occurs.
99    ///
100    /// # Examples
101    /// ```no_run
102    /// use nanonis_rs::NanonisClient;
103    ///
104    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
105    ///
106    /// if client.scan_status_get()? {
107    ///     println!("Scan is currently running");
108    /// } else {
109    ///     println!("Scan is stopped");
110    /// }
111    /// # Ok::<(), Box<dyn std::error::Error>>(())
112    /// ```
113    pub fn scan_status_get(&mut self) -> Result<bool, NanonisError> {
114        let result = self.quick_send("Scan.StatusGet", vec![], vec![], vec!["I"])?;
115
116        match result.first() {
117            Some(value) => Ok(value.as_u32()? == 1),
118            None => Err(NanonisError::Protocol(
119                "No scan status returned".to_string(),
120            )),
121        }
122    }
123
124    /// Configure the scan buffer parameters.
125    ///
126    /// Sets which channels to record during scanning and the scan resolution.
127    /// The channel indexes refer to the 24 signals assigned in the Signals Manager (0-23).
128    ///
129    /// **Important**: The number of pixels is coerced to the closest multiple of 16
130    /// because scan data is sent in packages of 16 pixels.
131    ///
132    /// # Arguments
133    /// * `channel_indexes` - Indexes of channels to record (0-23 for signals in Signals Manager)
134    /// * `pixels` - Number of pixels per line (coerced to multiple of 16)
135    /// * `lines` - Number of scan lines
136    ///
137    /// # Errors
138    /// Returns `NanonisError` if communication fails or invalid parameters provided.
139    ///
140    /// # Examples
141    /// ```no_run
142    /// use nanonis_rs::NanonisClient;
143    ///
144    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
145    ///
146    /// // Record channels 0, 1, and 2 with 512x512 resolution
147    /// client.scan_buffer_set(vec![0, 1, 2], 512, 512)?;
148    ///
149    /// // High resolution scan with multiple channels
150    /// client.scan_buffer_set(vec![0, 1, 2, 3, 4], 1024, 1024)?;
151    /// # Ok::<(), Box<dyn std::error::Error>>(())
152    /// ```
153    pub fn scan_buffer_set(
154        &mut self,
155        channel_indexes: Vec<i32>,
156        pixels: i32,
157        lines: i32,
158    ) -> Result<(), NanonisError> {
159        self.quick_send(
160            "Scan.BufferSet",
161            vec![
162                NanonisValue::ArrayI32(channel_indexes),
163                NanonisValue::I32(pixels),
164                NanonisValue::I32(lines),
165            ],
166            vec!["+*i", "i", "i"],
167            vec![],
168        )?;
169        Ok(())
170    }
171
172    /// Configure scan speed parameters.
173    ///
174    /// Sets the tip scanning speeds for both forward and backward scan directions.
175    /// You can specify either linear speed or time per line, and set speed ratios
176    /// between forward and backward scanning.
177    ///
178    /// # Arguments
179    /// * `forward_linear_speed_m_s` - Forward linear speed in m/s
180    /// * `backward_linear_speed_m_s` - Backward linear speed in m/s
181    /// * `forward_time_per_line_s` - Forward time per line in seconds
182    /// * `backward_time_per_line_s` - Backward time per line in seconds
183    /// * `keep_parameter_constant` - Which parameter to keep constant: 0=no change, 1=linear speed, 2=time per line
184    /// * `speed_ratio` - Backward tip speed relative to forward speed
185    ///
186    /// # Errors
187    /// Returns `NanonisError` if communication fails or invalid parameters provided.
188    ///
189    /// # Examples
190    /// ```no_run
191    /// use nanonis_rs::NanonisClient;
192    /// use nanonis_rs::scan::ScanConfig;
193    ///
194    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
195    ///
196    /// // Set 1 μm/s forward, 2 μm/s backward, keep linear speed constant
197    /// let config = ScanConfig {
198    ///     forward_linear_speed_m_s: 1e-6,
199    ///     backward_linear_speed_m_s: 2e-6,
200    ///     forward_time_per_line_s: 0.1,
201    ///     backward_time_per_line_s: 0.05,
202    ///     keep_parameter_constant: 1,
203    ///     speed_ratio: 2.0,
204    /// };
205    /// client.scan_config_set(config)?;
206    /// # Ok::<(), Box<dyn std::error::Error>>(())
207    /// ```
208    pub fn scan_config_set(&mut self, config: ScanConfig) -> Result<(), NanonisError> {
209        self.quick_send(
210            "Scan.SpeedSet",
211            vec![
212                NanonisValue::F32(config.forward_linear_speed_m_s),
213                NanonisValue::F32(config.backward_linear_speed_m_s),
214                NanonisValue::F32(config.forward_time_per_line_s),
215                NanonisValue::F32(config.backward_time_per_line_s),
216                NanonisValue::U16(config.keep_parameter_constant),
217                NanonisValue::F32(config.speed_ratio),
218            ],
219            vec!["f", "f", "f", "f", "H", "f"],
220            vec![],
221        )?;
222        Ok(())
223    }
224
225    /// Get the current scan speed parameters.
226    ///
227    /// Returns all scan speed configuration values including linear speeds,
228    /// time per line, and speed ratio settings.
229    ///
230    /// # Returns
231    /// A tuple containing:
232    /// - `f32` - Forward linear speed (m/s)
233    /// - `f32` - Backward linear speed (m/s)
234    /// - `f32` - Forward time per line (s)
235    /// - `f32` - Backward time per line (s)
236    /// - `u16` - Keep parameter constant (0=linear speed, 1=time per line)
237    /// - `f32` - Speed ratio (backward relative to forward)
238    ///
239    /// # Errors
240    /// Returns `NanonisError` if communication fails or protocol error occurs.
241    ///
242    /// # Examples
243    /// ```no_run
244    /// use nanonis_rs::NanonisClient;
245    ///
246    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
247    ///
248    /// let config = client.scan_speed_get()?;
249    ///
250    /// println!("Forward speed: {:.2e} m/s", config.forward_linear_speed_m_s);
251    /// println!("Backward speed: {:.2e} m/s", config.backward_linear_speed_m_s);
252    /// println!("Speed ratio: {:.1}", config.speed_ratio);
253    /// # Ok::<(), Box<dyn std::error::Error>>(())
254    /// ```
255    pub fn scan_speed_get(&mut self) -> Result<ScanConfig, NanonisError> {
256        let result = self.quick_send(
257            "Scan.SpeedGet",
258            vec![],
259            vec![],
260            vec!["f", "f", "f", "f", "H", "f"],
261        )?;
262
263        if result.len() >= 6 {
264            Ok(ScanConfig {
265                forward_linear_speed_m_s: result[0].as_f32()?,
266                backward_linear_speed_m_s: result[1].as_f32()?,
267                forward_time_per_line_s: result[2].as_f32()?,
268                backward_time_per_line_s: result[3].as_f32()?,
269                keep_parameter_constant: result[4].as_u16()?,
270                speed_ratio: result[5].as_f32()?,
271            })
272        } else {
273            Err(NanonisError::Protocol(
274                "Invalid scan speed response".to_string(),
275            ))
276        }
277    }
278
279    /// Get the current XY position during scanning.
280    ///
281    /// Returns the current values of the X and Y signals, useful for monitoring
282    /// tip position during scanning operations.
283    ///
284    /// # Arguments
285    /// * `wait_newest_data` - If `true`, discards first value and waits for fresh data
286    ///
287    /// # Returns
288    /// A tuple containing (X position in m, Y position in m)
289    ///
290    /// # Errors
291    /// Returns `NanonisError` if communication fails or protocol error occurs.
292    ///
293    /// # Examples
294    /// ```no_run
295    /// use nanonis_rs::NanonisClient;
296    ///
297    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
298    ///
299    /// // Get current position immediately
300    /// let (x, y) = client.scan_xy_pos_get(false)?;
301    /// println!("Current position: ({:.6}, {:.6}) m", x, y);
302    ///
303    /// // Wait for fresh position data
304    /// let (x, y) = client.scan_xy_pos_get(true)?;
305    /// # Ok::<(), Box<dyn std::error::Error>>(())
306    /// ```
307    pub fn scan_xy_pos_get(&mut self, wait_newest_data: bool) -> Result<(f32, f32), NanonisError> {
308        let wait_flag = if wait_newest_data { 1u32 } else { 0u32 };
309
310        let result = self.quick_send(
311            "Scan.XYPosGet",
312            vec![NanonisValue::U32(wait_flag)],
313            vec!["I"],
314            vec!["f", "f"],
315        )?;
316
317        if result.len() >= 2 {
318            Ok((result[0].as_f32()?, result[1].as_f32()?))
319        } else {
320            Err(NanonisError::Protocol(
321                "Invalid XY position response".to_string(),
322            ))
323        }
324    }
325
326    /// Save the current scan data buffer to file.
327    ///
328    /// Saves the current scan data into a file. If `wait_until_saved` is true,
329    /// the function waits for the save operation to complete before returning.
330    ///
331    /// # Arguments
332    /// * `wait_until_saved` - If `true`, waits for save completion before returning
333    /// * `timeout_ms` - Timeout in milliseconds (-1 for indefinite wait)
334    ///
335    /// # Returns
336    /// `true` if timeout occurred while waiting for save completion, `false` otherwise
337    ///
338    /// # Errors
339    /// Returns `NanonisError` if communication fails or protocol error occurs.
340    ///
341    /// # Examples
342    /// ```no_run
343    /// use nanonis_rs::NanonisClient;
344    ///
345    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
346    ///
347    /// // Save immediately without waiting
348    /// let timed_out = client.scan_save(false, 5000)?;
349    ///
350    /// // Save and wait up to 30 seconds for completion
351    /// let timed_out = client.scan_save(true, 30000)?;
352    /// if timed_out {
353    ///     println!("Save operation timed out");
354    /// }
355    /// # Ok::<(), Box<dyn std::error::Error>>(())
356    /// ```
357    pub fn scan_save(
358        &mut self,
359        wait_until_saved: bool,
360        timeout_ms: i32,
361    ) -> Result<bool, NanonisError> {
362        let wait_flag = if wait_until_saved { 1u32 } else { 0u32 };
363
364        let result = self.quick_send(
365            "Scan.Save",
366            vec![NanonisValue::U32(wait_flag), NanonisValue::I32(timeout_ms)],
367            vec!["I", "i"],
368            vec!["I"],
369        )?;
370
371        match result.first() {
372            Some(value) => Ok(value.as_u32()? == 1),
373            None => Err(NanonisError::Protocol(
374                "No save status returned".to_string(),
375            )),
376        }
377    }
378
379    /// Get scan frame data for a specific channel and direction.
380    ///
381    /// Returns the complete 2D scan data array for the selected channel.
382    /// The channel must be one of the channels configured in the scan buffer.
383    ///
384    /// # Arguments
385    /// * `channel_index` - Index of channel to retrieve data from (must be in acquired channels)
386    /// * `data_direction` - Data direction: `true` for forward, `false` for backward
387    ///
388    /// # Returns
389    /// A tuple containing:
390    /// - `String` - Channel name
391    /// - `Vec<Vec<f32>>` - 2D scan data array \[rows\]\[columns\]
392    /// - `bool` - Scan direction: `true` for up, `false` for down
393    ///
394    /// # Errors
395    /// Returns `NanonisError` if:
396    /// - Invalid channel index (not in acquired channels)
397    /// - Communication fails or protocol error occurs
398    ///
399    /// # Examples
400    /// ```no_run
401    /// use nanonis_rs::NanonisClient;
402    ///
403    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
404    ///
405    /// // Get forward scan data for channel 0
406    /// let (channel_name, data, scan_up) = client.scan_frame_data_grab(0, true)?;
407    /// println!("Channel: {}, Direction: {}", channel_name, if scan_up { "up" } else { "down" });
408    /// println!("Data size: {}x{}", data.len(), data[0].len());
409    ///
410    /// // Get backward scan data
411    /// let (_, back_data, _) = client.scan_frame_data_grab(0, false)?;
412    /// # Ok::<(), Box<dyn std::error::Error>>(())
413    /// ```
414    pub fn scan_frame_data_grab(
415        &mut self,
416        channel_index: u32,
417        data_direction: bool,
418    ) -> Result<(String, Vec<Vec<f32>>, bool), NanonisError> {
419        let direction_flag = if data_direction { 1u32 } else { 0u32 };
420
421        let result = self.quick_send(
422            "Scan.FrameDataGrab",
423            vec![
424                NanonisValue::U32(channel_index),
425                NanonisValue::U32(direction_flag),
426            ],
427            vec!["I", "I"],
428            vec!["i", "*-c", "i", "i", "2f", "I"],
429        )?;
430
431        if result.len() >= 6 {
432            let channel_name = result[1].as_string()?.to_string();
433
434            // The protocol layer already parses "2f" into a 2D array
435            let data_2d = result[4].as_f32_2d_array()?.clone();
436
437            let scan_direction = result[5].as_u32()? == 1;
438            Ok((channel_name, data_2d, scan_direction))
439        } else {
440            Err(NanonisError::Protocol(
441                "Invalid frame data response".to_string(),
442            ))
443        }
444    }
445
446    /// Wait for the End-of-Scan.
447    ///
448    /// Waits for the current scan to complete or timeout to occur, whichever comes first.
449    /// This is useful for synchronizing operations with scan completion.
450    ///
451    /// # Arguments
452    /// * `timeout` - Timeout duration (-1 for indefinite wait)
453    ///
454    /// # Returns
455    /// A tuple containing:
456    /// - `bool` - `true` if timeout occurred, `false` if scan completed normally
457    /// - `String` - File path where data was auto-saved (empty if no auto-save)
458    ///
459    /// # Errors
460    /// Returns `NanonisError` if communication fails or protocol error occurs.
461    ///
462    /// # Examples
463    /// ```no_run
464    /// use nanonis_rs::NanonisClient;
465    /// use nanonis_rs::scan::{ScanAction, ScanDirection};
466    /// use std::time::Duration;
467    ///
468    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
469    ///
470    /// // Start a scan
471    /// client.scan_action(ScanAction::Start, ScanDirection::Up)?;
472    ///
473    /// // Wait for scan to complete (up to 5 minutes)
474    /// let (timed_out, file_path) = client.scan_wait_end_of_scan(Duration::from_secs(300))?;
475    ///
476    /// if timed_out {
477    ///     println!("Scan timed out after 5 minutes");
478    /// } else {
479    ///     println!("Scan completed");
480    ///     if !file_path.is_empty() {
481    ///         println!("Data saved to: {}", file_path);
482    ///     }
483    /// }
484    /// # Ok::<(), Box<dyn std::error::Error>>(())
485    /// ```
486    pub fn scan_wait_end_of_scan(
487        &mut self,
488        timeout: Duration,
489    ) -> Result<(bool, String), NanonisError> {
490        let result = self.quick_send(
491            "Scan.WaitEndOfScan",
492            vec![NanonisValue::I32(timeout.as_millis() as i32)],
493            vec!["i"],
494            vec!["I", "I", "*-c"],
495        )?;
496
497        if result.len() >= 3 {
498            let timeout_occurred = result[0].as_u32()? == 1;
499            let file_path = result[2].as_string()?.to_string();
500            Ok((timeout_occurred, file_path))
501        } else {
502            Err(NanonisError::Protocol(
503                "Invalid scan wait response".to_string(),
504            ))
505        }
506    }
507
508    /// Get scan properties configuration.
509    ///
510    /// Returns current scan properties including continuous scan, bouncy scan,
511    /// autosave, series name, comment, modules names, and autopaste settings.
512    ///
513    /// # Returns
514    /// `ScanProps` structure containing all scan property settings
515    ///
516    /// # Errors
517    /// Returns `NanonisError` if communication fails or protocol error occurs.
518    ///
519    /// # Examples
520    /// ```no_run
521    /// use nanonis_rs::NanonisClient;
522    ///
523    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
524    ///
525    /// let props = client.scan_props_get()?;
526    /// println!("Continuous scan: {:?}", props.continuous_scan);
527    /// println!("Bouncy scan: {:?}", props.bouncy_scan);
528    /// println!("Series name: {}", props.series_name);
529    /// # Ok::<(), Box<dyn std::error::Error>>(())
530    /// ```
531    pub fn scan_props_get(&mut self) -> Result<ScanProps, NanonisError> {
532        let result = self.quick_send(
533            "Scan.PropsGet",
534            vec![],
535            vec![],
536            vec!["I", "I", "I", "i", "*-c", "i", "*-c", "i", "i", "*+c", "i", "*+i", "i", "i", "*+c", "I"],
537        )?;
538
539        if result.len() >= 16 {
540            // Index 0: Continuous scan (0=Off, 1=On)
541            let continuous_scan = result[0].as_u32()? == 1;
542
543            // Index 1: Bouncy scan (0=Off, 1=On)
544            let bouncy_scan = result[1].as_u32()? == 1;
545
546            // Index 2: Autosave (0=All, 1=Next, 2=Off)
547            let autosave = AutosaveMode::try_from(result[2].as_u32()?)?;
548
549            // Index 3: series_name size (i)
550            // Index 4: series_name string (*-c)
551            let series_name = result[4].as_string()?.to_string();
552
553            // Index 5: comment size (i)
554            // Index 6: comment string (*-c)
555            let comment = result[6].as_string()?.to_string();
556
557            // Index 7: modules_names size (i)
558            // Index 8: modules_names count (i)
559            // Index 9: modules_names array (*+c)
560            let modules_names = result[9].as_string_array()?.to_vec();
561
562            // Index 10: num_params_per_module array size (i)
563            // Index 11: num_params_per_module array (*+i)
564            let num_params_per_module = result[11].as_i32_array()?.to_vec();
565
566            // Index 12: parameters rows (i)
567            let rows = result[12].as_i32()?;
568            // Index 13: parameters columns (i)
569            let cols = result[13].as_i32()?;
570            // Index 14: parameters 2D array (*+c)
571            let params_flat = result[14].as_string_array()?;
572
573            // Convert flat array to 2D (rows x cols)
574            let mut parameters = Vec::new();
575            for row in 0..rows as usize {
576                let mut row_params = Vec::new();
577                for col in 0..cols as usize {
578                    let idx = row * (cols as usize) + col;
579                    if idx < params_flat.len() {
580                        row_params.push(params_flat[idx].clone());
581                    }
582                }
583                parameters.push(row_params);
584            }
585
586            // Index 15: Autopaste (0=All, 1=Next, 2=Off)
587            let autopaste = AutopasteMode::try_from(result[15].as_u32()?)?;
588
589            Ok(ScanProps {
590                continuous_scan,
591                bouncy_scan,
592                autosave,
593                series_name,
594                comment,
595                modules_names,
596                num_params_per_module,
597                parameters,
598                autopaste,
599            })
600        } else {
601            Err(NanonisError::Protocol(format!(
602                "Invalid scan props response: expected 16 values, got {}",
603                result.len()
604            )))
605        }
606    }
607
608    /// Set scan properties configuration.
609    ///
610    /// Configures scan parameters including continuous scan, bouncy scan,
611    /// autosave behavior, series name, comment, and autopaste settings.
612    /// Use `ScanPropsBuilder` to set only the properties you want to change.
613    ///
614    /// # Arguments
615    /// * `builder` - Builder with properties to set. Fields set to `None` will not be changed.
616    ///
617    /// # Errors
618    /// Returns `NanonisError` if communication fails or invalid parameters provided.
619    ///
620    /// # Examples
621    /// ```no_run
622    /// use nanonis_rs::NanonisClient;
623    /// use nanonis_rs::scan::{ScanPropsBuilder, AutosaveMode, AutopasteMode};
624    ///
625    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
626    ///
627    /// // Set only specific properties using the builder
628    /// let builder = ScanPropsBuilder::new()
629    ///     .continuous_scan(true)        // Enable continuous scan
630    ///     .bouncy_scan(true)            // Enable bouncy scan
631    ///     .autosave(AutosaveMode::Off); // Disable autosave
632    ///
633    /// client.scan_props_set(builder)?;
634    /// # Ok::<(), Box<dyn std::error::Error>>(())
635    /// ```
636    pub fn scan_props_set(&mut self, builder: ScanPropsBuilder) -> Result<(), NanonisError> {
637        // Convert boolean options to u32 (0=no change, 1=On, 2=Off)
638        let continuous_flag = match builder.continuous_scan {
639            None => 0u32,
640            Some(true) => 1u32,
641            Some(false) => 2u32,
642        };
643
644        let bouncy_flag = match builder.bouncy_scan {
645            None => 0u32,
646            Some(true) => 1u32,
647            Some(false) => 2u32,
648        };
649
650        let autosave_flag = match builder.autosave {
651            None => 0u32,
652            Some(mode) => mode.into(),
653        };
654
655        let autopaste_flag = match builder.autopaste {
656            None => 0u32,
657            Some(mode) => mode.into(),
658        };
659
660        // For strings/arrays: empty string/array means no change
661        let series_name = builder.series_name.unwrap_or_default();
662        let comment = builder.comment.unwrap_or_default();
663        let modules_names = builder.modules_names.unwrap_or_default();
664
665        self.quick_send(
666            "Scan.PropsSet",
667            vec![
668                NanonisValue::U32(continuous_flag),
669                NanonisValue::U32(bouncy_flag),
670                NanonisValue::U32(autosave_flag),
671                NanonisValue::String(series_name),
672                NanonisValue::String(comment),
673                NanonisValue::ArrayString(modules_names),
674                NanonisValue::U32(autopaste_flag),
675            ],
676            vec!["I", "I", "I", "+*c", "+*c", "+*c", "I"],
677            vec![],
678        )?;
679
680        Ok(())
681    }
682
683    /// Paste background image at specified location.
684    ///
685    /// Pastes a previously copied background image to the scan buffer at the
686    /// specified position. This is used for background subtraction operations.
687    ///
688    /// # Arguments
689    /// * `x` - X position in meters for paste location
690    /// * `y` - Y position in meters for paste location
691    ///
692    /// # Errors
693    /// Returns `NanonisError` if communication fails or no background is available.
694    pub fn scan_background_paste(&mut self, x: f64, y: f64) -> Result<(), NanonisError> {
695        self.quick_send(
696            "Scan.BackgroundPaste",
697            vec![NanonisValue::F64(x), NanonisValue::F64(y)],
698            vec!["d", "d"],
699            vec![],
700        )?;
701        Ok(())
702    }
703
704    /// Delete the stored background image.
705    ///
706    /// Removes the background image from memory, freeing resources and
707    /// disabling background subtraction until a new background is captured.
708    ///
709    /// # Errors
710    /// Returns `NanonisError` if communication fails.
711    pub fn scan_background_delete(&mut self) -> Result<(), NanonisError> {
712        self.quick_send("Scan.BackgroundDelete", vec![], vec![], vec![])?;
713        Ok(())
714    }
715}