diff --git a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py index ecb2fbdc7f..48ec27e3c9 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py @@ -5,8 +5,10 @@ classes = ( operator.EnableEditingGeoreferencing, operator.DisableEditingGeoreferencing, operator.EditGeoreferencing, - operator.SetNorthOffset, - operator.GetNorthOffset, + operator.SetIfcGridNorth, + operator.SetBlenderGridNorth, + operator.SetIfcTrueNorth, + operator.SetBlenderTrueNorth, operator.RemoveGeoreferencing, operator.AddGeoreferencing, operator.ConvertLocalToGlobal, diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index b506ebb08c..e1f4cb944e 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -69,6 +69,11 @@ class EnableEditingGeoreferencing(bpy.types.Operator): elif data_type == "boolean": new.bool_value = False if new.is_null else Data.map_conversion[attribute.name()] + props.has_true_north = bool(Data.true_north) + if Data.true_north: + props.true_north_abscissa = str(Data.true_north[0]) + props.true_north_ordinate = str(Data.true_north[1]) + props.is_editing = True return {"FINISHED"} @@ -129,19 +134,31 @@ class EditGeoreferencing(bpy.types.Operator): elif blender_attribute.data_type == "boolean": map_conversion[attribute.name()] = blender_attribute.bool_value + true_north = None + if props.has_true_north: + try: + true_north = [float(props.true_north_abscissa), float(props.true_north_ordinate)] + except: + pass + ifcopenshell.api.run( "georeference.edit_georeferencing", self.file, - **{"map_conversion": map_conversion, "projected_crs": projected_crs, "map_unit": map_unit} + **{ + "map_conversion": map_conversion, + "projected_crs": projected_crs, + "map_unit": map_unit, + "true_north": true_north, + } ) Data.load(IfcStore.get_file()) bpy.ops.bim.disable_editing_georeferencing() return {"FINISHED"} -class SetNorthOffset(bpy.types.Operator): - bl_idname = "bim.set_north_offset" - bl_label = "Set North Offset" +class SetBlenderGridNorth(bpy.types.Operator): + bl_idname = "bim.set_blender_grid_north" + bl_label = "Set Blender Grid North" def execute(self, context): context.scene.sun_pos_properties.north_offset = -radians( @@ -153,9 +170,9 @@ class SetNorthOffset(bpy.types.Operator): return {"FINISHED"} -class GetNorthOffset(bpy.types.Operator): - bl_idname = "bim.get_north_offset" - bl_label = "Get North Offset" +class SetIfcGridNorth(bpy.types.Operator): + bl_idname = "bim.set_ifc_grid_north" + bl_label = "Set IFC Grid North" def execute(self, context): x_angle = -context.scene.sun_pos_properties.north_offset @@ -164,6 +181,31 @@ class GetNorthOffset(bpy.types.Operator): return {"FINISHED"} +class SetBlenderTrueNorth(bpy.types.Operator): + bl_idname = "bim.set_blender_true_north" + bl_label = "Set Blender True North" + + def execute(self, context): + context.scene.sun_pos_properties.north_offset = -radians( + ifcopenshell.util.geolocation.yaxis2angle( + float(context.scene.BIMGeoreferenceProperties.true_north_abscissa), + float(context.scene.BIMGeoreferenceProperties.true_north_ordinate), + ) + ) + return {"FINISHED"} + + +class SetIfcTrueNorth(bpy.types.Operator): + bl_idname = "bim.set_ifc_true_north" + bl_label = "Set IFC True North" + + def execute(self, context): + y_angle = -context.scene.sun_pos_properties.north_offset + radians(90) + context.scene.BIMGeoreferenceProperties.true_north_abscissa = str(cos(y_angle)) + context.scene.BIMGeoreferenceProperties.true_north_ordinate = str(sin(y_angle)) + return {"FINISHED"} + + class RemoveGeoreferencing(bpy.types.Operator): bl_idname = "bim.remove_georeferencing" bl_label = "Remove Georeferencing" diff --git a/src/blenderbim/blenderbim/bim/module/georeference/prop.py b/src/blenderbim/blenderbim/bim/module/georeference/prop.py index 9c7db5a841..0bcaafdc7d 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/prop.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/prop.py @@ -46,3 +46,6 @@ class BIMGeoreferenceProperties(PropertyGroup): blender_orthogonal_height: StringProperty(name="Blender Orthogonal Height", default="0") blender_x_axis_abscissa: StringProperty(name="Blender X Axis Abscissa", default="1") blender_x_axis_ordinate: StringProperty(name="Blender X Axis Ordinate", default="0") + has_true_north: BoolProperty(name="Has True North", default=True) + true_north_abscissa: StringProperty(name="True North Abscissa") + true_north_ordinate: StringProperty(name="True North Ordinate") diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py index fdd06b9765..b0ceb6f751 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py @@ -1,7 +1,9 @@ +import ifcopenshell.util.geolocation from bpy.types import Panel from ifcopenshell.api.georeference.data import Data from blenderbim.bim.ifc import IfcStore + class BIM_PT_gis(Panel): bl_label = "IFC Georeferencing" bl_idname = "BIM_PT_gis" @@ -58,8 +60,8 @@ class BIM_PT_gis(Panel): for attribute in props.map_conversion: if attribute.name == "Scale" and hasattr(context.scene, "sun_pos_properties"): row = self.layout.row(align=True) - row.operator("bim.get_north_offset", text="Set IFC North") - row.operator("bim.set_north_offset", text="Set Blender North") + row.operator("bim.set_ifc_grid_north", text="Set IFC North") + row.operator("bim.set_blender_grid_north", text="Set Blender North") row = self.layout.row(align=True) if attribute.data_type == "string": row.prop(attribute, "string_value", text=attribute.name) @@ -72,6 +74,18 @@ class BIM_PT_gis(Panel): if attribute.is_optional: row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + 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 @@ -103,6 +117,17 @@ class BIM_PT_gis(Panel): row = self.layout.row(align=True) row.label(text="XAxisOrdinate") row.label(text=props.blender_x_axis_ordinate) + row.label(text="Derived Grid North") + row.label( + text=str( + round( + ifcopenshell.util.geolocation.xaxis2angle( + float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate) + ), + 3, + ) + ) + ) elif IfcStore.get_file().schema == "IFC2X3": row = self.layout.row() row.label(text="Not Georeferenced") @@ -152,6 +177,15 @@ class BIM_PT_gis(Panel): ) ) + if 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(Data.true_north[0:2])[1:-1]) + row = self.layout.row(align=True) + row.label(text="Derived Angle") + row.label(text=str(round(ifcopenshell.util.geolocation.yaxis2angle(*Data.true_north[0:2]), 3))) class BIM_PT_gis_utilities(Panel): diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/data.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/data.py index b8d029b2be..7e1f9ebddb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/georeference/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/data.py @@ -2,12 +2,14 @@ class Data: is_loaded = False map_conversion = {} projected_crs = {} + true_north = {} @classmethod def purge(cls): cls.is_loaded = False cls.map_conversion = {} cls.projected_crs = {} + cls.true_north = None @classmethod def load(cls, file): @@ -15,6 +17,7 @@ class Data: return cls.map_conversion = {} cls.projected_crs = {} + cls.true_north = {} if file.schema == "IFC2X3": return for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): @@ -28,4 +31,9 @@ class Data: if cls.projected_crs["MapUnit"]: cls.projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info() break + for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + if not context.TrueNorth: + continue + cls.true_north = context.TrueNorth.DirectionRatios + break cls.is_loaded = True diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py index aafe61dd83..e4ac84221a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py +++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py @@ -7,6 +7,7 @@ class Usecase: self.settings = { "map_conversion": {}, "projected_crs": {}, + "true_north": [], "map_unit": "", } for key, value in settings.items(): @@ -21,6 +22,7 @@ class Usecase: setattr(projected_crs, name, value) self.remove_existing_map_unit(projected_crs) self.set_map_unit(projected_crs) + self.set_true_north() def remove_existing_map_unit(self, projected_crs): if projected_crs.MapUnit and len(self.file.get_inverse(projected_crs.MapUnit)) == 1: @@ -50,3 +52,20 @@ class Usecase: self.settings["map_unit"], self.file.createIfcMeasureWithUnit(value_component, si_unit), ) + + def set_true_north(self): + if 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: + 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]