mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
Change from XY to EN and use strings not floats in Blender due to precision
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
col.operator("saikei.name_segments", icon="FONT_DATA")
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user