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, variant_empty, variant_bstr_array};
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 {
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 documents_names(&self) -> WinResult<Vec<String>>;
fn active_document_name(&self) -> WinResult<Option<String>>;
fn active_document_path(&self) -> WinResult<Option<String>>;
fn windows_count(&self) -> WinResult<Option<i32>>;
fn windows_names(&self) -> WinResult<Vec<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)>>;
fn close_all_documents(&self) -> WinResult<bool>;
fn selection_select_element2(&self, types: &[&str], message: &str, multi: bool) -> WinResult<Option<i32>>;
fn selection_item_detail(&self, index_one_based: i32) -> WinResult<Option<(String, String, Option<String>)>>;
fn selection_details(&self) -> WinResult<Vec<(String, String, Option<String>)>>;
fn windows_activate(&self, index_one_based: i32) -> WinResult<bool>;
fn windows_activate_by_name(&self, name: &str) -> WinResult<bool>;
fn save_all_documents(&self) -> WinResult<bool>;
fn activate_document_by_name(&self, name: &str) -> WinResult<bool>;
fn save_documents_by_names(&self, names: &[&str]) -> WinResult<bool>;
fn close_documents_by_names(&self, names: &[&str]) -> WinResult<bool>;
}
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 documents_names(&self) -> WinResult<Vec<String>> {
use crate::variant::variant_to_i32;
let mut names = Vec::new();
let docs = self.documents()?;
let count_v = docs.get("Count")?;
let count = variant_to_i32(&count_v)?.unwrap_or(0);
for i in 1..=count {
let doc_v = docs.call("Item", vec![variant_i4(i)])?;
if let Some(doc_disp) = variant_to_dispatch(&doc_v)? {
let doc = ComObject::new(doc_disp);
let name_v = doc.get("Name")?;
let name = crate::variant::variant_to_string(&name_v)?.unwrap_or_default();
names.push(name);
}
}
Ok(names)
}
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 active_document_path(&self) -> WinResult<Option<String>> {
let v = self.inner.get("ActiveDocument")?;
if let Some(disp) = variant_to_dispatch(&v)? {
let doc = ComObject::new(disp);
let path_v = doc.get("Path")?;
crate::variant::variant_to_string(&path_v)
} else {
Ok(None)
}
}
fn windows_count(&self) -> WinResult<Option<i32>> {
use crate::variant::variant_to_i32;
let v = self.inner.get("Windows")?;
if let Some(disp) = variant_to_dispatch(&v)? {
let wins = ComObject::new(disp);
let count_v = wins.get("Count")?;
variant_to_i32(&count_v)
} else {
Ok(Some(0))
}
}
fn windows_names(&self) -> WinResult<Vec<String>> {
use crate::variant::variant_to_i32;
let mut names = Vec::new();
let v = self.inner.get("Windows")?;
if let Some(disp) = variant_to_dispatch(&v)? {
let wins = ComObject::new(disp);
let count_v = wins.get("Count")?;
let count = variant_to_i32(&count_v)?.unwrap_or(0);
for i in 1..=count {
let w_v = wins.call("Item", vec![variant_i4(i)])?;
if let Some(w_disp) = variant_to_dispatch(&w_v)? {
let w = ComObject::new(w_disp);
let name_v = w.get("Name")?;
let name = crate::variant::variant_to_string(&name_v)?.unwrap_or_default();
names.push(name);
}
}
}
Ok(names)
}
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_all_documents(&self) -> WinResult<bool> {
let docs = self.documents()?;
use crate::variant::variant_to_i32;
let count_v = docs.get("Count")?;
let mut count = variant_to_i32(&count_v)?.unwrap_or(0);
while count > 0 {
let doc_v = docs.call("Item", vec![variant_i4(count)])?;
if let Some(doc_disp) = variant_to_dispatch(&doc_v)? {
let doc = ComObject::new(doc_disp);
let _ = doc.call("Close", vec![])?;
}
count -= 1;
}
Ok(true)
}
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)
}
}
fn selection_select_element2(&self, types: &[&str], message: &str, multi: bool) -> 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 types_var = if types.is_empty() { variant_empty() } else { variant_bstr_array(types) };
let _ = sel.call("SelectElement2", vec![types_var, variant_bstr(message), variant_bool(multi)])?;
let count_v = sel.get("Count")?;
return variant_to_i32(&count_v);
}
}
Ok(None)
}
fn selection_item_detail(&self, index_one_based: i32) -> WinResult<Option<(String, String, Option<String>)>> {
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 elem_v = sel.call("Item2", vec![variant_i4(index_one_based)])?;
if let Some(elem_disp) = crate::variant::variant_to_dispatch(&elem_v)? {
let elem = ComObject::new(elem_disp);
let t = crate::variant::variant_to_string(&elem.get("Type")?)?.unwrap_or_default();
let mut name = String::new();
if let Some(val_disp) = crate::variant::variant_to_dispatch(&elem.get("Value")?)? {
let val = ComObject::new(val_disp);
if let Some(n) = crate::variant::variant_to_string(&val.get("Name")?)? { name = n; }
}
let mut leaf: Option<String> = None;
if let Ok(lp_var) = elem.get("LeafProduct") {
if let Some(lp_disp) = crate::variant::variant_to_dispatch(&lp_var)? {
let lp = ComObject::new(lp_disp);
leaf = crate::variant::variant_to_string(&lp.get("Name")?)?;
}
}
return Ok(Some((t, name, leaf)));
}
}
}
Ok(None)
}
fn selection_details(&self) -> WinResult<Vec<(String, String, Option<String>)>> {
let mut out: Vec<(String, String, Option<String>)> = Vec::new();
if let Some(count) = self.selection_count()? {
for i in 1..=count {
if let Some(d) = self.selection_item_detail(i)? { out.push(d); }
}
}
Ok(out)
}
fn windows_activate(&self, index_one_based: i32) -> WinResult<bool> {
let v = self.inner.get("Windows")?;
if let Some(disp) = variant_to_dispatch(&v)? {
let wins = ComObject::new(disp);
let w_v = wins.call("Item", vec![variant_i4(index_one_based)])?;
if let Some(w_disp) = crate::variant::variant_to_dispatch(&w_v)? {
let w = ComObject::new(w_disp);
let _ = w.call("Activate", vec![])?;
return Ok(true);
}
}
Ok(false)
}
fn windows_activate_by_name(&self, name: &str) -> WinResult<bool> {
let names = self.windows_names()?;
if let Some((idx, _)) = names.iter().enumerate().find(|(_, n)| n == &name) {
return self.windows_activate((idx + 1) as i32);
}
Ok(false)
}
fn save_all_documents(&self) -> WinResult<bool> {
use crate::variant::variant_to_i32;
let docs = self.documents()?;
let count_v = docs.get("Count")?;
let count = variant_to_i32(&count_v)?.unwrap_or(0);
for i in 1..=count {
let doc_v = docs.call("Item", vec![variant_i4(i)])?;
if let Some(doc_disp) = variant_to_dispatch(&doc_v)? {
let doc = ComObject::new(doc_disp);
let _ = doc.call("Save", vec![])?;
}
}
Ok(true)
}
fn activate_document_by_name(&self, name: &str) -> WinResult<bool> {
let docs = self.documents()?;
let doc_v = docs.call("Item", vec![variant_bstr(name)])?;
if let Some(doc_disp) = variant_to_dispatch(&doc_v)? {
let doc = ComObject::new(doc_disp);
let _ = doc.call("Activate", vec![])?;
return Ok(true);
}
Ok(false)
}
fn save_documents_by_names(&self, names: &[&str]) -> WinResult<bool> {
let docs = self.documents()?;
for n in names {
let doc_v = docs.call("Item", vec![variant_bstr(n)])?;
if let Some(doc_disp) = variant_to_dispatch(&doc_v)? {
let doc = ComObject::new(doc_disp);
let _ = doc.call("Save", vec![])?;
}
}
Ok(true)
}
fn close_documents_by_names(&self, names: &[&str]) -> WinResult<bool> {
let docs = self.documents()?;
for n in names {
let doc_v = docs.call("Item", vec![variant_bstr(n)])?;
if let Some(doc_disp) = variant_to_dispatch(&doc_v)? {
let doc = ComObject::new(doc_disp);
let _ = doc.call("Close", vec![])?;
}
}
Ok(true)
}
}
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 documents_names(&self) -> WinResult<Vec<String>> {
Ok(vec![
"MockPart.CATPart".to_string(),
"MockProduct.CATProduct".to_string(),
"MockDrawing.CATDrawing".to_string(),
])
}
fn active_document_name(&self) -> WinResult<Option<String>> { Ok(Some("MockDocument.CATPart".to_string())) }
fn active_document_path(&self) -> WinResult<Option<String>> { Ok(Some("C\\\\Mock\\\\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()),
])
}
fn windows_count(&self) -> WinResult<Option<i32>> { Ok(Some(2)) }
fn windows_names(&self) -> WinResult<Vec<String>> { Ok(vec!["Window1".into(), "Window2".into()]) }
fn close_all_documents(&self) -> WinResult<bool> { println!("[MOCK] Close all documents"); Ok(true) }
fn selection_select_element2(&self, types: &[&str], message: &str, multi: bool) -> WinResult<Option<i32>> {
println!("[MOCK] Selection.SelectElement2(types={:?}, message='{}', multi={})", types, message, multi);
let base = 1i32;
let cnt = if multi { base + (types.len() as i32 - 1).max(0) } else { base };
Ok(Some(cnt))
}
fn selection_item_detail(&self, index_one_based: i32) -> WinResult<Option<(String, String, Option<String>)>> {
let data = vec![
("Face".to_string(), "Pad.1".to_string(), Some("Part1".to_string())),
("Edge".to_string(), "Sketch.2".to_string(), Some("Part1".to_string())),
("Vertex".to_string(), "Point.3".to_string(), None),
];
Ok(data.get((index_one_based-1) as usize).cloned())
}
fn selection_details(&self) -> WinResult<Vec<(String, String, Option<String>)>> {
let mut out = Vec::new();
if let Some(count) = self.selection_count()? {
for i in 1..=count {
if let Some(d) = self.selection_item_detail(i)? { out.push(d); }
}
}
Ok(out)
}
fn windows_activate(&self, index_one_based: i32) -> WinResult<bool> {
println!("[MOCK] Windows.Item({}).Activate()", index_one_based);
Ok(true)
}
fn windows_activate_by_name(&self, name: &str) -> WinResult<bool> {
println!("[MOCK] Activate window by name: '{}'", name);
let exists = self.windows_names()?.iter().any(|n| n == name);
Ok(exists)
}
fn save_all_documents(&self) -> WinResult<bool> {
println!("[MOCK] Save all documents");
Ok(true)
}
fn activate_document_by_name(&self, name: &str) -> WinResult<bool> {
println!("[MOCK] Activate document by name: '{}'", name);
let exists = self.documents_names()?.iter().any(|n| n == name);
Ok(exists)
}
fn save_documents_by_names(&self, names: &[&str]) -> WinResult<bool> {
println!("[MOCK] Save documents by names: {:?}", names);
Ok(true)
}
fn close_documents_by_names(&self, names: &[&str]) -> WinResult<bool> {
println!("[MOCK] Close documents by names: {:?}", names);
Ok(true)
}
}