Implement Blender UI for handling new coordinate operations in IFC4X3

This commit is contained in:
Dion Moult
2024-06-22 23:32:25 +10:00
parent 4ef237e0f7
commit fd8cb7e8ed
8 changed files with 113 additions and 51 deletions
@@ -20,6 +20,7 @@
import bpy import bpy
import ifcopenshell.util.geolocation import ifcopenshell.util.geolocation
import blenderbim.tool as tool import blenderbim.tool as tool
from ifcopenshell.util.doc import get_entity_doc
def refresh(): def refresh():
@@ -32,8 +33,9 @@ class GeoreferenceData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.data["coordinate_operation_class"] = cls.coordinate_operation_class()
cls.data["blender_derived_angle"] = cls.blender_derived_angle() cls.data["blender_derived_angle"] = cls.blender_derived_angle()
cls.data["map_conversion"] = cls.map_conversion() cls.data["coordinate_operation"] = cls.coordinate_operation()
cls.data["map_derived_angle"] = cls.map_derived_angle() cls.data["map_derived_angle"] = cls.map_derived_angle()
cls.data["projected_crs"] = cls.projected_crs() cls.data["projected_crs"] = cls.projected_crs()
cls.data["true_north"] = cls.true_north() cls.data["true_north"] = cls.true_north()
@@ -42,6 +44,14 @@ class GeoreferenceData:
cls.data["map_unit_symbol"] = cls.map_unit_symbol() cls.data["map_unit_symbol"] = cls.map_unit_symbol()
cls.is_loaded = True cls.is_loaded = True
@classmethod
def coordinate_operation_class(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcCoordinateOperation")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)]
@classmethod @classmethod
def blender_derived_angle(cls): def blender_derived_angle(cls):
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
@@ -56,36 +66,37 @@ class GeoreferenceData:
) )
@classmethod @classmethod
def map_conversion(cls): def coordinate_operation(cls):
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
map_conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion") coordinate_operation = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
if not map_conversion: if not coordinate_operation:
return {} return {}
del map_conversion["id"] del coordinate_operation["id"]
return map_conversion coordinate_operation["type"] = "ePSet_MapConversion"
return coordinate_operation
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False): for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation: if context.HasCoordinateOperation:
map_conversion = context.HasCoordinateOperation[0].get_info() coordinate_operation = context.HasCoordinateOperation[0].get_info()
del map_conversion["id"] del coordinate_operation["id"]
del map_conversion["type"] del coordinate_operation["SourceCRS"]
del map_conversion["SourceCRS"] del coordinate_operation["TargetCRS"]
del map_conversion["TargetCRS"] return coordinate_operation
return map_conversion
return {} return {}
@classmethod @classmethod
def map_derived_angle(cls): def map_derived_angle(cls):
if ( if (
cls.data["map_conversion"] cls.data["coordinate_operation"]
and cls.data["map_conversion"].get("XAxisAbscissa", None) is not None and cls.data["coordinate_operation"].get("XAxisAbscissa", None) is not None
and cls.data["map_conversion"].get("XAxisOrdinate", None) is not None and cls.data["coordinate_operation"].get("XAxisOrdinate", None) is not None
): ):
return str( return str(
round( round(
ifcopenshell.util.geolocation.xaxis2angle( ifcopenshell.util.geolocation.xaxis2angle(
cls.data["map_conversion"]["XAxisAbscissa"], cls.data["map_conversion"]["XAxisOrdinate"] cls.data["coordinate_operation"]["XAxisAbscissa"],
cls.data["coordinate_operation"]["XAxisOrdinate"],
), ),
3, 3,
) )
@@ -29,7 +29,7 @@ class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a new georeference" bl_description = "Add a new georeference"
def _execute(self, context): def _execute(self, context):
core.add_georeferencing(tool.Ifc) core.add_georeferencing(tool.Georeference)
class EnableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator): class EnableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
@@ -29,11 +29,21 @@ from bpy.props import (
FloatVectorProperty, FloatVectorProperty,
CollectionProperty, CollectionProperty,
) )
from blenderbim.bim.module.georeference.data import GeoreferenceData
def get_coordinate_operation_class(self, context):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
return GeoreferenceData.data["coordinate_operation_class"]
class BIMGeoreferenceProperties(PropertyGroup): class BIMGeoreferenceProperties(PropertyGroup):
coordinate_operation_class: bpy.props.EnumProperty(
items=get_coordinate_operation_class, name="Coordinate Operation Class"
)
is_editing: BoolProperty(name="Is Editing") is_editing: BoolProperty(name="Is Editing")
map_conversion: CollectionProperty(name="Map Conversion", type=Attribute) coordinate_operation: CollectionProperty(name="Coordinate Operation", type=Attribute)
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute) projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
local_coordinates: StringProperty( local_coordinates: StringProperty(
name="Local Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0" name="Local Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
@@ -53,9 +53,9 @@ class BIM_PT_gis(Panel):
draw_attributes(props.projected_crs, self.layout) draw_attributes(props.projected_crs, self.layout)
row = self.layout.row() row = self.layout.row()
row.label(text="Map Conversion", icon="GRID") row.label(text="Coordinate Operation", icon="GRID")
for attribute in props.map_conversion: for attribute in props.coordinate_operation:
if attribute.name == "Scale" and hasattr(context.scene, "sun_pos_properties"): if attribute.name == "Scale" and hasattr(context.scene, "sun_pos_properties"):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.operator("bim.set_ifc_grid_north", text="Set IFC North") row.operator("bim.set_ifc_grid_north", text="Set IFC North")
@@ -80,12 +80,14 @@ class BIM_PT_gis(Panel):
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
row = self.layout.row() row = self.layout.row()
row.label(text="IFC2X3 Fallback In Use", icon="ERROR") row.label(text="IFC2X3 Fallback In Use", icon="INFO")
if not GeoreferenceData.data["projected_crs"]: if not GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True) row = self.layout.row()
row.label(text="Not Georeferenced") row.label(text="Not Georeferenced", icon="ERROR")
if tool.Ifc.get_schema() != "IFC2X3": if tool.Ifc.get_schema() != "IFC2X3":
row = self.layout.row(align=True)
row.prop(props, "coordinate_operation_class", text="")
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:
@@ -125,13 +127,15 @@ class BIM_PT_gis(Panel):
row.label(text=key) row.label(text=key)
row.label(text=str(value)) row.label(text=str(value))
if GeoreferenceData.data["map_conversion"]: if GeoreferenceData.data["coordinate_operation"]:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Map Conversion", icon="GRID") row.label(text="Coordinate Operation", icon="GRID")
for key, value in GeoreferenceData.data["map_conversion"].items(): for key, value in GeoreferenceData.data["coordinate_operation"].items():
if value is None: if value is None:
continue continue
if key == "type":
key = "Type"
row = self.layout.row(align=True) row = self.layout.row(align=True)
if key in ("Eastings", "Northings", "OrthogonalHeight"): if key in ("Eastings", "Northings", "OrthogonalHeight"):
row.label(text=f"{key} ({GeoreferenceData.data['map_unit_symbol']})") row.label(text=f"{key} ({GeoreferenceData.data['map_unit_symbol']})")
@@ -17,13 +17,13 @@
# 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(georeference):
ifc.run("georeference.add_georeferencing") georeference.add_georeferencing()
def enable_editing_georeferencing(georeference): def enable_editing_georeferencing(georeference):
georeference.import_projected_crs() georeference.import_projected_crs()
georeference.import_map_conversion() georeference.import_coordinate_operation()
georeference.import_true_north() georeference.import_true_north()
georeference.enable_editing() georeference.enable_editing()
@@ -40,7 +40,7 @@ def edit_georeferencing(ifc, georeference):
ifc.run( ifc.run(
"georeference.edit_georeferencing", "georeference.edit_georeferencing",
projected_crs=georeference.get_projected_crs_attributes(), projected_crs=georeference.get_projected_crs_attributes(),
map_conversion=georeference.get_map_conversion_attributes(), coordinate_operation=georeference.get_coordinate_operation_attributes(),
true_north=georeference.get_true_north_attributes(), true_north=georeference.get_true_north_attributes(),
) )
georeference.disable_editing() georeference.disable_editing()
@@ -77,9 +77,11 @@ def convert_global_to_local(georeference):
georeference.set_coordinates("local", coordinates) georeference.set_coordinates("local", coordinates)
georeference.set_cursor_location() georeference.set_cursor_location()
def convert_angle_to_coord(georeference, type): def convert_angle_to_coord(georeference, type):
vector_coordinates = georeference.angle2coords(georeference.get_angle(type), type) vector_coordinates = georeference.angle2coords(georeference.get_angle(type), type)
georeference.set_vector_coordinates(vector_coordinates,type) georeference.set_vector_coordinates(vector_coordinates, type)
def import_plot(georeference, filepath): def import_plot(georeference, filepath):
georeference.import_plot(filepath) georeference.import_plot(filepath)
+2 -2
View File
@@ -423,7 +423,7 @@ class Georeference:
def get_angle(cls, type): pass def get_angle(cls, type): pass
def get_coordinates(cls, io): pass def get_coordinates(cls, io): pass
def get_cursor_location(cls): pass def get_cursor_location(cls): pass
def get_map_conversion_attributes(cls): pass def get_coordinate_operation_attributes(cls): pass
def get_projected_crs_attributes(cls): pass def get_projected_crs_attributes(cls): pass
def get_true_north_attributes(cls): pass def get_true_north_attributes(cls): pass
def import_map_conversion(cls): pass def import_map_conversion(cls): pass
@@ -1019,4 +1019,4 @@ class Voider:
@interface @interface
class Web: class Web:
pass pass
+50 -14
View File
@@ -22,10 +22,17 @@ 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 math import radians, degrees, atan, tan, cos, sin from math import radians, cos, sin
class Georeference(blenderbim.core.tool.Georeference): class Georeference(blenderbim.core.tool.Georeference):
@classmethod
def add_georeferencing(cls):
tool.Ifc.run(
"georeference.add_georeferencing",
ifc_class=bpy.context.scene.BIMGeoreferenceProperties.coordinate_operation_class,
)
@classmethod @classmethod
def import_projected_crs(cls): def import_projected_crs(cls):
def callback(name, prop, data): def callback(name, prop, data):
@@ -59,24 +66,44 @@ class Georeference(blenderbim.core.tool.Georeference):
return return
@classmethod @classmethod
def import_map_conversion(cls): def import_coordinate_operation(cls):
def callback(name, prop, data): def callback(name, prop, data):
if name not in ["SourceCRS", "TargetCRS"]: if name in ("FirstCoordinate", "SecondCoordinate"):
props = bpy.context.scene.BIMGeoreferenceProperties
if name == "FirstCoordinate":
new = props.coordinate_operation.add()
new.name = "Measure Type"
new.data_type = "enum"
new.is_optional = False
new.is_null = False
new.enum_items = json.dumps(["IfcLengthMeasure", "IfcPlaneAngleMeasure"])
new.enum_value = data[name].is_a()
prop = props.coordinate_operation.add()
prop.name = name
prop.is_optional = False
prop.is_null = False
# 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].wrappedValue)
return True
elif name not in ("SourceCRS", "TargetCRS"):
# Enforce a string data type to prevent data loss in single-precision Blender props # Enforce a string data type to prevent data loss in single-precision Blender props
prop.data_type = "string" prop.data_type = "string"
prop.string_value = "" if prop.is_null else str(data[name]) prop.string_value = "" if prop.is_null else str(data[name])
return True return True
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
props.map_conversion.clear() props.coordinate_operation.clear()
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
return return
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False): for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation: if context.HasCoordinateOperation:
map_conversion = context.HasCoordinateOperation[0] coordinate_operation = context.HasCoordinateOperation[0]
blenderbim.bim.helper.import_attributes2(map_conversion, props.map_conversion, callback=callback) blenderbim.bim.helper.import_attributes2(
coordinate_operation, props.coordinate_operation, callback=callback
)
return return
@classmethod @classmethod
@@ -105,15 +132,24 @@ class Georeference(blenderbim.core.tool.Georeference):
return blenderbim.bim.helper.export_attributes(props.projected_crs, callback=callback) return blenderbim.bim.helper.export_attributes(props.projected_crs, callback=callback)
@classmethod @classmethod
def get_map_conversion_attributes(cls): def get_coordinate_operation_attributes(cls):
measure_type = None
def callback(attributes, prop): def callback(attributes, prop):
if not prop.is_null and prop.data_type == "string": global measure_type
if prop.name == "Measure Type":
measure_type = prop.get_value()
return True
elif prop.name in ("FirstCoordinate", "SecondCoordinate"):
attributes[prop.name] = tool.Ifc.get().create_entity(measure_type, float(prop.string_value))
return True
elif not prop.is_null and prop.data_type == "string":
# We store our floats as string to prevent single precision data loss # We store our floats as string to prevent single precision data loss
attributes[prop.name] = float(prop.string_value) attributes[prop.name] = float(prop.string_value)
return True return True
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
return blenderbim.bim.helper.export_attributes(props.map_conversion, callback=callback) return blenderbim.bim.helper.export_attributes(props.coordinate_operation, callback=callback)
@classmethod @classmethod
def get_true_north_attributes(cls): def get_true_north_attributes(cls):
@@ -210,15 +246,15 @@ class Georeference(blenderbim.core.tool.Georeference):
def set_ifc_grid_north(cls): def set_ifc_grid_north(cls):
x_angle = bpy.context.scene.sun_pos_properties.north_offset x_angle = bpy.context.scene.sun_pos_properties.north_offset
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
props.map_conversion.get("XAxisAbscissa").string_value = str(cos(x_angle)) props.coordinate_operation.get("XAxisAbscissa").string_value = str(cos(x_angle))
props.map_conversion.get("XAxisOrdinate").string_value = str(sin(x_angle)) props.coordinate_operation.get("XAxisOrdinate").string_value = str(sin(x_angle))
@classmethod @classmethod
def set_blender_grid_north(cls): def set_blender_grid_north(cls):
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
angle = ifcopenshell.util.geolocation.xaxis2angle( angle = ifcopenshell.util.geolocation.xaxis2angle(
float(props.map_conversion.get("XAxisAbscissa").string_value), float(props.coordinate_operation.get("XAxisAbscissa").string_value),
float(props.map_conversion.get("XAxisOrdinate").string_value), float(props.coordinate_operation.get("XAxisOrdinate").string_value),
) )
bpy.context.scene.sun_pos_properties.north_offset = -radians(angle) bpy.context.scene.sun_pos_properties.north_offset = -radians(angle)
@@ -247,7 +283,7 @@ class Georeference(blenderbim.core.tool.Georeference):
bpy.context.scene.BIMGeoreferenceProperties.y_axis_ordinate_output = str(y) bpy.context.scene.BIMGeoreferenceProperties.y_axis_ordinate_output = str(y)
@classmethod @classmethod
def import_plot(cls, filepath, map_conversion): def import_plot(cls, filepath):
import bmesh import bmesh
def parse_csv(file_path): def parse_csv(file_path):
@@ -226,13 +226,12 @@ def get_helmert_transformation_parameters(ifc_file: ifcopenshell.file) -> Option
else: else:
factor_x = factor_y = factor_z = 1 factor_x = factor_y = factor_z = 1
elif conversion.is_a() == "IfcRigidOperation": elif conversion.is_a() == "IfcRigidOperation":
# TODO e = conversion.FirstCoordinate.wrappedValue
e = conversion.FirstCoordinate n = conversion.SecondCoordinate.wrappedValue
n = conversion.SecondCoordinate
h = conversion.Height or 0 h = conversion.Height or 0
xaa = 1.0 xaa = 1.0
xao = 0.0 xao = 0.0
factor_x = factor_y = factor_z = 1 scale = factor_x = factor_y = factor_z = 1
if not xaa and not xao: if not xaa and not xao:
xaa = 1.0 xaa = 1.0