Cached SnapObj entries holding references to freed Blender
objects raised ReferenceError on .obj.name access. Evict
stale entries at the start of create_snap_obj.
Generated with the assistance of an AI coding tool.
EditAssignedMaterial propagated layer set usage attributes
to all selected objects but skipped this loop for profile
set usage. Add the same loop so CardinalPoint and
ReferenceExtent are copied to each selected object's
IfcMaterialProfileSetUsage on save.
Generated with the assistance of an AI coding tool.
When the user enters `-0' - 10"`, Python parses feet as -0.0.
The check `feet < 0` is False for negative zero, so the sign was
silently dropped. Use math.copysign to detect it correctly.
Generated with the assistance of an AI coding tool.
Two paired test files for the framework infrastructure landed
earlier in this PR.
test_decorator_cache.py (11 tests):
* The 4-hook invalidation list (depsgraph_update_post + undo_post +
redo_post + load_post) is symmetrically managed by
install_decorator_cache_handlers / uninstall_decorator_cache_handlers.
A future edit that drops a hook from one side without the other
would land as a Blender segfault when a cached bpy.types.Object
ref outlives its underlying ID block — the regression must surface
as a test failure first.
* install is idempotent (calling twice doesn't double-register).
* uninstall when not installed doesn't raise.
* The bump handler accepts Blender's variadic args.
* The depsgraph predicate gates correctly: bumps on Object geometry
or transform updates, silently skips on Material / NodeTree / Image
updates (which would otherwise rebuild every cache on every node
edit).
* TokenCache.get_or_compute short-circuits on key+token match and
recomputes when the token bumps.
test_undo_resync_parametric_drafts.py (3 tests):
* UNDO_REGENERATORS keys must all be in tool.Parametric.EDIT_TYPES.
A typo would silently no-op on Ctrl+Z, restoring the desync the
helper is meant to prevent.
* The dispatcher skips objects with no active parametric edit
(undo_post fires for every undo, most of which touch zero drafts).
* The dispatcher silently skips parametric types that have no
UNDO_REGENERATORS entry (door / window / array are IFC-derived
with no draft preview mesh — they don't need a regenerator).
Mocks use spec=bpy.types.Depsgraph / spec=bpy.types.DepsgraphUpdate
/ spec=tool.parametric.ParametricObject so typos in mocked-attribute
access fail loudly (CLAUDE.md test discipline).
Generated with the assistance of an AI coding tool.
Splitting a wall through a door orphaned the door (door.FillsVoids
became empty). The fill rel was being reassigned by setting its
RelatedBuildingElement slot — schema-wise that's the filling slot, not
the wall slot — so when remove_feature deleted the old opening it
also cascade-removed the rel. Transferring via RelatingOpeningElement
keeps the rel pointing at the new opening so the door stays
associated. Pre-existing bug from 5a6476a57, surfaced by ef144dce2.
Generated with the assistance of an AI coding tool.
DumbWallJoiner.split assigned openings by projecting the opening's
centre-point onto the wall axis, so any opening whose footprint
straddled the cut was silently dropped from whichever wall its centre
missed. Now the full axis-projected extent (via ifcopenshell.geom.
create_shape) drives the assignment; for filled openings whose void
straddles the cut, a pure-void copy is added back to the neighbour
wall so its body is also cut.
Generated with the assistance of an AI coding tool.
Five inline copies of the same defensive pattern lived across
``tool/parametric.py``, ``bim/parametric_lifecycle.py``,
``bim/module/model/preview_base.py`` (twice), and as a near-twin
in ``tool/blender.py:update_all_viewports`` itself.
``tool.Blender.update_all_viewports`` already covered the
``tag_redraw`` job but used an ``assert context.screen`` that would
raise during background-mode operators or early-load_post calls
where ``screen`` legitimately is None. Relax to a defensive
``getattr(context, "screen", None)`` + silent return so the helper
fits every caller's needs, then collapse the 4 inline copies to
single calls.
Net -9 LOC. The helper now describes its contract ("silent no-op
when no screen attached") rather than naming specific callers, so
moving a caller doesn't rot the docstring.
Generated with the assistance of an AI coding tool.
Ctrl+Z / Ctrl+Shift+Z on an in-progress parametric draft (wall /
stair / roof) used to leave the preview mesh frozen in its
pre-undo shape — the IFC mutation rolls back but the bmesh built
from draft props doesn't repaint.
Add a registry of per-type regenerator functions
(``UNDO_REGENERATORS``) that re-build each type's preview mesh
from its current props. The dispatcher
``resync_parametric_drafts_after_undo`` walks all objects, skips
any without an active parametric edit, looks up the regenerator
by feature name, and calls it. Tagged 3D viewports for redraw.
Types without an entry (door / window / railing / etc.) are
intentionally absent — they're IFC-derived, so the undo's
representation rollback + next-frame refresh already repaints
correctly without a draft-side regenerator.
Undo/redo wiring is self-installed by
``bonsai.bim.parametric_lifecycle``: a ``@persistent``
``_resync_on_undo`` callback dispatches into the registry, and
``install_parametric_lifecycle_handlers()`` /
``uninstall_parametric_lifecycle_handlers()`` append/remove it
from ``bpy.app.handlers.undo_post`` and ``redo_post``.
``bim/__init__.py``'s ``register()`` calls the install function
*after* the central ``handler.undo_post`` / ``redo_post`` appends
so the regenerators see restored IFC state — ``bpy.app.handlers``
fire in append order. ``handler.py`` itself stays ignorant of the
parametric subsystem. The lazy function-local imports in each
regenerator break the addon-load cycle —
``bonsai.bim.parametric_lifecycle`` loads before
``bim/module/model/*``.
Generated with the assistance of an AI coding tool.
Three changes to the shared Enable/Finish/Cancel mixins:
1. Always-on drift triad on ParametricEditMixinBase. The base now
provides ``_handle_drift_on_enable`` / ``_handle_drift_on_finish``
/ ``_handle_drift_on_cancel`` classmethods, called from the
per-mixin ``_enable_one`` / ``_finish_one`` / ``_cancel_one``.
Pre-edit Blender-side translations commit to IFC on Enable
(apply_scale=False — only translation/rotation, not the user's
accidental scale), in-edit drag commits on Finish (apply_scale=True),
and Cancel restores the committed IFC placement via
``restore_or_rebaseline_placement``. Prevents the
"uncommitted drag disappears on Finish" and "preview snaps back
on Cancel" UX bugs.
2. ``_ParametricEditMixinBase`` renamed to ``ParametricEditMixinBase``
(public). Per-feature mixins that need to subclass directly
(e.g., when neither FeatureModifier nor PathPreserving fits)
can do so without reaching into a private name.
3. ``_update_modifier_bmesh`` (PathPreserving) renamed to
``_restore_viewport_after_cancel``. The old name was inaccurate
for subclasses that load a different IFC representation on
Cancel rather than rebuilding a bmesh preview from props.
Plus two polish changes:
* ``_mark_type_thumbnail_dirty`` helper on the base centralises the
``ifcopenshell.util.element.get_type`` + thumbnail-mark pattern
that both mixins repeated inline.
* ``FeatureModifierEditMixin._cancel_one`` and
``PathPreservingEditMixin._cancel_one`` wrap the restore in
``try/finally`` so ``props.is_editing = False`` flips even on
partial restore failure. Without this, a Cancel that raised
mid-restore would leave the user locked out of the edit lifecycle.
* ``PathPreservingEditMixin._finish_one`` / ``_cancel_one`` skip the
pset commit + viewport rebuild when the draft equals the stored
pset (no-op Enable→Finish round-trip should not pollute the
representation list or burn an undo entry).
``FeatureModifierEditMixin._finish_one`` now routes the pset commit
through ``tool.Pset.write_bbim_data`` instead of inlining the
``createIfcText(json.dumps(...))`` + ``ifcopenshell.api.pset.edit_pset``
dance. Two test assertions updated to match.
Generated with the assistance of an AI coding tool.
Three concerns folded into ``load_post`` argue for separation:
1. Save-file invariants every load must re-establish (msgbus
subscription, owner-settings, thumbnail cache, draft-flag healing,
blend-warning flag, H5 lock probe).
2. User-preference-driven UI setup (toolbar, workspace, viewport
shading, panel hijack, snap defaults).
3. Viewport overlay sync (every decorator's install/uninstall).
Pull each into its own function (``_apply_save_file_invariants`` /
``_apply_user_preferences`` / ``_install_viewport_overlays``). The
``load_post`` callback becomes a 3-line orchestrator. Each phase
is independently call-able from tests and from PR4 features that
need to re-trigger one phase without the others.
Two new hooks land with the decompose:
* ``tool.Parametric.heal_stale_edit_flags()`` + ``discard_pending_previews(scene)``
fire in ``_apply_save_file_invariants``. The first clears
object-level ``BIM<Name>Properties.is_editing`` flags that lost
their backing IFC element across a load; the second clears
scene-level ``BIMPreviewProperties.<x>.is_active`` so saved
preview state never resurfaces with no UI to interact with it.
* ``install_decorator_cache_handlers`` / ``uninstall_decorator_cache_handlers``
wrap the decorator install/install pass in
``_install_viewport_overlays``. The bump handlers append to
``depsgraph_update_post`` + ``undo_post`` + ``redo_post`` +
``load_post`` so the previous commit's ``TokenCache`` in
``tool.System.get_decoration_data`` finally invalidates on
structural scene changes.
Generated with the assistance of an AI coding tool.
Shared helpers for Bonsai's Scene-level parametric preview flows.
Two PR4 features will consume this — MEP bend preview and wall
fillet preview — both following the same shape:
Enable<X>Preview — populates draft on Scene.BIMPreviewProperties.<x>
Gizmo<X>Preview — polls on is_active, surfaces tunable widgets
<X>PreviewDecorator — GPU lines while is_active is True
Finish<X>Preview — bpy.ops.bim.<verb>(...) with draft kwargs
Cancel<X>Preview — pure state reset
The module hosts the cross-cutting accessors (``get_preview_props``,
``is_preview_active``), lazy-closure factories for gizmo dimension
callbacks (``make_props_callback`` / ``make_dim_getter`` /
``make_dim_setter`` — defensive against missing scene / freed RNA
struct on file open / undo), the Enable-time IFC-placement sync
(``sync_uncommitted_moves``), and the Esc + load_post discard
machinery (``PREVIEW_CANCEL_OPS`` registry, ``try_cancel_active_preview``,
``discard_pending_previews``).
Ships standalone — the consumer features land in PR4 (preview
PropertyGroups, Enable/Finish/Cancel operators, gizmo groups,
decorators, Esc keymap binding). All accessors are defensive
against missing PropertyGroups / operators on v0.8.0 — calling
``discard_pending_previews(scene)`` from the next commit's
load_post hook is a no-op until PR4 attaches BIMPreviewProperties.
Generated with the assistance of an AI coding tool.
System decoration draws on every viewport refresh — the
``_build_decoration_data`` body walks every distribution element,
resolves connected ports, builds the vert/edge arrays for the GPU
batch. A bare call per frame burns time on an unchanged scene.
Add a single-entry cache keyed on ``(decorator_cache_token,
id(decorated_elements_set))``. Reads short-circuit when neither
component moved:
* ``decorator_cache_token`` from ``bim.decorator_cache`` invalidates
on depsgraph / undo / redo / load via the bump handler.
* ``id(decorated_elements_set)`` invalidates when
``SystemDecorationData.load()`` reassigns the set (e.g. when the
user changes the set of decorated systems via the panel).
The handler that bumps the token is installed in the next commit
(bim/handler.py decompose). Until then the token stays at 0, so
the cache only hits when ``id()`` also matches — degraded behaviour
during the bisect window but not incorrect.
Generated with the assistance of an AI coding tool.
New helper module for POST_VIEW decorators. Exports:
* ``get_decorator_cache_token()`` — global int counter consumers
include in their cache key so the value invalidates on structural
scene changes.
* ``_bump_decorator_cache_token()`` — ``@bpy.app.handlers.persistent``
callback that increments the token. Gates on the depsgraph payload
so animation playback / driver evaluation doesn't churn the token.
* ``install_decorator_cache_handlers`` / ``uninstall_…`` — idempotent
append / remove against depsgraph_update_post + undo_post + redo_post
+ load_post. Called once from ``bim.register`` / ``unregister``.
* ``TokenCache[T]`` — single-entry memoiser keyed on ``(caller_key,
token)``. Cached ``bpy.types.Object`` references can't outlive the
underlying ID blocks because any depsgraph / undo / load bumps the
token and forces a recompute.
This commit ships the module standalone. The next commits in this
PR wire it: tool/system.py adds the cache wrap on get_decoration_data
and bim/handler.py installs the bump callbacks. Until both land,
the module is intentionally dead code — keeps the diff narrow and
the commit history bisectable.
Generated with the assistance of an AI coding tool.
Five code paths in slim PR2 referenced symbols that don't exist in
v0.8.0's bim layer, raising at first call. Plus three type
annotations that ty flagged as unresolved.
1. tool/system.py:get_decoration_data — drop the cache layer that
keyed on a token from a bim/decorator_cache.py module. The cache
is dead-or-broken in slim: the depsgraph bump handler that would
invalidate the token lives in PR3's bim/handler.py decompose, so
the token stays at 0 forever. Either the cache never hits
(decorated_elements rebuilt → new id() per call) or returns
stale data (list reused). Revert to direct
`_build_decoration_data()` calls. PR3 reintroduces the cache
atomically: decorator_cache module + handler install + cache
wrap + tests. Keeps `_build_decoration_data` extraction
(cleaner than v0.8.0's monolithic version regardless of cache).
2. tool/spatial.py — add `get_host_element` + `get_host_wall`.
The interface stubs in `core/tool.py:1037-1038` were declared
but never implemented. `tool/duplicate.py:99` (object duplication
with fills) and `tool/model.py:1260` (array per-child opening
mirror) call these and would raise AttributeError.
3. tool/model.py:recreate_wall — drop the fillet-corner branch
that function-locally imports `regenerate_fillet_corner_wall`
from `bim/module/model/wall`. The function lands with PR4; fall
through to the straight-extrusion path preserves v0.8.0
behaviour for fillet walls until then. Tag FIXME(PR4).
4. tool/model.py — drop `get_pipe_segment_props` /
`get_duct_segment_props` accessors. Their return types reference
`BIMPipeSegmentProperties` / `BIMDuctSegmentProperties` which
land with PR4's prop.py; calling either accessor on v0.8.0 would
AttributeError on `obj.BIM<X>SegmentProperties`. Zero callers in
slim — PR4 reintroduces both accessors together with the
PropertyGroups they wrap. Also drops the matching TYPE_CHECKING
imports.
5. tool/blender.py:557 — `Mapping[type[ViewportDecorator], bool]`
needs the qualified `Blender.ViewportDecorator` because the
annotation is on a method INSIDE the same nested class; the
bare name doesn't resolve at type-check time.
6. core/tool.py Surveyor — drop the `obj: "bpy.types.Object"` /
`z: float` / `-> float` / `-> None` annotations on
`get_z_rotation` / `set_z_rotation`. The `@interface` decorator
wraps each method as `classmethod(abstractmethod(...))` at
import time, but ty doesn't track the wrap and flags every
call site as `missing-argument` plus the `pass` body as
`empty-body` against the declared return type, plus the
`bpy.types.Object` forward-ref as `unresolved-reference`.
Reverting to v0.8.0's untyped style (matching the sibling
`get_absolute_matrix(cls, obj)` stub) clears six ty errors at
the cost of zero runtime semantics — the abstract stubs only
serve as registry markers, concrete `tool.Surveyor.*` carries
the real signatures.
Generated with the assistance of an AI coding tool.
Surfaces any regression in:
* the modules dict in bim/__init__.py (added a folder, forgot the entry)
* PointerProperty wiring on bpy.types.{Scene,Object,...}
* registry-driven GizmoPreferences<Name> auto-registration in
tool.Parametric.iter_gizmo_preference_classes
* bpy.app.handlers append/remove balance
* every register()/unregister() across the 45+ feature modules
as a single PASSED/FAILED test instead of the silent "addon failed to
enable" users encounter in a fresh Blender. Paired with the existing
test_parametric_registry.py contract tests, this catches both the
registry-shape regressions (operators/PropertyGroups/predicates) and
the registration-mechanics regressions (PointerProperty types not
registered before their owners).
Generated with the assistance of an AI coding tool.
Three corrective fixes folded into one commit. All surface as
addon-load / save-time exceptions on v0.8.0's bim layer because
PR2's tool.Parametric refactor over-committed to the PR4 contract.
1. iter_gizmo_preference_classes — the previous implementation
returned only the shared GizmoPreferencesFeature class. v0.8.0's
bim/ui.py declares PointerProperty fields ('door', 'window', ...)
on GizmoPreferences that point at per-feature
GizmoPreferences<Name> classes; those must be registered BEFORE
GizmoPreferences itself. The shared-class-only return broke
addon registration with:
'door' PointerProperty could not register (see previous error)
Restore the v0.8.0 per-feature lookup (iterate EDIT_TYPES, look
up each GizmoPreferences<Capitalize(name)> on ui_module) and
keep the shared-class lookup as forward-compat. Tag FIXME(PR5).
2. EDIT_TYPES — drop the array / pipe_segment / duct_segment
entries from the registry. Their bim.finish_editing_<name>
operators land with PR4. Registering them in PR2's EDIT_TYPES
without the operators makes auto-commit-on-save dispatch a
non-existent finish_op for any object whose
BIM<Name>Properties.is_editing flag is True, raising:
RuntimeError: 'bim.finish_editing_array' must be a registered
tool.Ifc.Operator subclass for undo-safe IFC mutation
PR4 re-adds the three entries together with their operators.
Tag FIXME(PR4).
3. tool.Blender.Modifier shim block — upgrade the prose comment to
a formal FIXME(PR5) marker so the PR5 cleanup sweep finds it via
grep alongside every other tagged shim site.
Generated with the assistance of an AI coding tool.
Fixes addon-load ImportError that surfaces when tool/geometry.py
and tool/model.py (extracted in C8 / C9) reference symbols that
don't exist on v0.8.0:
* bim/ifc.py: get_cache_or_detect_lock — IfcStore.get_cache
variant that tracks the multi-instance-cache-locked-by-other-
process flag, sets it on PermissionError, clears it (along with
the dismiss flag) on subsequent success. Used by
tool.Geometry.* to gate IFC cache reads without crashing when
another Blender instance holds the cache lock.
* tool/cad.py: WELD_TOLERANCE constant + paired CAD helpers
(auto-detect-curves vertex precision, polyline normal helpers,
etc.) used by tool.Model.* + by the parametric model operators
that land in PR4.
Both modules had zero upstream commits since the gizmos-8088 fork
point — safe bulk extraction. PR4 has no caller-line work for
either file (the additions are pure additions, no existing API
removed); the v0.8.0 callers of get_cache_or_detect_lock and
WELD_TOLERANCE are the PR2-scope files that needed them.
Generated with the assistance of an AI coding tool.
The previous commit moved is_<type> predicates off tool.Blender.Modifier
onto tool.Parametric, and earlier C4 moved the Array helper bag off
tool.Blender.Modifier.Array onto tool.Array. PR4 will migrate every
caller; this commit keeps the OLD entry points alive as thin delegates
so PR2 ships without breaking ~30 caller sites that still spell the
old API in v0.8.0:
* tool.Blender.Modifier.is_door / is_railing / is_roof / is_stair /
is_wall / is_window — delegate to tool.Parametric.is_<type>.
* tool.Blender.Modifier.Array.bake_children_transform / constrain_
children_to_parent / get_all_children_objects / get_all_objects /
get_children_objects / get_modifiers_data / remove_constraints /
set_children_lock_state — delegate to tool.Array.<same name>.
These shims are removed in PR5's cleanup commit once PR4 has rewritten
the call sites in bim/import_ifc.py, bim/module/geometry/operator.py,
bim/module/geometry/data.py, bim/module/model/array.py + the per-feature
operators (door, wall, window, railing, roof, stair, ui).
Generated with the assistance of an AI coding tool.
tool.Parametric becomes the central registry for Bonsai's parametric
features (wall, slab, door, window, railing, roof, stair, plus
mep-segment variants). Each feature registers a ParametricObject spec
declaring its enable/finish/cancel op names, props accessor, regen
callback, and is_element_type predicate.
Public surface:
* tool.Parametric.WALL / SLAB / DOOR / WINDOW / RAILING / ROOF /
STAIR / PIPE_SEGMENT / DUCT_SEGMENT — typed accessors per feature.
* tool.Parametric.is_wall / is_door / is_window / is_railing /
is_roof / is_stair — element-type predicates that move off
tool.Blender.Modifier into the parametric registry. The next
commit adds backward-compat shims on tool.Blender.Modifier so
v0.8.0 callers keep working.
* tool.Parametric.is_object_editing(obj) — returns the registered
feature an object is currently editing, or None.
* tool.Parametric.run_bim_op(op_name) — invoke a parametric op by
bl_idname.
* tool.Parametric.heal_stale_edit_flags — clear is_editing flags
on file load so a saved-mid-edit project doesn't leave gizmos
poll-locked.
* supports_build_edit_lifecycle field on ParametricObject — declares
whether the feature implements the build/edit/cancel triad.
The previous bare `print(f"Bonsai: commit of {obj.name!r} via
{finish_op} failed: {e}")` exception-handler is replaced with
logger.warning(..., exc_info=True). Same channel (Bonsai configures
logging to the Blender console at WARNING level), strictly more
information (full traceback), correct idiom for an error-path
message. A second logger.warning is added for parametric predicate
failures, also exception-handler scope.
Generated with the assistance of an AI coding tool.
tool.Model gains:
* get_pipe_segment_props / get_duct_segment_props — typed prop accessors
for the MEP-segment edit lifecycle.
* resolve_active_props_for_edit — picks the right BIM*Properties to
drive a parametric edit triad based on the active object's IFC class.
* mirror_parent_void_fillings_to_children — when an array parent has
hosted fillings (door/window in a wall), replicate the same fill
rels onto each array child. Uses tool.Array.get_parametric_propagation_
targets so the propagation stays within the array family (the old
get_all_element_occurrences over-propagated to standalone occurrences
of the same type, which silently mutated unrelated arrays).
* unshare_opening_representation — fork a shared IfcShapeRepresentation
so editing one opening doesn't mutate its array sibling.
* duplicate_ifc_objects gains a post-condition select-restore on the
array parent so callers don't get a deselected parent for N>=2 arrays.
sync_object_ifc_position is kept as a thin delegate to
tool.Geometry.commit_placement_if_moved (the new home, added in C8) so
the 6 v0.8.0 callers in mep / product / system don't AttributeError;
PR4 migrates each caller and removes the delegate.
tool.Pset gains:
* upsert_pset — get-or-add-or-edit in one call.
* write_bbim_data — JSON-encode + write BBIM_* metadata in one call.
tool.Slab is new — slab-specific reads (active extrusion, axis
direction) used by the slab gizmos, pure-IFC, no PropertyGroup mutation.
Generated with the assistance of an AI coding tool.
Adds:
* get_body_representation(element) — DRY of the repeated
ifcopenshell.util.representation.get_representation(element, "Model",
"Body", "MODEL_VIEW") call across slab / wall / opening / stair /
roof / door / window / mep. One central place to read the body rep;
every caller stops re-spelling the four magic strings.
* has_axis_representation(element) — predicate for elements with a
GRAPH_VIEW Axis representation. Used by the wall/MEP path decorators
to skip elements without an unambiguous 1D path.
* has_material_styles(element) — predicate for whether the element
carries IfcStyledItem material assignments.
* restore_placement_from_ifc(obj, element) — snap obj.matrix_world back
to element's committed IFC placement + rebaseline the drift checksum.
* restore_or_rebaseline_placement(obj, element) — Cancel-flow helper:
restores if ObjectPlacement exists, just rebaselines the checksum if
not.
* detach_representation(product) — remove the active representation
from a product without deleting the entity (used by parametric
rebuilds that wipe + re-add).
commit_placement_if_moved docstring expanded with a "drop-in scope"
note so callers don't redundantly wrap it in an is_moved check that
the helper already does.
Switches the duplicate-aware helper calls (formerly tool.Root.*) to
tool.Duplicate.* now that the service exists (C6).
Generated with the assistance of an AI coding tool.
Adds:
* ViewportDecorator base class — install/uninstall/draw lifecycle for
3D viewport gpu overlays, with handler-rollback-on-failure so a
partial install can't leave dangling draw handlers.
* sync_all classmethod — drive each listed ViewportDecorator subclass
to its desired install state in one call.
* is_view_top_down + top_down_factor — viewport-camera orientation
predicates used by gizmo billboarding and decorator layout.
* get_screen_up_world — screen-up vector in world space for gizmo
text orientation.
* are_viewport_gizmos_enabled — central gate for the global
draw_gizmos_in_3d_viewport pref, replacing duplicated prefs reads.
* DecoratorColors NamedTuple + get_decorator_colors — single source
for the colour palette every viewport decorator binds.
Preserves Ryan Schultz's add_layout_hotkey_operator polish (719309571,
2026-05-25): the row-position move + separator(factor=1) between the
modifier and key icons stay intact in this extraction.
Generated with the assistance of an AI coding tool.
Extract the duplicate-aware relationship-walk + restoration logic
(get_decomposition_relationships, get_connection_relationships,
get_port_connection_relationships, recreate_decompositions,
recreate_connections, recreate_port_connections, consume_warnings)
out of tool.Root into its own service.
tool.Root's responsibility is identity and addressing of IFC roots;
the duplicate-aware bookkeeping of "before duplication, what relations
did this graph have, and how do I restore them on the new copies?"
deserves its own home. The split was already declared on core/tool.py
(C2); this commit lands the concrete tool.Duplicate implementation.
tool.Root keeps its own copies of the methods on v0.8.0's tool/root.py
during this PR so callers in bim/module/spatial/operator.py keep
working at runtime; the Root cleanup lands in PR4 alongside the
caller updates.
Generated with the assistance of an AI coding tool.
Adds:
* direction_from_port_pair(port_a, port_b) — derive the connect_port
direction kwarg from each port's FlowDirection (NOTDEFINED for
non-canonical pairs). Centralises a pattern that callers were
inlining inconsistently.
* tool.System.walk_connected_mep_elements — BFS over connected MEP
flow elements via IfcRelConnectsPorts.
* tool.System.get_port_world_position — port placement → world-space
Vector, used by the MEP path decorator.
* tool.System._build_decoration_data — cached decoration metadata
for the MEP system-path overlay.
Plus a get_port_relating_element return-type tightening (Union with
None) and a partial-init cycle workaround on bim.module.system.data
imports (now function-local — top-level import triggered the cycle
through tool.Ifc.Operator).
Generated with the assistance of an AI coding tool.
Top-level array-domain service extracted out of tool.Blender.Modifier.Array.
Owns the BBIM_Array pset graph navigation (constrain_children_to_parent,
remove_constraints, get_modifiers_data, get_children_objects,
get_all_children_objects, get_child_layer_index, bake_children_transform),
plus the Blender-side CHILD_OF constraint lifecycle that ties each child
replica to its parent's transform.
Array's own module gives the parent/child semantics a clean home — array
behaviour was previously scattered between tool.Blender.Modifier and ad-hoc
helpers in bim/module/model/array.py. The relocation eliminates the inline
duplication and gives Bonsai callers a single import surface.
Generated with the assistance of an AI coding tool.
Bpy-permitted wall reads — get_axis_local_extent, get_length_and_height,
get_x_angle, get_path_connection_location, walk_connected_walls — used
by gizmo lambdas that need wall dimensions and join topology without
the side effect of loading the wall's draft BIMWallProperties (the
loader mutates PropertyGroup state and would clobber the wall's own
gizmo state when both the wall and a hosted filling are selected).
All reads go through ifcopenshell.util.representation / .util.element
so the IFC graph stays the source of truth. tool.Wall consumes
core.model's PARALLEL_DOT_THRESHOLD + collinearity helpers (no inline
magic numbers).
Generated with the assistance of an AI coding tool.
Declares the bpy-free contract for tool services landing in subsequent
commits — tool.Wall, tool.Array, tool.System, tool.Duplicate (extracted
from tool.Root), tool.Parametric, plus minor additions on existing
interfaces (tool.Spatial.get_host_element / get_host_wall,
tool.Geometry.has_axis_representation / has_material_styles,
tool.Surveyor.get_z_rotation / set_z_rotation).
The @interface declarations are empty-bodied; concrete implementations
land in the per-service tool/* commits below. Keeping the contract in
core lets core/* helpers and tests reference the surface without
importing the concrete tool modules.
Moves get_decomposition_relationships + recreate_decompositions off
tool.Root onto the new tool.Duplicate (extraction of duplicate-aware
behaviour into its own service).
Generated with the assistance of an AI coding tool.
core/model.py gains:
* Three calibrated dot-product / distance thresholds — PARALLEL_DOT_THRESHOLD
(~2° from parallel, cos(2°) ≈ 0.9994), COLLINEAR_LINE_TOLERANCE (50mm
perpendicular distance for two parallel wall axes to share a line),
BASELINE_OFFSET_TOLERANCE — replacing inline magic numbers that the
wall-join classifier, fillet-state machine, and gizmo preview decorator
all read from.
* Pure wall-join geometry helpers (project_axis_intersection,
are_axes_collinear, classify_wall_join_state, wall_join_preview_lines,
resolve_extend_walls_target, extrusion_depth_from_vertical_height,
length_and_height_from_extrusion). They take primitive tuples + floats,
no bpy, no ifcopenshell — testable in the core lane.
core/product.py is new — pure-Python aggregate-walk helpers (resolve_host_
of_product, collect_decomposed_products) that downstream tool/spatial and
tool/aggregate consumers can call without importing ifcopenshell at module
load.
Generated with the assistance of an AI coding tool.
add_railing_representation now factors into two parts:
* compute_wall_mounted_handrail_geometry returns a pure-geometry
WallMountedHandrailGeometry dataclass (handrail polyline + support
list + terminal caps), no IFC mutation.
* add_railing_representation wraps that dataclass into an
IfcShapeRepresentation as before.
Downstream consumers that want the same math without round-tripping
through an IFC file (Blender gizmo previews, viewport drafts) now
drive compute_X directly. Future add_X_representation work in the
geometry API is encouraged to follow the same shape — a sibling
compute_X function + thin IFC wrapper.
The railing_type parameter is dropped from the signature — only
WALL_MOUNTED_HANDRAIL was ever supported, so the kwarg was dead.
The Bonsai railing-modifier caller is updated in the same commit
to stop passing it; without that update Bonsai's
finish_editing_railing_path raises TypeError on the first edit.
RailingSupport and WallMountedHandrailGeometry use @dataclass(slots=True)
— they're constructed N-per-cap during arc sampling, so the per-instance
overhead matters.
Public symbols (RailingSupport, TERMINAL_TYPE,
WallMountedHandrailGeometry, compute_wall_mounted_handrail_geometry,
add_railing_representation) re-exported from ifcopenshell.api.geometry.
New test/api/geometry/test_add_railing_representation.py covers the
compute/wrap contract.
Generated with the assistance of an AI coding tool.
Drops the module-local ``mm()`` helper in favour of the centralised
``ifcopenshell.util.unit.mm_to_m`` (added earlier in this PR). The
``as mm`` import alias preserves the existing call sites' readability.
Generated with the assistance of an AI coding tool.
Drops the module-local ``mm()`` helper in favour of the centralised
``ifcopenshell.util.unit.mm_to_m`` (added earlier in this PR). The
``as mm`` import alias preserves the existing call sites' readability.
Generated with the assistance of an AI coding tool.
ShapeBuilder gains module-level NP_X / NP_Y / NP_Z / NP_XY / NP_XZ /
NP_YZ / NP_YX axis-index constants. Downstream geometry builders had
been redefining local copies for indexing np.ndarray vectors of shape
(3,) or (N, 3); centralising removes the duplication.
mep_transition_length and mep_transition_calculate verbose default
flipped from True to False. The prints are diagnostic-only output;
True-by-default spammed the console on every transition computation,
which fires per-fitting on IFC load.
Generated with the assistance of an AI coding tool.
Centralises the millimetre-to-metre conversion shortcut that
add_door_representation and add_window_representation each defined
locally. Subsequent commits in this PR switch both call sites to
import this from util.unit, removing the duplicate definitions.
Generated with the assistance of an AI coding tool.
Use add_layout_hotkey_operator for draw_regen_operations so the Regen
button shows text and shortcut icons in the sidebar like all other
panel buttons. Add a separator between modifier and key icons for
readability.
test_parametric_lifecycle.py covers the door/window/railing/roof
state-transition contracts (enable/finish/cancel; no-op on
non-matching elements; draft preserved on finish-time failure)
that the registry smoke test never exercised.
test_parametric_registry.py gains a check that every is_<name>
predicate stays total (never raises on a non-matching IFC entity)
— a raising predicate would break the save path for unrelated
types. Also rewrites the gizmo-prefs check to read __annotations__
instead of hasattr, which depended on Blender registration timing.
Generated with the assistance of an AI coding tool.
Aligns with the test/bim/ convention: heavy imports go inside test
functions so the autouse _require_real_bpy fixture skips cleanly
when bpy is mocked, rather than module-level imports failing at
collection time and erroring out the whole file.
Generated with the assistance of an AI coding tool.
PowerShell and some wrapper scripts on Windows occasionally strip
or reorder the `--` separator before Blender sees it, dropping the
pytest args into Blender's positional file-load slot ("File format
is not supported"). The env var carries the same args via a
shell-evaluation-free channel. Default `--` path is byte-identical
to the pre-change behaviour.
Generated with the assistance of an AI coding tool.
Docstrings naming sibling methods, private helpers, test files, or
historical symbols silently go wrong on rename. Strip Sphinx :meth:
/ :class: / :func: / :attr: markup that mostly added noise (no
Sphinx in this project), and rewrite five docstrings that cited
specific test paths or private hooks to describe the behaviour
instead.
Generated with the assistance of an AI coding tool.
The dialog's only outcomes were "Apply & Save" (same as silent save)
or "Cancel" (same as not saving) — net friction with no actual choice.
Auto-commit stays as the safety net; the count now suffixes the
existing save-success report so it isn't immediately overwritten.
Generated with the assistance of an AI coding tool.
When two GizmoDimension hit regions overlap (a short dimension
nested inside a longer one along the same axis), the larger one
used to win because hit boxes are scaled by world-space length —
the long box fully contains the short one, leaving the short
gizmo unreachable. The larger gizmo stays clickable at its
exposed ends, so smaller-wins is the right UX default.
Sets self.select_bias = -self._dimension_length inside
GizmoDimension.set_dimension_length. The smaller gizmo writes a
less-negative depth value in the GPU select buffer and wins the
tie-break. select_bias is unused elsewhere in the codebase, so
icon and arrow gizmos keep bias=0 and are unaffected (icons
correctly still win against dimensions, since 0 > -length).
Adds test/bim/module/drawing/test_dimension_gizmo_priority.py
with 5 cases: direct ordering, monotonicity across length ranges,
abs() handling for signed dimensions, and NaN/Inf safety.
Generated with the assistance of an AI coding tool.
set_icon_gizmo_position computed
``mw @ (Translation @ billboard_rot @ Scale)`` — the object's world
matrix was applied AFTER the billboard rotation, so any non-trivial
object rotation (e.g. a wall rotated in plan, a stair rotated to
match a corridor) carried over into the icon's transform and tilted
it edge-on to the camera instead of facing it.
Switch to ``billboarded_at(world_pos, billboard_rot, scale)`` where
``world_pos = mw @ local_pos``: translate to world space first, then
apply the billboard rotation independently of the object's rotation.
This matches the manual pattern the base class's
``update_editing_gizmos`` already uses for validate/cancel/cycle for
exactly this reason.
Drops the now-stale workaround docstring on
``GizmoWallEdition._update_icon_row_extras`` that documented why it
bypassed ``set_icon_gizmo_position`` — the helper does the right
thing now.
Adds ``test/bim/module/model/test_stair_gizmos.py`` as the regression
guard: parametrised over six rotation angles, asserts that the rotation
part of the resulting matrix equals ``billboard_rot`` (no contribution
from ``mw``'s rotation) and that the translation lands at
``world_pos``. Also exercises ``set_icon_gizmo_position`` end-to-end via
a stub gizmo to catch the exact shape of the previously-broken call
site.
Generated with the assistance of an AI coding tool.
Walls gain in-viewport parametric editing matching the door/window/stair
UX: drag handles for length, height, slope (x-angle), layer baseline
cycle, plus cursor-anchored quality-of-life operators (split at cursor,
extend to cursor, extend height, rotate 90, toggle openings) and
two-object state-machine gizmos (unjoin / merge / join-corner /
extend-to-wall / extend-vertically / add-opening).
Wall enters tool.Parametric.EDIT_TYPES, so save-time auto-commit,
GizmoPreferencesWall registration, and the in-progress-edit predicates
all light up automatically through the registry plumbing landed two
commits back.
The three-layer commit model (drag -> BIMWallProperties -> bmesh
preview -> Finish -> single ifc.run) means dragging a handle through
hundreds of intermediate values produces zero extra IFC entities. A
no-op enable->finish round-trip is byte-identical. The snapshot diff
in FinishEditingWall skips unchanged params.
_commit_active_wall_edit_if_any ensures cursor-anchored operators see
committed geometry, not the draft preview box.
Also lands the `prompt_auto_commit_parametric_edits` BoolProperty on
BIM_ADDON_preferences (consumed by the auto-commit dialog landed in
the framework commit) and refactors
`draw_{door,window,stair}_gizmo_parameters` into a shared
`_draw_parametric_gizmo_parameters` helper that the new
`draw_wall_gizmo_parameters` reuses. This commit and the framework
commit are stacked - the framework commit references the BoolProperty
defined here, so they must land together.
Tests cover pure math (core/test_model.py), DimensionGizmoConfig text
formatter, GizmoWallExtendVertically.poll() preconditions, and the
refresh_post_commit cache-invalidation regression. BDD scenarios in
model.feature cover the edit triad, auto-commit on save, and the
two-object gizmos. Documentation added to creating_walls.rst.
Generated with the assistance of an AI coding tool.
Three small post-landing cleanups against the parametric framework commit:
* core/model.py had `are_axes_collinear` and `closest_endpoint_midpoint`
each defined twice — Python silently kept the second copy, the first
was dead code. Removed the dead copies; runtime behavior unchanged
(the live versions were already the kept ones).
* bim/__init__.py's `_parametric_gizmo_preference_classes` docstring
named the wrong link in the import chain (`tool.blender → bim.ifc`).
The real chain is `tool/ifc.py` (and ~6 other tool/* modules) which
import `from bonsai.bim.ifc import IfcStore` at module load. Updated
docstring to cite that root cause and the architectural fix (move
`IfcStore` out of `bim/`).
* tool/blender.py's `from bonsai.bim.ifc import IFC_CONNECTED_TYPE`
carried a 5-line comment claiming it was "lazy" to avoid a circular
load. The import sits inside an `if TYPE_CHECKING:` block with
`from __future__ import annotations` — it never runs at runtime
regardless. Comment removed; the TYPE_CHECKING guard is
self-explanatory.
Generated with the assistance of an AI coding tool.
Establish a single source of truth for parametric element types (door,
window, stair, railing, roof). tool.Parametric.EDIT_TYPES drives:
- BIM<Name>Properties PointerProperty attachment via the registry
- GizmoPreferences<Name> class registration in bim/__init__.py
- save-time auto-commit of pending draft edits
- the refresh_post_commit epilogue called from IfcStore after every IFC
mutation, which fixes the stale-header bug where in-place hotkey
mutations (S_E / C_E) left BIMModelProperties and the gizmo cache
pointing at obsolete values.
Refactors door/window/railing/roof onto shared mixins from
bim/parametric_lifecycle.py (FeatureModifierEditMixin and
PathPreservingEditMixin); stair gets the lock-gizmo refactor and
frame-cache integration. Behavior preserved.
Adds BaseParametricGizmoGroup._prime_frame_caches so the parametric
gizmos stop re-deriving preferences, view direction, and billboard
rotation per frame; reorders poll() to short-circuit on the cheapest
predicate first. Adds the icon library + BillboardingGizmoGroupMixin
that the wall feature in the next commit will consume.
Generated with the assistance of an AI coding tool.
When a project has a ifc file associated, selecting non-ifc objects and duplicating them with SHIFT + D now correctly both duplicate them, keep the new objects selected and starts the transform modal. IFC objects behaviour is unaffected.
The x-angle transformation for LAYER3 slabs assumed SweptArea
is always IfcArbitraryClosedProfileDef (which has OuterCurve),
but composite profiles use IfcCompositeProfileDef instead.
Apply the coord scaling to each sub-profile individually.
Generated with the assistance of an AI coding tool.
generate_section_reference_points had no handler for
MODEL_VIEW target view, causing it to silently return
None. Add MODEL_VIEW branch that clips the section line
to XY camera bounds while preserving the Z coordinate
for correct 3D placement.
Generated with the assistance of an AI coding tool.
Guard the int() cast on CardinalPoint in
BIM_OT_edit_assigned_material so a None value (no cardinal
point set) no longer raises a TypeError.
Generated with the assistance of an AI coding tool.
Extracted from the font file like so:
python3 -c "
from fontTools.ttLib import TTFont
tt = TTFont('src/bonsai/bonsai/bim/data/fonts/OpenGost Type B TT.ttf')
for record in tt['name'].names:
if record.nameID == 13:
print(record.toUnicode())
"
The https://github.com/IfcOpenShell/website repo already has bonsai-docs.yml workflow that does the same thing - builds Bonsai docs from the main repo and deploys to bonsaibim_org_docs, so this workflow is redundant and confusing.
Don't leave a broken repo if ifcmerge is misinstalled.
Fix bug where only local branches could be merged.
Fix gitch where merge commits were not considered relevant.
Add update callbacks to the relating_object and related_object
PointerProperties so that selecting an object via the eyedropper
in BIM_PT_aggregate immediately calls aggregate_assign_object
and closes the editing panel, removing the need to click the
checkmark button manually.
Generated with the assistance of an AI coding tool.
Adds `remove_coplanar_boundary_lines()` to operator.py (Bonsai uses this
path, not draw.py's main()). After `merge_linework_and_add_metadata()`
assigns material CSS classes, this post-processes the SVG to delete
projection line segments that appear in two or more adjacent, coplanar
elements with the same material and presentation style.
Key design decisions:
- Material identity: compared via sorted IFC material ID tuples from
`get_materials()`, not CSS class names — avoids false matches between
unrelated `material-null` elements.
- Presentation style identity: compared via IFC IfcPresentationStyle IDs
from `StyledByItem` on geometry representation items — handles elements
with no material but distinct visual styles.
- Physical adjacency: confirmed by a 3D shared-vertex test (tol=0.01 m)
after a quick AABB guard, rejecting elements whose 2D projections
overlap but sit at different depths.
- Coplanarity: determined by the dominant (largest-area) face normal of
each Blender mesh object — area-weighted averages are unreliable for
slabs whose equal top/bottom faces cancel out. Folded walls sharing an
edge but meeting at an angle are correctly rejected (normal dot ≪ 1.0).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Anthropic prompt caching (cache_control on system prompt and
tools) to reduce repeated token costs by ~90%
- Truncate large tool results in conversation history (2000 char cap)
to prevent context bloat from ifc_tree/ifc_select responses
- Add sliding window (40 messages) on conversation history, trimming
at user message boundaries to avoid breaking tool-call sequences
- Default "New IFC" button to IFC4X3 schema instead of IFC4
- Constrain ifc_new schema parameter with enum to prevent invalid
schema strings like "IFC4X3ADD2"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parse ifcmerge JSON output and display a per-conflict breakdown in the
panel when merge fails. Ctrl+click on the Merge button previews
conflicts without committing. Add SelectConflictEntity operator to
select and frame the conflicting object in the 3D viewport.
Generated with the assistance of an AI coding tool.
A degenerate edge (zero-length segment) caused an early `return`
of a tuple instead of continuing the loop, resulting in a
TypeError when snap.py iterated the result and tried to assign
`point["group"]` on a float.
Generated with the assistance of an AI coding tool.
Add a provider selector (OpenAI / Anthropic) to the ifcchat web UI,
allowing users to use their Anthropic API key with Claude models
instead of only OpenAI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a remote branch tip is checked out (resulting in detached HEAD),
the new-branch name field is now pre-filled with the local equivalent
of the remote branch name (generating a unique suffix if that name is
already taken), so the commit button is immediately usable.
See #7580
Generated with the assistance of an AI coding tool.
This version has some functional differences:
- Structured JSON error message instead of free text (on STDOUT not STDERR)
- New --prioritise-local flag to control which side wins in merge conflicts (not used by Bonsai yet)
- IfcLocalPlacement conflicts now auto-resolve instead of failing the merge (partial solution to #6885)
- Float values are normalised when comparing entities (workaround for #7696)
Include bonsai_git_branch in get_debug_info(). For dev environments
using the GitPython-based update_commit_data() path, the branch is
read from repo.active_branch.name. For built extensions, a 7777777
placeholder is replaced at build time via the Makefile, matching the
existing pattern for bonsai_commit_hash and bonsai_commit_date.
Generated with the assistance of an AI coding tool.
Move all business logic into bonsai core and tool. Performance fixes to
minimise file IO, various minor bug fixes and tests.
Generated with the assistance of an AI coding tool.
In order to fix error of the type:
| point, _ = cls.intersect_edge_region_border(
| ^^^^^^^^
| TypeError: cannot unpack non-iterable NoneType object
a tuple is expected.
Two bugs introduced in 31b571322:
- SnapObj assumed obj.data is always a Mesh; non-mesh
objects (empties, lights, etc.) have obj.data = None,
causing an AttributeError on obj.data.edges.
- view3d_utils was used but never imported.
Generated with the assistance of an AI coding tool.
Add --format ids to the ifcquery.rst format description and a new
"Scripting with ifcedit" section showing composition examples. Add
the foreach subcommand to ifcedit.rst with usage examples.
Add ifcquery, ifcedit and ifcmcp to the README contents table, the
Sphinx docs toctree and introduction utilities table. Add new .rst
pages for each package documenting subcommands, installation, usage,
and parameter types. Fix plot and render CLI examples in ifcquery
README to use -o/--out-format flags. Update ifcmcp README to use the
installed ifcmcp command rather than python3 -m ifcmcp.
Generated with the assistance of an AI coding tool.
These three packages were added to src/ but lacked the Makefile needed
by common.mk to build distribution wheels, and the GitHub Actions
workflow to publish them to PyPI.
Adds make dist / make test / make qa targets and ci-*-pypi.yaml
workflows matching the pattern used by ifcpatch, ifcclash, etc.
IfcRelConnectsPathElements has an optional ConnectionGeometry attribute for
recording the geometric cut-plane between adjacent elements, but there was
no way to set it via the API.
Generated with the assistance of an AI coding tool.
assign_product creates IfcRelAssignsToProduct linking a structural member to
a physical building element. assign_to_building creates IfcRelServicesBuildings
linking a structural analysis model to a building. add_topology_representation
creates IfcTopologyRepresentation for structural elements, inferring the
representation type from the item class.
Generated with the assistance of an AI coding tool.
Both attributes are required by the IFC schema but were not settable via
the API function. Add physical_or_virtual and internal_or_external parameters
with "NOTDEFINED" defaults for backward compatibility. Update Bonsai boundary
panel to expose both fields in the editor.
Generated with the assistance of an AI coding tool.
Add --format ids to ifcquery to output step IDs suitable for piping into
ifcedit parameters. Add ifcedit foreach to apply an operation to every
element in a query result. Extend clash and relations output so --format ids
extracts all involved element IDs, enabling one-liners like clash detection
piped directly into render.
Generated with the assistance of an AI coding tool.
Enhances the performance of mouse intersection checks for wireframe objects.
Details:
- Calculated the intersection with the mouse in 2D pixels first.
- Converted objects to a BVH Tree to reduce the number of edges checked against the mouse position.
mcp is an optional dependency so that the embedded API (embedded.py) can
be used from Pyodide without pulling in pydantic-core and the rest of the
MCP protocol stack, which may not be available in all WASM environments.
Fix#7034
bpy.ops.bim.extend_to_underside doesn't exist - the correct operator
name is bim.extend_walls_to_underside. The AttributeError killed the
entire panel draw, hiding mirror, align, aggregation, and QTO buttons.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If a file had an IfcProjectedCRS without an IfcCoordinateOperation (or
vice versa), add_georeferencing would return early without creating the
missing entity. This caused edit_georeferencing to crash with IndexError.
Now detects the inconsistent state, cleans up, and recreates both.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ifcmcp is a new Model Context Protocol server that wraps ifcquery and ifcedit, holding an IFC model in memory across tool calls. It is the preferred way to interact with IFC models from AI assistants and MCP-compatible clients.
Setup:
claude mcp add --transport stdio ifc -- python3 -m ifcmcp
Session tools: ifc_load, ifc_save
Query tools: ifc_summary, ifc_tree, ifc_info, ifc_select, ifc_relations, ifc_clash, ifc_validate, ifc_schedule, ifc_cost, ifc_schema, ifc_contexts, ifc_materials, ifc_plot, ifc_render, ifc_shape, ifc_shape_list, ifc_shape_docs
Edit discovery: ifc_list, ifc_docs
Edit execution: ifc_edit, ifc_quantify
The model stays in memory between calls - ifc_edit does not auto-save; call ifc_save explicitly when done.
Depends on both ifcquery and ifcedit
Generated with the assistance of an AI coding tool.
ifcedit is a new command-line tool for executing ifcopenshell.api mutations from the shell. It wraps the entire API surface — any function callable via ifcopenshell.api can be invoked without writing Python.
Subcommands:
list [module] — list all API modules, or functions within a module
docs <module.function> — full documentation (params, types, descriptions)
run <file> <module.function> [--param value ...] — execute a mutation; overwrites input file by default, or use -o <output> to write elsewhere; --dry-run validates without executing
quantify list — list available QTO rules
quantify run <file> <rule> — run quantity take-off, writing IfcElementQuantity psets back to the file
Parameter coercion: entity references can be passed as step IDs (strings); lists, dicts, booleans, and None are handled automatically.
Usage:
python3 -m ifcedit run model.ifc root.remove_product --product 42
python3 -m ifcedit docs geometry.edit_object_placement
Generated with the assistance of an AI coding tool.
ifcquery is a new command-line tool for querying and inspecting IFC models. All output is JSON.
Subcommands:
summary — schema version, entity counts, project metadata
tree — full spatial hierarchy (Project → Site → Building → Storeys → Spaces → Elements)
info <id> — deep inspection of any entity by step ID (attributes, psets, placement matrix, type, material)
select <query> — filter elements using ifcopenshell selector syntax
relations <id> — relationships for an element; --traverse up walks to IfcProject
clash <id> — geometric intersection and clearance detection
validate — schema/constraint validation; --rules adds EXPRESS checks
schedule — work schedules with nested task trees
cost — cost schedules with nested cost item trees
schema <class> — IFC class documentation from the model's schema version
plot — SVG plan drawing
render — 3D geometry rendering
contexts — geometric representation contexts
materials — material assignments
Usage:
python3 -m ifcquery <file.ifc> <subcommand> [args]
Generated with the assistance of an AI coding tool.
* Add geometry.clip_solid API
* Add geometry.clip_solid_bounded API
* Add geometry.copy_representation API
Deep-copies the named representation from a source element to a target
element.
Generated with the assistance of an AI coding tool.
* Doc clarification for api.sequence.assign_process
* Doc clarification for api.geometry.edit_object_placement
* Doc clarification for api.feature.remove_feature
* Doc clarification for api.geometry.add_wall_representation clippings normal
* regenerate_wall_representation: document BBIM_Boolean preservation requirement
Generated with the assistance of an AI coding tool.
* shape_builder: complete docstrings and return type annotations
* shape_builder: warn about mixed item types in get_representation
* shape_builder: fix half_space_solid agreement_flag docstring
Generated with the assistance of an AI coding tool.
Add tests for all four covering generation operators: flooring/ceiling
from walls and flooring/ceiling from cursor. Previously only flooring
from walls was tested.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The covering tool used bmesh as an intermediate and relied on
type.assign_type post-listeners (removed in 44a52863a) to generate
the IfcExtrudedAreaSolid body. With those listeners gone, coverings
had no body representation and assign_swept_area_outer_curve crashed.
Build covering representations from scratch using ShapeBuilder, reading
the extrusion depth from the type's IfcMaterialLayerSet. Also replace
bpy.ops.bim.assign_class with bonsai.core.root.assign_class using
should_add_representation=False, consistent with the space fix.
Refactored shared coordinate-conversion and extrusion-building logic
into get_2d_vertices_from_polygon and set_extrusion_representation_from_polygon,
used by both space and covering code paths. Removed all bmesh-dependent
dead code from the spatial tool.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Space regeneration was only updating the Blender mesh and marking the
object as edited, but the IFC representation was never synced on save.
Replace the bmesh-based approach with ShapeBuilder to write geometry
directly to IFC as an IfcExtrudedAreaSolid, then reload via
switch_representation. This applies to both new space creation and
existing space regeneration.
Also changes assign_ifcspace_class_to_obj to call
bonsai.core.root.assign_class directly with
should_add_representation=False instead of bpy.ops.bim.assign_class.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The nest assign_object API now removes existing spatial containment and
aggregate relationships before creating the nest, matching the behavior
documented in its docstring and consistent with aggregate.assign_object.
Fix#7248
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Walk up the full hierarchy via get_parent() in can_nest() and
can_aggregate() to reject assignments that would create a cycle.
Also reject self-assignment.
Fix#7248
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After assigning an IFC class and undoing, msgbus subscriptions registered
with the old Python object wrapper survived (PERSISTENT flag) but could
not be cleared because: (1) rollback_link_element looked up objects by
their post-link name which no longer exists after undo, and (2) the
per-object clear_by_owner calls in rebuild_element_maps used new Python
wrappers that didn't match the old subscription owners.
Fix by using a dedicated stable object (object_subscription_owner) as
the msgbus owner for all per-object subscriptions, allowing
rebuild_element_maps to clear all stale subscriptions in one call
regardless of Python wrapper identity changes during undo/redo.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `is not ...` was being captured by the walrus assignment due to
missing parentheses, causing the condition to always evaluate incorrectly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
remove_representation_item now returns early if removing the item would
leave Items empty. edit_text_literals returns early on empty attributes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
add_boolean was removing second operands from ALL IfcShapeRepresentations
that referenced them, which could corrupt unrelated shapes and leave
representations with empty Items (bug #7803).
The API no longer modifies Items — callers manage this explicitly.
validate_type and Bonsai's AddBoolean operator now handle their own
item removal scoped to the correct representation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
remove_deep is deprecated and can silently delete elements still in use.
remove_deep2 requires zero inverses before removal, making it safer.
Also fixes a double-removal bug in remove_grid_axis and prevents
removing the last prop template from a pset template.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests using custom first/last tread runs were not setting
custom_tread_lock=False, so the custom values were silently ignored
since 8f7cf76d9 introduced the lock gate in the calculation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SyntaxWarning: invalid escape sequence '\/' at line 312.
`\/` in a plain string is treated as `/` by accident; replaced with raw string r"..." to be explicit.
- `function_item`, `tags` added in df7318973
- MakeVolume added in c385b93, ignore as all other conversion settings
- moved `SeparateZUpNode` ignore to the other geom serializer settings
Mentioned in https://github.com/IfcOpenShell/IfcOpenShell/issues/7667#issuecomment-4076645173
Traceback:
```
Traceback (most recent call last):
File "\bonsai\bim\module\patch\prop.py", line 55, in get_ifcpatch_recipes
docs = ifcpatch.extract_docs(f, "Patcher", "__init__", ("src", "file", "logger", "args"))
File "\ifcpatch\__init__.py", line 168, in extract_docs
spec.loader.exec_module(submodule)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "<frozen importlib._bootstrap_external>", line 1027, in exec_module
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "\ifcpatch/recipes/FixRevit2025TINs.py", line 31, in <module>
class Patcher:
...<509 lines>...
return co / self.unit_scale
File "\ifcpatch/recipes/FixRevit2025TINs.py", line 168, in Patcher
def create_edges(self, obj: bpy.types.Object) -> None:
^^^
NameError: name 'bpy' is not defined
File "\bonsai\bim\module\patch\prop.py", line 43, in get_ifcpatch_recipes
```
To fix warnings below:
```
<python-input-1>:1: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
```
Closes#7787: Previously bim.assign_selected_as_product required exactly
2 objects. With multiple annotations referencing the same
product, users had to repeat the operation once per
annotation. Now any number of IfcAnnotations can be selected
alongside a single product object and all are assigned in
one operation and one undo step.
Generated with the assistance of an AI coding tool.
* buildingSMART Data Dictionary module: added textfield to change data dictionary url
* moved change of bsdd baseurl change to addon settings
* Receiving Psets from other dictionary sources has been made available by dynamizing the identifier_url using the client baseurl
* Remove unnecessary blank lines in prop.py
* Remove unused import of bsdd module
* Fix#7712 - global alignment controls now affects all literals
* Fix#7760 - goodbye 3x3 box alignment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previous logic always skipped the first point. Instead, it should only
skip when actually closing a loop (i.e. >= 3 points).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Since we added more data to debug info in fcf5614 Fatal Error itself started to fail and was never displayed due some props being inaccessible during load, should be fixed now.
Possible error that were fixed:
```
File "\Blender\5.1\extensions\raw_githubusercontent_com\bonsai\__init__.py", line 328, in <module>
print(format_debug_info(get_debug_info()))
~~~~~~~~~~~~~~^^
File "\Blender\5.1\extensions\raw_githubusercontent_com\bonsai\__init__.py", line 117, in get_debug_info
if bpy.data.is_saved:
^^^^^^^^^^^^^^^^^
AttributeError: '_RestrictData' object has no attribute 'is_saved'
Traceback (most recent call last):
File "\Blender\5.1\extensions\raw_githubusercontent_com\bonsai\__init__.py", line 366, in draw
info = get_debug_info()
File "\Blender\5.1\extensions\raw_githubusercontent_com\bonsai\__init__.py", line 152, in get_debug_info
bim_props = tool.Blender.get_bim_props()
^^^^
NameError: name 'tool' is not defined. Did you mean: 'bool'?
Traceback (most recent call last):
File "\Blender\5.1\extensions\raw_githubusercontent_com\bonsai\__init__.py", line 366, in draw
info = get_debug_info()
File "\Blender\5.1\extensions\raw_githubusercontent_com\bonsai\__init__.py", line 141, in get_debug_info
import bonsai.tool as tool
File "\Blender\5.1\extensions\.local\lib\python3.13\site-packages\bonsai\__init__.py", line 355, in <module>
print(format_debug_info(get_debug_info()))
~~~~~~~~~~~~~~^^
File "\Blender\5.1\extensions\.local\lib\python3.13\site-packages\bonsai\__init__.py", line 141, in get_debug_info
import bonsai.tool as tool
File "\Blender\5.1\extensions\.local\lib\python3.13\site-packages\bonsai\tool\__init__.py", line 23, in <module>
from bonsai.tool.attribute import Attribute
File "\Blender\5.1\extensions\.local\lib\python3.13\site-packages\bonsai\tool\attribute.py", line 31, in <module>
import bonsai.bim.helper as helper
File "\Blender\5.1\extensions\.local\lib\python3.13\site-packages\bonsai\bim\__init__.py", line 28, in <module>
from . import handler, operator, prop, ui
File "\Blender\5.1\extensions\.local\lib\python3.13\site-packages\bonsai\bim\handler.py", line 36, in <module>
from bonsai.bim.module.aggregate.decorator import AggregateDecorator
File "\Blender\5.1\extensions\.local\lib\python3.13\site-packages\bonsai\bim\module\aggregate\__init__.py", line 21, in <module>
from . import operator, prop, ui
File "\Blender\5.1\extensions\.local\lib\python3.13\site-packages\bonsai\bim\module\aggregate\operator.py", line 32, in <module>
class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
^^^^^^^^
AttributeError: partially initialized module 'bonsai.tool' from '\Blender\5.1\extensions\.local\lib\python3.13\site-packages\bonsai\tool\__init__.py' has no attribute 'Ifc' (most likely due to a circular import)
```
Blender doesn't have it's own quick favorites manager and working with them can be not very flexible - you can add them in context menu and remove them from Quick Favorites menu. But you can't reorder them, you can't rename them and you can't even add a new button to favorites if it's not added by some addon in the UI.
Have been stumbling upon this for awhile and decided to create an experimental manager UI for this. Things it can do:
- help user create a button with any operator in Blender and properties they prefer to then save it Quick Favorites. Which seems can be very useful in Bonsai, since you can create separate buttons for all kinds of selectors expressions, class assignment or other operators.
- it can import quick favorites from user's actual current quick favorites, so they can just modify them a bit, reorder, rename and then add them again.
- Since quick favorites are not exposed to Python API in Blender, we're using a very hacky way to retrieve them from Blender and don't provide our own buttons for adding and removing quick favorites, as it may be dangerous and even more hacky in implementation. So the workflow for user is to either generate some buttons and add them to quick favorites using Manager or to import it's own quick favorites, then change them how they like, then remove quick favorites using usual quick favorites menu and then add new button one by one.
Small demo - https://files.catbox.moe/vyffp6.mp4
Refactored methods for accessing objects in linked models and added a simple operator to select object in linked model by providing guid.
A quick demo - https://files.catbox.moe/sjjw37.mp4
* Fix#7718: Fix FallDecorator label calculation for all slope annotation types
- Fix wrong dict key type in decoration.py: DecoratorData.data["fall"] is
keyed by obj.name (str) but was looked up with obj (Object), causing
object_type to always be None
- Apply obj.matrix_world transform to spline points before computing rise/run
in both decoration.py and svgwriter.py; local coordinates have Z=0 for flat
annotations, world coordinates correctly reflect elevation change
- Use hypotenuse (segment_length) instead of run as the denominator for
SLOPE_FRACTION label display
Generated with the assistance of an AI coding tool.
* docs: add BonsaiPR bleeding edge installation section
Add new section to installation.rst documenting the BonsaiPR
community build, including why it exists, how the automated
PR-merging system works, installation steps with automated
updates, manual installation, and the PR workflow for
contributors.
Generated with the assistance of an AI coding tool.
* whoops
Linking flags were missing for `MODULE` type libraries, example warning: `IfcPythonPYTHON_wrap.obj : MSIL .netmodule or module compiled with /GL found; restarting link with /LTCG; add /LTCG to the link command line to improve linker performance`
Which is useful when debugging and calling tools.ps1 directly - less thing to modify to make it work.
Also replaced `exit 0` with `return`, so it would be possible to reuse functions inside `tools.ps1`
To avoid running in a situation when some builds are using one tag and some are using another and then unstable repo script fails to find builds for some platforms.
Objects with hide_select=True could not be selected during
isolation, causing hide_view_set to incorrectly hide them.
Objects with hide_viewport=True had their H-key hide state
modified as a side effect of hide_view_clear/hide_view_set.
Both are now left unaffected by bim.activate_drawing.
Generated with the assistance of an AI coding tool.
Use EPset_Parametric.LayerSetDirection exclusively to
determine horizontal vs vertical layer rendering in type
thumbnails, rather than hardcoding IfcSlabType checks.
Also fix line drawing to use the is_horizontal flag
consistently.
Generated with the assistance of an AI coding tool.
Fix Sphinx docs: replace csv-table with list-table for formatting functions
The documentation table of formatting/query functions was not rendering
because `.. csv-table::` requires strict RFC4180 CSV escaping. The table
contains nested quotes, inch marks (e.g. `3' - 0"`), backticks, and code
examples, which cause the CSV parser in docutils to treat rows as malformed
and drop the entire directive.
Replaced the directive with `.. list-table::`, which parses reStructuredText
instead of CSV and safely supports inline code, quotes, and multi-line cells.
Also moved the examples text outside the directive block and ensured a blank
line after the table so Sphinx does not interpret following paragraphs as
table rows.
No content changes — documentation now renders correctly.
Generated with the assistance of an AI coding tool.
* Enhance AddReferenceImage operator to use file browser instead of independent popup dialogue
* Fix dimensions assertion in TestAddReferenceImage
* Remove error in return in _execute (it is not execute)
* Add IFC2X3 support to AddReferenceImage
* Adde unit="LENGTH" to the x/y properties (every length dimension everywhere in the UI is in project length units. No need to say it explicitly)
* Manually create the texture always, not just for IFC2X3
* Add poll method to AddReferenceImage operator to check for loaded IFC project
* Refactor AddReferenceImage to add representation manually following pattern in root/operator.py's bim.add_element
* Improve File explorer options between new and select from existing project Ifc Reference Images
* Refactor get_existing_reference_images to use selector for filtering image annotations
* No extra args needed after should_add_representation is False
* Doing clean=True deletes everything
* Don't manually add geometry and materials, don't call bpy.ops. Only create IFC data, then use preexisting loading functions to create geometry.
* Black formatting, also now we can start to remove this operator as it becomes obsolete
* Consolidate duplicate UV generation into Loader.load_generated_uv_map
Replace 3 identical XY-UV baking blocks (create_object IMAGE,
bm_add_image_plane, ImageScalingTool) with a single reusable
classmethod in tool.Loader.
* Fix IFC4 texture display in Solid viewport Texture mode
IFC4 IfcTextureCoordinateGenerator Mode=COORD is used, load_texture_maps
falls back to load_generated_uv_map to bake XY-UV data onto the mesh.
* Fix IFC2X3 texture display
* This looks wrong
* Remove legacy override image feature, because we now have a proper styles and texture manager
* Remove legacy override existing image element, because we now have a dedicated styles texture manager
* Remove unnecessary roundtrip to bmesh and mesh
---------
Co-authored-by: Dion Moult <dion@thinkmoult.com>
Guidelines for external contributors using AI coding tools,
covering licensing, AI disclosure requirements, PR scope,
commit style, code formatting, and testing expectations.
Generated with the assistance of an AI coding tool.
Previously, there was a dance between invoke, execute, and draw. This
can probably be resolved, but is a high-risk for undo bugs. This
simplifies the logic flow to just a traditional _invoke -> _execute.
I add a new feature test to at least make sure it does something, and
this also fixes the segfault in tool tests as it no longer requires the
launching of the file browser.
In my previous commit, I mistakenly believed that there was an API change from
snap_angle_increment to snap_angle_increment_3d
But since the feature was introduced in blender 4.2 the setting is called
snap_angle_increment_3d.
Previously it was implemented inline. This now implements it as a
tool.Blender function with tests. Also the previous tests didn't
actually run and weren't actually testing any tools despite being in a
tool tests.
IfcSectionedSurface and IfcSectionedSolidHorizontal both of CrossSectionPositions attributes which are lists of IfcAxis2PlacementLinear. The implementation of each class used its own bespoke mapping of IfcAxis2PlacementLinear, which were identical to each other and slightly different than IfcAxis2PlacementLinear. Now the two sectioned classes use the one and only mapping for IfcAxis2PlacementLinear
* Linked IFC projects enhancement (multiple links to same project file)
- Implement link management system using UUIDs as identifiers to support multiple links to the same IFC file
- Add georeferencing compatibility detection and UI display (NONE, NOT_COMPATIBLE, PARTIAL_COMPATIBLE, FULL_COMPATIBLE)
- Support for duplicate link creation with Shift+D shortcut and automatic position offset
- Add false origin and project north calculation from 3D cursor for MANUAL mode
- Only store one cache per file, regardless of the amount of links
- Prevent duplicate links based on filepath and position comparison
- Improve error handling for missing files and loading failures
- Update tests
* Remove duplicate georef UI
I try to avoid duplicate UI (especially for one that can be as
sophisticated as georef - e.g. missing is WCS) as it means double the
code, double the tests, potential user confusion. BTW the note about
vertical datum isn't quite accurate as it may be included in the CRS
definition so vertical datum is optional.
* Remove depsgraph_update_post handler for update_link_ui_on_transform as per core developer feedback
* Move get_projected_crs to geolocation module
* Refactor get_projected_crs to simplify as per core developer feedback
* Remove unused import of bonsai.tool from project module
* Use IfcDocumentInformation per linked file and IfcDocumentReference for locaiton information
* Refactor SaveBlendMetadataFile operator to remove try-except blocks and remove linked projects collections since they are recreated by bonsai
* Cleanup removing empty collection instances for linked models in metadata.blend file and call determine_georeferencing_compatibility on link reload
* Add locking mechanism for linked models and update UI to reflect lock status
* Update logic that track IFC to execute_ifc_duplicate_operator instead of having it in execute() which does not track IFC undo/redo
* Refactor link handling to use get_link_empty_handle and set_link_empty_handle methods which in turn use the standard blender-ifc integrations patters (tool.Ifc.get_object(doc_reference) and tool.Ifc.link(doc_reference, empty_handle)
* remove operator.DuplicateLink and move it to tool.Project.duplicate_link()
* Refactor link handling to use sequential identifiers (no need for STEP ID DocRef)
* Refactor IFC linking logic to handle cases without a parent IFC file loaded. Firts link flase origin becomes parent origin
* Lock should not affect selection.
This makes it consistent with grid / spatial lock, and also toggle
selectability is already implemented.
* Remove unnecessary check for loaded library as Blender seems to do this internally already
* Rename util to get_crs because in IFC4X3 you can also have geographic CRS not just projected
* Remove unnecessary call to determine_georeferencing_compatibility
This function is already always called prior to calculate_link_position
so shouldn't be called here. It's also a very expensive function: as it
currently stands, just to link a single IFC, ifcopenshell.open() is
called 3 times. This reduces it to 2.
* Store CRS as metadata for linked models, and compare metadata when indicating georeferencing compatibility
Previously, to check georeferencing compatibility, ifcopenshell.open()
was used. When linking large models, this adds considerable time and
memory usage. This instead captures the georef as standard metadata in
our .cache.json. This now reduces the ifcopenshell.open() calls back
down to only 1 as necessary (see previous commit).
* Use link index instead of link name to fetch link collection item
Link name runs into issues with name uniqueness. This is why you created
a function for "get next link ID". After this refactoring, we can no
longer worry about uniqueness and that function may be removed.
* Simplify reloadlink into just unload and reload (with cache disabled)
This function should not be responsible for editing any data.
* Remove unnecessary get_next_link_id as names no longer need uniqueness
This now frees up the name variable to track a more meaningful, human
name like IfcDocumentInformation's Name attribute.
* Rewrite get / set link_empty_handle to just use the link directly
This prevents needless logic to fetch the link and also removes issues
related to duplicate names.
* Temporarily remove logic in prop callback
Right now, pretty much all the logic is done in a prop callback. In
general logic in prop callbacks should be minimised, since it's hard to
test and easily triggered as a domino effect of another change, and may
also impact undo/redo.
* Remove code that unnecessarily removes cache
This code removes cache, which means any project unlinking an IFC auto
clears the cache for any other project which doesn't make sense, and
also breaks the ability to readd it quickly.
* Rewrite link, unlink, load, and unload IFC
There were a few issues tackled here:
- Operators that change any IFC data must use tool.Ifc.Operator and
_execute, otherwise undo/redo will break. That's one of the risks of
using prop callbacks, as it is not explicit when an IFC edit happens.
- The usage of IfcDocumentReference was not correct. The Location
should store the URL, _not_ the position. The position should be in the
Identification attribute.
- The URL was stored in IfcDocumentInformation location, which does not
work in IFC2X3. There are a few changes here to make it IFC2X3
compatible.
- Generally move logic in operators, not prop callback.
* Remove restriction around manual mode.
Users should be able to use manual mode if they want.
* Restore AUTOMATIC mode to identical behaviour to file open
This is the first step to reusing cache files agnostic of the host.
* Revert tests for a fresh start for updating tests
* Revert "test_feature - clean up .ifc.cache. files after test was executed"
This reverts commit 99ae768ddf.
* Update tests and reimplement calculations for matrix of empty handle
Previously, the empty would always be placed at the origin, unless a
"position" offset was present. This is a problem, because the "position"
is simply a local offset relative to the Blender cache! If the cache was
regenerated, the offsets would be outdated. Also, the cache appeared in
different locations depending on the false origin mode, so the offset
would mean different things to different people.
Instead, a more robust method is:
1. When you link a file, a Blender cache is generated. The Blender
origin of this cache is arbitrary! It depends on the user's false origin
mode and is purely a Blender session specific thing.
2. When you load a link, a link is _always_ loaded into the correct
location with regards to IFC global coordinates. All math is done from
the perspective of IFC.
3. If you choose to transform (move / rotate / scale!?) this link from
its correct location, that gets recorded as a 4x4 transformation matrix.
Note: I haven't implemented this properly yet.
Tests all pass, with a minor modification to the new behaviour that
false origin mode now won't affect the location it ends up in, only the
generation of the cache.
* Remove arbitrary convention around display name
Not needed anymore now that A/M/D is a detail and not significant on
actual coordinates, and also that the UUID is no longer needed.
* Simplify implementation of loading linked models when opening an IFC
* Move link matrix calculation from operator to tool for reuse
* Implement editing link location and calculation of transformation matrix
I changed my mind on the is_locked thing, since it isn't clear to the
user that locking need to be done to save changes.
* Remove old is_locked, prop update callback no longer needed (dedicated operator instead), remove old calculation code
* Simplify code related to placed_as_per_georef
* For now, simple skip for duplicate / delete
IMO duplicate / delete / move a link are very rare and explicit
operations.
* Update tests
* Remove host_model coordinate data as cache is no longer host model dependent
* Move icons outside list because there are too many
* Minor tweaks
---------
Co-authored-by: Dion Moult <dionmoult@gmail.com>
Co-authored-by: Dion Moult <dion@thinkmoult.com>
IfcOpenshell read file, and write file without changes. This round trip introduces truncation noise. It should not hurt to increase the precision to keep this clean.
Replace hardcoded 5-degree angle snapping with Blender's
snap_angle_increment setting in create_wall_from_2_points()
and create_profile_from_2_points().
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace hardcoded 5-degree angle snapping with Blender's
snap_angle_increment setting in handle_lock_axis() for:
- Initial angle rounding when locking axis (A key)
- Angle rounding and increments on Shift+Wheel scroll
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace hardcoded 5-degree angle snapping with Blender's
snap_angle_increment setting in calculate_distance_and_angle().
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This function retrieves the angle snap increment from Blender's
tool_settings.snap_angle_increment property, which was added in
Blender 4.2. This allows users to configure the angle snap value
through Blender's native UI instead of using hardcoded values.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, sort, reverse list, and join functionality was implemented
as special cases in Bonsai itself. Given that it has usecases
(especially in material lists, but any sort of list applies) I've moved
this function into the IOS formatting language.
The IOS formatting language previously wasn't capable of this, but the
awesome addition by @falken10vdl made the formatting language accept
queries inline, so that means it can handle lists. I also added tests
for all the new functions and expression syntax (+-*/ operators).
I simplified the code that gets the evaluated text literal - previously
it seems to call format() multiple times.
Previously, copy attribution was coupled with text editing. This meant
that you couldn't just do something like change the font or alignment
without also affecting literals. Now like most apps you can just select
bunch of text and change font size etc, using the same UI look and feel
that copying attribute has when editing attributes.
This refactor also removes the need for explicit props tracking each
possible attribute to copy, and the settings collection group. Bulk
applying is now done in core with no calls to UI.
Turned out `aud` module we had in our makefile had nothing to do with Blender built-in `uad` module 🫣
So no need to install anything from PyPI since this module is generally available in Blender
This isn't complete yet, but it hopefully demonstrates a preferred
implementation:
* Logic in core, not operator
* Loop done in core, without needing to call other core functions, so
the overhead of enabling and disabling editing per object is removed. No
more Blender logic, just straight editing in IFC.
* Reuse existing function to grab text attributes instead of
reimplementing it twice.
* Remove dead code, there seems to be a function
apply_to_selected_objects which was completely unused and duplicated
code twice.
Fix issue where selected text annotations remained in editing mode after
applying changes. Now properly restores original editing state for each
selected object.
Sync children was a bit odd because it's not actually an "array
parameter" per se, just a way to regenerate. It's now an operator.
There was a deeper issue I encountered where the way arrays work is that
they duplicate the parent element. (first encountered in e51d2d )
However, the duplication code has special array handling too. To avoid
issues with this cyclical coupling the previous solution was to
reimplement object duplication (with all sorts of pitfalls that has).
Now, I've tried to decouple it further by clearing all array psets prior
to any change, and readding the pset after everything has been
regenerated.
This can be improved upon but I don't feel confident until there is more
comprehensive test coverage for the duplicate operator.
This has a few advantages:
- The collection logic is centralised
- The collection logic is configurable based on the collection mode
- The name is not hardcoded
This reimplements @theoryshaw 's commit 9adbd4 but has a few upgrades:
- Considers all parent / child relationships, not just aggregates
- Puts business logic in core where it belongs and tool code in tool
- Uses existing utils where possible like get_decomposition
- Does not use name based collection checking which is fragile
- Reuses tool.Collector
- Makes container assignment handle the API's capability to do things
in bulk instead of one by one in a loop, so it's faster
- Tests
This is a cool idea, but users have all sorts of Blender collection
strategies and I think it's a good idea for Bonsai code to just touch
Bonsai's stuff and leave everything else. Separate functionality can be
built for non-Bonsai workflows and preferrably in a more discoverable
way than in individual Bonsai features.
When joining objects with mismatched representation types, deselect and
skip incompatible objects rather than canceling the entire operation.
This allows users to join compatible objects even when the selection
includes incompatible ones.
The previous fix by @theoryshaw had the correct conclusion but had some
issues:
1. Don't implement anything that fetches IFC data in the UI draw calls.
This historically has lead to race conditions and crashes. The data
classes are much preferred.
2. Rather than load the inapplicable attribute then selectively omit it
in the UI, it's preferred to just never load it in the first place.
Reuse tool.Geometry.duplicate_ifc_objects because that's where all
duplication logic should stay instead of half reimplementing it every
time which introduces subtle bugs.
I generally like to minimise knobs and dials, so this is now part of the
wizard but now defaults to a "NONE" unit. You also now have the option
to choose "NONE" for area / volume units which makes things consistent.
Enum names also match the IFC lowercase convention for conversion based
units. This also simplifies the core logic and treats all units the same
way instead of special cases for each unit type. (length is still
special and required in Bonsai as we are inherently graphical)
- is_skipped is calculated upstream instead of reimplementing logic
- the viewer now shows cardinality (not just in edit mode) to make it
clear what the requirements are
- prohibited specs are not allowed to have any requirements, so that
section is hidden
- failed prohibited specs now shows list of failed (applicable)
entities
The report_specification() method in the Json reporter class was raising
an UnboundLocalError when processing IDS specifications with certain
minOccurs/maxOccurs combinations that weren't explicitly handled.
Problem:
The cardinality variable was only assigned for three specific cases:
- minOccurs=1, maxOccurs="unbounded" → "required"
- minOccurs=0, maxOccurs="unbounded" → "optional"
- minOccurs=0, maxOccurs=0 → "prohibited"
However, the IDS schema allows other valid combinations such as:
- minOccurs=0, maxOccurs=1 (commonly used for optional specifications)
- minOccurs=1, maxOccurs=1 (exactly one occurrence required)
- Any other valid XML Schema cardinality values
When processing IDS files with these combinations, the cardinality
variable remained unassigned, causing an UnboundLocalError at line 382
when attempting to use it in ResultsSpecification().
Solution:
Added fallback logic to handle all valid IDS cardinality combinations:
- If minOccurs >= 1: cardinality = "required" (must occur at least once)
- Otherwise (minOccurs == 0): cardinality = "optional" (may occur)
This maintains semantic compatibility with the existing codebase, which
expects cardinality to be one of the semantic strings ("required",
"optional", "prohibited") rather than numeric ranges. This is critical
for:
- HTML template rendering (line 457: .capitalize())
- Conditional logic for skipped specs (line 454)
- UI rendering for prohibited specs (line 456)
Testing:
- Tested with IDS file containing minOccurs=0 without explicit maxOccurs
(defaults to 1 per XML Schema specification)
- Validation now completes successfully without UnboundLocalError
- HTML report generation works correctly with semantic cardinality labels
- Maintains backward compatibility with existing IDS files
Fixes: Validation failure when using valid IDS cardinality combinations
* black .
* add typing
* move function
* add get_class_relations
* add test for class relation
---------
Co-authored-by: Dion Moult <dionmoult@gmail.com>
Demo - https://files.catbox.moe/extsxf.mp4
But now it requires ifcsverchok graph to be nested inside a subgroup because we need a way to indicate which graph parameters are important, so user can modify them from Parametric Geometry tab without actually explicitly opening sverchok graph.
This reverts commit 96fe9b5398.
This commit contains a logical error where hide_viewport is checked in
the if statement but hide_set() is used as the effect. This causes a
regression in tests about project visibility. `hide_viewport` is the
correct hiding setting to use, because hide_set is more ephemeral and
affected by Shift-H, Alt-H etc.
Previously, when the add occurrence modal operator was executed, on
every modal loop (i.e. every mouse movement) it would fetch the mesh
geometry to be previewed, store the verts / edges / faces in mesh
collections, then the decorator would fetch that geometry, the clear the
collections, in a loop.
I've removed the Blender collections. Instead the same strategy is used
as in ItemDecorator i.e. the mesh and verts are fetched once during
decorator installation, then on each draw call only a single vertex loop
to multiply by the transformation matrix for snapping and mouse
position.
You can test with the LOD400 model in #7566. On my machine it would
cause lag on anything with >500 faces. Now it seems to work without lag
on a 26k polygon mesh.
Although it is a very cool trick, I feel this essentially duplicates the
UI in two spots, and is an outlier in UX. I'd prefer for all IFC data
and relationships to be in one location only (the panels). I think there
are a better unified solutions (e.g. favourite bookmark panels) for
quick access for things like this.
To be consistent with all other settings, I've moved the visibility
config UI from inline into the add-on settings. This restores the
previous tab layout and no longer needs the "settings" icons to be
there. This also removes the need for a "enable UI config" checkbox.
Most of the code previously had dedicated operators to toggle booleans.
This has been removed. This new approach also means helpers aren't
needed.
Previously calculation of visibility was done on every draw (3x 10tabs x
10 collection items). The data class is intended to calculate UI data
once only which is more efficient. This also removes all helper calls
from the UI.
When assigning a container to an aggregated element, automatically promote
the operation to the root aggregate and move all nested parts to the
container's collection in the Blender outliner.
Changes:
- AssignContainer now traverses the aggregate hierarchy to find the root
aggregate when a user selects any nested part
- All parts and sub-aggregates are moved to the container's collection in
the outliner while preserving IFC aggregate relationships
- Parts remain aggregated in IFC (not directly contained), only their
Blender collection membership changes
- RefreshLinkedAggregate now also moves all parts to the correct container
collection when restoring original data
This provides a more intuitive UX - users can select any part and the entire
assembly moves together, properly organized under the spatial container.
Fixes the previous behavior where:
- Aggregated elements were skipped with a warning
- Parts weren't organized under the container in the outliner
- Aggregate nesting was broken after container assignment
- Auto-include all parts when duplicating aggregates
- Preserve nested aggregate relationships during duplication
- Select all duplicated objects for immediate moving
- Add dual-rotation support for AXIS3 slabs (IFC angle + object rotation)
- Fix profile editing to display horizontal projection for tilted slabs
- Fix AXIS2 layer slicing to use local extrusion direction for walls
- Fix ChangeExtrusionDepth to refresh geometry after depth changes
- Remove rotation lock on slabs to allow free rotation
- Fix undefined variable bug in add_slab_representation.py
This commit introduces a new BBIM_MaterialLayer property set to persist custom
material layer offset settings in IFC files, along with significant UI improvements
for material editing.
Features Added:
- New BBIM_MaterialLayer pset with properties:
- UseCustomOffset (bool): Toggle for custom offset
- CustomOffset (float): Offset value in SI units
- CustomWallReference (str): Wall reference point (EXTERIOR/CENTER/INTERIOR)
- CustomSlabReference (str): Slab reference point (TOP/MIDDLE/BOTTOM)
Tool Updates (tool.py):
- Added save_custom_offset_to_pset(): Saves custom offset from UI props to pset
- Added load_custom_offset_from_pset(): Loads custom offset from pset to UI props
- Updated get_material_layer_custom_offset(): Reads from pset when props unavailable
Operator Updates (operator.py):
- EnableEditingAssignedMaterial: Loads custom offset from pset on edit start
- EditAssignedMaterial: Saves custom offset to pset on edit completion
- Fixed KeyError for CardinalPoint in material constituent sets
Data Layer (data.py):
- Added bbim_material_layer_pset() to ObjectMaterialData for caching pset data
- Improves performance by avoiding repeated IFC queries during UI drawing
UI Improvements (ui.py):
- Added custom offset display in both editable and read-only material UIs
- Added OffsetFromReferenceLine display in read-only UI
- Implemented dynamic headers based on material type (Layers/Profiles/Constituents)
- Improved visual hierarchy with consistent boxing and indentation
- Aligned editable and read-only UI layouts for consistency
- Fixed layer set boundary labels (Top/Bottom for slabs, Interior/Exterior for walls)
- Reorganized "Add Material" section into material layers box
Bug Fixes:
- Fixed format_distance() to correctly handle negative imperial values
(e.g., -0.5' now displays as "-0' - 6"" instead of "0' - -6"")
This allows users to set custom material layer offsets that persist in the IFC
file and remain available across sessions, with clear visual feedback in both
editing and viewing modes.
When appending a wall type and a slab type in turn, if their material
layer sets have the same name then the slab type would have a wall
construction. Now the material sets are compared before reusing an
existing material set.
Fixes typo introduced in b4740b6 where `element in MATERIAL_SETS` should
have been `element.is_a() in MATERIAL_SETS`. This resulted in
deduplication of layersets, but not of the layers themselves.
Enable multiple CSS files in stylesheet_path using comma separation.
Files are loaded in order with natural CSS cascading behavior.
Example: "base.css, overrides.css"
"In some versions of Python, instances of classes may have an
__annotations__ attribute. However, this is not supported functionality.
If you need the annotations of an instance, you can use type() to access
its class" https://docs.python.org/3/howto/annotations.html
When editing representation items for elements with IfcMaterialLayerSetUsage
(LAYER2/LAYER3), the depth attribute is now hidden from the UI as it should
not be modified at the item level for these parametric elements.
The check is performed by accessing the parent element through the
representation_obj property in geometry props and checking its material
usage type.
The copy_node_graph() method checks for a "window" key in the context
override, but get_shader_editor_context() wasn't providing it. This
caused the function to return early, preventing external styles from
loading.
Updated get_shader_editor_context() to include the window when the
screen differs from the current context, following the same pattern
used in get_viewport_context().
Changed tolerance from 1e-5 to 1e-4 to account for floating-point errors
introduced by matrix transformations. Fixes section annotations being
incorrectly excluded from drawings when they should be visible.
Moves IfcSpace hiding from collection assignment to after scene
addition, allowing hide_set() to work properly once objects are
in the view layer. Fixes RuntimeError during IFC import.
When duplicating elements, skip assign_body_styles if the element's
constituent materials already have style representations. This prevents
creating redundant IfcStyledItem entities on the geometry when styles
should be inherited from the material definition.
To speed up the process. Previously we were using `/m` which build different targets in parallel, but not .cpp files. So rocksdb could have been building for 30 mins because of this.
sync_references was syncing spatial element placements FROM Blender TO IFC,
corrupting their correct positions. Spatial elements (storeys, spaces,
buildings) often have Blender objects at Z=0 for modeling convenience, but
their IFC placements store absolute positions.
Solution: Skip syncing placements for IfcSpatialElement and IfcGrid types,
as their IFC placement is the source of truth.
Fixes storey elevation corruption during SECTION_LEVEL annotation generation.
- Removed interface.rst (was added in this PR)
- Removed all PNG documentation images that were added in this PR:
- bonsai_customization1.png
- bonsai_customization2.png
- bonsai_interface.png
- bonsai_interface2.png
- bookmarks.png
- gear_tabs.png
These files were not present in the upstream v0.8.0 branch and have been removed to restore the original documentation state.
Force UI redraw to flush pending Blender operations, as Blender was crashing during bulk printing. Not 100% this fixed everything, as there are a few crashes that spring up still, but is better.
Force viewport to use scene camera before rendering underlay. When
print_all was used, viewport remained locked to local camera from
previous drawing, causing OpenGL render to capture wrong view.
Enable importing multiple .svg reference files at once using
standard Blender multi-select (Shift/Ctrl+click).
Includes backward compatibility and test coverage.
After 5d56025 we were using `LibXml2::LibXml2` instead of `LIBXML2_LIBRARIES` for linking, but this target was missing debug variants leading to issues during linking.
Added more gizmos for multi-panel windows and for the door transom.
Support negative dimension values (lining offset for door and window)
Fix railing, stair, and roof being regenerated during UI panel draw instead of on property change
Various code quality changes and DRY improvements
Major changes:
- Relocate gizmo infrastructure from bim/gizmo.py to module/drawing/gizmos.py
- Replace arrow-based property gizmos with dimension line gizmos throughout
- Add view-dependent positioning: gizmos automatically reposition based on camera angle to stay visible and avoid overlapping geometry
- Add special icons for stair to deal with edge cases
Code quality:
- Add DRY helper methods for gizmo positioning across stair, door, and window
- Remove redundant visibility logic
- Extract integer input handling to dedicated module for reuse
- Add comprehensive documentation for gizmo architecture
Add dialog to DuplicateType operator allowing users to set name, description,
and optionally assign the active object to the duplicated type. Add duplicate
button to product UI in type panel with auto-assignment enabled by default.
- You can now input values using the keyboard once you clicked on a gizmo
- Gizmos now support click > move > click in addition to drag and drop (yay carpal tunnel !)
- Optimize snapping performance
- Consolidate door and window type in model.py
- You can now cycle through window and door types using the cycle gizmo
- Pen, validate, cancel, lock, and cycle gizmos are now billboards and follow view direction
- Draggable gizmos are now billboard 2D arrows instead of 3D cones
Implement visual parameter manipulation in 3D viewport with reusable
gizmo components. Features snap-to-mesh (Ctrl), precision mode (Shift),
and real-time feedback. Includes cone gizmos for dimensions,
arc gizmo for swing direction, and icon gizmos for edit controls. Gizmos are globally are individually toggleable in the addon preferences.
May be expanded upon later to add gizmos to other paramaterized elements (window, railing, roof, stairs, array, ...).
Unfortunately gizmos can't tap into the builtin snap system or bonsai snap system which is built for modal operators so gizmos implement yet another custom rather naive snap system.
When importing spatial decomposition, get_storey_elevation() returns values in the project's unit system (e.g., feet), but format_distance() expects values in meters. Added unit_scale conversion (elevation * unit_scale) before formatting to ensure elevations display correctly regardless of project units.
Level annotations were incorrectly converting feet to meters and back,
causing values like 3ft to display as 9'10". Updated format_value() to
accept in_unit_length parameter and pass it to format_distance(), since
Blender's Z coordinates are already in project units.
- Add in_unit_length parameter to BaseDecorator.format_value()
- Set in_unit_length=True in PlanLevelDecorator.draw_labels()
- Set in_unit_length=True in SectionLevelDecorator.draw_labels()
Replaced duplicate format_distance in unit.py with comprehensive MeasureIt-ARCH version from helper.py. Fixed critical floating point errors by changing inPerFoot from 11.9999 to 12.0, eliminating spurious fractions on whole numbers. Implemented auto-precision detection to simplify fractions (64/256 → 1/4) while preserving precise values like 3/256 when needed. Enhanced imperial parsing to support flexible input formats (optional symbols, mixed fractions, leading decimals) and fixed suppress_zero_inches functionality in dimension annotations.
Files saved in previous Blender versions stored position checksums as float64
(24 bytes for location, 72 bytes for rotation), but the current code only
expected float32 format. This caused a shape mismatch error (6,) vs (3,) when
comparing checksums during save operations.
Added byte size detection in is_moved() methods to automatically handle both
old float64 and new float32 checksum formats, ensuring backward compatibility
with files from previous versions.
Fixes ValueError when saving IFC files loaded from older Blender versions.
Unexpected consequence of #7365 - previously `OPENCASCADE_LIBRARIES` supposed to be a list of libraries filepaths, but after refactor `OpenCASCADE_LIBRARIES` end up being both list of library names and list of filepaths.
And since link directories for the names was not provided, it failed to find the libraries to link.
If using OpenCASCADEconfig it wouldn't fail as cmake would evaluate library names as imported target names.
To fix it and simplify it even further, in case of manually provided paths, it now creates `OpenCASCADE_INTERFACE` target and stores it in `OpenCASCADE_LIBRARIES` - target will automatically have all the includes, library names and library dirs.
Since this if-block happens in parentheses, then all `%VAR%` are expanded at the start and then `%ERRORLEVEL%` and `%RET%` values have no relation to the actual result of `git clone`. So if user were (un)lucky, then `%RET%` would be not yet set to "0" (by the previous function call), then it would skip checking out revision.
`GitCloneOrPullRepository` had similar issue, but it was never used anywhere.
`EIgen::Eigen` target used in #7340 for linking exists only if there's a cmake config, while there are cases when there's just include directory. Added same name interface target to mimic config.
It doesn't really matter in this case - it matters really only for FindLibXml2, because `PROJECT_SOURCE_DIR` point to svgfill, when it's config executed, instead of `CMAKE_SOURCE_DIR` - so `CMAKE_SOURCE_DIR ` is never removed from the path, leading to cyclic calls. Adding for HDF5 too just for consistency.
Not sure when it was introduced, but it was still using previous dependency path and leading to confusing error:
```
Boost 1.86.0 already downloaded. Skipping.
Extracting Boost 1.86.0 into L:\Projects\Github\IfcOpenShell\_deps from boost-1.86.0-b2-nodocs.7z
An error occurred
```
Note: rv3d.perspective_matrix should be avoided in these context. Use a numpy version of np.array(window_matrix) @ np.array(view_matrix). See https://github.com/IfcOpenShell/IfcOpenShell/issues/7046
Note2: region_2d_to_origin_3d_np clamp should be used for the snap to work in orthographic view.
Add target view detection to generate_section_reference_points() to create
appropriate geometry for each view type. Plan views get horizontal lines
(clip_segment), section/elevation views get vertical lines (elevate_segment).
Added steps to set 'is_visible' to TRUE and 'is_locked' to FALSE in the 'Spatial Decomposition' panel for the 'Override paste buffer - with active IFC data' scenario. This ensures the test covers visibility and lock state handling for IFC objects.
Example error:
```
TypeError: bpy_struct: item.attr = val: enum "Default" not found in ('city.exr', 'courtyard.exr', 'forest.exr', 'interior.exr', 'night.exr', 'studio.exr', 'sunrise.exr', 'sunset.exr')
```
There was a bug in shading_styles.json where we were using `Default` for `studio_light`, which is only available for `SOLID` shading type. Changed value to `forest.exr`, added code to handle old bugged value since users already copied and possibly modified shading_styles.json
The edit_assigned_product() function now removes the annotation from all
existing IfcRelAssignsToProduct relationships instead of just one, preventing
annotations from being incorrectly assigned to multiple products simultaneously.
This fixes issues where leader lines wouldn't update correctly because the
annotation retained old product assignments.
Previously, when multiple CSS selectors were comma-separated in a rule,
only the last selector received the unique drawing ID suffix. This caused
style conflicts when multiple drawings were placed on the same sheet.
Now all selectors in comma-separated lists receive the unique suffix,
ensuring proper style isolation between drawings.
Example:
Before: .cut.Status-DEMOLISH, .cut.Option-D.d2156 { ... }
After: .cut.Status-DEMOLISH.d2156, .cut.Option-D.d2156 { ... }
In Blender 5.0 some theme attributes were removed and moved to other places and code broke.
In the commit I've also switched the code to less dynamic approach, so static type checker could caught potential errors.
`get_active_stair_calculated_params` wasn't updated when zero tread runs support was added and it's still was relying on a convention that `0.0` custom tread run = no custom tread run.
`s0 = V_(custom_first_last_tread_run[0] or tread_run, tread_rise) + td_vector` was assuming `0` means "no custom first tread run", not it means actually "zero first tread run", so could should be adjusted
before - https://files.catbox.moe/5ovqnh.mp4
after - https://files.catbox.moe/srorl2.mp4
Before f52aafd default `(0,0)` meant that there are no custom tread runs, but after the change it actually means that runs for first and last treads are 0. So default value should be adjusted.
Added explicit `None` value for clarity.
Since it's not illegal in Blender and can be helpful - e.g. when you were assigning class to ifc space, you would lose selection completely and would need to reselect object to continue working on it.
After 3aa5ddb07 `select_products` adds to current selection, instead of resetting it.
So couple tests started to fail since they create new objects and `view_layer` is not yet updated (previously `bpy.ops.object.select_all(action="DESELECT")` was also implicitly updating view layer).
```
FAILED test/tool/test_spatial.py::TestSelectProducts::test_select_products - AssertionError: assert bpy.data.objects['Object'] in []
FAILED test/tool/test_system.py::TestSelectSystemProducts::test_run - AssertionError: assert bpy.data.objects['Object'] in []
```
Long story short - since 5.0 `np.array(Vector())` is now producing `np.float32` instead of `np.float64`. So we have to provide `dtype` explicitly to support both <5.0 and >= 5.0.
See https://projects.blender.org/blender/blender/issues/149283
It's probably will be very popular bug, when someone would try to migrate to Blender 5.0 using old Bonsai.
Example traceback:
```
File "Blender\5.0\extensions\.local\lib\python3.11\site-packages\bonsai\bim\prop.py", line 34, in <module>
import bonsai.bim.schema
File "Blender\5.0\extensions\.local\lib\python3.11\site-packages\bonsai\bim\schema.py", line 23, in <module>
import bpy_restrict_state
ModuleNotFoundError: No module named 'bpy_restrict_state'
```
Noticed testing command from #7277. I believe Serializers it not actually needed to build IfcGeomServer.
Example error:
```
/usr/bin/ld: cannot find -lSerializers: No such file or directory
```
`-Wl,-undefined,suppress` have no meaning in emscripten and they are just ignored. And emscripten is ignoring undefined symbols by default without any flags.
Next to `EXPORTED_FUNCTIONS` and avoid possible issues with pyodide in the future (pyodide is using `SIDE_MODULE=1`, but currently not providing it libraries of type `MODULE`, only for shared ones).
Adding extension, but avoiding adding sources as we're not actually building.
As a result build will have a correct suffix `pyodide_2025_0_wasm32.whl` that's pyodide is currently using and this is pyodide is plan to use when it's going to release to PyPI, see https://peps.python.org/pep-0783/
Documentation claimed that ODS/XLSX output was to a given filename, but
the code actually writes files with generated names in a folder.
(also create the folder if necessary for convenience)
This also deprecates one more instance of the add_representation API
call. As this is slowly cleaned up we can move towards "regenerating"
annotations, not just generating from scratch.
* first attempt to do annotation rotation when dong bulk tags
* Enhance annotation rotation handling based on camera orientation
* handle properly S_G
* black .
* reorder property in UI
* cleanup imports
Remove validate_sheet_files() check in regenerate_sheet() that prevented
regenerating missing LAYOUT files. The validation was blocking the
exact scenario that regeneration was designed to handle.
This allows users to open a bare IFC file with configured drawings and
sheets and have the SVG layouts automatically recreated with drawings
placed at default positions.
Note: now we are using hide_viewport not hide_set anymore. There might
be pending issues from this. Also, now because we use foreach_set, we
can quickly also apply hide_render, which saves time during underlay
generation.
The issue was with ci.yml - it was using system boost shared libraries (since Boost_USE_STATIC_LIBS by default is OFF), but CGALConfig was overriding leading to compilation issues.
Text annotations can now reverse the order of list values (e.g., materials) and use custom separators instead of the default comma-space. Two new properties have been added to EPset_Annotation: Reverse_List (boolean) and List_Separator (string). When text literals contain IFC list/tuple values, they are now detected during variable replacement and can be reversed and joined with the specified separator. The list separator supports escape sequences like \n for newlines, enabling multi-line list displays. These properties are exposed in the text editing UI and properly persist to the IFC file through the new update_text_annotation_properties method, which consolidates all annotation property updates into a single efficient operation.
Not sure why it occurred only now, maybe after recent changes with shared libraries this problem just got more apparent and previously it was masked by some other library after mappings also using IfcGeom.
But the issue was that mapping were not linked against IfcGeom, though it's symbols were used in them. E.g. IfcSectionedSurface.cpp was using `has_loft` from `infra_sweep_helper`.
Issue occurred only on Linux - msvc and clang are less strict and probably do multiple passes to fetch symbols.
Example errors:
```
libgeometry_mapping_ifc4x3.a(IfcSectionedSolidHorizontal.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3::map_impl(Ifc4x3::IfcSectionedSolidHorizontal const*)':
IfcSectionedSolidHorizontal.cpp:(.text._ZN12ifcopenshell8geometry14mapping_Ifc4x38map_implEPKN6Ifc4x327IfcSectionedSolidHorizontalE+0x19a4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3.a(IfcSectionedSurface.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3::map_impl(Ifc4x3::IfcSectionedSurface const*)':
IfcSectionedSurface.cpp:(.text._ZN12ifcopenshell8geometry14mapping_Ifc4x38map_implEPKN6Ifc4x319IfcSectionedSurfaceE+0x19c4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3_tc1.a(IfcSectionedSolidHorizontal.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3_tc1::map_impl(Ifc4x3_tc1::IfcSectionedSolidHorizontal const*)':
IfcSectionedSolidHorizontal.cpp:(.text._ZN12ifcopenshell8geometry18mapping_Ifc4x3_tc18map_implEPKN10Ifc4x3_tc127IfcSectionedSolidHorizontalE+0x19a4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3_tc1.a(IfcSectionedSurface.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3_tc1::map_impl(Ifc4x3_tc1::IfcSectionedSurface const*)':
IfcSectionedSurface.cpp:(.text._ZN12ifcopenshell8geometry18mapping_Ifc4x3_tc18map_implEPKN10Ifc4x3_tc119IfcSectionedSurfaceE+0x19c4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3_add1.a(IfcSectionedSolidHorizontal.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3_add1::map_impl(Ifc4x3_add1::IfcSectionedSolidHorizontal const*)':
IfcSectionedSolidHorizontal.cpp:(.text._ZN12ifcopenshell8geometry19mapping_Ifc4x3_add18map_implEPKN11Ifc4x3_add127IfcSectionedSolidHorizontalE+0x19a4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3_add1.a(IfcSectionedSurface.cpp.o):IfcSectionedSurface.cpp:(.text._ZN12ifcopenshell8geometry19mapping_Ifc4x3_add18map_implEPKN11Ifc4x3_add119IfcSectionedSurfaceE+0x19c4): more undefined references to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)' follow
collect2: error: ld returned 1 exit status
```
Otherwise it can be overlooked if you build IfcOpenShell-Python or any other target specifically, it was only built if you run build-all.py without any target arguments.
This will give you additional styling granularity when working with material layers, allowing you to target elements not just by their material name but also by their material category in your SVG styling.
cmake configs seems to be provided with the default installation, so `find_package` should do the job that `find_path` and `find_library` did previously.
Previously we were setting `HDF5_LIBRARIES` explicitly, but I'm not sure if really worked, since we never set `HDF5_INCLUDE_DIR` and user would have to provide it manually either way.
`find_package(HDF5)` will search for default paths and will set `HDF5_LIBRARIES` and `HDF5_INCLUDE_DIR` automatically.
As it was needed for more recent swig version. Bumped swig in ae771cb without knowing this, so essentially building swig with build-all.py was broken for a bit.
After version 1.13.0 it seems hdf5 stopped publishing builds on the ftp and they also stopped publishing `CMake-hdf5-*` builds.
Switching to cmake, it will also make it consistent with build-deps.cmd
Browser was stuck trying to initialize the environment, because of the error below:
[worker] Received message: {type: 'init', payload: {…}, id: '9dLz/0eBQDa0xTQORXfdUg/0'} :5000/pyodide/pyodide.mjs:1 Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "text/plain". Strict MIME type checking is enforced for module scripts per HTML spec.
They were hardcoded and `:download:` wasn't used, so sphinx never copied the scripts to be available to download.
They also were always pointing to main docs website, so they would show up on unstable documentation too, and I guess because they were hardcoded, there were no warnings when those files were recently moved.
Noticed compiling against libxml2 2.13.8 package that it's trying to use dynamic libs by default if `LIBXML_STATIC` is not defined. Adding `LIBXML2_DEFINITIONS` helps.
Actually after 2ec2ac0 there's no need for json serialization as we support working with string subitems natively.
For some reason previously it was using get_georeference_props to add attributes, I guess it a typo that happened awhile ago.
To avoid crashes and either way we stored the same data twice - once bim text props (and they were updated on each data refresh) and then again in data.py. Now it's going to use just data.py
Resolves issue #6099: PythonOCC >=7.8.0 changed WriteToString() method
signature, causing TypeError in ifcopenshell.geom.serialise().
This fix uses signature inspection to detect the method signature:
- For PythonOCC < 7.8.0: Use WriteToString() (no parameters)
- For PythonOCC >= 7.8.0: Fall back to Write() method
- Graceful handling of signature inspection failures
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Simplify path definitions in `test_write.py` and `test_open.py`. Replace `typing` with `collections.abc` for `Sequence` usage in `test_sweeps.py` and adjust type hints to use Python's generic collections. Add optional verbosity to `load_ifc_occ_shape` and refine geometry test assertions. Update `simple_sweep_2.ifc` fixtures for consistency with test expectations.
Introduces a new `lint` feature with `ruff` and `black` dependencies. Extends test setup with additional dependencies and tasks for building, testing, and installation. Cleans up `.gitignore` and removes unused entries from `pixi.lock`.
Improves formatting and readability in `test_sweeps.py`, adjusts imports and comments, and adds assertions for geometry validation in `simple_sweep_1.ifc` and `simple_sweep_2.ifc` tests. Updates code comments in `IfcFixedReferenceSweptAreaSolid`.
Includes `simple_sweep_2.ifc` test input, geometry validations in `test_sweeps.py`, and a new constructor for `sweep_along_curve` to handle directrix-based sweeps.
Enhances geometry processing for swept area solids by integrating matrix transformations. Includes new utility `_is_swept_shape` for topology analysis, extends testing with `load_ifc_occ_shape`, and updates schema versions in CMake presets.
Currently it's in Bonsai docs as this next to previously added formatter docs. Probably, should move it to ifcopenshell docs later.
C++ code style part about braces seems to be outdated.
Our minimum cmake version is 3.21, so policies below use `NEW` by default already:
- CMP0048 - VERSION option support for `project`, introduced in 3.0
- CMP0074 - find_package() uses <PackageName>_ROOT variables, introduced in 3.12
- CMP0078 - UseSWIG generates standard target names, introduced in 3.13
- CMP0086 - UseSWIG honors SWIG_MODULE_NAME via -module flag, introduced in 3.14
If you try to append an asset which has a relationship such as
IfcRelAssociatesClassification to a whole bunch of opening elements, all
those openings will also (incorrectly) be pulled in.
To fix 1k warnings running in cmake 3.30+
The new policy is that cmake is normalizing destination paths in `install` and warns if normalization has any effect on them. In our case paths after normalization should be fine, so switching it to `NEW`.
* Enhance AddElement operator to orient the extruded ifcopening along the AXIS orientation
* Adjust extrusion direction based on DirectionSense for AXIS2 & AXIS3 IfcOpeningElement
* Remove transform orientation settings in BoundingBoxDecorator
* Remove unused transform orientation handling in update_measure_xyz and black formatting
It adds this option to open directory in system browser using alt+click and we don't need anymore a special operator `bim.select_dir` to fill these props.
Example - https://files.catbox.moe/t8ejrs.png
The problem was with the line `read_surface_style(surface_styles[i], surface_style_ptrs[i]);` since `surface_style_ptrs[i]` was a nullptr.
Changed the signature to `style&` to make it more clear that it's expecting already created style struct.
It wasn't written, but HdfSerializer was still requiring and throwing errors if it couldn' t find it, so cache wasn't working...
Honestly, me when I realized what happened - 🫣🫣
Though there are no particular known issues with https://github.com/ortega2247/pypi-upload-action we've used before, but it wasn't updating for 5 years now, so it's better to switch to official PyPA workflow.
Args for workflow seem to be exactly the same, so it's just a drop-in replacement.
E.g. when user opened .blend file and ifc filepath was pointing to a wrong destination, after they changed the filepath pset templates wouldn't reload.
There was a contradiction - modal operators were treated as special and were always creating new transactions (`is_top_level_operator = method == "MODAL"`), but those transactions were only closed if modal returned `FINISHED` or `CANCELLED`, so in most case previous transactions were just overridden, so IFC undo data was lost.
Ping @Moult just in case
verbose logging was setting log level only for `logger`, never setting for the `logger`'s handler, so `-v` option, it seems, never worked. For simplicity now handler will just inherit log level from the `logger`.
See #6831. Warning: this is a breaking change.
Linked Aggregate names are numbered to keep them consistent when reopening
the file instead of relying on Blender auto numbering objects with the
same name. However, for annotation purposes, the numbering is irrelevant,
and the user might want to use just the aggregate name. So it was added a
"Name" to the "BBIM_Linked_Aggregate" Pset that can be used in these cases.
When refreshing the linked aggregates, their names will be updated to reflect
the "Name" value and the product assignment will be preserved to keep
annotations in sync.
In order to adapt existing files to this new Pset structure see this gist:
https://gist.github.com/brunoperdigao/37a5b838935b15a91aa70b2bc4de325f
E.g. IfcWorkSchedule.Duration wasn't recognized as such or IfcWorkSchedule.TotalFloat wasn't imported properly and was uneditable.
Now we also check IfcDuration type on attributes explicitly, instead of relying on hardcoded list
Location - https://files.catbox.moe/sxfxv4.png
To mirror the similar UI we have in 4d, this way feature will much easier to find than checking File->Import
A feature allowing duplicate existing IfcCostSchedule - either from API or from Bonsai UI
Button location in Bonsai - https://files.catbox.moe/ct058q.png
After ba2456a if main IFC project had an offset and linked object had more than 333 faces, then it was imported at it's original location instead, missing an offset.
Also processing occurrences should be now more optimized, since we reuse the same verts buffer instead of recreating it 10 times.
* Export_IfcCostSchedule_to_PDF_improvements
* IfcCostSchedule CSV export: Added ItemIsASum column
New column in the ifc export that tracks if IfcCostItem is a sum, also added a new static method to the IfcDataGetter class.
* IfcCostSchedule CSV export: Added cost quantities column
Cost quantities are a serialsed list containing the name of the quantity and the quantity value.
* IfcCostScheduel PDF export: add options to fine tune export
New options include nested_structure_depth, should_print_cover, should_print_description, should_print_rates, should_print_summary, should_print_cost_ids.
Also pass project currency to typst (still not used).
Added footer with "proudly created with IfcOpenShell".
Updated Cover with formatting and IfcCostSchedule Description
Now we store in the IFC file list of active bsdd user was using in Bonsai.
So when you open IFC file it automatically marks dictionaries as active and there's no need to go to bsdd UI, unless there are new dictionaries to add/remove.
Example - https://files.catbox.moe/kzwa2n.mp4
Since we added lookup for multiple paths, it was failing with confusing "Could not find Bonsai path in expected locations" even if the problem was with BLENDER_PATH.
Previously some of them were saved only for current .blend file, moving them to preferences will make it more consistent with usual Blender UX. If needed we'll be able to add some way to fine-grain them later.
There's also a temporary patch that's going to migrate old .blend-props to new preferences-props to make process less disruptive.
`settings` wasn't defined, so `create_shape` was always resulting in an error. Also, `create_shape` is returning `Triangulation` in this kind of cases, so there's no `.geometry`.
Also removed IfcCircleProfileDef if-check since it's covered by IfcParameterizedProfileDef.
It's probably was just an overlook in the original commit - it was referring to `settings["organisation"]`, though `"organization"` was never passed, leading to an error. And probably by accident remove_role tests were using the same tests as remove_organization, so the issue went under the radar for a while.
I guess it only affected how `defineOption` worked. Noticed the issue when was looking through `ifcconvert --help` and `context-ids` were duplicated 3 times.
To make it a bit more easier to understand what to do in cases like below (the solution is to provide generator explicitly: `run-cmake.bat vs2022-x64 -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON`).
```
run-cmake.bat -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON
vs-cfg.cmd: The used CMake version does not support generator `-DADD_COMMIT_SHA` - cannot proceed.
```
It was an overlook with using by accident bim.disable_status_filters instead of bim.enable_status_filters in bim.activate_status_filters, but it was also kind of unexpectedly introduced in 7858cb1.
But thinking about it, bim.activate_model shouldn't activate Statuses UI either way, since it might bring their filters they had before but explicitly disabled.
Since this feature can be generally useful, let's try it out to see the cases when it should be disabled.
Additionally to Materials UI, it's now enabled in object attributes, materials, profiles, styles and explorer ui.
* Some basic COGO survey points functions
* Update alignment api
Includes defining alignment segment by segment, automatic geometry definitions, and automatric stationing and referents
* Fixes bonsai import alignment from csv
* Adds DMS angle conversion functions to COGO api
* Updated per @civilx64 review comments
* Fixes problem with segment representations
* Fixes problem with segment transition codes
* Allows for compound vertical and horizontal curves
* Implements callbacks for referent naming
* Renames angle_from_bearing to bearing2dd for consistency with ifcopenshell.util.geolocation.dms2dd. Removes angle_from_dms because it duplicates dms2dd
* Documents register_referent_name_callback
* Fixes all sorts of problems with Cant/SegRefCurve implementation
* refactor referent unit tests to use a fixture for test setup
* lint with black
---------
Co-authored-by: Scott Lecher <civilx64@gmail.com>
Previously when you would load/open/create sheets and some files were missing you would get traceback errors.
Also removed warnings for missing sheets svgs since they can go missing if someone is opening ifc project and want to regenerate sheets. We'll be able to return them in the future, if needed, but they will need some refinement.
A generic UI to allow editing simple entities like #6869 without creating an entire edit attributes UI inside edit attributes UI.
Still work in progress and should be connected to attributes UI.
Example - https://www.imgchest.com/p/agyv9lpq978
Previously it was asking for a name to use to save search and users might type anything they find convenient at the moment, but it was possible that this name was used already by something important (for example drawing's IfcGroup) and would change it's description and assign objects there unexpectedly.
Now it will target IfcGroups with SEARCH ObjectType by default and only if user confirms, it will target all IfcGroups available (to keep the search a powerful tool for advanced users that allows them to populate any IfcGroup they want with items).
There's also a temporary patch in place - when you load/save search Bonsai will try to find old search IfcGroups with ObjectType = None and update their ObjectType, this needed to make sure transition works seamlessly for the simple cases.
Just to avoid accidental crashes using .get() / .next() when using them from Python - in cases when iterator was initialized but there were no elements or if iterator got exhausted.
The issue happens when a wall has a "ATPATH" connection and has a
negative direction sense. It mixes the profile points in an incorrect
order. This commit seems to fix it, but maybe there is a better solution.
Moved it to `TYPE_CHECKING` since it's currently used just for typing.
Was breaking validation in Bonsai since it doesn't provide all submodules with IfcOpenShell https://github.com/IfcOpenShell/IfcOpenShell/issues/6723#issuecomment-3016223786
Also added a simple test so github workflow would catch a simple error like this.
Code isn't used for over 3 years now and it's quite simple, so it's easy to restore if we'll need it again.
Noticed when started to investigate what was the use for `include_query` and `exclude_query` and turned out they basically had no effect for all this time.
- Remove INFO - No statuses selected,
- Remember and set last status on disable-reenable
- Moved duplicated code into shared tool.Sequence class method
E.g.
```
File "\bonsai\bim\module\model\wall.py", line 142, in poll
if not tool.Ifc.get_entity(obj).is_a("IfcWall"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'is_a'
```
Example - https://files.catbox.moe/i0wgid.png
Previously before 71d469a27 we produced a lot of IfcApplications on live-builds because of the version differences, now it's possible to edit them from UI - fix the differences and merge them using 106323847
In xmlschema 4.0 it started to throw a more generic XMLSchemaValidationError - I guess there are many things that can trigger validation check and in the new version some other issue gets there first.
Previously it was using Python 3.12 for everything, therefore syntax errors from Python 3.9 (e.g. `match` statement in ifcopenshell-python) went under the radar.
Added an utility to merge organizations and application that have exactly the same data. Noticed working with different files that sometimes they have these data duplicated - I guess reasons vary (revit exporter, our bug - c58b84d), but it's nice to be able to clean up redundant data.
Though still need to add some UI to change IfcApplications, so user could also get rid of them in case if they have small differences.
Feature location - https://files.catbox.moe/pwymx1.png
@theoryshaw in file from #6784 there were 2481 identical organizations and 1234 identical applications 😅
In some cases remove_representation got 2x times faster (when there were a lot representation items that are not IfcTessellatedFaceSets).
getattr with attribute that might not be present on the element could be much slower then checking it's class explicitly.
Stop iterating early and don't build a list to make it a bit more optimized.
Though I'm unsure if anyone is really using `unpack_non_aggregate_inverses`
If user has fake-bpy-module for type hints, it will fail with `ImportError: cannot import name 'Vector' from 'mathutils' (unknown location)` instead of `ModuleNotFoundError`.
As described in #6744 partial stub wrapper is completely replacing the type information from the wrapper, so having partial stub is making things worse than they were before.
This commit is adding all available symbols from the wrapper to the stub and allows to gradually include type information to the stub.
It also adds a safe net to ensure stub and wrapper won't go out of sync.
* bSDD now supports a couple more args when querying the API.
* Fetching properties via the classification UI has been removed
* The bSDD dictionary selector has been merged into the classification
source dropdown
* The bSDD dictionary selector merged into the property set name
dropdown
* You can now search for properties in the bSDD directly when adding
properties
* You can browse properties via groups or classifications (based on
assigned classifications)
* You can browse properties via keyword (with some limitations due to
the API)
* You can selectively choose which properties you then want to add.
Only basic support right now.
changed bim.select_similar.calculate_sum to `ctrl + click` instead, since `shift + click` is a UI pattern used in vanilla blender when selecting/deselecting things.
Also refactored into helper methods.
This was actually fixed earlier already due to restructuring of
structural element authoring (i.e. the representation_template items now
always has OBJ unless it is a structural item)
Now when a cost rate is assigned to a cost item, it creates also a relation between them, making the relation more consistent. It is also shown in the UI
* Added Quickstart guides for Linux and Windows IDEs using VSCode
* added some tips to Linux QS
* Added point 15
* extra picture for step 15
* Added windows part
* updated windows vscode launch settings
* added info about branch rebase for PRs
* updated dev_environment to cope for some errors
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later | [](https://pypi.org/project/bsdd/) |
| ifc2ca | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
| ifc4d | Convert to and from IFC and project management software | LGPL-3.0-or-later | [](https://pypi.org/project/ifc4d/) |
| ifc5d | Report and optimise cost information from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifc5d/) |
| ifcbimtester | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
| [bcf](https://docs.ifcopenshell.org/bcf.html) | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later | [](https://pypi.org/project/bcf-client/) [](https://anaconda.org/conda-forge/bcf-client) |
| [bsdd](https://docs.ifcopenshell.org/bsdd.html) | Library to query the bSDD API | LGPL-3.0-or-later | [](https://pypi.org/project/bsdd/) |
| [ifc2ca](https://docs.ifcopenshell.org/ifc2ca.html) | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
| [ifc4d](https://docs.ifcopenshell.org/ifc4d.html) | Convert to and from IFC and project management software | LGPL-3.0-or-later | [](https://pypi.org/project/ifc4d/) |
| [ifc5d](https://docs.ifcopenshell.org/ifc5d.html) | Report and optimise cost information from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifc5d/) |
| [ifcbimtester](https://docs.ifcopenshell.org/bimtester.html) | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
| [ifcconvert](https://docs.ifcopenshell.org/ifcconvert.html) | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
| [ifccsv](https://docs.ifcopenshell.org/ifccsv.html) | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
| [ifcedit](https://docs.ifcopenshell.org/ifcedit.html) | CLI wrapper for ifcopenshell.api IFC model mutation functions | LGPL-3.0-or-later | [](https://pypi.org/project/ifcedit/) |
| [ifcfm](https://docs.ifcopenshell.org/ifcfm.html) | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
| [ifcmax](https://docs.ifcopenshell.org/ifcmax.html) | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
| [ifcmcp](https://docs.ifcopenshell.org/ifcmcp.html) | MCP server for querying and editing IFC building models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcopenshell-mcp/) |
configure-release={cmd=["cmake","--preset","win-release","-B","build/win-release","cmake"],description="Configure the project",depends-on=[{task="init-submodules",environment="common"}]}
build-release={cmd=["cmake","--build","build/win-release","--config","Release"],description="Build the project"}
[feature.dev.target.win-64.tasks]
configure-debug={cmd=["cmake","--preset","win-debug","-B","build/win-debug","cmake"],description="Configure the project",depends-on=[{task="init-submodules",environment="common"}],outputs=["build/win-debug/CMakeCache.txt"]}
build-debug={cmd=["cmake","--build","build/win-debug","--config","Debug"],description="Build the project",depends-on=["configure-debug"]}
install-debug={cmd=["cmake","--install","build/win-debug","--config","Debug"],description="Install the project"}# Optionally Install files to your desired env using --prefix
vsdebug={cmd=["python"],description="Run a python script with vs debugger attached"}
[feature.tests.tasks]
configure-test={cmd=["cmake","--preset","win-test","-B","build/win-test","cmake"],description="Configure the project",depends-on=[{task="init-submodules",environment="common"}],outputs=["build/win-test/CMakeCache.txt"]}
build-test={cmd=["cmake","--build","build/win-test","--config","Release"],description="Build the project",depends-on=["configure-test"],outputs=["build/win-test/IfcGeom.lib"]}
install-test={cmd=["cmake","--install","build/win-test","--config","Release"],description="Install the project",depends-on=["build-test"]}# Optionally Install files to your desired env using --prefix
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.