Commit Graph

11 Commits

Author SHA1 Message Date
Dion Moult b62e14a06a ifcviewer: de-Qt ViewportWindow public API (QString → std::string)
Take QString out of ViewportWindow's outward-facing surface so it can
eventually move into a Qt-free ViewportCore:

  void     queueLoadSidecar(const QString&)    →  (const std::string&)
  uint32_t loadSidecar(const QString&)         →  (const std::string&)
  QString  cameraString() const                →  std::string …
  void     captureNextFrameToPng(const QString&, bool)
                                               →  (const std::string&, bool)
  void     setHudText(const QString&)          →  (const std::string&)

Internal members also moved off QString:
  std::deque<QString> pending_sidecars_     →  std::deque<std::string>
  QString             pending_screenshot_path_ →  std::string

Implementation strategy: convert at the boundary where ViewportWindow
still leans on Qt internals — `loadSidecar` bridges to QString once
for QFile/QDir/QFileInfo path handling; the screenshot save path
constructs a QString locally for QImage::save; the OverlayRenderer's
HUD setter still takes QString so setHudText converts before calling
through. Each of those bridges goes away when ViewportCore lands and
OverlayRenderer / SceneLoader / SidecarBuilder get their own de-Qt
sweeps. cameraString now produces its CSV via snprintf — no QString
ever instantiated.

Bonsai-side updates (compile-only):
  ifcviewer-minimal/main.cpp — queueLoadSidecar / captureNextFrameToPng
                              callers add .toStdString() on the QString
                              parser result
  modules/viewport/View.cpp  — setHudText callers add .toStdString() to
                              their `QString::arg(...)` formatter chains;
                              two `QString()` empty sentinels become
                              `std::string()`
  Measurement.cpp            — same pattern, two setHudText sites
  ifcviewer/LengthMeasurement.cpp — same, three sites

The cameraString string-streaming fix-up in ViewportWindow.cpp drops
the temporary .toUtf8().constData() bridge from #82 — Log::Stream's
std::string overload now handles it directly.

Builds: desktop / bonsai / web all green. Tests 100/100. Closes #80.
2026-06-05 09:14:19 +10:00
Dion Moult 6dd3558db9 ifcviewer: replace qInfo/qWarning with a Qt-free logger seam
Add Log.h (in IfcViewerCore) — a tiny stream-style logger that backs
fprintf(stderr,...), with overloads for the common primitives + char
strings. Mimics qInfo()/qWarning()'s syntax surface enough that
mass-replacing qInfo()→Log::info() and qWarning()→Log::warn() keeps
existing call sites parsing unchanged; .noquote() / .nospace() exist
as compat no-ops so chained qInfo().noquote()<<x<<y patterns survive.

QString streaming is a transitional concern — the QString → std::string
sweep (#80) hasn't landed yet, so ViewportWindow and friends still
construct QStrings for log payloads. LogQt.h (in IfcViewer, not Core)
adds the QString / QStringView operator<< overloads so those streaming
sites work without source changes during the in-flight Qt removal.
When #80 retires QString, LogQt.h drops out.

ViewportWindow.cpp: 132 qInfo/qWarning callsites converted. The two
printf-style qInfo("fmt %s", ...) callsites get fprintf with explicit
[info]/[warn] prefixes to keep the output discoverable.

Also de-Qt'd:
  AreaMeasurement.cpp  — 1 qInfo("fmt", …) → fprintf
  SceneLoader.cpp      — 4 qDebug + 1 qWarning printf-style → fprintf
  GeometryStreamer.cpp — 2 qDebug printf-style → fprintf
  ifcviewer-minimal/main.cpp — 2 qWarning << → Log::warn

Drops <QDebug> from each. Closes #82.

Builds: desktop / bonsai / web all green. Tests 100/100 pass.
2026-06-05 08:58:45 +10:00
Dion Moult 748b4e72a9 macOS: re-enable Python wrapper + stage IfcViewerMinimal.app bundle
Three coupled fixes that close the macOS bring-up loop:

## 1. ifcwrap: fix INSTALL_RPATH on Apple

The ifcopenshell_wrapper Python module had `INSTALL_RPATH "$ORIGIN"` set
for "NOT WIN32 AND NOT WASM_BUILD" — but `$ORIGIN` is a Linux ld.so
placeholder, not a macOS dyld one. macOS dyld doesn't expand it; it
bakes the literal string `$ORIGIN` into LC_RPATH, which resolves to
nothing at runtime. The wrapper's hard-link `@rpath/ifcopenshell
.document.rdb.dylib` then fails to load even though INSTALL(TARGETS …
LIBRARY DESTINATION "${python_package_dir}/ifcopenshell") above had
already dropped the plug-in dylib right next to the wrapper.

Split the rpath assignment: `@loader_path` on Apple (the dyld
equivalent of `$ORIGIN`), `$ORIGIN` elsewhere.

This is what b0ef47819 (the build_osx IFCOS_BUILD_PYTHON_WRAPPER=off
gate) was working around. The gate is removed below.

## 2. ifcviewer-minimal: stage IfcOpenShell + wgpu_native into the .app

IfcViewerMinimal.app was building on macOS via the cmake
`BUILD_BONSAIVIEWER → BUILD_BONSAIVIEWER_WGPU` promotion, but had no
bundle staging — Contents/Frameworks/ only contained the Qt
frameworks macdeployqt deposited, so the .app would refuse to start
("Library not loaded: @rpath/libwgpu_native.dylib").

Mirror what src/bonsaiviewer/CMakeLists.txt does for BonsaiViewer.app:

* Set INSTALL_RPATH to `@executable_path/../Frameworks` so the exe
  knows where to look for @rpath/* deps.
* install(FILES) libwgpu_native.dylib into the bundle's Frameworks/
  (globbed from WGPU_NATIVE_LIB_DIR rather than hard-coded so it
  covers any future versioned name).
* install(CODE) staging block that copies every `*.dylib` from
  <prefix>/lib/ into the bundle's Frameworks/, excluding the
  geometry-writer plug-ins (same EXCLUDE regex as BonsaiViewer.app —
  viewer doesn't need OBJ/glTF/DAE/STP/IGS/SVG/TTL export converters).

Same long-form rationale + caveats apply (macdeployqt doesn't follow
non-Qt @rpath deps, lib-prefixed core libs vs ifcopenshell.* plug-in
naming split, Linux's equivalent lives in build_rocky.yml workflow
bash via patchelf + stage_runtime_payload). See src/bonsaiviewer/
CMakeLists.txt for the full version.

## 3. build_osx.yml: drop IFCOS_BUILD_PYTHON_WRAPPER=off

With (1) fixed, the Python wrapper smoke test should pass again. The
gate goes away; the comment block in build_osx.yml is replaced with a
short note pointing at the ifcwrap rpath fix as the underlying change
that re-enables this.

Together, (1)+(2)+(3) close the standalone IfcViewerMinimal-on-macOS
gap (task #43) and re-enable IfcOpenShell-Python on the macOS arm64 CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 11:31:45 +10:00
Dion Moult 8ab5c31e75 refactor: merge ifcviewer-wgpu into ifcviewer, drop Wgpu prefix
The GL backend is gone (task #53). The wgpu/non-wgpu folder split and
the Wgpu* class prefix were both disambiguation artefacts from the
overlap period — now pure dead weight.

## Folder + library merge

* `src/ifcviewer-wgpu/`  → folded into `src/ifcviewer/` (git mv tracks
  every file as a rename so blame/log history survives).
* `src/ifcviewer-wgpu-minimal/` → `src/ifcviewer-minimal/` (the exe was
  already named `IfcViewerMinimal`; this just brings the folder + CMake
  target name into line).
* `src/ifcviewer-wgpu/tests/test_wgpu_{selection,visibility}.cpp` →
  `src/ifcviewer/tests/test_{selection,visibility}.cpp`, folded into
  the existing `add_ifcviewer_unit_test(...)` helper.
* The `IfcViewerWgpu` static library is dissolved — its sources become
  part of the unified `IfcViewer` static library, which now bundles
  scene/loader + renderer in one target. The pre-merge circular
  dependency (IfcViewer linking IfcViewerWgpu just to get the
  ViewportWindow.h include path that SceneLoader.h needs) goes away.
* The wgpu-native FetchContent block, the Cocoa/QuartzCore link on
  Apple, the OBJCXX-enabled `.mm` source, and the wgpu-native runtime
  install all move into `src/ifcviewer/CMakeLists.txt` unchanged.

## Type renames (Wgpu prefix dropped from every Wgpu* identifier)

  WgpuAreaMeasurement   → AreaMeasurement
  WgpuBufferPool        → BufferPool
  WgpuLengthMeasurement → LengthMeasurement
  WgpuMetalSurface      → MetalSurface
  WgpuModelGpuData      → ModelGpuData
  WgpuOverlayFrame      → OverlayFrame
  WgpuOverlayRenderer   → OverlayRenderer
  WgpuSectionPlane      → SectionPlane
  WgpuSelectionState    → SelectionState
  WgpuStreamingLoader   → StreamingLoader
  WgpuStreamingThread   → StreamingThread
  WgpuViewportWindow    → ViewportWindow
  WgpuVisibilityState   → VisibilityState

  CMake target IfcViewerWgpuMinimal → IfcViewerMinimal (exe name was
  already this since wgpu shipped as default).

Deliberately kept: `onWgpuLog` (wgpu-native log callback — names a
binding to an external API, not one of *our* types), and the WGPU*
enum/struct prefixes from wgpu-native's own headers. `WgpuMemProbe`
lives in the separate `src/wgpu-mem-probe/` standalone diagnostic
project and isn't touched.

## Include-path updates

Every `#include "../ifcviewer-wgpu/Wgpu<X>.h"` → `"../ifcviewer/<X>.h"`,
every in-directory `#include "Wgpu<X>.h"` → `"<X>.h"`. Includes from
sibling subdirectories (modules/, etc.) are updated to point at
`../../../ifcviewer/` instead of `../../../ifcviewer-wgpu/`.

## cmake/CMakeLists.txt simplification

The redundant `add_subdirectory(ifcviewer-wgpu)` blocks (one inside
the BUILD_BONSAIVIEWER fan-in, one in the BONSAIVIEWER-less standalone
block) collapse into a single unconditional
`add_subdirectory(../src/ifcviewer ifcviewer)`. The standalone block
keeps only `wgpu-mem-probe` (the diagnostic tool, unrelated to the
viewer lib).

## Verification

* Full build green: `IfcViewer` static lib, `IfcViewerMinimal` exe,
  `BonsaiViewer` exe, all four pre-existing ifcviewer unit tests, and
  the two new-location tests (`test_selection`, `test_visibility`).
* No stray `Wgpu<X>` identifier remains across `src/ifcviewer/`,
  `src/bonsaiviewer/`, `src/ifcviewer-minimal/` (verified by grep).
* Renames tracked by git as `R` entries — `git log --follow` on
  ViewportWindow.cpp etc. continues to show history through the move.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 11:11:14 +10:00
Dion Moult 2981500b3b Route bonsai through wgpu; delete the GL backend
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>
2026-06-01 17:38:33 +10:00
Dion Moult 5810894eb8 ifcviewer (GL minimal): --screenshot for parity diff with wgpu
Closes the other half of task #10. The wgpu minimal already wrote PNGs
via wgpuCommandEncoderCopyTextureToBuffer + mapAsync; the GL backend
now has the equivalent via glReadPixels on the back buffer just before
swapBuffers.

  - ViewportWindow::captureNextFrameToPng(path, quit_after=true) queues
    a one-shot capture. render() reads the default framebuffer at full
    pixel size (width * devicePixelRatio), flips bottom-up → top-down
    into a QImage::Format_RGBA8888, saves PNG, and optionally
    QCoreApplication::quit. Synchronous glReadPixels is fine here —
    pick is interactive and rare; not used per-frame.

  - ifcviewer-minimal --screenshot PATH wires through MinimalWindow
    just like --camera / --benchmark. Honoured after all loads complete
    (applyPendingBenchmark also drains pending_screenshot_).

Lets a parity script do:
    IfcViewerMinimal      foo.ifc      --camera A,B,C,D,E,F --screenshot gl.png
    IfcViewerWgpuMinimal  foo.ifcview  --camera A,B,C,D,E,F --screenshot wgpu.png
    # then pixel-diff with whatever (ImageMagick, PIL, etc.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:33:20 +10:00
Dion Moult 8b8fafa698 Unify sidecar production via SidecarBuilder
Renamed HeadlessSidecarBuilder to SidecarBuilder and reused it for live
loads. SceneLoader now constructs one per stream load, forwards meshReady
/instanceReady chunks alongside the viewport upload, and finalizes +
writes the sidecar at onStreamerFinished — no more GPU readback path
via ViewportWindow::snapshotModel (removed). Same code path now produces
sidecars for both live loads and the .rdbview offline export.

Sidecar use is opt-in per direction via SceneLoader::setShouldReadSidecar
and setShouldWriteSidecar; both default off so embedders that don't want
caching get a pure-streaming loader. ifcviewer-full and ifcviewer-minimal
opt in.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 16:34:18 +10:00
Thomas Krijnen 05cd19592d Qt install (not working) 2026-05-08 10:56:41 +02:00
Dion Moult 4e4553201b Fix viewer load termination
Handle streamer success, failure, and cancellation as distinct terminal states so failed or cancelled loads do not finalize as successful models. Clean up partial model/UI state in the full and minimal viewer apps when a load is cancelled or fails.

Generated with the assistance of an AI coding tool.
2026-04-23 21:32:25 +10:00
Dion Moult 29f5132510 ifcviewer: extract SceneLoader, remove duplicated load orchestration
MainWindow and MinimalWindow each carried ~150 lines of mirrored
load-queue, sidecar-thread, streamer-wiring, and ID-rebase code. Lift
all of it into a SceneLoader QObject in the library; both apps now
consume it via signals. Sidecar writes stay on the full-app side since
they need the consumer's element metadata strings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 21:32:25 +10:00
Dion Moult ae938d425b ifcviewer: split into shared library and two app executables
Turn src/ifcviewer into libIfcViewer.so holding the rendering engine +
geometry pipeline (ViewportWindow, GeometryStreamer, BvhAccel,
InstancedGeometry, SidecarCache, LodBuilder, AppSettings).  Move the
existing UI shell (MainWindow, SettingsWindow, main.cpp) into
src/ifcviewer-full as the IfcViewerFull executable.  Add a new
src/ifcviewer-minimal target with a MinimalWindow that hosts only the
viewport and reuses the sidecar fast-path for benchmark/debug runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:32:25 +10:00