diff --git a/src/bonsai/bonsai/bim/module/alignment/operator.py b/src/bonsai/bonsai/bim/module/alignment/operator.py index 7a64c29cfd..372561b5a4 100644 --- a/src/bonsai/bonsai/bim/module/alignment/operator.py +++ b/src/bonsai/bonsai/bim/module/alignment/operator.py @@ -199,8 +199,8 @@ def sync_pis_from_ifc(props): props.pis.clear() for pi_data in extracted_pis: pi = props.pis.add() - pi.x = pi_data["x"] - pi.y = pi_data["y"] + pi.e = pi_data["e"] + pi.n = pi_data["n"] pi.pi_type = pi_data["pi_type"] pi.radius = pi_data.get("radius", 0.0) @@ -449,13 +449,13 @@ def compute_deflection_angle(prev_pi, curr_pi, next_pi): Deflection angle in radians (signed: positive=left, negative=right) """ # Incoming tangent direction - dx1 = curr_pi.x - prev_pi.x - dy1 = curr_pi.y - prev_pi.y + dx1 = float(curr_pi.e) - float(prev_pi.e) + dy1 = float(curr_pi.n) - float(prev_pi.n) angle1 = math.atan2(dy1, dx1) # Outgoing tangent direction - dx2 = next_pi.x - curr_pi.x - dy2 = next_pi.y - curr_pi.y + dx2 = float(next_pi.e) - float(curr_pi.e) + dy2 = float(next_pi.n) - float(curr_pi.n) angle2 = math.atan2(dy2, dx2) # Deflection angle @@ -546,8 +546,8 @@ def compute_segment_length(props, start_pi_index, account_for_curves=True): end_pi = pis[start_pi_index + 1] # Full length between PIs - dx = end_pi.x - start_pi.x - dy = end_pi.y - start_pi.y + dx = float(end_pi.e) - float(start_pi.e) + dy = float(end_pi.n) - float(start_pi.n) full_length = math.sqrt(dx * dx + dy * dy) if not account_for_curves: @@ -588,7 +588,7 @@ def recalculate_pi_geometry(props): return # Extract PI coordinates for calculation - pi_coords = [(pi.x, pi.y) for pi in pis] + pi_coords = [(float(pi.e), float(pi.n)) for pi in pis] # Use tool layer for calculation (math belongs in tool, not core) result = tool.Alignment.calculate_pi_geometry(pi_coords, props.start_station) @@ -636,8 +636,8 @@ def rebuild_display_rows(props): curve_row.segment_number = segment_num curve_row.pi_index = i curve_row.display_type = "Curve" - curve_row.x = pi.x # Show PI coordinates on curve row - curve_row.y = pi.y + curve_row.e = pi.e # Show PI coordinates on curve row + curve_row.n = pi.n curve_row.radius = pi.radius curve_row.arc_length = compute_arc_length_for_pi(props, i) else: @@ -651,8 +651,8 @@ def rebuild_display_rows(props): else: point_row.display_type = "Mid" - point_row.x = pi.x - point_row.y = pi.y + point_row.e = pi.e + point_row.n = pi.n # Add tangent segment row after this point/curve (except after last PI) if i < len(pis) - 1: @@ -699,23 +699,23 @@ class SAIKEI_OT_add_pi(Operator): # Set default position based on existing PIs if len(props.pis) == 1: # First PI - start at origin - pi.x = 0.0 - pi.y = 0.0 + pi.e = str(0.0) + pi.n = str(0.0) pi.pi_type = "ENDPOINT" elif len(props.pis) == 2: # Second PI - offset from first prev = props.pis[0] - pi.x = prev.x + 100.0 - pi.y = prev.y + pi.e = str(float(prev.e) + 100.0) + pi.n = prev.n pi.pi_type = "ENDPOINT" else: # Additional PIs - extrapolate from last two prev = props.pis[-2] prev_prev = props.pis[-3] if len(props.pis) > 2 else prev - dx = prev.x - prev_prev.x if len(props.pis) > 2 else 100.0 - dy = prev.y - prev_prev.y if len(props.pis) > 2 else 0.0 - pi.x = prev.x + dx - pi.y = prev.y + dy + de = float(prev.e) - prev_prev.e if len(props.pis) > 2 else 100.0 + dn = float(prev.n) - prev_prev.n if len(props.pis) > 2 else 0.0 + pi.e = str(float(prev.e) + de) + pi.n = str(float(prev.n) + dn) pi.pi_type = "TANGENT" # Previous endpoint becomes tangent or curve @@ -918,7 +918,8 @@ class SAIKEI_OT_pick_pi_from_viewport(Operator): points = [] for pi in props.pis: # Convert from IFC to Blender coordinates - blender_coord = tool.Alignment.ifc_to_blender_coordinates(pi.x, pi.y, 0.0) + blender_coord = tool.Georeference.enh2xyz((float(pi.e), float(pi.n), 0.0)) + print(repr(blender_coord)) points.append(Vector(blender_coord)) return points @@ -941,11 +942,11 @@ class SAIKEI_OT_pick_pi_from_viewport(Operator): # Convert Blender coordinates to IFC coordinates # PIs are stored in IFC coordinate space (global/map coordinates) - ifc_coord = tool.Alignment.blender_to_ifc_coordinates(coord[0], coord[1], 0.0) + ifc_coord = tool.Georeference.xyz2enh((coord[0], coord[1], 0.0)) pi = props.pis.add() - pi.x = ifc_coord[0] - pi.y = ifc_coord[1] + pi.e = str(ifc_coord[0]) + pi.n = str(ifc_coord[1]) # Determine PI type based on position in list if len(props.pis) == 1: @@ -1005,7 +1006,7 @@ class SAIKEI_OT_recalculate_pis(Operator): return {"CANCELLED"} # Collect updated PI data - hpoints = [(pi.x, pi.y) for pi in props.pis] + hpoints = [(float(pi.e), float(pi.n)) for pi in props.pis] radii = [pi.radius for pi in props.pis[1:-1]] # Remove Blender visualization for segments (not the whole hierarchy) @@ -1154,7 +1155,7 @@ class SAIKEI_OT_create_alignment_by_pi(Operator): props = context.scene.SaikeiAlignmentProperties # Collect PI data - hpoints = [(pi.x, pi.y) for pi in props.pis] + hpoints = [(float(pi.e), float(pi.n)) for pi in props.pis] radii = [pi.radius for pi in props.pis[1:-1]] # Check if there's an active alignment we should add to instead of creating new diff --git a/src/bonsai/bonsai/bim/module/alignment/prop.py b/src/bonsai/bonsai/bim/module/alignment/prop.py index df30da6434..2363aa2f28 100644 --- a/src/bonsai/bonsai/bim/module/alignment/prop.py +++ b/src/bonsai/bonsai/bim/module/alignment/prop.py @@ -62,21 +62,8 @@ class AlignmentPI(PropertyGroup): """ # Coordinates - x: FloatProperty( - name="X", - description="X coordinate (Easting)", - default=0.0, - precision=3, - unit="LENGTH", - ) - - y: FloatProperty( - name="Y", - description="Y coordinate (Northing)", - default=0.0, - precision=3, - unit="LENGTH", - ) + e: StringProperty(name="E", description="Easting", default="0.0") + n: StringProperty(name="N", description="Northing", default="0.0") # PI Type pi_type: EnumProperty( @@ -177,8 +164,8 @@ class AlignmentDisplayRow(PropertyGroup): display_type: StringProperty(name="Type", default="") # Point coordinates (only for POINT rows) - x: FloatProperty(name="X", default=0.0, precision=3, unit="LENGTH") - y: FloatProperty(name="Y", default=0.0, precision=3, unit="LENGTH") + e: StringProperty(name="E", default="0.0") + n: StringProperty(name="N", default="0.0") # Segment properties (only for SEGMENT rows) length: FloatProperty(name="Length", default=0.0, precision=2, unit="LENGTH") diff --git a/src/bonsai/bonsai/bim/module/alignment/ui.py b/src/bonsai/bonsai/bim/module/alignment/ui.py index a8d3dade30..3f40d4219f 100644 --- a/src/bonsai/bonsai/bim/module/alignment/ui.py +++ b/src/bonsai/bonsai/bim/module/alignment/ui.py @@ -64,11 +64,11 @@ class SAIKEI_UL_alignment_pis(UIList): pi = data.pis[item.pi_index] if item.pi_index < len(data.pis) else None if pi: sub = row.row(align=True) - sub.prop(pi, "x", text="") - sub.prop(pi, "y", text="") + sub.prop(pi, "e", text="") + sub.prop(pi, "n", text="") else: - row.label(text=f"{item.x:.2f}") - row.label(text=f"{item.y:.2f}") + row.label(text=f"{item.e:.2f}") + row.label(text=f"{item.n:.2f}") # Length column - empty for point rows row.label(text="") @@ -86,8 +86,8 @@ class SAIKEI_UL_alignment_pis(UIList): row.label(text="Curve", icon="SPHERECURVE") # Show PI coordinates on curve row - row.label(text=f"{item.x:.2f}") - row.label(text=f"{item.y:.2f}") + row.label(text=f"{item.e:.2f}") + row.label(text=f"{item.n:.2f}") # Arc length row.label(text=f"{item.arc_length:.2f}") @@ -254,8 +254,8 @@ class SAIKEI_PT_pi_editor(Panel): header = layout.row(align=True) header.label(text="No.") header.label(text="Type") - header.label(text="X") - header.label(text="Y") + header.label(text="E") + header.label(text="N") header.label(text="Length") header.label(text="Radius") @@ -320,4 +320,4 @@ class SAIKEI_PT_alignment_stationing(Panel): # Stationing operators col = layout.column(align=True) col.operator("saikei.add_stationing_referent", icon="EMPTY_AXIS") - col.operator("saikei.name_segments", icon="FONT_DATA") \ No newline at end of file + col.operator("saikei.name_segments", icon="FONT_DATA") diff --git a/src/bonsai/bonsai/tool/alignment.py b/src/bonsai/bonsai/tool/alignment.py index 6799dcd156..6e3e72b4e9 100644 --- a/src/bonsai/bonsai/tool/alignment.py +++ b/src/bonsai/bonsai/tool/alignment.py @@ -571,7 +571,8 @@ class Alignment: obj.empty_display_size = 0.5 else: # Transform vertices from IFC to Blender coordinates - blender_vertices = [cls.ifc_to_blender_coordinates(v[0], v[1], v[2]) for v in vertices] + # TODO: I think this is wrong, this is probably from Local to Blender, not Global to Blender + blender_vertices = [tool.Georeference.enh2xyz((v[0], v[1], v[2])) for v in vertices] # Create Blender curve from vertices curve_data = bpy.data.curves.new(name, type="CURVE") @@ -923,104 +924,6 @@ class Alignment: return alignment - # ========================================================================= - # Coordinate Transformation Methods - # ========================================================================= - - @classmethod - def blender_to_ifc_coordinates(cls, x: float, y: float, z: float = 0.0) -> Tuple[float, float, float]: - """Convert Blender local coordinates to IFC global/map coordinates. - - When a Blender offset is configured (for handling large geospatial coordinates), - this transforms from Blender's local coordinate system (near origin) to - the IFC global coordinate system (large geospatial values). - - Args: - x: X coordinate in Blender space - y: Y coordinate in Blender space - z: Z coordinate in Blender space (default 0.0) - - Returns: - Tuple of (x, y, z) in IFC global coordinates - """ - import ifcopenshell.util.geolocation - import ifcopenshell.util.unit - - gprops = tool.Georeference.get_georeference_props() - ifc_file = tool.Ifc.get() - - if not gprops.has_blender_offset or ifc_file is None: - # No transformation needed - return (x, y, z) - - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) - offset_x = float(gprops.blender_offset_x) * unit_scale - offset_y = float(gprops.blender_offset_y) * unit_scale - offset_z = float(gprops.blender_offset_z) * unit_scale - x_axis_abscissa = float(gprops.blender_x_axis_abscissa) - x_axis_ordinate = float(gprops.blender_x_axis_ordinate) - - # Create a 4x4 identity matrix with translation set to position - import numpy as np - matrix = np.eye(4) - matrix[0, 3] = x - matrix[1, 3] = y - matrix[2, 3] = z - - # Apply local2global transformation (inverse of global2local) - global_matrix = ifcopenshell.util.geolocation.local2global( - matrix, offset_x, offset_y, offset_z, x_axis_abscissa, x_axis_ordinate - ) - - return (global_matrix[0, 3], global_matrix[1, 3], global_matrix[2, 3]) - - @classmethod - def ifc_to_blender_coordinates(cls, x: float, y: float, z: float = 0.0) -> Tuple[float, float, float]: - """Convert IFC global/map coordinates to Blender local coordinates. - - When a Blender offset is configured (for handling large geospatial coordinates), - this transforms from the IFC global coordinate system (large geospatial values) - to Blender's local coordinate system (near origin). - - Args: - x: X coordinate in IFC global space - y: Y coordinate in IFC global space - z: Z coordinate in IFC global space (default 0.0) - - Returns: - Tuple of (x, y, z) in Blender local coordinates - """ - import ifcopenshell.util.geolocation - import ifcopenshell.util.unit - - gprops = tool.Georeference.get_georeference_props() - ifc_file = tool.Ifc.get() - - if not gprops.has_blender_offset or ifc_file is None: - # No transformation needed - return (x, y, z) - - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) - offset_x = float(gprops.blender_offset_x) * unit_scale - offset_y = float(gprops.blender_offset_y) * unit_scale - offset_z = float(gprops.blender_offset_z) * unit_scale - x_axis_abscissa = float(gprops.blender_x_axis_abscissa) - x_axis_ordinate = float(gprops.blender_x_axis_ordinate) - - # Create a 4x4 identity matrix with translation set to position - import numpy as np - matrix = np.eye(4) - matrix[0, 3] = x - matrix[1, 3] = y - matrix[2, 3] = z - - # Apply global2local transformation - local_matrix = ifcopenshell.util.geolocation.global2local( - matrix, offset_x, offset_y, offset_z, x_axis_abscissa, x_axis_ordinate - ) - - return (local_matrix[0, 3], local_matrix[1, 3], local_matrix[2, 3]) - # ========================================================================= # PI Edit Mode Methods # ========================================================================= @@ -1248,7 +1151,7 @@ class Alignment: for i, pi in enumerate(pis): # Convert IFC coordinates to Blender coordinates - blender_pos = cls.ifc_to_blender_coordinates(pi["x"], pi["y"], 0.0) + blender_pos = tool.Georeference.enh2xyz((float(pi["e"]), float(pi["n"]), 0.0)) # Create EMPTY object name = f"PI.{i + 1:03d}" @@ -1344,10 +1247,7 @@ class Alignment: for i, empty in enumerate(empties): # Convert Blender position to IFC coordinates - blender_pos = empty.location - ifc_pos = cls.blender_to_ifc_coordinates( - blender_pos.x, blender_pos.y, blender_pos.z - ) + ifc_pos = tool.Georeference.xyz2enh(tuple(empty.matrix_world.translation)) hpoints.append((ifc_pos[0], ifc_pos[1])) # Collect radii for interior PIs only (not first or last)