This commit is contained in:
Andrej730
2024-08-08 12:11:36 +05:00
parent 9671be1734
commit eca1d47d06
5 changed files with 73 additions and 54 deletions
+22 -14
View File
@@ -16,26 +16,34 @@
# 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
def add_georeferencing(georeference): if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def add_georeferencing(georeference: tool.Georeference) -> None:
georeference.add_georeferencing() georeference.add_georeferencing()
def enable_editing_georeferencing(georeference): def enable_editing_georeferencing(georeference: tool.Georeference) -> None:
georeference.import_projected_crs() georeference.import_projected_crs()
georeference.import_coordinate_operation() georeference.import_coordinate_operation()
georeference.enable_editing() georeference.enable_editing()
def remove_georeferencing(ifc): def remove_georeferencing(ifc: tool.Ifc) -> None:
ifc.run("georeference.remove_georeferencing") ifc.run("georeference.remove_georeferencing")
def disable_editing_georeferencing(georeference): def disable_editing_georeferencing(georeference: tool.Georeference) -> None:
georeference.disable_editing() georeference.disable_editing()
def edit_georeferencing(ifc, georeference): def edit_georeferencing(ifc: tool.Ifc, georeference: tool.Georeference) -> None:
ifc.run( ifc.run(
"georeference.edit_georeferencing", "georeference.edit_georeferencing",
projected_crs=georeference.export_projected_crs(), projected_crs=georeference.export_projected_crs(),
@@ -45,7 +53,7 @@ def edit_georeferencing(ifc, georeference):
georeference.set_model_origin() georeference.set_model_origin()
def get_cursor_location(georeference): def get_cursor_location(georeference: tool.Georeference) -> None:
location = georeference.get_cursor_location() location = georeference.get_cursor_location()
if georeference.has_blender_offset(): if georeference.has_blender_offset():
georeference.set_coordinates("blender", location) georeference.set_coordinates("blender", location)
@@ -53,39 +61,39 @@ def get_cursor_location(georeference):
georeference.set_coordinates("local", location) georeference.set_coordinates("local", location)
def import_plot(georeference, filepath): def import_plot(georeference: tool.Georeference, filepath: str) -> None:
georeference.import_plot(filepath) georeference.import_plot(filepath)
def enable_editing_wcs(georeference): def enable_editing_wcs(georeference: tool.Georeference) -> None:
georeference.import_wcs() georeference.import_wcs()
georeference.enable_editing_wcs() georeference.enable_editing_wcs()
def disable_editing_wcs(georeference): def disable_editing_wcs(georeference: tool.Georeference) -> None:
georeference.disable_editing_wcs() georeference.disable_editing_wcs()
def edit_wcs(georeference): def edit_wcs(georeference: tool.Georeference) -> None:
wcs = georeference.export_wcs() wcs = georeference.export_wcs()
georeference.set_wcs(wcs) georeference.set_wcs(wcs)
georeference.disable_editing_wcs() georeference.disable_editing_wcs()
georeference.set_model_origin() georeference.set_model_origin()
def enable_editing_true_north(georeference): def enable_editing_true_north(georeference: tool.Georeference) -> None:
georeference.import_true_north() georeference.import_true_north()
georeference.enable_editing_true_north() georeference.enable_editing_true_north()
def disable_editing_true_north(georeference): def disable_editing_true_north(georeference: tool.Georeference) -> None:
georeference.disable_editing_true_north() georeference.disable_editing_true_north()
def edit_true_north(ifc, georeference): def edit_true_north(ifc: tool.Ifc, georeference: tool.Georeference) -> None:
ifc.run("georeference.edit_true_north", true_north=georeference.get_true_north_attributes()) ifc.run("georeference.edit_true_north", true_north=georeference.get_true_north_attributes())
georeference.disable_editing_true_north() georeference.disable_editing_true_north()
def remove_true_north(ifc): def remove_true_north(ifc: tool.Ifc) -> None:
ifc.run("georeference.edit_true_north", true_north=None) ifc.run("georeference.edit_true_north", true_north=None)
+1 -1
View File
@@ -690,7 +690,7 @@ class Blender(blenderbim.core.tool.Blender):
return False, None return False, None
@classmethod @classmethod
def toggle_edit_mode(cls, context: bpy.types.Context) -> set: def toggle_edit_mode(cls, context: bpy.types.Context) -> set[str]:
ao = context.active_object ao = context.active_object
if not ao: if not ao:
return {"CANCELLED"} return {"CANCELLED"}
+28 -23
View File
@@ -27,18 +27,21 @@ import ifcopenshell.util.unit
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.helper import blenderbim.bim.helper
from typing import Any, Union, Literal
class Georeference(blenderbim.core.tool.Georeference): class Georeference(blenderbim.core.tool.Georeference):
COORDINATE_TYPE = Literal["blender", "local", "map"]
@classmethod @classmethod
def add_georeferencing(cls): def add_georeferencing(cls) -> None:
tool.Ifc.run( tool.Ifc.run(
"georeference.add_georeferencing", "georeference.add_georeferencing",
ifc_class=bpy.context.scene.BIMGeoreferenceProperties.coordinate_operation_class, ifc_class=bpy.context.scene.BIMGeoreferenceProperties.coordinate_operation_class,
) )
@classmethod @classmethod
def import_projected_crs(cls): def import_projected_crs(cls) -> None:
def callback(name, prop, data): def callback(name, prop, data):
if name == "MapUnit": if name == "MapUnit":
new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add() new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add()
@@ -70,7 +73,7 @@ class Georeference(blenderbim.core.tool.Georeference):
return return
@classmethod @classmethod
def import_coordinate_operation(cls): def import_coordinate_operation(cls) -> None:
def callback(name, prop, data): def callback(name, prop, data):
if name in ("FirstCoordinate", "SecondCoordinate"): if name in ("FirstCoordinate", "SecondCoordinate"):
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
@@ -128,7 +131,7 @@ class Georeference(blenderbim.core.tool.Georeference):
return return
@classmethod @classmethod
def import_true_north(cls): def import_true_north(cls) -> None:
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
return return
@@ -150,7 +153,7 @@ class Georeference(blenderbim.core.tool.Georeference):
return return
@classmethod @classmethod
def export_projected_crs(cls): def export_projected_crs(cls) -> dict[str, Any]:
def callback(attributes, prop): def callback(attributes, prop):
if not prop.is_null and prop.name == "MapUnit": if not prop.is_null and prop.name == "MapUnit":
attributes[prop.name] = tool.Ifc.get().by_id(int(prop.enum_value)) attributes[prop.name] = tool.Ifc.get().by_id(int(prop.enum_value))
@@ -160,7 +163,7 @@ 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 export_coordinate_operation(cls): def export_coordinate_operation(cls) -> dict[str, Any]:
measure_type = None measure_type = None
def callback(attributes, prop): def callback(attributes, prop):
@@ -191,7 +194,7 @@ class Georeference(blenderbim.core.tool.Georeference):
return blenderbim.bim.helper.export_attributes(props.coordinate_operation, 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) -> Union[list[float], None]:
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
try: try:
return [float(props.true_north_abscissa), float(props.true_north_ordinate)] return [float(props.true_north_abscissa), float(props.true_north_ordinate)]
@@ -199,46 +202,48 @@ class Georeference(blenderbim.core.tool.Georeference):
print("ERROR, True North Abscissa and Ordinate expect a number") print("ERROR, True North Abscissa and Ordinate expect a number")
@classmethod @classmethod
def enable_editing(cls): def enable_editing(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing = True bpy.context.scene.BIMGeoreferenceProperties.is_editing = True
@classmethod @classmethod
def disable_editing(cls): def disable_editing(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing = False bpy.context.scene.BIMGeoreferenceProperties.is_editing = False
@classmethod @classmethod
def enable_editing_wcs(cls): def enable_editing_wcs(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing_wcs = True bpy.context.scene.BIMGeoreferenceProperties.is_editing_wcs = True
@classmethod @classmethod
def disable_editing_wcs(cls): def disable_editing_wcs(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing_wcs = False bpy.context.scene.BIMGeoreferenceProperties.is_editing_wcs = False
@classmethod @classmethod
def enable_editing_true_north(cls): def enable_editing_true_north(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = True bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = True
@classmethod @classmethod
def disable_editing_true_north(cls): def disable_editing_true_north(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = False bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = False
@classmethod @classmethod
def set_coordinates(cls, io, coordinates): def set_coordinates(cls, io: COORDINATE_TYPE, coordinates: list[float]) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
setattr(props, f"{io}_coordinates", ",".join([str(o) for o in coordinates])) setattr(props, f"{io}_coordinates", ",".join([str(o) for o in coordinates]))
@classmethod @classmethod
def get_coordinates(cls, io): def get_coordinates(cls, io: COORDINATE_TYPE) -> list[float]:
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
return [float(co) for co in getattr(props, f"{io}_coordinates").split(",")] return [float(co) for co in getattr(props, f"{io}_coordinates").split(",")]
@classmethod @classmethod
def get_cursor_location(cls): def get_cursor_location(cls) -> list[float]:
scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
return [o / scale for o in bpy.context.scene.cursor.location] return [o / scale for o in bpy.context.scene.cursor.location]
@classmethod @classmethod
def xyz2enh(cls, coordinates, should_return_in_map_units=True): def xyz2enh(
cls, coordinates: tuple[float, float, float], should_return_in_map_units: bool = True
) -> tuple[float, float, float]:
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset: if props.has_blender_offset:
coordinates = ifcopenshell.util.geolocation.xyz2enh( coordinates = ifcopenshell.util.geolocation.xyz2enh(
@@ -256,7 +261,7 @@ class Georeference(blenderbim.core.tool.Georeference):
) )
@classmethod @classmethod
def enh2xyz(cls, coordinates): def enh2xyz(cls, coordinates: tuple[float, float, float]) -> tuple[float, float, float]:
coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *coordinates) coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *coordinates)
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset: if props.has_blender_offset:
@@ -273,10 +278,10 @@ class Georeference(blenderbim.core.tool.Georeference):
return coordinates return coordinates
@classmethod @classmethod
def import_plot(cls, filepath): def import_plot(cls, filepath: str) -> None:
import bmesh import bmesh
def parse_csv(file_path): def parse_csv(file_path: str):
import csv import csv
with open(file_path, "r") as f: with open(file_path, "r") as f:
@@ -307,7 +312,7 @@ class Georeference(blenderbim.core.tool.Georeference):
bm.free() bm.free()
@classmethod @classmethod
def import_wcs(cls): def import_wcs(cls) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
wcs = None wcs = None
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False): for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
@@ -324,7 +329,7 @@ class Georeference(blenderbim.core.tool.Georeference):
props.wcs_x, props.wcs_y, props.wcs_z = map(str, placement[:, 3][:3]) props.wcs_x, props.wcs_y, props.wcs_z = map(str, placement[:, 3][:3])
@classmethod @classmethod
def export_wcs(cls): def export_wcs(cls) -> dict[str, float]:
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
return { return {
"x": float(props.wcs_x), "x": float(props.wcs_x),
@@ -334,7 +339,7 @@ class Georeference(blenderbim.core.tool.Georeference):
} }
@classmethod @classmethod
def set_wcs(cls, wcs): def set_wcs(cls, wcs: dict[str, float]) -> None:
ifcopenshell.api.georeference.edit_wcs(tool.Ifc.get(), **wcs, is_si=False) ifcopenshell.api.georeference.edit_wcs(tool.Ifc.get(), **wcs, is_si=False)
@classmethod @classmethod
@@ -259,7 +259,7 @@ class Usecase:
subelement_queue.extend(self.settings["library"].traverse(subelement, max_levels=1)[1:]) subelement_queue.extend(self.settings["library"].traverse(subelement, max_levels=1)[1:])
return new return new
def has_whitelisted_inverses(self, element): def has_whitelisted_inverses(self, element: ifcopenshell.entity_instance) -> bool:
for source_class, attributes in self.whitelisted_inverse_attributes.items(): for source_class, attributes in self.whitelisted_inverse_attributes.items():
if not element.is_a(source_class): if not element.is_a(source_class):
continue continue
@@ -274,6 +274,7 @@ class Usecase:
return True return True
elif value: elif value:
return True return True
return False
def check_inverses(self, element: ifcopenshell.entity_instance) -> None: def check_inverses(self, element: ifcopenshell.entity_instance) -> None:
for source_class, attributes in self.whitelisted_inverse_attributes.items(): for source_class, attributes in self.whitelisted_inverse_attributes.items():
@@ -318,7 +319,7 @@ class Usecase:
if new_attribute is not None: if new_attribute is not None:
new[i] = new_attribute new[i] = new_attribute
def is_another_asset(self, element): def is_another_asset(self, element: ifcopenshell.entity_instance) -> bool:
if element == self.settings["element"]: if element == self.settings["element"]:
return False return False
elif element.is_a("IfcFeatureElement"): elif element.is_a("IfcFeatureElement"):
@@ -332,7 +333,7 @@ class Usecase:
return True return True
return False return False
def reuse_existing_contexts(self): def reuse_existing_contexts(self) -> None:
added_contexts = set([e for e in self.added_elements.values() if e.is_a("IfcGeometricRepresentationContext")]) added_contexts = set([e for e in self.added_elements.values() if e.is_a("IfcGeometricRepresentationContext")])
added_contexts -= set(self.existing_contexts) added_contexts -= set(self.existing_contexts)
for added_context in added_contexts: for added_context in added_contexts:
@@ -344,7 +345,9 @@ class Usecase:
for added_context in added_contexts: for added_context in added_contexts:
ifcopenshell.util.element.remove_deep2(self.file, added_context) ifcopenshell.util.element.remove_deep2(self.file, added_context)
def get_equivalent_existing_context(self, added_context): def get_equivalent_existing_context(
self, added_context: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
for context in self.existing_contexts: for context in self.existing_contexts:
if context.is_a() != added_context.is_a(): if context.is_a() != added_context.is_a():
continue continue
@@ -361,7 +364,7 @@ class Usecase:
): ):
return context return context
def create_equivalent_context(self, added_context): def create_equivalent_context(self, added_context: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
if added_context.is_a("IfcGeometricRepresentationSubContext"): if added_context.is_a("IfcGeometricRepresentationSubContext"):
parent = self.get_equivalent_existing_context(added_context.ParentContext) parent = self.get_equivalent_existing_context(added_context.ParentContext)
if not parent: if not parent:
@@ -18,13 +18,14 @@
import math import math
import numpy as np import numpy as np
import numpy.typing as npt
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
from typing import NamedTuple, Optional, Union from typing import NamedTuple, Optional, Union
MatrixType = ifcopenshell.util.placement.MatrixType
class HelmertTransformation(NamedTuple): class HelmertTransformation(NamedTuple):
e: float e: float
@@ -159,7 +160,9 @@ def auto_xyz2enh(
return enh[0] / parameters.scale, enh[1] / parameters.scale, enh[2] / parameters.scale return enh[0] / parameters.scale, enh[1] / parameters.scale, enh[2] / parameters.scale
def auto_enh2xyz(ifc_file, easting, northing, height, is_specified_in_map_units: bool = True): def auto_enh2xyz(
ifc_file: ifcopenshell.file, easting: float, northing: float, height: float, is_specified_in_map_units: bool = True
) -> tuple[float, float, float]:
"""Convert from global map coordinate eastings, northings, and heights to local XYZ coordinates """Convert from global map coordinate eastings, northings, and heights to local XYZ coordinates
The necessary georeferencing map conversion is automatically detected from The necessary georeferencing map conversion is automatically detected from
@@ -351,7 +354,7 @@ def enh2xyz(
def local2global( def local2global(
matrix: npt.NDArray[np.float64], matrix: MatrixType,
eastings: float = 0.0, eastings: float = 0.0,
northings: float = 0.0, northings: float = 0.0,
orthogonal_height: float = 0.0, orthogonal_height: float = 0.0,
@@ -361,7 +364,7 @@ def local2global(
factor_x: float = 1.0, factor_x: float = 1.0,
factor_y: float = 1.0, factor_y: float = 1.0,
factor_z: float = 1.0, factor_z: float = 1.0,
) -> npt.NDArray[np.float64]: ) -> MatrixType:
"""Manually convert a 4x4 matrix from local to global coordinates """Manually convert a 4x4 matrix from local to global coordinates
This function is for advanced users as it allows you to specify your own This function is for advanced users as it allows you to specify your own
@@ -413,8 +416,8 @@ def local2global(
def auto_local2global( def auto_local2global(
ifc_file: ifcopenshell.file, matrix: npt.NDArray[np.float64], should_return_in_map_units: bool = True ifc_file: ifcopenshell.file, matrix: MatrixType, should_return_in_map_units: bool = True
) -> npt.NDArray[np.float64]: ) -> MatrixType:
"""Convert a local matrix to a global map matrix """Convert a local matrix to a global map matrix
The necessary georeferencing map conversion is automatically detected from The necessary georeferencing map conversion is automatically detected from
@@ -443,7 +446,7 @@ def auto_local2global(
def global2local( def global2local(
matrix: npt.NDArray[np.float64], matrix: MatrixType,
eastings: float = 0.0, eastings: float = 0.0,
northings: float = 0.0, northings: float = 0.0,
orthogonal_height: float = 0.0, orthogonal_height: float = 0.0,
@@ -453,7 +456,7 @@ def global2local(
factor_x: float = 1.0, factor_x: float = 1.0,
factor_y: float = 1.0, factor_y: float = 1.0,
factor_z: float = 1.0, factor_z: float = 1.0,
) -> npt.NDArray[np.float64]: ) -> MatrixType:
"""Manually convert a 4x4 matrix from global to local coordinates """Manually convert a 4x4 matrix from global to local coordinates
This function is for advanced users as it allows you to specify your own This function is for advanced users as it allows you to specify your own
@@ -504,8 +507,8 @@ def global2local(
def auto_global2local( def auto_global2local(
ifc_file: ifcopenshell.file, matrix: npt.NDArray[np.float64], is_specified_in_map_units: bool = True ifc_file: ifcopenshell.file, matrix: MatrixType, is_specified_in_map_units: bool = True
) -> npt.NDArray[np.float64]: ) -> MatrixType:
"""Convert a global map matrix to a local matrix """Convert a global map matrix to a local matrix
The necessary georeferencing map conversion is automatically detected from The necessary georeferencing map conversion is automatically detected from
@@ -651,7 +654,7 @@ def angle2yaxis(angle: float) -> tuple[float, float]:
return x, y return x, y
def get_wcs(ifc_file: ifcopenshell.file) -> Optional[npt.NDArray[np.float64]]: def get_wcs(ifc_file: ifcopenshell.file) -> Optional[MatrixType]:
"""Gets the WCS (prioritising 3D contexts) as a matrix """Gets the WCS (prioritising 3D contexts) as a matrix
:param: The IFC file :param: The IFC file