Code review and tests for georeferencing module refactor

This commit is contained in:
Dion Moult
2022-05-29 23:44:18 +10:00
parent 2d4e316df2
commit 3e1c2c4daf
11 changed files with 904 additions and 440 deletions
@@ -16,77 +16,108 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.util.geolocation
import blenderbim.tool as tool import blenderbim.tool as tool
def refresh(): def refresh():
GeoreferenceData.is_loaded = False GeoreferenceData.is_loaded = False
class GeoreferenceData: class GeoreferenceData:
data = {} data = {}
is_loaded = False is_loaded = False
@classmethod @classmethod
def load(cls): def load(cls):
cls.data = { cls.data["blender_derived_angle"] = cls.blender_derived_angle()
"map_conversion" : cls.map_conversion(), cls.data["map_conversion"] = cls.map_conversion()
"projected_crs" : cls.projected_crs(), cls.data["map_derived_angle"] = cls.map_derived_angle()
"true_north" : cls.true_north(), cls.data["projected_crs"] = cls.projected_crs()
} cls.data["true_north"] = cls.true_north()
cls.data["true_derived_angle"] = cls.true_derived_angle()
cls.is_loaded = True cls.is_loaded = True
@classmethod
def blender_derived_angle(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
return str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
),
3,
)
)
@classmethod @classmethod
def map_conversion(cls): def map_conversion(cls):
ifc = tool.Ifc.get() if tool.Ifc.get_schema() == "IFC2X3":
map_conversion_v = {} return {}
if ifc.schema == "IFC2X3": for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
return if context.HasCoordinateOperation:
map_conversion = context.HasCoordinateOperation[0].get_info()
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): del map_conversion["id"]
if not context.HasCoordinateOperation: del map_conversion["type"]
continue del map_conversion["SourceCRS"]
del map_conversion["TargetCRS"]
map_conversion_v = context.HasCoordinateOperation[0].get_info() return map_conversion
map_conversion_v["SourceCRS"] = map_conversion_v["SourceCRS"].id() return {}
map_conversion_v["TargetCRS"] = map_conversion_v["TargetCRS"].id()
@classmethod
break def map_derived_angle(cls):
if (
return map_conversion_v cls.data["map_conversion"]
and cls.data["map_conversion"]["XAxisAbscissa"] is not None
and cls.data["map_conversion"]["XAxisOrdinate"] is not None
):
return str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
cls.data["map_conversion"]["XAxisAbscissa"], cls.data["map_conversion"]["XAxisOrdinate"]
),
3,
)
)
return ""
@classmethod @classmethod
def projected_crs(cls): def projected_crs(cls):
ifc = tool.Ifc.get() if tool.Ifc.get_schema() == "IFC2X3":
projected_crs = {} return {}
if ifc.schema == "IFC2X3": for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
return if context.HasCoordinateOperation:
projected_crs = context.HasCoordinateOperation[0].TargetCRS.get_info()
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): del projected_crs["id"]
if not context.HasCoordinateOperation: del projected_crs["type"]
continue if projected_crs["MapUnit"]:
unit = projected_crs["MapUnit"]
projected_crs = context.HasCoordinateOperation[0].TargetCRS.get_info() projected_crs["MapUnit"] = (getattr(unit, "Prefix", "") or "") + unit.Name
if projected_crs["MapUnit"]: return projected_crs
projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info() return {}
break
return projected_crs
@classmethod @classmethod
def true_north(cls): def true_north(cls):
ifc = tool.Ifc.get() ifc = tool.Ifc.get()
true_north = {} true_north = {}
if ifc.schema == "IFC2X3": if ifc.schema == "IFC2X3":
return return
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if not context.TrueNorth: if not context.TrueNorth:
continue continue
true_north = context.TrueNorth.DirectionRatios true_north = context.TrueNorth.DirectionRatios
break break
return true_north return true_north
@classmethod
def true_derived_angle(cls):
if cls.data["true_north"]:
return str(round(ifcopenshell.util.geolocation.yaxis2angle(*cls.data["true_north"][0:2]), 3))
@@ -21,6 +21,7 @@ import bpy
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.core.georeference as core import blenderbim.core.georeference as core
class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator): class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_georeferencing" bl_idname = "bim.add_georeferencing"
bl_label = "Add Georeferencing" bl_label = "Add Georeferencing"
@@ -29,34 +30,38 @@ class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
core.add_georeferencing(tool.Ifc) core.add_georeferencing(tool.Ifc)
class EnableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator): class EnableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_georeferencing" bl_idname = "bim.enable_editing_georeferencing"
bl_label = "Enable Editing Georeferencing" bl_label = "Enable Editing Georeferencing"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Enable editing georeferencing" bl_description = "Enable editing georeferencing"
def _execute(self, context): def _execute(self, context):
core.enable_editing_georeferencing(tool.Georeference) core.enable_editing_georeferencing(tool.Georeference)
class RemoveGeoreferencing(bpy.types.Operator, tool.Ifc.Operator): class RemoveGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_georeferencing" bl_idname = "bim.remove_georeferencing"
bl_label = "Remove Georeferencing" bl_label = "Remove Georeferencing"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Remove the georeferencing" bl_description = "Remove the georeferencing"
def _execute(self, context): def _execute(self, context):
core.remove_georeferencing(tool.Ifc) core.remove_georeferencing(tool.Ifc)
class EditGeoreferencing(bpy.types.Operator, tool.Ifc.Operator): class EditGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_georeferencing" bl_idname = "bim.edit_georeferencing"
bl_label = "Edit Georeferencing" bl_label = "Edit Georeferencing"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Edit the georeferencing" bl_description = "Edit the georeferencing"
def _execute(self, context): def _execute(self, context):
core.edit_georeferencing(tool.Ifc, tool.Georeference) core.edit_georeferencing(tool.Ifc, tool.Georeference)
class DisableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator): class DisableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_georeferencing" bl_idname = "bim.disable_editing_georeferencing"
bl_label = "Disable Editing Georeferencing" bl_label = "Disable Editing Georeferencing"
@@ -66,37 +71,41 @@ class DisableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
core.disable_editing_georeferencing(tool.Georeference) core.disable_editing_georeferencing(tool.Georeference)
class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator): class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_grid_north" bl_idname = "bim.set_ifc_grid_north"
bl_label = "Set IFC Grid North" bl_label = "Set IFC Grid North"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Set IFC grid north" bl_description = "Set IFC grid north"
def _execute(self, context): def _execute(self, context):
core.set_ifc_grid_north(tool.Georeference) core.set_ifc_grid_north(tool.Georeference)
class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator): class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_blender_grid_north" bl_idname = "bim.set_blender_grid_north"
bl_label = "Set Blender Grid North" bl_label = "Set Blender Grid North"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Set Blender grid north" bl_description = "Set Blender grid north"
def _execute(self, context): def _execute(self, context):
core.set_blender_grid_north(tool.Georeference) core.set_blender_grid_north(tool.Georeference)
class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator): class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.get_cursor_location" bl_idname = "bim.get_cursor_location"
bl_label = "Get Cursor Location" bl_label = "Get Cursor Location"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Insert the current cursor coordinates" bl_description = "Insert the current cursor coordinates"
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return tool.Ifc.get() return tool.Ifc.get()
def _execute(self, context): def _execute(self, context):
core.get_cursor_location(tool.Georeference) core.get_cursor_location(tool.Georeference)
class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator): class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_cursor_location" bl_idname = "bim.set_cursor_location"
bl_label = "Set Cursor Location" bl_label = "Set Cursor Location"
@@ -105,13 +114,13 @@ class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties props = context.scene.BIMGeoreferenceProperties
return file and file.by_type("IfcUnitAssignment") and props.coordinate_output.count(",") == 2 return tool.Ifc.get() and props.coordinate_output.count(",") == 2
def _execute(self, context): def _execute(self, context):
core.set_cursor_location(tool.Georeference) core.set_cursor_location(tool.Georeference)
class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator): class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_true_north" bl_idname = "bim.set_ifc_true_north"
bl_label = "Set IFC True North" bl_label = "Set IFC True North"
@@ -121,6 +130,7 @@ class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
core.set_ifc_true_north(tool.Georeference) core.set_ifc_true_north(tool.Georeference)
class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator): class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_blender_true_north" bl_idname = "bim.set_blender_true_north"
bl_label = "Set Blender True North" bl_label = "Set Blender True North"
@@ -130,33 +140,34 @@ class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
core.set_blender_true_north(tool.Georeference) core.set_blender_true_north(tool.Georeference)
class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator): class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_local_to_global" bl_idname = "bim.convert_local_to_global"
bl_label = "Convert Local To Global" bl_label = "Convert Local To Global"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert local coordinate to global coordinate" bl_description = "Convert local coordinate to global coordinate"
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
file = tool.Ifc.get() file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties props = context.scene.BIMGeoreferenceProperties
return file and props.coordinate_input.count(",") == 2 return file and props.coordinate_input.count(",") == 2
def _execute(self, context): def _execute(self, context):
core.convert_local_to_global(tool.Georeference) core.convert_local_to_global(tool.Georeference)
class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator): class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_global_to_local" bl_idname = "bim.convert_global_to_local"
bl_label = "Convert Global To Local" bl_label = "Convert Global To Local"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert global coordinate to local coordinate" bl_description = "Convert global coordinate to local coordinate"
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
file = tool.Ifc.get() file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties props = context.scene.BIMGeoreferenceProperties
return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2 return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2
def _execute(self, context): def _execute(self, context):
core.convert_global_to_local(tool.Georeference) core.convert_global_to_local(tool.Georeference)
@@ -16,12 +16,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.geolocation import blenderbim.tool as tool
from bpy.types import Panel from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes, draw_attribute from blenderbim.bim.helper import draw_attributes, draw_attribute
from blenderbim.bim.module.georeference.data import GeoreferenceData from blenderbim.bim.module.georeference.data import GeoreferenceData
class BIM_PT_gis(Panel): class BIM_PT_gis(Panel):
bl_label = "IFC Georeferencing" bl_label = "IFC Georeferencing"
bl_idname = "BIM_PT_gis" bl_idname = "BIM_PT_gis"
@@ -30,15 +30,15 @@ class BIM_PT_gis(Panel):
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_context = "scene" bl_context = "scene"
bl_parent_id = "BIM_PT_geometry" bl_parent_id = "BIM_PT_geometry"
def draw(self, context): def draw(self, context):
self.layout.use_property_split = True self.layout.use_property_split = True
self.layout.use_property_decorate = False self.layout.use_property_decorate = False
props = context.scene.BIMGeoreferenceProperties props = context.scene.BIMGeoreferenceProperties
if not GeoreferenceData.is_loaded: if not GeoreferenceData.is_loaded:
GeoreferenceData.load() GeoreferenceData.load()
if props.is_editing: if props.is_editing:
return self.draw_editable_ui(context) return self.draw_editable_ui(context)
self.draw_ui(context) self.draw_ui(context)
@@ -75,16 +75,15 @@ class BIM_PT_gis(Panel):
row.operator("bim.set_ifc_true_north", text="Set IFC North") row.operator("bim.set_ifc_true_north", text="Set IFC North")
row.operator("bim.set_blender_true_north", text="Set Blender North") row.operator("bim.set_blender_true_north", text="Set Blender North")
def draw_ui(self, context): def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties props = context.scene.BIMGeoreferenceProperties
if not GeoreferenceData.data["projected_crs"]: if not GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Not Georeferenced") row.label(text="Not Georeferenced")
if IfcStore.get_file().schema != "IFC2X3": #TODO Is it correct to use IfcStore.get_file() or is it better tool.Ifc.get()? if tool.Ifc.get_schema != "IFC2X3":
row.operator("bim.add_georeferencing", icon="ADD", text="") row.operator("bim.add_georeferencing", icon="ADD", text="")
if props.has_blender_offset: if props.has_blender_offset:
row = self.layout.row() row = self.layout.row()
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS") row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
@@ -106,16 +105,7 @@ class BIM_PT_gis(Panel):
row.label(text=props.blender_x_axis_ordinate) row.label(text=props.blender_x_axis_ordinate)
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Derived Grid North") row.label(text="Derived Grid North")
row.label( row.label(text=GeoreferenceData.data["blender_derived_angle"])
text=str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
),
3,
)
)
)
if GeoreferenceData.data["projected_crs"]: if GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -124,12 +114,8 @@ class BIM_PT_gis(Panel):
row.operator("bim.remove_georeferencing", icon="X", text="") row.operator("bim.remove_georeferencing", icon="X", text="")
for key, value in GeoreferenceData.data["projected_crs"].items(): for key, value in GeoreferenceData.data["projected_crs"].items():
if key == "id" or key == "type" or not value: if not value:
continue continue
if key == "MapUnit":
unit_value = value.get("Prefix", "") or ""
unit_value += value["Name"]
value = unit_value
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=key) row.label(text=key)
row.label(text=str(value)) row.label(text=str(value))
@@ -139,7 +125,7 @@ class BIM_PT_gis(Panel):
row.label(text="Map Conversion", icon="GRID") row.label(text="Map Conversion", icon="GRID")
for key, value in GeoreferenceData.data["map_conversion"].items(): for key, value in GeoreferenceData.data["map_conversion"].items():
if key == "id" or key == "type" or key == "SourceCRS" or key == "TargetCRS" or value is None: if value is None:
continue continue
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=key) row.label(text=key)
@@ -147,16 +133,7 @@ class BIM_PT_gis(Panel):
if key == "XAxisOrdinate": if key == "XAxisOrdinate":
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Derived Angle") row.label(text="Derived Angle")
row.label( row.label(text=GeoreferenceData.data["map_derived_angle"])
text=str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
GeoreferenceData.data["map_conversion"]["XAxisAbscissa"], GeoreferenceData.data["map_conversion"]["XAxisOrdinate"]
),
3,
)
)
)
if GeoreferenceData.data["true_north"]: if GeoreferenceData.data["true_north"]:
row = self.layout.row() row = self.layout.row()
@@ -166,7 +143,7 @@ class BIM_PT_gis(Panel):
row.label(text=str(GeoreferenceData.data["true_north"][0:2])[1:-1]) row.label(text=str(GeoreferenceData.data["true_north"][0:2])[1:-1])
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Derived Angle") row.label(text="Derived Angle")
row.label(text=str(round(ifcopenshell.util.geolocation.yaxis2angle(*GeoreferenceData.data["true_north"][0:2]), 3))) row.label(text=GeoreferenceData.data["true_derived_angle"])
class BIM_PT_gis_utilities(Panel): class BIM_PT_gis_utilities(Panel):
@@ -178,9 +155,6 @@ class BIM_PT_gis_utilities(Panel):
bl_category = "BlenderBIM" bl_category = "BlenderBIM"
def draw(self, context): def draw(self, context):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties props = context.scene.BIMGeoreferenceProperties
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -193,4 +167,3 @@ class BIM_PT_gis_utilities(Panel):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.operator("bim.convert_local_to_global", text="Local to Global") row.operator("bim.convert_local_to_global", text="Local to Global")
row.operator("bim.convert_global_to_local", text="Global to Local") row.operator("bim.convert_global_to_local", text="Global to Local")
+43 -39
View File
@@ -16,63 +16,67 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
def add_georeferencing(ifc): def add_georeferencing(ifc):
ifc.run("georeference.add_georeferencing") ifc.run("georeference.add_georeferencing")
def enable_editing_georeferencing(georeference): def enable_editing_georeferencing(georeference):
georeference.clear_projected_crs()
georeference.import_projected_crs() georeference.import_projected_crs()
georeference.clear_map_conversion()
georeference.import_map_conversion() georeference.import_map_conversion()
georeference.set_has_true_north_prop() georeference.import_true_north()
georeference.set_true_north_props()
georeference.enable_editing() georeference.enable_editing()
def remove_georeferencing(ifc): def remove_georeferencing(ifc):
ifc.run("georeference.remove_georeferencing") ifc.run("georeference.remove_georeferencing")
def disable_editing_georeferencing(georeference): def disable_editing_georeferencing(georeference):
georeference.disable_editing() georeference.disable_editing()
def edit_georeferencing(ifc, georeference):
projected_crs = georeference.get_projected_crs_attributes()
map_conversion = georeference.get_map_conversion_attributes()
true_north = georeference.get_true_north_attributes()
georeference.edit_georeferencing(projected_crs, map_conversion, true_north)
georeference.disable_editing()
def set_ifc_grid_north(georeference):
x_angle = georeference.get_x_angle_from_sun_position()
georeference.set_xaxis_vector_from_angle(x_angle)
def set_blender_grid_north(georeference):
angle = georeference.get_angle_from_xaxis()
georeference.set_sun_pos_north_offset(angle)
def get_cursor_location(georeference):
georeference.get_cursor_location() def edit_georeferencing(ifc, georeference):
ifc.run(
def set_cursor_location(georeference): "georeference.edit_georeferencing",
scale = georeference.get_scale() projected_crs=georeference.get_projected_crs_attributes(),
coordinates = georeference.get_coordinates_from_coordinate_output_prop() map_conversion=georeference.get_map_conversion_attributes(),
georeference.set_cursor_location(coordinates, scale) true_north=georeference.get_true_north_attributes(),
)
georeference.disable_editing()
def set_ifc_grid_north(georeference):
georeference.set_ifc_grid_north()
def set_blender_grid_north(georeference):
georeference.set_blender_grid_north()
def set_ifc_true_north(georeference): def set_ifc_true_north(georeference):
georeference.set_ifc_true_north() georeference.set_ifc_true_north()
def set_blender_true_north(georeference): def set_blender_true_north(georeference):
georeference.set_blender_true_north() georeference.set_blender_true_north()
def convert_local_to_global(georeference):
map_conversion = georeference.get_map_conversion()
coordinates = georeference.get_easting_northing_height_from_xyz(map_conversion)
georeference.set_coordinate_output_prop(coordinates)
georeference.set_cursor_location(coordinates, scale = 1)
def convert_global_to_local(georeference):
map_conversion = georeference.get_map_conversion()
coordinates = georeference.get_xyz_from_easting_northig_height(map_conversion)
georeference.set_coordinate_output_prop(coordinates)
scale = georeference.get_scale()
georeference.set_cursor_location(coordinates, scale)
def get_cursor_location(georeference):
georeference.set_coordinates("input", georeference.get_cursor_location())
def set_cursor_location(georeference):
georeference.set_cursor_location(georeference.get_coordinates("output"))
def convert_local_to_global(georeference):
coordinates = georeference.xyz2enh(georeference.get_coordinates("input"), georeference.get_map_conversion())
georeference.set_coordinates("output", coordinates)
georeference.set_cursor_location(coordinates)
def convert_global_to_local(georeference):
coordinates = georeference.enh2xyz(georeference.get_coordinates("input"), georeference.get_map_conversion())
georeference.set_coordinates("output", coordinates)
georeference.set_cursor_location(coordinates)
+19 -31
View File
@@ -247,41 +247,29 @@ class Geometry:
def should_generate_uvs(cls, obj): pass def should_generate_uvs(cls, obj): pass
def should_use_presentation_style_assignment(cls): pass def should_use_presentation_style_assignment(cls): pass
@interface @interface
class Georeference: class Georeference:
def clear_projected_crs(cls): pass
def clear_map_conversion(cls): pass
def import_projected_crs(cls): pass
def import_map_conversion(cls): pass
def set_has_true_north_prop(cls): pass
def set_true_north_props(cls): pass
def enable_editing(cls): pass
def disable_editing(cls): pass def disable_editing(cls): pass
def get_projected_crs_attributes(cls): pass def enable_editing(cls): pass
def get_map_conversion_attributes(cls): pass def enh2xyz(cls, map_conversion, coordinates): pass
def get_true_north_attributes(cls): pass def get_coordinates(cls, io): pass
def edit_georeferencing(cls, projected_crs, map_conversion, true_north): pass
def get_file(cls): pass
def import_projected_crs_attributes(cls, name, prop, data): pass
def import_map_conversion_attributes(cls, name, prop, data): pass
def export_crs_attributes(cls, attributes, prop): pass
def export_map_attributes(cls, attributes, prop): pass
def set_ifc_grid_north(cls): pass
def set_blender_grid_north(cls): pass
def get_cursor_location(cls): pass def get_cursor_location(cls): pass
def set_cursor_location(cls, coordinates, scale): pass
def get_scale(cls): pass
def get_coordinates_from_coordinate_output_prop(cls): pass
def set_ifc_true_north(cls): pass
def set_blender_true_north(cls): pass
def get_map_conversion(cls): pass def get_map_conversion(cls): pass
def get_easting_northing_height_from_xyz(cls, map_conversion): pass def get_map_conversion_attributes(cls): pass
def get_xyz_from_easting_northig_height(cls, map_conversion): pass def get_projected_crs_attributes(cls): pass
def set_coordinate_output_prop(cls, coordinates): pass def get_true_north_attributes(cls): pass
def get_x_angle_from_sun_position(cls): pass def import_map_conversion(cls): pass
def set_xaxis_vector_from_angle(cls, x_angle): pass def import_projected_crs(cls): pass
def get_angle_from_xaxis(cls): pass def import_true_north(cls): pass
def set_sun_pos_north_offset(cls, angle): pass def set_blender_grid_north(cls): pass
def set_blender_true_north(cls): pass
def set_coordinates(cls, io, coordinates): pass
def set_cursor_location(cls, coordinates): pass
def set_ifc_grid_north(cls): pass
def set_ifc_true_north(cls): pass
def xyz2enh(cls, map_conversion, coordinates): pass
@interface @interface
class Ifc: class Ifc:
@@ -543,10 +531,10 @@ class Unit:
def get_scene_unit_name(cls, unit_type): pass def get_scene_unit_name(cls, unit_type): pass
def get_scene_unit_si_prefix(cls): pass def get_scene_unit_si_prefix(cls): pass
def get_si_name_from_unit_type(cls, unit_type): pass def get_si_name_from_unit_type(cls, unit_type): pass
def get_unit_class(cls, unit): pass
def import_unit_attributes(cls, unit): pass def import_unit_attributes(cls, unit): pass
def import_units(cls): pass def import_units(cls): pass
def is_scene_unit_metric(cls): pass def is_scene_unit_metric(cls): pass
def is_unit_class(cls, unit, ifc_class): pass
def set_active_unit(cls, unit): pass def set_active_unit(cls, unit): pass
+159 -263
View File
@@ -22,210 +22,142 @@ import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
import ifcopenshell import ifcopenshell
import blenderbim.bim.helper import blenderbim.bim.helper
from ifcopenshell.api.unit.data import Data as UnitData
from math import radians, degrees, atan, tan, cos, sin from math import radians, degrees, atan, tan, cos, sin
class Georeference(blenderbim.core.tool.Georeference): 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 @classmethod
def import_projected_crs(cls): def import_projected_crs(cls):
ifc = tool.Ifc.get() def callback(name, prop, data):
projected_crs = {} if name == "MapUnit":
new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add()
if ifc.schema == "IFC2X3": new.name = name
return new.data_type = "enum"
new.is_null = data[name] is None
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): new.is_optional = True
if not context.HasCoordinateOperation: new.enum_items = json.dumps(
continue {
u.id(): (getattr(u, "Prefix", "") or "") + u.Name
projected_crs = context.HasCoordinateOperation[0].TargetCRS.get_info() for u in tool.Ifc.get().by_type("IfcNamedUnit")
if projected_crs["MapUnit"]: if u.UnitType == "LENGTHUNIT"
projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info() }
)
if data["MapUnit"]:
new.enum_value = str(data["MapUnit"].id())
return True
break
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
blenderbim.bim.helper.import_attributes( props.projected_crs.clear()
"IfcProjectedCRS",
props.projected_crs, if tool.Ifc.get_schema() == "IFC2X3":
projected_crs, return
cls.import_projected_crs_attributes,
) for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation:
projected_crs = context.HasCoordinateOperation[0].TargetCRS
blenderbim.bim.helper.import_attributes2(projected_crs, props.projected_crs, callback=callback)
return
@classmethod @classmethod
def import_map_conversion(cls): def import_map_conversion(cls):
ifc = tool.Ifc.get() def callback(name, prop, data):
map_conversion_v = {} if name not in ["SourceCRS", "TargetCRS"]:
if ifc.schema == "IFC2X3": # Enforce a string data type to prevent data loss in single-precision Blender props
return prop.data_type = "string"
prop.string_value = "" if prop.is_null else str(data[name])
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): return True
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
props = bpy.context.scene.BIMGeoreferenceProperties
blenderbim.bim.helper.import_attributes(
"IfcMapConversion",
props.map_conversion,
map_conversion_v,
cls.import_map_conversion_attributes,
)
@classmethod
def set_has_true_north_prop(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
bpy.context.scene.BIMGeoreferenceProperties.has_true_north = bool(true_north) props = bpy.context.scene.BIMGeoreferenceProperties
props.map_conversion.clear()
@classmethod
def set_true_north_props(cls): if tool.Ifc.get_schema() == "IFC2X3":
ifc = tool.Ifc.get()
true_north = {}
if ifc.schema == "IFC2X3":
return return
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if not context.TrueNorth: if context.HasCoordinateOperation:
continue map_conversion = context.HasCoordinateOperation[0]
true_north = context.TrueNorth.DirectionRatios blenderbim.bim.helper.import_attributes2(map_conversion, props.map_conversion, callback=callback)
break return
if true_north: @classmethod
bpy.context.scene.BIMGeoreferenceProperties.true_north_abscissa = str(true_north[0]) def import_true_north(cls):
bpy.context.scene.BIMGeoreferenceProperties.true_north_ordinate = str(true_north[1]) if tool.Ifc.get_schema() == "IFC2X3":
return
props = bpy.context.scene.BIMGeoreferenceProperties
props.has_true_north = False
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.TrueNorth:
true_north = context.TrueNorth.DirectionRatios
props.true_north_abscissa = str(true_north[0])
props.true_north_ordinate = str(true_north[1])
props.has_true_north = True
return
@classmethod @classmethod
def get_projected_crs_attributes(cls): def get_projected_crs_attributes(cls):
def callback(attributes, prop):
if not prop.is_null and prop.name == "MapUnit":
attributes[prop.name] = tool.Ifc.get().by_id(int(prop.enum_value))
return True
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
projected_crs = blenderbim.bim.helper.export_attributes(props.projected_crs, cls.export_crs_attributes) return blenderbim.bim.helper.export_attributes(props.projected_crs, callback=callback)
return projected_crs
@classmethod @classmethod
def get_map_conversion_attributes(cls): def get_map_conversion_attributes(cls):
def callback(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
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
map_conversion = blenderbim.bim.helper.export_attributes(props.map_conversion, cls.export_map_attributes) return blenderbim.bim.helper.export_attributes(props.map_conversion, callback=callback)
return map_conversion
@classmethod @classmethod
def get_true_north_attributes(cls): def get_true_north_attributes(cls):
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
true_north = None
if props.has_true_north: if props.has_true_north:
try: try:
true_north = [float(props.true_north_abscissa), float(props.true_north_ordinate)] return [float(props.true_north_abscissa), float(props.true_north_ordinate)]
except ValueError: except ValueError:
print("ERROR, True North Abscissa and Ordinate expect a number") print("ERROR, True North Abscissa and Ordinate expect a number")
#self.report({"ERROR"}, "True North Abscissa and Ordinate expect a number") # self.report({"ERROR"}, "True North Abscissa and Ordinate expect a number")
return true_north
@classmethod @classmethod
def enable_editing(cls): def enable_editing(cls):
bpy.context.scene.BIMGeoreferenceProperties.is_editing = True bpy.context.scene.BIMGeoreferenceProperties.is_editing = True
@classmethod @classmethod
def disable_editing(cls): def disable_editing(cls):
bpy.context.scene.BIMGeoreferenceProperties.is_editing = False bpy.context.scene.BIMGeoreferenceProperties.is_editing = False
@classmethod @classmethod
def edit_georeferencing(cls, projected_crs, map_conversion, true_north): def set_coordinates(cls, io, coordinates):
tool.Ifc.run( if io == "input":
"georeference.edit_georeferencing", bpy.context.scene.BIMGeoreferenceProperties.coordinate_input = ",".join([str(o) for o in coordinates])
**{ elif io == "output":
"projected_crs": projected_crs, bpy.context.scene.BIMGeoreferenceProperties.coordinate_output = ",".join([str(o) for o in coordinates])
"map_conversion": map_conversion,
"true_north": true_north,
}
)
@classmethod @classmethod
def get_file(cls): def get_coordinates(cls, io):
return tool.Ifc.get() if io == "input":
return [float(co) for co in bpy.context.scene.BIMGeoreferenceProperties.coordinate_input.split(",")]
@classmethod elif io == "output":
def import_projected_crs_attributes(cls, name, prop, data): return [float(co) for co in bpy.context.scene.BIMGeoreferenceProperties.coordinate_output.split(",")]
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 @classmethod
def get_cursor_location(cls): def get_cursor_location(cls):
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
project_coordinates = [o / scale for o in bpy.context.scene.cursor.location] return [o / scale for o in bpy.context.scene.cursor.location]
props.coordinate_input = ",".join([str(o) for o in project_coordinates])
@classmethod @classmethod
def set_cursor_location(cls, coordinates, scale): def set_cursor_location(cls, coordinates):
bpy.context.scene.cursor.location = coordinates * scale scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
bpy.context.scene.cursor.location = [co * scale for co in coordinates]
@classmethod
def get_scale(cls):
return ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@classmethod
def get_coordinates_from_coordinate_output_prop(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
coordinates = [float(co) for co in props.coordinate_output.split(",")]
return coordinates
@classmethod @classmethod
def set_ifc_true_north(cls): def set_ifc_true_north(cls):
y_angle = -bpy.context.scene.sun_pos_properties.north_offset + radians(90) y_angle = -bpy.context.scene.sun_pos_properties.north_offset + radians(90)
@@ -236,41 +168,27 @@ class Georeference(blenderbim.core.tool.Georeference):
def set_blender_true_north(cls): def set_blender_true_north(cls):
bpy.context.scene.sun_pos_properties.north_offset = -radians( bpy.context.scene.sun_pos_properties.north_offset = -radians(
ifcopenshell.util.geolocation.yaxis2angle( ifcopenshell.util.geolocation.yaxis2angle(
float(context.scene.BIMGeoreferenceProperties.true_north_abscissa), float(bpy.context.scene.BIMGeoreferenceProperties.true_north_abscissa),
float(context.scene.BIMGeoreferenceProperties.true_north_ordinate), float(bpy.context.scene.BIMGeoreferenceProperties.true_north_ordinate),
) )
) )
@classmethod @classmethod
def get_map_conversion(cls): def get_map_conversion(cls):
ifc = tool.Ifc.get() if tool.Ifc.get_schema() == "IFC2X3":
map_conversion = {}
if ifc.schema == "IFC2X3":
return return
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False): if context.HasCoordinateOperation:
if not context.HasCoordinateOperation: return context.HasCoordinateOperation[0]
continue
map_conversion = context.HasCoordinateOperation[0].get_info()
map_conversion["SourceCRS"] = map_conversion["SourceCRS"].id()
map_conversion["TargetCRS"] = map_conversion["TargetCRS"].id()
break
return map_conversion
@classmethod
def get_easting_northing_height_from_xyz(cls, map_conversion):
props = bpy.context.scene.BIMGeoreferenceProperties
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
@classmethod
def xyz2enh(cls, coordinates, map_conversion):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset: if props.has_blender_offset:
results = ifcopenshell.util.geolocation.xyz2enh( coordinates = ifcopenshell.util.geolocation.xyz2enh(
x, coordinates[0],
y, coordinates[1],
z, coordinates[2],
float(props.blender_eastings), float(props.blender_eastings),
float(props.blender_northings), float(props.blender_northings),
float(props.blender_orthogonal_height), float(props.blender_orthogonal_height),
@@ -278,52 +196,41 @@ class Georeference(blenderbim.core.tool.Georeference):
float(props.blender_x_axis_ordinate), float(props.blender_x_axis_ordinate),
1.0, 1.0,
) )
x, y, z = results
# TODO: what if the project CRS units and the project units are different? # TODO: what if the project CRS units and the project units are different?
if map_conversion: #TODO return error when there is the map conversiona but no Xaxis, scale, etc..
results = ifcopenshell.util.geolocation.xyz2enh(
x,
y,
z,
map_conversion["Eastings"],
map_conversion["Northings"],
map_conversion["OrthogonalHeight"],
map_conversion.get("XAxisAbscissa", 1.0),
map_conversion.get("XAxisOrdinate", 0.0),
map_conversion.get("Scale", 1.0),
)
else:
results = (x, y, z)
return results
@classmethod
def get_xyz_from_easting_northig_height(cls, map_conversion):
props = bpy.context.scene.BIMGeoreferenceProperties
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
if map_conversion: if map_conversion:
results = ifcopenshell.util.geolocation.enh2xyz( coordinates = ifcopenshell.util.geolocation.xyz2enh(
x, coordinates[0],
y, coordinates[1],
z, coordinates[2],
map_conversion["Eastings"], map_conversion.Eastings,
map_conversion["Northings"], map_conversion.Northings,
map_conversion["OrthogonalHeight"], map_conversion.OrthogonalHeight,
map_conversion.get("XAxisAbscissa", 1.0), map_conversion.XAxisAbscissa or 1.0,
map_conversion.get("XAxisOrdinate", 0.0), map_conversion.XAxisOrdinate or 0.0,
map_conversion.get("Scale", 1.0), map_conversion.Scale or 1.0,
) )
else: return coordinates
results = (x, y, z)
@classmethod
def enh2xyz(cls, coordinates, map_conversion):
props = bpy.context.scene.BIMGeoreferenceProperties
if map_conversion:
coordinates = ifcopenshell.util.geolocation.enh2xyz(
coordinates[0],
coordinates[1],
coordinates[2],
map_conversion.Eastings,
map_conversion.Northings,
map_conversion.OrthogonalHeight,
map_conversion.XAxisAbscissa or 1.0,
map_conversion.XAxisOrdinate or 0.0,
map_conversion.Scale or 1.0,
)
if props.has_blender_offset: if props.has_blender_offset:
results = ifcopenshell.util.geolocation.enh2xyz( coordinates = ifcopenshell.util.geolocation.enh2xyz(
results[0], coordinates[0],
results[1], coordinates[1],
results[2], coordinates[2],
float(props.blender_eastings), float(props.blender_eastings),
float(props.blender_northings), float(props.blender_northings),
float(props.blender_orthogonal_height), float(props.blender_orthogonal_height),
@@ -331,31 +238,20 @@ class Georeference(blenderbim.core.tool.Georeference):
float(props.blender_x_axis_ordinate), float(props.blender_x_axis_ordinate),
1.0, 1.0,
) )
return coordinates
return results
@classmethod
def set_coordinate_output_prop(cls, coordinates):
props = bpy.context.scene.BIMGeoreferenceProperties
props.coordinate_output = ",".join([str(r) for r in coordinates])
@classmethod
def get_x_angle_from_sun_position(cls):
return -bpy.context.scene.sun_pos_properties.north_offset
@classmethod
def set_xaxis_vector_from_angle(cls, x_angle):
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 get_angle_from_xaxis(cls):
return 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),
)
@classmethod
def set_sun_pos_north_offset(cls, angle):
bpy.context.scene.sun_pos_properties.north_offset = -radians(angle)
@classmethod
def set_ifc_grid_north(cls):
x_angle = -bpy.context.scene.sun_pos_properties.north_offset
props = bpy.context.scene.BIMGeoreferenceProperties
props.map_conversion.get("XAxisAbscissa").string_value = str(cos(x_angle))
props.map_conversion.get("XAxisOrdinate").string_value = str(sin(x_angle))
@classmethod
def set_blender_grid_north(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
angle = ifcopenshell.util.geolocation.xaxis2angle(
float(props.map_conversion.get("XAxisAbscissa").string_value),
float(props.map_conversion.get("XAxisOrdinate").string_value),
)
bpy.context.scene.sun_pos_properties.north_offset = -radians(angle)
+1
View File
@@ -10,6 +10,7 @@ markers =
document document
drawing drawing
geometry geometry
georeference
library library
material material
misc misc
@@ -0,0 +1,82 @@
@georeference
Feature: Georeference
Scenario: Add georeferencing
Given an empty IFC project
When I press "bim.add_georeferencing"
Then nothing happens
Scenario: Enable editing georeferencing
Given an empty IFC project
And I press "bim.add_georeferencing"
When I press "bim.enable_editing_georeferencing"
Then nothing happens
Scenario: Remove georeferencing
Given an empty IFC project
And I press "bim.add_georeferencing"
When I press "bim.remove_georeferencing"
Then nothing happens
Scenario: Edit georeferencing
Given an empty IFC project
And I press "bim.add_georeferencing"
And I press "bim.enable_editing_georeferencing"
When I press "bim.edit_georeferencing"
Then nothing happens
Scenario: Disable editing georeferencing
Given an empty IFC project
And I press "bim.add_georeferencing"
And I press "bim.enable_editing_georeferencing"
When I press "bim.disable_editing_georeferencing"
Then nothing happens
Scenario: Set IFC grid north
Given an empty IFC project
And I press "bim.add_georeferencing"
And I press "bim.enable_editing_georeferencing"
When I press "bim.set_ifc_grid_north"
Then nothing happens
Scenario: Set Blender grid north
Given an empty IFC project
And I press "bim.add_georeferencing"
And I press "bim.enable_editing_georeferencing"
And I press "bim.set_ifc_grid_north"
When I press "bim.set_blender_grid_north"
Then nothing happens
Scenario: Get cursor location
Given an empty IFC project
When I press "bim.get_cursor_location"
Then nothing happens
Scenario: Set cursor location
Given an empty IFC project
When I set "scene.BIMGeoreferenceProperties.coordinate_output" to "0,0,0"
And I press "bim.set_cursor_location"
Then nothing happens
Scenario: Set IFC true north
Given an empty IFC project
When I press "bim.set_ifc_true_north"
Then nothing happens
Scenario: Set Blender true north
Given an empty IFC project
And I press "bim.set_ifc_true_north"
When I press "bim.set_blender_true_north"
Then nothing happens
Scenario: Convert local to global
Given an empty IFC project
When I set "scene.BIMGeoreferenceProperties.coordinate_input" to "0,0,0"
And I press "bim.convert_local_to_global"
Then "scene.BIMGeoreferenceProperties.coordinate_output" is "0.0,0.0,0.0"
Scenario: Convert global to local
Given an empty IFC project
When I set "scene.BIMGeoreferenceProperties.coordinate_input" to "0,0,0"
And I press "bim.convert_global_to_local"
Then "scene.BIMGeoreferenceProperties.coordinate_output" is "0.0,0.0,0.0"
+7
View File
@@ -98,6 +98,13 @@ def geometry():
prophet.verify() prophet.verify()
@pytest.fixture
def georeference():
prophet = Prophecy(blenderbim.core.tool.Georeference)
yield prophet
prophet.verify()
@pytest.fixture @pytest.fixture
def library(): def library():
prophet = Prophecy(blenderbim.core.tool.Library) prophet = Prophecy(blenderbim.core.tool.Library)
@@ -0,0 +1,114 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import blenderbim.core.georeference as subject
from test.core.bootstrap import ifc, georeference
class TestAddGeoreferencing:
def test_run(self, ifc):
ifc.run("georeference.add_georeferencing").should_be_called()
subject.add_georeferencing(ifc)
class TestEnableGeoreferencing:
def test_run(self, georeference):
georeference.import_projected_crs().should_be_called()
georeference.import_map_conversion().should_be_called()
georeference.import_true_north().should_be_called()
georeference.enable_editing().should_be_called()
subject.enable_editing_georeferencing(georeference)
class TestRemoveGeoreferencing:
def test_run(self, ifc):
ifc.run("georeference.remove_georeferencing").should_be_called()
subject.remove_georeferencing(ifc)
class TestEditGeoreferencing:
def test_run(self, ifc, georeference):
georeference.get_projected_crs_attributes().should_be_called().will_return("projected_crs_attributes")
georeference.get_map_conversion_attributes().should_be_called().will_return("map_conversion_attributes")
georeference.get_true_north_attributes().should_be_called().will_return("true_north_attributes")
ifc.run(
"georeference.edit_georeferencing",
projected_crs="projected_crs_attributes",
map_conversion="map_conversion_attributes",
true_north="true_north_attributes",
).should_be_called()
georeference.disable_editing().should_be_called()
subject.edit_georeferencing(ifc, georeference)
class TestSetIfcGridNorth:
def test_run(self, georeference):
georeference.set_ifc_grid_north().should_be_called()
subject.set_ifc_grid_north(georeference)
class TestSetBlenderGridNorth:
def test_run(self, georeference):
georeference.set_blender_grid_north().should_be_called()
subject.set_blender_grid_north(georeference)
class TestSetIfcTrueNorth:
def test_run(self, georeference):
georeference.set_ifc_true_north().should_be_called()
subject.set_ifc_true_north(georeference)
class TestSetBlenderTrueNorth:
def test_run(self, georeference):
georeference.set_blender_true_north().should_be_called()
subject.set_blender_true_north(georeference)
class TestGetCursorLocation:
def test_run(self, georeference):
georeference.get_cursor_location().should_be_called().will_return("coordinates")
georeference.set_coordinates("input", "coordinates").should_be_called()
subject.get_cursor_location(georeference)
class TestSetCursorLocation:
def test_run(self, georeference):
georeference.get_coordinates("output").should_be_called().will_return("coordinates")
georeference.set_cursor_location("coordinates").should_be_called()
subject.set_cursor_location(georeference)
class TestConvertLocalToGlobal:
def test_run(self, georeference):
georeference.get_coordinates("input").should_be_called().will_return("coordinates")
georeference.get_map_conversion().should_be_called().will_return("map_conversion")
georeference.xyz2enh("coordinates", "map_conversion").should_be_called().will_return("enh")
georeference.set_coordinates("output", "enh").should_be_called()
georeference.set_cursor_location("enh").should_be_called()
subject.convert_local_to_global(georeference)
class TestConvertGlobalToLocal:
def test_run(self, georeference):
georeference.get_coordinates("input").should_be_called().will_return("coordinates")
georeference.get_map_conversion().should_be_called().will_return("map_conversion")
georeference.enh2xyz("coordinates", "map_conversion").should_be_called().will_return("xyz")
georeference.set_coordinates("output", "xyz").should_be_called()
georeference.set_cursor_location("xyz").should_be_called()
subject.convert_global_to_local(georeference)
@@ -0,0 +1,357 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import bpy
import math
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
from mathutils import Vector
from test.bim.bootstrap import NewFile
from blenderbim.tool.georeference import Georeference as subject
class TestImplementsTool(NewFile):
def test_run(self):
assert isinstance(subject(), blenderbim.core.tool.Georeference)
class TestImportProjectedCRS(NewFile):
def test_importing_nothing_with_no_georeferencing(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
subject.import_projected_crs()
props = bpy.context.scene.BIMGeoreferenceProperties
assert len(props.projected_crs) == 0
def test_importing_projected_crs(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
projected_crs = ifc.by_type("IfcProjectedCRS")[0]
projected_crs.Name = "Name"
projected_crs.Description = "Description"
projected_crs.GeodeticDatum = "GeodeticDatum"
projected_crs.VerticalDatum = "VerticalDatum"
projected_crs.MapProjection = "MapProjection"
projected_crs.MapZone = "MapZone"
unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT", name="METRE")
projected_crs.MapUnit = unit
subject.import_projected_crs()
props = bpy.context.scene.BIMGeoreferenceProperties
assert props.projected_crs.get("Name").string_value == "Name"
assert props.projected_crs.get("Description").string_value == "Description"
assert props.projected_crs.get("GeodeticDatum").string_value == "GeodeticDatum"
assert props.projected_crs.get("VerticalDatum").string_value == "VerticalDatum"
assert props.projected_crs.get("MapProjection").string_value == "MapProjection"
assert props.projected_crs.get("MapZone").string_value == "MapZone"
assert props.projected_crs.get("MapUnit").enum_value == str(unit.id())
def test_run_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
subject.import_projected_crs()
props = bpy.context.scene.BIMGeoreferenceProperties
assert len(props.projected_crs) == 0
class TestImportMapConversion(NewFile):
def test_importing_nothing_with_no_georeferencing(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
subject.import_map_conversion()
props = bpy.context.scene.BIMGeoreferenceProperties
assert len(props.map_conversion) == 0
def test_importing_map_conversion(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
map_conversion = ifc.by_type("IfcMapConversion")[0]
map_conversion.Eastings = 1
map_conversion.Northings = 2
map_conversion.OrthogonalHeight = 3
map_conversion.XAxisAbscissa = 4
map_conversion.XAxisOrdinate = 5
map_conversion.Scale = 6
subject.import_map_conversion()
props = bpy.context.scene.BIMGeoreferenceProperties
assert props.map_conversion.get("Eastings").string_value == "1.0"
assert props.map_conversion.get("Northings").string_value == "2.0"
assert props.map_conversion.get("OrthogonalHeight").string_value == "3.0"
assert props.map_conversion.get("XAxisAbscissa").string_value == "4.0"
assert props.map_conversion.get("XAxisOrdinate").string_value == "5.0"
assert props.map_conversion.get("Scale").string_value == "6.0"
def test_run_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
subject.import_map_conversion()
props = bpy.context.scene.BIMGeoreferenceProperties
assert len(props.map_conversion) == 0
class TestImportTrueNorth(NewFile):
def test_detecting_no_true_north(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
context = ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
subject.import_true_north()
props = bpy.context.scene.BIMGeoreferenceProperties
assert props.has_true_north is False
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
context = ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
context.TrueNorth = ifc.createIfcDirection((1.0, 2.0, 0.0))
subject.import_true_north()
props = bpy.context.scene.BIMGeoreferenceProperties
assert props.true_north_abscissa == "1.0"
assert props.true_north_ordinate == "2.0"
assert props.has_true_north is True
class TestGetProjectedCRSAttributes(NewFile):
def test_run(self):
TestImportProjectedCRS().test_importing_projected_crs()
assert subject.get_projected_crs_attributes() == {
"Name": "Name",
"Description": "Description",
"GeodeticDatum": "GeodeticDatum",
"VerticalDatum": "VerticalDatum",
"MapProjection": "MapProjection",
"MapZone": "MapZone",
"MapUnit": tool.Ifc.get().by_type("IfcNamedUnit")[0],
}
class TestGetMapConversionAttributes(NewFile):
def test_run(self):
TestImportMapConversion().test_importing_map_conversion()
assert subject.get_map_conversion_attributes() == {
"Eastings": 1.0,
"Northings": 2.0,
"OrthogonalHeight": 3.0,
"XAxisAbscissa": 4.0,
"XAxisOrdinate": 5.0,
"Scale": 6.0,
}
class TestGetTrueNorthAttributes(NewFile):
def test_run(self):
TestImportTrueNorth().test_run()
assert subject.get_true_north_attributes() == [1.0, 2.0]
class TestEnableEditing(NewFile):
def test_run(self):
bpy.context.scene.BIMGeoreferenceProperties.is_editing = False
subject.enable_editing()
assert bpy.context.scene.BIMGeoreferenceProperties.is_editing is True
class TestDisableEditing(NewFile):
def test_run(self):
bpy.context.scene.BIMGeoreferenceProperties.is_editing = True
subject.disable_editing()
assert bpy.context.scene.BIMGeoreferenceProperties.is_editing is False
class TestSetCoordinates(NewFile):
def test_run(self):
subject.set_coordinates("input", [1., 2., 3.])
assert bpy.context.scene.BIMGeoreferenceProperties.coordinate_input == "1.0,2.0,3.0"
subject.set_coordinates("output", [4., 5., 6.])
assert bpy.context.scene.BIMGeoreferenceProperties.coordinate_output == "4.0,5.0,6.0"
class TestGetCoordinates(NewFile):
def test_run(self):
bpy.context.scene.BIMGeoreferenceProperties.coordinate_input = "1.0,2.0,3.0"
assert subject.get_coordinates("input") == [1., 2., 3.]
bpy.context.scene.BIMGeoreferenceProperties.coordinate_output = "4.0,5.0,6.0"
assert subject.get_coordinates("output") == [4., 5., 6.]
class TestGetCursorLocation(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT", prefix="MILLI", name="METRE")
ifcopenshell.api.run("unit.assign_unit", ifc, units=[unit])
bpy.context.scene.cursor.location = (1., 2., 3.)
assert subject.get_cursor_location() == [1000., 2000., 3000.]
class TestSetCursorLocation(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT", prefix="MILLI", name="METRE")
ifcopenshell.api.run("unit.assign_unit", ifc, units=[unit])
subject.set_cursor_location([1000., 2000., 3000.])
assert list(bpy.context.scene.cursor.location) == [1., 2., 3.]
class TestSetIfcTrueNorth(NewFile):
def test_run(self):
subject.set_ifc_true_north()
assert round(float(bpy.context.scene.BIMGeoreferenceProperties.true_north_abscissa), 3) == 0.0
assert round(float(bpy.context.scene.BIMGeoreferenceProperties.true_north_ordinate), 3) == 1.0
bpy.context.scene.sun_pos_properties.north_offset = math.radians(45)
subject.set_ifc_true_north()
assert round(float(bpy.context.scene.BIMGeoreferenceProperties.true_north_abscissa), 3) == 0.707
assert round(float(bpy.context.scene.BIMGeoreferenceProperties.true_north_ordinate), 3) == 0.707
class TestSetBlenderTrueNorth(NewFile):
def test_run(self):
bpy.context.scene.BIMGeoreferenceProperties.true_north_abscissa = "0.0"
bpy.context.scene.BIMGeoreferenceProperties.true_north_ordinate = "1.0"
subject.set_blender_true_north()
assert bpy.context.scene.sun_pos_properties.north_offset == 0
bpy.context.scene.BIMGeoreferenceProperties.true_north_abscissa = "0.707"
bpy.context.scene.BIMGeoreferenceProperties.true_north_ordinate = "0.707"
subject.set_blender_true_north()
assert round(math.degrees(bpy.context.scene.sun_pos_properties.north_offset)) == 45
class TestGetMapConversion(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
assert subject.get_map_conversion().is_a("IfcMapConversion")
class TestXyz2Enh(NewFile):
def test_run(self):
assert subject.xyz2enh([0., 0., 0.], None) == [0., 0., 0.]
def test_using_the_blender_offset(self):
props = bpy.context.scene.BIMGeoreferenceProperties
props.has_blender_offset = True
props.blender_eastings = "1.0"
assert subject.xyz2enh([0., 0., 0.], None) == (1., 0., 0.)
def test_using_the_map_conversion(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
map_conversion = ifc.by_type("IfcMapConversion")[0]
map_conversion.Eastings = 1.0
assert subject.xyz2enh([0., 0., 0.], map_conversion) == (1., 0., 0.)
def test_applying_both_blender_offset_and_map_conversion(self):
props = bpy.context.scene.BIMGeoreferenceProperties
props.has_blender_offset = True
props.blender_eastings = "1.0"
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
map_conversion = ifc.by_type("IfcMapConversion")[0]
map_conversion.Northings = 1.0
assert subject.xyz2enh([0., 0., 0.], map_conversion) == (1., 1., 0.)
class TestEnh2Xyz(NewFile):
def test_run(self):
assert subject.enh2xyz([0., 0., 0.], None) == [0., 0., 0.]
def test_using_the_blender_offset(self):
props = bpy.context.scene.BIMGeoreferenceProperties
props.has_blender_offset = True
props.blender_eastings = "1.0"
assert subject.enh2xyz([0., 0., 0.], None) == (-1., 0., 0.)
def test_using_the_map_conversion(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
map_conversion = ifc.by_type("IfcMapConversion")[0]
map_conversion.Eastings = 1.0
assert subject.enh2xyz([0., 0., 0.], map_conversion) == (-1., 0., 0.)
def test_applying_both_blender_offset_and_map_conversion(self):
props = bpy.context.scene.BIMGeoreferenceProperties
props.has_blender_offset = True
props.blender_eastings = "1.0"
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
map_conversion = ifc.by_type("IfcMapConversion")[0]
map_conversion.Northings = 1.0
assert subject.enh2xyz([0., 0., 0.], map_conversion) == (-1., -1., 0.)
class TestSetIfcGridNorth(NewFile):
def test_run(self):
props = bpy.context.scene.BIMGeoreferenceProperties
new = props.map_conversion.add()
new.name = "XAxisAbscissa"
new = props.map_conversion.add()
new.name = "XAxisOrdinate"
subject.set_ifc_grid_north()
assert round(float(props.map_conversion.get("XAxisAbscissa").string_value), 3) == 1.0
assert round(float(props.map_conversion.get("XAxisOrdinate").string_value), 3) == 0.0
bpy.context.scene.sun_pos_properties.north_offset = math.radians(45)
subject.set_ifc_grid_north()
assert round(float(props.map_conversion.get("XAxisAbscissa").string_value), 3) == 0.707
assert round(float(props.map_conversion.get("XAxisOrdinate").string_value), 3) == -0.707
class TestSetBlenderGridNorth(NewFile):
def test_run(self):
props = bpy.context.scene.BIMGeoreferenceProperties
new = props.map_conversion.add()
new.name = "XAxisAbscissa"
new = props.map_conversion.add()
new.name = "XAxisOrdinate"
props.map_conversion.get("XAxisAbscissa").string_value = "1.0"
props.map_conversion.get("XAxisOrdinate").string_value = "0.0"
subject.set_blender_grid_north()
assert bpy.context.scene.sun_pos_properties.north_offset == 0
props.map_conversion.get("XAxisAbscissa").string_value = "0.707"
props.map_conversion.get("XAxisOrdinate").string_value = "-0.707"
subject.set_blender_grid_north()
assert round(math.degrees(bpy.context.scene.sun_pos_properties.north_offset)) == 45