catia 0.1.0

Rust COM bindings and helpers for CATIA V5
Documentation
use windows::core::Result as WinResult;
use windows::Win32::System::Com::{CLSIDFromProgID, CoCreateInstance, CLSCTX_ALL, IDispatch};
use windows::core::PCWSTR;

use crate::com::{ComObject};
use crate::variant::{variant_bool, variant_bstr, variant_i4, variant_to_string, variant_to_dispatch};

pub struct CatiaApp { pub inner: ComObject }

impl CatiaApp {
    pub fn new() -> WinResult<Self> {
        let clsid = unsafe { CLSIDFromProgID(windows::core::w!("CATIA.Application"))? };
        let disp: IDispatch = unsafe { CoCreateInstance(&clsid, None, CLSCTX_ALL)? };
        Ok(Self { inner: ComObject::new(disp) })
    }

    fn to_wide_null(s: &str) -> Vec<u16> {
        s.encode_utf16().chain(std::iter::once(0)).collect()
    }

    pub fn with_progid(progid: &str) -> WinResult<Self> {
        let wide = Self::to_wide_null(progid);
        let clsid = unsafe { CLSIDFromProgID(PCWSTR(wide.as_ptr()))? };
        let disp: IDispatch = unsafe { CoCreateInstance(&clsid, None, CLSCTX_ALL)? };
        Ok(Self { inner: ComObject::new(disp) })
    }

    pub fn set_visible(&self, v: bool) -> WinResult<()> {
        self.inner.put("Visible", variant_bool(v))
    }

    pub fn version_string(&self) -> WinResult<Option<String>> {
        let v = self.inner.get("Version")?;
        let s = variant_to_string(&v)?;
        Ok(s)
    }

    pub fn documents(&self) -> WinResult<ComObject> {
        let v = self.inner.get("Documents")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            Ok(ComObject::new(disp))
        } else {
            // If missing, return an error to signal issue
            Err(windows::core::Error::from_win32())
        }
    }
}

// 统一接口抽象,便于真实与模拟实现切换
pub trait CatiaApi {
    fn set_visible(&self, v: bool) -> WinResult<()>;
    fn version_string(&self) -> WinResult<Option<String>>;
    fn documents_count(&self) -> WinResult<Option<i32>>;
    fn active_document_name(&self) -> WinResult<Option<String>>;
    fn add_document(&self, kind: &str) -> WinResult<Option<String>>;
    fn open_document(&self, path: &str) -> WinResult<Option<String>>;
    fn read_document(&self, path: &str) -> WinResult<Option<String>>;
    fn selection_count(&self) -> WinResult<Option<i32>>;
    fn selection_clear(&self) -> WinResult<()>;
    fn selection_search(&self, query: &str) -> WinResult<Option<i32>>;
    fn file_selection_box(&self, title: &str, extension: &str, mode: i32) -> WinResult<Option<String>>;
    fn save_active_document(&self) -> WinResult<bool>;
    fn save_active_document_as(&self, path: &str) -> WinResult<bool>;
    fn close_active_document(&self) -> WinResult<bool>;
    fn export_active_document(&self, path: &str, fmt: &str) -> WinResult<bool>;
    fn selection_items(&self) -> WinResult<Vec<(String, String)>>;
}

impl CatiaApi for CatiaApp {
    fn set_visible(&self, v: bool) -> WinResult<()> { self.set_visible(v) }
    fn version_string(&self) -> WinResult<Option<String>> { self.version_string() }
    fn documents_count(&self) -> WinResult<Option<i32>> {
        use crate::variant::variant_to_i32;
        let docs = self.documents()?;
        let count_v = docs.get("Count")?;
        variant_to_i32(&count_v)
    }
    fn active_document_name(&self) -> WinResult<Option<String>> {
        let v = self.inner.get("ActiveDocument")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            let doc = ComObject::new(disp);
            let name_v = doc.get("Name")?;
            crate::variant::variant_to_string(&name_v)
        } else {
            Ok(None)
        }
    }
    fn add_document(&self, kind: &str) -> WinResult<Option<String>> {
        let docs = self.documents()?;
        let r = docs.call("Add", vec![variant_bstr(kind)])?;
        if let Some(disp) = variant_to_dispatch(&r)? {
            let doc = ComObject::new(disp);
            let name_v = doc.get("Name")?;
            crate::variant::variant_to_string(&name_v)
        } else {
            Ok(None)
        }
    }
    fn open_document(&self, path: &str) -> WinResult<Option<String>> {
        let docs = self.documents()?;
        let r = docs.call("Open", vec![variant_bstr(path)])?;
        if let Some(disp) = variant_to_dispatch(&r)? {
            let doc = ComObject::new(disp);
            let name_v = doc.get("Name")?;
            crate::variant::variant_to_string(&name_v)
        } else {
            Ok(None)
        }
    }
    fn read_document(&self, path: &str) -> WinResult<Option<String>> {
        let docs = self.documents()?;
        let r = docs.call("Read", vec![variant_bstr(path)])?;
        if let Some(disp) = variant_to_dispatch(&r)? {
            let doc = ComObject::new(disp);
            let name_v = doc.get("Name")?;
            crate::variant::variant_to_string(&name_v)
        } else {
            Ok(None)
        }
    }
    fn selection_count(&self) -> WinResult<Option<i32>> {
        use crate::variant::variant_to_i32;
        let v = self.inner.get("ActiveDocument")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            let doc = ComObject::new(disp);
            let sel_v = doc.get("Selection")?;
            if let Some(sel_disp) = crate::variant::variant_to_dispatch(&sel_v)? {
                let sel = ComObject::new(sel_disp);
                let count_v = sel.get("Count")?;
                variant_to_i32(&count_v)
            } else {
                Ok(Some(0))
            }
        } else {
            Ok(Some(0))
        }
    }
    fn selection_clear(&self) -> WinResult<()> {
        let v = self.inner.get("ActiveDocument")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            let doc = ComObject::new(disp);
            let sel_v = doc.get("Selection")?;
            if let Some(sel_disp) = crate::variant::variant_to_dispatch(&sel_v)? {
                let sel = ComObject::new(sel_disp);
                let _ = sel.call("Clear", vec![])?;
                Ok(())
            } else {
                Ok(())
            }
        } else {
            Ok(())
        }
    }
    fn selection_search(&self, query: &str) -> WinResult<Option<i32>> {
        use crate::variant::variant_to_i32;
        let v = self.inner.get("ActiveDocument")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            let doc = ComObject::new(disp);
            let sel_v = doc.get("Selection")?;
            if let Some(sel_disp) = crate::variant::variant_to_dispatch(&sel_v)? {
                let sel = ComObject::new(sel_disp);
                let _ = sel.call("Search", vec![variant_bstr(query)])?;
                let count_v = sel.get("Count")?;
                variant_to_i32(&count_v)
            } else {
                Ok(Some(0))
            }
        } else {
            Ok(Some(0))
        }
    }
    fn file_selection_box(&self, title: &str, extension: &str, mode: i32) -> WinResult<Option<String>> {
        let r = self.inner.call("FileSelectionBox", vec![variant_bstr(title), variant_bstr(extension), variant_i4(mode)])?;
        crate::variant::variant_to_string(&r)
    }
    fn save_active_document(&self) -> WinResult<bool> {
        let v = self.inner.get("ActiveDocument")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            let doc = ComObject::new(disp);
            let _ = doc.call("Save", vec![])?;
            Ok(true)
        } else {
            Ok(false)
        }
    }
    fn save_active_document_as(&self, path: &str) -> WinResult<bool> {
        let v = self.inner.get("ActiveDocument")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            let doc = ComObject::new(disp);
            let _ = doc.call("SaveAs", vec![variant_bstr(path)])?;
            Ok(true)
        } else {
            Ok(false)
        }
    }
    fn export_active_document(&self, path: &str, fmt: &str) -> WinResult<bool> {
        let v = self.inner.get("ActiveDocument")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            let doc = ComObject::new(disp);
            let _ = doc.call("ExportData", vec![variant_bstr(path), variant_bstr(fmt)])?;
            Ok(true)
        } else {
            Ok(false)
        }
    }
    fn selection_items(&self) -> WinResult<Vec<(String, String)>> {
        use crate::variant::variant_to_i32;
        let mut items: Vec<(String, String)> = Vec::new();
        let v = self.inner.get("ActiveDocument")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            let doc = ComObject::new(disp);
            let sel_v = doc.get("Selection")?;
            if let Some(sel_disp) = crate::variant::variant_to_dispatch(&sel_v)? {
                let sel = ComObject::new(sel_disp);
                let count_v = sel.get("Count")?;
                let count = variant_to_i32(&count_v)?.unwrap_or(0);
                for i in 1..=count {
                    let elem_v = sel.call("Item2", vec![variant_i4(i)])?;
                    if let Some(elem_disp) = crate::variant::variant_to_dispatch(&elem_v)? {
                        let elem = ComObject::new(elem_disp);
                        let t_v = elem.get("Type")?;
                        let v_v = elem.get("Value")?;
                        let t = crate::variant::variant_to_string(&t_v)?.unwrap_or_default();
                        let val = crate::variant::variant_to_string(&v_v)?.unwrap_or_default();
                        items.push((t, val));
                    }
                }
            }
        }
        Ok(items)
    }
    fn close_active_document(&self) -> WinResult<bool> {
        let v = self.inner.get("ActiveDocument")?;
        if let Some(disp) = variant_to_dispatch(&v)? {
            let doc = ComObject::new(disp);
            let _ = doc.call("Close", vec![])?;
            Ok(true)
        } else {
            Ok(false)
        }
    }
}

pub struct CatiaMock;
impl CatiaMock {
    pub fn new() -> Self { Self }
}

impl CatiaApi for CatiaMock {
    fn set_visible(&self, _v: bool) -> WinResult<()> { println!("[MOCK] Set Visible: {}", _v); Ok(()) }
    fn version_string(&self) -> WinResult<Option<String>> { Ok(Some("V5R2020 (mock)".to_string())) }
    fn documents_count(&self) -> WinResult<Option<i32>> { Ok(Some(3)) }
    fn active_document_name(&self) -> WinResult<Option<String>> { Ok(Some("MockDocument.CATPart".to_string())) }
    fn add_document(&self, kind: &str) -> WinResult<Option<String>> {
        println!("[MOCK] Add Document: {}", kind);
        let name = match kind {
            "Part" => "Part1.CATPart",
            "Product" => "Product1.CATProduct",
            "Drawing" => "Drawing1.CATDrawing",
            _ => "Unknown1.CATDoc",
        };
        Ok(Some(name.to_string()))
    }
    fn open_document(&self, path: &str) -> WinResult<Option<String>> {
        println!("[MOCK] Open Document: {}", path);
        let name = std::path::Path::new(path)
            .file_name()
            .and_then(|s| s.to_str())
            .unwrap_or("OpenedDoc.CATDoc")
            .to_string();
        Ok(Some(name))
    }
    fn read_document(&self, path: &str) -> WinResult<Option<String>> {
        println!("[MOCK] Read Document: {}", path);
        let name = std::path::Path::new(path)
            .file_name()
            .and_then(|s| s.to_str())
            .unwrap_or("ReadDoc.CATDoc")
            .to_string();
        Ok(Some(name))
    }
    fn selection_count(&self) -> WinResult<Option<i32>> { Ok(Some(2)) }
    fn selection_clear(&self) -> WinResult<()> { println!("[MOCK] Selection.Clear()"); Ok(()) }
    fn selection_search(&self, query: &str) -> WinResult<Option<i32>> {
        println!("[MOCK] Selection.Search(\"{}\")", query);
        let count = if query.to_lowercase().contains("line") { 3 } else { 1 };
        Ok(Some(count))
    }
    fn file_selection_box(&self, title: &str, extension: &str, mode: i32) -> WinResult<Option<String>> {
        println!("[MOCK] FileSelectionBox(title='{}', ext='{}', mode={})", title, extension, mode);
        Ok(Some(format!("C\\\\Mock\\\\selected.{}", extension)))
    }
    fn save_active_document(&self) -> WinResult<bool> {
        println!("[MOCK] ActiveDocument.Save()");
        Ok(true)
    }
    fn save_active_document_as(&self, path: &str) -> WinResult<bool> {
        println!("[MOCK] Save ActiveDocument As: {}", path);
        Ok(true)
    }
    fn close_active_document(&self) -> WinResult<bool> {
        println!("[MOCK] Close ActiveDocument");
        Ok(true)
    }
    fn export_active_document(&self, path: &str, fmt: &str) -> WinResult<bool> {
        println!("[MOCK] ActiveDocument.ExportData(path='{}', fmt='{}')", path, fmt);
        Ok(true)
    }
    fn selection_items(&self) -> WinResult<Vec<(String, String)>> {
        println!("[MOCK] Selection.Items()");
        Ok(vec![
            ("Point".to_string(), "Point.1".to_string()),
            ("Line".to_string(), "Line.1".to_string()),
        ])
    }
}