# What
Two upstream-shaped fixes to ifcopenshell's runtime plug-in discovery
so the schema/serializer plug-ins are findable in deployment layouts
other than a flat \`<prefix>/lib/\` (specifically: macOS .app bundles).
## (1) Stable anchor variable instead of a function pointer
\`schema_plugin_directory()\` used \`&load_schema_plugins\` as the
anchor whose containing module \`dladdr\` is asked to resolve. Function
addresses are not reliably equal to a single canonical location across
toolchains — on macOS arm64 with BonsaiViewer.app, \`&load_schema_plugins\`
took the address of a PLT/stub inside the consumer binary rather than
the actual symbol inside \`libIfcParse.dylib\`. \`dladdr\` then dutifully
returned the consumer's path and the loader started searching
\`BonsaiViewer.app/Contents/MacOS/\` for plug-ins that were never
installed there.
A variable doesn't suffer from this — it has exactly one canonical
address inside its defining dylib. Add \`ifcopenshell_libifcparse_anchor\`
(exported via IFC_PARSE_API) and use \`&that\` instead. Standard pattern
used by Boost.DLL, GStreamer, \`_dyld_get_image_*\`, etc.
## (2) Bundle-aware fallback search paths
The primary search path is \`dirname(libIfcParse)\`. That works for
flat installs (Linux \`lib/\`, Windows \`bin/\`) where plug-ins are
siblings of libIfcParse. macOS app bundles split the layout:
\`macdeployqt\` puts non-Qt @rpath deps in \`Contents/Frameworks/\`,
Apple convention asks for \`Contents/PlugIns/\`, and some install rules
co-locate libIfcParse with the exe in \`Contents/MacOS/\`. Plug-ins
typically end up in a sibling directory, not the same one.
In \`add_search_paths_or_default\`, after registering the primary path,
also register \`<parent>/PlugIns\`, \`<parent>/Frameworks\`, and
\`<parent>/MacOS\` on Apple platforms. \`discover_exact\` short-circuits
on the first hit so duplicates and missing directories are harmless.
# What this does NOT do
The plug-in dylibs still need to actually be inside the app bundle
somewhere for these fallbacks to find them — the upstream install
rules (\`install(TARGETS …)\` in \`src/ifcparse/CMakeLists.txt\`,
\`src/serializers/CMakeLists.txt\`, etc.) put them in \`<prefix>/lib/\`
which lives outside \`BonsaiViewer.app\`. That side of the fix is a
follow-up — either an explicit bundle-aware install destination on
the plug-in targets, or an install(CODE) sweep that mirrors them
into the bundle.
# What this also un-does
Reverts the BonsaiViewer-only \`install(CODE)\` hack that was about to
copy \`ifcopenshell.*.dylib\` from \`lib/\` into
\`BonsaiViewer.app/Contents/MacOS/\` — superseded by the loader-side
fix above, which lets us put the plug-ins anywhere sane inside the
bundle without further consumer-side stitching.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BonsaiViewer.app crashed at launch on a fresh macOS arm64 mac with:
Library not loaded: @rpath/libwgpu_native.dylib
Referenced from: /Applications/BonsaiViewer.app/Contents/MacOS/BonsaiViewer
Reason: no LC_RPATH's found
The exe had \`LC_LOAD_DYLIB @rpath/libwgpu_native.dylib\` (CMake baked
that in from the upstream dylib's install_name), but zero \`LC_RPATH\`
entries, so dyld had nowhere to look — and macdeployqt hadn't pulled
the dylib in either, since it sat at \`<install_root>/lib/\` rather
than inside the .app.
Two changes:
- \`src/ifcviewer-wgpu/CMakeLists.txt\`: on macOS, when
BUILD_BONSAIVIEWER is on, install libwgpu_native.dylib straight
into \`BonsaiViewer.app/Contents/Frameworks/\` instead of
\`<prefix>/lib/\`. That matches the standard macOS bundle layout.
- \`src/bonsaiviewer/CMakeLists.txt\`: set
\`INSTALL_RPATH "@executable_path/../Frameworks"\` on the
BonsaiViewer target on Apple. That's where dyld looks at launch,
and where the dylib now lives.
Together the @rpath load resolves at launch without depending on
macdeployqt to follow non-Qt @rpath references (it usually only
chases Qt frameworks).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Linux (Rocky manylinux, GCC 11):
- WgpuAreaMeasurement.h: include <cstddef> directly. GCC 11 does not
transitively pull `size_t` through <vector>, so triangleCount()'s
return type fails to parse.
- WgpuViewportWindow.cpp:meshLocalToGlobal: use static_cast<double>(...)
instead of double(mesh_local[N]) when constructing the Eigen::Vector4d.
The latter triggers GCC 11's most-vexing-parse: it reads
`Vector4d local(double(mesh_local[0]), double(mesh_local[1]), ...)`
as a function declaration of `local` taking parameters
`double mesh_local[0]` etc., colliding with the outer `mesh_local`
parameter and failing with "redefinition of double* mesh_local".
macOS arm64:
- IfcViewerWgpuMinimal + BonsaiViewer install rules: change
`BUNDLE DESTINATION bin` → `BUNDLE DESTINATION .`. Qt's deploy
generator emits `macdeployqt <Target>.app` with no path prefix, which
only resolves when the bundle sits at the install-prefix root.
`BUNDLE DESTINATION bin` put it at `<prefix>/bin/Target.app` and the
install/strip step failed with "Could not find app bundle".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bundles four portability fixes uncovered by manually firing the platform
workflows against this branch:
- WgpuOverlayRenderer.cpp: GCC 11 (Rocky manylinux runner) does not parse
a multi-line raw string inside `#define`. Converted
THICK_LINE_HELPERS_WGSL from a `#define` to a `static const char*` and
switched AXIS_WGSL / SECTION_WGSL / MARQUEE_WGSL to `std::string` so
they can concatenate at static-init time. Three call sites now pass
`.c_str()` to svFromCStr.
- bonsaiviewer/CMakeLists.txt: added BUNDLE DESTINATION to the install
rule (same fix already applied to IfcViewerWgpuMinimal). MACOSX_BUNDLE
targets fail at configure on macOS without it even when nobody runs
`make install`.
- build_osx.yml: dropped the x64 (Intel cross-compile) matrix row. The
runner is arm64 so `brew --prefix qt` returns the arm64 prefix; we'd
need a separate x86_64 Qt install under /usr/local to cross-build
BonsaiViewer. Revisit if Intel-Mac demand resurfaces.
- build_win.yml: dropped the ARM64 matrix row. wgpu-native does not ship
a Windows-ARM64 binary, so IfcViewerWgpu's link step fails with ~60
unresolved wgpu* externs. Re-enable when upstream publishes that
target.
Cherry-pick this commit to v0.8.0 so the workflow_dispatch buttons see
the dropped rows.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bonsai now drives the wgpu viewport for both sidecar and direct-IFC
loads. The GL viewer and its supporting state classes are gone.
SceneLoader rewire:
- Takes WgpuViewportWindow* instead of ViewportWindow*.
- Sidecar path reads metadata only (readSidecarMetadataOnly) and hands
the StreamingSidecar off to the new applyCachedModel. Field accesses
inside applySidecarData go through .meta.
- Direct-IFC path uses the wgpu A-path (upload{Mesh,Instance}Chunk +
finalizeModel). The applyLodExtension call is dropped — wgpu has no
live LOD1 splice; LOD1 still lands in the on-disk sidecar for the
next open.
Bonsai migration:
- ViewportWindow → WgpuViewportWindow across MainWindow, Measurement,
SessionState, and every modules/*/{Commands,Panel,View}.{h,cpp} —
116 sites total. Same s/OverlayRenderer::/WgpuOverlayRenderer::/
rename, 12 sites.
- Includes flipped from ../ifcviewer/ViewportWindow.h to
../ifcviewer-wgpu/WgpuViewportWindow.h. OverlayRenderer.h include
dropped (transitively reached via the viewport header).
- BonsaiViewer links IfcViewerWgpu in addition to IfcViewer for the
duration of the migration; the GL-side IfcViewer also publicly links
IfcViewerWgpu so SceneLoader can resolve WgpuViewportWindow.
GL backend deletion:
- src/ifcviewer/ViewportWindow.{cpp,h}, BvhAccel.*, OverlayRenderer.*,
Selection.*, Visibility.* all gone.
- src/ifcviewer-minimal/ removed entirely (MinimalWindow drove the GL
viewport).
- src/ifcviewer/tests: test_bvh_accel, test_selection, test_visibility
removed. The first has no replacement (wgpu doesn't use a per-instance
BVH); the latter two are ported separately. test_lod_builder,
test_sidecar_cache, test_instanced_geometry, test_federation remain
(backend-agnostic).
- IfcViewer's CMakeLists drops OpenGL, Qt::OpenGL, Qt::Widgets — none
of the surviving translation units reach for them.
Build flag plumbing:
- BUILD_BONSAIVIEWER now auto-enables BUILD_BONSAIVIEWER_WGPU since
SceneLoader requires the wgpu lib for its WgpuViewportWindow* arg.
- The wgpu subprojects add_subdirectory ahead of the GL one so
IfcViewerWgpu exists when IfcViewer's link evaluates.
- src/ifcviewer-minimal subdir reference removed from cmake/CMakeLists.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduce pytest coverage for the previously untested connector — rpc,
cache, settings, autodesk (auth + APS client) and connector handlers —
94 tests, runnable via the new `test` optional-dependency extra.
To make HTTP, time and the OAuth redirect testable without a network or
real sockets, add dependency-injection seams to autodesk.py:
AuthSessionService and ApsClient accept an optional httpx transport;
AuthSessionService accepts an injectable clock and callback_waiter; and
_wait_for_callback is extracted to the module-level wait_for_oauth_callback.
All seams default to the previous behaviour.
Remove the APS_CLIENT_ID environment-variable override: the client id now
comes solely from settings.json, collapsing settings.load_client_id and
simplifying the settings dialog.
CI: the build-bonsaiviewer-autodesk workflow gains a `test` job
(Python 3.11 + 3.13) that gates the build matrix.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Compile the Bonsai Viewer as part of the Linux and Windows binary builds,
and ship the Autodesk connector alongside the viewer executable.
Qt6 dependencies:
- The viewer links Qt6::Svg for runtime icon tinting. Svg is a separate
base-Qt archive, so aqt now installs "qtbase qtsvg" (plus icu on Linux)
rather than qtbase alone, on both Linux and Windows.
- Qt6::CorePrivate is exposed differently across Qt versions: Qt 6.8 ships
the target inside Qt6Core, while Qt 6.10 provides it only as a separate
CorePrivate config package. The viewer CMakeLists requests it via
OPTIONAL_COMPONENTS so it resolves on both.
- When cross-compiling Windows ARM64, windeployqt runs from the host x64
Qt, so qtsvg is installed into the host Qt as well.
Windows build:
- build-all-win.py passed -DBUILD_IFCVIEWER, a flag since renamed to
BUILD_BONSAIVIEWER, so the Windows build compiled no viewer at all. It
now passes -DBUILD_BONSAIVIEWER.
- The Autodesk connector is bundled under connectors/ next to
BonsaiViewer.exe in the packaged archive, mirroring the Linux builds.
- The Windows workflow builds the connector (PyInstaller) before the main
build so it is available to bundle.
Connector bundling:
- The Linux rocky workflows build the connector and bundle it into the
BonsaiViewer archive; the Windows build now does the same.
Generated with the assistance of an AI coding tool.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Connector discovery scanned a per-user data directory
(QStandardPaths::GenericDataLocation -> ~/.local/share/IfcOpenShell/
BonsaiViewer/connectors and the macOS/Windows equivalents). Connectors
are now meant to ship with the application, so there is no reason to
look outside the install tree.
Replace userConnectorsDir() with bundledConnectorsDir(), which returns
QCoreApplication::applicationDirPath() + "/connectors". discoverConnectors()
scans only that path; its first-wins / malformed-manifest handling is
unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Models panel: replace the manual resizeEvent column-sizing hack with
QHeaderView Stretch/Fixed modes, re-applied via sectionCountChanged so
they survive the model rebuilds that QHeaderView resets them on.
Dialog: only wrap the body in a QScrollArea when scrollable, mirroring
Panel. The scroll area caps its sizeHint at 36x24 cells, which turned
wide fixed-size dialog content into spurious scrollbars.
Add Model dialog: reserve a stable, font-metrics-measured height for the
hover description so longer text never reflows the buttons; regroup the
buttons into LOCAL / CLOUD / TOOLS.
Buttons: move the trailing-separator decision out of makeButtonGroup
into a new addButtonGroups row builder, so the last group in a row
never draws a dangling divider.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Create a standalone Sphinx docs tree for Bonsai Viewer and migrate the Autodesk connector Markdown documentation into RST.\n\nGenerated with the assistance of an AI coding tool.
Replace the "Open Recent coming soon" placeholder with a working
most-recently-used project list. RecentProjects persists .ifcfed paths
via QSettings, capped and pruned to existing files. The Open Recent
ribbon button now shows a popup menu of recent projects; every
successful open or save (local or cloud) records an entry.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Directory src/ifcviewer-full -> src/bonsaiviewer, CMake target
IfcViewerFull -> BonsaiViewer, namespace ifcviewerfull -> bonsaiviewer,
QApplication / window titles / connector path now use the new brand.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>