Verifies that convert_curve_to_mesh produces the closing edge instead of overwriting the last segment, matching the fix from PR #8043.
Generated with the assistance of an AI coding tool.
convert_curve_to_mesh built the edge chain of a polyline with extend, then for a
closed polyline overwrote the last edge with the closing edge instead of
appending it. That discarded the final real segment, so every closed IfcPolyline
loop came back one edge short and open. On the edit mode round trip the inner
void loop of an IfcArbitraryProfileDefWithVoids was then lost or misclassified,
and the profile was rewritten without its void, collapsing the extrusion to a
bounding box.
Append the closing edge instead, matching the IfcIndexedPolyCurve branch. Live
tested: the Tab round trip now keeps both loops closed and re-exports the
IfcArbitraryProfileDefWithVoids with its inner void intact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The auto-generate-ifc-based-space-boundaries path builds a geometry cache from the IFC file. If a user (or a BDD helper) only moves the Blender object matrix, the cache still sees the old IFC placement and the space footprint is open. Commit any moved visible bounding objects and clear the cache before generating the space.
Generated with the assistance of an AI coding tool.
When regenerating an existing IfcSpace, the default container is no longer required. Instead, the space's container is found via get_parent(element), which walks the full spatial hierarchy (aggregation, containment, nesting). For new space creation, the default container is still required.
Add optional container parameter to get_space_polygon_from_context_visible_objects so regeneration can pass the resolved container directly.
Generated with the assistance of an AI coding tool.
When several elements match the same space face, offset matches that only
duplicate coplanar coverage are now skipped, and a single bounding element
within a small plane offset gets the full space face instead of a clipped
polygon. Existing boundaries are removed before regeneration so stale 2nd
level boundaries are not left behind, and the Bonsai operator delegates
element filtering to auto_generate_boundaries.
Regenerates SmallHouse boundaries to match the reference output and keeps
the ExternalEarth opening unioning intact.
Generated with the assistance of an AI coding tool.
Move Blender-independent boundary generation algorithm from Bonsai
(GPL) to ifcopenshell.util.boundary (LGPL):
- ifcopenshell.util.shape.dissolve_faces: reconstruct polygonal faces
from triangulated mesh using original edges from get_edges() + Union-Find
- ifcopenshell.util.boundary.auto_generate_boundaries: full boundary
generation algorithm using IFC geometry (numpy, shapely) without
Blender — replaces bmesh, matrix_world, tool.Cad.is_x, mathutils with
numpy equivalents
- Uses existing ifcopenshell.api.boundary.assign_connection_geometry
for connection geometry creation
- Uses existing ifcopenshell.util.placement.a2p + np_normal for face
matrix construction
- BOUNDARY_ELEMENT_CLASSES expanded to include IfcColumn and
IfcCurtainWall
Bonsai's boundary/operator.py auto_generate_boundaries is now a thin
adapter handling Blender-specific preprocessing (flushing moved
objects, building iterator + tree) then delegating to the util module.
Added 12 tests: 3 for dissolve_faces, 3 for auto_generate_boundaries.
Generated with the assistance of an AI coding tool.
Add buffer(0) validation for space_face_polygon and face_polygon
before intersection, following the same pattern as tool/cad.py.
Wrap the intersection in try/except for shapely.errors.GEOSException
to catch remaining topology errors. On exception, set
bonsai.last_error (so the 'Copy Error Message To Clipboard' button
appears in the UI), report an ERROR to the operator, and continue
processing other face pairs instead of crashing.
Generated with the assistance of an AI coding tool.
Add explicit import bonsai.core.tool to satisfy pyright's type checker,
which requires submodules to be explicitly imported rather than relying
on transitive imports from import bonsai.
All 8 space-generation tests now create IFC walls/slabs with real
solid-block representations using IfcExtrudedAreaSolid, instead of
relying on the old Blender-mesh bisection path (broken since 79ee88da5
switched to IFC-geometry-only for boundary detection).
- _BlockHelper provides create_wall (10x10xheight block) and create_slab
(12x12x1.0 block) helpers using standard IfcOpenShell API calls.
- The wall block bisects to a 10x10 polygon at the cutting plane
(matching the old cube-behaviour), and auto-height detects wall_top_z.
- Pre-existing height assertions (z=10) now pass correctly because
auto-height = wall_top_z - base_z = 10 - 0 = 10 (the old values were
wrong for the Blender path where h defaulted to 3).
- test_regenerate_after_wall_height_change modifies the IFC extrusion
depth directly and bumps the geom cache token via
_bump_geom_cache_token() instead of relying on Blender depsgraph.
- No Blender cubes are created except when absolutely needed for
selection/active-object flow (regeneration, apply-height).
- Added ifcopenshell.util.representation to imports.
- Import _bump_geom_cache_token from bonsai.tool.spatial.
Generated with the assistance of an AI coding tool.
Covers all three covering operators with success and error-path tests
using the Prophecy mocking framework. The key assertion verifies that
get_space_polygon_from_context_visible_objects' return value is unpacked
so the polygon (not the tuple of polygon+bounding_elements) reaches
set_covering_representation_from_polygon.
Shapely geometry objects are not JSON-serialisable (Prophecy call
serialisation), so we use the plain integer 42 as a stand-in for the
polygon value.
Generated with the assistance of an AI coding tool.
Three covering core functions (add_instance_flooring_covering_from_cursor,
add_instance_ceiling_covering_from_cursor, regen_selected_covering_object)
used the old single-value assignment from
get_space_polygon_from_context_visible_objects, which now returns a
(polygon, bounding_walls) tuple. The isinstance(str) guard never fired,
causing the tuple to flow into set_covering_representation_from_polygon
and raise a shapely error.
Fix by unpacking space_polygon, _ at all three call sites.
Generated with the assistance of an AI coding tool.
Move Blender-independent space generation algorithms from Bonsai
(GPL) to ifcopenshell.util (LGPL):
- ifcopenshell.util.shape.bisect_mesh_plane_vf: vectorized numpy
triangle/plane intersection for mesh bisection
- ifcopenshell.util.element.iter_top_connections: walker for
IfcRelConnectsElements(TOP) relationships
- ifcopenshell.util.space: new module with get_boundary_lines,
get_space_polygon, get_auto_space_height and height detection
helpers — all operating on IFC geometry without Blender
Bonsai's tool/spatial.py now delegates to these utilities via
thin wrappers, keeping only Blender-specific concerns (cache
management with depsgraph invalidation, UI property reads).
tool/wall.py iter_wall_slab_connections delegates to
ifcopenshell.util.element.iter_top_connections.
Added 22 tests: 6 for bisect_mesh_plane_vf, 10 for space
generation algorithms, 4 for iter_top_connections, 2 Bonsai
integration tests for cache behavior.
Generated with the assistance of an AI coding tool.
Space height is now auto-detected using IFC geometry directly
(ifcopenshell.geom.create_shape + get_shape_bottom/top_elevation)
instead of Blender object bounding boxes. This fixes height detection
when the slab above is not loaded in Blender.
Detection priority:
1. IfcRelConnectsElements(TOP) connections on bounding walls
2. IfcSlab / IfcRoof elements above with XY overlap to space polygon
3. Minimum wall top Z of bounding walls
4. Fallback to space_height property (default 3m)
Added space_height and force_space_height properties to
BIMSpatialDecompositionProperties. The height field is synced to
the active space's height via active_object_callback (msgbus), not
in draw().
Added ApplySpaceHeightToSelection operator to modify
IfcExtrudedAreaSolid.Depth in place without regenerating footprint.
bounding_walls changed from list[tuple[element, obj]] to
list[entity_instance] since Blender objects are no longer needed.
Generated with the assistance of an AI coding tool.
Add a paste button to IfcRelSpaceBoundary specific attributes
(RelatingSpace, RelatedBuildingElement, ParentBoundary,
CorrespondingBoundary, PhysicalOrVirtualBoundary,
InternalOrExternalBoundary) reusing the existing
copy_attribute_to_selection core function.
The core function value type hint is broadened from Union[str, None]
to Any since boundary relation attributes pass IFC entity instances.
Generated with the assistance of an AI coding tool.
Removing translate_obj_to_z_location from the existing-IfcSpace
regeneration branch. The ShapeBuilder rewrite (d8de62308) builds
geometry in local space preserving obj.matrix_world, making the
translate call redundant — it adds z on top of the already-correct
location.z, producing 2*z.
Add test_regenerate_space_preserves_z_location to cover the
regeneration path with a non-zero Z elevation.
Generated with the assistance of an AI coding tool.
This branch carried v0.8.0's strict `[tool.ty.rules] all = "error"` config but
not the source fixes that were made upstream to satisfy it, so both ci-lint ty
gates were failing: `poe ty-ios` reported 256 diagnostics and `poe ty-bonsai`
258. Both are now clean.
Most fixes are ported from v0.8.0 and follow two idioms: initialise a name
before a conditional that may not bind it (plus an `assert` where the invariant
is real but not provable), and close an exhaustive `if`/`elif` chain with
`else: assert False, <discriminant>`.
The branch's own newer accessors are preserved throughout - `.file`,
`.declaration`, `file.types()`, `get_max_id()` are kept rather than reverted to
`wrapped_data.*`, and non-ty upstream changes (notably the in-progress geometry
cache removal) are deliberately not pulled in.
Notable fixes that are not straight ports:
* ifcopenshell_wrapper.pyi: `entity_instance.file` was declared as
`def file(self) -> file`, where the property name shadows the `class file`
below it, so the annotation resolved to `Unknown`. Every `element.file` in
the codebase was therefore unchecked. Qualifying it to `ifcopenshell.file`
restores `.schema` to its Literal union and surfaces no new diagnostics.
* model/wall.py: a duplicated merge fragment in the void-straddle path ran an
always-true `if void_straddles:` that read `new_opening` from the mutually
exclusive branch (stale value, or NameError on the first iteration), followed
by an unreachable duplicate `elif`. Removing it makes the file match v0.8.0.
* light/operator.py: upstream's own fix unpacks three targets from two values
and raises ValueError unconditionally; corrected to `None, None, None`.
* assign_system.py, validate.py, geom/main.py: walrus-in-genexp is valid at
runtime (PEP 572 binds in the containing scope) but ty does not model it;
rewritten as explicit loops, matching upstream.
Verified: poe ty-ios, poe ty-bonsai, ruff check src/ nix/, black --check .,
and compileall -W error at py3.10 (ifcopenshell-python) and py3.11 (bonsai).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
angle_round_threshold was only assigned inside the `distance > 0`
branch of calculate_distance_and_angle, but read unconditionally
whenever should_round is True. When the mouse sample coincides with
the last placed point (distance == 0), such as the first mouse move
after placing a wall's start point on a YZ plane view, this crashed
the modal wall tool.
angle_round_threshold is a fixed cutoff unrelated to whether distance
is currently zero, so it is now assigned once before the branch.
Fixes#8597.
Generated with the assistance of an AI coding tool.
(cherry picked from commit 89523999b3)
test_returns_none_when_report_file_absent/empty build a MagicMock repo
without configuring index.unmerged_blobs(), so it returned a truthy
MagicMock and git_mergetool's load-bearing "unresolved conflicts remain"
fallback (tool/ifcgit.py:646-647) returned that list instead of None -
failing "assert [] is None". The production fallback is correct and
intentionally left untouched; the tests just misrepresented the
"mergetool resolved cleanly" scenario they are named for. Set
mock_repo.index.unmerged_blobs.return_value = {} in both.
Verified in headless Blender: test/tool/test_ifcgit.py::TestGitMergetool
2 failed / 1 passed -> 3 passed.
This change was made with the assistance of an AI tool.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 51ab38de27)
Browsers were caching /static/js and /static/css for the standalone
webui (costing, gantt, drawings, index, demo pages) indefinitely, so a
shipped JS fix (e.g. the Download CSV button) would only reach a user
after a manual hard refresh.
Two changes, applied consistently across all five webui pages.
1. Every locally served link/script tag in the pystache templates now
carries a ?v=<bonsai version> query string, falling back to a static
asset mtime hash when BONSAI_VERSION isn't set (e.g. running
sioserver.py standalone). Since get_bonsai_version() includes the
build's commit hash, the token changes on every shipped update.
2. Responses under /static/ and /jsgantt/ now carry
Cache-Control: no-cache, must-revalidate. This covers what query
stamping alone can't reach: cost.js and gantt.js statically import
utilities/costui.js by a fixed relative path with no query string, so
that nested module still needed server side revalidation to pick up
changes.
Verified against a live aiohttp instance of sioserver.py: rendered
HTML for all five routes shows the stamped URLs, and the token
changes when BONSAI_VERSION changes between two server runs. A
conditional GET against a static file with a stale If-Modified-Since
header confirms the cheap 304 revalidation path still works.
Also used this instance plus a real headless Chromium (Playwright) to
click test the previously untested Download CSV button on the costing
page. The ribbon renders it correctly, and clicking it (with a
synthetic cost-items table injected into the DOM to stand in for a
connected Blender's data) triggers a real Blob download with the
correct filename and CSV content. No bug found, the button works as
intended.
AI-generated with Claude Code.
(cherry picked from commit e759135608)
Stefano's final ask on #6251 was specific: the ODS/XLSX export should
show exactly what the cost panel shows, ID (Identification), Name,
Quantity, Value, Total Cost, no more, no less. The previous fix in
this PR removed the internal bookkeeping columns but still exported
Description, Unit and a per-category cost breakdown (Labor Cost,
Material Cost, etc), none of which appear in the panel.
Presentation formats (.ods/.xlsx) now use an explicit allow-list of
columns instead of a block-list of internal ones, and relabel headers
to match the panel's own wording (ID / Value / Total Cost). The .csv
format is unchanged: csv2ifc still reads back the extra bookkeeping
columns for the import round trip, which is why it keeps them.
Also add a "Download CSV" button to the browser costing view
(Generate spreadsheet browser), which previously only offered a
clipboard-based Copy Selected. It reuses the already-rendered table
(respecting the user's column visibility settings) and triggers a
real file download, dropping only the UI-only Actions column.
AI-generated with Claude Code; reviewed and tested by Petru Conduraru.
(cherry picked from commit 1df738d968)
* Fix ci-bonsai-daily: ProjectLibraryData duplicate parent-library enum
parent_libraries_enum() adds an explicit entry for get_root_context(),
then loops over cls.data["project_libraries"] (all IfcProjectLibrary
entities) and appends each. For a library-only file (no IfcProject),
get_root_context falls back to the top-level IfcProjectLibrary itself,
so the root is appended twice with the same enum key (its STEP id),
which Blender EnumProperty requires to be unique -> the data load
asserts. Normal project files are unaffected (root is an IfcProject
whose id never collides with a library id).
Skip library_id == root.id() in the loop (dedup by id, the colliding
key). Verified in headless Blender:
test_project_library_data.py::TestLibraryOnlyFile goes from 1 failed /
5 passed to 6 passed.
This change was made with the assistance of an AI tool.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Bonsai: repair library files missing the required IfcProject, not just the symptom
Per the IFC Project Context concept template, every project data set (library
files included) shall contain exactly one IfcProject, and IfcProjectLibrary
instances are assigned to it via IfcRelDeclares. There is no such thing as a
spec-valid file rooted on IfcProjectLibrary alone.
get_root_context() (added in 260a387069, #8184) treated a missing IfcProject
as license to use the top-level IfcProjectLibrary as the file's root context
instead. That invalid premise is why project_libraries() (which walks every
IfcProjectLibrary, root included) then re-added that same entity, producing
the duplicate, colliding enum key this PR originally papered over with a
dedup guard.
Add tool.Project.ensure_project_context(), which repairs a file missing
IfcProject by creating one and declaring the file's root-level
IfcProjectLibrary instances to it, and tool.Project.open_library_file(),
which opens a library file through that repair. Route all three
IfcStore.library_file load sites in SelectLibraryFile through it. Downstream
code (get_root_context, ProjectLibraryData, RefreshLibrary,
AddProjectLibrary) now always operates on a spec-valid model, so the
duplicate enum entry cannot occur; the previous one-line dedup guard in
parent_libraries_enum() is kept only as cheap defense in depth for callers
that bypass the load-time repair, not as the fix.
Rework test_project_library_data.py: the previous _make_library_only_file()
fixture built an invalid library-only model and asserted that as correct
behaviour. Replace it with a spec-valid fixture (IfcProject + IfcProjectLibrary
declared to it) for the downstream tests, and a malformed fixture used only to
exercise the new repair path.
Verified live in headless Blender (isolated profile): reproduced the original
duplicate-enum-key failure mode, then confirmed ensure_project_context/
open_library_file repair a malformed file and ProjectLibraryData,
refresh_library and add_project_library all operate correctly on the result,
with no duplicate keys and no regression on already-valid files or IFC2X3.
This change was made with the assistance of an AI tool.
* Bonsai: stop supporting library-only files, do not repair them
Per Moult's feedback: if the IFC is invalid, our default position is to not
support it, not to patch around it. A library file with no IfcProject is
invalid IFC (Project Context concept template requires exactly one
IfcProject), and it is not ubiquitous: every library file bonsai ships under
bim/data/libraries has an IfcProject with the IfcProjectLibrary declared to
it via IfcRelDeclares. The single #8183 report is an outlier, not a common
authoring pattern worth accommodating.
Remove tool.Project.ensure_project_context() and open_library_file() (the
load-time repair added in the previous commit here) and revert
SelectLibraryFile's three load sites to plain ifcopenshell.open. Simplify
get_root_context() back to returning ifc_file.by_type("IfcProject")[0]
directly, no IfcProjectLibrary fallback: a file without IfcProject now raises
IndexError instead of being silently treated as valid. AddProjectLibrary's
nest-under-library branch is now dead code (root_context is always an
IfcProject) and is removed. The one-line enum dedup guard from the original
commit here is also removed: since get_root_context can only return an
IfcProject or raise, an IfcProject id can never collide with a library id, so
the guard has nothing left to guard against.
Rework test_project_library_data.py: drop the invalid _make_library_only_file
fixture and its tests, which asserted an unsupported model as correct
behaviour. Replace with a single spec-valid fixture matching bonsai's own
shipped library files (IfcProject + IfcProjectLibrary declared to it), used
for the ci-bonsai-daily regression test and the refresh/add-library
operators, plus one explicit test that get_root_context raises for a file
without IfcProject, documenting that this input is intentionally
unsupported rather than silently tolerated.
Verified live in headless Blender (isolated profile, source-loaded, never
the real profile): confirmed the removed methods are gone, that a
library-only file now raises instead of being handled, that
ProjectLibraryData/refresh_library/add_project_library all work correctly
on a spec-valid model with unique enum keys, and spot-checked that every
library file under bim/data/libraries already has an IfcProject.
This change was made with the assistance of an AI tool.
* Bonsai: inline get_root_context, trim docstrings, confirm get_parent_library unchanged
Per Moult's round 3 review. get_root_context added nothing over
ifc_file.by_type("IfcProject")[0], which is guaranteed by the IFC Project
Context concept template; remove it and inline the call at its three sites
(operator.py's RefreshLibrary and AddProjectLibrary, data.py's
parent_libraries_enum). Trim the get_parent_library docstring to one line;
its logic is untouched by this PR, byte for byte identical to origin/v0.8.0,
and still returns None only when project_library has neither Nests nor
HasContext, never for a library declared directly to IfcProject.
Rework test_project_library_data.py to match: replace the two
get_root_context-specific tests with one that exercises the real call site
(ProjectLibraryData.parent_libraries_enum raising IndexError for a file
without IfcProject), and add an explicit test that get_parent_library
returns None for a genuinely orphaned library. Also drop a long inline
comment that restated what the test body already shows.
Verified live in headless Blender (isolated profile, source-loaded, never
the real profile): all 17 test/bim/module/project tests pass, including the
new get_parent_library None-for-orphan case. Ran the full test/bim suite
before and after on the identical harness: 82 failed/1335 passed both times,
same failing tests (all pre-existing, unrelated to this module).
This change was made with the assistance of an AI tool.
* Bonsai: fix EditProjectLibrary leaving stale declarations after reparenting
Per Moult's round 4 review. The assertion change (get_parent_library(root)
now returns the IfcProject instead of None) is correct: in the old
library-only test model a top-level library had neither IfcRelNests nor
IfcRelDeclares, so None meant "top level". In the new spec-valid model a
top-level library is always declared to the guaranteed IfcProject via
IfcRelDeclares, so get_parent_library correctly resolves it through the
HasContext branch instead of falling through to None. get_project_hierarchy
already keys top-level libraries under the project for exactly this reason,
so the library tree still renders correctly.
Auditing every caller found one real bug in EditProjectLibrary, which
Gorgious56 originally wrote for the library-only model. Its move-library
logic assumed a top-level library (previous_parent_library is None) needed
no cleanup before nesting it under a new parent, and that unnesting a
library back to the project needed no new relationship because it was
"already assigned by default". Both assumptions relied on a top-level
library never actually holding a IfcRelDeclares, which is no longer true.
Reproduced live: moving a project-declared library under another library
left its old IfcRelDeclares dangling alongside the new IfcRelNests (an
invalid double parentage), and moving a nested library back to the project
left it with neither relationship, orphaning it out of the tree entirely.
Fixed by tearing down whichever of IfcRelDeclares/IfcRelNests the library
previously had before establishing whichever one the new parent requires,
instead of assuming which prior state applies.
Added tests: get_parent_library resolving a nested sub-library to its
library parent (the third contract case alongside project-declared and
orphaned), and both EditProjectLibrary reparenting directions, which fail
without the operator.py fix and pass with it.
Verified live in headless Blender (isolated profile, source-loaded, never
the real profile): all 20 test/bim/module/project tests pass. Ran the full
test/bim suite before and after on the identical harness: 123 failed/1294
passed before, 123 failed/1297 passed after, identical failing test names
in both runs (diffed), the extra 3 passes are the new tests above.
This change was made with the assistance of an AI tool.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit d1f9e5243e)
on_depsgraph_update and on_depsgraph_update_caps are registered together
as persistent depsgraph handlers (bim/module/clip_box/__init__.py:50-52).
on_depsgraph_update guards with `if cls._file_loading: return`, but the
sibling on_depsgraph_update_caps did not, so a depsgraph tick during the
file-load window still ran it. Beyond the failing test, this can re-arm a
cap-rebuild bpy.app.timers callback in the exact load window _on_load_pre
cancels timers for, against regions whose GPU state is not yet wired.
Add the same _file_loading guard as the first check.
Verified in headless Blender:
test_clip_box.py::TestRefreshTimerLifecycle::test_depsgraph_update_no_op_while_loading
1 failed -> passed.
This change was made with the assistance of an AI tool.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 537317b26f)
Link IFC with 'Use Cache' unchecked crashed with FileNotFoundError
when no .ifc.cache.blend existed yet (a fresh link). Regression from
35e3d9c42, which refactored the cache-clear guard from
'if not self.use_cache and blend_filepath.exists()' into
should_clear_cache() but dropped the existence check on the
not-use_cache path, so os.remove() ran on a non-existent file.
Check blend_filepath.exists() first in should_clear_cache() so the
remove is never attempted when there is nothing to clear, while
keeping the query-mismatch cache invalidation intact.
Fixes#8350
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4a095f9810)
tool.Bsdd.identifier_url() (pset/ui.py pset name check in the Property
Sets panel) reads client.baseurl unconditionally, but the test stub
never had that attribute, so any scenario that opens the Property Sets
panel dies with AttributeError under the stub. The boolean.feature
scenarios only surfaced this once their STEP id failures were fixed,
the id failure had been masking it. Mirror the real bsdd.Client
default so identifier_url() resolves to the standard identifier URL.
This change was made with the assistance of an AI tool.
(cherry picked from commit 81a42cec1a)
The two boolean.feature scenarios pinned representation item objects by
absolute STEP id (Item/IfcHalfSpaceSolid/90, the BBIM_Boolean pset text
[91]). Those ids shift every time any earlier entity allocation in an
empty project changes (latest instance: #8577 moved 90 to 86), so this
cluster re-breaks on unrelated commits.
Make the object-name and panel-text BDD steps run their argument through
replace_variables, the same substitution 'the variable' and the
connection steps already use, and have boolean.feature capture the real
ids from the IFC file (by_type(...)[0].id()) into variables at the point
the entities are created. The steps stay strict: the substituted name
must still resolve to exactly the named object, there is no wildcard
matching. Substitution is a no-op for every existing feature string
without a {variable} placeholder.
This change was made with the assistance of an AI tool.
(cherry picked from commit 45fa04a94b)
Two independent test-harness/fixture defects in test/bim/test_feature.py:
- OperatorSpy had no bl_rna, so any BDD step that redraws a panel calling
helper.draw_filter() (which tests "module" in op.bl_rna.properties)
crashed with AttributeError. Give OperatorSpy a bl_rna property that
forwards to the real registered operator class
(bpy.types[bl_idname].bl_rna), matching live UILayout.operator()
semantics. Fixes test_select_all_walls and test_edit_filter_query.
- The shared "I create default MEP types" step looked up
bpy.data.objects["IfcDistributionPort/Port"], but port creation never
sets port.Name, so tool.Loader.get_name deterministically names the
object "IfcDistributionPort/Unnamed". Update the literal. Fixes the MEP
scenarios (connect/transition/bend) that share this setup.
Verified in headless Blender: OperatorSpy scenarios 2 passed (were
AttributeError); MEP test_connect_mep_elements* go from
KeyError 'IfcDistributionPort/Port' to passing.
This change was made with the assistance of an AI tool.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit e27624c77f)
Pset names containing spaces (e.g. "SOLIDWORKS Custom Properties") were
not quoted when building selector keys in SelectSimilarData, causing
get_element_value to fail when the operator ran. Now wraps pset names
and property names in double quotes if they contain spaces, consistent
with the selector syntax used elsewhere.
Generated with the assistance of an AI coding tool.
(cherry picked from commit 836d57e7ff)
Bsdd.get_dictionaries() unconditionally did cls.client = bsdd.Client(),
replacing whatever client was already set - including the
bSDDClientStub the BDD suite injects at module load
(test_feature.py: tool.Bsdd.client = bSDDClientStub()) to avoid live
network calls. Because "Load bSDD Dictionaries" is the first step of
every bsdd.feature scenario, the stub was discarded before its fixture
data ("LCA", "BonsaiTestDict") could ever be returned.
The re-init is unnecessary: bsdd.Client.__init__ only sets baseurl and
blank tokens, and the next line already updates baseurl defensively via
hasattr. Drop the clobbering assignment; reuse whichever client is
already set.
Verified in headless Blender: bsdd scenarios (load dictionaries, search
all/single dictionary) go from 3 failed ("Could not see LCA/
BonsaiTestDict") to 3 passed.
This change was made with the assistance of an AI tool.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 5c8eab981c)
Fixes#4443. The Wall/Slab/other authoring tools (BimTool subclasses)
already bind Shift+Q to bim.perform_quantity_take_off via hotkey_S_Q,
but the Spatial tool has its own separate keymap/operator
(bim.spatial_hotkey) that never registered a Q entry, forcing users to
switch tools just to (re)calculate quantities for a selected element.
Added the same Shift+Q keymap entry and a matching hotkey_S_Q handler
to the Spatial tool, mirroring BimTool's existing behavior exactly
(including the same selected-objects guard).
The other part of the request, a bulk "calculate all quantities"
entry point, already exists today: bim.perform_quantity_take_off
computes quantities for every IfcElement when no objects are
selected, exposed via the Scene > Quantity Take-off panel regardless
of which workspace tool is active, so no change was needed there.
AI-generated, reviewed and tested by BIMvoice.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 98a25eca96)
can_nest() only permitted IfcElement-to-IfcElement pairs, so nesting
two IfcElementType objects (e.g. an IfcElementAssemblyType nesting a
component IfcDoorType) was silently rejected. IfcRelNests.RelatingObject/
RelatedObjects are typed as the general IfcObjectDefinition in the
schema, so type-to-type nesting is schema legal, IfcOpenShell's core
nest.assign_object API already handles it generically, and the Nest
UI panel is driven purely by ifcopenshell.util.element.get_nest/
get_components (IFC data queries, not Blender collection structure),
so once the relationship exists it displays correctly with no other
changes needed.
Extended is_compatible_class to also accept a same-kind IfcTypeProduct
pair. Mixing an occurrence element with a type is intentionally still
rejected, that isn't a real modeling pattern.
Verified live in headless Blender: type-to-type nesting now creates
a real IfcRelNests and the Nest panel's own data functions reflect
it correctly; mixing an occurrence with a type is still rejected;
existing element-to-element nesting is unaffected.
Generated with the assistance of an AI coding tool.
(cherry picked from commit 99c370b755)
Footings are authored two ways with different local axis conventions. Beam-like
footings (STRIP_FOOTING, FOOTING_BEAM) are a profile extruded along local Z, so
Length is local Z and the cross section sits on local X (Width, horizontal) and
local Y (Height, vertical). Slab-like footings (PAD_FOOTING, PILE_CAP) have their
footprint on local X/Y and their thickness (Height) on local Z.
The engine rule set is keyed per IfcFooting and cannot branch on predefined type,
so the previous static rule (Height=net_get_z, Length=net_get_max_xy, Width=null)
swapped Length and Height for beam-like footings and never emitted Width.
Add predefined-type-aware get_footing_length/width/height to the IfcOpenShell and
Blender calculators, and point the IfcFooting rule at them in all four IFC4/IFC4X3
ios/Blender rule files.
Confirmed by authoring footings through the real Bonsai generators and measuring
world-axis orientation: a beam-like footing with a 0.3 wide by 0.6 tall cross
section and 6.0 run reports Length 6.0, Width 0.3, Height 0.6, with the 0.3
physically horizontal and 0.6 physically vertical; a 2.0x1.5x0.3 pad reports
Length 2.0, Width 1.5, Height 0.3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4ceadd8f10)
See #6835. Qto_OpeningElementBaseQuantities came out axis-scrambled for
openings authored in a Z-up local frame (X along the voided wall, Y
through it, Z vertical), which is how Bonsai authors every wall opening:
- The IfcOpenShell engine mapped Height to the local Y extent and Depth
to the local Z extent, so a 0.9 x 2.0 door opening with Bonsai's
default 1.2m void depth reported Height 1.2 and Depth 2.0, and Area
(max side area) picked the through-wall side, 2.4 instead of 1.8.
This matches the wrong Height=1.2/Area=1.2 screenshots reported for a
1x1 window opening in #6835.
- The Blender engine mapped opening Width to get_length, which returns
the longest bounding box edge, i.e. the opening height for typical
door openings (the same defect 4adaf0d fixed for IfcDoor Width), and
get_opening_depth used min(x, y), which returns the opening width
whenever the width is smaller than the void depth.
The IfcOpenShell engine now has opening-aware internal calculators
(get_opening_width/height/depth/area) that detect horizontal (slab
style) openings with the same heuristic as the Blender calculator, so
slab opening depths keep reporting the slab thickness. The Blender
ruleset uses get_x for opening Width, and get_opening_depth measures the
through-element Y extent for vertical openings.
Door and window quantities themselves are addressed separately: the
Blender engine door Width was fixed in 4adaf0d, and the remaining
door/window defects (door not quantified on the IfcOpenShell engine,
inflated areas) are fixed by the attribute-based calculators in #8389.
Generated with the assistance of an AI coding tool.
(cherry picked from commit efac8a0ec0)
falken10vdl reviewed 16b1b4e7b1 on #8843 and pointed out that tagging
every area for redraw was overkill. The actual problem was that the
Object Material panel and the scene Materials list read from plain
python caches (ObjectMaterialData and MaterialsData) that only get
invalidated when the Materials editing UI list is reloaded, which
never happens while you are not in editing mode. The redraw itself was
never the issue, closing the rename dialog already triggers one.
Removed the tag_redraw loop from RenameMaterial and instead call the
existing bonsai.bim.module.material.data.refresh() function from
core.rename_material, unconditionally, through a new tool.Material.refresh()
method. This is the same invalidate-on-next-load mechanism already used
by every other module's Data classes, just wired up for this operator
too, instead of introducing a new one.
Also updates the core tests to prescribe the new unconditional refresh()
call, and adds tool-layer coverage for tool.Material.refresh().
Generated with the assistance of an AI coding tool.
(cherry picked from commit 7ab0628c54)
theoryshaw tested #8843 and asked for the new name to show up right
away instead of needing a manual refresh. The Object Material panel
and the scene Materials list both already re-read live IFC data on
their next draw (tool.Ifc.Operator purges those caches after every
IFC-mutating operator), so the button text was correct on the next
redraw. What was missing was the redraw itself: the material name is
a plain button label, not an RNA property Blender tracks, so nothing
told the Properties editor to repaint after the rename dialog closed.
Tag every area for redraw once the rename completes, the same pattern
used elsewhere in Bonsai for popup-triggered edits that need an
immediate repaint.
Also adds core-layer test coverage for rename_material, which had
none.
Generated with the assistance of an AI coding tool.
(cherry picked from commit 16b1b4e7b1)
Adds a "Rename Material" entry to the context menu that already
extends every button in the properties editor (UI_MT_button_context_menu),
triggered when right-clicking a material name button
(bim.select_by_material) that points to a real IfcMaterial. This
gives a quick entry point to renaming from the Object Material panel
without navigating to the scene Materials list.
This follows the pattern that #6680's thread converged on: theoryshaw
requested a right-click entry (rather than a pencil icon or
double-click) that keeps the existing single-click select-by-material
behaviour intact. falken10vdl is the issue's assignee; this is offered
as a starting point for that discussion, not a replacement for it.
Generated with the assistance of an AI coding tool.
(cherry picked from commit 8c667b8ae0)
Add an "Is Selected" checkbox to each target-view category header in
BIM_UL_drawinglist that toggles selection for all drawings in the
category. The toggle only affects drawings currently visible in the
list (honoring the show_drawings_on_sheets_only filter), and the header
checkbox reflects the aggregate selection state of its drawings.
Also make category headers more obvious: wrap them in a box() for a
distinct inset background and make the header name clickable to
expand/contract the category (same as the disclosure triangle).
Ref: #8825
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit e52e5e2e58)
Adds a "Show Only Drawings on Sheets" toggle below the drawing list. When
enabled, the list is filtered to drawings referenced by at least one sheet
(target-view headers with no sheeted drawings are hidden too), and
bim.select_all_drawings only acts on the visible/filtered drawings.
A drawing is considered sheeted when its drawing document Location matches a
document reference Location on any SHEET-scoped IfcDocumentInformation.
Filtering is computed live so it reflects sheet edits without reloading.
Closes#8823
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 2d59ea1988)
* docs: cover the Blender 5.1 / Python 3.13 transition in installation guides
The system requirements still listed Blender 4.3-4.5 with Python 3.11
only, and nothing documented the pitfall from issue 7623: importing
preferences into a Blender whose Python version changed carries over an
incompatible Bonsai build that silently fails to load. Document the two
Python generations, that Get Extensions picks the matching build
automatically while manual zip installs do not, and the
uninstall-reinstall step that resolves the upgrade case.
Generated with the assistance of an AI coding tool.
* docs: keep it simple, only Blender 5.1 and 5.2 with Python 3.13
Per review, drop the descriptive text and the Python 3.11 line.
Generated with the assistance of an AI coding tool.
(cherry picked from commit 55a2430d71)