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_f64()?;
55            let center_y = result[1].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            let rows = result[2].as_i32()? as usize;
434            let cols = result[3].as_i32()? as usize;
435
436            // Parse 2D array from flat f32 array
437            let flat_data = result[4].as_f32_array()?;
438            let mut data_2d = Vec::with_capacity(rows);
439
440            for row in 0..rows {
441                let start_idx = row * cols;
442                let end_idx = start_idx + cols;
443                data_2d.push(flat_data[start_idx..end_idx].to_vec());
444            }
445
446            let scan_direction = result[5].as_u32()? == 1;
447            Ok((channel_name, data_2d, scan_direction))
448        } else {
449            Err(NanonisError::Protocol(
450                "Invalid frame data response".to_string(),
451            ))
452        }
453    }
454
455    /// Wait for the End-of-Scan.
456    ///
457    /// Waits for the current scan to complete or timeout to occur, whichever comes first.
458    /// This is useful for synchronizing operations with scan completion.
459    ///
460    /// # Arguments
461    /// * `timeout` - Timeout duration (-1 for indefinite wait)
462    ///
463    /// # Returns
464    /// A tuple containing:
465    /// - `bool` - `true` if timeout occurred, `false` if scan completed normally
466    /// - `String` - File path where data was auto-saved (empty if no auto-save)
467    ///
468    /// # Errors
469    /// Returns `NanonisError` if communication fails or protocol error occurs.
470    ///
471    /// # Examples
472    /// ```no_run
473    /// use nanonis_rs::NanonisClient;
474    /// use nanonis_rs::scan::{ScanAction, ScanDirection};
475    /// use std::time::Duration;
476    ///
477    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
478    ///
479    /// // Start a scan
480    /// client.scan_action(ScanAction::Start, ScanDirection::Up)?;
481    ///
482    /// // Wait for scan to complete (up to 5 minutes)
483    /// let (timed_out, file_path) = client.scan_wait_end_of_scan(Duration::from_secs(300))?;
484    ///
485    /// if timed_out {
486    ///     println!("Scan timed out after 5 minutes");
487    /// } else {
488    ///     println!("Scan completed");
489    ///     if !file_path.is_empty() {
490    ///         println!("Data saved to: {}", file_path);
491    ///     }
492    /// }
493    /// # Ok::<(), Box<dyn std::error::Error>>(())
494    /// ```
495    pub fn scan_wait_end_of_scan(
496        &mut self,
497        timeout: Duration,
498    ) -> Result<(bool, String), NanonisError> {
499        let result = self.quick_send(
500            "Scan.WaitEndOfScan",
501            vec![NanonisValue::I32(timeout.as_millis() as i32)],
502            vec!["i"],
503            vec!["I", "I", "*-c"],
504        )?;
505
506        if result.len() >= 3 {
507            let timeout_occurred = result[0].as_u32()? == 1;
508            let file_path = result[2].as_string()?.to_string();
509            Ok((timeout_occurred, file_path))
510        } else {
511            Err(NanonisError::Protocol(
512                "Invalid scan wait response".to_string(),
513            ))
514        }
515    }
516
517    /// Get scan properties configuration.
518    ///
519    /// Returns current scan properties including continuous scan, bouncy scan,
520    /// autosave, series name, comment, modules names, and autopaste settings.
521    ///
522    /// # Returns
523    /// `ScanProps` structure containing all scan property settings
524    ///
525    /// # Errors
526    /// Returns `NanonisError` if communication fails or protocol error occurs.
527    ///
528    /// # Examples
529    /// ```no_run
530    /// use nanonis_rs::NanonisClient;
531    ///
532    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
533    ///
534    /// let props = client.scan_props_get()?;
535    /// println!("Continuous scan: {:?}", props.continuous_scan);
536    /// println!("Bouncy scan: {:?}", props.bouncy_scan);
537    /// println!("Series name: {}", props.series_name);
538    /// # Ok::<(), Box<dyn std::error::Error>>(())
539    /// ```
540    pub fn scan_props_get(&mut self) -> Result<ScanProps, NanonisError> {
541        let result = self.quick_send(
542            "Scan.PropsGet",
543            vec![],
544            vec![],
545            vec!["I", "I", "I", "i", "*-c", "i", "*-c", "i", "i", "*+c", "i", "*+i", "i", "i", "*+c", "I"],
546        )?;
547
548        if result.len() >= 16 {
549            // Index 0: Continuous scan (0=Off, 1=On)
550            let continuous_scan = result[0].as_u32()? == 1;
551
552            // Index 1: Bouncy scan (0=Off, 1=On)
553            let bouncy_scan = result[1].as_u32()? == 1;
554
555            // Index 2: Autosave (0=All, 1=Next, 2=Off)
556            let autosave = AutosaveMode::try_from(result[2].as_u32()?)?;
557
558            // Index 3: series_name size (i)
559            // Index 4: series_name string (*-c)
560            let series_name = result[4].as_string()?.to_string();
561
562            // Index 5: comment size (i)
563            // Index 6: comment string (*-c)
564            let comment = result[6].as_string()?.to_string();
565
566            // Index 7: modules_names size (i)
567            // Index 8: modules_names count (i)
568            // Index 9: modules_names array (*+c)
569            let modules_names = result[9].as_string_array()?.to_vec();
570
571            // Index 10: num_params_per_module array size (i)
572            // Index 11: num_params_per_module array (*+i)
573            let num_params_per_module = result[11].as_i32_array()?.to_vec();
574
575            // Index 12: parameters rows (i)
576            let rows = result[12].as_i32()?;
577            // Index 13: parameters columns (i)
578            let cols = result[13].as_i32()?;
579            // Index 14: parameters 2D array (*+c)
580            let params_flat = result[14].as_string_array()?;
581
582            // Convert flat array to 2D (rows x cols)
583            let mut parameters = Vec::new();
584            for row in 0..rows as usize {
585                let mut row_params = Vec::new();
586                for col in 0..cols as usize {
587                    let idx = row * (cols as usize) + col;
588                    if idx < params_flat.len() {
589                        row_params.push(params_flat[idx].clone());
590                    }
591                }
592                parameters.push(row_params);
593            }
594
595            // Index 15: Autopaste (0=All, 1=Next, 2=Off)
596            let autopaste = AutopasteMode::try_from(result[15].as_u32()?)?;
597
598            Ok(ScanProps {
599                continuous_scan,
600                bouncy_scan,
601                autosave,
602                series_name,
603                comment,
604                modules_names,
605                num_params_per_module,
606                parameters,
607                autopaste,
608            })
609        } else {
610            Err(NanonisError::Protocol(format!(
611                "Invalid scan props response: expected 16 values, got {}",
612                result.len()
613            )))
614        }
615    }
616
617    /// Set scan properties configuration.
618    ///
619    /// Configures scan parameters including continuous scan, bouncy scan,
620    /// autosave behavior, series name, comment, and autopaste settings.
621    /// Use `ScanPropsBuilder` to set only the properties you want to change.
622    ///
623    /// # Arguments
624    /// * `builder` - Builder with properties to set. Fields set to `None` will not be changed.
625    ///
626    /// # Errors
627    /// Returns `NanonisError` if communication fails or invalid parameters provided.
628    ///
629    /// # Examples
630    /// ```no_run
631    /// use nanonis_rs::NanonisClient;
632    /// use nanonis_rs::scan::{ScanPropsBuilder, AutosaveMode, AutopasteMode};
633    ///
634    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
635    ///
636    /// // Set only specific properties using the builder
637    /// let builder = ScanPropsBuilder::new()
638    ///     .continuous_scan(true)        // Enable continuous scan
639    ///     .bouncy_scan(true)            // Enable bouncy scan
640    ///     .autosave(AutosaveMode::Off); // Disable autosave
641    ///
642    /// client.scan_props_set(builder)?;
643    /// # Ok::<(), Box<dyn std::error::Error>>(())
644    /// ```
645    pub fn scan_props_set(&mut self, builder: ScanPropsBuilder) -> Result<(), NanonisError> {
646        // Convert boolean options to u32 (0=no change, 1=On, 2=Off)
647        let continuous_flag = match builder.continuous_scan {
648            None => 0u32,
649            Some(true) => 1u32,
650            Some(false) => 2u32,
651        };
652
653        let bouncy_flag = match builder.bouncy_scan {
654            None => 0u32,
655            Some(true) => 1u32,
656            Some(false) => 2u32,
657        };
658
659        let autosave_flag = match builder.autosave {
660            None => 0u32,
661            Some(mode) => mode.into(),
662        };
663
664        let autopaste_flag = match builder.autopaste {
665            None => 0u32,
666            Some(mode) => mode.into(),
667        };
668
669        // For strings/arrays: empty string/array means no change
670        let series_name = builder.series_name.unwrap_or_default();
671        let comment = builder.comment.unwrap_or_default();
672        let modules_names = builder.modules_names.unwrap_or_default();
673
674        self.quick_send(
675            "Scan.PropsSet",
676            vec![
677                NanonisValue::U32(continuous_flag),
678                NanonisValue::U32(bouncy_flag),
679                NanonisValue::U32(autosave_flag),
680                NanonisValue::String(series_name),
681                NanonisValue::String(comment),
682                NanonisValue::ArrayString(modules_names),
683                NanonisValue::U32(autopaste_flag),
684            ],
685            vec!["I", "I", "I", "+*c", "+*c", "+*c", "I"],
686            vec![],
687        )?;
688
689        Ok(())
690    }
691
692    /// Paste background image at specified location.
693    ///
694    /// Pastes a previously copied background image to the scan buffer at the
695    /// specified position. This is used for background subtraction operations.
696    ///
697    /// # Arguments
698    /// * `x` - X position in meters for paste location
699    /// * `y` - Y position in meters for paste location
700    ///
701    /// # Errors
702    /// Returns `NanonisError` if communication fails or no background is available.
703    pub fn scan_background_paste(&mut self, x: f64, y: f64) -> Result<(), NanonisError> {
704        self.quick_send(
705            "Scan.BackgroundPaste",
706            vec![NanonisValue::F64(x), NanonisValue::F64(y)],
707            vec!["d", "d"],
708            vec![],
709        )?;
710        Ok(())
711    }
712
713    /// Delete the stored background image.
714    ///
715    /// Removes the background image from memory, freeing resources and
716    /// disabling background subtraction until a new background is captured.
717    ///
718    /// # Errors
719    /// Returns `NanonisError` if communication fails.
720    pub fn scan_background_delete(&mut self) -> Result<(), NanonisError> {
721        self.quick_send("Scan.BackgroundDelete", vec![], vec![], vec![])?;
722        Ok(())
723    }
724}