From b62e14a06ae06113903b57c0605379f09b44955d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 5 Jun 2026 09:14:19 +1000 Subject: [PATCH] =?UTF-8?q?ifcviewer:=20de-Qt=20ViewportWindow=20public=20?= =?UTF-8?q?API=20(QString=20=E2=86=92=20std::string)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 pending_sidecars_ → std::deque 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. --- src/bonsaiviewer/Measurement.cpp | 6 +-- src/bonsaiviewer/modules/viewport/View.cpp | 8 ++-- src/ifcviewer-minimal/main.cpp | 4 +- src/ifcviewer/LengthMeasurement.cpp | 6 +-- src/ifcviewer/ViewportWindow.cpp | 50 +++++++++++++--------- src/ifcviewer/ViewportWindow.h | 16 +++---- 6 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/bonsaiviewer/Measurement.cpp b/src/bonsaiviewer/Measurement.cpp index 104689f497..b24c072269 100644 --- a/src/bonsaiviewer/Measurement.cpp +++ b/src/bonsaiviewer/Measurement.cpp @@ -662,7 +662,7 @@ void LengthMeasurement::clear(ViewportWindow& vp) { vp.setOverlayPoints({}, 0,0,0,0, 0, 0,0,0,0, 0); vp.setOverlayLines({}); vp.setOverlayLabels({}); - vp.setHudText(QString()); + vp.setHudText(std::string()); } void LengthMeasurement::onPick(ViewportWindow& vp, int x, int y, bool /*alt*/) { @@ -805,7 +805,7 @@ void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) { vp.setOverlayLines(groups); vp.setOverlayLabels(labels); - vp.setHudText(formatReadout()); + vp.setHudText(formatReadout().toStdString()); } namespace { @@ -1029,7 +1029,7 @@ void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) { pushDots(vp, std::vector(wp, wp + 3)); vp.setOverlayLines(groups); vp.setOverlayLabels(labels); - vp.setHudText(hud_lines.join('\n')); + vp.setHudText(hud_lines.join('\n').toStdString()); } QString LengthMeasurement::formatReadout() const { diff --git a/src/bonsaiviewer/modules/viewport/View.cpp b/src/bonsaiviewer/modules/viewport/View.cpp index 9b9a781b27..e5eaa02723 100644 --- a/src/bonsaiviewer/modules/viewport/View.cpp +++ b/src/bonsaiviewer/modules/viewport/View.cpp @@ -80,7 +80,7 @@ ViewportView::ViewportView(bonsaiviewer::SessionState* session_state, area_measurement_->onPick(*viewport_, x, y, alt); viewport_->setHudText(QString("Area: %1 m² (%2 tris)") .arg(area_measurement_->totalArea(), 0, 'f', 4) - .arg(area_measurement_->triangleCount())); + .arg(area_measurement_->triangleCount()).toStdString()); break; case ViewportWindow::ToolMode::Length: length_measurement_->onPick(*viewport_, x, y, alt); @@ -96,7 +96,7 @@ ViewportView::ViewportView(bonsaiviewer::SessionState* session_state, length_measurement_->clear(*viewport_); switch (mode) { case ViewportWindow::ToolMode::NoTool: - viewport_->setHudText(QString()); + viewport_->setHudText(std::string()); viewport_->setOverlayLabels({}); session_state_->setStatusMessage("Measure", "Measurement tool off"); break; @@ -241,7 +241,7 @@ void ViewportView::updateVolumeReadout() { const auto& sel = viewport_->selection().selectionIds(); if (sel.empty()) { - viewport_->setHudText(QString()); + viewport_->setHudText(std::string()); viewport_->setOverlayLabels({}); return; } @@ -268,7 +268,7 @@ void ViewportView::updateVolumeReadout() { viewport_->setHudText(QString("Volume: %1 m³ (%2 object%3)") .arg(total, 0, 'f', 4) .arg(per_obj.size()) - .arg(per_obj.size() == 1 ? "" : "s")); + .arg(per_obj.size() == 1 ? "" : "s").toStdString()); viewport_->setOverlayLabels(labels); } diff --git a/src/ifcviewer-minimal/main.cpp b/src/ifcviewer-minimal/main.cpp index 19d5d31dce..bb059b8798 100644 --- a/src/ifcviewer-minimal/main.cpp +++ b/src/ifcviewer-minimal/main.cpp @@ -75,7 +75,7 @@ int main(int argc, char* argv[]) { // Queue sidecars; they're loaded after wgpu init completes in // exposeEvent. Ordering matches the command line. for (const QString& path : parser.positionalArguments()) { - viewport->queueLoadSidecar(path); + viewport->queueLoadSidecar(path.toStdString()); } if (parser.isSet("camera")) { @@ -97,7 +97,7 @@ int main(int argc, char* argv[]) { } if (parser.isSet("screenshot")) { - viewport->captureNextFrameToPng(parser.value("screenshot"), + viewport->captureNextFrameToPng(parser.value("screenshot").toStdString(), /*quit_after=*/true); } if (parser.isSet("benchmark")) { diff --git a/src/ifcviewer/LengthMeasurement.cpp b/src/ifcviewer/LengthMeasurement.cpp index 7f0b620144..7e1c532388 100644 --- a/src/ifcviewer/LengthMeasurement.cpp +++ b/src/ifcviewer/LengthMeasurement.cpp @@ -356,7 +356,7 @@ void LengthMeasurement::clear(ViewportWindow& vp) { vp.setOverlayPoints({}, 0,0,0,0, 0, 0,0,0,0, 0); vp.setOverlayLines({}); vp.setOverlayLabels({}); - vp.setHudText(QString()); + vp.setHudText(std::string()); } void LengthMeasurement::onPick(ViewportWindow& vp, @@ -486,7 +486,7 @@ void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) { vp.setOverlayLines(groups); vp.setOverlayLabels(labels); - vp.setHudText(formatReadout()); + vp.setHudText(formatReadout().toStdString()); } void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) { @@ -679,7 +679,7 @@ void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) { pushDots(vp, std::vector(wp, wp + 3)); vp.setOverlayLines(groups); vp.setOverlayLabels(labels); - vp.setHudText(hud_lines.join('\n')); + vp.setHudText(hud_lines.join('\n').toStdString()); } QString LengthMeasurement::formatReadout() const { diff --git a/src/ifcviewer/ViewportWindow.cpp b/src/ifcviewer/ViewportWindow.cpp index 1aa3f5fbad..8bd46e342d 100644 --- a/src/ifcviewer/ViewportWindow.cpp +++ b/src/ifcviewer/ViewportWindow.cpp @@ -689,7 +689,7 @@ void ViewportWindow::setBackgroundColor(const QColor& color) { // Sidecar load + GPU upload // ----------------------------------------------------------------------------- -void ViewportWindow::queueLoadSidecar(const QString& path) { +void ViewportWindow::queueLoadSidecar(const std::string& path) { if (wgpu_initialized_) { loadSidecar(path); } else { @@ -697,7 +697,13 @@ void ViewportWindow::queueLoadSidecar(const QString& path) { } } -uint32_t ViewportWindow::loadSidecar(const QString& path) { +uint32_t ViewportWindow::loadSidecar(const std::string& path_std) { + // Internal implementation still uses Qt's path helpers (QDir tilde + // expansion, QFile readability checks, QFileInfo for absolute resolve). + // Bridging at the entry boundary keeps the public API Qt-free without + // a full internal rewrite — those will move to std::filesystem when + // ViewportCore lands (#84). + const QString path = QString::fromStdString(path_std); if (!wgpu_initialized_) { Log::warn().noquote() << "loadSidecar called before wgpu init:" << path; return 0; @@ -1502,7 +1508,7 @@ void ViewportWindow::frameOnFederatedOrigin(uint32_t model_id, void ViewportWindow::flushPendingSidecarQueue() { while (!pending_sidecars_.empty()) { - const QString p = pending_sidecars_.front(); + const std::string p = pending_sidecars_.front(); pending_sidecars_.pop_front(); loadSidecar(p); } @@ -3109,8 +3115,11 @@ void ViewportWindow::setOverlayLabels( if (isExposed()) requestUpdate(); } -void ViewportWindow::setHudText(const QString& text) { - overlays_.setHudText(text); +void ViewportWindow::setHudText(const std::string& text) { + // OverlayRenderer still uses QString internally (Qt's QImage/QPainter + // rasterizes the HUD text). Conversion at the boundary keeps the + // public API Qt-free; OverlayRenderer's de-Qt comes later. + overlays_.setHudText(QString::fromStdString(text)); if (isExposed()) requestUpdate(); } @@ -5011,7 +5020,7 @@ void ViewportWindow::render() { // ---- Optional capture: encode copy on the same command buffer ------- WGPUBuffer capture_buffer = nullptr; uint32_t capture_padded_bpr = 0; - const bool want_capture = !pending_screenshot_path_.isEmpty(); + const bool want_capture = !pending_screenshot_path_.empty(); if (want_capture) { const uint32_t row_bytes_unpadded = uint32_t(configured_w_) * 4u; capture_padded_bpr = uint32_t( @@ -5100,11 +5109,12 @@ void ViewportWindow::render() { } wgpuBufferUnmap(capture_buffer); - if (img.save(pending_screenshot_path_, "PNG")) { - Log::info().noquote() << "[wgpu] saved screenshot:" - << pending_screenshot_path_ << "(" << w << "x" << h << ")"; + const QString qpath = QString::fromStdString(pending_screenshot_path_); + if (img.save(qpath, "PNG")) { + Log::info().noquote() << "[wgpu] saved screenshot: " + << pending_screenshot_path_ << " (" << w << "x" << h << ")"; } else { - Log::warn().noquote() << "[wgpu] QImage::save failed for" + Log::warn().noquote() << "[wgpu] QImage::save failed for " << pending_screenshot_path_; } } @@ -6524,7 +6534,7 @@ void ViewportWindow::driveStreamingLoads() { // wait would let the window manager re-layout the window while we // wait, capturing at the wrong size. With sync loads the chunk // appears in the same frame we enqueue, no deferred-state to manage. - if (!pending_screenshot_path_.isEmpty()) { + if (!pending_screenshot_path_.empty()) { if (loadChunkBytesAndUploadGpu(*cand.m, cand.ci)) { ++enqueued; c.last_visible_frame_idx = streaming_frame_idx_; @@ -7002,14 +7012,12 @@ void ViewportWindow::toggleProjection() { if (isExposed()) requestUpdate(); } -QString ViewportWindow::cameraString() const { - return QString("%1,%2,%3,%4,%5,%6") - .arg(camera_target_[0], 0, 'f', 4) - .arg(camera_target_[1], 0, 'f', 4) - .arg(camera_target_[2], 0, 'f', 4) - .arg(camera_distance_, 0, 'f', 4) - .arg(camera_yaw_deg_, 0, 'f', 2) - .arg(camera_pitch_deg_, 0, 'f', 2); +std::string ViewportWindow::cameraString() const { + char buf[128]; + std::snprintf(buf, sizeof(buf), "%.4f,%.4f,%.4f,%.4f,%.2f,%.2f", + camera_target_[0], camera_target_[1], camera_target_[2], + camera_distance_, camera_yaw_deg_, camera_pitch_deg_); + return std::string(buf); } void ViewportWindow::enterFpsMode() { @@ -7202,7 +7210,7 @@ void ViewportWindow::applyNavPreset(const char* name) { #include #include -void ViewportWindow::captureNextFrameToPng(const QString& path, bool quit_after) { +void ViewportWindow::captureNextFrameToPng(const std::string& path, bool quit_after) { pending_screenshot_path_ = path; pending_screenshot_quit_ = quit_after; if (isExposed()) requestUpdate(); @@ -7773,7 +7781,7 @@ void ViewportWindow::keyPressEvent(QKeyEvent* event) { return; } if (key == Qt::Key_C && !(mods & Qt::ControlModifier)) { - Log::info() << "--camera " << cameraString().toUtf8().constData(); + Log::info() << "--camera " << cameraString(); return; } // Standard axis-aligned views: X/Y/Z look from +axis, Shift+X/Y/Z from diff --git a/src/ifcviewer/ViewportWindow.h b/src/ifcviewer/ViewportWindow.h index b640c54417..8c7f854549 100644 --- a/src/ifcviewer/ViewportWindow.h +++ b/src/ifcviewer/ViewportWindow.h @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include @@ -95,14 +95,14 @@ public: // call before the window is exposed. The path is resolved against the // working directory and read via SidecarCache::readSidecar (which // normalises stem → .ifcview). - void queueLoadSidecar(const QString& path); + void queueLoadSidecar(const std::string& path); // Synchronous metadata load + GPU upload. Requires wgpu init to have // completed (i.e. the window has been exposed at least once). Returns // the assigned model_id, or 0 on failure. Reads metadata only (mesh // dict + instance dict + georef); per-chunk vertex / index bytes are // read on demand by the per-frame loader as chunks become visible. - uint32_t loadSidecar(const QString& path); + uint32_t loadSidecar(const std::string& path); // Allocates per-chunk small buffers and the model-shared mesh / // instance storage upfront, but leaves each chunk's pool ranges @@ -172,7 +172,7 @@ public: void focusOnSelectedObject(); void toggleProjection(); bool projectionOrtho() const { return projection_ortho_; } - QString cameraString() const; + std::string cameraString() const; // Snapshot of the orbit camera. Mirrors GL ViewportWindow::CameraState // so bonsai's "save view" / "restore view" commands port unchanged. @@ -250,7 +250,7 @@ public: // `quit_after` is true, QCoreApplication::quit() is called once the // PNG is written. Use this for headless verification and pixel-diff // parity testing against the GL backend. - void captureNextFrameToPng(const QString& path, bool quit_after = true); + void captureNextFrameToPng(const std::string& path, bool quit_after = true); // Benchmark mode: render N timed frames (after a small warmup), yaw- // sweeping the camera at 0.5°/frame, then print a stats block on @@ -390,7 +390,7 @@ public: float stroke_b, float stroke_a, float stroke_extra); void setOverlayLabels(const std::vector& labels); - void setHudText(const QString& text); + void setHudText(const std::string& text); // Translucent world-space triangle overlay (Area-tool patch shading). // Empty list disables; color is RGBA in [0, 1]. void setHighlightTriangles(const std::vector& world_xyz, @@ -1023,7 +1023,7 @@ private: uint32_t next_object_id_ = 1; // Sidecar paths queued before init completes. - std::deque pending_sidecars_; + std::deque pending_sidecars_; // Direct-IFC staging buffers, keyed by streamer model_id. Populated // by uploadMeshChunk / uploadInstanceChunk; consumed and cleared by @@ -1057,7 +1057,7 @@ private: bool last_cull_was_motion_ = false; // Pending one-shot screenshot, captured at the end of the next render(). - QString pending_screenshot_path_; + std::string pending_screenshot_path_; bool pending_screenshot_quit_ = false; // Mouse navigation state. LMB drag orbits, MMB drag pans, wheel zooms.