diff --git a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py index 876023fc2e..4d6a7d687e 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py +++ b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py @@ -22,6 +22,7 @@ import numpy.typing as npt import ifcopenshell import ifcopenshell.util.unit import ifcopenshell.util.element +import ifcopenshell.util.placement from typing import NamedTuple, Optional, Union @@ -522,6 +523,14 @@ def auto_global2local( def xaxis2angle(x: float, y: float) -> float: """Converts X axis abscissa and ordinates to an angle in decimal degrees + The X axis abscissa and ordinate is how IFC stores grid north. + + This X axis vector indicates "where is project east, if grid north is up + the page?". See the diagram on the IfcGeometricRepresentationContext + documentation for clarification. + + The angle indicates "how do I rotate grid east to get to project east?". + :param x: The X axis abscissa :param y: The X axis ordinate :return: The equivalent angle in decimal degrees from the X axis @@ -534,6 +543,12 @@ def yaxis2angle(x: float, y: float) -> float: The Y axis abscissa and ordinate is how IFC stores true north. + This Y axis vector indicates "where is true north, if project north is up + the page?". See the diagram on the IfcGeometricRepresentationContext + documentation for clarification. + + The angle indicates "how do I rotate project north to get to true north?". + :param x: The Y axis abscissa :param y: The Y axis ordinate :return: The equivalent angle in decimal degrees from the Y axis @@ -617,3 +632,18 @@ def angle2yaxis(angle: float) -> tuple[float, float]: x = -math.sin(angle_rad) y = math.cos(angle_rad) return x, y + + +def get_wcs(ifc_file: ifcopenshell.file) -> Optional[npt.NDArray[np.float64]]: + """Gets the WCS (prioritising 3D contexts) as a matrix + + :param: The IFC file + :return: A 4x4 matrix in project units + """ + wcs = None + for context in ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + wcs = context.WorldCoordinateSystem + if context.ContextType == "Model": + break + if wcs: + return ifcopenshell.util.placement.get_axis2placement(wcs) diff --git a/src/ifcopenshell-python/test/util/test_geolocation.py b/src/ifcopenshell-python/test/util/test_geolocation.py index 1b54973d7a..0d9d5d92c4 100644 --- a/src/ifcopenshell-python/test/util/test_geolocation.py +++ b/src/ifcopenshell-python/test/util/test_geolocation.py @@ -358,3 +358,43 @@ class TestAutoGlobal2Local(test.bootstrap.IFC4): m2[:, 1][0:3] = [-1, 0, 0] m2[:, 3][0:3] = [0, 3, 3] assert np.allclose(subject.auto_global2local(self.file, m2), m) + + +class TestXAxis2Angle(test.bootstrap.IFC4): + def test_run(self): + assert subject.xaxis2angle(1, 0) == 0 + assert subject.xaxis2angle(1, 1) == -45 + assert subject.xaxis2angle(-1, 1) == -135 + assert subject.xaxis2angle(1, -1) == 45 + assert subject.xaxis2angle(-1, -1) == 135 + assert np.isclose(subject.xaxis2angle(0.8660254, -0.5), 30) + + +class TestAngle2XAxis(test.bootstrap.IFC4): + def test_run(self): + a = 0.707106781186 + assert np.allclose(subject.angle2xaxis(0), (1, 0)) + assert np.allclose(subject.angle2xaxis(-45), (a, a)) + assert np.allclose(subject.angle2xaxis(-135), (-a, a)) + assert np.allclose(subject.angle2xaxis(45), (a, -a)) + assert np.allclose(subject.angle2xaxis(135), (-a, -a)) + + +class TestYAxis2Angle(test.bootstrap.IFC4): + def test_run(self): + assert subject.yaxis2angle(0, 1) == 0 + assert np.isclose(subject.yaxis2angle(-0.5, 0.8660254), 30) + assert subject.yaxis2angle(1, 1) == -45 + assert subject.yaxis2angle(-1, 1) == 45 + assert subject.yaxis2angle(1, -1) == -135 + assert subject.yaxis2angle(-1, -1) == 135 + + +class TestAngle2YAxis(test.bootstrap.IFC4): + def test_run(self): + a = 0.707106781186 + assert np.allclose(subject.angle2yaxis(0), (0, 1)) + assert np.allclose(subject.angle2yaxis(-45), (a, a)) + assert np.allclose(subject.angle2yaxis(45), (-a, a)) + assert np.allclose(subject.angle2yaxis(-135), (a, -a)) + assert np.allclose(subject.angle2yaxis(135), (-a, -a))