Commit Graph

21203 Commits

Author SHA1 Message Date
Andrej730 3955718145 express: update transpiled express rules using latest Python's AST
AST parser has changed a bit and there are some minor differences in the .py output. Updating files just to avoid seeing these diffs when rerunning rule compiler.
2026-07-14 18:42:12 +05:00
Andrej730 501246cd0b rule_compiler: fix error running on Python 3.14
Example error:
```
    ast.Str(s=node.attr),
    ^^^^^^^
AttributeError: module 'ast' has no attribute 'Str'
```

`ast.Str` was deprecated since 3.8 and was removed in 3.14, see https://docs.python.org/3/whatsnew/3.14.html#id9
2026-07-14 18:42:12 +05:00
Andrej730 1b863ff8be pyproject: add dev-setup poe task to setup environment for ide 2026-07-14 18:42:11 +05:00
Andrej730 dcd88b6cdd pyproject: Move tool deps from to requirements-tools.txt
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.
2026-07-14 18:42:05 +05:00
Andrej730 fb8b2ee878 bonsai pyproject: move pytest deps to requirements-dev.txt 2026-07-14 18:42:05 +05:00
Andrej730 8a00ce84cc geom/main.py: fix ty complaint 2026-07-14 14:56:06 +05:00
Andrej730 4032bbbd17 entity_instance.py: fix oveloads signatures (f93d79dc)
Without `/` overload implies that it also accepts kw args, while the implementation signature doesn't support them.
2026-07-14 14:56:06 +05:00
Andrej730 d728f09d86 Bump ty
Dropping `conflicting-argument-forms` rule as it was removed in ty 0.0.49.
2026-07-14 14:56:06 +05:00
Andrej730 3e9ef82448 maintenance.rst: move pyver matrix to bundled Python version section 2026-07-14 14:56:06 +05:00
Andrej730 a5c77fd096 ci-bonsai-daily: Use Blender 5.2 for tests 2026-07-14 14:56:06 +05:00
Andrej730 56ed79792e bonsai tests: fix test_failed_to_load_returns_only_base_keys (fdb2947) 2026-07-14 14:56:06 +05:00
Andrej730 9c91727402 express: drop Python 2 fallbacks 2026-07-14 14:56:06 +05:00
Andrej730 d183961280 pyproject: support formatting with ruff
Since it's black-compatible drop-in replacement and they can be used
almost interchangeably.
2026-07-14 14:56:06 +05:00
Andrej730 d30c25010c dev_environment.py: detect Python 3.13 on any Blender 5.1+ 2026-07-14 12:18:00 +05:00
Andrej730 97d1a6e488 dev_environment.py: add shebang and make executable 2026-07-14 12:12:54 +05:00
sboddy 6dec340161 Merge pull request #8576 from IfcOpenShell/fix/docker-macos-arm64-compat
docker: more robust in getting a GID, and editing the .env file.
2026-07-13 13:09:29 +01:00
Petru Conduraru 780739719f Bonsai docs: fix version switcher scheme mismatch (http vs https)
versionURLs in brand.html used http:// while the docs sites are
served over https://, so currentURL.includes(url) never matched and
the <select> never reflected/switched to Unstable. Fixes #8023.

Generated with the assistance of an AI coding tool.
bonsai-0.8.6-alpha2607131046 bonsai-0.8.6-alpha2607131042 bonsai-0.8.6-alpha2607131041
2026-07-13 20:44:19 +10:00
Petru Conduraru 4a717ca7ff Fix ci-bonsai-daily: reconnect Cost/IfcGit tool interfaces (TestImplementsTool)
Two TestImplementsTool failures on v0.8.0:

- test_cost.py: Cost could not be instantiated because
  core.tool.Cost declared abstract get_direct_cost_item_products, which
  tool.cost.Cost never implements. The method is dead (zero call sites;
  get_cost_item_products(is_deep=False) already covers the 'direct'
  case), so remove the abstract declaration.
- test_ifcgit.py: tool.ifcgit.IfcGit was not declared as a subclass of
  its core.tool.IfcGit interface (unlike every sibling tool class), so
  the isinstance check failed. Add the base class (and the
  bonsai.core.tool import it needs). All 50 interface methods are
  already implemented on the concrete class.

No behaviour change. Verified in headless Blender: isinstance(Cost(), core.tool.Cost) and isinstance(IfcGit(), core.tool.IfcGit) both True (were TypeError / False); repo abstract-vs-impl diff confirms all IfcGit abstracts are implemented.

This change was made with the assistance of an AI tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 20:43:25 +10:00
Petru Conduraru 5a831e3d21 Fix ci-lint: black-format selector.py
black (the version CI's psf/black@stable resolves to) flags three spots
in util/selector.py: the chained .replace() in FormatTransformer.number,
the suppress_zero_inches kwarg in format_length, and the long
`elif key in (...) and hasattr(...)` placement-key tuple in
set_element_value. Reformat all three to black's multi-line style.
Formatting only, no behavioural change (all keys preserved).

This change was made with the assistance of an AI tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 20:42:25 +10:00
Petru Conduraru d30286225c Bonsai: deterministic annotation order in generated drawing SVGs (#6608)
generate_annotation built the annotation list from a set union and sorted it by
ZIndex and TEXT-ness only. Annotations that tied on that key kept set iteration
order, which follows entity hash (step id plus the process memory address), so
the order of tied annotations (for example a label and its background fill)
shuffled between Blender restarts and flipped their draw order.

Add the stable IFC step id as a final tiebreaker so the order is total and
session independent. Behavior preserving, no z-layer semantics changed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 20:41:41 +10:00
Bruno Postle 65695fb878 ifcedit: fix Optional[entity_instance] coercion crash on native JSON values
coerce_value assumed value_str was always a CLI string, but ifcmcp
passes JSON-decoded native types (int, None) straight through. Guard
the Union/Optional "none" check so it only calls .lower() on strings,
and handle native None explicitly.
2026-07-13 08:55:43 +01:00
Bruno Postle ab15750747 ifcedit: include IfcSpace in default QTO element scope
IfcSpace is not a subtype of IfcElement, so quantify.run_quantify()'s
default selector silently skipped all spaces, reporting
elements_quantified: 0 with no error or warning.

Generated with the assistance of an AI coding tool.
2026-07-13 09:45:25 +01:00
Petru Conduraru 694a44e638 ifc4d: tolerate activities without a CalendarObjectId in P6 import (#5617)
Importing a Primavera P6 XML crashed with
`AttributeError: 'NoneType' object has no attribute 'text'` in
P62Ifc.parse_activity_xml, which read
activity.find("pr:CalendarObjectId").text unconditionally. CalendarObjectId
is optional on a P6 Activity; when omitted, the activity inherits the
project's ActivityDefaultCalendarObjectId.

Capture the project default in parse_xml and fall back to it when an
activity has no CalendarObjectId (`calendar_id or self.default_calendar_id`).

Verified on the reporter's attached file (20241021 Cronograma.xml): 3 of 14
activities lack a CalendarObjectId and reproduced the exact crash on
v0.8.0; after the fix parse_xml completes and those activities resolve to
the project default calendar "2" (a valid calendar in the file). An
activity with an explicit CalendarObjectId keeps its own value.

Fixes the P6 re-import crash reported in #5617 (that issue tracks several
Gantt items; this addresses the import AttributeError).

Generated with the assistance of an AI coding tool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bonsai-0.8.6-alpha2607130715
2026-07-13 17:15:36 +10:00
Petru Conduraru a3950ac191 util.element: read property sets inside an IfcPropertySetDefinitionSet (#6330)
get_pset and get_psets assumed RelatingPropertyDefinition is a single property
definition and read definition.Name directly. When it is an
IfcPropertySetDefinitionSet (a defined type wrapping a list of property set
definitions) that attribute access raised AttributeError, so an element whose
psets are grouped in a set returned none of them.

Unpack IfcPropertySetDefinitionSet into its members in both loops and process
each one. Single property definitions and the psets_only and qtos_only filters
are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bonsai-0.8.6-alpha2607130706
2026-07-13 17:06:18 +10:00
Petru Conduraru 6f90badda8 Fix ci-bonsai-daily: renumber stale STEP ids in BDD feature fixtures
Several BDD scenarios hardcode absolute representation-item object names
whose trailing number is the IFC STEP line id
(f"Item/{item.is_a()}/{item.id()}"). Those ids drift when file-creation
order changes; a recent shift moved all of them by a uniform -4, so the
scenarios failed with "Item/.../NN does not exist".

The failing step (the_object_name_exists in test_feature.py) dumps the
full bpy.data.objects listing on failure, so the correct current ids are
recoverable directly from the CI log (run 29208793599, tested commit
36e21e882f, an ancestor of HEAD with only a .gitignore commit between).
Renumber to match:
  IfcExtrudedAreaSolid/77->73, IfcPolygonalFaceSet/76->72,
  IfcVertexPoint/69->65, IfcEdge/72->68, IfcFace/74->70.

Verified against the CI failure dump (a local build produces different
ids, so this is validated by CI's own object listing rather than a local
run). boolean.feature also hardcodes IfcHalfSpaceSolid/90 and panel text
[91] downstream of the failing assertion, which CI never reached and so
never dumped; left as-is to avoid guessing - they will print a fresh dump
next run for a follow-up if still stale.

This change was made with the assistance of an AI tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bonsai-0.8.6-alpha2607130649
2026-07-13 16:49:33 +10:00
Petru Conduraru 8b05510d6c docker: fix GID collision and macOS sed portability
Two host-environment bugs in the build-env scripts that break on
macOS/Apple Silicon hosts, independent of target architecture:

- Dockerfile: groupadd fails outright when USER_GID collides with an
  existing system group in the rockylinux9 base image (e.g. macOS
  default user GID 20 "staff" collides with RHEL's GID 20 "games").
  Guard with getent so useradd attaches to the existing group instead.
- ifcos_env: `sed -si` is GNU-only syntax and errors under BSD/macOS
  sed. Do the UNIQUE_ID substitution via a portable temp-file + mv.

Per sboddy's review on the original PR: dropped the linux/amd64
platform-pin additions from this change. The stack already targets
Rocky9/x64 build outputs by design, and Docker Desktop on macOS has
no native container runtime regardless (it's a Linux VM either way),
so forcing the image to run under emulation doesn't produce anything
that's actually loadable into a native macOS Blender/Bonsai install.
That's a separate, harder problem worth solving via a native build
path instead (mirroring build_osx.yml), not by fighting emulation
here. These two fixes stand on their own merits on any host.

This change was made with the assistance of an AI tool.
2026-07-13 09:43:57 +03:00
Stephen Boddy b1470223d3 Share ccache volume across checkouts, cap at 2G
The ccache named volume had no explicit name, so Docker Compose
namespaced it under the per-checkout project name (derived from
UNIQUE_ID), giving each checkout its own cache even though
docker/README.md already documented them as shared. Give the volume
a fixed name so all checkouts attach the same one.

Measured cache size after a full build (IfcParse+IfcGeom+IfcConvert+
wrapper, one Python version) is ~300MB, only ~5% of the previous 5G
cap. Shrink CCACHE_MAXSIZE to 2G, which comfortably covers the shared
baseline plus per-branch deltas from several diverging checkouts.

Generated with the assistance of an AI coding tool.
2026-07-13 06:42:05 +01:00
Petru Conduraru f25b072fa0 docker: make the build env work on macOS / Apple Silicon hosts
Three host-portability fixes to the docker/ toolchain from #8564 so it
runs on macOS as well as Linux. All three are no-ops on native amd64
Linux.

1. Dockerfile: only groupadd when the target GID is free. macOS's default
   primary group `staff` is GID 20, which already exists as `games` in
   rockylinux:9, so `groupadd -g 20` aborted the image build. Guard with
   `getent group "${USER_GID}" || groupadd ...`; useradd -g accepts the
   existing GID.

2. ifcos_env unique(): replace GNU-only `sed -si` (BSD/macOS sed errors
   "illegal option -- s") with a portable `sed > tmp && mv` rewrite of the
   UNIQUE_ID line. Verified against macOS BSD sed.

3. create() + compose.yaml: build with an explicit `--platform linux/amd64`
   so the locally built image's platform matches the `platform:
   linux/amd64` pin in compose.yaml. Without it, on arm64 the local image
   is tagged linux/arm64, compose treats the platform-mismatched image as
   absent and tries to pull `ifcopenshell-build-env:updated` from Docker
   Hub (which does not exist -> access denied). Also add `pull_policy:
   never` as a safety net so a future mismatch surfaces as a clear "image
   not found" rather than a registry auth error.

Note: on Apple Silicon the amd64 build runs under emulation and a cold
full build is slow; ccache makes incremental rebuilds tolerable. A native
Linux/Intel host or CI remains the better choice for routine use, but these
fixes turn "hard broken" into "works with a caveat" on macOS.

This change was made with the assistance of an AI tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 06:44:55 +03:00
sboddy ffb867f254 Add .gitignore entries for docker build env (#8569) 2026-07-12 22:23:42 +01:00
sboddy 36e21e882f Merge pull request #8568 from sboddy/fix-autosave-recovery-segfault
Fix segfault on autosave recovery dialog Cancel
bonsai-0.8.6-alpha2607122100
2026-07-12 22:00:06 +01:00
Stephen Boddy d0eca6fa90 Fix segfault closing autosave recovery dialog
Reported: Blender segfaults when clicking Cancel on the "newer
autosave found" recovery popup shown by LoadProject at startup.

Root cause: LoadProject.execute()/invoke() triggered the recovery
popup via bpy.ops.bim.load_autosaved_recovery_popup("INVOKE_DEFAULT",
...) and returned that call's result ({'RUNNING_MODAL'}) as their own
return value, without LoadProject itself ever calling
modal_handler_add(). Blender's window manager takes a RUNNING_MODAL
return as a promise the operator registered its own modal handler;
since it hadn't, the WM's operator bookkeeping was left corrupted -
silently, since this is heap/state corruption rather than an
immediate crash. It only surfaced later, when the real modal operator
(the popup) closed and the WM reconciled its modal stack, which lines
up with the crash occurring specifically on dialog close regardless
of which button was pressed. check_autosave_recovery() now returns a
plain bool and fires the popup fire-and-forget; LoadProject reports
its own honest {"FINISHED"}.

Also hardened, as defense in depth: LoadAutosavedRecoveryPopup's
execute()/cancel() call back into bim.load_project(...), which (with
should_start_fresh_session) calls wm.read_homefile() and tears down
the window manager/screens. Doing that synchronously from inside this
popup's own execute()/cancel() - itself invoked from deep inside
Blender's modal handling for the popup's button click - risks the
same class of use-after-free as the timer bug fixed in the previous
commit. The reload is now deferred by one timer tick so it runs after
the popup's modal handling has fully unwound, and the deferred
callback closes over plain values rather than `self`, since the
operator instance may not survive past cancel()/execute() returning.

This defer-only change was tried and tested first, on the (incorrect)
assumption it was the root cause: it produced a byte-for-byte
identical crash backtrace on retest, which is what pointed at the
RUNNING_MODAL bug above as the actual cause - the defer change alone
was insufficient because the corruption happens when the popup is
first shown, not when it's closed.

Generated with the assistance of an AI coding tool.
2026-07-12 21:52:09 +01:00
Stephen Boddy 6306ce0f80 Fix autosave timer self-unregister crash risk
The periodic autosave timer called reset_timer() at the end of its
own callback, which unregistered the timer that was still executing
(itself). Blender frees the timer's internal registry entry on that
manual unregister, then frees it again when the callback returns
None - a double free that corrupts the heap and can crash Blender
later, once the corrupted memory is reused.

Reschedule by returning the next interval from the callback instead,
which is the safe, documented way to repeat a bpy.app.timers
callback. External reset_timer() calls (from SaveProject,
LoadProject, AutosavePrompt) are unaffected since they run from a
separate call stack (UI events), not from inside the timer.

Found while investigating a segfault reported when cancelling the
autosave recovery popup; not itself the cause of that crash (see the
following commit), but the same reentrant-unregister pattern and a
real, independent latent bug in the periodic reminder path.

Generated with the assistance of an AI coding tool.
2026-07-12 21:51:56 +01:00
sboddy 53187ddae9 Merge pull request #8564 from sboddy/docker-build-env-tooling
Add a local docker build environment for IfcOpenShell (docker/)

See PR #8564 for full explanation.
2026-07-12 20:50:53 +01:00
Stephen Boddy 92c50ed3b4 Harden docker build tooling: non-root, clean lifecycle, try()
Dockerfile (renamed from Dockerfile_init, Dockerfile_update removed):
- Run as a non-root `builder` user matching the host UID/GID (passed as
  --build-arg by create() from id -u/id -g), so build output under the
  bind mount stays owned by the host user instead of root.
- Fix CCACHE_MAXSIZE: `ccache -M 5G` wrote its limit to a config file
  under /ccache at image-build time, but /ccache is a volume mount
  point, so that file gets shadowed by the (empty) volume the moment
  the container actually runs - the cap never took effect. Set
  CCACHE_MAXSIZE=5G as an image ENV instead.
- Dedupe ccache/libffi-devel, add --setopt=install_weak_deps=False
  --setopt=tsflags=nodocs, add `git lfs install --system`, combine the
  dnf update+install into one layer.
- Drop Dockerfile_update: it built FROM its own previous output, so
  every `update` call made the image strictly larger forever (Docker
  layers are append-only, `dnf clean` in a later layer can't shrink an
  earlier one). `update` now just calls create(), which already runs
  `dnf update -y` FROM a clean rockylinux:9 every time.

compose.yaml: pin platform: linux/amd64 so this doesn't silently run
under emulation on an ARM host.

ifcos_env:
- Split the previously-conflated stop/down into six distinct,
  Compose-native lifecycle commands: up (create-or-start), down
  (remove), stop, start, restart (stop+start, same container),
  recreate (down+up, fresh container). Previously `stop` was aliased
  to `down`, which silently removed the container instead of pausing
  it.
- Implement try(): copies the built wrapper into a real Blender/Bonsai
  install for manual testing, reading the target from a new
  BLENDER_USER_RESOURCE .env variable and auto-detecting the built
  Python version (disambiguating via PY_TGT for multi-version builds).
  Deliberately kept human-only - it mutates a live Blender install, so
  it shouldn't run unattended as part of an automated/AI workflow,
  which should instead copy the wrapper into the repo's own
  src/ifcopenshell-python/ifcopenshell/ (documented in SKILL.md).
- Fix unique(): the "has .env already got a UNIQUE_ID line" check
  referenced an unset $FILE instead of $ENV_FILE, so it always
  evaluated true and appended a fresh "UNIQUE_ID=dummy" line to .env
  on every single `up`.
- Minor: differentiate remove()'s log message from down()'s (no longer
  identical now that they're distinct operations), tidy help text
  alignment and a stray double-space typo in clean().

SKILL.md: rewritten as current-state documentation (no more "fixed in
this copy" changelog framing) covering the above, plus a migration
note for anyone hitting root-owned leftovers from an older image.

Verified by actually building the image and driving every new
lifecycle command (stop/start/restart keep the same container ID;
down+up and recreate produce a new one) and try() (including the
quoted-tilde BLENDER_USER_RESOURCE edge case) against the real container.

Generated with the assistance of an AI coding tool.
2026-07-12 20:35:01 +01:00
Stephen Boddy fa98aad469 First docker build environment
First functional version, but it needs some improvements and fixes
identified as I've used it personally on one thing, and when an AI
(Claude) used it to work through the CI test errors.

I had the AI make a SKILL.md file. If the AI indicates it needs to
build the ifcopenshell binary, use this and let it rip.
2026-07-12 18:43:27 +01:00
Petru Conduraru 980988f208 Bonsai: fix KeyError in format_distance for kilometre and mile units #8255
The project-unit to Blender-unit mapping in format_distance only knew
FOOT/INCH/METRE/DECIMETRE/CENTIMETRE/MILLIMETRE, so creating a project
with Kilometers or Miles in the New Project Wizard crashed with
KeyError: 'KILOMETRE' (or 'MILE') as soon as the spatial tree formatted
an elevation. Add the missing Blender-supported units (kilometre, mile,
micrometre) and fall through gracefully for anything else (for example
HECTOMETRE) so unknown units use the adaptive formatting branch instead
of raising.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bonsai-0.8.6-alpha2607121251 bonsai-0.8.6-alpha2607121249
2026-07-12 22:51:37 +10:00
Petru Conduraru d4805387ef Selector: add rotation_x/y/z value keys #6262
Expose the Euler rotation of an element's placement in degrees through
get_element_value, alongside the existing x/y/z and easting/northing/
elevation keys. This makes element rotation exportable through ifccsv,
e.g. for placing oriented symbols in GIS.

Adopts the approach agreed in the review of the stale PR #6272 by
@TZwielehner: reuse util.shape_builder.np_matrix_to_euler and do the
degree conversion inside get_element_value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 22:49:43 +10:00
Petru Conduraru 69a4be68e8 Bonsai: fall back to adaptive units for unsupported SI prefixes #8074
Project loading set scene length_unit to f"{Prefix}METERS", but Blender's
enum only defines KILOMETERS, CENTIMETERS, MILLIMETERS and MICROMETERS.
A model with a DECIMETRE (or HECTO/DECA/etc.) length unit therefore raised
on the enum assignment and the file failed to open. Guard with the set of
supported values and fall back to ADAPTIVE display for the rest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bonsai-0.8.6-alpha2607121240 bonsai-0.8.6-alpha2607121237 bonsai-0.8.6-alpha2607121236
2026-07-12 22:38:10 +10:00
Petru Conduraru 06da416b8f docs: remove TODO placeholder sections from the create-model quickstart #8208
The quickstart ended with three empty sections whose bodies were only
"TODO" (placing occurrences, changing locations, modeling a building),
which read as a dead end on docs.bonsaibim.org. The page now ends on the
completed save-and-view flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 22:37:34 +10:00
Petru Conduraru 21ae78fbc2 resource.assign_resource: fix typo in duplicate guard #8203
The guard that avoids re-assigning the same object to the same resource
tested is_a("IfclRelAssignsToResource") (stray "l"), so it never matched.
A repeat assignment therefore fell through and appended the related object
to RelatedObjects a second time. Corrected to "IfcRelAssignsToResource".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:36:26 +10:00
Petru Conduraru 0a8ae14789 fix(ifcdiff): check attributes by default so PredefinedType changes are caught (#8214)
IfcDiff defaulted to relationships=["geometry"], so a plain diff only ever
compared geometry. Attribute-only edits on an element that kept its GlobalId
(a modified or removed PredefinedType, a renamed element, etc.) were silently
missed. The CLI made this worse: --relationships did not list "attributes" or
"geometry" as valid values, so there was no documented way to enable it.

The default is now ["attributes", "geometry"], so a plain `ifcdiff old new`
reports attribute changes alongside geometry changes. The CLI help and the
IfcDiff docstring now document all valid relationship values.

Added a regression test covering a PredefinedType change detected with the
default configuration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bonsai-0.8.6-alpha2607121229 bonsai-0.8.6-alpha2607121228
2026-07-12 22:29:31 +10:00
Petru Conduraru 2eea7728d2 fix(selector): round() should not crash on non-numeric values (#6776)
FormatTransformer.round() called Decimal() directly on the input value,
which raises decimal.InvalidOperation when the value is a non-numeric
string (a text property, or a value carrying a unit suffix like "12.5 m").
In a spreadsheet export this crashed the entire operation as soon as one
element carried such a value.

Now round() catches InvalidOperation and returns the value unchanged, the
same graceful-fallback convention used by add(). Numeric rounding is
unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 22:28:38 +10:00
Petru Conduraru 6b3cc54afc ifcfm: convert COBie Coordinate space points to project units (#5926)
In the cobie24 Coordinate sheet, Floor rows use get_local_placement, whose values
are in the project length unit, but Space rows come from ifcopenshell.geom
create_shape, whose vertices are in SI metres, and the space branch never scaled
them back. So on a non metre model (for example millimetres) the Coordinate sheet
mixed units a thousandfold apart and disagreed with the Facility sheet's declared
LinearUnits.

Scale the space bounding box by the project unit scale so the whole Coordinate
sheet is consistent. A metre model is unchanged since the scale is 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:27:07 +10:00
Petru Conduraru 5c11946470 Support block comments in selector filter syntax (#5023)
The filter_elements selector grammar had no way to comment out part of a
query, so users had to delete and retype text to temporarily toggle a
facet. Add a /* ... */ block comment terminal that is ignored by the
lexer, and tolerate a trailing "+" so that commenting out the final
operand (e.g. "IfcWall + /* IfcSlab */") parses cleanly. Comments may
span multiple lines; a /* sequence inside a quoted string is not treated
as a comment. Only the filter grammar is affected, not get_element or
format which use "/" for regex and division.

Adds a regression test and documents the syntax.

Generated with the assistance of an AI coding tool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bonsai-0.8.6-alpha2607121223
2026-07-12 22:23:30 +10:00
Ryan Schultz 0b7e25a3ef Docs: clarify immediate vs. any-depth spatial selectors
The location and parent filters both match at any depth in the spatial
hierarchy, which surprises users who want only the elements immediately
under a given container. Document that the parent query key resolves the
direct parent only (e.g. query:"parent.Name"="My Site"), add a matching
filter example, and note the immediacy on the parent value key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:44:22 -05:00
Ryan Schultz d16c283aef Add bulk-load of selected drawings' annotations (#8525)
SHIFT+CTRL+CLICK on Activate Drawing now imports the
annotations of all selected drawings without switching
the active view or camera, then selects their cameras with
the first as active. SHIFT+CTRL+ALT+CLICK also selects the
loaded annotation objects. The drawing camera is imported
when missing so annotations land in the correct collection.
Loading is idempotent.

Generated with the assistance of an AI coding tool.
bonsai-0.8.6-alpha2607112054
2026-07-11 15:54:20 -05:00
Petru Conduraru a0f493b471 IfcConvert: report an error when the output file cannot be opened (#438)
Converting to a path whose directory does not exist (or is not writable)
failed silently: the serializer's ready() check correctly returned false,
but IfcConvert deleted the temp file and returned EXIT_FAILURE without any
message, so the user saw no reason for the failure.

Log a SYS error naming the output file before returning, matching the
existing "Unable to open output file" reporting used elsewhere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 15:28:51 +02:00
Petru Conduraru e389939092 serializers: expand IfcPropertySetDefinitionSet in XML output (#6330)
Property sets contained in an IfcPropertySetDefinitionSet were exported as
an empty element in XML. The XmlSerializer already had a block to expand
such a set into its member property sets, but it was gated behind
#ifdef SCHEMAS_HAS_IfcPropertySetDefinitionSet while the schema generator
emits SCHEMA_HAS_IfcPropertySetDefinitionSet (singular). The plural spelling
is defined nowhere, so the block was dead code and a RelatingPropertyDefinition
holding a set produced nothing.

Correct the macro name so the set is expanded and its property sets are
serialized. The parse layer already reads these nested sets (they are
reachable from util.element), so this only completes the XML path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 15:26:49 +02:00
Petru Conduraru 380675e214 ifcparse: strip XML-illegal control characters in escape_xml (#2043, #3074)
escape_xml escaped the five XML metacharacters but passed control
characters (0x00 to 0x1F other than tab, newline and carriage return)
through unchanged. Those bytes are illegal in XML 1.0 and cannot be
represented even as numeric character references, so any IFC string
containing them produced non-well-formed XML and SVG output.

Strip those illegal control characters before escaping. Bytes belonging to
a valid UTF-8 multibyte sequence are always >= 0x80, so filtering on the low
control range leaves real text intact. This is the shared helper used by the
SVG serializer text and attribute sites (audited: all route through it) and
by the XML/Collada paths, so both reports are resolved at one place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 15:20:47 +02:00
Petru Conduraru 3e55c5126c ifcgeom: honour PnIndex in triangulated and polygonal face sets (#3434)
IfcTriangulatedFaceSet and IfcPolygonalFaceSet used CoordIndex values to
index Coordinates.CoordList directly, ignoring the optional PnIndex
attribute. When PnIndex is present it remaps point references, so a
CoordIndex value i must resolve as CoordList[PnIndex[i-1]-1] (both 1-based).
Without the indirection any model carrying a PnIndex was built from the wrong
points.

Add a resolve() helper in both mappings that applies the PnIndex indirection
when present and is a plain bounds-checked lookup otherwise, with bounds
checks at both index levels. When PnIndex is absent the behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 15:19:06 +02:00