Implemented as described in #5753, with two options:
- A nag dialog with save or cancel options.
- An autosaved file.
Settings are in preference to activate the feature (default: off), the period before prompting/saving,
and choosing between the two methods.
Prevent the autosave file being added to the recent files list when the user opens the original, but selects to open the autosaved version.
black/ruff
This commit was created using AI assistance. Cursor for the initial code, then Grok and I fixing all the errors
that Cursor made. Finally Copilot did a code review.
I have reviewed and tested the code, and I understand it, and it works and does not introduce any obvious bugs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Grok
Co-authored-by: Cursor
(cherry picked from commit 6f1737bb58)
- tool.py: drop the `-> int` annotation on the Parametric interface's
get_geom_generation stub; its `pass` body implicitly returns None, which
ty can't reconcile with the runtime @interface/@abstractmethod rewriting
it never sees statically. Matches the file's other stubs (-> None).
- railing.py: qualify the "BIMRailingProperties" string annotations as
"prop.BIMRailingProperties" on the two functions using it, since the bare
name was never imported into this module's namespace.
- product.py: suppress ty's missing-argument errors on
copy_z_rotation_to_selected's Surveyor.get_z_rotation/set_z_rotation
calls with targeted ty: ignore comments. The function is unused and its
two dependencies were never implemented on the concrete Surveyor tool;
left as-is rather than deleted or implemented.
(cherry picked from commit 9f848a73e1)
- gizmos.py: TYPE_CHECKING-guard `import bmesh` for the string-literal
annotation in build_schematic_mesh; suppress the still-unresolved
gizmo_textures import in TexturedQuadGizmoMixin (WIP dependency, not dead
code).
- model/__init__.py: register the `decorator` submodule, which unregister()
already calls (would have raised NameError on addon disable).
- mep.py / tool/model.py: add explicit imports for bonsai.core.geometry and
bonsai.core.model, previously only reachable by accident of import order.
- Test files: add explicit ifcopenshell.api.pset / ifcopenshell.util.element
submodule imports used but not imported.
(cherry picked from commit 4fb8af2278)
Convert bare Mock() to Mock(spec=bpy.types.Object) for Blender-object
stand-ins in TestRecalculateWallsWithNewConnections, TestMEPActionGuards,
and TestRecreateAggregateIteratesAllNew so typos on the Blender API
fail loudly instead of silently returning a MagicMock.
Replace the ad-hoc Mock() ConnectionRecord stand-in in
TestRecreateConnectionsZipsPairs with a real ConnectionRecord instance,
which pins field names at construction and catches drift if the
dataclass fields ever get renamed.
IFC entity mocks remain bare Mock() intentionally: entity_instance
attributes are schema-driven at runtime rather than defined statically
on the class, so spec= would refuse the .GlobalId / .HasFillings /
.ConnectedTo attribute writes the tests need.
Relates to #8088.
Generated with the assistance of an AI coding tool.
Rewrite tool.Array.select_only_parent as a thin call to
tool.Blender.select_and_activate_single_object; drop the ad-hoc
per-child deselect loop and the unused parent_element parameter.
Replace the tail parent_obj.select_set(True) in _regenerate_array_body
with tool.Blender.set_object_selection, which wraps select_set in the
hidden-object try/except the utility already owns.
Relates to #8088.
Generated with the assistance of an AI coding tool.
Add tool.Array.is_array_child helper. Port decorator and MEP
action gizmos (lock, pen, join) hide on array children — writes
on children get wiped by the next regen, and the port topology
is inherited from the parent.
Introduce tool.Array.select_only_parent and wire it into both
bim.regenerate_array and bim.finish_editing_array so post-regen
state converges on parent-only-selected + active. Grow and shrink
paths otherwise diverge (grow left new children selected alongside
the parent; shrink left only the parent).
Relates to #8088.
Generated with the assistance of an AI coding tool.
Sweep [0]-indexing in recreate_aggregate, recreate_connections,
and recreate_port_connections so batched N-child duplicates
recreate relationships on every new child, not just the first.
Single-source callers unaffected (loop collapses to one iteration
on 1-element lists).
Relates to #8088.
Generated with the assistance of an AI coding tool.
Replace N sequential duplicate_ifc_objects([parent]) calls in
_regenerate_array_body with one duplicate_ifc_object_n_times call
per layer, batching the fixed per-call overhead (snapshot gather,
UI refresh, decorator reload).
Guard batch_host_recut drain against dead StructRNA refs and prune
orphan array-child GUIDs at regen so outliner-delete of a
Bonsai-managed child cannot crash subsequent regenerate_array.
Recalculate walls after recreate_connections so Shift+D of
connected walls produces correct junction geometry without a
manual regen step.
Relates to #8088.
Generated with the assistance of an AI coding tool.
IfcSurfaceFeature (e.g. road markings) adheres to a host element through
IfcRelAdheresToElement, a [1:1] cardinality hierarchical relationship in the
same family as aggregation, containment and nesting since IFC4.3. The spatial
traversal never followed it, so surface features had no resolvable parent or
container: on import they landed in the Unsorted collection instead of the
host's spatial collection, and were dropped entirely in DECOMPOSITION filter
mode.
Add get_adhered_element (feature to host) to the get_parent resolver chain and
walk HasSurfaceFeatures in get_decomposition, plus a get_surface_features helper
mirroring get_parts/get_contained. With get_parent resolving adherence,
get_container now returns the host's spatial container, so tool.Collector places
surface features under the host. Also follow HasSurfaceFeatures in the Bonsai
DECOMPOSITION filter path so they load in that mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
generate_linework() loops over the main file plus any linked models
(added in 5db955d4), reassigning drawing_elements each iteration. The
SHAPELY fill pass builds elements_with_faces/raycast_objs from
drawing_elements, but did so once *after* that loop finished, so it
only ever saw whichever file was processed last -- silently dropping
surface fills for every other file, including the main model whenever
any link was loaded.
Accumulate elements_with_faces/raycast_objs across every file inside
the loop instead of capturing drawing_elements once after it ends.
Generated with the assistance of an AI coding tool.
The underlying bug (has_material_styles bypassing the tool layer) was
already fixed by e76455913, which added the required mock expectation
here, but left the test under its quarantine placeholder name
test_AAAAAAAAAAAA. Restore the real name now that it genuinely passes.
is_drawing_active() required an open VIEW_3D area purely as a poll()
gate for bim.create_drawing, even though SVG generation is
ifcopenshell.geom-based with no viewport dependency; skip that check
when bpy.app.background is true, since a viewport is neither
obtainable nor meaningful there. Interactive behaviour is unchanged.
Generated with the assistance of an AI coding tool.
Linked IFC files were included in SVG output but without their
world transform, causing geometry to appear at wrong coordinates.
Falls back to no transform if the link cache is unavailable.
Generated with the assistance of an AI coding tool.
Link empty handles were missing from visible_objects, so linked
models were always hidden when activating a drawing.
Generated with the assistance of an AI coding tool.
bpy.ops.bim.reload_link(link_index=...) from a script skips invoke(),
so self.query stayed at its empty default and execute() overwrote the
link's stored query, reloading everything. Only update link.query when
the property was explicitly set (dialog or script argument), and reload
using the stored query.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Store the selector query used at Link IFC time on the Link
PropertyGroup, restore it from the sidecar cache JSON on host
IFC reopen, and forward it through LoadLink and ReloadLink so
subsequent reloads replay the original filter instead of loading
every element. ReloadLink now opens a small dialog pre-populated
with the current query, allowing the user to edit it in place
without unlink-and-relink.
Also swap TestCalculateLinkMatrix off NamedTemporaryFile(delete=True)
which held an exclusive Windows handle and blocked the
code-under-test from reopening the sidecar path.
Closes#8219
Generated with the assistance of an AI coding tool.
Fold two related cleanups from post-PR review into one commit:
Shared filling predicate — the gizmo poll and AddOpening._add_openings
both need to decide whether an IFC entity is a Bonsai-supported filling
(IfcDoor / IfcWindow, the classes the opening generator can derive
geometry from). Centralise the check in bim.module.model.opening as
is_filling_supported so a schema-broadening tomorrow only edits one
predicate. The gizmo's own predicate is renamed
is_supported_filling_or_opening to reflect its wider domain (also
accepts None for raw meshes and IfcOpeningElement for reassignment).
Aggregate-host guard — regenerate_filling_opening_body returns the
voided host Blender object so callers can recut it. Aggregates have
no mesh data; returning them made callers hit switch_representation
against a None data-block. Guard on voided_obj.data is None and
return None so callers can skip cleanly.
Adds a direct position_gizmos test asserting host-at-index-1 (filling
active) still anchors on the slab — pins the class-based dispatch's
selection-order independence.
Generated with the assistance of an AI coding tool.
Both files interleaved bpy.types imports with ifcopenshell.util
imports, which ruff's I001 rejects for standard-library / third-party
ordering. Running ruff check --fix on the two files reorders them into
the isort-canonical shape with no behaviour change.
Generated with the assistance of an AI coding tool.
EditOpenings.edit_openings unconditionally walked sibling wall sets
twice on every processed opening — once by mapped source id via
get_similar_openings_building_objs, once by filling type via
get_all_building_objects_of_similar_openings — and unioned both into
the building_objs recut set. reload_body_representation then hit every
one of those walls with a switch_representation call, even for the
show/hide toggle path where nothing about the opening changed.
Move both sibling-wall unions inside the is_edited / is_moved branch.
Pure show/hide (no shape edit, no move) now touches only the wall(s)
directly hosting the toggled openings. The edit and move paths still
refresh siblings the same as before, since a mapped-source rewrite
propagates the new shape to every sharing wall and each one needs a
recut.
Generated with the assistance of an AI coding tool.