Bonsai: fix clash pin drifting away when a false origin is active

ifcclash computes p1/p2 directly from the IFC file's true (SI metre)
coordinates. Every other imported object gets Bonsai's false origin /
georeferencing offset applied to its matrix_world on import, but the
clash pin in SelectClash assigned the raw ifcclash points straight to
the viewport, with no such conversion. Whenever a false origin was
active, the pin landed off by the entire offset (translation and
project-north rotation), which can be very large for real georeferenced
projects, matching the "shows up a very long ways away" symptom in #7178.

Live reproduction (synthetic IFC models, real ifcclash + real Bonsai
import pipeline in headless Blender) showed the raw unit-scale hypothesis
from our earlier comment on the issue does not hold: ifcclash already
returns proper SI metres regardless of project unit system (feet vs mm
projects gave identical, correct clash points for identical real-world
geometry). The false origin/georeferencing offset was the actual cause:
forcing a 50m offset moved the pin ~70m from the true intersection, and
adding a 30 degree project-north rotation on top moved it ~88m away.

Fix: convert ifcclash points through the same
false-origin/project-north transform already used for every other
displayed object (tool.Loader.apply_blender_offset_to_matrix_world),
via a new tool.Clash.convert_clash_point_to_blender() helper used by
SelectClash. Verified the fix brings the pin back to within the
existing measurement/approximation noise floor (~0.1-0.3m) of the true
intersection in both the translation-only and translation+rotation
cases, and leaves the false-origin-disabled case byte-for-byte
unchanged.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Petru Conduraru
2026-07-20 17:44:22 +03:00
parent 55a2430d71
commit e6489c2d9a
2 changed files with 25 additions and 3 deletions
@@ -428,10 +428,10 @@ class SelectClash(bpy.types.Operator):
tool.Spatial.select_products(products, unhide=True)
ClashDecorator.install(bpy.context)
target = Vector(clash["p1"])
target = tool.Clash.convert_clash_point_to_blender(clash["p1"])
tool.Clash.look_at(target, target + Vector((5, 5, 5)))
self.props.p1 = clash["p1"]
self.props.p2 = clash["p2"]
self.props.p1 = target
self.props.p2 = tool.Clash.convert_clash_point_to_blender(clash["p2"])
self.props.active_clash_text = clash["type"].title() + " " + str(round(clash["distance"] * 1000)) + "mm"
return {"FINISHED"}
+22
View File
@@ -22,6 +22,9 @@ import json
from typing import TYPE_CHECKING, Literal, Union
import bpy
import ifcopenshell.util.geolocation
import ifcopenshell.util.unit
import numpy as np
from ifcclash import ifcclash
from ifcclash.ifcclash import ClashSource
from mathutils import Vector
@@ -129,6 +132,25 @@ class Clash(bonsai.core.tool.Clash):
with open(fn) as f:
ClashStore.clash_sets = json.load(f)
@classmethod
def convert_clash_point_to_blender(cls, point: Union[list[float], tuple[float, float, float]]) -> Vector:
"""Convert a raw ifcclash point into Bonsai's displayed viewport coordinates."""
props = tool.Georeference.get_georeference_props()
if not props.has_blender_offset:
return Vector(point)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
matrix = np.eye(4)
matrix[:3, 3] = point
matrix = ifcopenshell.util.geolocation.global2local(
matrix,
float(props.blender_offset_x) * unit_scale,
float(props.blender_offset_y) * unit_scale,
float(props.blender_offset_z) * unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
return Vector(matrix[:3, 3])
@classmethod
def look_at(cls, target: Vector, location: Vector) -> None:
camera_location = location