diff --git a/src/blenderbim/blenderbim/bim/module/georeference/data.py b/src/blenderbim/blenderbim/bim/module/georeference/data.py
index 93366b6baa..66df12b93b 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/data.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/data.py
@@ -16,77 +16,108 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import ifcopenshell.util.geolocation
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(),
- }
+ def load(cls):
+ cls.data["blender_derived_angle"] = cls.blender_derived_angle()
+ cls.data["map_conversion"] = cls.map_conversion()
+ cls.data["map_derived_angle"] = cls.map_derived_angle()
+ 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
-
+
+ @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
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
-
+ if tool.Ifc.get_schema() == "IFC2X3":
+ return {}
+
+ for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
+ if context.HasCoordinateOperation:
+ map_conversion = context.HasCoordinateOperation[0].get_info()
+ del map_conversion["id"]
+ del map_conversion["type"]
+ del map_conversion["SourceCRS"]
+ del map_conversion["TargetCRS"]
+ return map_conversion
+ return {}
+
+ @classmethod
+ def map_derived_angle(cls):
+ if (
+ 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
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 tool.Ifc.get_schema() == "IFC2X3":
+ return {}
+
+ for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
+ if context.HasCoordinateOperation:
+ projected_crs = context.HasCoordinateOperation[0].TargetCRS.get_info()
+ del projected_crs["id"]
+ del projected_crs["type"]
+ if projected_crs["MapUnit"]:
+ unit = projected_crs["MapUnit"]
+ projected_crs["MapUnit"] = (getattr(unit, "Prefix", "") or "") + unit.Name
+ return projected_crs
+ return {}
- 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
+ @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))
diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py
index af7d34dec5..71bde91ba8 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py
@@ -21,6 +21,7 @@ import bpy
import blenderbim.tool as tool
import blenderbim.core.georeference as core
+
class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_georeferencing"
bl_label = "Add Georeferencing"
@@ -29,34 +30,38 @@ class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
core.add_georeferencing(tool.Ifc)
-
+
+
class EnableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_georeferencing"
bl_label = "Enable Editing Georeferencing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Enable editing georeferencing"
-
+
def _execute(self, context):
core.enable_editing_georeferencing(tool.Georeference)
+
class RemoveGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_georeferencing"
bl_label = "Remove Georeferencing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Remove the georeferencing"
-
+
def _execute(self, context):
core.remove_georeferencing(tool.Ifc)
-
+
+
class EditGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_georeferencing"
bl_label = "Edit Georeferencing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Edit the georeferencing"
-
+
def _execute(self, context):
core.edit_georeferencing(tool.Ifc, tool.Georeference)
-
+
+
class DisableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_georeferencing"
bl_label = "Disable Editing Georeferencing"
@@ -66,37 +71,41 @@ class DisableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
core.disable_editing_georeferencing(tool.Georeference)
+
class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_grid_north"
bl_label = "Set IFC Grid North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set IFC grid north"
-
+
def _execute(self, context):
core.set_ifc_grid_north(tool.Georeference)
+
class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_blender_grid_north"
bl_label = "Set Blender Grid North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set Blender grid north"
-
+
def _execute(self, context):
core.set_blender_grid_north(tool.Georeference)
+
class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.get_cursor_location"
bl_label = "Get Cursor Location"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Insert the current cursor coordinates"
-
+
@classmethod
def poll(cls, context):
return tool.Ifc.get()
-
+
def _execute(self, context):
core.get_cursor_location(tool.Georeference)
-
+
+
class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_cursor_location"
bl_label = "Set Cursor Location"
@@ -105,13 +114,13 @@ class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- file = tool.Ifc.get()
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):
core.set_cursor_location(tool.Georeference)
-
+
+
class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.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):
core.set_ifc_true_north(tool.Georeference)
+
class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.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):
core.set_blender_true_north(tool.Georeference)
+
class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_local_to_global"
bl_label = "Convert Local To Global"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert local coordinate to global coordinate"
-
+
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and props.coordinate_input.count(",") == 2
-
+
def _execute(self, context):
core.convert_local_to_global(tool.Georeference)
-
+
+
class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_global_to_local"
bl_label = "Convert Global To Local"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert global coordinate to local coordinate"
-
+
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2
-
+
def _execute(self, context):
core.convert_global_to_local(tool.Georeference)
-
diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py
index 93dcc34c38..15c28e3467 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py
@@ -16,12 +16,12 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
-import ifcopenshell.util.geolocation
+import blenderbim.tool as tool
from bpy.types import Panel
-from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes, draw_attribute
from blenderbim.bim.module.georeference.data import GeoreferenceData
+
class BIM_PT_gis(Panel):
bl_label = "IFC Georeferencing"
bl_idname = "BIM_PT_gis"
@@ -30,15 +30,15 @@ class BIM_PT_gis(Panel):
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
-
+
def draw(self, context):
self.layout.use_property_split = True
self.layout.use_property_decorate = False
props = context.scene.BIMGeoreferenceProperties
-
+
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
-
+
if props.is_editing:
return self.draw_editable_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_blender_true_north", text="Set Blender North")
-
def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
if not GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True)
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="")
-
+
if props.has_blender_offset:
row = self.layout.row()
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 = self.layout.row(align=True)
row.label(text="Derived Grid North")
- row.label(
- text=str(
- round(
- ifcopenshell.util.geolocation.xaxis2angle(
- float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
- ),
- 3,
- )
- )
- )
+ row.label(text=GeoreferenceData.data["blender_derived_angle"])
if GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True)
@@ -124,12 +114,8 @@ class BIM_PT_gis(Panel):
row.operator("bim.remove_georeferencing", icon="X", text="")
for key, value in GeoreferenceData.data["projected_crs"].items():
- if key == "id" or key == "type" or not value:
+ if not value:
continue
- if key == "MapUnit":
- unit_value = value.get("Prefix", "") or ""
- unit_value += value["Name"]
- value = unit_value
row = self.layout.row(align=True)
row.label(text=key)
row.label(text=str(value))
@@ -139,7 +125,7 @@ class BIM_PT_gis(Panel):
row.label(text="Map Conversion", icon="GRID")
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
row = self.layout.row(align=True)
row.label(text=key)
@@ -147,16 +133,7 @@ class BIM_PT_gis(Panel):
if key == "XAxisOrdinate":
row = self.layout.row(align=True)
row.label(text="Derived Angle")
- row.label(
- text=str(
- round(
- ifcopenshell.util.geolocation.xaxis2angle(
- GeoreferenceData.data["map_conversion"]["XAxisAbscissa"], GeoreferenceData.data["map_conversion"]["XAxisOrdinate"]
- ),
- 3,
- )
- )
- )
+ row.label(text=GeoreferenceData.data["map_derived_angle"])
if GeoreferenceData.data["true_north"]:
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 = self.layout.row(align=True)
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):
@@ -178,9 +155,6 @@ class BIM_PT_gis_utilities(Panel):
bl_category = "BlenderBIM"
def draw(self, context):
- if not GeoreferenceData.is_loaded:
- GeoreferenceData.load()
-
props = context.scene.BIMGeoreferenceProperties
row = self.layout.row(align=True)
@@ -193,4 +167,3 @@ class BIM_PT_gis_utilities(Panel):
row = self.layout.row(align=True)
row.operator("bim.convert_local_to_global", text="Local to Global")
row.operator("bim.convert_global_to_local", text="Global to Local")
-
diff --git a/src/blenderbim/blenderbim/core/georeference.py b/src/blenderbim/blenderbim/core/georeference.py
index 5096594ba1..8cde5d9b20 100644
--- a/src/blenderbim/blenderbim/core/georeference.py
+++ b/src/blenderbim/blenderbim/core/georeference.py
@@ -16,63 +16,67 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
+
def add_georeferencing(ifc):
ifc.run("georeference.add_georeferencing")
+
def enable_editing_georeferencing(georeference):
- georeference.clear_projected_crs()
georeference.import_projected_crs()
- georeference.clear_map_conversion()
georeference.import_map_conversion()
- georeference.set_has_true_north_prop()
- georeference.set_true_north_props()
+ georeference.import_true_north()
georeference.enable_editing()
-
+
+
def remove_georeferencing(ifc):
ifc.run("georeference.remove_georeferencing")
+
def disable_editing_georeferencing(georeference):
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 set_cursor_location(georeference):
- scale = georeference.get_scale()
- coordinates = georeference.get_coordinates_from_coordinate_output_prop()
- georeference.set_cursor_location(coordinates, scale)
+
+def edit_georeferencing(ifc, georeference):
+ ifc.run(
+ "georeference.edit_georeferencing",
+ projected_crs=georeference.get_projected_crs_attributes(),
+ map_conversion=georeference.get_map_conversion_attributes(),
+ 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):
georeference.set_ifc_true_north()
+
def set_blender_true_north(georeference):
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)
diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py
index 2858a6a943..3fa9c6808b 100644
--- a/src/blenderbim/blenderbim/core/tool.py
+++ b/src/blenderbim/blenderbim/core/tool.py
@@ -247,41 +247,29 @@ class Geometry:
def should_generate_uvs(cls, obj): pass
def should_use_presentation_style_assignment(cls): pass
+
@interface
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 get_projected_crs_attributes(cls): pass
- def get_map_conversion_attributes(cls): pass
- def get_true_north_attributes(cls): 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 enable_editing(cls): pass
+ def enh2xyz(cls, map_conversion, coordinates): pass
+ def get_coordinates(cls, io): 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_easting_northing_height_from_xyz(cls, map_conversion): pass
- def get_xyz_from_easting_northig_height(cls, map_conversion): pass
- def set_coordinate_output_prop(cls, coordinates): pass
- def get_x_angle_from_sun_position(cls): pass
- def set_xaxis_vector_from_angle(cls, x_angle): pass
- def get_angle_from_xaxis(cls): pass
- def set_sun_pos_north_offset(cls, angle): pass
+ def get_map_conversion_attributes(cls): pass
+ def get_projected_crs_attributes(cls): pass
+ def get_true_north_attributes(cls): pass
+ def import_map_conversion(cls): pass
+ def import_projected_crs(cls): pass
+ def import_true_north(cls): 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
class Ifc:
@@ -543,10 +531,10 @@ class Unit:
def get_scene_unit_name(cls, unit_type): pass
def get_scene_unit_si_prefix(cls): 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_units(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
diff --git a/src/blenderbim/blenderbim/tool/georeference.py b/src/blenderbim/blenderbim/tool/georeference.py
index 14c2f0dc95..5a22ab5c42 100644
--- a/src/blenderbim/blenderbim/tool/georeference.py
+++ b/src/blenderbim/blenderbim/tool/georeference.py
@@ -22,210 +22,142 @@ import blenderbim.core.tool
import blenderbim.tool as tool
import ifcopenshell
import blenderbim.bim.helper
-
-from ifcopenshell.api.unit.data import Data as UnitData
from math import radians, degrees, atan, tan, cos, sin
+
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 import_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()
+ def callback(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(): (getattr(u, "Prefix", "") or "") + u.Name
+ for u in tool.Ifc.get().by_type("IfcNamedUnit")
+ if u.UnitType == "LENGTHUNIT"
+ }
+ )
+ if data["MapUnit"]:
+ new.enum_value = str(data["MapUnit"].id())
+ return True
- break
-
props = bpy.context.scene.BIMGeoreferenceProperties
- blenderbim.bim.helper.import_attributes(
- "IfcProjectedCRS",
- props.projected_crs,
- projected_crs,
- cls.import_projected_crs_attributes,
- )
-
+ props.projected_crs.clear()
+
+ if tool.Ifc.get_schema() == "IFC2X3":
+ return
+
+ 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
def import_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
-
- 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
+ def callback(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
- bpy.context.scene.BIMGeoreferenceProperties.has_true_north = bool(true_north)
-
- @classmethod
- def set_true_north_props(cls):
- ifc = tool.Ifc.get()
- true_north = {}
-
- if ifc.schema == "IFC2X3":
+ props = bpy.context.scene.BIMGeoreferenceProperties
+ props.map_conversion.clear()
+
+ if tool.Ifc.get_schema() == "IFC2X3":
return
-
- for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
- if not context.TrueNorth:
- continue
- true_north = context.TrueNorth.DirectionRatios
- break
-
- if true_north:
- bpy.context.scene.BIMGeoreferenceProperties.true_north_abscissa = str(true_north[0])
- bpy.context.scene.BIMGeoreferenceProperties.true_north_ordinate = str(true_north[1])
-
+
+ for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
+ if context.HasCoordinateOperation:
+ map_conversion = context.HasCoordinateOperation[0]
+ blenderbim.bim.helper.import_attributes2(map_conversion, props.map_conversion, callback=callback)
+ return
+
+ @classmethod
+ def import_true_north(cls):
+ 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
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
- projected_crs = blenderbim.bim.helper.export_attributes(props.projected_crs, cls.export_crs_attributes)
- return projected_crs
-
+ return blenderbim.bim.helper.export_attributes(props.projected_crs, callback=callback)
+
@classmethod
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
- map_conversion = blenderbim.bim.helper.export_attributes(props.map_conversion, cls.export_map_attributes)
- return map_conversion
-
+ return blenderbim.bim.helper.export_attributes(props.map_conversion, callback=callback)
+
@classmethod
def get_true_north_attributes(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
- true_north = None
if props.has_true_north:
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:
print("ERROR, True North Abscissa and Ordinate expect a number")
- #self.report({"ERROR"}, "True North Abscissa and Ordinate expect a number")
- return true_north
-
+ # self.report({"ERROR"}, "True North Abscissa and Ordinate expect a number")
+
@classmethod
def enable_editing(cls):
bpy.context.scene.BIMGeoreferenceProperties.is_editing = True
-
+
@classmethod
def disable_editing(cls):
bpy.context.scene.BIMGeoreferenceProperties.is_editing = False
-
+
@classmethod
- def edit_georeferencing(cls, projected_crs, map_conversion, true_north):
- tool.Ifc.run(
- "georeference.edit_georeferencing",
- **{
- "projected_crs": projected_crs,
- "map_conversion": map_conversion,
- "true_north": true_north,
- }
- )
-
+ def set_coordinates(cls, io, coordinates):
+ if io == "input":
+ bpy.context.scene.BIMGeoreferenceProperties.coordinate_input = ",".join([str(o) for o in coordinates])
+ elif io == "output":
+ bpy.context.scene.BIMGeoreferenceProperties.coordinate_output = ",".join([str(o) for o in coordinates])
+
@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
-
+ def get_coordinates(cls, io):
+ if io == "input":
+ return [float(co) for co in bpy.context.scene.BIMGeoreferenceProperties.coordinate_input.split(",")]
+ elif io == "output":
+ return [float(co) for co in bpy.context.scene.BIMGeoreferenceProperties.coordinate_output.split(",")]
+
@classmethod
def get_cursor_location(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
- project_coordinates = [o / scale for o in bpy.context.scene.cursor.location]
- props.coordinate_input = ",".join([str(o) for o in project_coordinates])
-
+ return [o / scale for o in bpy.context.scene.cursor.location]
+
@classmethod
- def set_cursor_location(cls, coordinates, scale):
- bpy.context.scene.cursor.location = coordinates * scale
-
- @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
-
+ def set_cursor_location(cls, coordinates):
+ scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
+ bpy.context.scene.cursor.location = [co * scale for co in coordinates]
+
@classmethod
def set_ifc_true_north(cls):
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):
bpy.context.scene.sun_pos_properties.north_offset = -radians(
ifcopenshell.util.geolocation.yaxis2angle(
- float(context.scene.BIMGeoreferenceProperties.true_north_abscissa),
- float(context.scene.BIMGeoreferenceProperties.true_north_ordinate),
+ float(bpy.context.scene.BIMGeoreferenceProperties.true_north_abscissa),
+ float(bpy.context.scene.BIMGeoreferenceProperties.true_north_ordinate),
)
)
-
+
@classmethod
def get_map_conversion(cls):
- ifc = tool.Ifc.get()
- map_conversion = {}
- if ifc.schema == "IFC2X3":
+ if tool.Ifc.get_schema() == "IFC2X3":
return
-
- for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
- if not context.HasCoordinateOperation:
- 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(",")]
+ for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
+ if context.HasCoordinateOperation:
+ return context.HasCoordinateOperation[0]
+ @classmethod
+ def xyz2enh(cls, coordinates, map_conversion):
+ props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
- results = ifcopenshell.util.geolocation.xyz2enh(
- x,
- y,
- z,
+ coordinates = ifcopenshell.util.geolocation.xyz2enh(
+ coordinates[0],
+ coordinates[1],
+ coordinates[2],
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
@@ -278,52 +196,41 @@ class Georeference(blenderbim.core.tool.Georeference):
float(props.blender_x_axis_ordinate),
1.0,
)
- x, y, z = results
-
# 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:
- results = ifcopenshell.util.geolocation.enh2xyz(
- 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),
+ coordinates = ifcopenshell.util.geolocation.xyz2enh(
+ 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,
)
- else:
- results = (x, y, z)
+ return coordinates
+ @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:
- results = ifcopenshell.util.geolocation.enh2xyz(
- results[0],
- results[1],
- results[2],
+ coordinates = ifcopenshell.util.geolocation.enh2xyz(
+ coordinates[0],
+ coordinates[1],
+ coordinates[2],
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
@@ -331,31 +238,20 @@ class Georeference(blenderbim.core.tool.Georeference):
float(props.blender_x_axis_ordinate),
1.0,
)
-
- 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)
+ return coordinates
+ @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)
diff --git a/src/blenderbim/pytest.ini b/src/blenderbim/pytest.ini
index 0f1e811706..4ad6fa92de 100644
--- a/src/blenderbim/pytest.ini
+++ b/src/blenderbim/pytest.ini
@@ -10,6 +10,7 @@ markers =
document
drawing
geometry
+ georeference
library
material
misc
diff --git a/src/blenderbim/test/bim/feature/georeference.feature b/src/blenderbim/test/bim/feature/georeference.feature
new file mode 100644
index 0000000000..85922285d6
--- /dev/null
+++ b/src/blenderbim/test/bim/feature/georeference.feature
@@ -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"
diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py
index d18f7e815c..686d23ab65 100644
--- a/src/blenderbim/test/core/bootstrap.py
+++ b/src/blenderbim/test/core/bootstrap.py
@@ -98,6 +98,13 @@ def geometry():
prophet.verify()
+@pytest.fixture
+def georeference():
+ prophet = Prophecy(blenderbim.core.tool.Georeference)
+ yield prophet
+ prophet.verify()
+
+
@pytest.fixture
def library():
prophet = Prophecy(blenderbim.core.tool.Library)
diff --git a/src/blenderbim/test/core/test_georeference.py b/src/blenderbim/test/core/test_georeference.py
new file mode 100644
index 0000000000..957e43b232
--- /dev/null
+++ b/src/blenderbim/test/core/test_georeference.py
@@ -0,0 +1,114 @@
+# 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.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)
diff --git a/src/blenderbim/test/tool/test_georeference.py b/src/blenderbim/test/tool/test_georeference.py
new file mode 100644
index 0000000000..028ab83d44
--- /dev/null
+++ b/src/blenderbim/test/tool/test_georeference.py
@@ -0,0 +1,357 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2021 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 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