Fixes builds with newer GCC/libstdc++ that no longer provide <cstdint>,
<cstring>, <cfloat>, <memory>, <algorithm> etc. transitively. Also
disambiguates visit<> calls in taxonomy.h with the full namespace and
casts the character value in IfcCharacterDecoder to uint32_t to silence
ambiguous overload warnings.
props.ifc_class is an EnumProperty whose items list only the element/space
types present in the model. Assigning element_type.is_a() crashed with
`enum "<class>" not found` when the selected element's type wasn't a member
(e.g. a raw IfcTypeProduct, or a stale item list mid-rebuild), aborting the
post-commit refresh.
Wrap the assignment in the same try/except TypeError guard already used for
the sibling relating_type_id assignments (added in 233cc344fa).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When validate_type selected a preferred_item from remaining_items
(e.g. the sole IfcBooleanResult in a representation), it left that
item in the list. The subsequent Items filter removed every item,
leaving Items=[] and causing guess_type to return
"MappedRepresentation" — silently corrupting the representation.
Also removes temporary debug print statements added during
investigation of the wall-to-slab extension workflow.
Generated with the assistance of an AI coding tool.
Re-running the operator on the same wall/slab pair created
additional IfcPolygonalFaceSet booleans each time. Now each
wall's existing booleans are removed before re-clipping, and
previously connected slabs are merged with the new selection
so no earlier clips are silently discarded.
Generated with the assistance of an AI coding tool.
When Shift+G is pressed on a LAYER3 element, any LAYER2 walls
connected via IfcRelConnectsElements(TOP) are now re-clipped
to the slab's updated geometry after recalculate_slab runs.
Generated with the assistance of an AI coding tool.
extend_walls_to_underside now accepts multiple slab/roof
objects in a single operation — all selected non-LAYER2 IFC
elements are treated as clip targets, all LAYER2 elements as
walls. Placement sync is done once upfront; each wall is then
clipped against every selected slab before reloading.
Also adds bim.regenerate_wall_to_underside (Shift+G): after
moving a slab, re-clips connected walls using the existing
IfcRelConnectsElements(TOP) relationship. Old booleans are
removed via remove_representation_item before re-clipping.
Generated with the assistance of an AI coding tool.
When extend_walls_to_underside is applied to a wall and the
roof/slab is later moved, pressing Shift+G now re-clips the
wall to the slab's new position.
The IFC relationship created by connect_wall_to_slab
(IfcRelConnectsElements, Description="TOP") is used to look
up which slabs a wall is clipped to. On regeneration, the
existing manual booleans (IfcPolygonalFaceSet operands) are
cleanly removed via remove_representation_item, then
clip_wall_to_slab is re-applied for each connected slab.
Shift+G on a LAYER2 wall that has a TOP connection now calls
bim.regenerate_wall_to_underside; walls without a connection
continue to call bim.recalculate_wall as before.
Generated with the assistance of an AI coding tool.
When the operator was called twice on the same wall for a
ridge roof, the two IfcPolygonalFaceSet clip solids shared
an exact ridge edge (kissing-solid). OCCT produced spurious
extra vertices at the coincident boundary.
Fix by building the clip solid from a rectangle on the slope
plane that extends slightly past the face edge (1 project
unit margin) rather than the exact face footprint. Adjacent
slope solids now volumetrically overlap at the ridge instead
of sharing a boundary face, which OCCT handles correctly.
Generated with the assistance of an AI coding tool.
ifc.get_object(element) can return None for IFC elements that aren't loaded as Blender objects (e.g., decomposed sub-elements).
The loop now skips those instead of passing None into collector.assign().
Cheers!
The temporary-offset workaround (#7408, commit bd57cc8735) subtracts the
directrix centroid (`mean`) from the curve points before building the
sweep near the origin, then must add it back to restore the original
location. The restore negated the sign — `Move(-mean)` instead of
`Move(+mean)` — placing the swept solid at -mean (mirrored through the
origin) rather than its true position.
Only triggers for polyline directrixes (`is_polyhedron()`) whose centroid
is more than 100 m from the origin (`mean.norm() > 1e2`), so models
centered near the origin are unaffected. Models that keep absolute site
coordinates (e.g. many Revit/ODA IFC exports) render affected swept
solids — reinforcing bars, pipes — at a mirrored phantom location far
from the rest of the model.
Three test files covering PR4's new surfaces — preview registry,
wall-gizmo poll behaviour, fillet operator registration. Every test
walks the live registry or class hierarchy instead of hard-coding
preview keys, operator names, or helper function names, so adding a
new preview / wall gizmo group / fillet operator exercises the same
invariants without test edits.
test_preview_base.py (6 tests):
* RegistryContract: every PREVIEW_CANCEL_OPS entry resolves to a
callable cancel operator on bpy.ops.bim.
* GetPreviewPropsTolerance: get_preview_props returns None for
contexts without a scene (regression guard for the SimpleNamespace
bug fixed in commit ee63137c6).
* ActivationCycle (registry-driven loop): any_preview_active toggles
with each registered preview's is_active flag;
discard_pending_previews clears every active flag across every
registered preview.
* SaveOnDiscardWired: locates the bim.save_project operator
dynamically and verifies its execute path references the discard
helper by its actual __name__.
test_wall_gizmo_poll_gate.py (4 tests):
* WallGizmoGroupsHideDuringPreview: walks the wall module for
bpy.types.GizmoGroup subclasses (skips preview-owner exceptions
whose bl_idname contains 'preview'), mocks any_preview_active to
True, and asserts every discovered gizmo's poll returns False.
* BaseParametricGizmoPollHidesDuringPreview: mirrors the test for
the cross-feature parametric framework base class.
test_fillet_operators.py (3 tests):
* FilletOperatorsRegistered: at-least-four-ops + every-discovered-op-
is-callable. Catches accidental deregistration.
* EnableRejectsIneligibleSelection: poll returns False without a
selection so the operator is greyed-out in menus.
State-clearing tests via bpy.ops.bim.cancel_wall_fillet_preview() are
deliberately omitted — the operator early-returns when context.screen
is unattached and prior tests in the model lane can leave the screen
in that state, making the dispatch path inherently flaky. Live testing
covers the behaviour.
Net: 13 tests pass cleanly in both single-file and full model lane.
Generated with the assistance of an AI coding tool.
deepdiff 9.1.0 added cachebox<6,>=5.2 as a direct runtime dep.
cachebox 5.2.3 only publishes macOS x86_64 wheels for macosx_10_12+,
incompatible with the macos py311 build's --platform macosx_10_10_x86_64.
The daily build's linux-wheel safeguard fires when the resulting
cachebox-*-manylinux_*.whl leaks into the macOS / windows wheels folder
(builds run on ubuntu-latest and cross-build via pip download --platform).
Pin deepdiff to <9.1 (resolves to 9.0.0, no cachebox transitive dep) as
the minimal hotfix. Long-term cleanup: bump the macos py311 platform tag
from 10_10 to 10_13 (matching py312/py313) and re-flag this line with the
standard \$(PYPI_PLATFORM) --only-binary=:all: pattern used by brickschema
and python-socketio.
Partly generated with the assistance of an AI coding tool.
Save-path:
* SaveProject._execute (project/operator.py) now calls
preview_base.discard_pending_previews(context.scene) right after
tool.Parametric.commit_pending_edits(). Previews are session-
transient — discard rather than commit. Sibling gizmo polls gate
on each preview's is_active flag; a stuck flag persisted through
the save would silently hide them on reload. Mirrors the pattern
already in gizmos-8088.
Preview-active gate hardening:
* preview_base.get_preview_props tolerates contexts without a
``scene`` attribute. Pre-existing tests use SimpleNamespace mocks
for the context; the previous getattr(context.scene, ...) raised
AttributeError before the inner default kicked in.
Test update:
* test_wall_header_refresh.test_geom_generation_invalidates_wall_geom_cache
patches tool.Wall.read_geometry instead of the now-deleted local
wall._read_wall_geometry (commit 7e5e7b8d6 migrated the call site).
Generated with the assistance of an AI coding tool.
When override_mode_set_edit encounters an unsupported profile (Couldn't
import profile), deselect the object so Tab continues to cycle cleanly.
Also restores the selection that existed before entering aggregate mode
when finally tabbing out, via save/restore_previous_selection().
Following the pattern from 586f9be077, deselect the active object after
exiting item mode so Tab continues to cycle cleanly. Also deselects
parametric LAYER1/LAYER2 items that cannot be edited directly, avoiding
the need to manually deselect before Tab-cycling out of aggregate mode.
Two cohesive cleanups in one commit.
A. Migrate wall.py to PR3-absorbed tool methods (fixes bug 4: pen icon
missing on fillet corner walls):
PR3 shipped tool.Wall.read_geometry + tool.Wall.validate_for_parametric_edit
but wall.py kept local duplicates predating that work. The local
_read_wall_geometry guards on tool.Blender.Modifier.is_wall (LAYER2-only)
while the tool method guards on tool.Parametric.is_path_connectable_wall
(LAYER2 OR fillet corner). Consequence: _get_wall_geom_cached → local
_read_wall_geometry returned None for every fillet corner →
GizmoWallFilletReedit.position_gizmos hit `if geom is None: hide` →
pen icon was unreachable for every fillet corner the user created.
Three _read_wall_geometry callers migrated to tool.Wall.read_geometry
(_read_wall_state_into_props, _get_wall_geom_cached,
GizmoWallJoinIntersection.position_gizmos). Two
_validate_wall_for_parametric_edit callers migrated to
tool.Wall.validate_for_parametric_edit (_maybe_resync_wall_props_from_ifc,
EnableEditingWall._execute). Local helpers deleted; docstring references
updated.
B. Drop over-restrictive gizmo gates (fixes bug 1: join icons missing
when walls intersect away from endpoints):
GizmoWallJoinIntersection.position_gizmos no longer hides itself when
the projected intersection lands further than MAX_DISTANCE_TO_ENDPOINT_
FACTOR (0.75 wall lengths) from any endpoint. The remaining
PARALLEL_DOT_THRESHOLD (cos 2°) gate via project_axis_intersection
returns None for near-parallel walls and is the only correctness bound;
distance from endpoints is a UI concern, not a geometric one.
GizmoWallFilletReedit.poll drops the has_a / has_b ConnectedFrom +
ConnectedTo guard — the IsFilletCorner pset is the authoritative signal.
EnableWallFilletPreviewFromCorner.execute already separately validates
both neighbour connections and reports a user-facing error if either
side is disconnected.
Generated with the assistance of an AI coding tool.
Three live-session regressions surfaced after the fillet feature
landed.
Sister gizmos competed with the active preview:
* preview_base.any_preview_active(context): new helper iterates the
PREVIEW_CANCEL_OPS registry and returns True if any preview is open.
Future previews registered there automatically gate sister gizmos.
* BaseParametricGizmoGroup.poll (gizmos.py): short-circuits on
any_preview_active so every parametric gizmo (door/window/stair/
roof/railing/wall edition) hides during ANY preview.
* The 4 wall gizmo groups with explicit polls (GizmoWallAddOpening,
GizmoWallExtendVertically, GizmoWallJoinIntersection,
GizmoWallUnjoinSingle) + GizmoWallFilletReedit gain the same gate.
DRY: extract _wall_gizmo_poll_gate(context):
* 5 wall gizmo polls each duplicated the 2 pre-flight checks
(viewport-gizmos enabled + no preview active). The helper centralises
them — each poll becomes a single short-circuit line followed by its
per-feature selection inspection.
ESC cancels the active preview:
* try_cancel_active_preview already existed in preview_base since PR3
but had no caller. Hooked into OverrideEscape.execute (geometry/
operator.py) as a new elif branch — same keymap that already cancels
pen gizmo edit mode + item mode + edit mode + aggregate mode. Order
in the branch chain matters: try preview cancel before falling back
to try_canceling_editing_modifier_parameters_or_path so the in-
flight preview wins over a stale modifier-edit cancel attempt.
Generated with the assistance of an AI coding tool.
In BIM_PT_type_attributes and BIM_PT_object_attributes (when
the active object is a type), attribute value buttons now use
"type.<Attr>" as the selector key so the operator finds
matching occurrences via their relating type rather than the
occurrence's own (often unset) attributes.
Generated with the assistance of an AI coding tool.
After selecting objects in the same container, copy a `location="Name"`
filter query to the clipboard and report it — consistent with the same
behaviour in SelectSimilarType, SelectSimilarAggregate, SelectIfcClass,
and SelectSimilarMaterial.
Generated with the assistance of an AI coding tool.
When a `+`-separated filter group returns no results, `FacetTransformer.facet_list`
was skipping the reset of `has_additive_facet_in_current_list` because the reset
was inside the `if self.elements:` guard. The stale flag caused the next group's
`add_default_elements()` to bail out early, leaving its element set empty and
silently dropping every subsequent group from the result.
Move the flag reset outside the guard so it always fires regardless of whether
the group produced any results.
In the Placement panel, show Location and Rotation X/Y/Z
each on their own row beneath a header label. When the IFC
file uses imperial units, display a read-only feet-and-inches
label alongside each Location input field.
Generated with the assistance of an AI coding tool.
Display general and calculated stair parameters (Width,
Height, Tread Run, Tread Rise, Length, etc.) formatted
to the IFC file's configured length unit rather than
raw numeric values.
Generated with the assistance of an AI coding tool.
End-to-end fillet flow on top of the helpers + recreate_wall hook
(landed in the previous commit). Users select two LAYER2 walls, click
the fillet entry icon, drag the live radius widget, and validate to
replace the corner with a curved LAYER2 corner wall (banana body).
Operators (5):
* EnableWallFilletPreview: 2-wall selection → validates LAYER2 +
straight axis + zero-slope + intersect-or-joined state → seeds the
preview props with a default radius computed from the shorter
available leg.
* FinishWallFilletPreview: dispatches CreateWallFillet with the tuned
radius; clears preview state on FINISHED, preserves it on failure so
the user can re-tune without re-selecting.
* CancelWallFilletPreview: clears preview state, no IFC mutation.
* EnableWallFilletPreviewFromCorner: pen-icon re-edit on an existing
fillet corner — pre-fills the preview from the corner's BBIM_Wall
pset + walks the inverse graph to recover wall A and wall B.
* CreateWallFillet: deletes any prior corner + A↔B path connection,
shortens A and B to the tangent points, instantiates a corner wall
from A's type, unassigns the swept-layer material/type (the explicit
banana body MUST own its geometry), assigns the dominant material,
rebuilds the body, sets a straight 2-point chord axis, stores
BBIM_Wall.IsFilletCorner+FilletRadius, reconnects A and B to the
corner with NOTDEFINED on the corner's side.
Gizmo groups (2 new + entry icon on existing):
* GizmoWallFilletPreview: visible while a preview is active. Bundles
a radius_dim widget at the arc apex, a trim_dim widget along wall A
expressing the same DOF via the leg setback distance
(trim = |radius| * tan(sweep/2)), and validate / cancel icons
anchored above the apex in screen-up.
* GizmoWallFilletReedit: pen-icon entry on an existing fillet corner
wall (single-selection, BBIM_Wall.IsFilletCorner set, both neighbour
connections present). Mutually exclusive with an active preview.
* GizmoWallJoinIntersection now stacks a fillet entry icon
(VIEW3D_GT_fillet → bim.enable_wall_fillet_preview) above the
existing join/unjoin icon in the joined and intersect state branches.
Property + decorator infrastructure:
* prop.py: BIMWallFilletPreviewProperties (Scene-level draft) +
BIMPreviewProperties umbrella with only the wall_fillet pointer.
The umbrella is the seam preview_base.py (landed in PR3) already
reads via getattr(scene, "BIMPreviewProperties", None).
* decorator.py: _stroke_lines_alpha helper + WallFilletPreviewDecorator.
Polls is_active; renders leg projections + arc + arc-center
construction lines from tool.Wall.compute_wall_fillet_geometry.
* __init__.py: registers operators + gizmo groups + property groups +
wires Scene.BIMPreviewProperties.
* handler.py: WallFilletPreviewDecorator.install/uninstall in
_install_decorators — always installed, self-polls on is_active.
Drive-by: extract gizmo.get_screen_up(billboard_rot) helper —
the local +Y of a billboard rotation is the camera's screen-up world
direction. Replaces 4 inline `billboard_rot @ Vector((0.0, 1.0, 0.0))`
sites added across the fillet feature's gizmo groups.
Generated with the assistance of an AI coding tool.
DecorationsHandler now caches dissolved edges (mesh-keyed), world-space
draw payload, and GPUBatch objects with per-object epoch invalidation —
moving one wall doesn't wipe 50 opening caches. Object-mode dissolve
removes triangulation noise; 2-pass depth-test split dims occluded lines
instead of hiding them. Edit-mode behavior unchanged.
Also: disable viewport shadows for IfcFeatureElementSubtraction objects,
and wire DecorationsHandler.uninstall() into the model module's
unregister() so the new persistent handlers don't leak on addon disable.
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.
Eleven module-level helpers in wall.py that the upcoming wall-fillet
operators + gizmo groups depend on. Each is self-contained or
references only helpers earlier in the file; the operators and
gizmos themselves land in follow-up commits.
* _wall_fillet_props / _wall_fillet_preview_active /
_wall_fillet_preview_walls: thin read-side accessors over the
BIMPreviewProperties.wall_fillet pointer (added with the
operators commit). Safe today: get_preview_props returns None
until the pointer is attached.
* _walls_have_zero_slope_for_fillet: validates that input walls
are vertical (x_angle ~ 0); slanted-extrusion fillets require
swept-along-curve geometry the banana profile builder doesn't
support.
* _build_curved_corner_body_representation: builds the banana
(annular sector) IfcExtrudedAreaSolid as a polyline-tessellated
IfcIndexedPolyCurve.
* _apply_fillet_corner_geometry: positions the corner wall at
tangent_a and rebuilds its body. Shared by the creation operator
and the regenerate path.
* _resolve_two_walls: pulls (active, other) from a 2-wall
selection, validates both as LAYER2 + straight-axis + not-already-
a-fillet-corner.
* _pick_dominant_wall_material: returns the thickest layer's
material from an element's IfcMaterialLayerSet / Usage.
* regenerate_fillet_corner_wall: re-runs the geometry build from
BBIM_Wall.FilletRadius + current neighbour layer parameters.
Called by tool.Model.recreate_wall when the IsFilletCorner pset
is set; the FIXME(PR4) placeholder in recreate_wall is dropped.
* _wall_fillet_gizmo_x_matrix: 4x4 placement matrix with local +X
aligned to a world-space direction; used by the fillet preview
gizmo group.
Centralises the IsFilletCorner pset read as
tool.Parametric.is_fillet_corner_wall — replaces 3 inline
get_pset(element, "BBIM_Wall", "IsFilletCorner") sites
(tool.Model.recreate_wall, tool.Model.recalculate_walls,
tool.Parametric.is_path_connectable_wall) plus the new
_resolve_two_walls call.
Generated with the assistance of an AI coding tool.
The framework's parametric-edit icon row currently binds an array
icon to bim.add_array_from_feature_edit, but the supporting per-
feature add-array flow and gizmo positioning haven't fully landed.
Showing the icon today lets the user click it and trigger a half-
wired flow.
Force the icon hidden inside the props.is_editing branch of
BaseParametricGizmoGroup.update_editing_gizmos. The else-branch
(not editing) already hides it, so this just mirrors that behavior
during edit mode. Drop this gate when array integration completes
to re-enable the icon position + visibility plumbing.
Generated with the assistance of an AI coding tool.
The extend-X / extend-Z icons in GizmoWallEdition's cursor row are
billboarded toward the camera; without orientation polish they
always point in the same screen-space direction regardless of which
wall endpoint the click will move (or whether the cursor sits above
or below the wall top). New helper mirrors the icon's local-X (extend-X)
or local-Y (extend-Z) axis so each arrow points toward the end it
will move:
* Extend-X: walk wall midpoint to figure out which endpoint stays
fixed (cursor past midpoint → ATSTART stays; cursor before midpoint
→ ATEND stays). Project the fixed endpoint into screen-space and
flip the arrow when the gizmo's anchor sits on the same side.
* Extend-Z: flip when the cursor is below the wall top (within
EXTEND_FLIP_EPSILON tolerance).
Called once per resolved cursor gizmo from
``GizmoWallEdition._update_cursor_gizmos``, after the gizmo's
``matrix_basis`` is set by ``gizmo.billboarded_at``. Reuses
``gizmo.should_flip_extend_arrow`` + ``EXTEND_FLIP_MIRROR_X/Y`` +
``EXTEND_FLIP_EPSILON`` already on tool.
Generated with the assistance of an AI coding tool.
GizmoWallJoinIntersection's unjoin only fires when exactly two walls
are selected and surfaces one icon at their shared corner — useless
when the wall has 3+ joins and the user wants to disconnect just one.
* UnjoinWallPathConnection: surgical counterpart to UnjoinWalls.
Disconnects the active wall from a single partner wall identified
by IFC GlobalId (invariant under Blender-object renames + file
save/reload + undo). Walks both inverse arrays of the active wall
for the specific IfcRelConnectsPathElements joining the pair —
matches DumbWallJoiner.split's pattern and avoids disconnect_path's
direction-sensitivity. Resyncs both walls' draft props after the
recreate_wall pass.
* GizmoWallUnjoinSingle: activates on exactly-one selected
LAYER2 wall. Preallocates a pool of 16 unjoin icons (Blender forbids
gizmo allocation outside setup(); ATSTART + ATEND + ATPATH rels are
rarely more than a handful). Per-frame, iterates _iter_path_connections,
positions one billboarded icon at each join via
tool.Wall.path_connection_location_world, and hides the rest. Each
visible icon's bound operator carries the partner GlobalId, so a
click removes only that one rel.
* model/__init__.py: register both classes alphabetically.
Mutually exclusive with GizmoWallJoinIntersection via poll() — that
group requires len(selected) == 2; this one requires 1.
Generated with the assistance of an AI coding tool.
The single-wall unjoin gizmo needs to enumerate every
IfcRelConnectsPathElements a wall participates in, regardless of which
side of the rel the wall was authored on, and place an icon at each
join's physical location. Two helpers carry that work:
_path_connection_location_world wraps core.compute_path_connection_location
at the Vector boundary. _iter_path_connections walks ConnectedTo +
ConnectedFrom, normalises orientation to (other, self_ct, other_ct),
and filters non-wall partners + None refs so per-frame gizmo positioning
survives malformed IFC.
Generated with the assistance of an AI coding tool.
After a one-shot wall IFC mutation (unjoin / split / merge / extend /
join-at-corner …) the always-visible gizmos on the OTHER side of the
join can be left reading stale ``BIMWallProperties`` — the IFC
geometry moved but the draft props that drive the gizmo handles still
point at the pre-mutation numbers, so a subsequent edit-mode enter
shows the wall at its old length / position.
* New ``_maybe_resync_wall_props_from_ifc(obj)``: re-primes a single
wall's draft props from current IFC, with guards for non-walls,
non-parametric walls, and walls in an active draft session (the
draft is then the source of truth, not IFC). Must run from an
operator ``_execute`` — ID writes from gizmo refresh raise.
* New ``_resync_walls_after_mutation(objs)``: iterates the above
across a selection.
* Six existing mutation operators gain a resync call after their
``core.*`` / ``DumbWallJoiner`` mutation completes:
UnjoinWalls, ExtendWallsToUnderside, ExtendWallsToWall, SplitWall,
MergeWall, JoinWallsIntersection. MergeWall resyncs only the
surviving wall — the active wall is the deletion target.
Generated with the assistance of an AI coding tool.
Bundle of bugs surfaced when exercising the new gizmo framework
end-to-end in a live Blender session after the
bim/module/drawing/gizmos.py refactor + TypeAccessor/CycleType/PickType
mixins landed.
Register / annotation resolution
* parametric_lifecycle.py: hoist `entity_instance` import out of
TYPE_CHECKING so typing.get_type_hints resolves the
Callable[[entity_instance], bool] annotation at operator registration
(CycleDoorType, CycleWindowType, CycleStairType failed with NameError).
Clarify the INTERFACE return contract on the picker entry-point so
readers see why the gizmo step stays off the undo stack.
Framework callable contracts
* model/wall.py, door.py, window.py, stair.py: migrate `props_getter`
and `element_checker` from bl_idname strings to bound classmethods
on tool.Model / tool.Parametric. BaseParametricGizmoGroup.get_props
expects a callable; the string form raised TypeError on first
gizmo poll.
* model/door.py, model/stair.py: drop the dead `prop_path=` operator
kwarg from create_arc_gizmo / create_icon_gizmo call sites. The
framework helper blindly setattrs every kwarg onto the operator's
OperatorProperties, but ToggleDoorSwing / ToggleStairProperty don't
declare prop_path — the setattr raised mid-setup_element_specific_gizmos,
so self.gizmo_door_type / self.lock_gizmo never got assigned and
every subsequent draw_prepare tornadoed AttributeError. Nothing
reads op.prop_path anywhere; the kwarg was dead data.
Dispatcher operators
* model/array.py: add EnableEditingParametric (the framework pen-icon
dispatcher that routes to a per-feature edit operator by bl_idname
string) and AddArrayFromFeatureEdit (binds the framework's array
icon to bim.add_array on the current parametric draft).
* model/__init__.py: register both new operators.
Per-frame robustness
* drawing/gizmos.py: guard BaseParametricGizmoGroup.draw_prepare with
is_setup_complete() — matches the existing guard in refresh() and
in BaseSchematicGizmoGroup.draw_prepare(). Defense-in-depth: when
any subclass's setup raises mid-way, draw_prepare now no-ops cleanly
instead of per-frame AttributeError-tornadoing on whatever attribute
the failed setup phase was meant to populate.
* model/decorator.py: guard ProfileDecorator.__call__ against
context.active_object is None. The decorator is a per-frame
viewport draw handler; deselecting or deleting the active object
while it's installed crashed on obj.mode access. Treat None the
same as "no longer in edit mode" — uninstall + fire the exit
callback if present.
* geometry/data.py: ViewportData.load() populates `data` before
flipping `is_loaded`, so a raise from cls.mode() no longer leaves
the class flag-set but data-empty for subsequent reads.
Generated with the assistance of an AI coding tool.
Three concerns bundled into one cohesive refactor of gizmos.py
(splitting them surgically requires intermediate commits with
duplicate same-named classes that Python can't parse):
1. Framework primitives — StaticTrisGizmoMixin + TexturedQuadGizmoMixin
replace the older TrisGizmoMixin. New module-level helpers:
_get_static_tris_shader / _get_static_tris_batch / clear_static_
tris_cache for cached GPU batch reuse, _draw_outline_and_body for
the shared outline-then-body render path, draw_tris_with_outline
as the public wrapper. billboarded_at(world_pos, billboard_rot,
scale) is the canonical billboard-matrix helper; should_flip_extend_
arrow encapsulates the view-aware mirror decision for extend
gizmos; get_warning_color_from_prefs reads the user's warning
color.
2. Config classes — BaseValueGizmoConfig (shared visibility + dimension-
text contract), CountGizmoConfig (array N indicator),
DimensionGizmoConfig (length / height / depth labels), IconActionConfig
(icon-only gizmos that invoke an operator on click). DimensionRenderer
draws the actual numeric label using BLF.
3. Icon classes — each rewritten on StaticTrisGizmoMixin so they share
the cached GPU batch + outline-then-body render path:
GizmoLockOpen / GizmoLockClosed (replacing the single-state
GizmoLock), GizmoArc, GizmoFillet, GizmoWallCornerIcon,
GizmoWallTeeIcon, GizmoPen / GizmoValidate / GizmoCancel (the
parametric-edit triad), GizmoPlus / GizmoMinus / GizmoTrash,
GizmoArrayParent / GizmoArrayAll / GizmoArrayLayerIndicator (array
context indicators with a small digit-rendering helper for the "xN"
count label), GizmoMerge / GizmoSplit / GizmoUnjoin (wall-join
icons), and GizmoMenu (textured-quad icon-action menu trigger).
The legacy TrisGizmoMixin, GizmoLock, and DimensionDrawConfig are
removed; downstream callers in subsequent PR4 commits swap to the
new mixin and config classes when their feature operators land.
CycleTypeMixin / PickTypeMixin / TypeAccessorBase live in
bim.parametric_lifecycle (previous commit). The three mixins are
re-exported from gizmos.py here so feature-module access via
``gizmo.<MixinName>`` keeps working until PR5 cleanup drops the
re-exports.
bim/module/drawing/__init__.py is updated in the same commit to
register the 11 new gizmo classes (GizmoLockOpen / GizmoLockClosed /
GizmoFillet / GizmoWallCornerIcon / GizmoWallTeeIcon / GizmoTrash /
GizmoArrayParent / GizmoArrayAll / GizmoArrayLayerIndicator /
GizmoUnjoin / GizmoMenu) — without that, the new classes exist in
gizmos.py but aren't usable as bpy gizmo types.
Generated with the assistance of an AI coding tool.
Three operator mixins for type-selection ops on parametric features
(door type-cycle, window type-pick, stair type-cycle, railing
type-pick, roof type-cycle, etc.). Each shares the same contract:
* ``element_checker`` validates the active object is the expected
IFC type
* ``props_getter`` resolves the BIM<Name>Properties group
* ``type_literal`` is the Literal type whose args drive the enum
* ``type_attr`` is the PropertyGroup field to read/write
* ``skip_element_check=True`` bypasses element validation (for
operators that target a non-IFC context)
CycleTypeMixin shift-click reverses direction (forward by default).
PickTypeMixin opens a popup menu and routes the picked value
through execute() so F6 redo / EXEC_DEFAULT reach the apply path.
The PickType modal-handler dance waits for LEFTMOUSE release before
opening the menu when invoked mid-click (e.g. from a gizmo's
target_set_operator) so Blender's drag-through-pick gesture doesn't
commit an accidental item.
Ships standalone — the next commit's gizmos.py framework refactor
re-exports these names from bonsai.bim.parametric_lifecycle so
gizmo modules can spell ``gizmo.CycleTypeMixin`` / ``gizmo.PickTypeMixin``.
Concrete operator subclasses land in subsequent PR4 commits per
feature (door / window / stair / railing / roof).
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
`+` has a higher precedence than `or`, so it resulted in the error below
```
Traceback (most recent call last):
File "\bonsai\bim\module\spatial\prop.py", line 86, in update_active_container_index
tool.Spatial.load_contained_elements()
File "\bonsai\tool\spatial.py", line 312, in load_contained_elements
cls.load_contained_elements_by_type(container)
File "\bonsai\tool\spatial.py", line 321, in load_contained_elements_by_type
results = cls.get_container_elements_grouped_by_type(container)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "\bonsai\tool\spatial.py", line 294, in get_container_elements_grouped_by_type
element_type.is_a() + "/" + element_type.Name or "Unnamed"
~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
TypeError: can only concatenate str (not "NoneType") to str
```
There seems to be cases when Blender might crash too unexpectedly.
E.g. #6686 - during viewport render system is running out of resources and crashing without leaving blender.crash.txt, leaving everyone clueless. Which is arguably a bug in Blender (both crash and lack of any crash report), but at least we'll have a fallback for this case.
What bonsai.crash.txt does - it's created in system temp folder with the current Python traceback, just before some dangerous operation. If operation didn't crashed Blender, then file will be unlinked. If crash did occurred, it might be the only clue for users and devs on what actually happened.
Otherwise it was trying to load drawing styles too early, when drawing was not yet active. And then they were reloaded again during ActivateDrawingBase. Occurred after c2860e6, before this commit issue was kind of hidden.
Possibly occurred after 4c255cc, but haven't investigated deeper.
@BIMvoice
The error you were reporting about Split Along Edge operator was caused by this issue, should be fixed now too.
See https://jmp.sh/hgJEHsgH
Example error traceback:
```
Error: Python: Traceback (most recent call last):
File "C\bonsai\bim\ifc.py", line 487, in execute_ifc_operator
result = getattr(operator, "_execute")(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C\bonsai\bim\module\geometry\operator.py", line 541, in _execute
self.update_obj_mesh_representation(context, obj)
File "C\bonsai\bim\module\geometry\operator.py", line 666, in update_obj_mesh_representation
tool.Ifc.link(new_representation, data)
File "C\bonsai\tool\ifc.py", line 230, in link
IfcStore.link_element(element, obj)
File "C\bonsai\bim\ifc.py", line 258, in link_element
tool.Geometry.get_mesh_props(obj).ifc_definition_id = element.id()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C\bonsai\tool\geometry.py", line 92, in get_mesh_props
return mesh.BIMMeshProperties
^^^^^^^^^^^^^^^^^^^^^^
ReferenceError: StructRNA of type Mesh has been removed
```
It was deprecated a while ago in 43869c3, when we stopped using curves for text annotations. bim.resize_text was also using curve annotations and now also was unused
Not that skipping non-string properties was a bad idea, but inserting it right at the beginning was - non-string IFC properties were missing IFC descriptions because of it. Moved it past `Attribute` check to avoid this issue.
For anyone hearing about this feature for the first time - https://imgur.com/a/dNRao3R
I assume this was also the issue that was encountered in #5892
Drawings are now activated in a bit different way - not by the camera button located above the drawings list, but by using the camera indicator next to each drawing. Which allows do change active drawing very quickly. All activation options (shift+, alt+) are also available from this indicator.
Now - https://imgur.com/a/DmnWkul
Before - https://imgur.com/a/zjPDkPa
Otherwise errors were passing silently (just console errors) and remained unfixed. 'PYTEST_VERSION' is needed to be even more strict when we're running pytests, which allows us to catch outdated Blender properties in time.
Example - https://i.imgur.com/K7Trkxf.png
Very dangerous bug that could create a situation when .blend will go out of sync with IFC leading to unpredictable issues.
Example issue:
1. Create a drawing and activate it.
2. Create a grid.
3. Activate the drawing again.
4. Undoing just drawing activation here would work fine - annotations would be still floating in IFC without objects, but that would be fixed on the next drawing activation
To make it more dangerous undo grid creation. Then you'll get the traceback similar to the one below. The most dangerous part of it, that user won't see an error, it will just pop up in the system console silently.
```
Traceback (most recent call last):
File "\bonsai\bim\handler.py", line 244, in undo_post
IfcStore.undo(until_key=props.last_transaction)
File "\bonsai\bim\ifc.py", line 533, in undo
transaction["rollback"](transaction["data"])
File "\bonsai\bim\ifc.py", line 449, in <lambda>
operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
^^^^^^^^^^^^^^^^^^^^^
File "\ifcopenshell\file.py", line 345, in undo
transaction.rollback()
File "\ifcopenshell\file.py", line 161, in rollback
element = self.file.by_id(operation["id"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "\ifcopenshell\file.py", line 498, in by_id
return self[id]
~~~~^^^^
File "\ifcopenshell\file.py", line 485, in __getitem__
return entity_instance(self.wrapped_data.by_id(key), self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "\ifcopenshell\ifcopenshell_wrapper.py", line 8884, in by_id
return _ifcopenshell_wrapper.file_by_id(self, id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Instance 1565 not found
```
Noticed fixing #6670 that though example project in this issue had camera named "PERSPECTIVE" but it was saved to IFC as orthogonal and there is no straightforward way to change it, besides changing Blender camera props and updating representation.
Example - https://imgur.com/a/GQ9A3EK
It's kind of exposed to UI now, but there's still bunch of issues with it to address.
Similar issue to 82f25f5
Basically matrix is a little bit different every time you activate drawing even if user made no changes to camera position. My guess is basically all float values stored by Blender somewhere deep as float32 but when you access matrix world, Blender converts them to Python floats (which are float64) and some garbage values introduced along the way creating this noise.
To make it possible to run the tests with PYTEST_DISABLE_PLUGIN_AUTOLOAD set, which is very useful if you don't want to start Blender anytime you run any tests on your system.
Traceback errors give users an impression that some broke and maybe project now is in some invalid state and they should ctrl-z immediately, error reports are much more friendly.
Before - https://i.imgur.com/N4fUU1M.png
After - https://i.imgur.com/6On1Be0.png
Example error traceback:
```
Traceback (most recent call last):
File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\geometry\operator.py", line 1044, in execute
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\geometry\operator.py", line 1055, in execute_duplicate_operator
IfcStore.execute_ifc_operator(operator, context)
File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\ifc.py", line 458, in execute_ifc_operator
result = getattr(operator, "_execute")(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\geometry\operator.py", line 1047, in _execute
return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\geometry\operator.py", line 1085, in execute_ifc_duplicate_operator
old_to_new, new_active_obj = tool.Geometry.duplicate_ifc_objects(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\tool\geometry.py", line 2131, in duplicate_ifc_objects
tool.Root.recreate_aggregate(old_to_new)
File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\tool\root.py", line 397, in recreate_aggregate
tool.Blender.select_and_activate_single_object(bpy.context, tool.Ifc.get_object(new_aggregate[0]))
~~~~~~~~~~~~~^^^
TypeError: 'NoneType' object is not subscriptable
```
So non MESH or CURVE objects are not listed in the Colour by property.
Now all unrelated empty objects are excluded from the coloring section as they were just taking space, see - https://imgur.com/sPA8I5V
Essentialy outliner.delete should do the same as object.delete, but in our case the main implementation was in object.delete while outliner.delete was missing very important parts - e.g. handling arrays.
So if user could have remove part of an array without errors, leaving pset data invalid. See #6590, #6547.
Found this by trying to debug issues above.
Now outliner.delete will call object.delete explicitly, so the two stay connected.
I also had an idea that may be it somehow can produce #6632/#6619, but couldn't find a way to reproduce it. So it's still uncertain.
I find myself often having element guid copied (either from other viewer or from text editor or from elsewhere) and to find it in scene I needed to create a new search group, add a filter, paste guid and then search for it. So decided to add a shortcut for this that just takes guid from the clipboard and runs the search for it.
Location - https://i.imgur.com/3c5rm1N.png
Since 3b1e893 black formatter workflow basically had no use since it wasn't reporting any issues (no one checks workflow logs unless workflow failed) and wasn't reliable for catching syntax errors either (black formatter catches syntax errors only when they get in the way of it's formatting the code, but now we delegate to python itself to catch syntax errors, see 406962287c).
And we also have some docs on how to apply black formatter - https://docs.bonsaibim.org/guides/development/code_style.html
Rely on `winreg` instead of running powershell scripts with cmd commands + .reg file + weird workflow with the need to retype cmd command. Also use HKCU instead of HKCR because it doesn't require admin rights.
If someone hearing about this feature for the first time, here's a short demo - https://imgur.com/a/dfG44pY (icon appears too but after explorer restart).
Previously:
E TypeError: Object of type Prophecy is not JSON serializable
Now:
E TypeError: Failed to serialize call: '{'name': 'duplicate_ifc_objects', 'args': ([<Prophecy for 'bonsai.core.tool.Ifc'>],), 'kwargs': {}}'.
E Possibly due to unpredicted return value for some call.
E See the list of the recent calls without return values:
E - {'name': 'get_object', 'args': ('annotation',), 'kwargs': {}}
E - {'name': 'run', 'args': ('group.assign_group',), 'kwargs': {'group': 'new_group', 'products': ['new_drawing']}}
E - {'name': 'run', 'args': ('group.edit_group',), 'kwargs': {'group': 'new_group', 'attributes': {'Name': 'unique_name', 'ObjectType': 'DRAWING'}}}
E - {'name': 'run', 'args': ('group.unassign_group',), 'kwargs': {'group': 'group', 'products': ['new_drawing']}}
E.g. it shows now:
AttributeError: Interface 'bonsai.core.tool.Drawing' has no attribute 'weird_method'.
Instead of:
Prophecy <class 'abc.Drawing'> has no attribute weird_method
`camera_props.update_representation` basically always returned True leading to `bim.update_representation` call on camera on every drawing activation. It was always True because `create_camera` wasn't setting `representation` and some other related camera props.
Changes:
- create_camera now setting representation
- ortho_scale, rather_x and raster_y now explicitly is tied to width, height and drawing scale props, not on depsgraph update. Depsgraph update wasn't allowing to set these props in time for create_camera to record representation correctly
- depsgraph update now only in charge of ortho_scale (just to be sure user won't touch it by hand) and scene.render resolution
Previously you would use bim.override_mesh_separate from UI with any of the available `type` options, then running operator separately would leading to running this operator from F3 to always choose this options (possibly leading to errors in objects mode)
mesh.separate will be used for all other selected non-ifc objects.
Included some code to support multple objects but there are still a few issue with it, I'll look into soon.
`obj` variable was used but never passed to `separate_element`
```
Error: Python: Traceback (most recent call last):
File "\bonsai\bim\ifc.py", line 458, in execute_ifc_operator
result = getattr(operator, "_execute")(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "\bonsai\bim\module\geometry\operator.py", line 105, in _execute
self.separate_element(element)
File "\bonsai\bim\module\geometry\operator.py", line 175, in separate_element
new_objs = [obj]
^^^
NameError: name 'obj' is not defined
```
Mimicking default mesh.separate behaviour.
@Gorgious56 I don't know if you've seen this before, but you might be interested - I've found a way to figure if operator wasn't called without any arguments.
People kept on getting confused with the align tool thinking it changed
the baseline to reference line offset whereas it actually just aligned
the object bodies. This tool now does exactly that.
Also start refactoring the "DumbWall" classes into the tools so we can
test them properly.
I think this was overengineered and a bad decision. RASTER is a
technical detail, not a semantic label (in contrast with whether it's a
title or a drawing).
I guess after all it's not very reliable idea since it's possible to have a cost schedule without quantities (even non-SoR) and it's giving users false assumptions that they don't need to worry about "Is Schedule of Rates" flag in some cases.
Previously code wasn't shared and duplication was reimplemented in an
incomplete way. Now it's shared which means that it's also more
sophisticated (i.e. does all the linked aggregate magic).
Also the duplicate code seems super complicated especially with arrays
and linked aggregates. Moving it into a tool makes it more reusable and
we can test it more granularly in the future.
Historically we overrode the operator, so we had to reinvent native
functionality. Now we only override the hotkey, so we can reuse native
functionality. This also means that things we failed to reinvent like
duplicating unlinked animation data now works properly. This bit me when
I was doing animation work and Bonsai's duplicate messed things up.
E.g. if you would call this operator for 1 specific cost schedule, Blender would remember it's value and would keep on using it even if operator is going to be called outside this schedule
Mentioned in #6545
Override filename to be always empty, so user won't have an expectation that csvs will be saved as a single file (currently they're always saved to a directory).
To prevent confusion with 'Hierarchy' column that's added on the export and make current and export compatible.
Indices now always start from 1 for consistency.
Previously it was saved using Windows 1252 and importing it would fail with an error like so:
```
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 650: invalid start byte
```
Because apparently it's not invalid IFC.
IfcStructuralPointConnection documentation says that they should have a topology representation with a IfcVertexPoint but it doesn't restict this representation from having Undefined type if it does have a IfcVertexPoint as it's item.
I can't figure out a more elegant way of doing this. I.e. I don't know
how to detect whether or not the user has explicitly set their own snap
default.
There's now operator to check that all profiles, styles, materials in the current project have a unique name. Which is important since it's used when we append assets to ensure there are no duplicated assets.
Operator location - https://i.imgur.com/bvXjZ8Y.png
Example output:
```
IfcMaterial name 'KnownUnknown' is used by multiple assets:
- # 260=IfcMaterial('KnownUnknown',$,$)
- # 1220=IfcMaterial('KnownUnknown',$,$)
Found issues validating IfcProfileDef assets.
IfcProfileDef name 'DEMO-C' is used by multiple assets:
- # 266=IfcCShapeProfileDef(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005)
- # 261=IfcIShapeProfileDef(.AREA.,'DEMO-C',$,0.1,0.2,0.005,0.01,0.005,$,$)
Found issues validating IfcPresentationStyle assets.
IfcPresentationStyle name 'Frame' is used by multiple assets:
- # 363=IfcSurfaceStyle('Frame',.BOTH.,(# 362))
- # 1221=IfcSurfaceStyle('Frame',.BOTH.,(# 1222))
```
I'd much rather not expose this option to ifcpatch but I guess models are different and there should be a way to extract elements even if assets name uniqueness is not ensured (since it's not IFC requirement anyway). But it may cause duplicated assets.
Set style name explicitly, this way what user does in UI is what they get in IFC.
Previously confusingly if you set style name to "Glass" and "Glass" already existed, you would get "Glass.001" style internally but "Glass" name in UI.
Just to make sure user will set what was actually saved to IFC right away. To prevent confusion when they see it only after model was reloaded completely.
The problem was that during class assignment, it was setting the object name and `is_renaming` flag but only then linking to IFC. Since object wasn't linked to IFC, name callback wasn't triggered and `is_renaming` wasn't reset and first rename wouldn't work.
Now it's just not setting the flag if object is not linked to IFC, as there's no callback then and don't need to protect from it.
Use temp_override or modify selection not to modify some object by accident, because this operator can also be called not from UI but by providing `object` argument.
Previously, objects with 2D bounding boxes outside the view were still being added. This issue is now resolved.
Additionally, implement simple AABB detection to check if the bounding box is within the viewport.
Use explicit element argument instead of props.representation_obj as representation_obj may not be there (e.g. removing rep items without item mode) or some other object might be in item mode and then booleans won't be unmarked.
Found the issue because of the failing test 🥳
It led to confusion when object A would be in Item Mode and you open properties for object B and try to show rep items for B and UI would be unresponsive.
As it's probably unintuitive (user can't see the linked model in viewport but we still load it) and user then have no option to keep model in Links but keep it unloaded.
Also producing traceback below if linked file is missing.
```
Error: Python: Traceback (most recent call last):
File "\bonsai\bim\module\drawing\operator.py", line 299, in execute
linework_svg = self.generate_linework(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "\bonsai\bim\module\drawing\operator.py", line 828, in generate_linework
IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "\ifcopenshell\__init__.py", line 162, in open
raise FileNotFoundError(f"File does not exist: '{path}'.")
FileNotFoundError: File does not exist: 'D:\Dropbox\GitLab\OD_Library\BIM\OD_Revit_Template - 2020 - IFC4 - Reference View.ifc'.
```
Currently teh parameters that have spaces are not properly parsed when invoking os.execv.
Ej:
We start blender with some parameters and then invoke bim.restart_blender:
C:\Users\falke>c:\Users\falke\Documents\blender-4.2.8-windows-x64\blender.exe --debug --python-expr "import bpy; bpy.ops.bim.load_project(filepath='C:/Users/falke/Documents/bonsaiDevel/Testing.ifc', should_start_fresh_session=True, use_detailed_tooltip=True)"
TBBmalloc: skip allocation functions replacement in ucrtbase.dll: unknown prologue for function _msize
Switching to fully guarded memory allocator.
Blender 4.2.8 LTS
Build: 2025-03-18 01:34:37 Windows Release
argv[0] = c:\Users\falke\Documents\blender-4.2.8-windows-x64\blender.exe
argv[1] = --debug
argv[2] = --python-expr
argv[3] = import bpy; bpy.ops.bim.load_project(filepath='C:/Users/falke/Documents/bonsaiDevel/Testing.ifc', should_start_fresh_session=True, use_detailed_tooltip=True)
Starting import process :: 0.00
Loading file :: 0.00
Calculate unit scale :: 0.00
Process context filter :: 0.01
Calculate model offset :: 0.00
Set units :: 0.00
Create project :: 0.00
Process element filter :: 0.00
Create styles :: 0.00
Parsing native elements :: 0.00
Done creating geometry
Create native elements :: 0.00
Done creating geometry
Create elements :: 0.03
Create annotations :: 0.00
Create positioning elements :: 0.00
0 / 3 elements processed ...
Create spatial elements :: 0.00
Create structural items :: 0.00
Create element types :: 0.00
Place objects in collections :: 0.00
Setup arrays :: 0.00
Load linked models :: 0.00
Add project to scene :: 0.00
Setting default context :: 0.01
Import finished in 0.06 seconds
No MySQL support
Writing userprefs: "C:\Users\falke\AppData\Roaming\Blender Foundation\Blender\4.2\config\userpref.blend" ok
Info: Preferences saved
TBBmalloc: skip allocation functions replacement in ucrtbase.dll: unknown prologue for function _msize
Switching to fully guarded memory allocator.
Blender 4.2.8 LTS
Build: 2025-03-18 01:34:37 Windows Release
argv[0] = c:\Users\falke\Documents\blender-4.2.8-windows-x64\blender.exe
argv[1] = --debug
argv[2] = --python-expr
argv[3] = import
argv[4] = bpy;
argv[5] = bpy.ops.bim.load_project(filepath='C:/Users/falke/Documents/bonsaiDevel/Testing.ifc',
argv[6] = should_start_fresh_session=True,
argv[7] = use_detailed_tooltip=True)
C:\Users\falke> File "<string>", line 1
import
^
SyntaxError: invalid syntax
Error: Cannot read file "C:\Users\falke\bpy;": No such file or directory
Error: argument has no '.blend' file extension, not using as new file, exiting! C:\Users\falke\bpy;
Blender quit
By adding the proposed list2cmdline based solution, the commandline is properly formated before invoking os.execv
Thanks!
Just found that all scroll bars for UIList kept in sync using `list_id` - it's that second argument for `UILayout.template_list` that I forgot exist, since I've never seen set it to anything besides the empty string.
So in #6439 we were reusing same UIList for multiple collection props and since one of the props was empty, it was constantly resetting scroll bar position.
Was about to report it as a bug to Blender but stumbled upon similar issue https://projects.blender.org/blender/blender/issues/124364
So the solution is make sure each UIList has unique `list_id`.
Fyi @Moult @Gorgious56
Traceback:
```
Error: Python: Traceback (most recent call last):
File "\bonsai\bim\module\project\operator.py", line 127, in execute
self._execute(context)
File "\bonsai\bim\module\project\operator.py", line 143, in _execute
core.create_project(
File "\bonsai\core\project.py", line 108, in create_project
project.append_all_types_from_template(template)
File "\bonsai\tool\project.py", line 55, in append_all_types_from_template
bpy.ops.bim.select_library_file(filepath=filepath.__str__())
File "\Blender\4.4\scripts\modules\bpy\ops.py", line 109, in __call__
ret = _op_call(self.idname_py(), kw)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Converting py args to operator properties:: keyword "filepath" unrecognized
```
Fix bug where uncommitted changes were not fully colourised in the same
way as diffs. Also try and catch more changes, eg. highlight if a
Products Type has changed.
Setting the default container switches to an appropriate orientation
slot if the orientation of this container isn't global. eg. if a building is
rotated, selecting to work in a storey will set the orientation to the
building orientation. Closes#6128
When adding a drawing to a sheet, place to the right of the last drawing
if it fits on the sheet - otherwise start a new row below all existing
drawings.
This means that new drawings are no longer just piled up on top of each
other at the top-left, but it does mean that new rows of drawings are
added below the title box when the sheet is full.
* ISSUE #6385 Delete iterator elements as soon as they are moved past
* ISSUE #6385 added loop to destructor to clean up initialised elements not yet disposed of by the iterator
Example error:
```python
RuntimeError: could not create instance of BIM_UL_containers_manager to call callback function 'filter_items'
2025-03-19:16:13:14,001 ERROR [log.py:69] Uncaught exception
RuntimeError: could not create instance of BIM_UL_containers_manager to call callback function 'draw_item'
```
IfcConversionBasedUnit's Name is case insenstive and it was producing UI errors.
Noticed working with file from Revit Ryan attached in #6374
Traceback:
Traceback (most recent call last):
File "\bonsai\bim\module\pset\ui.py", line 456, in poll
ObjectMaterialData.load()
File "\bonsai\bim\module\material\data.py", line 167, in load
cls.data["total_thickness"] = cls.total_thickness()
^^^^^^^^^^^^^^^^^^^^^
File "\bonsai\bim\module\material\data.py", line 331, in total_thickness
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "\bonsai\bim\module\drawing\helper.py", line 157, in format_distance
unit_length = unit_length_mapping[unit_length]
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
KeyError: 'FOOT'
1) get_object_main_axis - replace branching with just max between dimensions. There is a small caveat that previously for `x,y,z=1,2,2` it would return 'x' but now it returns 'y', but I'm not sure if 'x' was really intended here.
2) get_length - current implementation does the same as get_linear_length
Ping @maxfb87 just in case.
This solution brings new questions to #5938.
Currently, we lack a reliable way to calculate the existing x_angle only based solely on the extrusion direction. For example, a 30 degree angled extrusion with positive direction has the same extrusion direction as a -150 degree angled extrusion with negative direction. The difference lies in the object's rotation.
This means that things can get messy if the user changes the object x angle somehow. We may need to explore alternative approaches.
The error was:
```
Traceback (most recent call last):
File "\IfcOpenShell\src\ifcopenshell-python\ifcopenshell\util\generate_pset_templates.py", line 416, in <module>
templates_generator.parse_ifc4x3_data()
File "\IfcOpenShell\src\ifcopenshell-python\ifcopenshell\util\generate_pset_templates.py", line 94, in parse_ifc4x3_data
self.parse_psets_data("IFC4X3", pset_data_glob, "IFC4X3 Property Set Templates", str(IFC4x3_OUTPUT_PATH))
File "\IfcOpenShell\src\ifcopenshell-python\ifcopenshell\util\generate_pset_templates.py", line 129, in parse_psets_data
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "\ifcopenshell\ifcopenshell_wrapper.py", line 9037, in schema_by_name
return _ifcopenshell_wrapper.schema_by_name(arg1)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: No schema named IFC4X3
```
When adding a single duct in MEP no ports where attached.
The function create_profiles_from_polyline does not call DumbProfileJoiner (which eventually calls MEPGenerator().setup_ports).
I have added a check so if there is only one segment, the MEPGenerator().setup_ports is called explictily
Currently the flow direction decorations run in the local Y. This means that if one is creating ducts/pipes in the xy plane, the decorations are drawn in planes perpendicular to that one and than means that the arrows are not visible from a top/bottom view.
By adding another set of arrows perpendicular the arrows are always visible no matter what projection is taken
This Shift-E still needs more polish as we figure out the best hotkeys
and what's most natural to users when they select different combinations
of objects, so expect the conditionals that govern when Shift-E does
things to still change in the future.
There are a few differences to the previous implementation:
1. It is no longer recalculated on wall regeneration. The new wall
regeneration is strict to the spec on the rules of rel connects path,
and so this being a "userdefined" connection we only calculate it
explicitly when the user invokes the operator.
2. It uses meshes instead of clipping planes, so you can clip to
strange shapes or gable roofs or whatever. Nice.
The issue was caused by deleting an object outside the aggregate while in
aggregate mode. The selection for these objects are now disabled to prevent
this issue
- Implementation of @theoryshaw PR #5922.
- Note: `IfcPropertyEnumeration` allows multiple choices, but this is not
applicable for this use case. Users must manually deselect other options;
otherwise, it defaults to the first selected option.
for occurrence classes without special type.
E.g. previously:
you select IfcSlab and change it to IfcRoof - since there is not IfcRoofType in IFC2X3 it would figure the matching product type is IfcBeamType and change this slab's type element to IfcBeamType. IfcBuildingElementProxyType seems more generic and fitting.
E.g. you select IfcSlab and change it to IfcRoof - since there is not IfcRoofType in IFC2X3 it would figure the matching product type is IfcBeamType (which is confusing too but that's another subject) and change this slab's type object to IfcBeamType which consequently change IfcSlab to IfcBeam instead of IfcRoof that was selected originally.
Noticed investigating #5918
Previously, changing props.ifc_file had an update hook to reload
information about the IFC model. But this isn't always correct because
there are two situations:
1. The ifc_file path changed because you saved the file for the first
time or saved as, and this is merely recording a new saved path of the
existing file object.
2. The ifc_file path changed because the user manually changed it or
selected a new file. This could reference an entirely new file object.
This is dangerous because we can't trust anything anymore, including our
undo history.
So the new default situation is that there is no magic hook. If you
change props.ifc_file, that's all it changes ... just a path stored in
Blender with not much significance.
If the user runs select_ifc_file to explicitly relink the file, it now
explicitly purges in that situation and clears the undo history.
Basically now behaviour is explicit, not using magic hooks.
We had Chinese translation for Bonsai and some stub file for German and translation.py generation was failing with the error below. Chinese translation is more recent so it has some keys that German file hadn't and some keys were gone in the more recent version. In theory we should make all .po file in sync but let's skip it for now to keep things going.
```
Traceback (most recent call last):
File "/home/runner/work/IfcOpenShell/IfcOpenShell/src/bonsai/scripts/bonsai_translations.py", line 379, in <module>
update_translations_from_po(po_directory=Path(args.input), translations_module=Path(args.output))
File "/home/runner/work/IfcOpenShell/IfcOpenShell/src/bonsai/scripts/bonsai_translations.py", line 203, in update_translations_from_po
if (msgstr := msg.translations[lang]) in (None, ""):
~~~~~~~~~~~~~~~~^^^^^^
KeyError: 'de_DE'
```
create_mesh returns None only in case if it meets some exception and it prints logs in that case, but atleast some breaking mesh won't be in the way of users trying to open some model
This operator assumes all material definitions have a Name attribute,
mostly they do except that Material Layer Sets have a LayerSetName
attribute for reasons.
To support obtuse x_angle a refactoring had to be made, which helped
improve the general code for slab addition. This is challenging because
there are a few features that interact with each other to create slabs,
like `depth`, `direction_sense`, `offset` and `x_angle`. In addition,
these interactions can happen in different parts of the code. This
commit addresses `ChangeExtrusionXAngle` in `model/wall.py`.
See e8e88c25fdcef55826443fe6b000aec1baa6f25a for more information.
To support obtuse x_angle a refactoring had to be made, which helped
improve the general code for slab addition.
This is challenging because there are a few features that interact
with each other to create slabs, like `depth`, `direction_sense`, `offset`
and `x_angle`. In addition, these interactions can happen in different parts
of the code. This refactor improves the coherence between those different parts.
Files changed:
- `api/geometry/add_slab_representation.py`
- `model/slab.py` - inside the function `change_thickness()`
File to be changed in a following commit:
- `model/wall.py` - inside the operator `ChangeExtrusionXAngle` - To-do
1) Add default values for new IfcSurfaceStyleLighting so they won't appear invalid.
2) Temporarily disable starting surface style with a texture style since it requires additional texture UI to be exposed or some default texture to be assigned to keep it valid.
Also fix a bug with broken patch descriptions if some argument had more than one line for the description.
Example - https://i.imgur.com/1syL4gJ.png
Notcied working on #6227
Refactor the snapping system to better organize the weighting and ordering
of snap distances. This change enhances the "stickiness" of snapping points,
allowing for prioritized control over different types of snapping points.
The code was recalculating type_elements and relating_type_id every time Blender trying to check enum items.
Couldn't replicate issue when type duplicated, perhaps it's resolved some other way already.
relating_type_id now relies on type_elements which relies on ifc_class_current and they wasn't updated when ifc_class was changed.
Typical failing test looked like "TypeError: bpy_struct: item.attr = val: expected a string enum, not int".
If data was already loaded for BIMTool ("all") this ` elif ifc_element_type == "all" and AuthoringData.data["ifc_element_type"] is not None:` would always fail and this `AuthoringData.data["ifc_element_type"] != ifc_element_type` would always result to True, constantly recalculating data on every draw call.
This may uncover some issues that were hidden by the constant update (e.g. the bug fixed in the next commit).
Simplified it so now ifc_element_type is almost always referring to either ifc class or None. It's still using "all" in the BIMTool itself but then it's converted to None when passed to draw methods.
Currently you can edit container name directly from spatial manager, so this operator is no longer needed (and it also was missing from UI either way).
Fixes mapping of vertical alignment parabola business logic to geometry. Adds creation function of h and v alignment. Adds stationing referent at start of alignment. Fixes utility functions
(I hope I don't miss anything, no tests seem to fail and performance is increased significantly) but because `subelement_queue` initiated with the `traverse` all `element`'s subelements will be traversed inside `while` loop twice - once as a part of initial queue and another time when `element` is traversed inside the loop and all those elements added to the queue again.
Now we just initiate the `queue` with the `element` and it will be traversed inside the loop like any other element.
1) `do_not_delete` performs best when it's set
2) also_consider when `element` related elements go first, so there will be no need to traverse all other elements to see if they cover `element`'s inverses.
1) replaced walk with traverse(max_levels=1)
2) early return if there total_inverses == 0
3) early return if also_considered_inverses is enough to cover total_inverses
When locked to a plane, the mouse will be loosely locked to and angle
that is divided by 15 degrees. This was not working properly for "XZ"
and "YZ" planes.
check schema version instead of accessing attribute directly (hasattr under the hood is just doing getattr and checking whether it returns AttributeError), so one less IFC access
Similar thing with element.Item to access IFC just once.
As a result if all elements were assigned to sublibrary, it was showing that library itself has 0 assets and wasn't allowing to expand to see the sublibraries.
Previously if there was library A and it had sublibrary B and then some element were directly assigned to library A, UI still wouldn't show this element and would show only elements assigned to the B, the last library in the hierarchy
If a tool.Ifc.Operator updates a prop, the prop update function will
only call _after_ the operator finishes (and therefore adds an undo step
to the undo stack). If the prop update function then calls another
tool.Ifc.Operator, that will result in another "top-level" operator
call. This second operator _won't_ get added to Blender's undo history,
yet the Bonsai history / IfcOpenShell history will have another undo
step added. Yikes!
TL;DR don't call tool.Ifc.Operator from a prop update function.
I don't like this, but PyPI makes it really, really hard to "discover"
optional deps and what they're used for. So it's just easier to make it
a mandatory dependency and advanced users can always strip it out. This
stops user reports about "it doesn't work out of the box I only did pip
install ifctester"
Previously it would switch representation (and sync changes) every
single time a new representation was created (in the
replace_obj_ifc_representation function). I think this is misplaced
responsibility. The function should change the representation, but not
be responsible for switching.
1) avoid calling geometry.xxx multiple times as each time it creates a new copy
2) use numpy arrays with correct data type, so Blender could perform buffer copy significantly reducing overhead
Original Blender `view3d.pastebuffer` had an info missing and it wasn't displayed in UI since it's coming from suboperator. New info message also mentions that objects were unlinked from IFC
Added UIData class with function for storing icon color mode. Open Recent IFC Project icons now supports blender dark, blender light, and custom "white" themes
I guess this is a Bonsaism but it makes sense as named profiles are
significant, and it isn't the first time materials and profiles have
been treated as "rooted" in a sense. It's also annoying to keep on
having my profile library drop off when I'm just switching profiles.
Previously remove_profile would purge unused materials and profiles, but
remove_layer and remove_constituent wouldn't. This was as subtle
inconsitency. Now everything by default consistently retains materials
and profiles, and has options to change this default.
The fix in 0899a2a8 ensured that when you switched geometry, all other
elements (sharing the same type or representation) would also switch
alongside it. However it would lead to mesh invalidation which broke
some assumptions in the style code. The style code also repeated a lot
of the logic of switch representation (i.e. finding shared
representations) so this seems simpler.
In general the whole representation part of the code is messy and
hopefully over time it'll get better.
For occurrences, the iterator would populate ios_materials if there is a
material / style associated with it. However, for types we do
create_shape to the representation directly, so we need to do the
material / style check manually.
The same issue of handling relative paths, doing as_posix, checking
drives, etc was solved again and again in slightly different ways. This
centralises it.
Example - https://imgur.com/a/4PZr0px
It's very clunky and mainly exposed just to be available in some way, it will all make more sense when we display project libraries as trees in project library ui.
* Add AssignConstituentFractions recipe
* license, units & added context description
* removed src, using better unit handling and several other small improvements based on feedback
Force the color of the icons in the Properties-Scene tab to match Regular Text
Use the icon display mode function to detect where to use dm_ifc.png or lm_ifc.png
Co-authored-by: tim <tjrhyder@gmail.com>
Handle missing IfcRelationship - eg. if all sofas are deleted the
IfcRelDefinesByType is deleted too, but we reincarnate as an empty list
so it can be merged.
Sanity check internal graph - If an entity is deleted in one branch but
has a new usage in another branch, merging would break the graph leaving
a dangling reference. ie. now you can't delete types, materials etc..
in one branch while using them for new objects in another and still
expect the merge to succeed.
Previously I was only toggling Blender collections which is an
optimisation for large projects but will result in incorrect behaviour
if things aren't in the collection you think they're in. So for now it's
slower but more correct. We can optimise it later.
This means that seeing the extrusion type graphically is now possible,
adding booleans is now through a consistent interface. It's still
possible to immediately edit the axis via Alt-E and profile via Shift-E.
This helps consoliate code too.
I can't believe I accidentally left out the minus sign. The ability to
handle simple remapping of location A --> B is useful for software that
doesn't support map conversion properly.
Previously the collection and scene object was removed, but this is
obviously not sufficient since the bpy.data.library still exists (which
meant that if you relinked it, regardless of your cache setting, it'd
just load the existing Blender file which is completely useless).
* feature_faster_unit_method > main: added constants
* feature_faster_unit_method > main: extended `file` class to dynamically save unit information
* feature_faster_unit_method > main: refactored `get_property_unit` method
1. split out case that returns the wrong type (dictionary of units) into its own method
2. cleaned up (but preserved) logic
3. refactored common part of all cases (the method which prioritises unit then value-entity then measure_class)
---------
Co-authored-by: raj-open <raj-open@users.noreply.github.com>
Co-authored-by: Dion Moult <dion@thinkmoult.com>
* linting-workflow > v0.8.0: make QA only throw warning instead of error on unprettified code with no syntax errors
Without a Linting policy, the workflow should only fail if code base contains syntax errors.
* linting-workflow > v0.8.0: better logging
* linting-workflow > v0.8.0: changed instruction to notice-type
---------
Co-authored-by: raj-open <raj-open@users.noreply.github.com>
This means that openings can (and default to) start with extrusions. It
also starts to open up the possibility to add other types of feature
elements. Still really incomplete.
As it also has a third value "UNKNOWN" which has it's own meanig in IFC and therefore IfcLogical cannot be represented by simple booleans.
Example in Bonsai - https://i.imgur.com/xuPMcwA.png
Since calculate_unit_scale now requires a project.
def calculate_unit_scale(ifc_file: ifcopenshell.file, unit_type: str = "LENGTHUNIT") -> float:
"""Returns a unit scale factor to convert to and from IFC project units and SI units.
Example:
.. code:: python
ifc_project_length * unit_scale = si_meters
si_meters / unit_scale = ifc_project_length
:param ifc_file: The IFC file.
:param unit_type: The type of SI unit, defaults to "LENGTHUNIT"
:returns: The scale factor
"""
> if not (units := ifc_file.by_type("IfcProject")[0].UnitsInContext):
E IndexError: list index out of range
It was triggering name_callback, adding indices to the names and writing the name with index to ifc.
Also now check previous and new object name explicitly instead of relying on `obj.name` as it may have Blender indices (Blender doesn't support multiple objects sharing the same name).
It's important to have somewhere information on how many elements were actually affected since some of the selected objects may not support the used attribute.
Example - https://i.imgur.com/UrLNWcF.png
E.g. previously if you would tab into editing IfcExtrudedAreaSolid that was using not supported profile type (e.g. IFCCSHAPEPROFILEDEF) it would import it as empty geometry and tabbing out then would save it corrupting the original profile. Now there is an error message that it's not yet supported - https://i.imgur.com/NzksYVP.png
Bonsai now creates a user data folder that allows you overriding Bonsai data files. E.g. you can put you own `default.css` as `data/assets/default.css` in that folder and it will override the `default.css` Bonsai is using for the drawings. Previously you could have change internal `default.css` but Bonsai would always restore the original one on every update / reinstallation.
You can also store your .ifc files in data/libraries or data/templates and Bonsai will ensure they're loaded and you won't need to worry about Bonsai removing them later.
E.g. on Windows this path is `C:\Users\xxx\AppData\Roaming\bonsai\bonsai\data`/
You can also find path in the preferences - https://i.imgur.com/kwMc0JU.png
Previously cache_dir was located under site-packages, which is managed
by blender and could be read-only. Now location defaults to eg.
~/.cache/bonsai or equivalent location on other platforms. See #5564
Note this adds a dependency on the platformdirs python module
I can't explain this one so I'm pretty embarrassed to commit this, but
it crashes (see situation in #5799) on my machine and I can't recreate
it outside this scenario. In any case I think as a general strategy we
should:
1. Minimise reliance on msgbus
2. Msgbus listeners themselves should be wary of invalid state (e.g. if
object is now unlinked)
3. Not clear listeners (don't overreach)
The commit that introduced the problem was trying to optimise rendering
dense meshes with the opening decorator. However, it introduced more
calculation in the initial project load and stored more data on every
object. This resulted in models loading 3x slower for me which is pretty
major. The original problem needs further investigation.
Sometimes we want to fetch selected objects, and that includes the
active object, even if the active object isn't actually highlighted in
the viewport (albiet rare, I think?).
Conversely sometimes we want to get the active object, even if it isn't
actually highlighted. The tool.Blender functions now have kwargs to
distinguish between these.
I find this a bit of a strange behaviour in IFC. Imagine you start with
a Tessellation representation type. You're only allowed meshy things, no
solid extrusion. And vice versa if you have a SweptSolid representation
type.
Now imagine you suddenly create a boolean. Now, you can mix both
tessellations and solids together.
The ZIP installer now contains a `libs/bin` folder that contains
`ifcmerge`. Bonsai on `register()` adds this folder to the system `PATH`
for the current Blender session. Advantage of this is that we don't have
to fiddle with the registry on windows, or install files to
`~/.local/bin` on Linux, it _should_ work on Darwin, updates are
automatic, and we have a mechanism to ship other executables if
required.
(Note that ifcmerge.exe increases the size of the Windows Bonsai ZIP
download by about 7MB)
If Git isn't installed on Windows, the Git panel now offers to install
it from the Windows Package Manager Community Repository using `winget`.
The annotation adding code was a bit of a mess, spread around
create_annotation, create_annotation_occurrence, and
bpy.ops.bim.add_annotation. I've now consolidated it all into
core.add_annotation and updated it to work with the new item editing
mode (basically new objects need to reload their representation to
populate item_ids). Things are still messy, but a bit less now.
Otherwise it was hidden until user guesses that 2 objects need to be selected for it to appear.
Also, not sure if it's useful operator anymore - it seems just duplicating an opening and then adding it does the same thing.
WARNING! The roof generation is now quite significantly different. This
WILL change your geometry when you refresh roofs from existing models.
- The roof profile now always represents the top of eave. This better
represents how things are built rather than bottom of eave which was
previous.
- The roof therefore always grows down from the profile. The rafter
edge angle can only be acute. This means that the roof will never grow
larger than the footprint profile that you've drawn. (before, the
footprint was not guaranteed to match).
- The roof thickness is now the actual thickness of the roof, not the
"vertical dimension" of the roof. This means that the roof thickness can
now match intended layer thicknesses instead of you needing to do math
to work it out.
I've rewritten the internals of how roofs were generated to be hopefully
a lot simpler but as a tradeoff it's more restrictive. After the
skeleton is generated, non-uniform angles would be handled through
vertex splitting and moving. This works in simple scenarios but fails in
more complex ones. The new approach only handles non-uniform angles on
triangular faces. These faces are really easy to handle compared to
ngons, but are also more robust. The rafter edge angle is also now
handled using a clipping plane, which is a lot, lot simpler than vertex
sliding math.
Each face is now processed separately and then merged at the end. This
means that if a face has a different angle, it will now correctly
represent the different thickness at that portion of the roof.
In the process consistent roof thickness / rafter angle bugs were fixed.
E.g. two objects are selected - tesselation and profile and tesellation being active object. Using s-e with deselect tesselation object as it doens't have a usage but Bonsai would still try to run bim.enable_editing_extrusion_profile on it leading to the error.
Also fix possible issues for hotkey executed without active object (it seems all operators in hotkey do require active object)
Apparently (https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62572) coreutils have changed `mv -n` to throw an error now instead of working silently, therefore our build was failing on Mac when Github Action switched from Ubuntu 22 to 24.
`--update=none` seems to restore the old behaviour.
The for/else block was too greedy, meaning that if nothing was selected,
it would still try to create slabs from walls. I find for/else confusing
so I restructured the section to do more early returns and unindent the
logic.
• Removed bpy.context.preferences.themes[0] from the input and hardcoded it in the function.
• color_path now only requires the attribute path suffix (e.g., "user_interface.wcol_regular.text").
• Updated error handling to default to "dm" instead of "lm".
If you move a wall, and that wall has features (e.g. openings), it's
desirable to also move those openings (because they are invisible). This
is a special exception to `should_transform_children` because the
definition of the feature (opening) is inherently tied to the parent
(wall).
What wasn't considered is that this would typically then also move
subchildren of the features (e.g. fills like doors). I'm surprised
nobody caught this earlier.
I did also consider another approach where if you move a wall, it moves
all unfilled openings, and if you move a door which fills a opening, it
moves the opening too. Intuitively it sounds nice, but it doesn't work
because:
- Openings can have multiple fillings. If you move all fillings, they
all fight to move the openings.
- All logic about children goes one way: a placement may have child
placements relative to it. This breaks the convention (if moving a door
instead moves its opening) which can make brains explode.
- It starts to conflate rules about relative / referenced placements
with spatial decomposition. We assume all IFCs are valid and follows the
convention of relative placement but we cannot guarantee this. This also
leads to brain explosion.
Previously the code looked for additive facets (entity, instance)
anywhere in the query, and then only added the default basket once at
the beginning of the query. This change makes it look for additive
facets in order (that way you could start with a default selection, and
then later use an additive facet) and does it per facet list (so you can
omit the additive facet at the beginning of each list).
The IDS documentation used to state that "optional" on requirement
facets was "for information only" and didn't affect the pass / fail
state. This was changed a while ago (which I missed) to a new behaviour:
both no values and passing values will pass.
Previously, to edit a single IfcMaterial pset, you'd do so in the
material manager. In contrast, to edit a IfcMaterialSet pset, you'd do
so only when assigned to an object. Not only is it bad to edit psets in
two locations, it also means you can't edit psets until you've first
assigned the material set to an object which is bad. Now it's less code,
and material / material set psets are in the same spot.
There still more consolidation work to do, to allow editing of sets in
the material manager and to figure out how to show item psets, but it's
a step in the right direction.
Went back to previous toolbar : icon + type name + dot grid icon which all launch the type manager.
Validating the type manager popup now only closes it. Functionally is the same thing as clicking Cancel. Don't really know what to do to fix it. Does cancel make sense in the type manager ? Do we want to be able to undo what is possible to be done from within the type manager ?
You can change the active type either with the dropdown in the popup and in the grid flow. I changed it so that changing the type in the dropdown jumps to the corresponding page. BTW first time using github copilot to figure out the maths and it worked magically :) . I'm not against removing the dropdown but I feel like it's nice to see a plain list to choose from.
You can input a specific page number or scrub the page number field. Might be usefull if user has dozens of pages to flip through.
Right now the search is very simple, it does not implement fuzzy search or anything fancy with *
Add back the type dropdown and looking glass icon in the toolbar
Clicking on a type in the manager no longer closes the popup anymore.
A visual indictor is displayed on the active type
Changed the parameters icon in the individual type interface
Fix bug in "Slab from Walls" Shift + A code
This highlights a potential bug that already existed due to the way the attribute system is designed. Where the user mass-edits attributes from different IFC types, resulting in discrepancies due to the same attributes having a different index. eg IfcWindow has the Tag attribute at index 3, IfcDoor at index 4. So using the ALT modifier feature from vanilla Blender to mass-modify similarly named attributes on the Tag attribute of the IfcWindow will modify the PredefinedType attribute on the IfcDoor (index 3). For this reason I added the ALT modifier to the buttons so mass-operations are a conscious effort for more knowledgeable users. Might reconsider if problems arise but this is IMO a good QOL feature.
Also fix floating point precision errors on Pset float values stored as strings (re last commit)
To do so Right Click on a material in the Asset Browser then `Set To External Style`. The style editor must be loaded in the properties editor.
Also run black on a file from previous commit and fix a bug soft-locking the style editor when removing a style that is being edited
It's a naive implementation and very inefficient. Moreover there may be false positives because it's testing overlapping the evaluated mesh element with all openings applied.
No functional change apart from in dissolve_triangulated_edges where it will try to load the attribute if it can. I believe it will be easier in the long term to use the builtin Attributes system. Unfortunately Curves don't support Attributes yet. Also, attributes are more lightweight compared to custom properties.
It uses a custom boolean attribute on the mesh to decide whether an edge should be displayed or not. I think later on it can be used to get rid of the dictionary accessors which make the blend file size skyrocket.
- Highlight new objects in red when in aggregate mode
- Introduce an operator to add new objects to the current aggregate, accessible via a button in the header menu
Now, when trying to move an object that is a part of an aggregate, the
`OverrideMoveAggregate` will be called, meaning that it will move the whole
aggregation. It works by creating constraints and moving the aggregate instead
of the selected object.
It also introduces the idea of editing an aggregate (no hotkey yet). When active, the user
will be able to move the parts of the aggregate instead of the whole aggregation: https://imgur.com/IcCGtPN
- Enables the vanilla Blender autosave feature back, since a running modal operator deactivates it
- ESC is not used by any other vanilla keymap in the 3D viewport (may be conflict with other addons)
- Hitting ESC over another type of editor (eg enum field in the properties editor) will not trigger disabling ifc item mode
- You can now hit ESC in IFC edit mode to cancel editing the geometry and go back to ifc object mode
description:Crashes, error messages, and broken features
type:bug
body:
- type:textarea
attributes:
label:Bug Description
placeholder:|
Describe what problem occurred and what you expected to happen instead.
1. To reproduce this, open file '...'
2. Click on '....'
3. See error
- type:textarea
attributes:
label:Attachments
description:"Private files can be uploaded to https://ifcopenshell.org/upload.html - only viewed by core developers and will be deleted afterwards. Please submit your report first then upload private files afterwards."
placeholder:"If applicable, add screenshots to help explain your problem. Please also drag-drop any files necessary to show the error (rename the file extension from .ifc to .txt to upload)."
- type:textarea
attributes:
label:Debug and Error Output
description:"If this is in Bonsai, paste the output from the Copy Debug Information option in Bonsai. It can be found under Quality and Coordination -> Quality Control -> Debug. If this is a general software issue, if relevant include details about IfcOpenShell version, operating system, Python version, etc."
placeholder:"Describe a feature you'd like us to add, or a change to the user interface, design, or workflow for usability. If it's not obvious, explain why this feature is awesome. Note that feature requests must be specific and measurable."
| 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.