SWIG wraps every public method of file, so the std::function-taking
helper leaked into the wrapper and the stub validation tripped over it.
Only the compiled %extend helpers call it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH
Review: the wrapper helpers visited the storage variant themselves and
threw for anything but in-memory storage. Move the dispatch into
file::all_referencing_instances(instance_id, pred), implemented for
both backends the way instances_by_reference is: the in-memory index
through inverse_index::all_sources, RocksDB through the same "v|<id>|"
prefix seek, stopping at the first source pred rejects. The SWIG
helpers only build the id set and call it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH
remove_deep2 decides whether a subelement may be purged by checking that
every instance referencing it lies inside the subgraph. It did so with
set(ifc_file.get_inverse(subelement)) - subgraph_set, which wraps every
referencing instance into a Python object first. For shared instances
that is enormous: a representation context is referenced once per
representation in the file, so purging one representation wrapped
~100k instances just to conclude "not exclusively mine". The same
question was asked once more for the start element against
also_consider, answered by traversing each considered element and
scanning the result with entity_instance.__eq__.
Add inverse_index::all_sources(id, pred), which visits the live records
referencing id and stops at the first source pred rejects, and two
wrapper helpers on file built on it: _all_inverses_within(e, ids) for
one instance, and _ids_referenced_only_within(ids) which returns the
subset of ids referenced only from within ids. remove_deep2 calls the
latter once per invocation, before the loop: clearing large aggregates
inside the loop only removes references whose source is inside the
subgraph, so "referenced only from inside" cannot change while it
runs. Both call sites are decision-identical to the old checks: the
set of surviving instances after root.remove_product on 300 products
of a 155 MB model is unchanged, and so is the element/api test suite.
root.remove_product per call, 300 products, on top of open-perf-v2:
see the pull request for the per-model table.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH
* ifcparse: make deleting and creating instances work on RocksDB-backed files
file.remove() on a RocksDB-backed file segfaulted. Reducing it turned up
five gaps that each made editing such a file crash or silently do
nothing:
- process_deletion_inverse() decoded the v| inverse-record values as
size_t while the serializer, register_inverse(), unregister_inverse()
and instances_by_reference() use uint32_t, so std::find failed and
vals.erase(end()) was undefined behaviour. It also took the DeleteRange
end from an iterator that is invalid when the instance has no inverse
records. Decode as uint32_t, remove every occurrence guarded on
"found", and derive the range end from the prefix itself.
- attribute_value::size() ignored storage_model_, so every aggregate
assignment on a RocksDB instance threw "Invalid variant index" from
set_attribute_value(). Branch on the storage model like the sibling
accessors and count the deserialized aggregate.
- rocks_db_file_storage::create() was a stub returning an empty handle,
which anything creating an instance then dereferenced. Implement it
after in_memory_file_storage::create().
- max_id_ is only initialised by the in-memory parse, so a RocksDB file
would have handed out ids that overwrite existing instances.
Implement the recalculate_id_counter() stub per backend and run it
once before the first fresh_id() on RocksDB.
- byid_.erase() was a no-op: set_to_map_transformer::erase() and
rocksdb_set_view::erase() were stubs. The deleted instance's attribute
keys and cached handle survived, entity_names() still listed it and
reopening the database threw. Erase deletes every key under the
instance's prefix; the transformer forwards to it and takes an
on-erase hook the storage uses to drop the cached handle.
root.remove_product on the first 200 products of a 61 MB model now
leaves the same surviving ids and inverse counts whether the file was
opened from SPF or converted to RocksDB.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH
* ifcparse: map argument_type to its stored type, size() as size_t
Review: argument_type enumerates the members of type_variant_parameter_pack
in order, so express that once as argument_storage_type_t<A> (pinned by
static_asserts) and let attribute_value::size() on RocksDB go through a
single aggregate_size_<A>() helper instead of spelling each vector type
out in the switch. size() now returns size_t; its only caller already
took size_t.
Also build the RocksDB DeleteRange upper bounds as prefix + ('|' + 1)
rather than a literal '}', which read as the {id} placeholder notation.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH
* ifcparse: fixed-width hex ids in RocksDB keys, batched instance erasure
Review: recalculate_id_counter() scanned every i|<id>|_ record because
ids were written as decimal text, which doesn't sort numerically. Make
every numeric key segment fixed-width 16-digit lowercase hex, produced
and parsed by key_to_string()/key_from_string() in rocksdb_map_adapter.h,
with named builders (rocksdb_key::attribute, header_attribute,
type_record, inverse, inverse_prefix, type_list, upper_bound) that the
storage, entity_instance_data.cpp, read_schema() and the serializer use
instead of assembling "i|" + std::to_string(id) + ... by hand. Keys now
sort by id, an instance's records are contiguous in id order, and the
largest id is the last key under i|, which recalculate_id_counter()
seeks to. The two dormant to_string_fixed_width() helpers are gone.
This changes the on-disk layout; databases converted before this commit
have to be re-converted.
Also review: instance_cache_ eviction went through an on-erase hook on
set_to_map_transformer, a std::function call under a mutex per deleted
instance. Drop the hook; file::remove_entity() and unbatch() call
file::erase_instances_(ids), which on RocksDB is
rocks_db_file_storage::erase_instances(): one WriteBatch of DeleteRanges
and one lock for the whole batch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH
* ifcopenshell-python: test_rocks queries the fixed-width hex keys
The test looked up raw keys in the decimal layout ("i|139|5",
"t|<identity>|0"); numeric key segments are fixed-width hex now.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Homebrew no longer publishes bottles for the macos-14 runner, so
`brew install qt` in build_osx.yml compiles Qt and QtWebEngine from source
and fails in the dependency step before any IfcOpenShell code is reached.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
6dbc4e28d raised the minimum CGAL to 5.4. The Windows dependency cache in
build-outputs is keyed on the install directory name and only ever adds to
it, so the unversioned `cgal` directory kept serving CGAL 5.2.3 and the
Windows build has failed at configure since:
Could not find a configuration file for package "CGAL" that is compatible
... cgal/lib/cmake/CGAL/CGALConfig.cmake, version: 5.2.3
Name the directory `cgal-5.5.5`, as the TODO already suggested, and record
it in the deps cache like the other installers do.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ae2f27c3a replaced the global include_directories(${Boost_INCLUDE_DIRS})
with a Boost::headers target appended to Boost_LIBRARIES. The wasm branch of
IfcParse's link line never included Boost_LIBRARIES, so it lost the include
path and the Pyodide build has failed since with
src/ifcparse/exception.h:25:10: fatal error: 'boost/lexical_cast.hpp' file not found
Nobody noticed because no Pyodide build had run since 2026-08-28.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fixes the following compilation error when building on MSVC with `USE_MMAP`.
```
parse.cpp(2202,23): error C2338: static_assert failed: 'Default instance_streamer requires a pushed sequential reader'
```
Apparently, when class has `IFC_PARSE_API`, compiler is very eager to instantiate all possible templates leading to this error. We already have explicit instantiation with export for the ones we actually use (e.g. `template class IFC_PARSE_API ifcopenshell::instance_streamer<file_reader<full_buffer_impl>>;`), so it should be no-op.
It also didn't propagate cgal includes for non-cmake configs.
So it means since roughly 0555523, when `CGAL_INCLUDE_DIR` was dropped from global includes, we only supported cmake configs discovery for cgal, so it's a good evidence to drop non-cmake config discovery in general.
Tested locally that build succeeds with all other components being
header-only or unused by now.
`regex` is only needed for Boost <1.76, see
https://www.boost.org/releases/1.76.0/
Replacing it with appending `Boost::headers` to `Boost_LIBRARIES` - this target is constructed both for Boost config and FindBoost and just references `Boost_INCLUDE_DIRS`, so it will automatically propagate to all `Boost_LIBRARIES` consumers. This is only needed for FindBoost users though.
1.14.0 fixes the use of deprecated `pgk_resources` warning (https://github.com/anaconda/anaconda-client/releases/tag/1.14.0). Though bumping to the latest version.
Warning:
```
/home/runner/micromamba/envs/test-env/lib/python3.11/site-packages/binstar_client/__init__.py:15: UserWarning:
pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources
package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
```
`IFCGEOM_SCHEMA_LIBRARIES` Is never set after we moved code to `src/ifcgeom/mapping` (b7a919925)
`SCHEMA_AGNOSTIC_H_FILES` is set but only inside `src/ifcgeom/mapping` and already handled there.
Route all polyline tools through a single PolylineOperator.cleanup that tears down the decorators, polyline, and raycast cache, and uninstall ProductDecorator unconditionally since it is idempotent.
Generated with the assistance of an AI coding tool.
- use draw handler to move the expensive synchronous GPU readback
out of the mouse-event loop.
- restore GPU state (depth, blend, face culling) around the offscreen