From ea524767a0c0fc46a5db95be7f074cdf4a9f6f6b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 19 Jan 2021 11:18:26 +1100 Subject: [PATCH] WIP implement georeference module for correctly geolocated files. See #1222. --- .../blenderbim/bim/__init__.py | 12 +- .../blenderbim/bim/import_ifc.py | 59 ----- .../bim/module/georeference/__init__.py | 25 ++ .../module/georeference/add_georeferencing.py | 20 ++ .../bim/module/georeference/data.py | 29 +++ .../georeference/edit_georeferencing.py | 52 ++++ .../bim/module/georeference/operator.py | 242 ++++++++++++++++++ .../bim/module/georeference/prop.py | 37 +++ .../georeference/remove_georeferencing.py | 12 + .../blenderbim/bim/module/georeference/ui.py | 125 +++++++++ .../blenderbim/bim/operator.py | 75 ------ src/ifcblenderexport/blenderbim/bim/prop.py | 23 +- src/ifcblenderexport/blenderbim/bim/ui.py | 47 ---- .../ifcopenshell/util/unit.py | 7 +- 14 files changed, 548 insertions(+), 217 deletions(-) create mode 100644 src/ifcblenderexport/blenderbim/bim/module/georeference/__init__.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/georeference/add_georeferencing.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/georeference/data.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/georeference/edit_georeferencing.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/georeference/operator.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/georeference/prop.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/georeference/remove_georeferencing.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/georeference/ui.py diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 593e0786df..a44c26b854 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -20,6 +20,7 @@ if bpy is not None: "bimtester": None, "debug": None, "geometry": None, + "georeference": None, "material": None, "model": None, "owner": None, @@ -146,8 +147,6 @@ if bpy is not None: operator.AddVariable, operator.RemoveVariable, operator.PropagateTextData, - operator.ConvertLocalToGlobal, - operator.ConvertGlobalToLocal, operator.SelectIfcPatchInput, operator.SelectIfcPatchOutput, operator.ExecuteIfcPatch, @@ -170,8 +169,6 @@ if bpy is not None: operator.BuildSchedule, operator.AddScheduleToSheet, operator.SetViewportShadowFromSun, - operator.SetNorthOffset, - operator.GetNorthOffset, operator.AddPresentationLayer, operator.AssignPresentationLayer, operator.UnassignPresentationLayer, @@ -216,8 +213,6 @@ if bpy is not None: prop.BIMProperties, prop.DocProperties, prop.BIMLibrary, - prop.MapConversion, - prop.TargetCRS, prop.IfcParameter, prop.BoundaryCondition, prop.PsetQto, @@ -242,7 +237,6 @@ if bpy is not None: ui.BIM_PT_search, ui.BIM_PT_ifcclash, ui.BIM_PT_library, - ui.BIM_PT_gis, ui.BIM_PT_presentation_layers, ui.BIM_PT_diff, ui.BIM_PT_patch, @@ -294,8 +288,6 @@ if bpy is not None: bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties) bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties) bpy.types.Scene.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary) - bpy.types.Scene.MapConversion = bpy.props.PointerProperty(type=prop.MapConversion) - bpy.types.Scene.TargetCRS = bpy.props.PointerProperty(type=prop.TargetCRS) bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) # Check if we need this @@ -319,8 +311,6 @@ if bpy is not None: bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) del bpy.types.Scene.BIMProperties del bpy.types.Scene.DocProperties - del bpy.types.Scene.MapConversion - del bpy.types.Scene.TargetCRS del bpy.types.Object.BIMObjectProperties del bpy.types.Material.BIMObjectProperties del bpy.types.Collection.BIMObjectProperties # Check if we need this diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 71b608cc06..7a170880dc 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -371,8 +371,6 @@ class IfcImporter: if self.ifc_import_settings.should_import_native: self.parse_native_elements() self.profile_code("Parsing native elements") - self.create_georeferencing() - self.profile_code("Georeferencing ifc") self.create_groups() self.profile_code("Creating groups") self.create_grids() @@ -645,46 +643,6 @@ class IfcImporter: if element.ObjectPlacement.RelativePlacement.RefDirection: element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1.0, 0.0, 0.0) - def create_georeferencing(self): - try: - map_conversion = self.file.by_type("IfcMapConversion") - projected_crs = self.file.by_type("IfcProjectedCRS") - if not map_conversion or not projected_crs: - return - except: - return # For example, in IFC2X3 - map_conversion = map_conversion[0] - projected_crs = projected_crs[0] - scene = bpy.context.scene - scene.BIMProperties.has_georeferencing = True - map_conversion_map = { - "Eastings": "eastings", - "Northings": "northings", - "OrthogonalHeight": "orthogonal_height", - "XAxisAbscissa": "x_axis_abscissa", - "XAxisOrdinate": "x_axis_ordinate", - "Scale": "scale", - } - target_crs_map = { - "Name": "name", - "Description": "description", - "GeodeticDatum": "geodetic_datum", - "VerticalDatum": "vertical_datum", - "MapProjection": "map_projection", - "MapZone": "map_zone", - "MapUnit": "map_unit", - } - for keyA, keyB in map_conversion_map.items(): - value = getattr(map_conversion, keyA) - if value is not None: - setattr(scene.MapConversion, keyB, str(value)) - for keyA, keyB in target_crs_map.items(): - value = getattr(projected_crs, keyA) - if value is not None: - if keyA == "MapUnit": - value = self.get_unit_name(value) - setattr(scene.TargetCRS, keyB, str(value)) - def get_unit_name(self, named_unit): name = "" if hasattr(named_unit, "Prefix") and named_unit.Prefix: @@ -710,7 +668,6 @@ class IfcImporter: else: obj = bpy.data.objects.new(f"{element.is_a()}/{element.Name}", None) obj.BIMObjectProperties.ifc_definition_id = element.id() - self.add_element_attributes(element, obj.BIMObjectProperties) group_collection.objects.link(obj) self.groups[element.GlobalId] = {"ifc": element, "blender": obj} @@ -748,7 +705,6 @@ class IfcImporter: obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh) obj.BIMObjectProperties.ifc_definition_id = element.id() obj.matrix_world = matrix_world - self.add_element_attributes(axis, obj.BIMObjectProperties) grid.objects.link(obj) def create_type_products(self): @@ -1756,21 +1712,6 @@ class IfcImporter: self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element) bpy.context.scene.collection.objects.link(obj) - def add_element_attributes(self, element, props): - attributes = element.get_info() - for key, value in attributes.items(): - if ( - value is None - or isinstance(value, (tuple, ifcopenshell.entity_instance)) - or key == "id" - or key == "type" - ): - continue - attribute = props.attributes.add() - attribute.name = key - attribute.data_type = "string" - attribute.string_value = str(self.cast_edge_case_attribute(element.is_a(), key, value)) - def cast_edge_case_attribute(self, ifc_class, key, value): if key == "RefLatitude" or key == "RefLongitude": return ifcopenshell.util.geolocation.dms2dd(*value) diff --git a/src/ifcblenderexport/blenderbim/bim/module/georeference/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/georeference/__init__.py new file mode 100644 index 0000000000..f199ff4361 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/georeference/__init__.py @@ -0,0 +1,25 @@ +import bpy +from . import ui, prop, operator + +classes = ( + operator.EnableEditingGeoreferencing, + operator.DisableEditingGeoreferencing, + operator.EditGeoreferencing, + operator.SetNorthOffset, + operator.GetNorthOffset, + operator.RemoveGeoreferencing, + operator.AddGeoreferencing, + operator.ConvertLocalToGlobal, + operator.ConvertGlobalToLocal, + prop.BIMGeoreferenceProperties, + ui.BIM_PT_gis, + ui.BIM_PT_gis_utilities, +) + + +def register(): + bpy.types.Scene.BIMGeoreferenceProperties = bpy.props.PointerProperty(type=prop.BIMGeoreferenceProperties) + + +def unregister(): + del bpy.types.Scene.BIMGeoreferenceProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/georeference/add_georeferencing.py b/src/ifcblenderexport/blenderbim/bim/module/georeference/add_georeferencing.py new file mode 100644 index 0000000000..6979fdca09 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/georeference/add_georeferencing.py @@ -0,0 +1,20 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + + def execute(self): + source_crs = None + for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + if context.ContextType == "Model": + source_crs = context + break + if not source_crs: + return + projected_crs = self.file.create_entity("IfcProjectedCRS", **{"Name": ""}) + self.file.create_entity("IfcMapConversion", **{ + "SourceCRS": source_crs, + "TargetCRS": projected_crs, + "Eastings": 0, + "Northings": 0, + "OrthogonalHeight": 0, + }) diff --git a/src/ifcblenderexport/blenderbim/bim/module/georeference/data.py b/src/ifcblenderexport/blenderbim/bim/module/georeference/data.py new file mode 100644 index 0000000000..b91a2fd212 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/georeference/data.py @@ -0,0 +1,29 @@ +from blenderbim.bim.ifc import IfcStore + + +class Data: + is_loaded = False + map_conversion = {} + projected_crs = {} + + @classmethod + def load(cls): + file = IfcStore.get_file() + if not file: + return + cls.map_conversion = {} + cls.projected_crs = {} + if file.schema == "IFC2X3": + return + for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + if not context.HasCoordinateOperation: + continue + map_conversion = context.HasCoordinateOperation[0] + cls.map_conversion = map_conversion.get_info() + cls.map_conversion["SourceCRS"] = cls.map_conversion["SourceCRS"].id() + cls.map_conversion["TargetCRS"] = cls.map_conversion["TargetCRS"].id() + cls.projected_crs = map_conversion.TargetCRS.get_info() + if cls.projected_crs["MapUnit"]: + cls.projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info() + break + cls.is_loaded = True diff --git a/src/ifcblenderexport/blenderbim/bim/module/georeference/edit_georeferencing.py b/src/ifcblenderexport/blenderbim/bim/module/georeference/edit_georeferencing.py new file mode 100644 index 0000000000..8e21d061f5 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/georeference/edit_georeferencing.py @@ -0,0 +1,52 @@ +import ifcopenshell.util.unit + + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "map_conversion": {}, + "projected_crs": {}, + "map_unit": "", + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + map_conversion = self.file.by_type("IfcMapConversion")[0] + projected_crs = self.file.by_type("IfcProjectedCRS")[0] + for name, value in self.settings["map_conversion"].items(): + setattr(map_conversion, name, value) + for name, value in self.settings["projected_crs"].items(): + setattr(projected_crs, name, value) + self.remove_existing_map_unit(projected_crs) + self.set_map_unit(projected_crs) + + def remove_existing_map_unit(self, projected_crs): + if projected_crs.MapUnit and len(self.file.get_inverse(projected_crs.MapUnit)) == 1: + # TODO: go deeper for conversion units + self.file.remove(projected_crs.MapUnit) + + def set_map_unit(self, projected_crs): + if not self.settings["map_unit"]: + return + + if "METRE" in self.settings["map_unit"]: + projected_crs.MapUnit = self.file.createIfcSIUnit( + None, + "LENGTHUNIT", + ifcopenshell.util.unit.get_prefix(self.settings["map_unit"]), + ifcopenshell.util.unit.get_unit_name(self.settings["map_unit"]), + ) + return + + value_component = self.file.create_entity( + "IfcReal", **{"wrappedValue": ifcopenshell.util.unit.si_conversions[self.settings["map_unit"]]} + ) + si_unit = self.file.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE") + projected_crs.MapUnit = self.file.createIfcConversionBasedUnit( + self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0), + "LENGTHUNIT", + self.settings["map_unit"], + self.file.createIfcMeasureWithUnit(value_component, si_unit), + ) diff --git a/src/ifcblenderexport/blenderbim/bim/module/georeference/operator.py b/src/ifcblenderexport/blenderbim/bim/module/georeference/operator.py new file mode 100644 index 0000000000..d711f03bb4 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/georeference/operator.py @@ -0,0 +1,242 @@ +import bpy +import json +import ifcopenshell +import ifcopenshell.util.unit +import blenderbim.bim.module.georeference.add_georeferencing as add_georeferencing +import blenderbim.bim.module.georeference.edit_georeferencing as edit_georeferencing +import blenderbim.bim.module.georeference.remove_georeferencing as remove_georeferencing +from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.georeference.data import Data +from math import radians, degrees, atan, tan, cos, sin + + +class EnableEditingGeoreferencing(bpy.types.Operator): + bl_idname = "bim.enable_editing_georeferencing" + bl_label = "Enable Editing Georeferencing" + + def execute(self, context): + self.file = IfcStore.get_file() + props = context.scene.BIMGeoreferenceProperties + + while len(props.map_conversion) > 0: + props.map_conversion.remove(0) + + for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes(): + data_type = str(attribute.type_of_attribute) + if "" in data_type: + new.string_value = "" if new.is_null else Data.map_conversion[attribute.name()] + new.data_type = "string" + elif "" in data_type: + new.float_value = 0.0 if new.is_null else Data.map_conversion[attribute.name()] + new.data_type = "float" + elif "" in data_type: + new.int_value = 0 if new.is_null else Data.map_conversion[attribute.name()] + new.data_type = "integer" + elif "" in data_type or "" in data_type: + new.bool_value = False if new.is_null else Data.map_conversion[attribute.name()] + new.data_type = "boolean" + + while len(props.projected_crs) > 0: + props.projected_crs.remove(0) + + for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes(): + data_type = str(attribute.type_of_attribute) + if "" in data_type: + new.string_value = "" if new.is_null else Data.projected_crs[attribute.name()] + new.data_type = "string" + elif "" in data_type: + new.float_value = 0.0 if new.is_null else Data.projected_crs[attribute.name()] + new.data_type = "float" + elif "" in data_type: + new.int_value = 0 if new.is_null else Data.projected_crs[attribute.name()] + new.data_type = "integer" + elif "" in data_type or "" in data_type: + new.bool_value = False if new.is_null else Data.projected_crs[attribute.name()] + new.data_type = "boolean" + + props.is_map_unit_null = Data.projected_crs["MapUnit"] is None + if not props.is_map_unit_null: + props.map_unit_type = Data.projected_crs["MapUnit"]["type"] + if props.map_unit_type == "IfcSIUnit": + prefix = ifcopenshell.util.unit.get_prefix(Data.projected_crs["MapUnit"]["Prefix"]) or "" + name = ifcopenshell.util.unit.get_unit_name(Data.projected_crs["MapUnit"]["Name"]) + props.map_unit_si = prefix + name + elif props.map_unit_type == "IfcConversionBasedUnit": + props.map_unit_imperial = Data.projected_crs["MapUnit"]["Name"] + + props.is_editing = True + return {"FINISHED"} + + +class DisableEditingGeoreferencing(bpy.types.Operator): + bl_idname = "bim.disable_editing_georeferencing" + bl_label = "Disable Editing Georeferencing" + + def execute(self, context): + props = context.scene.BIMGeoreferenceProperties + props.is_editing = False + return {"FINISHED"} + + +class EditGeoreferencing(bpy.types.Operator): + bl_idname = "bim.edit_georeferencing" + bl_label = "Edit Georeferencing" + + def execute(self, context): + self.file = IfcStore.get_file() + props = context.scene.BIMGeoreferenceProperties + + map_conversion = {} + for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes(): + data_type = str(attribute.type_of_attribute) + if "