Split true north in API and Blender UI to be editable independent of georeferencing

They are two separate concepts, after all.
This commit is contained in:
Dion Moult
2024-06-23 23:19:27 +10:00
parent 013b138e5c
commit e295d596fe
11 changed files with 273 additions and 110 deletions
@@ -24,7 +24,6 @@ def edit_georeferencing(
file: ifcopenshell.file,
coordinate_operation: Optional[dict[str, Any]] = None,
projected_crs: Optional[dict[str, Any]] = None,
true_north: Optional[tuple[float, float]] = None,
) -> None:
"""Edits the attributes of a map conversion, projected CRS, and true north
@@ -36,28 +35,18 @@ def edit_georeferencing(
https://docs.blenderbim.org/users/georeferencing.html
For more information about the attributes and data types of an
IfcMapConversion, consult the IFC documentation.
IfcCoordinateOperation, consult the IFC documentation.
For more information about the attributes and data types of an
IfcProjectedCRS, consult the IFC documentation.
True north is defined as a unitised 2D vector pointing to true north.
Note that true north is not part of georeferencing, and is only
optionally provided as a reference value, typically for solar analysis.
See ifcopenshell.util.geolocation for more utilities to convert to and
from local and map coordinates to check your results.
:param coordinate_operation: The dictionary of attribute names and values
you want to edit.
:type coordinate_operation: dict, optional
:param projected_crs: The IfcProjectedCRS dictionary of attribute
names and values you want to edit.
:type projected_crs: dict, optional
:param true_north: A unitised 2D vector, where each ordinate is a float
:type true_north: tuple[float, float], optional
:return: None
:rtype: None
Example:
@@ -88,64 +77,32 @@ def edit_georeferencing(
"Scale": 0.99956, # Ask your surveyor for your site's average combined scale factor!
})
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {
"coordinate_operation": coordinate_operation or {},
"projected_crs": projected_crs or {},
"true_north": true_north or [],
}
return usecase.execute()
class Usecase:
def execute(self):
self.set_true_north()
if self.file.schema == "IFC2X3":
if not (project := self.file.by_type("IfcProject")):
return
project = project[0]
if (crs := ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS")):
crs = self.file.by_id(crs["id"])
for k, v in self.settings["projected_crs"].items():
if k == "Description":
v = self.file.createIfcText(v)
elif k == "Name":
v = self.file.createIfcLabel(v)
else:
v = self.file.createIfcIdentifier(v)
ifcopenshell.api.pset.edit_pset(self.file, crs, properties=self.settings["projected_crs"])
if (conversion := ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")):
conversion = self.file.by_id(conversion["id"])
for k, v in self.settings["coordinate_operation"].items():
if k in ("XAxisAbscissa", "XAxisOrdinate", "Scale"):
v = self.file.createIfcReal(v)
else:
v = self.file.createIfcLengthMeasure(v)
ifcopenshell.api.pset.edit_pset(self.file, conversion, properties=self.settings["coordinate_operation"])
if file.schema == "IFC2X3":
if not (project := file.by_type("IfcProject")):
return
coordinate_operation = self.file.by_type("IfcCoordinateOperation")[0]
projected_crs = self.file.by_type("IfcProjectedCRS")[0]
for name, value in self.settings["coordinate_operation"].items():
setattr(coordinate_operation, name, value)
for name, value in self.settings["projected_crs"].items():
setattr(projected_crs, name, value)
def set_true_north(self):
if not self.settings["true_north"]:
return
for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.TrueNorth:
if len(self.file.get_inverse(context.TrueNorth)) != 1:
context.TrueNorth = self.file.create_entity("IfcDirection")
else:
context.TrueNorth = self.file.create_entity("IfcDirection")
direction = context.TrueNorth
if self.settings["true_north"] is None:
# TODO: code will never be executed since None value
# is substituted by an empty list
context.TrueNorth = self.settings["true_north"]
elif context.CoordinateSpaceDimension == 2:
direction.DirectionRatios = self.settings["true_north"][0:2]
else:
direction.DirectionRatios = self.settings["true_north"][0:2] + [0.0]
project = project[0]
if crs := ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS"):
crs = file.by_id(crs["id"])
for k, v in projected_crs.items():
if k == "Description":
v = file.createIfcText(v)
elif k == "Name":
v = file.createIfcLabel(v)
else:
v = file.createIfcIdentifier(v)
ifcopenshell.api.pset.edit_pset(file, crs, properties=projected_crs)
if conversion := ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion"):
conversion = file.by_id(conversion["id"])
for k, v in coordinate_operation.items():
if k in ("XAxisAbscissa", "XAxisOrdinate", "Scale"):
v = file.createIfcReal(v)
else:
v = file.createIfcLengthMeasure(v)
ifcopenshell.api.pset.edit_pset(file, conversion, properties=coordinate_operation)
return
conversion = file.by_type("IfcCoordinateOperation")[0]
crs = file.by_type("IfcProjectedCRS")[0]
for name, value in coordinate_operation.items():
setattr(conversion, name, value)
for name, value in projected_crs.items():
setattr(crs, name, value)