diff --git a/src/blenderbim/blenderbim/bim/module/georeference/data.py b/src/blenderbim/blenderbim/bim/module/georeference/data.py new file mode 100644 index 0000000000..2fa8ef89a0 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/georeference/data.py @@ -0,0 +1,112 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2022 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +# ############################################################################ # + +import blenderbim.tool as tool + +def refresh(): + GeoreferenceData.is_loaded = False + +class GeoreferenceData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = { + "map_conversion" : cls.map_conversion(), + "projected_crs" : cls.projected_crs(), + "true_north" : cls.true_north(), + } + cls.is_loaded = True + + @classmethod + def is_georeferenced(cls): + ifc = tool.Ifc.get() + if ifc.schema == "IFC2X3": + return + + map_conversions = [] + for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + if not context.HasCoordinateOperation: + continue + map_conversions.append(context) + + return True if map_conversions else False + + @classmethod + def map_conversion(cls): + ifc = tool.Ifc.get() + map_conversion_v = {} + + if ifc.schema == "IFC2X3": + return + + for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + if not context.HasCoordinateOperation: + continue + + map_conversion_v = context.HasCoordinateOperation[0].get_info() + map_conversion_v["SourceCRS"] = map_conversion_v["SourceCRS"].id() + map_conversion_v["TargetCRS"] = map_conversion_v["TargetCRS"].id() + + break + + return map_conversion_v + + @classmethod + def projected_crs(cls): + ifc = tool.Ifc.get() + projected_crs = {} + + if ifc.schema == "IFC2X3": + return + + for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + if not context.HasCoordinateOperation: + continue + + projected_crs = context.HasCoordinateOperation[0].TargetCRS.get_info() + if projected_crs["MapUnit"]: + projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info() + + if projected_crs["MapUnit"]: + projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info() + break + + return projected_crs + + @classmethod + def true_north(cls): + ifc = tool.Ifc.get() + true_north = {} + + if ifc.schema == "IFC2X3": + return + + for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + if not context.TrueNorth: + continue + true_north = context.TrueNorth.DirectionRatios + break + + return true_north + + + diff --git a/src/blenderbim/blenderbim/core/georeference.py b/src/blenderbim/blenderbim/core/georeference.py new file mode 100644 index 0000000000..4ef8dcf960 --- /dev/null +++ b/src/blenderbim/blenderbim/core/georeference.py @@ -0,0 +1,93 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2022 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import blenderbim.bim.helper +import ifcopenshell + +from blenderbim.bim.module.georeference.data import GeoreferenceData + +def add_georeferencing(ifc): + ifc.run("georeference.add_georeferencing") + +def enable_editing_georeferencing(georeference): #TODO simplify this function + georeference.clear_projected_crs() + + blenderbim.bim.helper.import_attributes( + "IfcProjectedCRS", + bpy.context.scene.BIMGeoreferenceProperties.projected_crs, + GeoreferenceData.data["projected_crs"], + georeference.import_projected_crs_attributes, + ) + + georeference.clear_map_conversion() + + blenderbim.bim.helper.import_attributes( + "IfcMapConversion", + bpy.context.scene.BIMGeoreferenceProperties.map_conversion, + GeoreferenceData.data["map_conversion"], + georeference.import_map_conversion_attributes, + ) + + bpy.context.scene.BIMGeoreferenceProperties.has_true_north = bool(GeoreferenceData.data["true_north"]) + + if GeoreferenceData.data["true_north"]: + bpy.context.scene.BIMGeoreferenceProperties.true_north_abscissa = str(GeoreferenceData.data["true_north"][0]) + bpy.context.scene.BIMGeoreferenceProperties.true_north_ordinate = str(GeoreferenceData.data["true_north"][1]) + + bpy.context.scene.BIMGeoreferenceProperties.is_editing = True + +def remove_georeferencing(ifc): + ifc.run("georeference.remove_georeferencing") + +def edit_georeferencing(ifc, georeference): + props = bpy.context.scene.BIMGeoreferenceProperties + ifc_file = georeference.get_file() + + projected_crs = blenderbim.bim.helper.export_attributes(props.projected_crs, georeference.export_crs_attributes) + map_conversion = blenderbim.bim.helper.export_attributes(props.map_conversion, georeference.export_map_attributes) + + true_north = None +# if props.has_true_north: +# try: +# true_north = [float(props.true_north_abscissa), float(props.true_north_ordinate)] +# except ValueError: +# self.report({"ERROR"}, "True North Abscissa and Ordinate expect a number") + + ifcopenshell.api.run( #TODO use ifc.run + "georeference.edit_georeferencing", + ifc_file, + **{ + "map_conversion": map_conversion, + "projected_crs": projected_crs, + "true_north": true_north, + } + ) + + bpy.ops.bim.disable_editing_georeferencing() + +def disable_editing_georeferencing(georeference): + georeference.set_false_is_editing() + +def set_ifc_grid_north(georeference): + georeference.set_ifc_grid_north() + +def set_blender_grid_north(georeference): + georeference.set_blender_grid_north() + + diff --git a/src/blenderbim/blenderbim/tool/georeference.py b/src/blenderbim/blenderbim/tool/georeference.py new file mode 100644 index 0000000000..16ee96efe0 --- /dev/null +++ b/src/blenderbim/blenderbim/tool/georeference.py @@ -0,0 +1,99 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2022 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + + +import bpy +import json +import blenderbim.core.tool +import blenderbim.tool as tool +import ifcopenshell + +from ifcopenshell.api.unit.data import Data as UnitData + +class Georeference(blenderbim.core.tool.Georeference): + + @classmethod + def clear_projected_crs(cls): + bpy.context.scene.BIMGeoreferenceProperties.projected_crs.clear() + + @classmethod + def clear_map_conversion(cls): + bpy.context.scene.BIMGeoreferenceProperties.map_conversion.clear() + + @classmethod + def get_file(cls): + return tool.Ifc.get() + + @classmethod + def import_projected_crs_attributes(cls, name, prop, data): + if name == "MapUnit": + new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add() + new.name = name + new.data_type = "enum" + new.is_null = data[name] is None + new.is_optional = True + new.enum_items = json.dumps( + {u["id"]: u["Name"] for u in UnitData.units.values() if u["UnitType"] == "LENGTHUNIT"} + ) + if data["MapUnit"]: + new.enum_value = str(data["MapUnit"]["id"]) + return True + + @classmethod + def import_map_conversion_attributes(cls, name, prop, data): + if name not in ["SourceCRS", "TargetCRS"]: + # Enforce a string data type to prevent data loss in single-precision Blender props + prop.data_type = "string" + prop.string_value = "" if prop.is_null else str(data[name]) + return True + + @classmethod + def export_crs_attributes(cls, attributes, prop,): + ifc_file = tool.Ifc.get() + if not prop.is_null and prop.name == "MapUnit": + attributes[prop.name] = ifc_file.by_id(int(prop.enum_value)) + return True + + @classmethod + def export_map_attributes(cls, attributes, prop): + if not prop.is_null and prop.data_type == "string": + # We store our floats as string to prevent single precision data loss + attributes[prop.name] = float(prop.string_value) + return True + + @classmethod + def set_false_is_editing(cls): + props = bpy.context.scene.BIMGeoreferenceProperties + props.is_editing = False + + @classmethod + def set_ifc_grid_north(cls): + x_angle = -bpy.context.scene.sun_pos_properties.north_offset + bpy.context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value = str(cos(x_angle)) + bpy.context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value = str(sin(x_angle)) + + @classmethod + def set_blender_grid_north(cls): + bpy.context.scene.sun_pos_properties.north_offset = -radians( + ifcopenshell.util.geolocation.xaxis2angle( + float(bpy.context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value), + float(bpy.context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value), + ) + ) + +