main
Drew Owens 0.9.20 source 5e44698 16d ago

Rendering — what the bridge can draw, and what it still has to measure

Drew, 2026-10-02: "Does SW have rendering like fusion so we can get a really gorgeous shot of this assembly?" — then "go ahead and try it… and if it works, let's add a bunch of intel into the bridge to handle rendering." The Fusion bridge has no render verb at all (docs/FUSION-PARITY-REPORT.md), so this is new ground.

Every line here is [measured] (seen on Drew's laptop) or [unmeasured] (written from the API documentation; not yet run live). Track A was MEASURED LIVE on 2026-10-05 (below); what is still unmeasured is named.

The facts this is built on

  • [measured] PhotoView 360 is ABSENT on this edition (3DEXPERIENCE SOLIDWORKS for Makers 2026). The renderer is the viewport itself: RealView, ambient occlusion, shadows and perspective, on an RTX 4090 Laptop GPU.
  • [measured] swx_show_assembly + ViewZoomtofit2 work live on the Moveo assembly (C:\Users\Public\adom-hero\BCN3D-Moveo-master\CAD files\BCN3D Moveo assembly.SLDASM, 110 documents, read-only) — the test subject.
  • [measured] swx_screenshot (ab's PrintWindow by handle) works, but it is a screen grab capped at the window, about 1568 px wide — not an export.
  • [measured] SOLIDWORKS Visualize 2026 is installed at …\SOLIDWORKS 3DEXPERIENCE R2026x\Visualize\SLDWORKSVisualize.exe, takes a model path as its argument, ships no scripting interface on disk, and raises its OWN 3DEXPERIENCE sign-in.

Track A — MEASURED 2026-10-05

On adom_drew2, hosted 0.9.18, SOLIDWORKS 2026 SP3 (Makers), the Moveo assembly (110 documents, read-only), the SOLIDWORKS window MINIMIZED throughout:

Request Result
{width:1920, height:1080} ok, 411,368 bytes, PNG header 1920×1080, capped:false, nothing applied or restored, 10,403 ms
{width:3840, height:2160, view:"isometric", displayStyle:"shaded", perspective:true, realView:true, ambientOcclusion:true, shadows:true, zoomToFit:true} ok, 979,997 bytes, PNG header 3840×2160, capped:false, 11,597 ms. Applied: ambientOcclusion false→true, displayMode 5→4, orientation isometric, perspective false→true, realView false→true, zoom fit. Restored: every one read back equal to its old value; camera captured and restored; restoreFailures: []
hosted 0.9.19, the same plus zoomFactor:2.0 ok, 932,312 bytes, 3840×2160, 11,157 ms, restoreFailures: []. settings.shadows: before true, why:"alreadyAsRequested" (the 0.9.18 gap explained: shadows were already on). settings.realView: before false, after false — the put returned OK and SOLIDWORKS left it off (0.9.19 still said changed:true, why:"applied"; 0.9.20 says setIgnored). zoomFactor: Scale2 0.176 → 0.352, the model twice as large — and the arm's tip clipped at the top edge. enumIds: swDisplayAmbientOcclusionShadows: 485, swDisplayShadowsInShadedMode: 131
  • [measured] The image is a real render: RealView shading, a floor shadow, the scene's gradient background.
  • [measured] PowerShell reflection over the installed swconst DLL resolved all four preference ids (no render_enum_unresolved). Measured on SOLIDWORKS 2026 SP3 (0.9.19's enumIds): swDisplayAmbientOcclusionShadows = 485, swDisplayShadowsInShadedMode = 131. The bridge still reads them from the installed DLL every time; these are the record, not a hard-coded table.
  • [measured] SaveBMP at 4K works with the window MINIMIZED, and 4K costs only ~1.2 s more than 1080p — ask for 3840×2160 for hero-grade output.
  • [measured] The gap that led to settings: shadows:true appeared in neither appliedSettings nor restoredSettings — already on, or never applied? 0.9.19 accounts for every requested setting (below).
  • [measured] Framing: zoomToFit leaves this assembly at ~25% of the frame's width (the fit box is the whole assembly at the window's aspect, then exported 16:9). 0.9.19 adds zoomFactor.
  • [measured] zoomFactor > 1 zooms IN: 2.0 took Scale2 0.176 → 0.352 and drew the model twice as large — and OVERSHOT on the Moveo (the arm's tip clipped at the top edge). After a fit, 1.4–1.6 usually fills the frame without clipping; 2.0 clipped a tall assembly.
  • [measured] Shadows were already on in Drew's view (alreadyAsRequested).
  • [measured] REALVIEW DID NOT TAKE on this laptop: ViewDisplayRealView put returned OK and the read-back stayed false. RealView needs a certified graphics card (this laptop has an RTX 4090 Laptop GPU, not on the certified list) and may also be unavailable while the window is minimized. The image was rendered shaded with shadows and ambient occlusion. Since 0.9.20 every set is read back: a set SOLIDWORKS ignored is why:"setIgnored", changed:false, restored:null, absent from appliedSettings, and the reply's notes[] carries a sentence saying so. The bridge changes no machine-wide setting to force RealView on — that would be a standing change on the user's machine.
  • [unmeasured] flicker or resizing with the window VISIBLE; the largest size (8192×4608); a background change (not passed in the live runs); RealView with the window visible.

settings — every requested setting, accounted for (0.9.19)

"settings": {
  "<name>": {
    "requested": <what was asked: true | "isometric" | "shaded" | "white" | 2.5 …>,
    "before":    <its value before the render, or null if it could not be read>,
    "after":     <its value after applying, before the export>,
    "changed":   true | false,
    "restored":  true | false | null,   // null when nothing was changed
    "why":       "applied" | "alreadyAsRequested" | "unreadable" | "setFailed" | "idUnresolved" | "setIgnored"
  }, …
},
"notes": [ "<one sentence per setting SOLIDWORKS ignored>" ]

setIgnored (0.9.20): the set call returned OK but the read-back right after was not the requested value — nothing changed, so nothing is restored (changed:false, restored:null) and it is never listed as applied. RealView's note is fixed text: "RealView was requested but SOLIDWORKS left it off — RealView needs a certified graphics card (this laptop has an RTX 4090 Laptop GPU, not on the certified list) and may also be unavailable while the window is minimized; the image was rendered shaded with shadows and ambient occlusion."

Names: view, displayStyle, perspective, realView, ambientOcclusion, shadows, background, zoomToFit, zoomFactor — one row for each one the caller asked for, and only those.

Track A — swx_render_image (0.9.18; zoomFactor and settings 0.9.19)

swx_render_image {reason, path?, width?, height?,
                  view?: "current"|"isometric"|"front"|"back"|"left"|"right"|"top"|"bottom"|"trimetric"|"dimetric",
                  displayStyle?: "shaded"|"shadedWithEdges"|"hiddenLinesRemoved"|"wireframe",
                  perspective?, realView?, ambientOcclusion?, shadows?: bool,
                  background?: "current"|"white"|"black"|"transparent", zoomToFit?: bool,
                  zoomFactor?: number (0.1–20, default 1.0; multiplies the view scale AFTER any fit)}
→ {path, bytes, width, height (read back from the PNG header), requested, capped,
   settings, appliedSettings, restoredSettings, restoreFailures, restoreIncomplete,
   enumIds, durationMs}

What it does, in order, all against the ACTIVE document:

  1. Validates: a reason; 16–8192 px per side (default 1920×1080); path absolute, ending .png, its folder EXISTING (never created — omit path for <data root>\exports\<doc>-<view>-<w>x<h>-<ts>.png), and not an existing file. background:"transparent" is refused render_option_unsupported: the export has no alpha channel.
  2. Captures every setting it is asked to change, then applies it (only what differs). Calls and enums — each from the official API help:
Setting Call Values
view IModelDoc2::ShowNamedView2("", id) help swStandardViews_e: front 1, back 2, left 3, right 4, top 5, bottom 6, isometric 7, trimetric 8, dimetric 9 help
displayStyle IModelView::DisplayMode get/put help swViewDisplayMode_e: wireframe 1, hiddenLinesRemoved 2, shaded 4, shadedWithEdges 5 help
perspective IModelView::HasPerspective / AddPerspective / RemovePerspective help bool
realView IModelDocExtension::ViewDisplayRealView get/put help bool
shadows ISldWorks::Get/SetUserPreferenceToggle(swDisplayShadowsInShadedMode) — a SYSTEM setting example bool
ambientOcclusion ISldWorks::Get/SetUserPreferenceToggle(swDisplayAmbientOcclusionShadows) enum — its scope (system vs document) [unmeasured] bool
background ISldWorks::Get/SetUserPreferenceIntegerValue(swColorsBackgroundAppearance) → Plain (0) help, and (swSystemColorsViewportBackground) → COLORREF white 0xFFFFFF, black 0
zoomToFit IModelDoc2::ViewZoomtofit2 help —
camera (restored) IModelView::Orientation3, Translation3, Scale2 get/put MathTransform, MathVector, double

The swUserPreference*_e NUMBERS are not on the help pages. The bridge never guesses one: it reads them by reflection from the installed SolidWorks.Interop.swconst.dll beside SLDWORKS.exe (ISldWorks::GetExecutablePath), the way the 3D Interconnect ids were read on 2026-09-17, and a render that needs one it cannot read is refused render_enum_unresolved before anything changes. 3. Exports with IModelDoc2::SaveBMP(path, width, height) — "If WidthIn or the HeightIn is less than or equal to 0, then the view size is based on the current window size" help. Why not "Save As PNG" with print capture: the export options swTiffScreenOrPrintCapture (0 screen, 1 print), swTiffPrintDPI, swTiffPrintPaperSize, swTiffPrintDrawingPaperWidth/Height and swTiffPrintScaleToFit are documented "For drawings only" (System Options > Export > TIF/PSD/JPG/PNG), so for a part or assembly only SaveBMP takes a pixel size. They are not touched. 4. Restores, newest first, on EVERY path (the error path too), reads each one back, and lists it in restoredSettings (value = what it was, readBack, restored). A restore that did not read back is restoreIncomplete:true with restoreFailures. The camera is put back last. The document is never saved. 5. Converts the BMP to a PNG itself (the BMP is deleted), writes it, and VERIFIES BY EFFECT: the file exists, starts with the PNG signature, and its IHDR width × height are the reply's width/height. If they differ from the request, capped:true — the export honoured only the window size.

Guards: the readiness gate (it is a COM verb); the user-presence guard when the request changes anything on screen (a view, a style, RealView, AO, shadows, perspective, a background, a zoom) — exactly like a sweep, user_is_active with the evidence. A render of the CURRENT view with nothing changed is NOT presence-guarded: it changes nothing the user sees. [unmeasured] whether SaveBMP at a size larger than the window flickers or resizes the viewport while it renders — the first live run must watch for it, and if it does, that case moves under the guard too.

Budget: ab waits 600 s (bridge.json timeouts.verbs.render_image); the render's host call is bounded by the sweep ceiling (540 s). swx_progress shows the stage.

Track A — what the 2026-10-05 run answered, and what is still open

Answered: 1 (4K honoured, ~11 s, shadow/gradient visible; RealView did NOT take — uncertified GPU, setIgnored since 0.9.20), 2 (every setting restored), 3 (ids read; 485 / 131 on 2026 SP3), 4 (GetExecutablePath worked, by implication: the DLL was found), zoomFactor's direction (>1 zooms in; 1.4–1.6 after a fit). Still open: 5 (visible-window flicker), 6 (8192×4608), a background change, and the AO setting's scope.

Track A — the original measure-first list

  1. swx_render_image {width:3840, height:2160, view:"isometric", realView:true, ambientOcclusion:true, shadows:true, perspective:true, background:"white", zoomToFit:true, reason} on the Moveo assembly: did SaveBMP honour 3840×2160 (capped:false)? How long (durationMs)? Does the image show RealView, AO and shadows, or does SaveBMP render a plainer pipeline than the screen?
  2. Did every setting read back restored (restoreIncomplete:false)? Compare the viewport before and after by eye (and swx_screenshot).
  3. Were the swconst ids read (no render_enum_unresolved)? Record the four ids. Is swDisplayAmbientOcclusionShadows a system or a document setting?
  4. Does GetExecutablePath return the folder holding SLDWORKS.exe and the swconst DLL?
  5. Did the window flicker or resize during a larger-than-window SaveBMP?
  6. 8192×4608 — the largest size SaveBMP will produce on this GPU.

Starting Visualize — MEASURED 2026-10-05, built in 0.9.19

  • [measured] 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; No exits the process). The coordinator answered it once, on Drew's explicit "deal with this"; the bridge never does.
  • [measured] The right way is the PLATFORM SHORTCUT, the same form as SOLIDWORKS Design's: C:\Users\Public\Desktop\SOLIDWORKS Visualize.lnk or %ProgramData%\Microsoft\Windows\Start Menu\Programs\Dassault Systemes SOLIDWORKS 3DEXPERIENCE R2026x\SOLIDWORKS Visualize.lnk → CATSTART.exe -run "SWXDesktopLauncher.exe" -object "--AppName=\"SWXCVIZ_AP\" -tenant=… -monoapp -3DRegistryURL=…" -nowindow (SOLIDWORKS Design: SWXCSWK_AP). 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 (already running before the launch).
  • [measured] FIRST RUN: a WPF dialog titled "SOLIDWORKS Visualize": "Please choose a location for your 'SOLIDWORKS Visualize Content' folder. This folder will be created to provide a place for this software to store data." — an edit prefilled C:\Users\drew\Documents, "…", "Continue". It creates a folder on the user's machine, a standing change: only the user answers it.
  • [measured] the WPF dialog CAN be read: hosted 0.9.19's swx_status.visualize read it through the desktop_ui_tree fallback — waitingOn: "userVisualizeContentFolder", its text, and prefilledPath: "C:/Users/drew/Documents". UIA gives that path with FORWARD slashes (the dialog draws backslashes); the bridge reports it as UIA gave it and does not normalise. On first run the dialog IS the main window (same hwnd, class HwndWrapper[SLDWORKSVisualize.exe;Main Application;…]); 0.9.19 said hwnd:null beside dialog.hwnd, 0.9.20 reports the top-level hwnd whenever the process has that window, dialog or not.
  • [unmeasured] whether the shortcut accepts a model path; how to quit Visualize.

0.9.19: swx_start {app:"visualize", reason, waitSeconds?} finds the platform shortcut (SWX_VISUALIZE_SHORTCUT overrides; only a .lnk named "SOLIDWORKS Visualize", never the executable), invokes it the same way SOLIDWORKS's is invoked, and waits up to waitSeconds (20) for the HwndWrapper main window or a dialog. Readiness = the main window exists and no dialog is waiting. The three dialogs are classified BY THEIR WORDS and never answered: the platform notice → visualize_direct_start_refused (the fix: start through the platform shortcut); the content folder → waitingOn: "userVisualizeContentFolder" with the text and the prefilled path; a 3DEXPERIENCE login → waitingOn:"userSignIn", answered only by swx_sign_in {userAuthorized:true, window:"visualize"} on the user's say-so. swx_status.visualize = {running, pid, hwnd, ready, waitingOn, dialog, companions}. A path is not passed (unmeasured). No quit is built — it is the next measurement.

Track B — SOLIDWORKS Visualize render (DESIGN ONLY, no verb)

Parked: the main window cannot be measured until the user answers the content-folder dialog on Drew's machine.

Visualize renders physically (Fast / Accurate modes, a render queue). It has no API on disk [measured], so the bridge would drive it through UI Automation:

  1. Launch through the platform shortcut — swx_start {app:"visualize"} (0.9.19). [measured] the executable takes a model path, but a direct start is refused on this edition; whether the shortcut passes a path on is [unmeasured].
  2. Answer its 3DEXPERIENCE sign-in with swx_sign_in {userAuthorized:true, window:"visualize"} [the sign-in path exists; the Visualize host's login was measured on 2026-10-02].
  3. Wait for the import to finish.
  4. In the Output Tools panel: output size, render mode (Fast / Accurate), passes or time limit, output folder and file name.
  5. Start the render; poll the OUTPUT FILE (exists, size stable, PNG header) — verification by effect, never by a progress bar alone.
  6. Close Visualize (or leave it, on the user's say-so).

Track B — MEASURE FIRST (exactly what to capture live, nothing assumed)

  • The main window: class name and title pattern, and whether its accessibility tree is lazy (the login page's was).
  • How "import finished" shows: a status text, a progress control's disappearance, the model in the scene tree — its role, name, automationId.
  • The Output Tools panel: how it is opened (button name / automationId), and each control: width, height, aspect lock, render mode (Fast / Accurate), passes / time limit, output folder, file name, format — role, name, automationId, settable / invokable, and whether a background ValuePattern set works or real keystrokes are needed (the login page refused a background set).
  • The render ("Start Render") button: name, automationId, invokable.
  • How completion shows: a dialog, a status text, the render queue entry — and whether the file appears only at the end (so polling the file is enough).
  • Whether a second render can be queued while one runs.
  • Whether Visualize keeps its own camera / environment per project, and where the project file goes (so the bridge never writes over the user's).