Commit Graph

22381 Commits

Author SHA1 Message Date
Dion Moult 13ec2be807 ifcviewer-web: streaming loading bar (metadata → geometry progress)
Users had no feedback during the seconds before/while a model streams. Add a
top progress strip + caption driven by the streaming state:
- "Loading model… N MB" while the critical metadata downloads (no chunks yet),
- "Loading geometry — R / T chunks · N MB" as chunks go resident,
- "Loaded — T chunks · N MB", then it hides.

ViewportCore::streamingProgress(resident, total) sums chunk residency across
models; main_web exports ifcv_chunks_resident_c / ifcv_chunks_total_c for
shell.html to poll each RAF. The EM_JS range reader accumulates
Module.__ifcvBytesLoaded so the caption can show MB downloaded. Shown only for
streamed loads (?model= URL / picked file), not the embedded sample.

6/6 web smoke pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 08:54:42 +10:00
Dion Moult 41a85a70ba ifcviewer: v15 — defer property metadata off the first-paint path
First-paint over a network is metadata-bound: the whole post-index metadata
(~10 MB on a 118 MB model) had to download before any geometry. But ~25% of
it — elements + string_table, the IFC element tree (names/GUIDs/hierarchy) —
is used only for UI/picking, never for rendering (ViewportCore never touches
it).

v15 splits the post-index metadata into a render-CRITICAL block (meshes,
instances, georef, chunk TOC) preceded by its byte length, then a DEFERRED
block (elements + string_table). The web loader reads only the critical block
before painting; the deferred block sits at a known, self-describing offset
([critical end, EOF)) and is fetched on demand. Desktop reads both (local).

Web on-demand path is wired and complete (not yet called — no UI consumer):
loadDeferredMetadataWeb(model_id) range-fetches + parses the deferred block
into ModelGpuData.elements/string_table, at most once; the first consumer
will be "show the selected object's name" on pick. No background prefetch —
view-only sessions never download the property data (saves 2.64 MB on this
model).

parseSidecarTail split into parseSidecarCritical + parseSidecarDeferred (pure,
unit-tested); StreamingSidecar gains the critical-block locator. Measured
(118 MB model): critical metadata 10.35 -> 7.71 MB, deferred 2.64 MB off the
path; first paint 10.5 -> 9.6 s @ 24 Mbps. (Instances still dominate the
critical block — the next metadata lever.) Format -> v15, no back-compat;
regenerate sidecars. 111/111 unit + 6/6 web smoke pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 08:01:15 +10:00
Ryan Schultz febde1bbbb Closes #8226: Add bulk-load of selected drawings' annotations
SHIFT+CTRL+CLICK on Activate Drawing now imports the
annotations of all selected drawings without switching
the active view or camera. The drawing camera is imported
when missing so annotations are collected into the correct
drawing collection. Loading is idempotent.

Generated with the assistance of an AI coding tool.
bonsai-0.8.6-alpha2606302023
2026-06-30 15:23:23 -05:00
Ryan Schultz 315835063c Fix #8225: Respect camera boundary for Include filter
The drawing Include filter replaced the camera-view element set
entirely, so elements outside the camera boundary were drawn.
Intersect the filter results with the camera-view set instead.

Generated with the assistance of an AI coding tool.
bonsai-0.8.6-alpha2606301810
2026-06-30 13:10:12 -05:00
Gorgious56 1b0a2fd6de Merge pull request #8222 from Gorgious56/viewport-decorator-lifecycle-sweep
Bonsai: migrate viewport decorators onto canonical lifecycle helper
bonsai-0.8.6-alpha2606301345
2026-06-30 15:45:08 +02:00
Gorgious56 c95c1905ed Bonsai: draw georef gizmo above 3D model geometry
The georef orientation gizmo (crosshair, project-north arrow,
grid-north arrow, true-north arrow, WCS leader) is a coordinate-
system overlay: its purpose is to communicate orientation regardless
of what the model contains, so it must remain visible regardless of
whether 3D geometry occupies the gizmo's z=0 footprint.

Wrap GeoreferenceDecorator.draw_geometry's draw cycle in a
gpu.state.depth_test_set("ALWAYS") / restore pair so the overlay
draws on top of any 3D geometry between the camera and the gizmo.
Matches the precedent set by the dashed-line overlay in
bim/module/model/opening.py.

Generated with the assistance of an AI coding tool.
2026-06-30 15:36:03 +02:00
Gorgious56 301dae103c Bonsai: guard decorator draws against None and empty lists
Three crashes that surfaced when viewport decorators ran against
selected non-IFC blender objects or top-level objects with no
aggregate parent:

- WallAxisDecorator.draw_wall_axis: tool.Ifc.get_entity(obj) returns
  None for a non-IFC selection (default cube, lamp, camera). The
  subsequent element.is_a("IfcWall") raised AttributeError on every
  redraw. Guard with `element and element.is_a(...)`.
- _ConnectedNetworkPathDecorator flow-segment loop: same shape;
  iterates entries that may be None, calls .is_a("IfcFlowSegment")
  unconditionally. Same guard.
- AggregateDecorator.draw_aggregate: indexes aggregates_list[-1]
  unconditionally in the else branch; raises IndexError when the
  selected element has no aggregate parent. Also leaves `aggregate`
  unbound across loop iterations in the `in_aggregate_mode` branch
  when `index <= 0`. Define `aggregate = None` per loop iteration
  and guard the [-1] indexing with `elif aggregates_list:`.

Generated with the assistance of an AI coding tool.
2026-06-30 14:06:53 +02:00
Gorgious56 85cd1c1923 Bonsai: migrate viewport decorators onto canonical base
Migrate 17 legacy viewport decorators (ClashDecorator, SolarDecorator,
MeasureDecorator, ItemDecorator, GeoreferenceDecorator, NestDecorator,
NestModeDecorator, GridDecorator, LoadsDecorator, AggregateDecorator,
AggregateModeDecorator, PolylineDecorator, ProductDecorator,
WallAxisDecorator, SlabDirectionDecorator, FaceAreaDecorator,
BoundingBoxDecorator) from hand-rolled install/uninstall lifecycles
onto the canonical tool.Blender.ViewportDecorator base. The legacy
uninstall removed each handler from Blender but never cleared
cls.handlers, growing a stale-reference list across enable/disable
cycles. The base's uninstall clears the list correctly.

State-derived install methods (ItemDecorator, ProductDecorator,
LoadsDecorator, PolylineDecorator) keep an install override per the
base's documented contract.

Drop the now-redundant per-class draw_batch copies and the module-
or method-scope transparent_color defs in favour of the base helpers
introduced in the preceding commit. system/decorator.py and
boundary/decorator.py keep their installed-flag lifecycle (different
pattern, no leak) but consume tool.Blender.transparent_color.

Add an AST forward-compat guard pinning the contract structurally:
any class declaring handlers = [] (Assign or AnnAssign) must subclass
tool.Blender.ViewportDecorator. Add a runtime regression on
ClashDecorator's install/uninstall cycle.

Generated with the assistance of an AI coding tool.
2026-06-30 14:03:17 +02:00
Gorgious56 091fc9e7e5 Bonsai: add viewport decorator base helpers
Add two helpers to tool.Blender that 17+ existing viewport decorators
re-implement byte-identically:

- ViewportDecorator.draw_batch(shader_type, content_pos, color, indices=None)
  collapses the validate + batch_for_shader + uniform_float + draw cycle
  every shader-driven decorator needs.
- Blender.transparent_color(color, alpha=0.1) is the RGBA-alpha-override
  helper duplicated across nest, project, aggregate, model, system module
  scopes plus six nested-def copies inside draw methods.

Pure additions on the tool/ layer with direct unit tests covering the
default-alpha, explicit-alpha, non-mutation, new-list-instance, and
validation-guard branches.

Generated with the assistance of an AI coding tool.
2026-06-30 13:59:15 +02:00
Dion Moult e1be2f208c ifcviewer: v14 chunk-contiguous sidecar + progressive network streaming
Makes large-model streaming over a network actually good — fixing read
amplification, then first-paint latency — building on the byte-range work.

v14 layout + TOC (SidecarLayout, pure + unit-tested)
  The loader chunks meshes by spatial Morton order, but the sidecar stored
  geometry in mesh-id order, so a chunk's meshes were scattered through the
  file: streaming one chunk meant either hundreds of tiny range requests or
  reading (and discarding) everything between them — a 113 MB model fetched
  ~340 MB, a 531 MB model 2.25 GB (4.2x). Fix: at bake, reorder meshes into
  the loader's chunk order and rebuild vertex/index(LOD0+LOD1)/instance
  sections so each chunk is one CONTIGUOUS byte range, and bake a chunk TOC
  ({first_mesh, mesh_count}). The loader builds chunks straight from the TOC
  rather than re-deriving the plan — the float Morton quantisation isn't
  bit-identical across toolchains (x86 baker vs wasm loader), so a re-derived
  plan scatters the chunks. Format bumped to v14 (regenerate sidecars). The
  reorder buckets instances by per-instance mesh_id (the baker never sets
  MeshInfo.first_instance — trusting it scrambled every transform → geometry
  at the origin). Multiset-verified on a 28,900-instance model: every
  instance's placement + geometry preserved. Result: 531 MB fetches 531 MB
  (1.0x) in 72 requests (was 2036).

Progressive streaming (concurrency cap + small chunks)
  Even at 1x, geometry appeared only after ~the whole model arrived: the
  browser multiplexes every in-flight Range request over one HTTP/2 conn, so
  unbounded concurrency (9 in flight) split the bandwidth and nothing finished
  until the end (measured: first paint after 113 of 118 MB / 35 s @ 24 Mbps).
  Cap concurrent chunk loads (kMaxWebInflightChunks=2): the priority-sorted
  top chunks finish and paint first, then the next → first paint 9 s. Chunk
  size dropped 16->4 MB (cheap now that each chunk is one read; matches Cesium
  3D Tiles / xeokit / SVF2) for smoother progression. First-paint is now
  metadata-bound (~10 MB tail) — the next lever.

111/111 unit (new test_sidecar_layout: geometry preserved, contiguous layout,
Morton-identity) + 6/6 web smoke pass; desktop bake (SceneLoader) reorders
before writeSidecar; embedded web sample regenerated to v14.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:22:47 +10:00
Gorgious56 a65f291a89 Bonsai: gate clip-box refresh timer across file load
A pending RegionView3D.update() timer registered before wm.open_mainfile()
fires during the load against freshly-allocated regions whose GPU contexts
are not yet wired, CTD-ing inside GPU_matrix_ortho_set. Cancel both the
refresh and cap-rebuild timers in a new load_pre handler, hold a
_file_loading gate from load_pre through the first on_pre_view tick (first
paint = GPU ready), and short-circuit on_depsgraph_update during the
window so its IFC-reload schedule_refresh + apply_clip_planes_direct
branches can't re-arm against unready regions.

Generated with the assistance of an AI coding tool.
bonsai-0.8.6-alpha2606300823
2026-06-30 10:23:06 +02:00
Thomas Krijnen e6dc582d82 ExtractElements - copy over coordionate operation #8199 bonsai-0.8.6-alpha2606300812 2026-06-30 10:12:45 +02:00
Thomas Krijnen 366fa67a84 Don't rely on cwd 2026-06-30 10:02:55 +02:00
Petru Conduraru 4a8b863b96 test(ifcpatch): ExtractElements regression test for georeferencing loss (#8199)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 09:59:07 +02:00
Dion Moult 46a695266a ifcviewer-web: export HEAPU8 for heap-size diagnostics
Lets tooling read the wasm heap size (e.g. to verify a large sidecar
streams by byte range instead of loading whole, and to watch memory while
battle-testing big models over the network). Standard emscripten runtime
method, zero size cost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:26:12 +10:00
Dion Moult 9db42df81c ifcviewer-web: stop large-model network streaming thrash (grow before fetch)
Battle-testing real sidecars over HTTP Range exposed severe thrash: a
531 MB model re-fetched 2.25 GB (4×) and never converged — viewAll puts
the whole model in frustum, so every chunk wants to be resident, and the
web async path made it worse two ways:

  - A web load only consumes pool space when it COMPLETES (async), so the
    per-frame issuance over-committed the pool; completions then failed
    applyStreamedChunk on a full pool, the chunk re-candidated with no
    cooldown, and re-fetched every frame.
  - Pool growth is itself async on web (provisional sub-buffers validated
    off the JS event loop), so even fetched chunks failed to alloc until
    the pool caught up, and re-fetched.

Fix: gate web chunk issuance on VALIDATED free space + in-flight
reservation, and grow the pool BEFORE fetching:

  - streaming_web_inflight_bytes_ reserves each in-flight load's footprint
    so we never have more bytes in flight than the pool can place.
  - When a visible chunk doesn't fit validated free, don't fetch — call
    pool_.requestGrowth() (BufferPool: drives the async provisional grow
    without allocating) and short-back-off; the chunk is fetched once,
    after space exists. When the pool is saturated (model > GPU memory),
    long-cooldown so a never-fitting chunk isn't re-fetched. Gating before
    the evictor also kills phase-2 visible↔visible swap thrash.
  - On async load failure, cool down (short if the pool can still grow,
    long if saturated) instead of re-candidating next frame.

Result (manual battle tool, host.mjs + real files): 531 MB now loads
23/23 chunks, 322 MB loads 14/14 — resident climbs monotonically with
ZERO thrash warnings and a stable resident set, vs the old re-fetch loop.
The whole model resides on the GPU and stays. (Remaining ~3× ramp
over-fetch — per-chunk re-loads during the async-growth ramp + read
amplification from chunk byte-locality — is a separate efficiency
follow-up, not thrash.) 6/6 web smoke + 107/107 unit pass; desktop
unaffected (the gate is web-only; requestGrowth is a no-op wrapper there).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:26:12 +10:00
Dion Moult 2c5e2d1685 ifcviewer-web: fetch a chunk's vertex + index ranges concurrently
beginWebChunkLoad read the vertex ranges, then in the completion callback
read the index ranges, then applied — two serial round trips per chunk.
On a network that's the dominant per-chunk latency. Now both reads fire
at once and a small shared join (payloads + per-read done/ok flags) runs
the apply when the second lands, halving per-chunk RTT. Model re-lookup
still happens at apply time, so a resetScene mid-flight is dropped safely.

Per-chunk concurrency stacks with the existing across-chunk concurrency
(driveStreamingLoads issues several loads per frame); the browser caps
simultaneous connections per origin, so no explicit in-flight cap is
needed. 6/6 web smoke + 107/107 unit pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:50:37 +10:00
Dion Moult 23dc5dac48 ifcviewer-web: stream remote sidecars over HTTP Range (?model=URL)
Adds a network byte-source alongside the local Blob one. The async-chunk
infra is source-agnostic — only the two JS primitives knew it was a Blob —
so this generalises them and reuses everything else:

  - ifcvReadRangeInto: local → Blob.slice; remote → fetch() with a Range
    header (206). If a server ignores Range and returns 200, the requested
    window is sliced out so it still works (without the bandwidth saving).
  - ifcvFileSize: Blob size, or the URL's total length resolved up front.
  - ifcvBeginUrlSource: resolves total size (HEAD Content-Length, else a
    0-0 ranged GET's Content-Range) then fires _ifcv_source_ready.
  - The metadata bootstrap is extracted into a source-agnostic
    loadSidecarMetadataWeb(label); loadSidecarFromBlobWeb / FromUrlWeb are
    thin entries. streaming_from_blob → streaming_from_web (now covers both).

main_web exports load_sidecar_from_url_c(url); shell.html reads a
?model=URL query param and ccalls it once the app is live (same-origin
needs no CORS; cross-origin hosts must send CORS + Accept-Ranges).

Test: serve.mjs now answers HEAD + Range (206) and falls back to the
ifcviewer-web source dir for sample.ifcview (embedded in the wasm, not in
build-web). New smoke case loads ?model=/sample.ifcview and asserts it
renders via the Range path. 6/6 web smoke + 107/107 unit pass; desktop
unaffected (web-guarded; only the shared field rename touches it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:44:37 +10:00
Dion Moult bc31e91e35 ifcviewer-web: handle device loss so GPU pressure can't freeze the tab
Reported: open a model in the web viewer, then launch the desktop
BonsaiViewer and open another model — the browser tab freezes, and a
fresh web tab then fails with "RequestDevice failed: Not enough memory
left". Root cause is GPU-memory contention: two heavy GPU clients on one
GPU, and the desktop app's allocations starve the browser's WebGPU
process, which reclaims our device.

We can't conjure GPU memory, but we were amplifying the symptom: with no
device-lost handler, render() kept driving a dead device —
wgpuSurfaceGetCurrentTexture returns Lost every frame and the
reconfigure + requestFrame retry becomes a tight per-RAF loop that hangs
the tab. Now the web device descriptor wires a device-lost callback that
latches device_lost_ (ignoring the intentional Destroyed reason from our
own shutdown); render() bails while set, so the loop goes idle instead of
spinning, and the console logs guidance to reload. The fresh-tab
RequestDevice OOM is genuine GPU exhaustion — surfaced as before, now with
a clearer message.

Verified the lost callback doesn't disturb Dawn-web's RequestDevice (all
5 web smoke tests still init + pass); desktop unaffected (device_lost_
stays false). The real contention path can't be reproduced in the headless
harness, so the loss handler itself is covered by review, not a test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:24:56 +10:00
Dion Moult a6cfb2651a ifcviewer-web: render through an sRGB surface view (fix dark colors)
The fragment shader pre-decodes sRGB→linear to cancel the surface's
automatic linear→sRGB write encoding, so the final bytes match the GL
backend. That only holds when the render target is an sRGB format. On
desktop the surface's preferred format already is (e.g. BGRA8UnormSrgb),
but the browser canvas only offers plain BGRA8Unorm — so nothing
re-encoded and the whole image (background + models) rendered ~3× too
dark (authored bg 0.125,0.137,0.161 → ~32,35,41 collapsed to ~3,4,6).

Fix: when the surface format isn't sRGB, render through an sRGB *view* of
it — the standard WebGPU canvas pattern. surface_view_format_ is the sRGB
sibling of surface_format_ (unchanged when already sRGB, so desktop is a
no-op); configureSurface advertises it via viewFormats, the colour
pipelines (main, MSAA target, edge) target it, and render() creates the
surface view with it. The screenshot path still reads the base texture, so
its BGRA byte-order check stays on surface_format_.

Regression test: sample a 1x1 background pixel and assert it isn't crushed
dark (R,B > 20). Verified visually too — bg is now the correct dark
blue-gray and the cube is properly lit. 5/5 web smoke + 107/107 unit pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:08:30 +10:00
Dion Moult 3cc759d72b ifcviewer: fix blank-until-interaction stall on web (streaming settle burst)
On first web load the sample stayed blank until a click/drag, then popped
in. Root cause: the main draw + cull run before driveStreamingLoads in
render(), so a chunk that becomes resident there is only painted a frame
later. On desktop the streaming thread keeps inFlightApprox() > 0 during a
load, so the render loop keeps ticking and the next frame paints it. On web
the sync MEMFS / Blob load finishes instantly (inFlightApprox stays 0), so
the single post-load requestFrame fired once and the on-demand loop went
idle before the geometry was ever drawn — until some input re-armed it.

Fix: arm a bounded settle burst (kStreamingSettleFrames) whenever there's
streaming activity — a load this frame, work still queued, or a visible
chunk not yet resident — and bleed it down over the next few frames, each
requesting one more. Covers the cull→display latency under an on-demand
loop and still quiesces at idle (no busy-rendering). General, not web-only.

Regression test: the sample must render with NO pointer input — a centred
patch (the framed cube) differs from a corner patch (background); a blank
stall leaves both as background. Verified empirically with a no-interaction
probe (canvas went from a static blank hash to a stable rendered one).
107/107 unit tests pass; 4/4 web smoke tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:11:58 +10:00
Dion Moult 2b58d7be74 ifcviewer-web: smoke-test click-to-select highlight
Third Playwright case: click dead-centre on the framed sample, assert the
canvas changes (selection highlight rendered) with zero WebGPU errors. A
broken async pick would hang init or leave the canvas unchanged. All three
cases pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:06:28 +10:00
Dion Moult 094575fc19 ifcviewer-web: async object pick → click-to-select on web
Desktop pick reads the pick staging buffer back with a blocking
`while(!done) waitTickInstance()` spin. On web that spin is a no-op
(Asyncify is off) and hangs the JS event loop, so click-to-select was
dead on web. This adds an async sibling that uses the spontaneous
map-callback pattern (AllowSpontaneous + the browser microtask loop)
already proven by the HiZ readback — no blocking.

  - encodePickReadbackToStaging(x,y,want_normal): the pick-pass render +
    copy-texel-to-staging, extracted from pickObjectAt verbatim and shared
    by both readbacks (desktop sync path unchanged).
  - pickObjectAtAsync(x,y,cb) [web]: encode, then map the staging buffer
    with a spontaneous callback that delivers object_id to cb. One pick in
    flight at a time (a pick issued mid-map is dropped → cb(0)).
  - applyPickToSelection(id, add, remove): routes a pick result through the
    selection state machine (replace / Shift-add / Ctrl-remove / empty-click
    clear), mirroring the desktop ViewportWindow click semantics. selection_
    marks dirty so the next render's uploadSelectionFlagsIfDirty flushes the
    highlight.

main_web wires it: a left release under a 4px drag threshold (no orbit) is
a pick at down-position * devicePixelRatio, with Shift/Ctrl modifiers;
the result callback applies selection and requests a frame. Web + desktop
build clean; 107/107 unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:06:28 +10:00
Ryan Schultz db0bbf6b84 adding vscode workspaces to .gitignore 2026-06-29 14:29:17 -05:00
Gorgious56 a757641b84 Bonsai: derive prefs PropertyGroups from EDIT_TYPES
Collapse two parallel hand-maintained lists in the addon-preferences
PropertyGroups into derivations from `tool.Parametric.EDIT_TYPES`:

- GizmoPreferences: the 10 `<name>: BoolProperty` annotations now
  generated from the full EDIT_TYPES list.

- DefaultParameters: add `has_default_parameters` flag to
  ParametricObject (set True on door/window/stair/railing/roof);
  derive the 5 `<name>: PointerProperty(type=BIM<X>Properties)`
  annotations and collapse the 5 hand-written `draw_expandable_panel`
  blocks in `draw_default_parameters` into loops driven by the flag.

Existing `test_gizmo_preferences_field_per_registry_entry` pinned the
GizmoPreferences contract; new
`test_default_parameters_field_per_registry_entry_with_defaults`
pins the DefaultParameters contract (one-directional: flag=True
implies field present, flag=False allows absence).

Generated with the assistance of an AI coding tool.
bonsai-0.8.6-alpha2606291429
2026-06-29 16:29:44 +02:00
Thomas Krijnen 8bd22178f7 OCC_VER Compatibility 2026-06-29 11:30:37 +02:00
Thomas Krijnen b4d7780e14 Use opencascade::handle for compatibility with earlier versions 2026-06-29 11:30:37 +02:00
Frozen Forest Reality Technologies 7c092db9e6 OCCT 8 Update Part 4
Fix For : ``C:\Program Files\OCCT\inc\NCollection_Sequence.hxx(45,18): error C2280: 'CSLib_Class2d::CSLib_Class2d(const CSLib_Class2d &)': attempting to reference a deleted function``.
2026-06-29 11:30:36 +02:00
Frozen Forest Reality Technologies 38e3bb0590 Boost 1.88 Update 2026-06-29 11:30:36 +02:00
Frozen Forest Reality Technologies 7f49c945b9 OCCT 8.0 Update Part 3 2026-06-29 11:30:36 +02:00
Frozen Forest Reality Technologies 81f71e6418 OCCT 8.0 Update Part 2 2026-06-29 11:30:36 +02:00
Frozen Forest Reality Technologies 6318610bdb OCCT Update to 8.0 Part 1 2026-06-29 11:30:36 +02:00
Gorgious56 82dd1d94de Bonsai: batch host recuts in array/opening paths
Refs gh#8088. Array regen + multi-opening drops fan out N+1 wall recuts
per operator (one per child filling deletion + the final mirror recut),
making CSG opening-subtraction O(N^2) for a linear UX action.

Introduces tool.Geometry.batch_host_recut() — a context manager that
coalesces switch_representation and bpy.ops.bim.update_representation
calls per voided element within one operator transaction. The drain
re-reads the active representation so the recut reflects current IFC.

Wraps 7 entry points (regenerate_array, RegenerateArray, RemoveArray,
AddOpening, RecalculateFill, CloneOpening, regenerate_from_type) and
rewires 7 leaf call sites in opening.py, void/operator.py, and
mirror_parent_void_fillings_to_children.

An AST forward-compat guard pins the rewire contract: no direct
switch_representation or bpy.ops.bim.update_representation in the
three target files outside the helper definitions.

A 16-child array regen now recuts the wall once instead of 17 times.
The CSG cost per recut is unchanged; only the count is reduced.

21 new tests across three lanes (helper unit, entry-point coalescing,
AST guard) — all green.

Generated with the assistance of an AI coding tool.
bonsai-0.8.6-alpha2606290835
2026-06-29 10:35:01 +02:00
Gorgious56 4004344c20 Bonsai: TAB enters wall parametric edit
Mirror the icon-click entry into wall parametric edit on the TAB key.
The dispatch in Modifier.try_applying_edit_mode had no branch for fresh
LAYER2 walls, so TAB landed in item mode instead of the parametric
draft + gizmos. Add the missing entry leg of the toggle, placed after
the generic is_object_editing branch so the finish leg still fires
when a wall is already in edit mode.

Generated with the assistance of an AI coding tool.
bonsai-0.8.6-alpha2606290748
2026-06-29 09:48:20 +02:00
Dion Moult e70c58fe99 ifcviewer-web: smoke-test the Blob.slice byte-range load path
Adds a second Playwright case that picks the sample sidecar through the
file input, waits for the C side to confirm the blob load (console),
then asserts an orbit drag changes the canvas with zero WebGPU errors.
This exercises the #88 path distinctly from the embedded MEMFS sample —
a broken metadata-head/tail or chunk range read renders blank and fails
the orbit-changed-canvas check. Both cases pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:44:02 +10:00
Dion Moult 584504dcdc ifcviewer-web: stream user sidecars via Blob.slice byte ranges (#88)
Picked files are no longer copied whole into the wasm heap. The browser
File object stays in JS (Module.__ifcvFile) and is read lazily through
Blob.slice byte ranges, so a 200-500 MB sidecar never enters wasm linear
memory — only chunk-sized slices do.

Mechanism (web-only, #if __EMSCRIPTEN__):

  - JS glue (EM_JS): ifcvFileSize + ifcvReadRangeInto — slice [off,off+n)
    of the File and copy it into a caller-provided heap pointer, then call
    back _ifcv_on_range_done. No malloc across the boundary; C pre-sizes
    the destination from the read plan.
  - webReadRangesAsync: reuses planSidecarReadRanges to coalesce a range
    set into Blob.slice reads (1 MB gap — each slice is an async hop),
    scatters them into a destination laid out in input order, and fires a
    continuation when the whole set lands. An in-flight map keyed by id
    survives unordered_map rehash (scratch buffers are heap-owned).
  - loadSidecarFromBlobWeb: async metadata load — head (16 B) -> index
    count -> tail-to-EOF -> parseSidecarHead/Tail -> applyCachedModel, then
    tags the model streaming_from_blob and frames it.
  - driveStreamingLoads: blob-sourced models route to beginWebChunkLoad
    (async vertex+index range reads -> applyStreamedChunk in the callback),
    holding is_loading until the bytes arrive. The embedded MEMFS sample
    keeps the synchronous fopen path.

shell.html stashes the File and calls _load_sidecar_from_blob_c instead of
FS.writeFile'ing the whole thing; EXPORTED_RUNTIME_METHODS=['FS'] dropped.
Desktop is untouched (the new members + driveStreamingLoads branch are all
emscripten-guarded). Web links clean; desktop rebuilds; 107/107 unit tests
pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:43:53 +10:00
Dion Moult a0ba3c0b98 ifcviewer: extract pure buffer-based sidecar parse + read-plan helpers
Splits the v13 metadata wire-format knowledge out of the FILE*-bound
streaming reader into pure, buffer-based functions so the web byte-range
path (#88) can reuse it without loading the whole sidecar into the wasm
heap:

  - parseSidecarHead  — validates the 16-byte head, yields num_vertex_bytes
  - parseSidecarTail  — parses meshes/instances/georef/elements/strings
                        from an in-memory tail buffer, bounds-checked
  - planSidecarReadRanges + SidecarReadPlan — the range-coalescing /
    scatter planner, promoted out of the anonymous namespace

readSidecarMetadataOnly and the range readers now call these; desktop
behaviour is unchanged (head + tail are small, the bulk is still skipped
via seek). The metadata tail is split from the head around the bulk
sections, so a blob-backed loader just slices those two regions and
hands the bytes to the same parsers.

Closes a coverage gap: StreamingLoader had no unit tests. Adds
test_streaming_loader.cpp (7 cases: metadata round-trip, corrupt/truncated
rejection, vertex+index range scatter, head validation, tail truncation,
read-plan coalescing). 107/107 unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:26:29 +10:00
Dion Moult fcf3a645db ifcviewer-web: add a headless-browser smoke test
Drives the built web page in a real Chrome (channel:'chrome', so no
`playwright install`): waits for wgpu init, then asserts an orbit drag
changes the composited canvas — one check that simultaneously proves the
scene rendered, mouse input is wired, and the log overlay isn't eating
events — and that zero uncaptured WebGPU errors were logged. Every web
bring-up bug so far (blank render, error-buffer cascade, overlay
swallowing input) is this shape; this would have caught them.

serve.mjs statically serves build-web; the config launches headed
against the real GPU (--use-angle=vulkan + --ignore-gpu-blocklist are
load-bearing for a non-null adapter on Linux Chrome). node_modules and
results are gitignored. See README.md to run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 12:14:27 +10:00
Dion Moult 89beee6514 ifcviewer: detect web pool-grow OOM via provisional sub-buffers
On web we skip the desktop error-scope spin-wait (it blocks the JS event
loop and hangs the page). The old web addSubBuffer then judged success by
`buf != nullptr` — but Dawn-web returns a NON-NULL error buffer on OOM,
so the pool committed an invalid sub-buffer, alloc handed out slices in
it, and every chunk_bind_group built against it failed ("BindGroup is
invalid" spam + a wgpuQueueSubmit panic). Loading a model larger than the
browser's WebGPU budget triggered exactly this.

Add the grown sub-buffer as *provisional* (alloc and the capacity/free
tallies skip it) and validate it through a non-blocking AllowSpontaneous
PopErrorScope. resolveProvisionalGrowth() clears the flag when it's good,
or drops the sub-buffer and latches growth_disabled_ on a real OOM — at
which point the streaming evictor bounds the working set to what fits
instead of cascading. Only one provisional grow is in flight at a time
(growth_pending_). Desktop keeps its synchronous halve-retry path
unchanged. All 100 unit tests still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 12:13:34 +10:00
Dion Moult 2237b4acdd ifcviewer-web: stop the log overlay from covering + blocking the canvas
The status overlay was position:fixed top/left/right with max-height
80vh and pointer-events:auto — a near-fullscreen div that both hid the
model and swallowed mouse events, so orbit drags over most of the canvas
did nothing. Move it to a small bottom-left box, set pointer-events:none
so it never intercepts navigation, and collapse it to a few dimmed lines
once the app goes live (errors re-expand it). It still auto-scrolls to
the newest line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:33:14 +10:00
Dion Moult e3722cf243 ifcviewer-web: load user sidecars via a file-browse button (#46)
Adds an "Open .ifcview…" button that opens the browser's native file
chooser. The picked file's bytes are written into MEMFS and a new
exported entry point, load_uploaded_model_c, reads them back via the
existing loadSidecarFromPath: it resetScene()s the current model
(replace, not append), loads the sidecar, and viewAll()s it. Geometry
becomes resident over subsequent frames through render()'s inline
driveStreamingLoads, same as the embedded sample.

Deliberately a file picker, not drag-drop: browser file drag-drop needs
an X11 drag source (a file manager) to drag *from*, which a minimal WM
(ratpoison) doesn't provide. The native chooser is WM-independent.

Exports _load_uploaded_model_c and the FS runtime method; URL/byte-range
fetch of remote models stays for #88.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:19:49 +10:00
Dion Moult d36d241da2 ifcviewer: size streaming pool to demand instead of probing for the max
The desktop pool walked down from 4 GB looking for the largest single
buffer the runtime would grant, then used that as the pool's first
sub-buffer and growth increment. On a stack that advertises an
effectively unbounded maxBufferSize (1 TB observed on wgpu-native here)
the walk lands on 2 GB, so loading even a 1.2 MB model allocated a 2 GB
sub-buffer. That plus the depth/MSAA/HiZ/pick attachments exhausted
VRAM, and the next tiny allocation — the ~4 KB selection_flags buffer —
failed with "Not enough memory left", invalidating its bind group and
panicking wgpuQueueSubmit.

Drop the probe and size the pool to demand, mirroring the web path:
configure a modest initial sub-buffer and let BufferPool::addSubBuffer
grow it lazily (halve-retrying to its 64 MB floor on constrained
devices). A single chunk is capped at 16 MB, so the initial sub-buffer
only has to clear that. Web keeps its 64 MB initial (Chrome contends on
large first allocations); desktop uses 256 MB to keep sub-buffer count
low for big models. The two platforms now share one createPool() (was
probeAndCreatePool — it no longer probes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:09:12 +10:00
Dion Moult c0c0c987c4 ifcviewer-web: wire mouse + wheel navigation (#85)
Register emscripten HTML5 pointer/wheel handlers that translate raw
events into ViewportCore::orbitBy / panBy / dollyBy: left-drag orbits,
middle/right-drag pans, wheel zooms. mousedown binds to the canvas;
mousemove/up bind to the window so a drag keeps tracking off-canvas. A
contextmenu suppressor lets right-drag pan without the browser menu.

Pure callbacks — no Asyncify, no sync spin — so none of the web init
gotchas apply. Pick stays deferred until the async buffer-readback
rewrite. The embedded sample is now navigable on Chrome + Firefox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:50:11 +10:00
Dion Moult d92f6e6226 ifcviewer: route desktop orbit/pan/zoom through ViewportCore (#85)
Replace the inline orbit/pan/wheel math in ViewportWindow's mouse
handlers with calls to ViewportCore::orbitBy / panBy / dollyBy. The core
methods request the frame (via the host), so the now-redundant
requestUpdate() calls drop out; the pivot-indicator afterglow and 3px
drag-promotion stay in the Qt layer where they belong. Behaviour is
unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:50:11 +10:00
Dion Moult 1d13b05acf ifcviewer: add incremental orbit/pan/zoom to ViewportCore (#85)
The orbit navigation math lived in the Qt ViewportWindow, operating on
ViewportCore's camera fields through references. That left the web host
with no way to drive the camera — orbit/pan/zoom were Qt-only.

Lift the three pixel-delta moves into ViewportCore as orbitBy / panBy /
dollyBy so every host (Qt desktop + web) shares one implementation and
the math can't drift between platforms. panBy takes the viewport height
as a parameter (the one Qt coupling: pan's world-units-per-pixel needs
it) so the core stays toolkit-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:49:54 +10:00
Petru Conduraru fce7cd3eb4 test(ifcpatch): add MergeProjects regression test for merging 3+ files (#7973)
Merging more than two IFC models with the MergeProjects recipe leaves
duplicated IfcGeometricRepresentationContext entities behind. All elements
are kept, but the accumulated contexts cause later disciplines to appear
"not merged" in viewers.

This test merges three projects and asserts the elements are kept, a single
IfcProject remains, and the geometric contexts are reused rather than
accumulated. It currently fails on the context assertion, reproducing #7973.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 13:43:07 +02:00
Thomas Krijnen f3dcf0b539 Attempt at less double counting of inverses #7973 2026-06-28 13:42:36 +02:00
Thomas Krijnen f5ebbd1917 Add test case for https://github.com/buildingSMART/validate/issues/305 2026-06-28 11:12:14 +02:00
Thomas Krijnen 77aa080334 Recompile rules bonsai-0.8.6-alpha2606280847 2026-06-28 10:37:33 +02:00
Thomas Krijnen 5afad2b179 Partial revert of 84abf5e91 2026-06-26 16:29:27 +02:00