- add_stationing_referent.py: black reformat (new drift from v0.8.0).
- update_fallback_position.py: v0.8.0's changes to this file made the
ifcopenshell.util.unit import (added in an earlier commit here) unused;
removed per ruff.
poe ty's sequence only reaches ty-ios once ty-bonsai passes, so these
never surfaced until now:
- util/alignment.py: drop the stale `include_referent=False` kwarg from
add_zero_length_segment() - that parameter was removed from the function's
signature in 45ea5eb07 but this caller in a different file was missed,
leaving a latent TypeError if this code path is ever exercised.
- ifcopenshell_wrapper.pyi: add the optional trailing `logger` parameter to
parse_ifcxml/open/construct_iterator*, matching the real SWIG signatures
in src/ifcwrap/*.i (all declare `Logger& logger = Logger::Root()`) that
the hand-maintained stub never picked up.
- ifcopenshell/__init__.py: remove a stale `ty: ignore[unknown-argument]`
comment that ty confirms is no longer suppressing anything.
- assign_cost_item_quantity.py: OPERATORS mixes 2-arg binary operators with
the 1-arg `operator.neg` (for ast.USub), but FormulaEvaluator has no
visit_UnaryOp so USub can never reach this lookup via visit_BinOp.
Suppressed at the call site rather than touching the dict, since this
looks like scaffolding for unary-minus support rather than dead code.
- Explicit submodule imports (ifcopenshell.geom / api.alignment / util.unit
/ api.aggregate / api.context / api.spatial) added where accessed but
only reachable by accident of import order.
- tool.py: drop the `-> int` annotation on the Parametric interface's
get_geom_generation stub; its `pass` body implicitly returns None, which
ty can't reconcile with the runtime @interface/@abstractmethod rewriting
it never sees statically. Matches the file's other stubs (-> None).
- railing.py: qualify the "BIMRailingProperties" string annotations as
"prop.BIMRailingProperties" on the two functions using it, since the bare
name was never imported into this module's namespace.
- product.py: suppress ty's missing-argument errors on
copy_z_rotation_to_selected's Surveyor.get_z_rotation/set_z_rotation
calls with targeted ty: ignore comments. The function is unused and its
two dependencies were never implemented on the concrete Surveyor tool;
left as-is rather than deleted or implemented.
- gizmos.py: TYPE_CHECKING-guard `import bmesh` for the string-literal
annotation in build_schematic_mesh; suppress the still-unresolved
gizmo_textures import in TexturedQuadGizmoMixin (WIP dependency, not dead
code).
- model/__init__.py: register the `decorator` submodule, which unregister()
already calls (would have raised NameError on addon disable).
- mep.py / tool/model.py: add explicit imports for bonsai.core.geometry and
bonsai.core.model, previously only reachable by accident of import order.
- Test files: add explicit ifcopenshell.api.pset / ifcopenshell.util.element
submodule imports used but not imported.
The datamodel-v1.0 merge (cf05bbd1b) overwrote build_rocky.yml with a
version that switched python3 -> uv run but dropped the --shared flag that
a91b1da28 ("Reduce Rocky package size") had added. build_osx.yml kept it
(ddee88bed).
Without --shared, nix/build-all.py builds IfcOpenShell as static libs, so
each of the ~40 plug-in .so files (schemas x8, kernels, mappings,
serializers, writers) statically embeds a full copy of libIfcParse +
libIfcGeom. The data-model rewrite made those base libs much larger, so the
duplication ballooned the Linux packages (~2-3x). With --shared the plug-ins
dynamically reference the shared libIfcParse/libIfcGeom instead. Restores the
same size reduction macOS already has.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a 'Rename' action to the model (non-group) context menu, mirroring
renameGroup: prompts via QInputDialog, trims, and calls
setModelDisplayName + notifyFederationChanged. The Federation already emits
modelChanged, so the panel item text updates in place.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With dbus-devel added, the connector's Rust code compiles fully and reaches
the final link, which fails: the bundled FLTK GUI toolkit needs the X11
extension, pango and cairo shared libs, plus libsupc++.a. ld reports every
unresolved -l at once, so this is the complete set:
-lXext -lXinerama -lXcursor -lXrender -lXfixes -lXft
-lpango-1.0 -lpangoxft-1.0 -lpangocairo-1.0 -lcairo -lsupc++
The X/pango/cairo -devel packages are in AppStream; libstdc++-static
(libsupc++.a) is in CRB, so enable it for the transaction. GitHub's ubuntu
runners ship all of this, which is why the dedicated connector workflow
never needed it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cargo build of bonsaiviewer-autodesk pulls dbus-secret-service (the Linux
OS-keyring backend for credential storage) -> dbus -> libdbus-sys, whose
build.rs needs dbus-1.pc via pkg-config. GitHub's ubuntu runners ship
libdbus-1-dev, so the dedicated connector workflow never needed it; the
minimal Rocky container doesn't. Add dbus-devel to both Rocky jobs
(pkg-config is already present). This was the last step after a fully
successful C++ build + cargo compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The community rockylinux/rockylinux:10 image omits PATH from its image
config, unlike the old Docker Official arm64v8/rockylinux:9. GitHub Actions
derives each run step's PATH from that config, so with no PATH the shell
exec (docker exec ... sh -e {0}) fails with exit 127, 'exec: sh: not found'
— it broke before any build logic ran. Restore a standard PATH via the
container env; the runner still layers GITHUB_PATH additions (uv, cargo)
on top.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two gaps left when BonsaiViewer and its Rust connector were newly added to
the Rocky CI jobs (May–Jun), neither previously exercised there:
1. Rust: the autodesk connector was rewritten from a PyInstaller Python
app to a Rust crate, so packaging/build.py now runs 'cargo build
--release'. Neither Rocky workflow installed a toolchain. Add rustup
(stable, matching the dedicated dtolnay/rust-toolchain@stable workflow)
to both x86 and ARM.
2. ARM glibc: aqt's official Qt6 ARM binaries link glibc 2.38, which Rocky
9 (glibc 2.34) can't load — moc fails, breaking IfcViewer_autogen. Move
the ARM job to Rocky 10 (glibc 2.39). The legacy arm64v8/rockylinux
image stopped at 9, so use rockylinux/rockylinux:10 (multi-arch, has
arm64). Rocky 10 defaults to Python 3.12 and drops python3.11, so the
script-runner references move python3.11 -> python3 (system Python only
runs helper scripts; ifcopenshell is built against uv's Python). Bump
the ccache key to rockylinux10. x86 stays on Rocky 9 to keep its lower
glibc floor for end users.
The rockylinux9-arm64 build-outputs deps branch is kept as-is: Rocky 9
deps are forward-compatible on Rocky 10, and no rocky10 branch exists yet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
createWgpuSurface() calls wgpu_macos_attach_metal_layer() in the Q_OS_MAC
branch at the top of the file, but the only #include of MetalSurface_mac.h
sat ~450 lines below the call site, so macOS builds failed with 'use of
undeclared identifier'. The header self-guards on __APPLE__, so move the
include up into the early platform block next to <Windows.h>; the lone
call site is the sole consumer, so the late include was dead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The data-model branch's spf_header::set_file_description/name/schema take a
const shared_pointer_type& (an internal instance_data* storage handle). SWIG
wraps them and emits the alias unqualified into the global-scope wrapper,
which MSVC rejects (C2065 'shared_pointer_type': undeclared identifier). The
matching getters are already %ignore'd and re-exposed via %extend; the raw
setters are not a usable Python API, so ignore them the same way.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Iterator::next() now returns express::Base (data-model branch). Comparing
it against 0 is ambiguous: 0 converts to Base via the pointer ctor while
Base converts to int via operator bool, so both operator!=(int,int) and
Base::operator!= are candidates. Use an explicit truthiness test — an
empty Base signals end-of-iteration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parse_num_ used std::from_chars for both integers and doubles, but the
floating-point from_chars overload is =deleted in Apple clang's libc++, so
the macOS build failed to compile (parse.cpp:136, instantiated for double).
Split parse_num_ with `if constexpr`: integers keep std::from_chars
everywhere; on macOS, doubles parse via strtod_l with a cached "C" locale
(locale-independent, restoring the pre-charconv Apple path). libstdc++ and
the MSVC STL have working float from_chars and are left unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build-all.py's install_qt6 runs `sys.executable -m aqt`, but the build now
runs under `uv run`, whose isolated env never got aqtinstall — it was pip
installed into the system Python. `uv run --with typing_extensions --with
aqtinstall` puts them where the script actually executes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A .rdbview is a zip of model.rdb/ (the lossy IFC data DB — the rdb
serializer skips IfcRepresentationItem) + model.ifcview (baked geometry).
The viewer could produce them but not open them.
- extractRdbview(): unzip a .rdbview (QZipReader) into a session temp dir
keyed by a hash of path+mtime+size (reused on re-open), returning the
extracted model.rdb. The producer's layout means sidecarPath(model.rdb)
resolves the sibling model.ifcview automatically, so it then loads exactly
like any pure .rdb: geometry from the sidecar, data from the .rdb via
ifcopenshell::file(FT_AUTODETECT). No SceneLoader/engine changes.
- detail::loadModels() resolves each source path through it before
queueModels (both fresh-open and project reload go through here), so the
Federation persists the .rdbview while the loader gets the extracted .rdb.
- cleanupRdbviewCache() clears stale extractions at startup.
- .rdbview is offered under "Add Geometry" (the file picker; "Add IFC
Database" is a directory picker), not "Add IFC File" — it's a lossy viewer
bundle, not a source IFC.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Opening a fresh .ifc streams geometry to the GPU, then bakes the .ifcview
cache. That bake — reorder + per-chunk zstd (level 19) — ran synchronously
in SceneLoader::onStreamerFinished, which is a QueuedConnection slot on the
main thread, so it froze the UI right as the progress bar hit 100% (≈15s of
zstd for a 130 MB-geometry model).
- Move the compress + writeSidecar onto a background thread. The geometry is
already resident and the sidecar is only a cache for the next open, so the
viewport is interactive the instant streaming finishes; the write is joined
before the next write and in the destructor.
- Parallelise the per-chunk zstd across hardware_concurrency threads (compress
all chunks, then write serially to keep contiguous offsets) so the
background write also finishes quickly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Backface Culling" checkbox persisted a value and reflected it, but
nothing consumed AppSettings::backfaceCulling — the opaque pipeline
hardcoded cullMode = Back, so toggling had no effect.
Build a second opaque pipeline (cullMode None) alongside the culled one and
pick between them per-frame from a backface_culling_ flag; setBackfaceCulling
flips the flag and requests a redraw (no rebuild). ViewportWindow forwards
it, and MainWindow applies the persisted value at startup and re-applies on
change — same wiring as the nav preset.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restructure the web viewer so the wasm is a reusable module and add a
second example that drives it from ordinary page DOM.
Build:
- Emit IfcViewerWeb.js (a `createIfcViewer` factory, MODULARIZE) + .wasm
instead of a single baked page (dropped --shell-file); copy the static
example pages next to it at build time.
- Unbreak the web build: CameraMath.h / ViewportCore.cpp used
boost::math::constants::pi just for pi, pulling all of boost/math into a
header shared with the Emscripten build (no Boost in its sysroot). Replace
with a constexpr kPiF — identical value, no dependency, desktop unaffected.
JS integration (web/ifcviewer.js):
- A small helper wraps the factory: boots the viewer on a canvas, runs the
RAF loop from onRuntimeInitialized (NOT a post-await .then, which stalls
Dawn-web's device callback and leaves the device half-initialised), and
exposes addFile/addUrl, clearScene, model list/progress, and onSelect(...).
- ViewportCore/main_web emit each pick to JS via Module.__ifcvOnSelect
(object id + IFC GlobalId + model index; empty on deselect); onSelect also
dispatches an 'ifcviewer:select' DOM event.
- Fix input coords for a non-fullscreen canvas: mousemove/mouseup are
window-targeted, so convert their coords to canvas-relative via the canvas
client-rect origin (marquee + box-pick were offset when embedded).
Examples:
- IfcViewerWeb.html: the fullscreen viewer (same DOM/behaviour as before,
now loading the module) — the Playwright smoke suite still targets it.
- embedded.html: a sized viewer with DOM outside it to add models (file or
URL), list loaded models with streaming progress, and show the model +
GlobalId of the clicked object. Starts empty (drops the wasm's embedded
sample, which the fullscreen page/tests still use).
- index.html links both.
Federation note: the web viewer already streams multiple models into one
scene (a byte-source per file/URL); it doesn't need the desktop Federation
document for this. Verified: 11/11 web smoke tests pass; embedded example
loads models, reports the picked model + GUID, and the marquee aligns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Theme QInputDialog (the New Group / Rename Group popup) so it follows
the dark theme instead of rendering light.
- Theme the generic QTabBar that QMainWindow creates for tabbed docks
(previously bright white). The app's own #appTabBar keeps its look via
more specific selectors.
- Reduce QHeaderView::section vertical padding (7px -> 4px) so table/tree
header rows match the body row height throughout the UI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
initWgpu() runs on the first exposeEvent, after MainWindow has already
applied the nav preset saved in Settings. It then unconditionally
re-applied "blender" whenever WGPU_NAV_PRESET was unset, silently
overriding the user's saved choice — so the applied navigation didn't
match what Settings showed.
Only apply the preset from WGPU_NAV_PRESET when that env override is
actually set; otherwise leave the current preset (MainWindow's persisted
choice, or the blender default). The startup log now reports the effective
orbit/pan bindings rather than a hardcoded name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously Del always removed the most recently added section plane. Now a
plane can be picked and deleted individually:
- ViewportCore tracks a selected plane index, kept valid as planes are
added (the new one becomes selected), removed, or cleared.
- Clicking a gizmo with the section tool active selects that plane.
- The section gizmo geometry is baked white and coloured via its per-plane
tint, so the selected plane draws in a bright amber highlight while the
rest stay red (unchanged look).
- Del/Backspace removes the selected plane, falling back to the most recent
one when nothing is selected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The filter field now actually filters. Typing shows only the sets whose
name matches, or that contain a matching property name/value — and when
it's a property/value match, only the matching rows are kept (neighbouring
rows are dropped). Matching is case-insensitive.
Set widgets live in a per-section container that's rebuilt from the raw
data on each keystroke, so filtering never recreates the filter field (its
focus and cursor are preserved). Placeholder reads "No properties/
quantities" with no data, "No matching properties/quantities" when the
filter excludes everything.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert bare Mock() to Mock(spec=bpy.types.Object) for Blender-object
stand-ins in TestRecalculateWallsWithNewConnections, TestMEPActionGuards,
and TestRecreateAggregateIteratesAllNew so typos on the Blender API
fail loudly instead of silently returning a MagicMock.
Replace the ad-hoc Mock() ConnectionRecord stand-in in
TestRecreateConnectionsZipsPairs with a real ConnectionRecord instance,
which pins field names at construction and catches drift if the
dataclass fields ever get renamed.
IFC entity mocks remain bare Mock() intentionally: entity_instance
attributes are schema-driven at runtime rather than defined statically
on the class, so spec= would refuse the .GlobalId / .HasFillings /
.ConnectedTo attribute writes the tests need.
Relates to #8088.
Generated with the assistance of an AI coding tool.
Rewrite tool.Array.select_only_parent as a thin call to
tool.Blender.select_and_activate_single_object; drop the ad-hoc
per-child deselect loop and the unused parent_element parameter.
Replace the tail parent_obj.select_set(True) in _regenerate_array_body
with tool.Blender.set_object_selection, which wraps select_set in the
hidden-object try/except the utility already owns.
Relates to #8088.
Generated with the assistance of an AI coding tool.
Add tool.Array.is_array_child helper. Port decorator and MEP
action gizmos (lock, pen, join) hide on array children — writes
on children get wiped by the next regen, and the port topology
is inherited from the parent.
Introduce tool.Array.select_only_parent and wire it into both
bim.regenerate_array and bim.finish_editing_array so post-regen
state converges on parent-only-selected + active. Grow and shrink
paths otherwise diverge (grow left new children selected alongside
the parent; shrink left only the parent).
Relates to #8088.
Generated with the assistance of an AI coding tool.
Sweep [0]-indexing in recreate_aggregate, recreate_connections,
and recreate_port_connections so batched N-child duplicates
recreate relationships on every new child, not just the first.
Single-source callers unaffected (loop collapses to one iteration
on 1-element lists).
Relates to #8088.
Generated with the assistance of an AI coding tool.
Replace N sequential duplicate_ifc_objects([parent]) calls in
_regenerate_array_body with one duplicate_ifc_object_n_times call
per layer, batching the fixed per-call overhead (snapshot gather,
UI refresh, decorator reload).
Guard batch_host_recut drain against dead StructRNA refs and prune
orphan array-child GUIDs at regen so outliner-delete of a
Bonsai-managed child cannot crash subsequent regenerate_array.
Recalculate walls after recreate_connections so Shift+D of
connected walls produces correct junction geometry without a
manual regen step.
Relates to #8088.
Generated with the assistance of an AI coding tool.
- Replace the mock IfcWall placeholder with a "No item selected" empty
state; the panel only fills in class/attributes/relationships/psets from
a resolved object, and safely stays empty otherwise.
- Show "No properties" / "No quantities" placeholders (muted, themed via
secondary_text) when those sets are empty.
- Drop the base application font 10pt -> 9pt to fit more data. Panel titles
keep their own explicit size and are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- helpers/placement: port get_storey_elevation (placement Z, falling back
to the Elevation attribute), matching ifcopenshell.util.placement.
- Add a secondary column: the storey elevation for IfcBuildingStorey,
otherwise the LongName when filled. Elevations are right-aligned.
- Columns: Name is drag-resizable (interactive) and defaults to 20% of the
width, Long Name stretches to fill the rest, and the eye is pinned to the
right at a fixed width. Header shown so the divider can be grabbed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The streaming settle burst re-armed the render loop whenever a
non-resident chunk was frustum-visible, but the enqueue only fetches
chunks that are contribution-visible (big enough on screen) and not in a
blocked cooldown. A chunk that is in the frustum but sub-pixel is never
loaded, so visible_pending stayed true forever and the loop spun at full
frame rate with no input.
Match visible_pending to the enqueue's eligibility test: a non-resident
chunk keeps the loop alive only if it's actively loading, or is
contribution-visible and past its cooldown. Sub-pixel / cooldown-blocked
chunks no longer prevent idle; they still stream in when a camera move or
eviction requests a frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>