main
Drew Owens 0.9.1 (a62fe72): the data root moves out of ab's directory to %LOCALAPPDATA%\adom-solidworks-bridge; legacy files that… 192302d 20d ago

Writes, dialogs and targeting — swx

Read this before anything that writes — saving, exporting over a file, setting properties or parameters, running a script or macro, closing someone's document — and before touching any dialog or window. Part of the solidworks-bridge skill — SKILL.md beside this folder has the rules that never bend. Trust each verb's own reply (_hint, errorCode) over anything written here.

Rules and gotchas

  • Target docs by path, not by activating them — every doc verb takes an optional path and falls back to the active document.
  • Files land on the DESKTOP filesystem. From a cloud container, pull exports back with ab's pull_file.
  • Background etiquette: the bridge never foregrounds SOLIDWORKS; responses carry windowEtiquette:"background".
  • A modal dialog inside SOLIDWORKS blocks every verb (COM is single threaded). If verbs time out, ask the user about a dialog, or check swx_status. The bridge holds the COM object in a separate child process, so a wedged call is killed and replaced rather than taking the bridge down — the verb after a timeout works again.
  • swx_props_set changes memory, not the file — follow with swx_save.
  • swx_com_eval {"expr":"...","reason":"..."} is the maintainer escape hatch: a late-bound COM call chain rooted at $sw (the application) or $doc (the active document), e.g. $sw.RevisionNumber() or $doc.Extension.CustomPropertyManager("").GetNames(). String, number and boolean literal arguments. For prototyping a capability against a real install before it becomes a verb; reason is required.
  • swx_screenshot delegates to ab's generic desktop_screenshot_window; swx_state {} bundles that with the dialog census and this machine's measured verb timings in one call (screenshot:false to skip the callbacks).
  • Do not dismiss dialogs casually. swx_dismiss_blocking_dialogs {} REPORTS by default. Closing needs close:true + reason + confirm; a "Save changes?" / Document Recovery / licence dialog is never closed at all; and a window that cannot be PROVED to belong to SOLIDWORKS is never closed either (ab's window list carries no owner pid, so a bare dialog class could be any application's). swx_close_window refuses the same way, with errorCode:"window_not_attributed". Closing a stuck SOLIDWORKS modal through the window manager has been observed to take the whole application down — and on Connected editions the bridge cannot start it again.
  • ☠ THE BIG ONE: writes are DENY-BY-DEFAULT. The bridge refuses to change any document it did not create, and to write over any file that already exists. That includes things that do not look like writes: swx_props_set (it dirties the document, so the user's own Ctrl+S would commit it), swx_rebuild_check (it calls ForceRebuild3), swx_bom {format:"csv"} (it writes a file), swx_pack_and_go, and swx_stop / swx_close_all_documents / swx_close_doc (they end or disturb the user's session). Check swx_status.userWrites ("disabled" / "enabled") before planning anything that writes. Refusals are errorCode:"user_writes_disabled" or "destination_exists"; the response lists bridgeDocuments (each with the path that names it unambiguously) so you can retarget. Exporting to a path that does not exist yet, from any document, always works — that is the normal way to get data out. To reach the user's own document or overwrite a file, the bridge process must have been started with SWX_ALLOW_USER_WRITES=1 AND the call must carry allowUserWrites:true + confirm:true + reason. You cannot grant this to yourself — ask the user. Every exception is recorded in swx_action_ledger.
  • The affected verbs: swx_save, swx_save_as, swx_export*, swx_bom {format:"csv"}, swx_pack_and_go, swx_close_doc, swx_props_set, swx_set_parameter, swx_set_preference, swx_run_script, swx_run_macro (always — see below), swx_rebuild_check, swx_stop, swx_kill, swx_close_all_documents, swx_open {readOnly:false}, and BOTH dialog-closing verbs — swx_dismiss_blocking_dialogs {close:true} and swx_close_window — for any dialog at all, including ones the bridge itself raised. Reading is never gated, and neither is exporting to a path that does not exist yet.
  • swx_run_macro is gated ALWAYS, with no safe case. A macro is a VBA project with the whole SOLIDWORKS API in scope and the filesystem behind it: it can model, save over anything, batch-export, delete, or quit, to documents this bridge has never seen — and nothing about the call predicts which. So the two questions that bound every other write ("is the document ours", "is the destination clear") do not apply, and running one needs the full opt-in chain every time, even for a macro the bridge itself shipped. In the default posture it refuses. module and procedure default to main/main (the macro recorder's defaults); the macro is unloaded after the run, so an edited .swp runs its new code. swx_run_macro has not yet been run against a live SOLIDWORKS — its response says so (provenLive:false), and the bundled resources/macros/adom-hello.swp is a placeholder, not a working macro.
  • What the opt-in actually opens. When the user HAS opted in, in full, the bridge does the thing — it does not refuse anyway. swx_close_doc and swx_close_all_documents {closeUnsaved:true} will close and discard the user's own unsaved documents on that chain, with each one named in swx_action_ledger. Both the server and the com host check the identical chain independently, so a partial chain is refused twice and a complete one is honoured by both.
  • A destination this bridge cannot CHECK is not free. Every write is gated on whether something is already at the destination, and that question has three answers: occupied (needs the opt-in), free (a bridge-created document may be written there without one), and unverifiable — a path this process cannot stat, which refuses with destination_unverifiable and is NOT waivable by the opt-in. It is not a permission question. The bridge and SOLIDWORKS do not share a filesystem view, so a UNC path the bridge cannot see may hold a file SOLIDWORKS would overwrite. Check it yourself, or write somewhere the bridge can see.
  • swx_close_window is gated exactly like swx_dismiss_blocking_dialogs. It refuses an application window (errorCode:"window_is_application_frame" — closing a SOLIDWORKS frame quits it, which is swx_stop's job); it refuses a dialog that asks about the user's own work at any posture (errorCode:"dialog_belongs_to_the_user" — Save changes?, Document Recovery, licence); and closing anything else needs the process-level write opt-in. swx_window_info marks every window with isFrame and closable, and closable already folds in all three refusals — trust it.
  • swx_screenshot captures SOLIDWORKS' own window, by handle. A free-text titleContains is ignored; pass hwnd only if swx_window_info attributes it to SOLIDWORKS. For anything else on the desktop, ab's own desktop_screenshot_window is the right tool and carries the user's approval prompt.
  • swx_open is READ-ONLY by default, and readOnly:false is a WRITE. A read-write open takes a write lock on the user's file and converts-and-dirties an older-version document in memory, so it goes through the same gate as every other write. Reading is always free. swx_close_doc {save:false} on a DIRTY document the bridge did not create additionally needs confirm:true + reason (errorCode:"unsaved_documents") — and if SOLIDWORKS will not say whether the document is dirty, it is treated as dirty. There is no undo. Do NOT reach for swx_close_doc {save:false} to "undo" a change to someone's document: that discards the entire document, not your edits. Undo belongs to the user, in SOLIDWORKS.
  • Destructive verbs ask first. swx_close_all_documents, swx_close_window, swx_kill and swx_close_doc-on-dirty-work need confirm:true (and swx_close_window/swx_kill a reason). swx_stop and swx_kill refuse outright while documents are unsaved (errorCode:"unsaved_documents") — pass force:true only if the user has said the changes are disposable. swx_close_all_documents {closeUnsaved:true} additionally needs a reason, and it discards ONLY the bridge's own scratch documents: the user's unsaved ones are left open and listed in skipped.
  • swx_props_set IS gated now. The old justification — "in memory until a gated save" — ignored the persist route that cannot be gated: the user. It leaves their document dirty, so SOLIDWORKS prompts "Save changes?" on close and their own Ctrl+S writes bridge-authored data into their file.
  • Name the document with targetDocument, not by hoping. Every mutating verb resolves its target ONCE — targetDocument (a title), else path, else the active document — and the bridge acts on exactly that. A targetDocument naming nothing open is an error (document_not_open), never a silent fall back to whatever the user has in front of them; naming two different documents two ways is ambiguous_target. Prefer targetDocument whenever it matters: SOLIDWORKS' active document follows window focus, and the user may click something else mid-run.
  • swx_com_eval evaluates READS ONLY, by allowlist. Members beginning Get/Is/Has/Can/Enum, plus a short list of navigation properties (ActiveDoc, Extension, SketchManager, FeatureManager, Frame, Name, Value, Mass, …). Everything else — including members nobody has thought of — is refused with errorCode:"eval_member_blocked" and a reason. It is an allowlist rather than a banned list because a banned list has to anticipate every dangerous member of an API with thousands, and the first one missed SendMsgToUser2 (a modal that wedges the host), SetUserPreferenceToggle and ForceRebuild3. If a genuine READ is refused, that is a bug worth filing.
  • swx_save_as RENAMES by default. The open document becomes that file and takes its name — which is what you want for a scratch part, and emphatically not what you want on someone's bracket.sldprt. copy:true writes the file and leaves the document alone; that is what every swx_export* does.
  • swx_reset_workspace is the safe cleanup: it closes only documents this bridge process created and has not saved. The list lives in memory, not on disk, because SOLIDWORKS names new documents Part1/Assem1 and a remembered title would eventually match a document the USER made. A bridge restart therefore forgets and the sweep closes nothing.