mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-17 10:59:17 +00:00
32f2dfd2949fe3e0eff44456feaee4f9fcdd6834
21808 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
66a21923b8 |
ifcviewer: move buildPipelines + selection-flags wiring into ViewportCore (#84-k)
Move the main render pipeline construction + the selection flags
buffer/bind group lifecycle. Both buildPipelines and the selection
flags methods produce/consume state ViewportCore already owns
(main_pipeline_, frame_bgl_, etc.) plus a handful of "frame
infrastructure" fields this commit also brings across.
State moved (7 fields):
WGPUBuffer frame_uniform_buffer_
WGPUBindGroup frame_bind_group_
WGPUBuffer selection_flags_buffer_
uint32_t selection_flags_capacity_
std::vector<u32> selection_flags_scratch_
SelectionState selection_
VisibilityState visibility_
Methods moved:
buildPipelines (~150 lines + 320-line MAIN_WGSL string)
ensureSelectionFlagsBuffer (~60 lines)
uploadSelectionFlagsIfDirty (~10 lines)
Plus the MAIN_WGSL constant + the svFromCStr helper into
ViewportCore.cpp's anonymous namespace. ViewportWindow.cpp keeps its
own svFromCStr copy (still used by 50+ label fields in the not-yet-
moved pipeline builders + render encoders).
Shared constants extracted to ViewportCore.h:
kMaxSectionPlanes (was OverlayRenderer::kMaxSectionPlanes — assert
in VW.cpp keeps them in sync)
kViewportSampleCount (was SAMPLE_COUNT in VW; VW keeps a static
constexpr alias for the existing callsites)
struct FrameUniforms (canonical layout for the per-frame UBO,
consumed by both core's buildPipelines and
VW's still-in-flight updateFrameUniforms)
Builds: desktop / bonsai / web all green. Tests 100/100.
|
||
|
|
8cf7d4346d |
ifcviewer: move volume readout helpers into ViewportCore (#84-j)
Tiny followup to #84-i — move the const-lookup volume helpers used by
bonsai's measurement HUD:
double volumeOfObjects(const std::vector<uint32_t>&) const
vector<pair<uint32_t, double>> volumesPerObject(
const std::vector<uint32_t>&) const
The det3OfPlacement static helper moves with them into ViewportCore.cpp's
anonymous namespace (the original kept its mirror in
ViewportWindow.cpp; ViewportWindow's own internal callers are gone now
since these methods moved).
Pure read of models_gpu_ + mesh_local_volumes — all in core already.
Trivial transplant.
Builds: desktop / bonsai / web all green. Tests 100/100.
|
||
|
|
707bb8f5d4 |
ifcviewer: move camera mutators + AABB helpers into ViewportCore (#84-i)
Move the cluster of camera-state mutators + per-object AABB helpers now that the camera fields all live in ViewportCore. CameraState struct is canonical in core; ViewportWindow keeps a `using` alias so bonsai's HomeView round-trip (Commands.cpp setHome / restoreHome) compiles unchanged. Moved: void viewAll() void setCamera(...) — pitch + distance clamping included void setStandardView(yaw, pitch) — bypasses clamp for ±90° void toggleProjection() std::string cameraString() const CameraState cameraState() const void frameAabb(mn, mx, padding) bool computeObjectAabb(id, float[3], float[3]) const bool computeObjectAabb(id, Eigen::Vector3f&, Eigen::Vector3f&) const ViewportWindow keeps thin forwarders for the public ones (bonsai calls them). setCamera additionally flips initial_view_applied_ on the VW side — the auto-viewAll suppression flag isn't in core yet because the trigger for auto-viewAll lives in the still-in-VW applyCachedModel path. The isExposed()+requestUpdate() Qt pattern inside the moved bodies becomes host_->requestFrame(); two viewAll/toggleProjection diagnostic prints become fprintf since Log::info() doesn't reach into core.cpp through the Qt logging surface. Builds: desktop / bonsai / web all green. Tests 100/100. |
||
|
|
14e7c9fc42 |
ifcviewer: move camera math into ViewportCore (#84-h)
Move the three camera-math methods that compute view/projection
matrices, scene bounds, and per-chunk screen footprint for the
streaming priority signal:
void buildViewProj(Eigen::Matrix4f&, Eigen::Matrix4f&) const
bool computeSceneAabb(float[3], float[3]) const
float chunkScreenAreaPx(const ModelGpuData::Chunk&,
const Eigen::Matrix4f&) const
Plus the orbitEye helper (anonymous namespace in ViewportCore.cpp;
the qDegreesToRadians dep got swapped for an inline M_PI/180 constant).
ViewportWindow.cpp's 9 internal callers (cull, streaming, pick,
render, debug) updated to use core_.buildViewProj() etc. The
buildViewProj forwarder stays out of ViewportWindow.h since no
external caller needs it — bonsai/minimal both go through
public API methods like viewAll which still wrap core_ access
on the VW side.
Builds: desktop / bonsai / web all green. Tests 100/100.
|
||
|
|
a0db182d2b |
ifcviewer: move camera + surface-geom state into ViewportCore (#84-g)
Move the camera/projection/clear-color fields that buildViewProj, updateFrameUniforms, the cull screen-area projector, and the bonsai- side cameraState/setCamera/viewAll surface depend on. Same alias pattern; no method bodies move in this commit — the next one moves the camera math methods now that all their state is in core. State moved (12 fields): int configured_w_, configured_h_ float camera_target_[3], camera_distance_ float camera_yaw_deg_, camera_pitch_deg_, camera_fov_y_deg_ float camera_near_, camera_far_ bool projection_ortho_ Eigen::Vector4f background_color_ ViewportWindow keeps reference aliases for each (including a proper `float (&camera_target_)[3]` reference-to-array binding) so the ~150 call sites that touch camera state stay unchanged. Aliases collapse when their owning methods migrate. Builds: desktop / bonsai / web all green. Tests 100/100. |
||
|
|
8da0993457 |
ifcviewer: move scene mutators + releaseWgpuModelGpuData into ViewportCore (#84-f)
Move the eight scene-mutation methods that drive bonsai's load/unload
and georeference setters, plus the per-model GPU teardown helper.
All are mechanical transplants — no logic change — so behaviour stays
identical; only the owner has changed.
Methods moved (ViewportWindow public-API methods stay as forwarders
to keep the bonsai-side callers compiling):
removeModel / resetScene / hideModel / showModel
setFederatedFalseOrigin
setModelCoordinateOperation
setModelTransformation
recomposeAndUploadModel
State moved:
bool wgpu_initialized_ (storage → core_, alias kept in VW for
the initWgpu call site that still flips
it; goes when initWgpu moves)
Free function moved:
releaseWgpuModelGpuData(ModelGpuData&, BufferPool&) → ViewportCore.cpp
(must live in IfcViewerCore now that ViewportCore.cpp's
removeModel / resetScene call it; ViewportWindow.cpp's remaining
two call sites continue to resolve through ModelGpuData.h's
declaration — same linker view, different definition TU)
The `if (isExposed()) requestUpdate()` Qt pattern inside the moved
bodies became `host_->requestFrame()` since ViewportCore can't see
QWindow; the desktop ViewportHost override at the bottom of
ViewportWindow.cpp continues to translate that into requestUpdate().
Builds: desktop / bonsai / web all green. Tests 100/100.
|
||
|
|
b2fe9c4a71 |
ifcviewer: move const-lookup methods into ViewportCore (#84-e)
Move two pure-read methods (no GPU touch, no Qt) that the bonsai measurement / federation-origin paths use: bool findInstance(uint32_t, InstanceLookup&) const bool firstGeometryPointWorldM(uint32_t, Vector3d&) const ViewportWindow keeps both public-API method names — they now forward to core_ for the implementation so existing callers in bonsaiviewer/Measurement.cpp + Federation hooks don't have to change. The InstanceLookup type also stays a `using` alias in ViewportWindow (was added in #74). Both methods were already de-Qt'd (`findInstance` delegates to InstanceCompose; `firstGeometryPointWorldM` is pure Eigen). The move is a straight transplant — no behaviour change. Builds: desktop / bonsai / web all green. Tests 100/100. |
||
|
|
ad6822ac85 |
ifcviewer: move composeInstanceFromPlacement into ViewportCore (#84-d)
First method-body migration. composeInstanceFromPlacement composes the
federated-false-origin × model-transformation × coordinate-operation ×
placement chain and re-derives the world AABB; it's a small,
self-contained method that only reads scene state and one matrix.
Moved:
Eigen::Matrix4d federated_false_origin_meters_ (storage → core_)
void composeInstanceFromPlacement(InstanceCpu&, ...) (body → core_)
ViewportWindow keeps:
- alias reference to federated_false_origin_meters_ (existing
setFederatedFalseOrigin call site still writes through it)
- no method declaration — internal callers route through core_
Internal caller (recomposeAndUploadModel) now invokes
core_.composeInstanceFromPlacement; once recomposeAndUploadModel
itself moves into ViewportCore the call shortens back.
Pattern for the rest of #84: state moves, then method body moves,
then internal callers update. Each commit leaves desktop / bonsai /
web green and tests 100/100. This is one of many such steps.
|
||
|
|
303f903a10 |
ifcviewer: move scene state into ViewportCore (#84-c)
Move the five scene-state fields that drive per-model GPU upload + the
streaming residency loop into ViewportCore:
BufferPool pool_ — vertex+index sub-allocator
StreamingThread streaming_thread_ — background chunk reader
std::unordered_map<uint32_t, ModelGpuData> models_gpu_
— per-model state
uint32_t next_model_id_ — model-id allocator
uint32_t next_object_id_ — globally-unique object-id allocator
ViewportCore.h gains transitive includes for BufferPool / StreamingThread
/ ModelGpuData; ViewportWindow keeps the same names as reference aliases
so existing method bodies that touch them don't have to change.
Same risk profile as #84-a and #84-b: the storage moved but the values
are still set and consumed by the same code paths, so behaviour stays
identical.
Builds: desktop / bonsai / web all green. Tests 100/100.
|
||
|
|
d0be7b775a |
ifcviewer: move render pipelines into ViewportCore (#84-b)
Move the 15 pipeline + bind-group-layout + shader-module handles
that buildPipelines / buildEdgePipeline / buildPickPipeline write to.
Same pattern as #84-a: storage lives in ViewportCore, ViewportWindow
keeps reference aliases so existing builder-method bodies don't
have to acquire a `core_.` prefix at every touch point.
Moved fields:
Main render group:
main_shader_module_, frame_bgl_ (group 0), model_bgl_ (group 1),
pipeline_layout_, main_pipeline_, main_pipeline_transparent_
HiZ occlusion-cull group:
hiz_shader_module_, hiz_bgl_, hiz_pipeline_layout_, hiz_pipeline_
Edge silhouette group:
edge_shader_module_, edge_bgl_, edge_pipeline_layout_, edge_pipeline_
Pick pass:
pick_pipeline_ (reuses pipeline_layout_ — same set of bindings)
ViewportWindow's constructor binds 16 new alias references after
the 7 lifecycle ones from #84-a; member-init order matches
declaration order so core_ is constructed before any alias binds.
Builds: desktop / bonsai / web all green. Tests 100/100.
|
||
|
|
e37a78f4f5 |
ifcviewer: move wgpu lifecycle state ownership into ViewportCore
First chunk of the #84 ViewportCore extraction. The seven wgpu lifecycle handles (instance, adapter, device, queue, surface, surface_format, surface_configured) now live as ViewportCore members; ViewportWindow keeps reference aliases pointing at ViewportCore's storage so its existing render-method bodies don't need a `core_.` prefix added at every call site — 230+ touches deferred until each method moves across. Member init order in ViewportWindow's constructor: core_(this) → constructs ViewportCore with host_=this instance_(core_.instance_) → binds the alias to core_'s field … same for adapter/device/queue/surface/… Friend declaration on ViewportCore::ViewportWindow lets the references bind to its private fields. The friend bond shrinks each commit as render methods (and their `device_` / `queue_` references) migrate into ViewportCore proper; the goal state is no friend and no aliases. Next #84 chunks (separate commits) move pipelines, models_gpu_, pool_, streaming_thread_, then the render/cull/encode methods. Each leaves the desktop build green. Builds: desktop / bonsai / web all green. Tests 100/100. |
||
|
|
1a17ba9e6d |
ifcviewer: de-Qt QColor/QPoint/QSet/QElapsedTimer in ViewportWindow
Last round of straight-swap Qt value types in ViewportWindow + its
overlay co-pilot.
setBackgroundColor(const QColor&) → (float r, float g, float b, float a)
QColor background_color_ → Eigen::Vector4f (linear, 0..1)
QPoint {nav_,box_select_,fps_, } → Eigen::Vector2i
{section_drag_start_mouse_}
QSet<int> fps_keys_held_ → std::unordered_set<int>
QElapsedTimer fps_last_tick_, → Stopwatch (new header in
fly_render_clock_, IfcViewerCore — std::chrono-
render_thread_local_ backed, exposes the existing
timers in render() QElapsedTimer .start/.restart/
.elapsed/.nsecsElapsed surface)
Also propagates the QPoint → Eigen::Vector2i change through
OverlayRenderer::encodeMarquee since the marquee corner coords flow
through that interface.
API-level helpers:
toV2i(QPoint) — small inline in ViewportWindow.cpp, isolates
the QMouseEvent→Vector2i conversion at the
five mouse-event handlers
Stopwatch.h — new file, IfcViewerCore. Same call shape as
QElapsedTimer; backed by std::chrono::steady_clock.
QSet method swaps:
.isEmpty() → .empty()
.contains(k) → .count(k) (C++17, no std contains() until C++20)
.remove(k) → .erase(k)
Eigen::Vector2i doesn't have .manhattanLength(); the box-select drag
threshold uses std::abs(diff.x()) + std::abs(diff.y()) inline.
Bonsai side: View.cpp's setBackgroundColor wrapper now decomposes the
QColor into floats at the call site (kept locally so the bonsai UI
keeps its QColor-driven theming).
Closes #81 + the QElapsedTimer half of #83. QTimer
(pivot_indicator_hide_timer_) still uses Qt — it needs the host's
scheduleOnce mechanism that lands with #85.
Builds: desktop / bonsai / web all green. Tests 100/100.
|
||
|
|
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.
|
||
|
|
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. |
||
|
|
77cf535b45 |
ifcviewer: de-Qt math types (Eigen everywhere)
Replace Qt math wrappers with Eigen across ViewportWindow, OverlayRenderer, Federation, and the bonsai-side viewport modules. Eigen was already the canonical type for the actually-important matrix work (InstanceCompose, ModelGpuData, federation matrices); QVector3D/QVector4D/QMatrix4x4 were leftover from when Qt was the path of least resistance. They offered nothing over Eigen for our use case beyond a few graphics helpers (lookAt / perspective / ortho) which were 30 lines to write. Substitutions: QMatrix4x4 → Eigen::Matrix4f QVector2D → Eigen::Vector2f QVector3D → Eigen::Vector3f QVector4D → Eigen::Vector4f API rewrites: .lengthSquared() → .squaredNorm() .length() → .norm() .isNull() → .isZero() .setToIdentity() → .setIdentity() .constData() → .data() .toVector3D() → .head<3>() .inverted(&ok) → tryInvert4f(M, out) Q::dotProduct(a,b) → a.dot(b) Q::crossProduct(a,b) → a.cross(b) QMat4x4(... row-major) → Eigen::Map<const Matrix4f>(col-major buf) QMat4x4().lookAt(...) → lookAtRH(eye, target, up) QMat4x4().perspective(.) → perspectiveYFovGL(fovy, aspect, n, f) QMat4x4().ortho(...) → orthoGL(l, r, b, t, n, f) Default-init divergence handled explicitly (QMatrix4x4() = identity, QVector3D() = zero; Eigen leaves both uninitialized). Public API (CameraState, HomeView, ViewportWindow::computeObjectAabb, the addSectionPlaneAtSurface / pickSurfaceAt / raycast signatures) follows through to Eigen too; bonsai-side View.cpp and Commands.cpp updated to match. Camera helpers (lookAtRH, perspectiveYFovGL, orthoGL, tryInvert4f) extracted to a new CameraMath.h so OverlayRenderer's gizmo MVP and ViewportWindow's buildViewProj share the same definitions. Federation drops its <QVector3D> include in favour of <Eigen/Dense> (already had the latter for the georef matrices). Builds: desktop IfcViewerMinimal ✓, BonsaiViewer ✓, web IfcViewerWeb ✓. Tests: 100/100 pass. Closes #78 + #79; opens the door for #80-#83. |
||
|
|
1f2b20fd86 | Fix --convert-back-units on transformation object #8137 | ||
|
|
94fab271cd | Check for empty result after BOPAlgo_MakerVolume and reset manifoldness state #8140 | ||
|
|
8583d0963f | Make faceset duplicate loop detection respect inner/outer #8140 | ||
|
|
77a2284f8a | Re-sew non-manifold operands; interior loop re-orientations affect edge identity #8140 | ||
|
|
4520a72152 | Sane error messages for unsupported items in geometry libs #8106 | ||
|
|
c314dd3ca8 |
ifcviewer: scaffold ViewportHost + ViewportCore (Path A step 1)
Define the boundary the Path-A web-bring-up refactor will move things
across:
- ViewportHost.h is the embedder interface — surface creation,
framebuffer geometry, frame scheduling, quit, and notification
callbacks (onObjectPicked, onToolModeChanged, …). Desktop hosts
forward notifications to Q_SIGNALS; the future web host pushes
them to JS callbacks.
- ViewportCore.{h,cpp} is the platform-agnostic render-core target.
Empty today — the body fills in across the #78-#86 sequence as
each Qt subsystem (matrices, vectors, strings, timers, render
path, input) gets de-Qt'd and moved over.
- ViewportWindow now multiply-inherits ViewportHost alongside QWindow
and implements the host overrides as thin forwarders: createSurface
returns the cached surface_, requestFrame -> requestUpdate, quit ->
QCoreApplication::quit, onObjectPicked -> emit objectPicked.
Renamed the DPR accessor `dpr()` (vs `devicePixelRatio`) to avoid
the inherited-virtual clash with QWindow's qreal-returning version.
No method movement yet — this is purely the architectural scaffold so
subsequent commits have a destination.
|
||
|
|
e55a360aa2 |
web: scaffold IfcViewerWeb (Emscripten clear-color renderer)
First Emscripten target. main_web.cpp brings up a wgpu instance against
a <canvas id="viewer-canvas">, requests adapter+device asynchronously
via the standard webgpu.h callback chain, configures the surface, and
clears to the BonsaiViewer slate background on each RAF tick. No
sidecar load, no pipelines, no scene state yet — the goal is to end-
to-end verify the build + canvas + wgpu plumbing.
src/ifcviewer-web/ is a separate CMake root (not a subdir under the
desktop cmake/CMakeLists.txt) so the web build doesn't have to opt out
of Qt / OpenCASCADE / IfcGeom find_packages it can't satisfy. It adds
src/ifcviewer EXCLUDE_FROM_ALL and consumes only IfcViewerCore.
src/ifcviewer/CMakeLists.txt now gates the wgpu-native fetch + the
Qt-using IfcViewer target + install commands behind NOT EMSCRIPTEN.
The wgpu_native link target still resolves under Emscripten as an
INTERFACE library that activates --use-port=emdawnwebgpu (Dawn's
webgpu.h, replaces the legacy -sUSE_WEBGPU=1).
Build:
source path/to/emsdk_env.sh
emcmake cmake -S src/ifcviewer-web -B build-web -G Ninja
ninja -C build-web
python3 -m http.server --directory build-web 8080
# open http://localhost:8080/IfcViewerWeb.html in a WebGPU-capable
# browser (Chrome 113+, Edge 113+).
Phase B step 3 of #45.
|
||
|
|
25651a1507 |
Fix demo preset crash + scope header refresh
bpy.ops.bim.new_project(preset='demo') crashed in refresh_bim_tool_headers: the post-commit hook fired for every nested bpy.ops.bim.append_library_element during template loading, and the operator context Blender hands to programmatically-invoked nested operators is stripped of the view-layer attributes the refresh reads. Two changes resolve it. Gate the header refresh in tool.Parametric.refresh_post_commit on operator.bl_idname being one of the EDIT_TYPES finish_op idnames. Only validate-gizmo commits (bim.finish_editing_<name>) now trigger the refresh; demo-loader and other non-edit operators skip it. Querying the registry directly is the canonical signal — string-prefix matching would silently drift if ParametricObject.finish_op changes derivation. Harden tool.Blender.get_active_object so its view_layer fallback also uses getattr; the 150+ callers routed through it now tolerate stripped contexts. _resolve_bim_tool_context applies the same defensive pattern to mode / workspace. Tests: - test_handler_restricted_context covers get_active_object's defensive path and the BimTool-family whitelist (excludes annotation, spatial, structural). - test_handler_forward_compat AST-pins that the gate consults EDIT_TYPES (not a string prefix). - test_wall_header_refresh rewritten — three tests cover the gated-by-registry contract: counter bumps for every commit, finish_op operators refresh headers, others don't. Hotkey-driven in-place edits (S_E / C_E) no longer trigger the refresh — they were caught by the pre-refactor "every commit" design. Left out of scope; the new skip-non-finish test pins this as intentional. Generated with the assistance of an AI coding tool. |
||
|
|
c098146c35 |
Remove Autodesk viewer examples
Remove the bonsaiviewer-autodesk Cargo examples that were used for local UI and dialog experiments. Generated with the assistance of an AI coding tool. |
||
|
|
fe6a0452bf |
ifcviewer: split out IfcViewerCore static library
Pull the Qt-free / OpenCASCADE-free files out of the IfcViewer target into a new IfcViewerCore static lib: BufferPool, ChunkPlanner, InstanceCompose, SidecarCache, StreamingLoader, StreamingThread, LodBuilder, plus the header-only InstancedGeometry / ModelGpuData / VertexQuantization / Selection / Visibility headers. IfcViewer PUBLIC- links IfcViewerCore so existing consumers see no change. This is the boundary the Emscripten web target will link against — keeps Qt, IfcGeom, OpenCASCADE, CGAL, and Boost out of the wasm build. Explicit file list, not glob, because the boundary is the whole point. |
||
|
|
cb19f22ee4 |
BufferPool: drop Qt log dependency
Replace qInfo() growth-event logging with fprintf(stderr,...) so BufferPool.cpp has no Qt touchpoints. Lets the test target drop its Qt6::Core link too. Prerequisite for the IfcViewerCore library boundary the web target will link against. |
||
|
|
1fe4570860 |
ifcviewer: extract ChunkPlanner + InstanceCompose; add Tier-1 test trio
The chunk planner (Morton sort + greedy pack) and instance composition (federation × placement matrix chain + world-AABB derive) were inline helpers in ViewportWindow.cpp. Pulled both out as free-function modules so the math + lookup logic can be exercised without a Qt window or a wgpu device. ViewportWindow now delegates; InstanceLookup is a using- alias to InstanceCompose::InstanceLookup. Also added an addSubBufferForTesting / clearSubPoolsForTesting seam to BufferPool so the sub-allocator invariants can be pinned with fake WGPUBuffer handles. The fakes are never dereferenced; the guard drops the sub-pools before destructor would call wgpuBufferRelease. Three new test binaries under src/ifcviewer/tests/, 33 cases / 173 assertions: BufferPool first-fit + alignment + coalescing + multi- sub-pool isolation; ChunkPlanner Morton split / interleave / stable sort / greedy-pack monotonicity and single-mesh-oversize; InstanceCompose identity / translation / order-of-multiplication / large-placement cancellation against federation false origin / column-major writeback / findInstance lookup paths. |
||
|
|
94faaa3160 |
Drop dead Geometry.has_material_styles + sanitation sweep
Two related cleanups bundled because each was too small on its own. == Drop dead Geometry.has_material_styles duplicate == Two parallel has_material_styles implementations existed on HEAD: * Geometry.has_material_styles (tool/geometry.py:853, added by |
||
|
|
749476d1a7 |
docs: rewrite stale GL-era docs (env-vars + viewport_architecture)
Two long-stale docs that described the deleted OpenGL backend are replaced with current-state rewrites under `src/bonsaiviewer/docs/` and wired into the toctree. The originals are removed. ## env-vars.rst (replaces src/ifcviewer/settings.rst) The orphan `src/ifcviewer/settings.rst` was written for the OpenGL backend (`IFC_*` prefix, MDI-specific knobs) and was never wired into any Sphinx toctree — it sat as a one-off file in the C++ source tree, undiscoverable from a normal docs build. * **Dead — dropped entirely.** `IFC_SKIP_MDI`, `IFC_MAX_SUBDRAWS`, `IFC_SUBDRAW_DIAG` were GL-only `glMultiDrawElementsIndirect` instrumentation. wgpu has no MDI. `IFC_FPS_HITCH_MS` no longer exists in source. * **Renamed.** `IFC_HIZ_MOTION` → `WGPU_HIZ_MOTION`, `IFC_CULL_THREADS` → `WGPU_CULL_THREADS`. * **New, previously undocumented.** Ten `WGPU_*` vars added during the port + bring-up (WGPU_HIZ, WGPU_HIZ_TRACE, WGPU_MIN_PX, WGPU_MIN_PX_MOTION, WGPU_FLY_DEBUG, WGPU_NAV_PRESET, WGPU_PRESENT_MODE, WGPU_STREAM_DEBUG, WGPU_STREAM_DEEP_DEBUG, WGPU_STREAM_EVICT_LOG). Descriptions written from each variable's use-site so wording matches actual behaviour. * **LOD-build section kept verbatim.** IFC_LOD_ERROR, IFC_LOD_RATIO, IFC_LOD_MIN_SAVINGS, IFC_LOD_DEBUG — sidecar-bake knobs, backend-agnostic. * **GUI-promoted "old IFC_* graveyard" section dropped.** The file is an env-var reference, not a record of historical spellings. ## viewport_architecture.rst (replaces src/ifcviewer/README.md) The 994-line `src/ifcviewer/README.md` was an archive of the GL-era phase-by-phase perf narrative. ~95% of it described deleted code: OpenGL 4.5 Core, `glMultiDrawElementsIndirect`, VAO/VBO/EBO, `GL_ARB_shader_draw_parameters`, BVH-per-model, sidecar v5/v7/v9 (current is v13), the now-non-existent `./IfcViewer` binary, Phase 3F "static batching next" plans superseded by the chunk-pool architecture, Phase 3E "GPU compute culling removed" since re-added as task #17 pending. Salvaging the ~50 lines of still-correct content would have left a Frankenstein doc internally contradicting itself. Replaced with a focused architecture page covering current reality: consumer split (BonsaiViewer shell vs IfcViewerMinimal standalone), stack (wgpu-native v29, Qt6, IfcOpenShell, IfcUtil, Eigen3, meshoptimizer), five core ideas (unique-mesh instancing, quantized 12 B vertex, chunked streaming on a probed VRAM pool, sidecar v13 fast path, event-driven rendering), per-frame pipeline (cull → upload → streaming → opaque pass → transparent pass → edge → overlay → present), federation + false-origin compose, file map limited to files that actually exist in `src/ifcviewer/` today, build/run via `build_viewer.sh`, cross-refs to env-vars.rst, debug-output.rst, and connectors/. ## Toctree `src/bonsaiviewer/docs/index.rst` gains `env-vars` and `viewport_architecture` entries alongside the existing `connectors/index` and `debug-output`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ce7d2fa329 |
docs: split Autodesk connector docs into user + developer pages
`autodesk.rst` had grown to mix end-user concerns (where do my tokens live, how do I install the bundle, why isn't sign-in working) with developer concerns (cargo build, fmt/clippy/test, packaging script flow, per-OS toolchain notes, CI). Reorganise into: * **`autodesk.rst`** — Autodesk Connector. User-facing. Bonsai-Viewer- level intro (Forma/APS/Docs, "Add from cloud"); install-from-zip per OS; first-run setup (client ID, OAuth port, browser redirect); where settings / cache / OAuth tokens live; proxy / TLS guidance for corporate installs. * **`autodesk_development.rst`** — Autodesk Connector Development. Developer-facing. Tech stack (FLTK, ureq, keyring, dirs, serde, chrono, webbrowser); `cargo build --release`; `cargo test --all-features` / clippy / fmt-check; protocol probing via stdio pipe; packaging via `packaging/build.py`; per-OS build / keychain / codesign notes; CI workflow overview. Absorbs the entirety of the old `autodesk_packaging.rst`, which is removed. `connectors/index.rst` toctree updated: `autodesk_packaging` → `autodesk_development`. `cloud_sync_protocol.rst` untouched — language-agnostic protocol spec. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
9d9f4054f6 |
bonsaiviewer-autodesk: replace Python connector with the Rust impl
The Python implementation of the Autodesk Forma connector
(bonsaiviewer_autodesk/) is deprecated. The Rust port that's been
maturing under src/bonsaiviewer-autodesk-rs/ is now the connector
and takes over the original folder name.
## File operations
* `git rm -r src/bonsaiviewer-autodesk` — drop the 18 tracked Python
source/test/packaging files. (~6.5k untracked build artefacts in
venv/build/dist/egg-info are removed too, but those were never in
the index.)
* `mv src/bonsaiviewer-autodesk-rs src/bonsaiviewer-autodesk` —
the Rust impl takes over the canonical folder name.
* `rm -rf src/bonsaiviewer-autodesk-rs-egui` — abandoned egui-based
experiment, never committed.
* `src/bonsaiviewer-autodesk/.gitignore` extended with `/dist` to
keep packaging output out of the index alongside the existing
`/target` rule.
The Rust binary in Cargo.toml already has `name = "bonsaiviewer-
autodesk"` and `connector.json`'s `exec` field already points at that
name — so the connector loader, build_viewer.sh symlink, and
win/build-all-win.py CONNECTOR_DIR all keep working without edits.
## Packaging shape preserved
`packaging/build.py` is rewritten to:
* shell out to `cargo build --release` instead of pyinstaller,
* copy the produced binary + connector.json into the same
`dist/autodesk/` layout the PyInstaller flow produced,
* zip into `dist/autodesk-<os>-<arch>.zip` with the same
naming pattern (CI artifact uploads keep working).
The Rust binary statically links its deps, so unlike PyInstaller
there's no `_internal/` directory — single executable inside
`dist/autodesk/`. Everything downstream (`build_viewer.sh` symlink,
`win/build-all-win.py collect_connector_files`, the zip step in
`build_rocky.yml`) only cares that `dist/autodesk/` exists, so the
on-disk contract is preserved.
Verified locally: `python3 src/bonsaiviewer-autodesk/packaging/build.py`
produces `dist/autodesk/{bonsaiviewer-autodesk, connector.json}`
(3.9 MB stripped ELF) and `dist/autodesk-linux-x86_64.zip` (~1.5 MB
compressed).
## CI updates
* `.github/workflows/build_rocky.yml` and `build_rocky_arm.yml`:
drop the `pip install ".[build]"` step — `packaging/build.py` is
stdlib-only now, the cargo build wrapped inside it does the work.
* `.github/workflows/build_win.yml`: same — drop pip install,
packaging script handles cargo internally.
* `.github/workflows/build-bonsaiviewer-autodesk.yml`: full rewrite
of the dedicated connector test/build workflow. Replaces the
Python {3.11, 3.13} test matrix with `cargo fmt --check`,
`cargo clippy --all-targets -- -D warnings`, and `cargo test
--all-features`. The OS/arch build matrix is unchanged
(linux-x86_64, macos-arm64, macos-x86_64, windows-x86_64) but
installs a Rust toolchain via dtolnay/rust-toolchain@stable and
caches target/ via Swatinem/rust-cache.
`win/build-all-win.py` and `build_viewer.sh` are unchanged — they
only reference the `dist/autodesk/` path, which the new
`packaging/build.py` populates identically.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
b3fbcd6a66 |
refactor: extract src/ifcutil/ from src/ifcviewer/ (Unit, Geolocation, Placement)
Unit / Geolocation / Placement are schema-agnostic IFC helpers ported
from ifcopenshell.util.{unit,geolocation,placement}. Nothing about
them is viewer-specific: pure IfcParse + Eigen, no Qt, no IfcGeom, no
renderer. Living under src/ifcviewer/ implies an unwanted dependency
direction every time a non-viewer caller (test_federation, the bonsai
SettingsView georef readout, a future standalone IFC tool) wants to
use them.
Move them to a new `src/ifcutil/` static lib (IfcUtil). The lib has
PUBLIC `target_include_directories(${CMAKE_CURRENT_SOURCE_DIR})` so
callers that link IfcUtil can keep `#include "Unit.h"` etc. without
relative-path adjustments — the include dir propagates transitively
via IfcViewer's PUBLIC link.
## Changes
* `git mv src/ifcviewer/{Geolocation,Placement,Unit}.{h,cpp}
→ src/ifcutil/` (history follows the rename).
* `src/ifcutil/CMakeLists.txt`: IfcUtil static lib, PUBLIC links
IfcParse + Eigen3::Eigen, PUBLIC include dir.
* `cmake/CMakeLists.txt`: `add_subdirectory(../src/ifcutil ifcutil)`
before ifcviewer/ so the link target exists when IfcViewer's
CMakeLists runs.
* `src/ifcviewer/CMakeLists.txt`: IfcUtil added to IfcViewer's PUBLIC
link_libraries.
* `src/ifcviewer/tests/CMakeLists.txt`: test_federation drops the
explicit `${IFCVIEWER_SRC}/{Unit,Geolocation,Placement}.cpp`
source list and links `IfcUtil` instead (matches how production
code resolves the symbols).
* `src/bonsaiviewer/modules/models/SettingsView.cpp`: the two
explicit `#include "../../../ifcviewer/{Geolocation,Unit}.h"`
paths swap to `../../../ifcutil/…`. All other callers use bare
`#include "Unit.h"` style and continue to work via the propagated
include dir.
## Verification
* `ninja -C build-viewer` builds clean: IfcUtil + IfcViewer +
IfcViewerMinimal + BonsaiViewer + all four pre-existing
ifcviewer tests + the two from-wgpu tests.
* `test_federation` runs green: 226 assertions in 22 test cases
pass with IfcUtil linked instead of the explicit-source compile.
* `git log --follow` traces e.g. `Geolocation.cpp` back through the
rename to its prior location in src/ifcviewer/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
32d9fd6c1c |
build-all: restore full PYTHON_VERSIONS list
|
||
|
|
b123ee69d6 |
viewer: two-pass alpha transparency + Alt+X global x-ray cap
## The bug
FZK-Haus windows rendered fully opaque despite every piece of the
data path carrying alpha correctly: vertex format is RGBA u8x4,
InstanceCpu/InstanceGpu carry color_override_rgba8 with its alpha
byte, fs_main returns vec4(rgb, in.color.a). Cause: the main render
pipeline's color target had `blend = nullptr`, which in wgpu disables
the blend stage entirely — fragment RGBA overwrites the back buffer
unmodified, alpha discarded.
## Why "just enable blend" isn't enough
Two failure modes that don't go away with a one-liner:
1. `depthWriteEnabled = True` on the main pipeline would make a
transparent window-frame pane occlude geometry behind it in
depth, so the wall behind the window then fails the depth test
and never draws — you'd see the silhouette of the window with
whatever colour was in the back buffer before, not the wall.
2. Order-dependent blending across transparent surfaces in arbitrary
cull order — overlapping transparent surfaces would shift colours
as the camera moves.
Standard fix for a BIM viewer is two-pass opaque-then-transparent.
## What this commit adds
### Per-mesh "has any alpha < 255" classifier
* `ModelGpuData::mesh_has_alpha` (uint8_t vector, parallel to meshes).
* Sized in `applyCachedModel`.
* Populated in `applyStreamedChunk` by scanning each in-chunk mesh's
vertex bytes for a vertex's alpha byte < 255 (offset 11 within
the 12-byte vertex record — the 4th byte of the third u32, which
the shader reads as `w2 >> 24`). Single chunk-arrival site covers
both sidecar streaming and the worker-result drain. First-load
IFC-without-sidecar geometry still routes opaque until the sidecar
bake completes; A-path scan is deferred.
### Per-chunk opaque/transparent partition during cull
* `Chunk::opaque_visible_vertices` / `opaque_visible_draws`
(per-frame counts).
* Transient `visible_draws_scratch_transparent` +
`transparent_per_draw_vertex_counts` filled alongside the existing
opaque half during the cull walk. Post-walk concat appends
transparent entries onto the opaque half and continues the
cumulative prefix-sum sequence — single buffer, single bind
group, no doubling.
* Classifier inside the cull lambda:
`xray_active ? always_transparent
: override_active ? (override.alpha < 255)
: mesh_has_alpha[mesh_id]`
### Per-chunk uniform layout extension
From `[total_draws, total_verts, 0, 0]` to
`[total_draws, total_verts, opaque_verts, opaque_draws]`. The third
slot is what `render()` passes as `firstVertex` to the transparent-
pass draw call so the shader's vid lands in the transparent range of
the same visible_draws_scratch buffer.
### `main_pipeline_transparent_`
Copy of `main_pipeline_` with `color_target.blend = SrcAlpha /
OneMinusSrcAlpha`. depthWriteEnabled stays True (see below).
### Two-pass `render()`
Opaque pass (`main_pipeline_`, firstVertex=0,
vertexCount=opaque_visible_vertices) then transparent pass
(`main_pipeline_transparent_`, firstVertex=opaque_visible_vertices,
vertexCount=total - opaque). Each loop skips empty halves so an
opaque-only chunk costs one draw call, transparent-only one draw,
mixed chunks two.
### depth_transparent.depthWriteEnabled = True (NOT off)
Initially set False (standard "let further-back geometry paint
through transparent front faces" trick) but that broke the edge-
detect pass: edge detection reads the depth buffer to find
silhouette discontinuities, and windows-without-depth meant the
glass had no silhouette at all (panes looked like framed holes) and
the edges of opaque geometry behind the glass painted through at
full intensity. Keeping the write avoids that — trade-off is depth-
test occlusion between transparent surfaces (closer occludes
farther), which for BIM panes that don't overlap in screen space
is invisible. Real fix for the overlap case is OIT or sort-back-
to-front, not depth-write toggling.
## Alt+X global X-ray (drops in basically free)
* `xray_alpha_cap` field on FrameUniforms + WGSL counterpart, default
1.0 (no effect). fs_main clamps `out.a = min(in.color.a, cap)`.
* `ViewportWindow::xray_alpha_cap_` member, default 1.0. Alt+X
toggles between 1.0 and 0.3.
* Cull classifier sees `xray_alpha_cap_ < 1.0` and forces every
instance into the transparent pass so the blend stage actually
fires (an opaque-pass fragment with capped alpha would still
overwrite the back buffer).
* No per-instance state mutation needed — toggle is a single float
in a uniform plus a re-cull. Excluding objects from x-ray later
would mean tagging them so the classifier skips the force-
transparent branch for them, also small.
Stress-tested on FZK-Haus: window glass visibly translucent with
correct silhouette edges; Alt+X turns the whole scene to a tinted
ghost of itself and back without artefact.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
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
|
||
|
|
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> |
||
|
|
7f87408b78 |
bonsaiviewer: rework add-models false-origin guess + drop fly-mode input lag
Two independent threads that landed on this branch.
## 1. Federation false-origin guess: arm-on-add + frame-on-origin
The
|
||
|
|
0e922074b9 |
Adopt _CommitWallDraftsFirstMixin on 7 wall operators
The 7 multi-wall operators (UnjoinWalls, UnjoinWallPathConnection,
ExtendWallsToUnderside, ExtendWallsToWall, SplitWall, MergeWall,
JoinWallsIntersection) each opened their _execute with an identical
prologue:
_commit_pending_wall_edits_for_selection(context)
# ... operator-specific logic
— flushing any in-progress wall parametric drafts so the operator
acts on committed IFC state rather than the draft preview box.
Extract that prologue into _CommitWallDraftsFirstMixin: its _execute
calls the commit helper, then delegates to a subclass-supplied
_perform. Subclasses inherit the mixin first in their bases tuple so
the mixin's _execute resolves first via the MRO. The IFC transaction
opened by tool.Ifc.Operator.execute still wraps both the commit and
the perform.
Behaviour-equivalent — same call, same order, same selection scope.
Architectural cleanup only: a future multi-wall operator can no
longer forget the commit step. The named helper
_commit_pending_wall_edits_for_selection stays as the single
encapsulation of the names=("wall",) filter; its docstring loses
the stale "every multi-wall operator calls it at the top of
_execute" sentence and now just describes the filter contract.
Matches gizmos-8088's _CommitWallDraftsFirstMixin pattern.
Generated with the assistance of an AI coding tool.
|
||
|
|
90ea256cc3 |
Shift-click add-opening preserves filling placement
The regular bim.add_opening click on the host-add-opening gizmo (wall + door/window co-selected) routes through FilledOpeningGenerator.generate, which snaps the filling to the wall's reference-line axis, optionally rotates 180° when the filling sits on the opposite side, and re-applies an rl1 / rl2 Z-elevation default. That is the right default for "drag a fresh door onto a wall and let the model place it for me", but defeats the workflow where the user has already positioned the filling precisely (e.g. snapped to a window in an adjacent wall, copy- pasted at an exact Z, aligned to a reference object). Holding SHIFT while clicking the gizmo now opts into a "preserve placement" mode: the filling stays at its current matrix_world and the opening is created at the filling's existing position. The opening / filling rels and representation work are unchanged — only the snap-to-axis branch is skipped, so the IFC graph is identical to the regular click; only the spatial position of the filling differs (user-chosen vs auto-snapped). Implementation: * bim/module/void/operator.py: AddOpening gains a hidden preserve_placement BoolProperty + an invoke() that sets it from event.shift. The call into FilledOpeningGenerator.generate forwards the flag. bl_description documents the SHIFT modifier so it surfaces in F3 search / hover tooltip. * bim/module/model/opening.py: FilledOpeningGenerator.generate accepts preserve_placement (default False — backwards-compatible with the other caller, tool.Model.add_filled_opening). The voided_obj.data-gated snap block (raycast + axis projection + rl-Z default + filling_obj.matrix_world write) skips entirely when the flag is True. The opening's matrix_world reads from filling_obj.matrix_world below the gate, so the opening lands at the filling's preserved position automatically. Generated with the assistance of an AI coding tool. |
||
|
|
387bd51b4a |
Use menu pick gizmo for door / window / stair type
The door / window / stair edit-row's type-cycle icon advanced one type per click (CycleDoorType / CycleWindowType / CycleStairType bound to cycle_type_operator). DoorType has 8 IFC variants, WindowType 9, StairType 3 — so cycling past the target was the norm. Threshold rule for cycle-vs-menu: cycle is appropriate for exactly 2 values (advance-one-per-click stays predictable). Three or more values warrants a popup menu. Door / window / stair all qualify; roof (RoofGenerationMethod has 2 values) keeps cycle. Wall has no type cycle. Array is unaffected. Swap to the popup-menu pattern (PickTypeMixin already on HEAD at bim/parametric_lifecycle.py:442): clicking the icon opens a menu listing all type_literal values; selecting one applies it in a single undo step. The hamburger icon (VIEW3D_GT_menu) is wired into BaseParametricGizmoGroup.setup_editing_gizmos whenever pick_type_operator is set (mutually exclusive with cycle_type_operator). Matches gizmos-8088's pattern exactly. Per-feature shape: * door.py: PickDoorType replaces CycleDoorType. GizmoDoorEdition.cycle_type_operator → pick_type_operator. * window.py: PickWindowType replaces CycleWindowType. Same swap. * stair.py: PickStairType replaces CycleStairType (no tool.Ifc.Operator inheritance — stair-type changes BIMStairProperties only, no IFC mutation). Same swap. * bim/module/model/__init__.py: registration entries renamed Cycle* → Pick*. * bim/module/drawing/gizmos.py: drop the CycleTypeMixin / PickTypeMixin / TypeAccessorBase shim re-export — its own docstring already noted "PR5 cleanup drops these" and the three callers (door / window / stair Cycle*Type) it served are gone. Roof's CycleTypeMixin import was already direct from bim.parametric_lifecycle. Also update GizmoMenu docstring to reflect the 2-vs-3+ threshold. Generated with the assistance of an AI coding tool. |
||
|
|
de4c394b50 |
Add host-wall offset gizmos for door/window edit
When entering parametric edit on a door or window that fills a wall opening, four dimension gizmos now measure the distances from the wall edges to the filling's jambs and from the wall's base/top to the sill/header. Dragging any gizmo translates the filling along the wall's local axis; 180°-flipped fillings and slanted LAYER2 walls round-trip correctly. The has_host_wall predicate hides all four when the filling → opening → wall chain cannot be resolved. Generated with the assistance of an AI coding tool. |
||
|
|
ab64b652ff |
Show wall cursor gizmos outside edit mode + axis previews
Four concerns that together make the cursor-anchored gizmos
(extend_x_gizmo, extend_z_gizmo, split_gizmo on GizmoWallEdition)
fully functional and visually informative without entering parametric
edit mode first:
* Drop the props.is_editing gate in _update_cursor_gizmos. The three
bound operators (bim.extend_wall_to_cursor,
bim.extend_wall_height_to_cursor, bim.split_wall_at_cursor) already
poll on wall-selected and commit any pending wall edit before
acting, so single-click without entering edit mode is now the
canonical flow. Matches gizmos-8088's always-on behaviour.
* Register GizmoWallEdition instances in a per-region weakref map
(_active_instances) populated at setup_element_specific_gizmos
time. The WallGizmoPreviewDecorator dereferences this map to read
live is_highlight state off the cursor icons. Without the
registration its _cursor_icon_hovered always returned False and
the hover-gated GPU previews silently never drew. Mirrors the
same pattern already in place on GizmoWallJoinIntersection.
* Add post-operator resync to all three cursor operators
(_maybe_resync_wall_props_from_ifc for the single-wall split /
extend-height paths, _resync_walls_after_mutation for the
selection-wide extend-X path). Without this, props.length /
props.height stayed stale after the operator ran, so the
orientation flips _apply_wall_extend_flips computes from
cursor_local vs wall dimensions kept using the pre-extend values
until the next selection change. Matches gizmos-8088's pattern.
* Hover-gated GPU previews per icon:
- extend-X: filled Z=0 floor quads spanning the wall's offset to
offset+thickness Y band, visible from plan view without side-
view clutter. Grow case (cursor beyond either endpoint): one
green decorator_color_selected quad over the extension. Shrink
case (cursor inside extent): green quad for the portion that
REMAINS + red decorator_color_error quad for the portion the
operator REMOVES.
- extend-Z: vertical lines at the cursor's projected X in the
wall's y=0 reference-line plane. Grow case (cursor above wall
top): one green segment from z=height to z=cursor.z. Shrink
case: green from z=0 to z=cursor.z (REMAINS) + red from
z=cursor.z to z=height (REMOVES).
- split: one red vertical line at the cursor's projected X from
base to wall top — the cut plane.
Quads use QUAD_ALPHA=0.25 so the underlying wall body stays
visible.
* New module-level _fill_quads_alpha helper next to
_stroke_lines_alpha, plus a per-decorator _fill convenience method
and a _wall_floor_quad corner builder.
Modal-active gizmo hiding (is_gizmo_hidden_by_modal) is preserved.
Generated with the assistance of an AI coding tool.
|
||
|
|
99bb1e30ad |
Generalise opening gizmos + DRY toolbar plumbing
Add openings — GizmoWallAddOpening only fired when a wall was active + co-selected with a non-host; slabs and roofs got no in-viewport handle. GizmoHostAddOpening covers all three host types via is_supported_host, dispatching walls to the axis-projection anchor and slabs/roofs to a world-Z anchor lifted just above the host's top face (predictable height regardless of the void's vertical position). Show openings on hosts with their own parametric-edit toolbar — GizmoRoofEdition gains an idle-row toggle_openings_gizmo parallel to the wall's, parked at the cancel-slot X next to the pen. Visible only when the host carries HasOpenings and the edit triad is idle. Roof overrides get_element_height to return the mesh's world-AABB top in object-local Z, so the WHOLE pen-row anchors visibly above sloped or stepped roof bodies. The wall's idle-row toggle now also hides when HasOpenings is empty. Show openings on hosts WITHOUT a parametric-edit toolbar — GizmoHostToggleOpenings scoped strictly to the fallback case: a single host selected, HasOpenings non-empty, NOT a path-connectable wall, NOT a parametric roof. Covers slabs today plus any foreign-authored IfcRoof without BBIM_Roof. Anchored at object origin XY + world-AABB top Z. When slab parametric-edit eventually lands, the slab predicate joins the exclusion list and this gizmo's poll narrows automatically. Operator move — ToggleWallOpenings was already host-agnostic; renamed to ToggleHostOpenings in opening.py (bl_idname bim.toggle_host_openings). Three callers (the wall idle-row binding, GizmoWallFilletToggleOpenings, and workspace.py's hotkey_A_O for Alt+O) now route through the renamed operator. The Alt+O binding is surfaced in the operator's bl_description so it appears in F3 search and hover tooltips. DRY refactors — * GizmoWallAddOpening deleted (subsumed by GizmoHostAddOpening) * tool.Blender.get_object_world_bounding_box added as the world-AABB sibling of the existing local helper; 3 inline call sites in tool/misc.py (set_object_origin_to_bottom, scale_object_to_height) and gizmos.py adopt it (2 other sites in drawing/operator.py and project/operator.py inherently need raw transformed corners for per-corner plane / NDC tests — not AABB candidates) * BaseParametricGizmoGroup gains setup_pen_row_toggle_openings_icon + update_pen_row_toggle_openings_icon; wall + roof + any future host gizmo wire up the idle-row toggle with two one-line calls * _resolve_active_host shared poll prologue between the two host gizmos (gate + selection count + active-in-selected + entity lookup + supported-host check) * HasOpenings non-empty checks at 3 sites route through tool.Geometry.has_openings * hotkey_A_O body collapsed to bpy.ops.bim.toggle_host_openings() The forward-compat AST guard pinning "must accept fillet-corner walls" retargets from GizmoWallAddOpening.poll to is_supported_host. Generated with the assistance of an AI coding tool. |
||
|
|
2b91e41fc4 |
bonsaiviewer: stage all IfcOpenShell dylibs (core + plug-ins) on macOS
The .app bundle's Frameworks/ staging rule was only globbing ifcopenshell.*.dylib (the dlopen-only plug-ins) on the assumption that macdeployqt would follow BonsaiViewer's link-time @rpath deps for the lib-prefixed core shared libs. In practice it doesn't — non-Qt @rpath deps whose source path is outside the standard system / Qt prefixes get skipped silently. In a static build this didn't matter: libifcopenshell.geometry, libIfcParse, libIfcViewer, etc. were statically embedded in BonsaiViewer.exe, so there was no runtime dep. With --shared (added in |
||
|
|
cc54237f51 |
viewport: move first-model false-origin guess out of refresh()
Loading a model whose first placement sits at the world origin
stack-overflowed BonsaiViewer instantly on Windows (and macOS).
WinDbg trace was a 5-frame Qt signal-slot cycle hitting the guard
page ~1400 levels deep; Linux escaped only because that machine's
iterator order put a non-origin instance first, which made the guess
return a non-default value and naturally terminated the recursion
after one step.
Root cause is the architecture, not the specific guard inside the
guess function. `ViewportView::refresh()` was connected to six
SessionState signals (projectReset, projectOpened, modelsChanged,
federationChanged, visibilityChanged, modelGeometryReady) and was
calling `maybeGuessFederatedFalseOrigin` on every model on every
fire. That helper called `session_state_->notifyFederationChanged()`
unconditionally after the mutation, which re-emitted
SessionState::federationChanged, which re-entered refresh(), which
re-entered the guess — a hidden emit-in-slot loop. The "current ==
defaults" guard at the top of the guess prevented further mutations
once the value moved off defaults, but on machines where the guess
itself returned defaults the guard never fired and the loop ran
forever.
Cleanup:
* refresh() is now terminal: it reads federation state, pushes it to
the viewport, and returns. No mutations, no signal emissions.
maybeGuessFederatedFalseOrigin is removed from its for-loop.
* The guess is renamed to `tryGuessFirstModelFalseOrigin(uint32_t)`
and is now invoked only from the modelGeometryReady connection,
not from refresh(). Conditions:
1. modelIds().size() == 1 (the just-loaded model is the only
model — i.e. this is the "first model added" edge)
2. federation->federatedFalseOrigin() == defaults (nobody has
set the origin yet — possibly because the previous attempt
guessed defaults and no-op'd, in which case we deliberately
want to retry next time a model lands)
No one-shot flag: add→remove→add cycles re-attempt the guess
precisely while the origin is still default, which is the right
semantics.
* SessionState now relays Federation::federatedFalseOriginChanged
onto its own bus via notifyFederationChanged. This replaces the
manual `session_state_->notifyFederationChanged()` call the old
guess made post-mutation. With the relay in place, any future
mutation site (commands, settings dialog, project load) will
propagate to views automatically — the emit point lives at the
data change, not at every caller. Views still subscribe to
SessionState only; Federation stays a back-end detail.
Reproduced with ISSUE_053_20181220Holter_Tower_10.ifcview on
Windows (build
|
||
|
|
ddee88bed3 |
build_osx: --shared + skip geometry-writer plug-ins (~3x bundle shrink)
Mirrors the Rocky workflow's two-part size reduction (27249770e "Reduce Rocky package size") on macOS: 1. Pass `--shared` to nix/build-all.py. The default builds IfcOpenShell as static libs, which means every plug-in dylib (schemas × 8, kernels × 3, mappings × 8, writers × 8, document serializers × ~4, linework processing) statically embeds a full copy of libIfcParse + libIfcGeom. With --shared the plug-ins reference @rpath/libIfcParse.dylib + @rpath/libIfcGeom.dylib and the per-plug-in dylib drops from ~30-50 MB to a few MB each. Dominant size win. 2. Filter `ifcopenshell.geometry.writer.*.dylib` out of BonsaiViewer's plug-in staging step in src/bonsaiviewer/CMakeLists.txt. These are the per-schema OBJ / glTF / DAE / STP / IGS / SVG / TTL export converters — heavy because each one inlines the full schema, and BonsaiViewer is a viewer, never an exporter, so they're pure deadweight inside the bundle. Additive on top of --shared. Bundle went 300 MB → expected ~100 MB, in line with Linux (~100 MB) and Windows (~80 MB). The IFCOPENSHELL_BUILD_PYTHON_WRAPPER=off gate is unchanged for now — once we confirm BonsaiViewer.app size + functionality look sane, we can ungate the Python wrapper and see if shared-builds-on-macOS shake out its install issues too. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
1707c36bd8 |
Stack cursor-anchored wall gizmos along screen-up in top view
The extend-X / extend-Z / split icons share the cursor's projected X on the wall axis, separated only by world Z (floor / cursor / wall top). World Z collapses to a single screen point in plan view, so every icon piled onto extend-X's hit target and only the topmost was clickable. Two refinements ported from gizmos-8088: * When ``tool.Blender.is_view_top_down(context)`` reports the camera is near plan-view, swap world-Z stacking for screen-up stacking: anchor all icons at the floor world position and offset each by ``index * CURSOR_STACK_OFFSET`` along ``tool.Blender.get_screen_up_world(context)``. Each icon lands in its own screen-space slot regardless of view rotation. * In the same top-down branch, drop ``extend_z_gizmo`` entirely. A vertical-intent gizmo has no readable cue when looking down +Z — clicking it would mutate the wall in a direction the user can't see change. * Bonus: split's local Z now goes through ``core.extrusion_depth_from_vertical_height(props.height, props.x_angle)`` so the icon lands on the slanted top edge of sloped walls (x_angle != 0) instead of the vertical-height target the wall isn't at. All three helpers (``is_view_top_down``, ``get_screen_up_world``, ``extrusion_depth_from_vertical_height``) already on HEAD from PR2/PR3. Non-top views unchanged — same world-Z stacking + cascading bumps as before. Generated with the assistance of an AI coding tool. |
||
|
|
44f5ee028f |
Port WallGizmoPreviewDecorator from gizmos-8088
Hover-gated viewport preview lines that show where a wall-join / extend / split operator would land before the user clicks. Four preview paths, each gated on a specific icon's ``is_highlight`` state: * **Join intersection** — two LAYER2 walls selected in the ``intersect`` state (non-joined, non-collinear, non-parallel). Draws four lines: each wall's axis at both base and top Z, extending from the wall's nearer endpoint to the projected XY intersection. The pair of lines per wall communicates the full plane the join welds at, not just the floor edge. * **Cursor extend** — single LAYER2 wall, hover on ``extend_x_gizmo``. One line from the wall's nearer X endpoint to the cursor's projected X on the wall axis. * **Cursor extend-Z** — hover on ``extend_z_gizmo``. Vertical line at the cursor's projected X from wall base to cursor Z (the new total height). * **Cursor split** — hover on ``split_gizmo``. Vertical line at the cursor's projected X from wall base to wall top — the cut plane. Warning-red colour matches the icon's destructive-action signal. Hover colour rules for the join preview: * **Join or Fillet hover** → all four lines highlight in ``decorator_color_selected``. Both icons commit a symmetric corner meet, so every line is part of the operation. * **Extend-to-Wall hover** → only the non-active wall's two lines (base + top) highlight. The default-direction extend operator moves the non-active wall into the active one's axis; only that wall's preview should signal motion. * No hover → all four lines in ``decorations_colour``. Three coordinated changes: * ``bim/module/model/wall.py`` gains the ``_classify_wall_join_state`` wrapper over ``core.classify_wall_join_state`` (feeds the ``_are_walls_joined`` flag the core helper expects) AND a ``_active_instances`` per-region weakref ClassVar on ``GizmoWallJoinIntersection`` populated in ``setup()``. Without the weakref registration, the decorator's ``_lookup_active_instance`` call returns None every frame and the hover gates silently evaluate False — the symptom would be preview lines that never switch colour. Both pieces ported from gizmos-8088. * ``bim/module/model/decorator.py`` gains ``WallGizmoPreviewDecorator`` (~280 LOC across the four preview paths + shared helpers ``_stroke`` / ``_active_layer2_wall_for_gizmo_preview`` / ``_join_group_hover_state`` / ``_extended_wall_index``). All cross-file dependencies (``core.classify_wall_join_state``, ``core.wall_join_preview_lines``, ``_stroke_lines_alpha``, ``_cursor_icon_hovered``, ``_lookup_active_instance``, ``tool.Parametric.is_path_connectable_wall``, ``_wall_axis_world_segment_from_geom``) already on HEAD. * ``bim/handler.py`` wires ``WallGizmoPreviewDecorator.install()`` / ``.uninstall()`` alongside the other always-on preview decorators. The decorator self-polls every frame; cost is one selection-count check + one ``is_highlight`` read when no eligible state is active. Verified: headless smoke green, ruff + black clean. Live testing confirms the four preview paths fire correctly when hovering each icon. Generated with the assistance of an AI coding tool. |
||
|
|
ed7b2fc233 |
Stack wall-join trio along screen-up + L/T glyphs
GizmoWallJoinIntersection used to place its icons at state-specific world points: join at floor Z, extend-to-wall at the active wall's top Z, fillet stacked screen-up above join. Same XY at different Z collapses to a single screen pixel in plan / top view, so two icons became one hit target — invisible from above. * position_gizmos now always-stacks along screen-up at a wall-top anchor in both the joined (unjoin + fillet) and the intersecting (extend + join + fillet) states. Order bottom-up is extend / L / fillet. Collinear-merge keeps its single boundary icon (no stack needed). * New _stack_anchor_z picks the active wall's top Z (or the taller of the two on mid-selection-transition frames). New _stack_at lays a tuple of icons along screen-up at the resolved anchor. * Glyph swap: join_icon -> VIEW3D_GT_wall_corner (L), extend_to_wall_icon -> VIEW3D_GT_wall_tee (T). Both classes already existed in bim/module/drawing/gizmos.py from an earlier commit; only the setup() bl_idname strings changed. The previous arrow-merge / arrow-extend pair read as the same direction once stacked. Forward-compat AST contracts in test_wall_gizmos_forward_compat.py pin the new invariants: the L and T bl_idnames must appear in setup(), and position_gizmos must route through _stack_at so a regression that reintroduces a direct billboarded_at write for any state-specific icon fails CI before it flattens the stack again. Also folds in a one-line typo fix in core/spatial.py: assign_container's per-element can_contain check iterated `e` but predicate-tested `root_element` (the outer for-loop variable), so every element in the comprehension was tested against the same container/element pair. Switch the argument to `e`. Generated with the assistance of an AI coding tool. |
||
|
|
2c18155d98 |
Merge ifcopenshell/v0.8.0 into parametric-framework-pt2
Bring in 13 commits from upstream v0.8.0 (tip
|