From e295d596feb7b547ec597c0d73bcdfd3097802f3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 23 Jun 2024 23:19:27 +1000 Subject: [PATCH] Split true north in API and Blender UI to be editable independent of georeferencing They are two separate concepts, after all. --- .../bim/module/georeference/__init__.py | 5 + .../bim/module/georeference/operator.py | 30 ++++++ .../bim/module/georeference/prop.py | 1 + .../blenderbim/bim/module/georeference/ui.py | 94 +++++++++++----- .../blenderbim/core/georeference.py | 15 ++- .../blenderbim/tool/georeference.py | 9 ++ .../ifcopenshell/api/georeference/__init__.py | 2 + .../api/georeference/edit_georeferencing.py | 101 +++++------------- .../api/georeference/edit_true_north.py | 75 +++++++++++++ .../georeference/test_edit_georeferencing.py | 13 +-- .../api/georeference/test_edit_true_north.py | 38 +++++++ 11 files changed, 273 insertions(+), 110 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/georeference/edit_true_north.py create mode 100644 src/ifcopenshell-python/test/api/georeference/test_edit_true_north.py diff --git a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py index ad6a98f229..f18cbf6817 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py @@ -25,10 +25,13 @@ classes = ( operator.ConvertGlobalToLocal, operator.ConvertLocalToGlobal, operator.DisableEditingGeoreferencing, + operator.DisableEditingTrueNorth, operator.DisableEditingWCS, operator.EditGeoreferencing, + operator.EditTrueNorth, operator.EditWCS, operator.EnableEditingGeoreferencing, + operator.EnableEditingTrueNorth, operator.EnableEditingWCS, operator.GetCursorLocation, operator.ImportPlot, @@ -39,6 +42,8 @@ classes = ( operator.SetIfcTrueNorth, prop.BIMGeoreferenceProperties, ui.BIM_PT_gis, + ui.BIM_PT_gis_wcs, + ui.BIM_PT_gis_true_north, ui.BIM_PT_gis_calculator, ) diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index 0e9eb7528f..88936ea2e6 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -219,3 +219,33 @@ class DisableEditingWCS(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): core.disable_editing_wcs(tool.Georeference) + + +class EnableEditingTrueNorth(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.enable_editing_true_north" + bl_label = "Enable Editing True North" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Enable editing True North" + + def _execute(self, context): + core.enable_editing_true_north(tool.Georeference) + + +class EditTrueNorth(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.edit_true_north" + bl_label = "Edit True North" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Edit the True North" + + def _execute(self, context): + core.edit_true_north(tool.Ifc, tool.Georeference) + + +class DisableEditingTrueNorth(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.disable_editing_true_north" + bl_label = "Disable Editing True North" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Close editing panel" + + def _execute(self, context): + core.disable_editing_true_north(tool.Georeference) diff --git a/src/blenderbim/blenderbim/bim/module/georeference/prop.py b/src/blenderbim/blenderbim/bim/module/georeference/prop.py index c79f7937ee..25e1a2cdd6 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/prop.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/prop.py @@ -44,6 +44,7 @@ class BIMGeoreferenceProperties(PropertyGroup): ) is_editing: BoolProperty(name="Is Editing") is_editing_wcs: BoolProperty(name="Is Editing WCS") + is_editing_true_north: BoolProperty(name="Is Editing True North") coordinate_operation: CollectionProperty(name="Coordinate Operation", type=Attribute) projected_crs: CollectionProperty(name="Projected CRS", type=Attribute) local_coordinates: StringProperty( diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py index bff8b4ee19..50a4383961 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py @@ -44,11 +44,6 @@ class BIM_PT_gis(Panel): else: self.draw_ui(context) - if props.is_editing_wcs: - self.draw_editable_wcs_ui(context) - else: - self.draw_wcs_ui() - def draw_editable_ui(self, context): props = context.scene.BIMGeoreferenceProperties row = self.layout.row(align=True) @@ -68,19 +63,6 @@ class BIM_PT_gis(Panel): row.operator("bim.set_blender_grid_north", text="Set Blender North") draw_attribute(attribute, self.layout.row()) - row = self.layout.row() - row.label(text="True North", icon="LIGHT_SUN") - row = self.layout.row() - row.prop(props, "has_true_north") - row = self.layout.row() - row.prop(props, "true_north_abscissa") - row = self.layout.row() - row.prop(props, "true_north_ordinate") - if hasattr(context.scene, "sun_pos_properties"): - row = self.layout.row(align=True) - row.operator("bim.set_ifc_true_north", text="Set IFC North") - row.operator("bim.set_blender_true_north", text="Set Blender North") - def draw_ui(self, context): props = context.scene.BIMGeoreferenceProperties @@ -153,24 +135,83 @@ class BIM_PT_gis(Panel): row.label(text="Derived Angle") row.label(text=GeoreferenceData.data["map_derived_angle"]) + +class BIM_PT_gis_true_north(Panel): + bl_idname = "BIM_PT_gis_true_north" + bl_label = "True North" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_gis" + + def draw(self, context): + if not GeoreferenceData.is_loaded: + GeoreferenceData.load() + + self.props = context.scene.BIMGeoreferenceProperties + + if self.props.is_editing_true_north: + self.draw_editable_ui(context) + else: + self.draw_ui() + + def draw_editable_ui(self, context): + row = self.layout.row() + row.prop(self.props, "has_true_north") + row = self.layout.row() + row.prop(self.props, "true_north_abscissa") + row = self.layout.row() + row.prop(self.props, "true_north_ordinate") + if hasattr(context.scene, "sun_pos_properties"): + row = self.layout.row(align=True) + row.operator("bim.set_ifc_true_north", text="Set IFC North") + row.operator("bim.set_blender_true_north", text="Set Blender North") + + row = self.layout.row(align=True) + row.operator("bim.edit_true_north", icon="CHECKMARK") + row.operator("bim.disable_editing_true_north", icon="CANCEL", text="") + + def draw_ui(self): if GeoreferenceData.data["true_north"]: - row = self.layout.row() - row.label(text="True North", icon="LIGHT_SUN") row = self.layout.row(align=True) row.label(text="Vector") row.label(text=str(GeoreferenceData.data["true_north"][0:2])[1:-1]) + row.operator("bim.enable_editing_true_north", icon="GREASEPENCIL", text="") row = self.layout.row(align=True) row.label(text="Derived Angle") row.label(text=GeoreferenceData.data["true_derived_angle"]) + else: + row = self.layout.row(align=True) + row.label(text="No True North Found", icon="LIGHT_SUN") + row.operator("bim.enable_editing_true_north", icon="GREASEPENCIL", text="") - def draw_wcs_ui(self): - row = self.layout.row(align=True) - row.label(text="World Coordinate System", icon="EMPTY_ARROWS") - row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="") +class BIM_PT_gis_wcs(Panel): + bl_idname = "BIM_PT_gis_wcs" + bl_label = "World Coordinate System" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_gis" + + def draw(self, context): + if not GeoreferenceData.is_loaded: + GeoreferenceData.load() + + props = context.scene.BIMGeoreferenceProperties + + if props.is_editing_wcs: + self.draw_editable_ui(context) + else: + self.draw_ui() + + def draw_ui(self): if GeoreferenceData.data["world_coordinate_system"]["has_transformation"]: - row = self.layout.row() + row = self.layout.row(align=True) row.label(text="Unrecommended Transformation Found", icon="ERROR") + row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="") row = self.layout.row(align=True) row.label(text="X") row.label(text=str(GeoreferenceData.data["world_coordinate_system"]["x"])) @@ -186,8 +227,9 @@ class BIM_PT_gis(Panel): else: row = self.layout.row() row.label(text="No WCS Transformation", icon="CHECKMARK") + row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="") - def draw_editable_wcs_ui(self, context): + def draw_editable_ui(self, context): props = context.scene.BIMGeoreferenceProperties row = self.layout.row(align=True) diff --git a/src/blenderbim/blenderbim/core/georeference.py b/src/blenderbim/blenderbim/core/georeference.py index 485cff511d..ca687c88ab 100644 --- a/src/blenderbim/blenderbim/core/georeference.py +++ b/src/blenderbim/blenderbim/core/georeference.py @@ -24,7 +24,6 @@ def add_georeferencing(georeference): def enable_editing_georeferencing(georeference): georeference.import_projected_crs() georeference.import_coordinate_operation() - georeference.import_true_north() georeference.enable_editing() @@ -100,3 +99,17 @@ def edit_wcs(ifc, georeference): wcs = georeference.export_wcs() georeference.set_wcs(wcs) georeference.disable_editing_wcs() + + +def enable_editing_true_north(georeference): + georeference.import_true_north() + georeference.enable_editing_true_north() + + +def disable_editing_true_north(georeference): + georeference.disable_editing_true_north() + + +def edit_true_north(ifc, georeference): + ifc.run("georeference.edit_true_north", true_north=georeference.get_true_north_attributes()) + georeference.disable_editing_true_north() diff --git a/src/blenderbim/blenderbim/tool/georeference.py b/src/blenderbim/blenderbim/tool/georeference.py index 5a3c87270e..b9d2c4f124 100644 --- a/src/blenderbim/blenderbim/tool/georeference.py +++ b/src/blenderbim/blenderbim/tool/georeference.py @@ -179,6 +179,15 @@ class Georeference(blenderbim.core.tool.Georeference): def disable_editing_wcs(cls): bpy.context.scene.BIMGeoreferenceProperties.is_editing_wcs = False + @classmethod + def enable_editing_true_north(cls): + bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = True + + @classmethod + def disable_editing_true_north(cls): + bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = False + + @classmethod def set_coordinates(cls, io, coordinates): if io == "local": diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/__init__.py index 986c1d3b1b..ffe0306db1 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/georeference/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/__init__.py @@ -26,6 +26,7 @@ map coordinates and project local engineering coordinates. from .. import wrap_usecases from .add_georeferencing import add_georeferencing from .edit_georeferencing import edit_georeferencing +from .edit_true_north import edit_true_north from .edit_wcs import edit_wcs from .remove_georeferencing import remove_georeferencing @@ -34,6 +35,7 @@ wrap_usecases(__path__, __name__) __all__ = [ "add_georeferencing", "edit_georeferencing", + "edit_true_north", "edit_wcs", "remove_georeferencing", ] diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py index 1b09ffa88d..8a6211b230 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py +++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py @@ -24,7 +24,6 @@ def edit_georeferencing( file: ifcopenshell.file, coordinate_operation: Optional[dict[str, Any]] = None, projected_crs: Optional[dict[str, Any]] = None, - true_north: Optional[tuple[float, float]] = None, ) -> None: """Edits the attributes of a map conversion, projected CRS, and true north @@ -36,28 +35,18 @@ def edit_georeferencing( https://docs.blenderbim.org/users/georeferencing.html For more information about the attributes and data types of an - IfcMapConversion, consult the IFC documentation. + IfcCoordinateOperation, consult the IFC documentation. For more information about the attributes and data types of an IfcProjectedCRS, consult the IFC documentation. - True north is defined as a unitised 2D vector pointing to true north. - Note that true north is not part of georeferencing, and is only - optionally provided as a reference value, typically for solar analysis. - See ifcopenshell.util.geolocation for more utilities to convert to and from local and map coordinates to check your results. :param coordinate_operation: The dictionary of attribute names and values you want to edit. - :type coordinate_operation: dict, optional :param projected_crs: The IfcProjectedCRS dictionary of attribute names and values you want to edit. - :type projected_crs: dict, optional - :param true_north: A unitised 2D vector, where each ordinate is a float - :type true_north: tuple[float, float], optional - :return: None - :rtype: None Example: @@ -88,64 +77,32 @@ def edit_georeferencing( "Scale": 0.99956, # Ask your surveyor for your site's average combined scale factor! }) """ - usecase = Usecase() - usecase.file = file - usecase.settings = { - "coordinate_operation": coordinate_operation or {}, - "projected_crs": projected_crs or {}, - "true_north": true_north or [], - } - return usecase.execute() - - -class Usecase: - def execute(self): - self.set_true_north() - if self.file.schema == "IFC2X3": - if not (project := self.file.by_type("IfcProject")): - return - project = project[0] - if (crs := ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS")): - crs = self.file.by_id(crs["id"]) - for k, v in self.settings["projected_crs"].items(): - if k == "Description": - v = self.file.createIfcText(v) - elif k == "Name": - v = self.file.createIfcLabel(v) - else: - v = self.file.createIfcIdentifier(v) - ifcopenshell.api.pset.edit_pset(self.file, crs, properties=self.settings["projected_crs"]) - if (conversion := ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")): - conversion = self.file.by_id(conversion["id"]) - for k, v in self.settings["coordinate_operation"].items(): - if k in ("XAxisAbscissa", "XAxisOrdinate", "Scale"): - v = self.file.createIfcReal(v) - else: - v = self.file.createIfcLengthMeasure(v) - ifcopenshell.api.pset.edit_pset(self.file, conversion, properties=self.settings["coordinate_operation"]) + if file.schema == "IFC2X3": + if not (project := file.by_type("IfcProject")): return - coordinate_operation = self.file.by_type("IfcCoordinateOperation")[0] - projected_crs = self.file.by_type("IfcProjectedCRS")[0] - for name, value in self.settings["coordinate_operation"].items(): - setattr(coordinate_operation, name, value) - for name, value in self.settings["projected_crs"].items(): - setattr(projected_crs, name, value) - - def set_true_north(self): - if not self.settings["true_north"]: - return - for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): - if context.TrueNorth: - if len(self.file.get_inverse(context.TrueNorth)) != 1: - context.TrueNorth = self.file.create_entity("IfcDirection") - else: - context.TrueNorth = self.file.create_entity("IfcDirection") - direction = context.TrueNorth - if self.settings["true_north"] is None: - # TODO: code will never be executed since None value - # is substituted by an empty list - context.TrueNorth = self.settings["true_north"] - elif context.CoordinateSpaceDimension == 2: - direction.DirectionRatios = self.settings["true_north"][0:2] - else: - direction.DirectionRatios = self.settings["true_north"][0:2] + [0.0] + 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) + 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) diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_true_north.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_true_north.py new file mode 100644 index 0000000000..0bb84b16e0 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_true_north.py @@ -0,0 +1,75 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell +import ifcopenshell.util.geolocation +from typing import Union, Optional + + +def edit_true_north(file: ifcopenshell.file, true_north: Optional[Union[tuple[float, float], float]] = 0.0) -> None: + """Edits the true north + + Given project north being up (i.e. a vector of 0, 1), true north is defined + as a unitised 2D vector pointing to true north. Alternatively, true north + may be defined as a rotation from project north to true north. + Anticlockwise is positive. + + Note that true north is not part of georeferencing, and is only optionally + provided as a reference value, typically for solar analysis. Remember: grid + north (what your surveyor will typically use) is not the same as true + north! + + :param true_north: A unitised 2D vector, where each ordinate is a float, or + an angle in decimal degrees where anticlockwise is positive. + + Example: + + .. code:: python + + # Both of these are identical, and indicate that: + # - If project north is up the page, true north is in the top left + # - The building is therefore facing north east + ifcopenshell.api.run("georeference.edit_true_north", model, true_north=30) + ifcopenshell.api.run("georeference.edit_true_north", model, true_north=(-0.5, 0.8660254)) + + # 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)): + x, y = ifcopenshell.util.geolocation.angle2yaxis(true_north) + else: + x, y = true_north + + for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + 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: + direction.DirectionRatios = (x, y) + else: + direction.DirectionRatios = (x, y, 0.0) diff --git a/src/ifcopenshell-python/test/api/georeference/test_edit_georeferencing.py b/src/ifcopenshell-python/test/api/georeference/test_edit_georeferencing.py index e6284f3923..c49febd755 100644 --- a/src/ifcopenshell-python/test/api/georeference/test_edit_georeferencing.py +++ b/src/ifcopenshell-python/test/api/georeference/test_edit_georeferencing.py @@ -31,7 +31,7 @@ class TestEditGeoreferencing(test.bootstrap.IFC4): ifcopenshell.api.georeference.edit_georeferencing( self.file, projected_crs={"Name": "EPSG:7856"}, - map_conversion={"Eastings": 123.45, "Northings": 234.56}, + coordinate_operation={"Eastings": 123.45, "Northings": 234.56}, ) crs = self.file.by_type("IfcProjectedCRS")[0] assert crs.Name == "EPSG:7856" @@ -39,15 +39,6 @@ class TestEditGeoreferencing(test.bootstrap.IFC4): assert conversion.Eastings == 123.45 assert conversion.Northings == 234.56 - def test_editing_true_north(self): - ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") - model = ifcopenshell.api.context.add_context(self.file, "Model") - plan = ifcopenshell.api.context.add_context(self.file, "Plan") - ifcopenshell.api.georeference.add_georeferencing(self.file) - ifcopenshell.api.georeference.edit_georeferencing(self.file, true_north=[0.0, 1.0]) - assert model.TrueNorth[0] == (0.0, 1.0, 0.0) - assert plan.TrueNorth[0] == (0.0, 1.0) - class TestEditGeoreferencingIFC2X3(test.bootstrap.IFC2X3): def test_editing_georeferencing(self): @@ -56,7 +47,7 @@ class TestEditGeoreferencingIFC2X3(test.bootstrap.IFC2X3): ifcopenshell.api.georeference.edit_georeferencing( self.file, projected_crs={"Name": "EPSG:7856"}, - map_conversion={"Eastings": 123.45, "Northings": 234.56}, + coordinate_operation={"Eastings": 123.45, "Northings": 234.56}, ) conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion", verbose=True) crs = ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS", verbose=True) diff --git a/src/ifcopenshell-python/test/api/georeference/test_edit_true_north.py b/src/ifcopenshell-python/test/api/georeference/test_edit_true_north.py new file mode 100644 index 0000000000..5076b46d67 --- /dev/null +++ b/src/ifcopenshell-python/test/api/georeference/test_edit_true_north.py @@ -0,0 +1,38 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2022 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import numpy as np +import test.bootstrap +import ifcopenshell.api.root +import ifcopenshell.api.context +import ifcopenshell.api.georeference +import ifcopenshell.util.geolocation + + +class TestEditTrueNorth(test.bootstrap.IFC4): + def test_editing_true_north(self): + ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") + model = ifcopenshell.api.context.add_context(self.file, "Model") + plan = ifcopenshell.api.context.add_context(self.file, "Plan") + ifcopenshell.api.georeference.edit_true_north(self.file, true_north=[0.0, 1.0]) + assert model.TrueNorth[0] == (0.0, 1.0, 0.0) + assert plan.TrueNorth[0] == (0.0, 1.0) + ifcopenshell.api.georeference.edit_true_north(self.file, true_north=[-0.5, 0.8660254]) + assert np.isclose(ifcopenshell.util.geolocation.get_true_north(self.file), 30) + ifcopenshell.api.georeference.edit_true_north(self.file, true_north=30) + assert np.isclose(ifcopenshell.util.geolocation.get_true_north(self.file), 30)