Addresses Rick Brice's PR review feedback on #7785.
Root cause: the AddElement dialog was manually stitching together
IfcAlignment layout containers and calling create_representation(),
bypassing the validated construction sequence in align_api.create().
This left IfcGradientCurve.BaseCurve potentially None and skipped
zero-length segment and stationing referent setup.
Changes:
- root/operator.py: alignment templates now call align_api.create()
directly (HORIZONTAL, GRADIENT, CANT) or _create_polyline_representation
(POLYLINE_2D/3D), then manually link the result to the Blender object via
tool.Ifc.link + tool.Collector.assign. The old generic core.assign_class
path is retained for all non-alignment templates unchanged.
- tool/alignment.py: remove create_representation_structure() — superseded
by the operator changes above.
- create_representation.py: revert the if layout_nest: guard; with
align_api.create() as the entry point the zero-length segment always
exists before create_representation is called.
- add_zero_length_segment.py: revert the BaseCurve None guard; the root
cause (gradient curve created without a base curve) no longer occurs.
- alignment/operator.py: guard _create_geometric_representation call so it
only runs when no curve representation exists yet; auto-invoke
create_alignment_by_pi after PI picker finishes if >=2 PIs are defined.
- util/file.py: fix StopIteration on short IFC template files
(next(ifc_file) → next(ifc_file, None) with break).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs prevented IfcAlignmentVertical segments from being added when
a geometric representation exists.
Bug 1 — _add_segment_to_curve.py:
Removed an unconditional `if not curve.is_a("IfcCompositeCurve")` guard
that was left over from when the function only supported horizontal
segments. The preceding if/elif/elif chain already validates the correct
curve type for each segment type; the redundant check always raised
TypeError for vertical (IfcGradientCurve) and cant
(IfcSegmentedReferenceCurve) segments.
Bug 2 — add_zero_length_segment.py:
Added a None guard before the recursive `add_zero_length_segment(file,
layout.BaseCurve)` call for IfcGradientCurve. When an IfcGradientCurve
is created without a BaseCurve (e.g. before a horizontal representation
exists), the recursive call previously crashed with AttributeError.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire IfcAlignment creation into Bonsai's Add IFC Element flow with
representation template options (Horizontal, Gradient, Cant, 3D/2D
Polyline). Adds create_representation_structure() to tool.Alignment
which creates layout containers, geometric representation, and
polyline placeholders. Guards create_representation against empty
layout nests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ignore CLAUDE.md, CLAUDE.local.md, and .mcp.json so personal
Claude Code configuration (managed via private dotfiles repo)
doesn't pollute the shared repository.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
get_mapped_segments now looks for representations attached to alignment segments before using the more complex method of computing the index of segments in the composite curve.
updates segment_vertices to use get_mapped_segments
Wrap SAIKEI_OT_enter_pi_edit_mode with Bonsai's IfcStore transaction
system so IFC segment changes from applying PI edits are tracked and
undoable via Ctrl+Z.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses Rick Brice's new ifcopenshell.api.alignment.segment_vertices()
to extract PI positions from alignment segments via the C++ geometry
engine, replacing ~300 lines of hand-coded trig that only handled
LINE and CIRCULARARC. Now supports all segment types (CLOTHOID,
Helmert curves, etc.). Includes backward-compatible fallback for
IFC files without Axis/Segment representations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Renamed PI and CC to TI and NI
Fixes handling of units (now works correctly with US and SI units)
Changed tests to use US feet units
Fixed documentation
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.
Replace custom SAIKEI_OT_pick_pi_from_viewport modal with a
PolylineOperator subclass, reusing Bonsai's proven polyline
infrastructure (same base class as wall/slab/profile drawing).
Gains: snapping, numeric D/A/X/Y input, axis locking, angle
locking, measurement display, undo-last-point, status bar hints.
Remove PIPickerDecorator (replaced by PolylineDecorator).
Fix EN string formatting in UI list display rows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.