app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
//! SOLIDWORKS VISUALIZE — start it, and say what it is waiting on (0.9.19).
//!
//! MEASURED 2026-10-05 on Drew's laptop:
//! * Starting `SLDWORKSVisualize.exe` DIRECTLY is wrong on this edition: the
//! next start raised a `#32770` titled "SOLIDWORKS Visualize": "SOLIDWORKS
//! Visualize must be opened from the 3DEXPERIENCE Platform or from the
//! desktop shortcut created from the Platform. Do you want to go to the
//! 3DEXPERIENCE Platform?" — Yes / No (No = automationId CommandButton_7,
//! which exits the process).
//! * The right way is the PLATFORM SHORTCUT, the same form as SOLIDWORKS
//! Design's: `C:\Users\Public\Desktop\SOLIDWORKS Visualize.lnk`, or the
//! Start-Menu group's, → `CATSTART.exe -run "SWXDesktopLauncher.exe" -object
//! "--AppName=\"SWXCVIZ_AP\" …"`. Invoked with SOLIDWORKS already signed in,
//! no login appeared; after 45 s: `SLDWORKSVisualize` (main window class
//! `HwndWrapper[SLDWORKSVisualize.exe;Main Application;<guid>]`, title
//! "SOLIDWORKS Visualize"), `SWVisualize.Exchange.PluginHost`,
//! `SWVisualize.Queue.Server`.
//! * FIRST RUN: a WPF dialog titled "SOLIDWORKS Visualize": "Please choose a
//! location for your 'SOLIDWORKS Visualize Content' folder…", an edit
//! prefilled `C:\Users\drew\Documents`, "…" and "Continue". Answering it
//! creates a folder on the user's machine — a standing change — so the bridge
//! NEVER answers it: `waitingOn:"userVisualizeContentFolder"`.
//!
//! MEASURED 2026-10-05 (0.9.19): swx_status read the REAL WPF first-run dialog
//! through the desktop_ui_tree fallback — waitingOn userVisualizeContentFolder,
//! its text, prefilledPath "C:/Users/drew/Documents".
//!
//! The bridge answers NONE of Visualize's dialogs. A 3DEXPERIENCE login goes to
//! `swx_sign_in {userAuthorized:true, window:"visualize"}` on the user's
//! say-so. Quitting Visualize is UNMEASURED and not built. Whether the shortcut
//! accepts a model path is UNMEASURED; a `path` is not passed to it.
use serde_json::{json, Value};
pub const IMAGE: &str = "SLDWORKSVisualize.exe";
pub const SHORTCUT_GLOBS: &[&str] = &[
r"%PUBLIC%\Desktop\SOLIDWORKS Visualize.lnk",
r"%ProgramData%\Microsoft\Windows\Start Menu\Programs\Dassault Systemes SOLIDWORKS 3DEXPERIENCE*\SOLIDWORKS Visualize.lnk",
r"%USERPROFILE%\Desktop\SOLIDWORKS Visualize.lnk",
r"%APPDATA%\Microsoft\Windows\Start Menu\Programs\Dassault Systemes SOLIDWORKS 3DEXPERIENCE*\SOLIDWORKS Visualize.lnk",
];
/// The Visualize platform shortcut. `SWX_VISUALIZE_SHORTCUT` overrides. Only a
/// `.lnk` named "SOLIDWORKS Visualize" — never the executable.
pub fn find_shortcut() -> Option<String> {
if let Ok(p) = std::env::var("SWX_VISUALIZE_SHORTCUT") {
if !p.trim().is_empty() {
return Some(p);
}
}
SHORTCUT_GLOBS
.iter()
.flat_map(|g| crate::detect::expand_glob(g))
.find(|p| p.to_lowercase().ends_with("solidworks visualize.lnk"))
}
/// What a Visualize window is, by its WORDS.
pub fn classify(title: &str, class: &str, words: &str) -> Option<&'static str> {
let w = words.to_lowercase();
if crate::launch::is_sign_in_window(class, title) {
return Some("signIn");
}
if w.contains("must be opened from the 3dexperience platform") {
return Some("platformNotice");
}
if w.contains("visualize content") && w.contains("folder") {
return Some("contentFolder");
}
None
}
fn is_main_window(class: &str) -> bool {
class.to_ascii_lowercase().starts_with("hwndwrapper[sldworksvisualize.exe;")
}
/// A window's words: its Win32 child text and buttons; a WPF window has none, so
/// its accessibility tree's names (and an edit's value, for the prefilled path —
/// not a secret) are read through ab when the bridge can.
fn words_of(os: &dyn crate::launchos::LaunchOs, hwnd: i64) -> (String, Option<String>) {
let mut words = String::new();
let mut path: Option<String> = None;
if let Ok(c) = os.dialog_contents(hwnd) {
words = c.text.iter().chain(c.buttons.iter()).cloned().collect::<Vec<_>>().join(" | ");
path = c.text.iter().find(|t| t.len() > 3 && t.as_bytes().get(1) == Some(&b':') && t.contains('\\')).cloned();
}
if words.trim().is_empty() && crate::caller::ab_available() {
let r = crate::caller::ad_callback("desktop_ui_tree", json!({"hwnd": hwnd, "maxDepth": 20, "maxNodes": 300}), std::time::Duration::from_secs(30));
fn walk(v: &Value, names: &mut Vec<String>, path: &mut Option<String>) {
match v {
Value::Array(a) => a.iter().for_each(|x| walk(x, names, path)),
Value::Object(m) => {
if let Some(n) = m.get("name").and_then(|n| n.as_str()).filter(|n| !n.is_empty()) {
names.push(n.to_string());
}
let role = m.get("role").or_else(|| m.get("controlType")).and_then(|r| r.as_str()).unwrap_or("");
if role.eq_ignore_ascii_case("edit") && path.is_none() {
// REPORTED AS UIA GAVE IT. MEASURED 2026-10-05: the
// dialog SHOWS C:\Users\drew\Documents and UIA's value is
// "C:/Users/drew/Documents" — forward slashes from the
// page itself; the bridge does not normalise either way.
*path = m.get("value").and_then(|v| v.as_str()).map(String::from);
}
m.values().for_each(|x| walk(x, names, path));
}
_ => {}
}
}
let mut names = Vec::new();
walk(&r, &mut names, &mut path);
words = names.join(" | ");
}
(words, path)
}
/// THE STATE of Visualize on this machine: running, pid, main window, and what it
/// is waiting on. Read-only; answers nothing.
pub fn status() -> Value {
let os = crate::launchos::os();
let census = match os.processes() {
Ok(c) => c,
Err(e) => return json!({"running": Value::Null, "why": format!("no process census: {e}")}),
};
let Some(p) = census.iter().find(|p| p.image.eq_ignore_ascii_case(IMAGE)) else {
// PARKED BEHIND THE PLATFORM'S SIGN-IN (0.9.23). MEASURED 2026-10-12:
// after a lapsed platform session, invoking the Visualize shortcut
// started SWXDesktopUpdate.exe, which showed a 3DEXPERIENCE ID login
// titled just "3DEXPERIENCE"; Visualize itself never started, and this
// block said running:false, waitingOn:null — the bridge saw nothing.
if let Some((hwnd, title)) = crate::signin::desktop_update_login(os.as_ref()) {
return json!({"running": false, "pid": Value::Null, "hwnd": Value::Null, "ready": false,
"waitingOn": "userSignIn",
"signInWindow": {"hwnd": hwnd, "title": title, "ownerImage": crate::signin::DESKTOP_UPDATE_IMAGE, "host": "desktopUpdate"},
"parkedBehind": "the 3DEXPERIENCE platform sign-in (SWXDesktopUpdate.exe) — Visualize starts after it; the bridge can prepare that page (user name, Continue), but ab's policy keeps the password with the user until ab issue #237 lands",
"how": "On the user's say-so, swx_sign_in {reason, userAuthorized:true, window:<signInWindow.hwnd>} fills the user name and moves to the password page; the USER types the password (ab gives saved passwords only to a verified local caller, not a bridge — ab issue #237)."});
}
return json!({"running": false, "pid": Value::Null, "hwnd": Value::Null, "waitingOn": Value::Null});
};
let windows = os.windows_of_pid(p.pid).unwrap_or_default();
let mut main: Option<i64> = None;
let mut waiting: Option<Value> = None;
// THE MAIN WINDOW, dialog or not (0.9.20). MEASURED 2026-10-05: on first run
// the content-folder dialog IS the main window (same hwnd, class
// HwndWrapper[SLDWORKSVisualize.exe;Main Application;…]) and the block said
// hwnd:null beside dialog.hwnd. Matched case-insensitively over EVERY window
// of the process (visible or not), before any dialog classification.
for w in windows.iter() {
if is_main_window(&w.class_name) {
main = Some(w.hwnd);
break;
}
}
for w in windows.iter().filter(|w| w.visible) {
let (words, path) = words_of(os.as_ref(), w.hwnd);
if let Some(kind) = classify(&w.title, &w.class_name, &words) {
waiting = Some(match kind {
"signIn" => json!({"waitingOn": "userSignIn", "hwnd": w.hwnd, "title": w.title,
"how": "swx_sign_in {reason, userAuthorized:true, window:\"visualize\"} — only on the user's say-so — prepares the page (user name, Continue); the user types the password (ab's policy, until ab issue #237)"}),
"contentFolder" => json!({"waitingOn": "userVisualizeContentFolder", "hwnd": w.hwnd, "title": w.title,
"text": words, "prefilledPath": path,
"how": "The user chooses where Visualize keeps its content (it creates a folder on their machine). The bridge never answers this."}),
_ => json!({"waitingOn": "visualizePlatformNotice", "hwnd": w.hwnd, "title": w.title, "text": words,
"how": "Visualize was started directly. Do not answer it (No exits Visualize; Yes opens the platform). Start Visualize through its platform shortcut: swx_start {app:\"visualize\"}."}),
});
break;
}
}
let waiting_on = waiting.as_ref().and_then(|w| w.get("waitingOn").cloned()).unwrap_or(Value::Null);
json!({
"running": true,
"pid": p.pid,
"hwnd": main,
"ready": main.is_some() && waiting.is_none(),
"waitingOn": waiting_on,
"dialog": waiting,
"companions": census.iter().filter(|q| q.image.starts_with("SWVisualize.")).map(|q| json!({"pid": q.pid, "image": q.image})).collect::<Vec<_>>(),
"quit": "UNMEASURED — the bridge does not quit Visualize yet",
})
}
/// `swx_start {app:"visualize", reason, waitSeconds?}`.
pub fn start(ctx: &crate::verbs::Ctx, args: &Value) -> Value {
let Some(reason) = args.get("reason").and_then(|r| r.as_str()).filter(|r| !r.trim().is_empty()) else {
return crate::verbs::err_code("reason_required", "starting SOLIDWORKS Visualize needs a reason", "Say why; it is ledgered.");
};
let path_note = args.get("path").filter(|p| !p.is_null()).map(|_| {
json!("`path` was not passed to Visualize: whether its platform shortcut accepts a model path is UNMEASURED. Open the model from inside Visualize.")
});
let now = status();
if now.get("running") == Some(&json!(true)) {
if now.get("waitingOn") == Some(&json!("visualizePlatformNotice")) {
return refuse_direct(now);
}
return crate::verbs::ok(json!({"app": "visualize", "launchedNow": false, "alreadyRunning": true,
"visualize": now, "pathNote": path_note,
"_hint": "Visualize is already running; nothing was launched. `visualize.waitingOn` says if it needs the user."}));
}
let Some(lnk) = find_shortcut() else {
return crate::verbs::err_code(
"visualize_shortcut_missing",
"no SOLIDWORKS Visualize platform shortcut was found",
"Visualize must be started from the 3DEXPERIENCE Platform or its desktop shortcut (\"SOLIDWORKS Visualize.lnk\" on the Public desktop or in the Start-Menu group). Ask the user to create it from the Platform. The bridge never starts SLDWORKSVisualize.exe directly.",
);
};
let r = ctx.call("launch_visualize", json!({"shortcut": lnk}), 60);
crate::ledger::record("visualize_launch", json!({"shortcut": lnk, "reason": reason, "ok": r.is_ok()}));
if let Err(e) = r {
return crate::verbs::err_code("visualize_launch_failed", format!("the Visualize shortcut could not be invoked: {}", e.message), "Nothing else was attempted.");
}
let wait = args.get("waitSeconds").and_then(|w| w.as_u64()).unwrap_or(20).min(120);
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(wait);
let mut st = status();
while std::time::Instant::now() < deadline
&& !(st.get("ready") == Some(&json!(true)) || !st.get("waitingOn").map(|w| w.is_null()).unwrap_or(true))
{
std::thread::sleep(std::time::Duration::from_millis(500));
st = status();
}
if st.get("waitingOn") == Some(&json!("visualizePlatformNotice")) {
return refuse_direct(st);
}
let stage = if st.get("ready") == Some(&json!(true)) {
"ready"
} else if !st.get("waitingOn").map(|w| w.is_null()).unwrap_or(true) {
"waitingOn"
} else {
"launching"
};
crate::verbs::ok(json!({
"app": "visualize",
"launchedNow": true,
"shortcut": lnk,
"stage": stage,
"waitingOn": st.get("waitingOn").cloned().unwrap_or(Value::Null),
"signInWindow": st.get("signInWindow").cloned().unwrap_or(Value::Null),
"visualize": st,
"pathNote": path_note,
"_hint": match stage {
"ready" => "Visualize is up (its main window exists, no dialog is waiting).",
"waitingOn" if st.get("waitingOn") == Some(&json!("userSignIn")) && st.get("running") == Some(&json!(false)) => "Visualize has not started: the launch is parked behind the 3DEXPERIENCE platform sign-in (visualize.signInWindow, hosted by SWXDesktopUpdate.exe). On the user's say-so, swx_sign_in {reason, userAuthorized:true, window:<signInWindow.hwnd>} prepares the page (user name, Continue); the USER types the password — ab's policy keeps it with them until ab issue #237. Visualize starts after it. Then poll swx_status.",
"waitingOn" => "Visualize is waiting on the USER (see visualize.dialog). The bridge never answers Visualize's dialogs; a 3DEXPERIENCE login can be answered with swx_sign_in {userAuthorized:true, window:\"visualize\"} on the user's say-so.",
_ => "Visualize is starting (measured: ~45 s). Poll swx_status — its `visualize` block says running / ready / waitingOn.",
},
}))
}
fn refuse_direct(st: Value) -> Value {
let mut out = crate::verbs::err_code(
"visualize_direct_start_refused",
"SOLIDWORKS Visualize is showing \"must be opened from the 3DEXPERIENCE Platform or from the desktop shortcut\" — it was started directly",
"The fix: start Visualize through its platform shortcut (swx_start {app:\"visualize\"}). The bridge does not answer that dialog (No exits Visualize; Yes opens the Platform) — the user does.",
);
if let Value::Object(m) = &mut out {
m.insert("visualize".into(), st);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_three_measured_dialogs_are_told_apart_by_their_words() {
assert_eq!(
classify("SOLIDWORKS Visualize", "#32770", "SOLIDWORKS Visualize must be opened from the 3DEXPERIENCE Platform or from the desktop shortcut created from the Platform. Do you want to go to the 3DEXPERIENCE Platform? | Yes | No"),
Some("platformNotice")
);
assert_eq!(
classify("SOLIDWORKS Visualize", "HwndWrapper[SLDWORKSVisualize.exe;Main Application;x]", "Please choose a location for your 'SOLIDWORKS Visualize Content' folder. | ... | Continue"),
Some("contentFolder")
);
assert_eq!(classify("Login - 3DEXPERIENCE ID - Dassault Systemes", "WindowsForms10.Window.8.app", ""), Some("signIn"));
assert_eq!(classify("SOLIDWORKS Visualize", "HwndWrapper[SLDWORKSVisualize.exe;Main Application;x]", "File | Edit | View"), None);
}
}