app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
Drew Owens
0.9.0: bridge data moved out of the install cache (recovery survives updates), closed-in-place records, gates route thr…
5d02ca9
20d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
//! **3D INTERCONNECT DECIDES HOW A NEUTRAL FILE COMES IN, AND IT IS THE USER'S
//! SETTING.**
//!
//! Measured live 2026-09-17, reading `GetUserPreferenceToggle` off the running
//! application with ids taken from the installed
//! `SolidWorks.Interop.swconst.dll` at
//! `C:\Program Files\Dassault Systemes\SOLIDWORKS 3DEXPERIENCE R2026x\SOLIDWORKS\`:
//!
//! ```text
//! 691 swMultiCAD_Enable3DInterconnect TRUE
//! 291 swImportAutoRunImportDiagnostics TRUE
//! 690 swImportNeutralRunDiagnostics TRUE
//! 639 swForceEnableImportDiagnosis FALSE
//! ```
//!
//! With 691 ON — the default — SOLIDWORKS does not translate a STEP through
//! `LoadFile4`/`LoadFile2` at all. It OPENS it as a part holding a LINKED 3D
//! Interconnect feature. That is why `LoadFile4` executed cleanly once 0.7.9
//! fixed the variant encoding and still returned no document with `errors = 1`
//! (`swGenericError`).
//!
//! The rule this file mostly exists to hold: **the bridge reads that preference
//! and never writes it.** Not set, and not flipped-and-restored — a crash between
//! the halves would leave Drew's SOLIDWORKS configured differently than he left
//! it.
mod common;
use common::{Bridge, TempDir};
use serde_json::{json, Value};
fn s(v: &Value) -> String {
serde_json::to_string_pretty(v).unwrap_or_default()
}
fn a_step(scratch: &TempDir, name: &str) -> String {
let p = scratch.join(name);
std::fs::write(&p, "ISO-10303-21;\n").unwrap();
p.to_string_lossy().to_string()
}
/// WITH 3D INTERCONNECT ON, NOTHING IMPORTS — AND THE BRIDGE SAYS SO BY NAME.
///
/// The matrix of 2026-09-17, on a real STEP with the toggle TRUE:
///
/// ```text
/// LoadFile4, Nothing -> no document, errors=1
/// LoadFile4, GetImportFileData object -> no document, errors=1
/// LoadFile2 -> false
/// OpenDoc6 docType 1, options 0/1/2/16 -> no document, errors=2097152 (requires repair)
/// OpenDoc6 docType 0, options 1 -> no document, errors=1024 (invalid file type)
/// ```
///
/// So there is no programmatic neutral-file import route on this edition while
/// that setting is on, and the imports that worked from 09-05 to 09-11 must have
/// run with it off. `auto` still makes ONE `LoadFile4` attempt — a future edition
/// that accepts it will simply work — and then reports what is ACTUALLY wrong.
/// Reporting a translator failure instead would send the reader hunting for a
/// missing translator that is not the problem.
#[test]
fn an_import_blocked_by_the_setting_says_which_setting() {
let scratch = TempDir::new("interconnect-blocked");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
std::fs::write(b.scratch.join("mock-interconnect-blocks-import"), "").unwrap();
let before = b.call("swx_docs", json!({}));
let step = a_step(&scratch, "F-W10.step");
let r = b.call("swx_import", json!({"path": step}));
check!(b, "the import fails", r["success"] == false, s(&r));
check!(
b,
"...with a code that names the cause, not a translator",
r["errorCode"] == "interconnect_blocks_import",
s(&r)
);
check!(
b,
"...telling the user exactly where the setting lives",
r["detail"].as_str().unwrap_or("").contains("Enable 3D Interconnect"),
s(&r)
);
check!(
b,
"...and that the bridge will not change it for them",
r["detail"].as_str().unwrap_or("").contains("DOES NOT CHANGE USER SETTINGS"),
s(&r)
);
// NOTHING WAS OPENED AND NOTHING WAS CHANGED.
let after = b.call("swx_docs", json!({}));
check!(b, "the session is untouched", after["count"] == before["count"], s(&after));
}
/// A DOCUMENT OPENED BY ANY ROUTE IS STILL OURS TO IDENTIFY AND DRAIN. The route
/// changed twice in two releases; ownership at birth did not, and the drain gate
/// depends on it.
#[test]
fn a_document_opened_by_any_import_route_is_still_ours() {
let scratch = TempDir::new("interconnect-ownership");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
let step = a_step(&scratch, "F-W10.step");
// The OpenDoc6 route, asked for by name — a measurement route now, but one
// that still opens a document, and anything this bridge opens it must own.
let r = b.call("swx_import", json!({"path": step, "route": "opendoc6"}));
check!(b, "the named route ran", r["via"] == "OpenDoc6 (3D Interconnect)", s(&r));
let identity = r["documentIdentity"].clone();
check!(b, "the document is identified as it is born", identity["identity"] != "unknown", s(&r));
let machine = TempDir::new("interconnect-ownership-machine");
std::fs::write(machine.join("windows.json"), "[]").unwrap();
let swept = b.call("swx_close_all_safely", json!({"reason": "draining the import"}));
check!(
b,
"and it is preserved and closed like any document this bridge made",
swept["recovered"].as_array().map(|a| !a.is_empty()).unwrap_or(false),
s(&swept)
);
}
/// WITH IT OFF, the translator route is used — and `linked` is false, because
/// that import really is self-contained.
#[test]
fn the_translator_route_is_used_when_interconnect_is_off() {
let scratch = TempDir::new("interconnect-off");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
std::fs::write(b.scratch.join("mock-3d-interconnect-off"), "").unwrap();
let step = a_step(&scratch, "F-W10.step");
let r = b.call("swx_import", json!({"path": step}));
check!(b, "the import succeeds", r["success"] == true, s(&r));
check!(b, "the setting is reported as off", r["interconnect"] == false, s(&r));
check!(b, "...the translator route is named", r["via"] == "LoadFile4", s(&r));
check!(b, "...and the result is not linked to anything", r["linked"] == false, s(&r));
}
/// THE PREFERENCE IS NEVER WRITTEN — not set, and not flipped-and-restored.
///
/// A crash between the flip and the restore would leave the user's SOLIDWORKS
/// configured differently than they left it, and a CAD setting silently altered
/// by a tool is the kind of harm this bridge exists not to do. So the import
/// obeys the setting rather than arranging the world to suit itself.
#[test]
fn the_import_never_changes_the_users_preference() {
let scratch = TempDir::new("interconnect-readonly");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
let step = a_step(&scratch, "F-W10.step");
let r = b.call("swx_import", json!({"path": step}));
check!(b, "precondition: the setting is READ and reported", r["interconnect"] == true, s(&r));
// The setting is what it was, and a second import agrees. If the bridge had
// flipped it to get the import through — even flipping it back afterwards —
// this is where a crash would have left the user's SOLIDWORKS changed.
let again = b.call("swx_import", json!({"path": a_step(&scratch, "second.step")}));
check!(b, "the setting is unchanged after an import", again["interconnect"] == true, s(&again));
// AND NO VERB EXISTS TO CHANGE IT. A caller cannot ask the bridge to flip
// 3D Interconnect, by any spelling — the preference belongs to the user.
let catalog = b.call("swx_describe", json!({}));
let text = s(&catalog).to_lowercase();
check!(
b,
"no verb offers to set a user preference",
!text.contains("setuserpreference") && !text.contains("set_user_preference"),
"the verb catalog mentions setting a user preference".to_string()
);
}
/// BREAKING THE LINK IS REFUSED UNTIL IT IS MEASURED — and the refusal is useful.
///
/// Dissolving a 3D Interconnect link WRITES to the user's document, and the call
/// that does it has not been verified on this install. This project already
/// shipped one confident claim about somebody else's format from memory (pass
/// 23's OLE signature, wrong for every modern SOLIDWORKS file); that one cost a
/// refusal, and this one would modify a document.
#[test]
fn dissolving_the_link_is_refused_rather_than_guessed() {
let scratch = TempDir::new("interconnect-dissolve");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
let step = a_step(&scratch, "F-W10.step");
let before = b.call("swx_docs", json!({}));
let r = b.call("swx_import", json!({"path": step, "dissolve": true}));
check!(b, "it refuses", r["success"] == false, s(&r));
check!(b, "...with a code a caller can branch on", r["errorCode"] == "dissolve_not_measured", s(&r));
check!(
b,
"...saying plainly that nothing was changed",
r["error"].as_str().unwrap_or("").contains("nothing was changed"),
s(&r)
);
check!(
b,
"...and what would have to be measured, and the safe way round it",
r["detail"].as_str().unwrap_or("").contains("EFFECT"),
s(&r)
);
// AND IT REALLY DID NOTHING.
let after = b.call("swx_docs", json!({}));
check!(
b,
"the refusal left the session exactly as it was",
after["count"] == before["count"],
s(&after)
);
}
/// THE TRANSLATOR ROUTE UNDER 3D INTERCONNECT IS THE LIVE FAILURE, and the reply
/// now carries the toggle that explains it. Before 0.8.0 a caller saw
/// "LoadFile4 opened nothing, errors=1" with no way to know why.
#[test]
fn a_translator_import_under_interconnect_reports_the_toggle_that_explains_it() {
let scratch = TempDir::new("interconnect-explains");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
std::fs::write(b.scratch.join("mock-import-translator-under-interconnect"), "").unwrap();
let step = a_step(&scratch, "F-W10.step");
let r = b.call("swx_import", json!({"path": step}));
check!(b, "it fails", r["success"] == false, s(&r));
check!(
b,
"and the reply carries the setting that explains the failure",
r["hostError"]["interconnect"] == true,
s(&r)
);
check!(
b,
"...and the generic error the translator returned",
r["hostError"]["errors"] == 1,
s(&r)
);
}
/// A MODAL IMPORT DIAGNOSTICS DIALOG IS NAMED, NOT ANSWERED, AND NOT A HANG.
///
/// `swImportAutoRunImportDiagnostics` is TRUE on this install (measured
/// 2026-09-17), so a STEP with faulty faces raises the Import Diagnostics dialog
/// DURING the import and the COM call never returns. A caller told only "the com
/// host did not answer" would reasonably retry — into the same dialog. A caller
/// told a dialog is on screen shows it to the user instead.
///
/// It is never answered: the choice it offers (attempt to heal the faces, or not)
/// changes the geometry the user ends up with.
#[test]
fn an_import_waiting_behind_a_dialog_says_so_instead_of_looking_hung() {
let machine = TempDir::new("interconnect-dialog-machine");
std::fs::write(
machine.join("dialog-open.json"),
json!({"hwnd": 9300, "className": "#32770", "title": "Import Diagnostics",
"visible": true})
.to_string(),
)
.unwrap();
let scratch = TempDir::new("interconnect-dialog");
let b = Bridge::start_in(
TempDir::borrowed(&scratch.0),
&[
("SWX_LAUNCH_FAKE_DIR", &machine.path()),
("SWX_MOCK_DOC_LATENCY_MS", "0"),
],
);
b.call("swx_start", json!({}));
// The import wedges, exactly as it does behind a modal dialog.
std::fs::write(b.scratch.join("mock-import-timeout-after-open"), "").unwrap();
let step = a_step(&scratch, "faulty.step");
let r = b.call("swx_import", json!({"path": step}));
check!(b, "the import does not claim success", r["success"] != true, s(&r));
check!(b, "it names the dialog it is waiting behind", r["stoppedByDialog"].is_object(), s(&r));
check!(
b,
"...identifying it as the Import Diagnostics prompt",
s(&r).contains("Import Diagnostics"),
s(&r)
);
check!(
b,
"...and says this bridge never answers one",
r["_hint"].as_str().unwrap_or("").contains("NEVER answers"),
s(&r)
);
}
/// THE MEASUREMENT SURFACE: a named route, exact arguments, and the invocation
/// echoed back.
///
/// No route is known to work on this edition today. `LoadFile4` executes and
/// returns nothing (`errors=1`); `LoadFile2` returns false, still unexplained;
/// `OpenDoc6(path, swDocPART, 1, …)` returns nothing with `errors=2097152`
/// (`swFileRequiresRepairError`) on two real STEP files. PowerShell cannot be
/// used to try alternatives — SOLIDWORKS Connected does not expose the type info
/// its COM adapter needs, so every member call fails `TYPE_E_ELEMENTNOTFOUND`
/// (0x8002802B) — which makes raw IDispatch through this bridge the only
/// instrument that exists. So the bridge grew a measurement surface, inside the
/// verb that already owns and registers what it opens, rather than an escape
/// hatch that would not.
#[test]
fn every_import_echoes_the_exact_invocation_it_made() {
let scratch = TempDir::new("interconnect-matrix");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
let step = a_step(&scratch, "F-W10.step");
// OPENDOC6 with explicit options and document type.
let r = b.call(
"swx_import",
json!({"path": step, "route": "opendoc6", "openOptions": 3, "docType": 2}),
);
check!(b, "the named route is taken", r["via"] == "OpenDoc6 (3D Interconnect)", s(&r));
check!(b, "...and the call is echoed exactly", r["invocation"]["method"] == "OpenDoc6", s(&r));
check!(
b,
"...including the arguments a matrix row has to state",
r["invocation"]["args"]["options"] == 3 && r["invocation"]["args"]["docType"] == 2,
s(&r["invocation"])
);
// LOADFILE4 with a different arg string and real import data requested.
let r = b.call(
"swx_import",
json!({"path": a_step(&scratch, "b.step"), "route": "loadfile4",
"argString": "", "withImportData": true}),
);
check!(b, "the translator route is taken by name", r["via"] == "LoadFile4", s(&r));
check!(b, "...with the argument string used", r["invocation"]["args"]["argString"] == "", s(&r));
check!(
b,
"...and says which importData was passed",
r["invocation"]["args"]["importData"] == "GetImportFileData(path)",
s(&r["invocation"])
);
// LOADFILE2 alone, so its answer is its own rather than a fallback's.
let r = b.call("swx_import", json!({"path": a_step(&scratch, "c.step"), "route": "loadfile2"}));
check!(b, "LoadFile2 can be asked for by name", r["invocation"]["method"] == "LoadFile2", s(&r));
// AND A TYPO IS REFUSED, never quietly treated as `auto` — a run that
// measures something other than what was asked is worse than no run.
let r = b.call("swx_import", json!({"path": a_step(&scratch, "d.step"), "route": "opendoc"}));
check!(
b,
"an unknown route is refused",
r["success"] == false && r["errorCode"] == "unknown_import_route",
s(&r)
);
}
/// DISSOLVE IS REFUSED ON EVERY ROUTE, not just the one it was written next to.
///
/// The refusal used to live inside the interconnect branch, so asking for
/// `dissolve:true` while the toggle was OFF — or while it could not be READ — was
/// silently dropped: the caller asked for an unlinked import, got an import, and
/// was told nothing either way about the link they asked to break. A request this
/// bridge will not honour has to fail closed wherever it arrives.
#[test]
fn dissolve_is_refused_on_every_route() {
for (label, faults) in [
("interconnect on", vec![]),
("interconnect off", vec!["mock-3d-interconnect-off"]),
("toggle unreadable", vec!["mock-interconnect-toggle-unreadable"]),
] {
let scratch = TempDir::new("interconnect-dissolve-routes");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
for f in &faults {
std::fs::write(b.scratch.join(f), "").unwrap();
}
let step = a_step(&scratch, "F-W10.step");
let before = b.call("swx_docs", json!({}));
let r = b.call("swx_import", json!({"path": step, "dissolve": true}));
// A REFUSAL, not a mention. "the reply contains the word dissolve" is
// satisfied by a hint that happens to name it while the import went
// ahead — which is the exact failure being tested for.
check!(
b,
"dissolve is REFUSED with its code",
r["success"] == false && r["errorCode"] == "dissolve_not_measured",
format!("[{label}] {}", s(&r))
);
// AND NOTHING WAS IMPORTED: failing closed means the caller does not get
// a linked part they did not ask for.
let after = b.call("swx_docs", json!({}));
check!(
b,
"...and nothing was opened",
after["count"] == before["count"],
format!("[{label}] {}", s(&after))
);
}
}
/// AN UNREADABLE TOGGLE IS ITS OWN OUTCOME — not "off".
///
/// `null` means SOLIDWORKS would not answer; `false` means the user turned it
/// off. They take the same route and mean opposite things about how much to trust
/// a failure: with the setting ON, nothing imports on this edition, so a failure
/// under an unreadable toggle cannot be attributed to a missing translator.
#[test]
fn an_unreadable_toggle_is_reported_as_unknown_not_as_off() {
let scratch = TempDir::new("interconnect-unreadable");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
std::fs::write(b.scratch.join("mock-interconnect-toggle-unreadable"), "").unwrap();
let step = a_step(&scratch, "F-W10.step");
let ok = b.call("swx_import", json!({"path": step}));
check!(b, "the import still runs", ok["success"] == true, s(&ok));
check!(b, "the setting is reported as UNKNOWN, not false", ok["interconnect"].is_null(), s(&ok));
check!(
b,
"...and the reply says the value could not be read",
ok["interconnectSettingReadable"] == false,
s(&ok)
);
// AND A FAILURE UNDER AN UNREADABLE TOGGLE IS ITS OWN ERROR.
let scratch2 = TempDir::new("interconnect-unreadable-fail");
let b2 = Bridge::start_in(TempDir::borrowed(&scratch2.0), &[]);
b2.call("swx_start", json!({}));
std::fs::write(b2.scratch.join("mock-interconnect-toggle-unreadable"), "").unwrap();
std::fs::write(b2.scratch.join("mock-interconnect-blocks-import"), "").unwrap();
let r = b2.call("swx_import", json!({"path": a_step(&scratch2, "x.step")}));
check!(
b2,
"a failure under an unreadable toggle is NOT reported as a translator problem",
r["errorCode"] == "interconnect_setting_unreadable",
s(&r)
);
check!(
b2,
"...and points at the setting to check",
r["detail"].as_str().unwrap_or("").contains("Enable 3D"),
s(&r)
);
}