mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Implement WCS consideration in XYZ <-> ENH conversions.
This commit is contained in:
@@ -81,28 +81,32 @@ def edit_georeferencing(
|
||||
if not (project := file.by_type("IfcProject")):
|
||||
return
|
||||
project = project[0]
|
||||
if crs := ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS"):
|
||||
crs = file.by_id(crs["id"])
|
||||
for k, v in projected_crs.items():
|
||||
if k == "Description":
|
||||
v = file.createIfcText(v)
|
||||
elif k == "Name":
|
||||
v = file.createIfcLabel(v)
|
||||
else:
|
||||
v = file.createIfcIdentifier(v)
|
||||
ifcopenshell.api.pset.edit_pset(file, crs, properties=projected_crs)
|
||||
if conversion := ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion"):
|
||||
conversion = file.by_id(conversion["id"])
|
||||
for k, v in coordinate_operation.items():
|
||||
if k in ("XAxisAbscissa", "XAxisOrdinate", "Scale"):
|
||||
v = file.createIfcReal(v)
|
||||
else:
|
||||
v = file.createIfcLengthMeasure(v)
|
||||
ifcopenshell.api.pset.edit_pset(file, conversion, properties=coordinate_operation)
|
||||
if projected_crs:
|
||||
if crs := ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS"):
|
||||
crs = file.by_id(crs["id"])
|
||||
for k, v in projected_crs.items():
|
||||
if k == "Description":
|
||||
v = file.createIfcText(v)
|
||||
elif k == "Name":
|
||||
v = file.createIfcLabel(v)
|
||||
else:
|
||||
v = file.createIfcIdentifier(v)
|
||||
ifcopenshell.api.pset.edit_pset(file, crs, properties=projected_crs)
|
||||
if coordinate_operation:
|
||||
if conversion := ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion"):
|
||||
conversion = file.by_id(conversion["id"])
|
||||
for k, v in coordinate_operation.items():
|
||||
if k in ("XAxisAbscissa", "XAxisOrdinate", "Scale"):
|
||||
v = file.createIfcReal(v)
|
||||
else:
|
||||
v = file.createIfcLengthMeasure(v)
|
||||
ifcopenshell.api.pset.edit_pset(file, conversion, properties=coordinate_operation)
|
||||
return
|
||||
conversion = file.by_type("IfcCoordinateOperation")[0]
|
||||
crs = file.by_type("IfcProjectedCRS")[0]
|
||||
for name, value in coordinate_operation.items():
|
||||
setattr(conversion, name, value)
|
||||
for name, value in projected_crs.items():
|
||||
setattr(crs, name, value)
|
||||
if projected_crs:
|
||||
crs = file.by_type("IfcProjectedCRS")[0]
|
||||
for name, value in projected_crs.items():
|
||||
setattr(crs, name, value)
|
||||
if coordinate_operation:
|
||||
conversion = file.by_type("IfcCoordinateOperation")[0]
|
||||
for name, value in coordinate_operation.items():
|
||||
setattr(conversion, name, value)
|
||||
|
||||
@@ -50,26 +50,26 @@ def edit_true_north(file: ifcopenshell.file, true_north: Optional[Union[tuple[fl
|
||||
# This unsets true north
|
||||
ifcopenshell.api.run("georeference.edit_true_north", model, true_north=None)
|
||||
"""
|
||||
if not true_north:
|
||||
return
|
||||
|
||||
if true_north is None:
|
||||
pass
|
||||
elif isinstance(true_north, (float, int)):
|
||||
if isinstance(true_north, (float, int)):
|
||||
x, y = ifcopenshell.util.geolocation.angle2yaxis(true_north)
|
||||
else:
|
||||
elif true_north is not None:
|
||||
x, y = true_north
|
||||
|
||||
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.TrueNorth and true_north is None:
|
||||
old_true_north = context.TrueNorth
|
||||
context.TrueNorth = None
|
||||
if not file.get_total_inverses(old_true_north):
|
||||
ifcopenshell.util.element.remove_deep2(file, old_true_north)
|
||||
continue
|
||||
|
||||
if context.TrueNorth:
|
||||
if file.get_total_inverses(context.TrueNorth) != 1:
|
||||
context.TrueNorth = file.create_entity("IfcDirection")
|
||||
else:
|
||||
context.TrueNorth = file.create_entity("IfcDirection")
|
||||
direction = context.TrueNorth
|
||||
if true_north is None:
|
||||
context.TrueNorth = None
|
||||
elif context.CoordinateSpaceDimension == 2:
|
||||
if context.CoordinateSpaceDimension == 2:
|
||||
direction.DirectionRatios = (x, y)
|
||||
else:
|
||||
direction.DirectionRatios = (x, y, 0.0)
|
||||
|
||||
@@ -33,9 +33,19 @@ def edit_wcs(
|
||||
) -> None:
|
||||
"""Edits the WCS for all geometric contexts to a translation and rotation
|
||||
|
||||
It's recommended to leave the WCS at 0,0,0. You should generally not be
|
||||
setting it to any value. Instead, your project's origin should use a
|
||||
coordinate operation to convert to the projected CRS.
|
||||
Typically, a project's local engineering origin (0, 0, 0) has a coordinate
|
||||
operation (e.g. map conversion) to a projected CRS. If a WCS is provided,
|
||||
the coordinate operation is relative to the WCS, not the local engineering
|
||||
origin.
|
||||
|
||||
For example, if I have an IfcSite with a placement at (10, 0, 0) and a map
|
||||
conversion of (50, 0, 0), my IfcSite's local XYZ is at (10, 0, 0) with an
|
||||
ENH (Easting, Northing, Height) of (60, 0, 0).
|
||||
|
||||
If I then define by WCS at (15, 0, 0), my IfcSite's local XYZ is still at
|
||||
(10, 0, 0) but its ENH is now at (45, 0, 0).
|
||||
|
||||
It's recommended to leave the WCS at 0,0,0. Please :)
|
||||
|
||||
:param x: The X translation of the WCS
|
||||
:param y: The Y translation of the WCS
|
||||
|
||||
@@ -150,6 +150,9 @@ def auto_xyz2enh(
|
||||
parameters = get_helmert_transformation_parameters(ifc_file)
|
||||
if not parameters:
|
||||
return x, y, z
|
||||
wcs = get_wcs(ifc_file)
|
||||
if wcs is not None:
|
||||
x, y, z = (np.linalg.inv(wcs) @ np.array((x, y, z, 1)))[:3]
|
||||
enh = xyz2enh(x, y, z, *parameters)
|
||||
if should_return_in_map_units:
|
||||
return enh
|
||||
@@ -181,7 +184,11 @@ def auto_enh2xyz(ifc_file, easting, northing, height, is_specified_in_map_units:
|
||||
easting *= parameters.scale
|
||||
northing *= parameters.scale
|
||||
height *= parameters.scale
|
||||
return enh2xyz(easting, northing, height, *parameters)
|
||||
xyz = enh2xyz(easting, northing, height, *parameters)
|
||||
wcs = get_wcs(ifc_file)
|
||||
if wcs is not None:
|
||||
xyz = tuple((wcs @ np.array((*xyz, 1)))[:3])
|
||||
return xyz
|
||||
|
||||
|
||||
def get_helmert_transformation_parameters(ifc_file: ifcopenshell.file) -> Optional[HelmertTransformation]:
|
||||
@@ -423,6 +430,9 @@ def auto_local2global(
|
||||
parameters = get_helmert_transformation_parameters(ifc_file)
|
||||
if not parameters:
|
||||
return matrix.copy()
|
||||
wcs = get_wcs(ifc_file)
|
||||
if wcs is not None:
|
||||
matrix = np.linalg.inv(wcs) @ matrix
|
||||
result = local2global(matrix, *parameters)
|
||||
if should_return_in_map_units:
|
||||
return result
|
||||
@@ -517,7 +527,11 @@ def auto_global2local(
|
||||
matrix[0][3] *= parameters.scale
|
||||
matrix[1][3] *= parameters.scale
|
||||
matrix[2][3] *= parameters.scale
|
||||
return global2local(matrix, *parameters)
|
||||
result = global2local(matrix, *parameters)
|
||||
wcs = get_wcs(ifc_file)
|
||||
if wcs is not None:
|
||||
return wcs @ result
|
||||
return result
|
||||
|
||||
|
||||
def xaxis2angle(x: float, y: float) -> float:
|
||||
|
||||
@@ -39,6 +39,11 @@ class TestEditGeoreferencing(test.bootstrap.IFC4):
|
||||
assert conversion.Eastings == 123.45
|
||||
assert conversion.Northings == 234.56
|
||||
|
||||
ifcopenshell.api.georeference.edit_georeferencing(self.file, projected_crs={"Name": "EPSG:1234"})
|
||||
assert crs.Name == "EPSG:1234"
|
||||
ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Eastings": 42})
|
||||
assert conversion.Eastings == 42
|
||||
|
||||
|
||||
class TestEditGeoreferencingIFC2X3(test.bootstrap.IFC2X3):
|
||||
def test_editing_georeferencing(self):
|
||||
|
||||
@@ -81,6 +81,22 @@ class TestAutoXYZ2ENH(test.bootstrap.IFC4X3):
|
||||
)
|
||||
assert np.allclose(subject.auto_xyz2enh(self.file, 1000, 1000, 0), (0, 3, 3))
|
||||
|
||||
def test_map_conversion_with_wcs(self):
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
ifcopenshell.api.context.add_context(self.file, "Model")
|
||||
ifcopenshell.api.georeference.add_georeferencing(self.file)
|
||||
ifcopenshell.api.georeference.edit_georeferencing(
|
||||
self.file,
|
||||
projected_crs={"Name": "EPSG:7856"},
|
||||
coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
|
||||
)
|
||||
ifcopenshell.api.georeference.edit_wcs(self.file, x=1, y=2, z=3)
|
||||
assert np.allclose(subject.auto_xyz2enh(self.file, 0, 0, 0), (0, 0, 0))
|
||||
assert np.allclose(subject.auto_xyz2enh(self.file, 1, 2, 3), (1, 2, 3))
|
||||
ifcopenshell.api.georeference.edit_wcs(self.file, x=1, y=1, z=2)
|
||||
assert np.allclose(subject.auto_xyz2enh(self.file, 0, 0, 0), (0, 1, 1))
|
||||
assert np.allclose(subject.auto_xyz2enh(self.file, 1, 2, 3), (1, 3, 4))
|
||||
|
||||
def test_map_conversion_scaled(self):
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
ifcopenshell.api.context.add_context(self.file, "Model")
|
||||
@@ -152,6 +168,22 @@ class TestAutoENH2XYZ(test.bootstrap.IFC4X3):
|
||||
)
|
||||
assert np.allclose(subject.auto_enh2xyz(self.file, 0, 3, 3), (1000, 1000, 0))
|
||||
|
||||
def test_map_conversion_with_wcs(self):
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
ifcopenshell.api.context.add_context(self.file, "Model")
|
||||
ifcopenshell.api.georeference.add_georeferencing(self.file)
|
||||
ifcopenshell.api.georeference.edit_georeferencing(
|
||||
self.file,
|
||||
projected_crs={"Name": "EPSG:7856"},
|
||||
coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
|
||||
)
|
||||
ifcopenshell.api.georeference.edit_wcs(self.file, x=1, y=2, z=3)
|
||||
assert np.allclose(subject.auto_enh2xyz(self.file, 0, 0, 0), (0, 0, 0))
|
||||
assert np.allclose(subject.auto_enh2xyz(self.file, 1, 2, 3), (1, 2, 3))
|
||||
ifcopenshell.api.georeference.edit_wcs(self.file, x=1, y=1, z=2)
|
||||
assert np.allclose(subject.auto_enh2xyz(self.file, 0, 1, 1), (0, 0, 0))
|
||||
assert np.allclose(subject.auto_enh2xyz(self.file, 1, 3, 4), (1, 2, 3))
|
||||
|
||||
def test_map_conversion_scaled(self):
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
ifcopenshell.api.context.add_context(self.file, "Model")
|
||||
@@ -324,6 +356,25 @@ class TestAutoLocal2Global(test.bootstrap.IFC4):
|
||||
m2[:, 3][0:3] = [0, 3, 3]
|
||||
assert np.allclose(subject.auto_local2global(self.file, m), m2)
|
||||
|
||||
def test_map_conversion_with_wcs(self):
|
||||
m = np.eye(4)
|
||||
m2 = np.eye(4)
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
ifcopenshell.api.context.add_context(self.file, "Model")
|
||||
ifcopenshell.api.georeference.add_georeferencing(self.file)
|
||||
ifcopenshell.api.georeference.edit_georeferencing(
|
||||
self.file,
|
||||
projected_crs={"Name": "EPSG:7856"},
|
||||
coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
|
||||
)
|
||||
ifcopenshell.api.georeference.edit_wcs(self.file, x=1, y=2, z=3)
|
||||
assert np.allclose(subject.auto_local2global(self.file, m), m2)
|
||||
ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Scale": 0.001})
|
||||
ifcopenshell.api.georeference.edit_wcs(self.file, x=1000, y=1000, z=2000)
|
||||
m[:, 3][0:3] = [1000, 2000, 3000]
|
||||
m2[:, 3][0:3] = [1, 3, 4]
|
||||
assert np.allclose(subject.auto_local2global(self.file, m), m2)
|
||||
|
||||
|
||||
class TestAutoGlobal2Local(test.bootstrap.IFC4):
|
||||
def test_no_georeferencing(self):
|
||||
@@ -359,6 +410,25 @@ class TestAutoGlobal2Local(test.bootstrap.IFC4):
|
||||
m2[:, 3][0:3] = [0, 3, 3]
|
||||
assert np.allclose(subject.auto_global2local(self.file, m2), m)
|
||||
|
||||
def test_map_conversion_with_wcs(self):
|
||||
m = np.eye(4)
|
||||
m2 = np.eye(4)
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
ifcopenshell.api.context.add_context(self.file, "Model")
|
||||
ifcopenshell.api.georeference.add_georeferencing(self.file)
|
||||
ifcopenshell.api.georeference.edit_georeferencing(
|
||||
self.file,
|
||||
projected_crs={"Name": "EPSG:7856"},
|
||||
coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
|
||||
)
|
||||
ifcopenshell.api.georeference.edit_wcs(self.file, x=1, y=2, z=3)
|
||||
assert np.allclose(subject.auto_global2local(self.file, m2), m)
|
||||
ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Scale": 0.001})
|
||||
ifcopenshell.api.georeference.edit_wcs(self.file, x=1000, y=1000, z=2000)
|
||||
m[:, 3][0:3] = [1000, 2000, 3000]
|
||||
m2[:, 3][0:3] = [1, 3, 4]
|
||||
assert np.allclose(subject.auto_global2local(self.file, m2), m)
|
||||
|
||||
|
||||
class TestXAxis2Angle(test.bootstrap.IFC4):
|
||||
def test_run(self):
|
||||
|
||||
Reference in New Issue
Block a user