Because uv was always trying to install when starting a venv in `ifcopenshell` folder, though they might be already available globally. And also they were listed twice - in pyproject and in the ci-lint.yml, now there's a single source of truth.
Per aothms's request on #8605: QtViewer is being superseded by the new
Bonsai Viewer, so its remains are deleted here (src/qtviewer, its
BUILD_QTVIEWER cmake option and add_subdirectory, and its references in
ci.yml's path filter, .gitignore, the conda recipe's license table, and
README's library table).
src/ifcopenshell-python/ifcopenshell/geom/app.py's qtViewer3d is
unrelated (pythonocc-core's own OCC.Display widget class, a name
coincidence) and is untouched.
Generated with the assistance of an AI coding tool.
Follow-up to the scalar-only fix in #8754, per aothms's direct request on
that PR ("Please do make all int types consistent") and his own original
2023 design intent on issue #3058 ("make all integers (incl. schema
namespaces) an int64_t"). Widens the remaining inconsistent spots now that
compatibility isn't a constraint on this v0.9 branch:
- Integer aggregates (IfcTriangulatedFaceSet.CoordIndex and similar
List<int> attributes), including the SWIG to_vec_int/to_vec_vec_int
helpers, which previously silently truncated via static_cast<int> on the
Python-set path - the same bug class as the original scalar issue.
- The schema code generator (express/mapping.py's integer type mapping),
and all 12 generated schema header/source pairs regenerated to match, so
every schema-typed getter/setter (e.g. IfcOwnerHistory::CreationDate) is
int64_t end to end, not just the dynamic attribute-value path.
Instance/reference identifiers (STEP #123 ids) are deliberately left at
32-bit: they're a file-local index into internal maps, not an EXPRESS
domain value an application chooses, and no realistic STEP file has
billions of entities. The lexer's Token_IDENTIFIER parsing still funnels
through a 32-bit int for this reason - flagged as a known, low-risk gap
rather than fixed, since fixing it would mean touching indexing/hashing
code for no realistic benefit.
Verified: original PR's round-trip tests extended with aggregate cases
(IfcTriangulatedFaceSet.CoordIndex, InnerCoordIndices) at 64-bit boundary
values, in memory and through STEP text, IFC2X3 and IFC4. A standalone C++
program exercising the generated schema API directly (Ifc4::IfcOwnerHistory
::setCreationDate/CreationDate, IfcTriangulatedFaceSet::setCoordIndex/
CoordIndex) confirms int64_t end to end, bypassing SWIG. Full build
(BUILD_IFCGEOM, WITH_OPENCASCADE, BUILD_IFCPYTHON, IFC2X3+IFC4) clean.
test/util/test_attribute.py and test_file.py pass unchanged.
This contribution was produced with the assistance of an AI coding tool.
Setting an IfcInteger/IfcTimeStamp typed attribute (e.g. IfcOwnerHistory.CreationDate)
outside the signed 32-bit range corrupted the value instead of raising, since the
Python wrapper's set_attribute_value_py() truncated it with a plain static_cast<int>
before handing it to the C++ storage. Unix timestamps before 1901-12-13 or after
2038-01-19 silently wrapped around (e.g. 3000000000 became -1294967296) rather than
being rejected or stored correctly. Fixes#3058, equivalent to PR #8683 but ported to
this branch's rewritten ifcparse (snake_case files, variant_array/instance_data
storage, SWIG PyObject-based attribute setter) instead of the old IfcEntityInstanceData
sources, which no longer exist here.
The scalar slot of the attribute variant (Argument_INT) becomes int64_t. Integer
aggregates (Argument_AGGREGATE_OF_INT, e.g. CoordIndex) and instance/reference
identifiers stay 32-bit, since neither is the value that overflows here; this narrow
scope is kept on its own technical merits (aggregates and identifiers were never the
source of the bug, and widening them would be a much larger, riskier change for no
benefit) even though aothms said compatibility isn't a concern on this v0.9-track
branch. express::Base::set_attribute_value promotes the schema-generated int to
int64_t at a single choke point, so the generated setters keep compiling unchanged.
The STEP lexer, writer, and SWIG wrapper (set_attribute_value_py, pythonize) are all
widened together, since widening only the Python-facing setter would have silently
wrapped the value on file write instead of raising.
Verified in a build (IFC2X3 and IFC4, BUILD_IFCGEOM off, no kernels): pre-1901,
post-2038, both 32-bit boundaries, and a 9e12 value all round trip exactly both in
memory and through STEP text serialization (write then reopen). A value outside the
64-bit range now raises a clean exception instead of corrupting data. Ordinary
in-range integers and integer aggregates (e.g. IfcTriangulatedFaceSet.CoordIndex) are
unaffected. The existing util/test_attribute.py and test_file.py suites pass
unchanged; test_entity_instance.py has 5 pre-existing failures unrelated to this
change (confirmed identical on an unfixed build of this branch, caused by a missing
get_info_2 binding and _patch_swig_comparisons never being implemented here).
Generated with the assistance of an AI coding tool.
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>
IfcSurfaceFeature (e.g. road markings) adheres to a host element through
IfcRelAdheresToElement, a [1:1] cardinality hierarchical relationship in the
same family as aggregation, containment and nesting since IFC4.3. The spatial
traversal never followed it, so surface features had no resolvable parent or
container: on import they landed in the Unsorted collection instead of the
host's spatial collection, and were dropped entirely in DECOMPOSITION filter
mode.
Add get_adhered_element (feature to host) to the get_parent resolver chain and
walk HasSurfaceFeatures in get_decomposition, plus a get_surface_features helper
mirroring get_parts/get_contained. With get_parent resolving adherence,
get_container now returns the host's spatial container, so tool.Collector places
surface features under the host. Also follow HasSurfaceFeatures in the Bonsai
DECOMPOSITION filter path so they load in that mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build the spatial hierarchy panel from the loaded model's real IFC
spatial structure instead of mock data:
- helpers/element: add get_spatial_children (IsDecomposedBy -> RelatedObjects,
filtered to spatial elements) to walk IfcProject -> IfcSite -> IfcBuilding
-> IfcBuildingStorey -> IfcSpace.
- SessionState: relay dataSourceReady as modelDataSourceReady (the .ifc for a
sidecar hit loads asynchronously, so the tree can only build once it arrives).
- spatial_hierarchy/View: walk the active model's IFC file into a TreeNode
tree, naming nodes by Name (fallback to class), mapping site/building/storey
kinds; siblings sorted with natural (numeric) collation.
- spatial_hierarchy/Panel: tree now fills the panel height (setBodyExpanding +
Expanding size policy); right-click menu for recursive Expand/Collapse
Subtree and Expand/Collapse All.
Add the concept of an active model:
- SessionState: activeModelId / setActiveModelId / activeModelChanged; the
first loaded model is active by default; reassigns/clears on removal.
- Models panel: clicking a model makes it active; its cube icon is drawn with
the accent colour (makeAccentSvgIcon) via FederationItemModel::setActiveModelId.
- The spatial hierarchy reflects only the active model.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Populate the Properties and Quantities sections from the pset helper:
get_psets(psets_only) for Pset_*, get_psets(qtos_only) for Qto_* /
BaseQuantities, inheriting occurrence-over-type values. A toPropertySets
converter drops the internal "id" key and non-scalar values, formats
scalars for single-line cells, and skips empty sets. Placeholders are
cleared once a project is loaded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add helpers to src/helpers/element: get_scalar_attributes (primitive
EXPRESS attributes only — entity refs / aggregates omitted), get_type
and get_container (ports of ifcopenshell.util.element), and a public
get_string_attribute for safe by-name reads.
Wire them into the properties panel:
- Attributes section shows the element's direct primitive attributes for
live entities, or cached GlobalId / Name for geometry-only elements.
- Relationships section shows the construction Type and spatial Container
by name (falling back to the class when unnamed).
- Placeholders are cleared once a project is loaded, so no mock data leaks.
Also: a deselect (click on empty space -> object_id 0) no longer resets
the panel; it keeps showing the last active object. Project reset/open
still clear it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add src/helpers/element.{h,cpp}, a schema-dispatched C++ port of
ifcopenshell.util.element.get_predefined_type: prefers the associated
type element's predefined type (IsTypedBy / IsDefinedBy), falls back to
ElementType / ProcessType when USERDEFINED, then the occurrence's own
PredefinedType / ObjectType. Attribute reads are by-name so they work
across the IfcElement / IfcType* subtypes that carry these attributes.
Wire it into the properties panel entity summary: live IFC entities show
their real predefined type; geometry-only elements (a .ifcview loaded
without its .ifc/.rdb) show "N/A". Clears the placeholder so a stale
predefined type no longer leaks once a project is loaded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the two overloaded model identifiers and make object_id
assignment single-authority, fixing a pick -> properties mismatch.
Identifiers:
- Per-model UUID fed_id -> model_id; the uint32 runtime handle
model_id -> session_model_id (SessionState accessors + mirror hashes
renamed to match). "fed_id" was a misnomer -- the federation is the
whole collection, not one model.
object_id assignment (fixes wrong class on click):
- Producers (GeometryStreamer, .ifcview sidecar) now stamp model-LOCAL
object_ids; ViewportCore::applyCachedModel is the sole authority that
assigns the session-global id (base + local). Removed
SceneLoader::next_object_id_, GeometryStreamer::lastObjectId(), and the
streamer's start_object_id parameter.
- The element table is stamped by the same base on both load paths
(applySidecarData and onStreamerFinished), so registry ids match the
ids pick returns. Previously the sidecar path double-rebased instances
vs the registry (click IfcSite -> showed IfcDoor); the live-stream path
had the same latent mismatch. Both closed.
Naming / cleanup:
- SceneLoader::addFiles -> queueModels; startStreamLoadFor ->
loadFromGeometryStreamer; readSidecarMetadataOnly -> readSidecarMetadata.
- Federation::addModel takes an explicit display_name (no QFileInfo
fallback); callers pass QFileInfo(path).fileName().
- Disambiguate cryptic short locals (d->sidecar, m->model, c->chunk, ...)
in SceneLoader, Federation, ViewportWindow, AreaMeasurement,
SectionGizmoRenderer, and the SidecarData/SidecarReadPlan spots in
ViewportCore.
Tests: 125/125 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
file.write() streamed directly onto the target path, so a crash mid-write
left a truncated file with dangling STEP references. Serialize to a temp
file in the same directory, then atomically rename it onto the target.
- New IfcUtil::path::atomic_rename_file: std::rename on POSIX, MoveFileExW
with MOVEFILE_REPLACE_EXISTING on Windows. Unlike rename_file it never
unlinks the destination first, so there is no window where it goes missing.
- Fully in C++/swig (per aothms), so the FILE_NAME header is untouched: it
comes from the model header, not the output path (verified empirically).
- Temp lives next to the target so the rename stays on one filesystem.
- Stream is closed before the rename (Windows cannot move an open file).
- On any write error the temp is removed and the original target is intact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>