First Commit
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Manage georeferencing metadata
|
||||
|
||||
IFC model geometry may have a coordinate reference system (CRS) assigned to it.
|
||||
It may also optionally have a map conversion defined to transform to and from
|
||||
map coordinates and project local engineering coordinates.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_georeferencing import add_georeferencing
|
||||
from .edit_georeferencing import edit_georeferencing
|
||||
from .edit_true_north import edit_true_north
|
||||
from .edit_wcs import edit_wcs
|
||||
from .remove_georeferencing import remove_georeferencing
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_georeferencing",
|
||||
"edit_georeferencing",
|
||||
"edit_true_north",
|
||||
"edit_wcs",
|
||||
"remove_georeferencing",
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,105 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.georeference
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def add_georeferencing(file: ifcopenshell.file, ifc_class: str = "IfcMapConversion", name: str = "EPSG:3857") -> None:
|
||||
"""Add empty georeferencing entities to a model
|
||||
|
||||
By default, models are not georeferenced. Georeferencing requires two
|
||||
entities: a definition of the projected coordinated reference system
|
||||
(CRS) used, and the transformation parameters between any local coordinate
|
||||
system and that projected CRS if any.
|
||||
|
||||
This function will create the entities to store the projected CRS and
|
||||
map conversion transformation, but will leave all the parameters blank.
|
||||
It is this the users responsibility to specify the correct
|
||||
georeferencing parameters. See
|
||||
ifcopenshell.api.georeference.edit_georeferencing.
|
||||
|
||||
:param ifc_class: A type of IfcCoordinateOperation. For IFC2X3, this has no
|
||||
impact and only uses ePSet_MapConversion.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcopenshell.api.georeference.add_georeferencing(model)
|
||||
"""
|
||||
if file.schema == "IFC2X3":
|
||||
if not (project := file.by_type("IfcProject")):
|
||||
return
|
||||
project = project[0]
|
||||
if ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS"):
|
||||
return
|
||||
conversion = ifcopenshell.api.pset.add_pset(file, project, "ePSet_MapConversion")
|
||||
crs = ifcopenshell.api.pset.add_pset(file, project, "ePSet_ProjectedCRS")
|
||||
ifcopenshell.api.pset.edit_pset(file, crs, properties={"Name": name})
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
file,
|
||||
conversion,
|
||||
properties={
|
||||
"Eastings": file.createIfcLengthMeasure(0),
|
||||
"Northings": file.createIfcLengthMeasure(0),
|
||||
"OrthogonalHeight": file.createIfcLengthMeasure(0),
|
||||
},
|
||||
)
|
||||
return
|
||||
has_crs = bool(file.by_type("IfcProjectedCRS"))
|
||||
has_conversion = bool(file.by_type("IfcCoordinateOperation"))
|
||||
if has_crs and has_conversion:
|
||||
return
|
||||
if has_crs or has_conversion:
|
||||
# This is technically invalid, but we shall forgive the industry here if they are wrong ...
|
||||
ifcopenshell.api.georeference.remove_georeferencing(file)
|
||||
source_crs = None
|
||||
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.ContextType == "Model":
|
||||
source_crs = context
|
||||
break
|
||||
if not source_crs:
|
||||
return
|
||||
projected_crs = file.create_entity("IfcProjectedCRS", Name=name)
|
||||
if ifc_class == "IfcMapConversion":
|
||||
file.create_entity(
|
||||
ifc_class, SourceCRS=source_crs, TargetCRS=projected_crs, Eastings=0, Northings=0, OrthogonalHeight=0
|
||||
)
|
||||
elif ifc_class == "IfcMapConversionScaled":
|
||||
file.create_entity(
|
||||
ifc_class,
|
||||
SourceCRS=source_crs,
|
||||
TargetCRS=projected_crs,
|
||||
Eastings=0,
|
||||
Northings=0,
|
||||
OrthogonalHeight=0,
|
||||
FactorX=1,
|
||||
FactorY=1,
|
||||
FactorZ=1,
|
||||
)
|
||||
elif ifc_class == "IfcRigidOperation":
|
||||
file.create_entity(
|
||||
ifc_class,
|
||||
SourceCRS=source_crs,
|
||||
TargetCRS=projected_crs,
|
||||
FirstCoordinate=file.createIfcLengthMeasure(0),
|
||||
SecondCoordinate=file.createIfcLengthMeasure(0),
|
||||
)
|
||||
@@ -0,0 +1,117 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def edit_georeferencing(
|
||||
file: ifcopenshell.file,
|
||||
coordinate_operation: Optional[dict[str, Any]] = None,
|
||||
projected_crs: Optional[dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Edits the attributes of a map conversion, projected CRS, and true north
|
||||
|
||||
Setting the correct georeferencing parameters is a complex topic and
|
||||
should ideally be done with three parties present: the lead architect,
|
||||
surveyor, and a third-party digital engineer with expertise in IFC to
|
||||
moderate. For more information, read the Bonsai documentation
|
||||
for Georeferencing:
|
||||
https://docs.bonsaibim.org/guides/authoring/georeferencing.html
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcCoordinateOperation, consult the IFC documentation.
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcProjectedCRS, consult the IFC documentation.
|
||||
|
||||
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.
|
||||
'MapUnit' attribute in IFC2X3 should be presented as a full unit name (string),
|
||||
in other IFC versions it's presented an IfcNamedUnit.
|
||||
:param projected_crs: The IfcProjectedCRS dictionary of attribute
|
||||
names and values you want to edit.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcopenshell.api.georeference.add_georeferencing(model)
|
||||
# This is the simplest scenario, a defined CRS (GDA2020 / MGA Zone
|
||||
# 56, typically used in Sydney, Australia) but with no local
|
||||
# coordinates. This is only recommended for horizontal construction
|
||||
# projects, not for vertical construction (such as buildings).
|
||||
ifcopenshell.api.georeference.edit_georeferencing(model,
|
||||
projected_crs={"Name": "EPSG:7856"})
|
||||
|
||||
# For buildings, it is almost always recommended to specify map
|
||||
# conversion parameters to a false origin and orientation to project
|
||||
# north. See the diagram in the Bonsai Georeferencing
|
||||
# documentation for correct calculation of the X Axis Abcissa and
|
||||
# Ordinate.
|
||||
ifcopenshell.api.georeference.edit_georeferencing(model,
|
||||
projected_crs={"Name": "EPSG:7856"},
|
||||
coordinate_operation={
|
||||
"Eastings": 335087.17, # The architect nominates a false origin
|
||||
"Northings": 6251635.41, # The architect nominates a false origin
|
||||
# Note: this is the angle difference between Project North
|
||||
# and Grid North. Remember: True North should never be used!
|
||||
"XAxisAbscissa": cos(radians(-30)), # The architect nominates a project north
|
||||
"XAxisOrdinate": sin(radians(-30)), # The architect nominates a project north
|
||||
"Scale": 0.99956, # Ask your surveyor for your site's average combined scale factor!
|
||||
})
|
||||
"""
|
||||
if file.schema == "IFC2X3":
|
||||
if not (project := file.by_type("IfcProject")):
|
||||
return
|
||||
project = project[0]
|
||||
if projected_crs:
|
||||
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)
|
||||
elif v is not None:
|
||||
v = file.createIfcIdentifier(v)
|
||||
ifcopenshell.api.pset.edit_pset(file, crs, properties=projected_crs)
|
||||
if coordinate_operation:
|
||||
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
|
||||
if projected_crs:
|
||||
crs = file.by_type("IfcProjectedCRS")[0]
|
||||
for name, value in projected_crs.items():
|
||||
setattr(crs, name, value)
|
||||
if coordinate_operation:
|
||||
conversion = file.by_type("IfcCoordinateOperation")[0]
|
||||
for name, value in coordinate_operation.items():
|
||||
setattr(conversion, name, value)
|
||||
@@ -0,0 +1,73 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
|
||||
|
||||
def edit_true_north(file: ifcopenshell.file, true_north: Optional[Union[tuple[float, float], float]] = 0.0) -> None:
|
||||
"""Edits the true north
|
||||
|
||||
Given project north being up (i.e. a vector of 0, 1), true north is defined
|
||||
as a unitised 2D vector pointing to true north. Alternatively, true north
|
||||
may be defined as a rotation from project north to true north.
|
||||
Anticlockwise is positive.
|
||||
|
||||
Note that true north is not part of georeferencing, and is only optionally
|
||||
provided as a reference value, typically for solar analysis. Remember: grid
|
||||
north (what your surveyor will typically use) is not the same as true
|
||||
north!
|
||||
|
||||
:param true_north: A unitised 2D vector, where each ordinate is a float, or
|
||||
an angle in decimal degrees where anticlockwise is positive.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Both of these are identical, and indicate that:
|
||||
# - If project north is up the page, true north is in the top left
|
||||
# - The building is therefore facing north east
|
||||
ifcopenshell.api.georeference.edit_true_north(model, true_north=30)
|
||||
ifcopenshell.api.georeference.edit_true_north(model, true_north=(-0.5, 0.8660254))
|
||||
|
||||
# This unsets true north
|
||||
ifcopenshell.api.georeference.edit_true_north(model, true_north=None)
|
||||
"""
|
||||
if isinstance(true_north, (float, int)):
|
||||
x, y = ifcopenshell.util.geolocation.angle2yaxis(true_north)
|
||||
elif true_north is not None:
|
||||
x, y = true_north
|
||||
|
||||
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.TrueNorth and true_north is None:
|
||||
old_true_north = context.TrueNorth
|
||||
context.TrueNorth = None
|
||||
if not file.get_total_inverses(old_true_north):
|
||||
ifcopenshell.util.element.remove_deep2(file, old_true_north)
|
||||
continue
|
||||
|
||||
if context.TrueNorth:
|
||||
if file.get_total_inverses(context.TrueNorth) != 1:
|
||||
context.TrueNorth = file.create_entity("IfcDirection")
|
||||
else:
|
||||
context.TrueNorth = file.create_entity("IfcDirection")
|
||||
context.TrueNorth.DirectionRatios = (x, y)
|
||||
@@ -0,0 +1,95 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from math import cos, radians, sin
|
||||
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
|
||||
|
||||
def edit_wcs(
|
||||
file: ifcopenshell.file,
|
||||
x: float = 0.0,
|
||||
y: float = 0.0,
|
||||
z: float = 0.0,
|
||||
rotation: float = 0.0,
|
||||
is_si: bool = True,
|
||||
) -> None:
|
||||
"""Edits the WCS for all geometric contexts to a translation and rotation
|
||||
|
||||
Typically, a project's local engineering origin (0, 0, 0) has a coordinate
|
||||
operation (e.g. map conversion) to a projected CRS. If a WCS is provided,
|
||||
the coordinate operation is relative to the WCS, not the local engineering
|
||||
origin.
|
||||
|
||||
For example, if I have an IfcSite with a placement at (10, 0, 0) and a map
|
||||
conversion of (50, 0, 0), my IfcSite's local XYZ is at (10, 0, 0) with an
|
||||
ENH (Easting, Northing, Height) of (60, 0, 0).
|
||||
|
||||
If I then define by WCS at (15, 0, 0), my IfcSite's local XYZ is still at
|
||||
(10, 0, 0) but its ENH is now at (45, 0, 0).
|
||||
|
||||
It's recommended to leave the WCS at 0,0,0. Please :)
|
||||
|
||||
:param x: The X translation of the WCS
|
||||
:param y: The Y translation of the WCS
|
||||
:param z: The Z translation of the WCS
|
||||
:param rotation: The rotation around the Z axis (i.e. top down plan view)
|
||||
in decimal degrees of the WCS. Anticlockwise is positive.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# This is the simplest scenario, resetting the WCS to 0,0,0 with no rotation (recommended)
|
||||
ifcopenshell.api.georeference.edit_wcs(model)
|
||||
"""
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
||||
builder = ShapeBuilder(file)
|
||||
if np.isclose(rotation, 0):
|
||||
xaxis_x = 1.0
|
||||
xaxis_y = 0.0
|
||||
else:
|
||||
xaxis_x = cos(radians(rotation))
|
||||
xaxis_y = sin(radians(rotation))
|
||||
if np.allclose((x, y, z), (0, 0, 0)):
|
||||
x = y = z = 0.0
|
||||
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
old_wcs = context.WorldCoordinateSystem
|
||||
if context.CoordinateSpaceDimension == 3:
|
||||
if is_si:
|
||||
xyz = (x / unit_scale, y / unit_scale, z / unit_scale)
|
||||
else:
|
||||
xyz = (x, y, z)
|
||||
placement = builder.create_axis2_placement_3d(xyz, (0.0, 0.0, 1.0), (xaxis_x, xaxis_y, 0.0))
|
||||
elif context.CoordinateSpaceDimension == 2:
|
||||
if is_si:
|
||||
point = file.createIfcCartesianPoint((x / unit_scale, y / unit_scale))
|
||||
else:
|
||||
point = file.createIfcCartesianPoint((x, y))
|
||||
placement = file.createIfcAxis2Placement2D(
|
||||
point,
|
||||
file.createIfcDirection((xaxis_x, xaxis_y)),
|
||||
)
|
||||
context.WorldCoordinateSystem = placement
|
||||
if file.get_total_inverses(old_wcs) == 0:
|
||||
ifcopenshell.util.element.remove_deep2(file, old_wcs)
|
||||
@@ -0,0 +1,51 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_georeferencing(file: ifcopenshell.file) -> None:
|
||||
"""Remove georeferencing data
|
||||
|
||||
All georeferencing parameters such as projected CRS and map conversion
|
||||
data will be lost.
|
||||
|
||||
In IFC2X3, the psets will be removed from the IfcProject.
|
||||
|
||||
Example:
|
||||
|
||||
ifcopenshell.api.georeference.add_georeferencing(model)
|
||||
# Let's change our mind
|
||||
ifcopenshell.api.georeference.remove_georeferencing(model)
|
||||
"""
|
||||
if file.schema == "IFC2X3":
|
||||
project = file.by_type("IfcProject")[0]
|
||||
if pset := ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS"):
|
||||
ifcopenshell.api.pset.remove_pset(file, project, file.by_id(pset["id"]))
|
||||
if pset := ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion"):
|
||||
ifcopenshell.api.pset.remove_pset(file, project, file.by_id(pset["id"]))
|
||||
return
|
||||
for projected_crs in file.by_type("IfcProjectedCRS"):
|
||||
if (unit := projected_crs.MapUnit) and file.get_total_inverses(unit) == 1:
|
||||
projected_crs.MapUnit = None
|
||||
ifcopenshell.util.element.remove_deep2(file, unit)
|
||||
file.remove(projected_crs)
|
||||
for coordinate_operation in file.by_type("IfcCoordinateOperation"):
|
||||
file.remove(coordinate_operation)
|
||||
Reference in New Issue
Block a user