diff --git a/src/bonsai/bonsai/bim/__init__.py b/src/bonsai/bonsai/bim/__init__.py index e3f26e1a5e..81c8f70592 100644 --- a/src/bonsai/bonsai/bim/__init__.py +++ b/src/bonsai/bonsai/bim/__init__.py @@ -31,11 +31,18 @@ from . import handler, operator, prop, ui def _parametric_gizmo_preference_classes() -> list[type]: - """Deferred lookup. Importing ``bonsai.tool`` at module top would cold-start - ``tool.blender`` → ``bim.ifc`` before the ``from . import handler, …`` above - has primed the ``bim.ifc`` ↔ ``bim.handler`` partial-import dance, crashing - addon registration. Resolved at classes-tuple build time below — by then the - relative imports have settled.""" + """Lazy resolution. ``bonsai.tool/__init__.py`` transitively loads + ``tool/ifc.py`` (and several siblings) which import ``from bonsai.bim.ifc + import IfcStore`` at module top — that ``tool → bim`` cycle means + ``bonsai.tool`` cannot be imported here before ``from . import handler, …`` + above has primed the bim partial-import dance through ``handler``'s own + ``import bonsai.tool``. By the time this function runs (during the + classes-tuple build below), ``handler`` has fully loaded and ``bonsai.tool`` + is safely importable. + + The architectural root cause is ``IfcStore`` living in ``bim/ifc.py``; + moving it to ``tool/ifc.py`` would let ``tool/`` stop reaching into ``bim/`` + and eliminate the need for this indirection. Tracked separately.""" import bonsai.tool as tool return tool.Parametric.iter_gizmo_preference_classes(ui) diff --git a/src/bonsai/bonsai/core/model.py b/src/bonsai/bonsai/core/model.py index 81fd25d109..7f4237fb45 100644 --- a/src/bonsai/bonsai/core/model.py +++ b/src/bonsai/bonsai/core/model.py @@ -260,54 +260,6 @@ def vertical_height_from_extrusion_depth(extrusion_depth: float, x_angle: float) return extrusion_depth * abs(math.cos(x_angle)) -def are_axes_collinear( - seg_a: tuple[tuple[float, float, float], tuple[float, float, float]], - seg_b: tuple[tuple[float, float, float], tuple[float, float, float]], - parallel_threshold: float = 0.9994, - line_tolerance: float = 0.05, -) -> bool: - """True if both segments lie on the same infinite line in plan (X,Y). - - Two conditions: their directions must be (anti-)parallel within - ``parallel_threshold`` (cos ~2°), AND any endpoint of B must lie on A's - infinite line within ``line_tolerance`` (~5cm). Z is ignored — two parallel - walls at different elevations are still considered collinear.""" - p1, p2 = seg_a - q1, q2 = seg_b - d1x, d1y = p2[0] - p1[0], p2[1] - p1[1] - d2x, d2y = q2[0] - q1[0], q2[1] - q1[1] - d1_len = (d1x * d1x + d1y * d1y) ** 0.5 - d2_len = (d2x * d2x + d2y * d2y) ** 0.5 - if d1_len < 1e-9 or d2_len < 1e-9: - return False - dot = (d1x * d2x + d1y * d2y) / (d1_len * d2_len) - if abs(dot) < parallel_threshold: - return False - # Project q1 onto the infinite line through seg_a; perpendicular distance - # from q1 to its projection tells us how far off the line B sits. - ux, uy = d1x / d1_len, d1y / d1_len - rx, ry = q1[0] - p1[0], q1[1] - p1[1] - t = rx * ux + ry * uy - proj_x = p1[0] + t * ux - proj_y = p1[1] + t * uy - perp_dist = ((q1[0] - proj_x) ** 2 + (q1[1] - proj_y) ** 2) ** 0.5 - return perp_dist < line_tolerance - - -def closest_endpoint_midpoint( - seg_a: tuple[tuple[float, float, float], tuple[float, float, float]], - seg_b: tuple[tuple[float, float, float], tuple[float, float, float]], -) -> tuple[float, float, float]: - """Midpoint of the closest pair of endpoints between two segments. - - For walls that meet end-to-end this is the shared corner; for walls with a - small gap it is the midpoint of the gap. Either way it is the user-meaningful - "boundary" where a merge would graft the two segments together.""" - pairs = ((a, b) for a in seg_a for b in seg_b) - pa, pb = min(pairs, key=lambda pair: sum((pair[0][i] - pair[1][i]) ** 2 for i in range(3))) - return ((pa[0] + pb[0]) / 2, (pa[1] + pb[1]) / 2, (pa[2] + pb[2]) / 2) - - def are_axes_collinear( seg_a: tuple[tuple[float, float, float], tuple[float, float, float]], seg_b: tuple[tuple[float, float, float], tuple[float, float, float]], diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 720e8f1839..53f91c06da 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -62,12 +62,6 @@ if TYPE_CHECKING: import bpy.stub_internal.rna_enums as rna_enums from sun_position.properties import SunPosProperties - # Type-only — imported lazily to avoid a circular load when ``bim/__init__.py`` - # imports ``bonsai.tool`` before ``bim.ifc`` has reached its line-43 definition - # of ``IFC_CONNECTED_TYPE`` (the chain re-enters ``bim.ifc`` through - # ``bim.handler`` and trips on a still-undefined ``IfcStore``). The file has - # ``from __future__ import annotations``, so the type hint at line 1884 is a - # deferred string and needs no runtime binding. from bonsai.bim.ifc import IFC_CONNECTED_TYPE from bonsai.bim.module.attribute.prop import BIMAttributeProperties from bonsai.bim.module.constraint.prop import (