Commit Graph

3540 Commits

Author SHA1 Message Date
Gorgious56 fdf4b82371 Add tool.Wall service
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.
2026-05-26 23:40:19 +02:00
Gorgious56 2f40441f1c Add tool.* interface stubs to core.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.
2026-05-26 23:31:28 +02:00
Gorgious56 230cbe1fd8 Add core/model.py constants + core/product.py helpers
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.
2026-05-26 23:28:19 +02:00
Ryan Schultz 719309571e Improve active tool panel hotkey button display
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.
2026-05-25 09:35:12 -05:00
Gorgious56 3e0978062f Add lifecycle-mixin tests + predicate-total registry guard
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.
2026-05-21 11:40:38 +02:00
Gorgious56 b3f482e0fa Defer mathutils imports in stair gizmo tests
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.
2026-05-21 11:18:00 +02:00
Gorgious56 4943c77c5e Add BONSAI_TEST_ARGS env-var fallback to runpytest.py
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.
2026-05-21 11:17:31 +02:00
Gorgious56 6caf94f1d3 Sweep docstrings for rot-prone references
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.
2026-05-21 11:09:29 +02:00
Gorgious56 1e36cc318e Drop save-time parametric-edit confirm dialog
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.
2026-05-21 11:00:19 +02:00
Gorgious56 46381ec08b Prioritize smaller distance gizmos in selection
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.
2026-05-21 10:30:32 +02:00
Gorgious56 47af955dd1 Simplify pending edit popup text 2026-05-21 09:48:00 +02:00
Gorgious56 f582d0230c Fix set_icon_gizmo_position so billboard ignores object rotation
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.
2026-05-20 17:28:18 +02:00
Gorgious56 26eef20eb5 Add wall parametric editing and gizmos
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.
2026-05-20 16:58:39 +02:00
Gorgious56 2143262883 Fix dead duplicates and misleading import comments
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.
2026-05-20 16:25:49 +02:00
Gorgious56 233cc344fa Add tool.Parametric registry and lifecycle mixins
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.
2026-05-20 15:18:44 +02:00
Gorgious56 1b2507e143 Fix 8077 : Fix SHIFT + D with non-ifc object selection
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.
2026-05-19 12:29:18 +02:00
Geert Hesselink 508b99cb73 Fix lint failures and add missing pyparsing dependency (#8048)
* unblock voxel schema loading, add test for express

* Apply black formatting

* Fix lint failures and add missing pyparsing dependency

* align ty -> 0.0.34
2026-05-18 22:17:45 +02:00
Ryan Schultz e78ef865b8 Fix #8056 - Dimensions with CustomUnit" = "Inches - Fractional" should not show 0. 2026-05-15 07:28:29 -05:00
Ryan Schultz 856631092b Fix #7885: LAYER3 crash on IfcCompositeProfileDef
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.
2026-05-01 08:54:02 +10:00
Ryan Schultz 7a61cf20a4 Fix #7927: Fix SECTION annotation for MODEL_VIEW drawings
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.
2026-05-01 08:52:55 +10:00
Ryan Schultz c999a92aa7 Fix #8024 - Fix TypeError when CardinalPoint is None
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.
2026-05-01 08:51:00 +10:00
E Shattow 434b179ed9 docs: project_overview: project_info blender tip to change display units after project creation
Link to Blender Manual for tip to change display units
2026-05-01 08:47:42 +10:00
falken10vdl 247a445458 Fix IfcSurfaceStyleRendering colour reset on save 2026-04-25 16:15:43 +10:00
Bruno Postle e4f5c630db Add license for OpenGost font shipped with Bonsai
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())
  "
2026-04-21 23:44:14 +01:00
Massimo Fabbro 6b2d25a5e5 Add tests for cost tool 2026-04-20 17:16:08 +02:00
Massimo Fabbro 2d05398b1c fix infinite recursion error
previously there was an almost silent error because the update function was called every time. Now it should be fixed.
2026-04-20 17:16:08 +02:00
Andrej730 29fe41edd0 maintenance: rename main.yml to publish-websites.yml in docs 2026-04-15 16:08:21 +05:00
Andrej730 29b648d8dd Makefiles - refer to python in more generic way 2026-04-15 11:26:11 +05:00
Andrej730 3ffdb9e74d maintenance: add publish-bonsai-releases.py to Blender Python version update checklist 2026-04-15 10:52:43 +05:00
Andrej730 00915409ac maintenance: add documentation about multiple Blender Python versions 2026-04-15 10:50:44 +05:00
Andrej730 d21543a24a maintenance: add corrective release documentation 2026-04-15 10:46:12 +05:00
Andrej730 3205a4ebb1 Add workflow to publish bonsai releases to Blender Extensions 2026-04-14 20:01:21 +05:00
Andrej730 5db65f4041 maintenance - list all things we do on release 2026-04-13 19:39:14 +05:00
Andrej730 4896946e78 ty ignore some upstream bpy stubs issues 2026-04-10 19:11:41 +05:00
Andrej730 588f365366 Remove unused ty ignores - issue is resolved upsteam in stubs 2026-04-10 19:11:41 +05:00
Andrej730 98338e0831 Rename ci-black-formatting workflow to ci-lint 2026-04-10 18:06:43 +05:00
Andrej730 5a3160eb62 black . 2026-04-10 18:02:47 +05:00
Andrej730 8eb0060d4a Get rid of pyright ignore reportRedeclaration noise
Welp, it was helping to point out untyped props, but it is getting too noisy now.
2026-04-10 17:55:16 +05:00
Andrej730 1689960257 Maintenence - document ci-bonsai.yml update 2026-04-10 16:20:50 +05:00
Bruno Postle b4558f7f75 Fix ruff import ordering complaints 2026-04-09 01:04:14 +01:00
Bruno Postle 27d9cae8ff Bonsai, bump ifcmerge.exe to working version with deps
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.
2026-04-07 22:25:31 +01:00
Ryan Schultz ab7d9fdf4a Auto-assign aggregate on eyedropper pick
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.
2026-04-05 16:43:03 -05:00
Ryan Schultz 5436467fc5 Whoops, this was supposed to be a PR...
Revert "Fix #3742: Remove coplanar boundary lines between adjacent same-material elements in Bonsai SVG drawings"

This reverts commit 1c7e134d78.
2026-04-04 13:49:02 -05:00
Ryan Schultz 1c7e134d78 Fix #3742: Remove coplanar boundary lines between adjacent same-material elements in Bonsai SVG drawings
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>
2026-04-04 13:42:49 -05:00
Bruno Perdigão 97ee4eaef0 See #7888 - Fix snap when object changes during modal operator.
Handle cases where the snapped target is modified while a modal operator
is active (e.g., adding a door or window that alters the wall geometry).
2026-04-04 14:30:59 -03:00
Bruno Postle 12144f76d3 Merge branch 'ifcgit-features' into v0.8.0 2026-04-04 00:10:33 +01:00
Bruno Postle ca6e950496 ifcgit: conflict report panel and dry-run merge preview
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.
2026-04-03 13:28:14 +01:00
Ryan Schultz eaf7950677 Fix TypeError in ray_cast_by_proximity_2d degenerate edge
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.
2026-04-02 23:24:35 -03:00
Bruno Postle 3a6881ca17 ifcgit: add rename branch button next to working branch label
See #7577

Generated with the assistance of an AI coding tool.
2026-04-03 00:20:22 +01:00
Bruno Postle c5210a4f82 ifcgit: sync load_project post-import steps with project operator
See #7578
2026-04-03 00:03:34 +01:00