An IfcSurfaceStyle with a NULL Styles set raised a TypeError when
loading a project. Guard the iterations, matching the existing
defensive pattern in import_presentation_styles.
Generated with the assistance of an AI coding tool.
See the comment, if there was couple whitespaces at the end of the file without newline, it would take running `check-whitespace` twice to finally fix it.
Apparently `ty` is being too strict here and warning about `Any` possibly being `PathLike` which is not supported on older Pythons.
```
error[deprecated]: The overload of `which` is deprecated
--> src\bonsai\bonsai\bim\module\drawing\operator.py:2267:34
|
2267 | command[0] = shutil.which(command[0]) or command[0]
| ^^^^^^^^^^^^ On Windows before Python 3.12, using a PathLike as `cmd` would always fail or return `None`.
error[deprecated]: The overload of `which` is deprecated
--> src\bonsai\bonsai\tool\drawing.py:1324:30
|
1324 | command[0] = shutil.which(command[0]) or command[0]
| ^^^^^^^^^^^^ On Windows before Python 3.12, using a PathLike as `cmd` would always fail or return `None`.
```
When fixing warnings, noticed that was working a bit inproperly - `bnode` always end up being an empty list (because there's no `brick, A, REF.IFCReference` triple), so then passing empty list to `triples` resulted in selecting all nodes isntead of just the expected `bnode` (that's the behaviour `sqlachemy` was sending the warnings about - when empty lists unexpectedly selected everything).
`None, None` assumed by default.
Though this was introduced in Python 3.13, before 3.13 it only breaks if we'd do `typing.Generator[T]` (which is deprecated) -`collections.abc.Generator[T]` works fine, it seems it never had an arity check.
Follow-up to cb08191. The comma-separated pset value was resolved by
tool.Ifc.resolve_uri() as a single path before add_stylesheet() split it,
so normpath collapsed the whole string down to the last entry.
Split the value into paths first, then resolve each one, and concatenate
the stylesheets in listed order into a single <style> element.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
get_special_type_for_prop() reached the declaration through
entity.wrapped_data, which v0.9.0 removed when entity instances started
inheriting from the wrapper type directly. Ten ci-bonsai-daily tests
fail on it (quantification, pset editing, templated quantities). Use the
declaration property the way entity_instance.is_entity() itself does.
Bonsai's Pset/Qto editor could display a property or quantity's own Unit
override, but had no UI to author one -- only the project-level Project
Units panel existed, which sets defaults, not per-instance overrides.
Builds on the edit_pset/edit_qto Unit-wrapping support and the
get_unit_scale/get_candidate_units helpers added in the previous commit.
- bim/prop.py: Attribute gains unit_id (the STEP id of the property's own
override, 0 = project default) and unit_id_enum (the dropdown-driving
dynamic enum, "Default (<symbol>)" plus every candidate unit for the
attribute's measure type). update_attribute_unit_id converts the stored
value live when a different unit is picked, so the physical quantity is
preserved rather than the number being silently relabeled.
- tool/pset.py: is_measurable_special_type/get_candidate_units_for_special_type/
resolve_effective_unit/convert_attribute_unit support the picker and the
live conversion. get_special_type_for_prop classifies a property by its
value's own declared measure type, falling back to an explicitly-attached
Unit for generic numeric types (e.g. IfcReal) whose spec carries no unit
semantics of its own but which may still legitimately carry one. Seeding
in import_pset_from_existing ignores a stray Unit attached to a property
whose value has no numeric/measure semantics at all (e.g. text), which
used to crash trying to select an identifier the picker's enum items
never include.
- bim/module/pset/ui.py: the picker widget itself, next to the value field
in edit mode, gated on the attribute being measurable.
- bim/module/pset/operator.py: EditPset wraps measurable values with their
chosen Unit on save, for both properties and quantities. The qto
rounding-loop fix reaches into the wrapped dict instead of assuming a
bare float/int, which would otherwise zero out every unit-overridden
quantity.
Adds regression tests across all of the above, including conversion
correctness, explicit-clear/default round-trips, an unrelated sibling
property's override surviving untouched, and the stray-Unit crash guard.
Previously, unit symbols only appeared while a Pset/Qto was in edit mode
(pencil icon) -- the read-only summary view read raw {name: value} dicts
straight from ifcopenshell.util.element.get_psets(), a completely separate
path from the Attribute/unit_symbol machinery, so it never showed a label
even after the earlier fixes. This matters for the "someone in the field
just looking at values" use case, not just editing.
- bim/module/pset/data.py: switch to get_psets(verbose=True) to get each
property's own entity id, then resolve its unit symbol the same
override-aware way the edit-mode path does (tool.Pset.get_unit_symbol_for_prop).
Falls back gracefully (empty symbol) for IfcPreDefinedPropertySet
attributes, which aren't IfcProperty entities and can't carry a Unit
override.
- bim/module/pset/ui.py: read-only value button now shows "250 mm" instead
of just "250".
Also adds the regression tests planned but not yet committed:
- test/tool/test_pset.py: edit a property with its own Unit override and
write it back, confirming no rescale and the override survives.
- test/bim/test_prop.py (new): get_display_name() falls back to the plain
name (no crash) when no unit is resolvable or the project has no units
assigned at all.
The imperial list ran the architectural scales from 1'=1'-0" down to
1/128"=1'-0", then restarted at 1"=10' for the engineering scales. Merge
both groups into a single sequence ordered by ratio, largest scale first.
The metric list was already ordered by ratio and is unchanged.
Also fix the enum cache invalidation, which compared the cached list's
length against hardcoded 13/31 while the imperial list has 32 entries, so
switching a scene from imperial back to metric kept showing imperial
scales. Track the unit system the cache was built for instead.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
First v0.9.0alpha0 binary set, so the version prefix moves with it.
The bump trackers had drifted (bonsai's OLD pointed at 3e7b739 while
ifcopenshell-python pinned e333c1c), so this was done by hand;
'make bump' works again from here.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Upstream binary builds no longer produce macos64 zips (build_osx builds
arm64 only since wgpu Qt), and Blender dropped Intel Mac support in 5.0,
so there is nothing left to package for that platform.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Because vert[0] can change based on kernel output, we now assert that 1)
origins are on a vert, any vert, and 2) both blender coords and map
coords are what we expect. I manually visually verified all tests
against Blender 5.1 + stable 0.8.5 to check that actual behaviour hasn't
changed, only tests need updating.
Assigning a material to an occurrence with a set material type has raised
"IfcMaterial cannot be assiged as a IfcMaterialLayerSetUsage" since the
default changed to assigning usages to occurrences. The type is upgraded to a
usage but the material is passed on unchanged, and material.assign_material
only accepts a material for a usage when that material is already the set,
whereas the Object Materials dropdown gives us a plain IfcMaterial. Pass
nothing in that case and let the API make the set, as it does when asked for
a usage with no material.
Look the set up past the usage afterwards, so the material the user picked is
added to it. get_material returns the usage, which is not a material set, so
neither branch of the repair below matched and the picked material was
dropped, leaving the set empty.
This is a stopgap and is commented as such: the real problem is that
assign_material builds sets with no items in them and ignores the material it
was given, which is not valid IFC and leaves callers patching up after it.
Also register "I evaluate expression" as a Then step. It has only ever been a
Given and a When, so the last line of the scenario covering this could never
run; it is the only Then of its kind in the suite.
test/bim goes from 16 failures to 15, with none introduced.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Most of them are actually correct, but they're not enforced in general on the repo, so using them blocks us from flagging `unused-noqa` for rules that we actually do use.
Removing translate_obj_to_z_location from the existing-IfcSpace
regeneration branch. The ShapeBuilder rewrite (d8de62308) builds
geometry in local space preserving obj.matrix_world, making the
translate call redundant — it adds z on top of the already-correct
location.z, producing 2*z.
Add test_regenerate_space_preserves_z_location to cover the
regeneration path with a non-zero Z elevation.
Generated with the assistance of an AI coding tool.
This branch carried v0.8.0's strict `[tool.ty.rules] all = "error"` config but
not the source fixes that were made upstream to satisfy it, so both ci-lint ty
gates were failing: `poe ty-ios` reported 256 diagnostics and `poe ty-bonsai`
258. Both are now clean.
Most fixes are ported from v0.8.0 and follow two idioms: initialise a name
before a conditional that may not bind it (plus an `assert` where the invariant
is real but not provable), and close an exhaustive `if`/`elif` chain with
`else: assert False, <discriminant>`.
The branch's own newer accessors are preserved throughout - `.file`,
`.declaration`, `file.types()`, `get_max_id()` are kept rather than reverted to
`wrapped_data.*`, and non-ty upstream changes (notably the in-progress geometry
cache removal) are deliberately not pulled in.
Notable fixes that are not straight ports:
* ifcopenshell_wrapper.pyi: `entity_instance.file` was declared as
`def file(self) -> file`, where the property name shadows the `class file`
below it, so the annotation resolved to `Unknown`. Every `element.file` in
the codebase was therefore unchecked. Qualifying it to `ifcopenshell.file`
restores `.schema` to its Literal union and surfaces no new diagnostics.
* model/wall.py: a duplicated merge fragment in the void-straddle path ran an
always-true `if void_straddles:` that read `new_opening` from the mutually
exclusive branch (stale value, or NameError on the first iteration), followed
by an unreachable duplicate `elif`. Removing it makes the file match v0.8.0.
* light/operator.py: upstream's own fix unpacks three targets from two values
and raises ValueError unconditionally; corrected to `None, None, None`.
* assign_system.py, validate.py, geom/main.py: walrus-in-genexp is valid at
runtime (PEP 572 binds in the containing scope) but ty does not model it;
rewritten as explicit loops, matching upstream.
Verified: poe ty-ios, poe ty-bonsai, ruff check src/ nix/, black --check .,
and compileall -W error at py3.10 (ifcopenshell-python) and py3.11 (bonsai).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>