First Commit
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
# 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/>.
|
||||
|
||||
"""Utility functions for extracting IFC data
|
||||
|
||||
Data in IFC files is represented using relationships between IFC entities. To
|
||||
extract data like "what properties does this wall have" involves looping
|
||||
through these relationships which can be tedious.
|
||||
|
||||
This module makes it easy to get commonly requested data from IFC
|
||||
relationships, such as properties of a wall, what elements are connected to
|
||||
pipes, dates from work schedules, filtering maintainable elements, and more.
|
||||
|
||||
The most commonly used utilities to help you get started are:
|
||||
|
||||
- See :mod:`ifcopenshell.util.element` which contains a lot of useful functions
|
||||
for getting most common relationships on elements.
|
||||
- See :func:`ifcopenshell.util.element.get_psets` to get all properties of an
|
||||
entity, like a wall.
|
||||
- See :func:`ifcopenshell.util.element.get_type` to get the corresponding type
|
||||
object (e.g. the wall type definition) of a single occurrence (e.g. an
|
||||
individual wall).
|
||||
- See :func:`ifcopenshell.util.placement.get_local_placement` to get the XYZ
|
||||
placement point of a single object.
|
||||
- See :func:`ifcopenshell.util.unit.calculate_unit_scale` to convert between SI
|
||||
units and project units.
|
||||
- See :mod:`ifcopenshell.util.shape` to calculate quantities from processed
|
||||
geometry.
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,111 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Thomas Krijnen <thomas@aecgeeks.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 math
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
|
||||
def add_linear_placement_fallback_position(file: ifcopenshell.file) -> ifcopenshell.file:
|
||||
import ifcopenshell.api.alignment
|
||||
|
||||
patched_file = ifcopenshell.file.from_string(file.wrapped_data.to_string())
|
||||
|
||||
linear_placements = patched_file.by_type("IfcLinearPlacement")
|
||||
for lp in linear_placements:
|
||||
ifcopenshell.api.alignment.update_fallback_position(patched_file, lp)
|
||||
|
||||
return patched_file
|
||||
|
||||
|
||||
def create_alignment_geometry(file: ifcopenshell.file) -> ifcopenshell.file:
|
||||
import ifcopenshell.api.alignment
|
||||
|
||||
patched_file = ifcopenshell.file.from_string(file.wrapped_data.to_string())
|
||||
|
||||
alignments = patched_file.by_type("IfcAlignment")
|
||||
for alignment in alignments:
|
||||
ifcopenshell.api.alignment.create_representation(patched_file, alignment)
|
||||
|
||||
return patched_file
|
||||
|
||||
|
||||
def append_zero_length_segments(file: ifcopenshell.file) -> ifcopenshell.file:
|
||||
"""Appends zero length segments to all alignment layouts and layout geometry, if missing."""
|
||||
import ifcopenshell.api.alignment
|
||||
|
||||
patched_file = ifcopenshell.file.from_string(file.wrapped_data.to_string())
|
||||
|
||||
alignments = patched_file.by_type("IfcAlignment")
|
||||
for alignment in alignments:
|
||||
layouts = ifcopenshell.api.alignment.get_alignment_layouts(alignment)
|
||||
for layout in layouts:
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(patched_file, layout, include_referent=False)
|
||||
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||
if curve:
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(patched_file, curve)
|
||||
|
||||
return patched_file
|
||||
|
||||
|
||||
def station_as_string(file: ifcopenshell.file, sta: float):
|
||||
"""
|
||||
Returns a stringized version of a station. Example 100.0 is 1+00.00 as a stationing string.
|
||||
If the project units are SI-based, the string is in the format xxx+yyy.zzz
|
||||
If the project units are Emperial-based, the string is in the format xx+yy.zz
|
||||
:param station: the station to be stringized
|
||||
:return: stringized station
|
||||
"""
|
||||
|
||||
unit_type = ifcopenshell.util.unit.get_project_unit(file, "LENGTHUNIT")
|
||||
if unit_type.is_a("IfcConversionBasedUnit"):
|
||||
station = ifcopenshell.util.unit.convert(
|
||||
sta, from_unit=unit_type.Name, from_prefix=None, to_unit="foot", to_prefix=None
|
||||
)
|
||||
plus_seperator = 2
|
||||
precision = 2
|
||||
else:
|
||||
station = ifcopenshell.util.unit.convert(
|
||||
sta, from_unit=unit_type.Name, from_prefix=unit_type.Prefix, to_unit="meter", to_prefix=None
|
||||
)
|
||||
plus_seperator = 3
|
||||
precision = 3
|
||||
|
||||
value = math.fabs(station)
|
||||
|
||||
shifter = math.pow(10.0, plus_seperator)
|
||||
v1 = math.floor(value / shifter)
|
||||
v2 = value - v1 * shifter
|
||||
|
||||
# Check to make sure that v2 is not basically the same as shifter
|
||||
# If station = 69500.00000, we sometimes get 694+100.00 instead of 695+00.00
|
||||
if math.isclose(v2 - shifter, 0.0, abs_tol=5.0 * math.pow(10.0, -(precision + 1))):
|
||||
v2 = 0.0
|
||||
v1 += 1
|
||||
|
||||
v1 = -1 * v1 if station < 0 else v1
|
||||
|
||||
station_string = "{:d}+{:0{}.{}f}".format(v1, v2, plus_seperator + precision + 1, precision)
|
||||
|
||||
# special case when v1 is 0 and station is negative, the string above doesn't get the leading
|
||||
# negative sign. this snippet fixes that
|
||||
if v1 == 0 and station < 0:
|
||||
station_string = "-" + station_string
|
||||
|
||||
return station_string
|
||||
@@ -0,0 +1,81 @@
|
||||
# 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 Literal, Union
|
||||
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
|
||||
PrimitiveType = Literal["entity", "string", "float", "integer", "boolean", "enum", "binary"]
|
||||
ComplexPrimitiveType = Literal["list", "array", "set"]
|
||||
PrimitiveTypeOutput = Union[
|
||||
PrimitiveType,
|
||||
tuple[ComplexPrimitiveType, "PrimitiveTypeOutput"],
|
||||
tuple[Literal["select"], tuple["PrimitiveTypeOutput", ...]],
|
||||
None,
|
||||
]
|
||||
|
||||
|
||||
def get_primitive_type(
|
||||
attribute_or_data_type: Union[ifcopenshell_wrapper.attribute, ifcopenshell_wrapper.parameter_type],
|
||||
) -> PrimitiveTypeOutput:
|
||||
if isinstance(attribute_or_data_type, ifcopenshell_wrapper.attribute):
|
||||
data_type = str(attribute_or_data_type.type_of_attribute())
|
||||
else:
|
||||
data_type = str(attribute_or_data_type)
|
||||
if data_type.find("<type") == 0:
|
||||
return get_primitive_type(data_type[data_type[1:].find("<") + 1 :])
|
||||
elif data_type.find("<list") == 0:
|
||||
return ("list", get_primitive_type(data_type[data_type[1:].find("<") + 1 :]))
|
||||
elif data_type.find("<array") == 0:
|
||||
return ("array", get_primitive_type(data_type[data_type[1:].find("<") + 1 :]))
|
||||
elif data_type.find("<set") == 0:
|
||||
return ("set", get_primitive_type(data_type[data_type[1:].find("<") + 1 :]))
|
||||
elif data_type.find("<select") == 0:
|
||||
select_definition = data_type[data_type.find("(") + 1 : data_type.find(")")].split("|")
|
||||
select_types = [get_primitive_type(d.strip()) for d in select_definition]
|
||||
return ("select", tuple(select_types))
|
||||
elif "<entity" in data_type:
|
||||
return "entity"
|
||||
elif "<string>" in data_type:
|
||||
return "string"
|
||||
elif "<real>" in data_type:
|
||||
return "float"
|
||||
elif "<number>" in data_type or "<integer>" in data_type:
|
||||
return "integer"
|
||||
elif "<boolean>" in data_type:
|
||||
return "boolean"
|
||||
elif "<logical>" in data_type or "<enumeration" in data_type:
|
||||
return "enum"
|
||||
elif "<binary" in data_type:
|
||||
return "binary"
|
||||
|
||||
|
||||
def get_enum_items(attribute: ifcopenshell_wrapper.attribute) -> tuple[str, ...]:
|
||||
named_type = attribute.type_of_attribute().as_named_type()
|
||||
assert named_type
|
||||
enumeration = named_type.declared_type().as_enumeration_type()
|
||||
assert enumeration
|
||||
return enumeration.enumeration_items()
|
||||
|
||||
|
||||
def get_select_items(attribute: ifcopenshell_wrapper.attribute) -> tuple[ifcopenshell_wrapper.declaration, ...]:
|
||||
named_type = attribute.type_of_attribute().as_named_type()
|
||||
assert named_type
|
||||
select_type = named_type.declared_type().as_select_type()
|
||||
assert select_type
|
||||
return select_type.select_list()
|
||||
@@ -0,0 +1,251 @@
|
||||
{
|
||||
"IfcPerformanceHistory": {
|
||||
"Identification": "LifeCyclePhase"
|
||||
},
|
||||
"IfcProcedure": {
|
||||
"Identification": "ProcedureID",
|
||||
"LongDescription": "ProcedureType",
|
||||
"PredefinedType": "UserDefinedProcedureType"
|
||||
},
|
||||
"IfcTask": {
|
||||
"Identification": "TaskId",
|
||||
"LongDescription": "Status",
|
||||
"Status": "WorkMethod",
|
||||
"WorkMethod": "IsMilestone",
|
||||
"IsMilestone": "Priority"
|
||||
},
|
||||
"IfcWorkPlan": {
|
||||
"Identification": "Identifier",
|
||||
"PredefinedType": "WorkControlType"
|
||||
},
|
||||
"IfcWorkSchedule": {
|
||||
"Identification": "Identifier",
|
||||
"PredefinedType": "WorkControlType"
|
||||
},
|
||||
"IfcSpace": {
|
||||
"PredefinedType": "InteriorOrExteriorSpace"
|
||||
},
|
||||
"IfcTransportElement": {
|
||||
"PredefinedType": "OperationType"
|
||||
},
|
||||
"IfcBuildingElementProxy": {
|
||||
"PredefinedType": "CompositionType"
|
||||
},
|
||||
"IfcRamp": {
|
||||
"PredefinedType": "ShapeType"
|
||||
},
|
||||
"IfcRelCoversSpaces": {
|
||||
"RelatingSpace": "RelatedSpace"
|
||||
},
|
||||
"IfcRoof": {
|
||||
"PredefinedType": "ShapeType"
|
||||
},
|
||||
"IfcStair": {
|
||||
"PredefinedType": "ShapeType"
|
||||
},
|
||||
"IfcAsset": {
|
||||
"Identification": "AssetID"
|
||||
},
|
||||
"IfcInventory": {
|
||||
"PredefinedType": "InventoryType"
|
||||
},
|
||||
"IfcActionRequest": {
|
||||
"Identification": "RequestID"
|
||||
},
|
||||
"IfcCostSchedule": {
|
||||
"Identification": "SubmittedBy",
|
||||
"PredefinedType": "PreparedBy",
|
||||
"Status": "SubmittedOn",
|
||||
"SubmittedOn": "Status",
|
||||
"UpdateDate": "TargetUsers"
|
||||
},
|
||||
"IfcPermit": {
|
||||
"Identification": "PermitID"
|
||||
},
|
||||
"IfcProjectOrder": {
|
||||
"Identification": "ID"
|
||||
},
|
||||
"IfcConstructionEquipmentResource": {
|
||||
"Identification": "ResourceIdentifier",
|
||||
"LongDescription": "ResourceGroup",
|
||||
"Usage": "ResourceConsumption",
|
||||
"BaseCosts": "BaseQuantity"
|
||||
},
|
||||
"IfcConstructionMaterialResource": {
|
||||
"Identification": "ResourceIdentifier",
|
||||
"LongDescription": "ResourceGroup",
|
||||
"Usage": "ResourceConsumption",
|
||||
"BaseCosts": "BaseQuantity",
|
||||
"BaseQuantity": "Suppliers",
|
||||
"PredefinedType": "UsageRatio"
|
||||
},
|
||||
"IfcConstructionProductResource": {
|
||||
"Identification": "ResourceIdentifier",
|
||||
"LongDescription": "ResourceGroup",
|
||||
"Usage": "ResourceConsumption",
|
||||
"BaseCosts": "BaseQuantity"
|
||||
},
|
||||
"IfcCrewResource": {
|
||||
"Identification": "ResourceIdentifier",
|
||||
"LongDescription": "ResourceGroup",
|
||||
"Usage": "ResourceConsumption",
|
||||
"BaseCosts": "BaseQuantity"
|
||||
},
|
||||
"IfcLaborResource": {
|
||||
"Identification": "ResourceIdentifier",
|
||||
"LongDescription": "ResourceGroup",
|
||||
"Usage": "ResourceConsumption",
|
||||
"BaseCosts": "BaseQuantity",
|
||||
"BaseQuantity": "SkillSet"
|
||||
},
|
||||
"IfcSubContractResource": {
|
||||
"Identification": "ResourceIdentifier",
|
||||
"LongDescription": "ResourceGroup",
|
||||
"Usage": "ResourceConsumption",
|
||||
"BaseCosts": "BaseQuantity",
|
||||
"BaseQuantity": "SubContractor",
|
||||
"PredefinedType": "JobDescription"
|
||||
},
|
||||
"IfcStructuralLinearAction": {
|
||||
"ProjectedOrTrue": "CausedBy",
|
||||
"PredefinedType": "ProjectedOrTrue"
|
||||
},
|
||||
"IfcStructuralPlanarAction": {
|
||||
"ProjectedOrTrue": "CausedBy",
|
||||
"PredefinedType": "ProjectedOrTrue"
|
||||
},
|
||||
"IfcReinforcingBar": {
|
||||
"PredefinedType": "BarRole"
|
||||
},
|
||||
"IfcOrganization": {
|
||||
"Identification": "Id"
|
||||
},
|
||||
"IfcPerson": {
|
||||
"Identification": "Id"
|
||||
},
|
||||
"IfcApproval": {
|
||||
"Identifier": "Description",
|
||||
"Name": "ApprovalDateTime",
|
||||
"Description": "ApprovalStatus",
|
||||
"TimeOfApproval": "ApprovalLevel",
|
||||
"Status": "ApprovalQualifier",
|
||||
"Level": "Name",
|
||||
"Qualifier": "Identifier"
|
||||
},
|
||||
"IfcApprovalRelationship": {
|
||||
"Name": "RelatedApproval",
|
||||
"Description": "RelatingApproval",
|
||||
"RelatingApproval": "Description",
|
||||
"RelatedApprovals": "Name"
|
||||
},
|
||||
"IfcObjective": {
|
||||
"LogicalAggregator": "ResultValues"
|
||||
},
|
||||
"IfcCostValue": {
|
||||
"Category": "CostType"
|
||||
},
|
||||
"IfcCurrencyRelationship": {
|
||||
"Name": "RelatingMonetaryUnit",
|
||||
"Description": "RelatedMonetaryUnit",
|
||||
"RelatingMonetaryUnit": "ExchangeRate",
|
||||
"RelatedMonetaryUnit": "RateDateTime",
|
||||
"ExchangeRate": "RateSource"
|
||||
},
|
||||
"IfcClassificationReference": {
|
||||
"Identification": "ItemReference"
|
||||
},
|
||||
"IfcDocumentInformation": {
|
||||
"Identification": "DocumentId",
|
||||
"Location": "DocumentReferences"
|
||||
},
|
||||
"IfcDocumentInformationRelationship": {
|
||||
"Name": "RelatingDocument",
|
||||
"Description": "RelatedDocuments",
|
||||
"RelatingDocument": "RelationshipType"
|
||||
},
|
||||
"IfcDocumentReference": {
|
||||
"Identification": "ItemReference"
|
||||
},
|
||||
"IfcLibraryInformation": {
|
||||
"Location": "LibraryReference"
|
||||
},
|
||||
"IfcLibraryReference": {
|
||||
"Identification": "ItemReference"
|
||||
},
|
||||
"IfcMaterialProperties": {
|
||||
"Properties": "ExtendedProperties"
|
||||
},
|
||||
"IfcBlobTexture": {
|
||||
"Mode": "TextureType",
|
||||
"Parameter": "RasterFormat",
|
||||
"RasterFormat": "RasterCode"
|
||||
},
|
||||
"IfcExternallyDefinedHatchStyle": {
|
||||
"Identification": "ItemReference"
|
||||
},
|
||||
"IfcExternallyDefinedSurfaceStyle": {
|
||||
"Identification": "ItemReference"
|
||||
},
|
||||
"IfcExternallyDefinedTextFont": {
|
||||
"Identification": "ItemReference"
|
||||
},
|
||||
"IfcImageTexture": {
|
||||
"Mode": "TextureType",
|
||||
"Parameter": "UrlReference"
|
||||
},
|
||||
"IfcPixelTexture": {
|
||||
"Mode": "TextureType",
|
||||
"Parameter": "Width",
|
||||
"Width": "Height",
|
||||
"Height": "ColourComponents",
|
||||
"ColourComponents": "Pixel"
|
||||
},
|
||||
"IfcTextureCoordinateGenerator": {
|
||||
"Maps": "Mode",
|
||||
"Mode": "Parameter"
|
||||
},
|
||||
"IfcTextureMap": {
|
||||
"Maps": "TextureMaps"
|
||||
},
|
||||
"IfcAsymmetricIShapeProfileDef": {
|
||||
"BottomFlangeWidth": "OverallWidth",
|
||||
"BottomFlangeThickness": "FlangeThickness",
|
||||
"BottomFlangeFilletRadius": "FilletRadius",
|
||||
"BottomFlangeEdgeRadius": "CentreOfGravityInY"
|
||||
},
|
||||
"IfcProfileProperties": {
|
||||
"Name": "ProfileName",
|
||||
"Description": "ProfileDefinition"
|
||||
},
|
||||
"IfcPropertyDependencyRelationship": {
|
||||
"Name": "DependingProperty",
|
||||
"Description": "DependantProperty",
|
||||
"DependingProperty": "Name",
|
||||
"DependantProperty": "Description"
|
||||
},
|
||||
"IfcBoundaryEdgeCondition": {
|
||||
"TranslationalStiffnessByLengthX": "LinearStiffnessByLengthX",
|
||||
"TranslationalStiffnessByLengthY": "LinearStiffnessByLengthY",
|
||||
"TranslationalStiffnessByLengthZ": "LinearStiffnessByLengthZ"
|
||||
},
|
||||
"IfcBoundaryFaceCondition": {
|
||||
"TranslationalStiffnessByAreaX": "LinearStiffnessByAreaX",
|
||||
"TranslationalStiffnessByAreaY": "LinearStiffnessByAreaY",
|
||||
"TranslationalStiffnessByAreaZ": "LinearStiffnessByAreaZ"
|
||||
},
|
||||
"IfcBoundaryNodeCondition": {
|
||||
"TranslationalStiffnessX": "LinearStiffnessX",
|
||||
"TranslationalStiffnessY": "LinearStiffnessY",
|
||||
"TranslationalStiffnessZ": "LinearStiffnessZ"
|
||||
},
|
||||
"IfcBoundaryNodeConditionWarping": {
|
||||
"TranslationalStiffnessX": "LinearStiffnessX",
|
||||
"TranslationalStiffnessY": "LinearStiffnessY",
|
||||
"TranslationalStiffnessZ": "LinearStiffnessZ"
|
||||
},
|
||||
"IfcStructuralLoadTemperature": {
|
||||
"DeltaTConstant": "DeltaT_Constant",
|
||||
"DeltaTY": "DeltaT_Y",
|
||||
"DeltaTZ": "DeltaT_Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"IfcClassification": {
|
||||
"Specification": "Location"
|
||||
},
|
||||
"IfcPropertyBoundedValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertyEnumeratedValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertyListValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertyReferenceValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertyTableValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertySingleValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcComplexProperty": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcWorkTime": {
|
||||
"StartDate": "Start",
|
||||
"FinishDate": "Finish"
|
||||
}
|
||||
}
|
||||
@@ -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 json
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.classification
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.system
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
ifc4_to_brick_map = {}
|
||||
|
||||
with open(os.path.join(cwd, "ifc4_to_brick.json")) as f:
|
||||
ifc4_to_brick_map = json.load(f)
|
||||
|
||||
|
||||
def get_brick_type(element: ifcopenshell.entity_instance) -> Union[str, None]:
|
||||
references = ifcopenshell.util.classification.get_references(element)
|
||||
for reference in references:
|
||||
system = ifcopenshell.util.classification.get_classification(reference)
|
||||
if system.Name == "Brick":
|
||||
return reference.Location
|
||||
result = None
|
||||
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
if predefined_type:
|
||||
result = ifc4_to_brick_map.get(f"{element.is_a()}.{predefined_type}", None)
|
||||
if not result:
|
||||
result = ifc4_to_brick_map.get(element.is_a(), None)
|
||||
if not result:
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
ifc_type_class = element_type.is_a().replace("Type", "")
|
||||
result = ifc4_to_brick_map.get(f"{ifc_type_class}.{predefined_type}", None)
|
||||
if not result:
|
||||
result = ifc4_to_brick_map.get(ifc_type_class, None)
|
||||
if result:
|
||||
if result.startswith("http"):
|
||||
return result
|
||||
return f"https://brickschema.org/schema/Brick#{result}"
|
||||
# Generic fallback
|
||||
if element.is_a("IfcDistributionElement"):
|
||||
return f"https://brickschema.org/schema/Brick#Equipment"
|
||||
elif element.is_a("IfcSpatialElement") or element.is_a("IfcSpatialStructureElement"):
|
||||
return f"https://brickschema.org/schema/Brick#Location"
|
||||
elif element.is_a("IfcSystem"):
|
||||
return f"https://brickschema.org/schema/Brick#System"
|
||||
|
||||
|
||||
def get_element_feeds(element: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
|
||||
current_element = element
|
||||
processed_elements = set()
|
||||
downstream_equipment: set[ifcopenshell.entity_instance] = set()
|
||||
|
||||
# A queue is a list of branches. A branch is a list of elements in
|
||||
# sequence, each one connecting to another element. An element in a
|
||||
# branch may have a child queue. The queue and child queues are
|
||||
# acyclic.
|
||||
|
||||
def extend_branch(element, branch, predecessor=None):
|
||||
processed_elements.add(element)
|
||||
branch_element = {"element": element, "children": [], "predecessor": predecessor}
|
||||
branch.append(branch_element)
|
||||
|
||||
connected = {
|
||||
e
|
||||
for e in ifcopenshell.util.system.get_connected_to(element, flow_direction="SOURCE")
|
||||
if e not in processed_elements
|
||||
}
|
||||
connected.update(
|
||||
[
|
||||
e
|
||||
for e in ifcopenshell.util.system.get_connected_from(element, flow_direction="SOURCE")
|
||||
if e not in processed_elements
|
||||
]
|
||||
)
|
||||
|
||||
for connected_element in connected:
|
||||
if connected_element.is_a("IfcFlowFitting") or connected_element.is_a("IfcFlowSegment"):
|
||||
branch_element["children"].append(extend_branch(connected_element, [], element))
|
||||
else:
|
||||
downstream_equipment.add(connected_element)
|
||||
|
||||
return branch
|
||||
|
||||
extended_branch = extend_branch(current_element, [])
|
||||
return downstream_equipment
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"IfcElectricDistributionPoint": "IfcElectricDistributionBoard"
|
||||
, "IfcAnnotationCurveOccurrence": "IfcStyledItem"
|
||||
, "IfcAnnotationFillAreaOccurrence": "IfcStyledItem"
|
||||
, "IfcAnnotationSurfaceOccurrence": "IfcStyledItem"
|
||||
, "IfcAnnotationSymbolOccurrence": "IfcStyledItem"
|
||||
, "IfcAnnotationTextOccurrence": "IfcStyledItem"
|
||||
, "IfcExtendedMaterialProperties": "IfcMaterialProperties"
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"IfcComplexPropertyTemplate": "",
|
||||
"IfcProjectLibrary": "",
|
||||
"IfcPropertySetTemplate": "",
|
||||
"IfcRelAssignsToGroupByFactor": "",
|
||||
"IfcRelDeclares": "",
|
||||
"IfcRelDefinesByObject": "",
|
||||
"IfcRelDefinesByTemplate": "",
|
||||
"IfcSimplePropertyTemplate": "",
|
||||
"IfcEvent": "",
|
||||
"IfcEventType": "",
|
||||
"IfcProcedureType": "",
|
||||
"IfcTaskType": "",
|
||||
"IfcWorkCalendar": "",
|
||||
"IfcCivilElement": "",
|
||||
"IfcCivilElementType": "",
|
||||
"IfcElementAssemblyType": "",
|
||||
"IfcExternalSpatialElement": "IfcSpace",
|
||||
"IfcGeographicElement": "",
|
||||
"IfcGeographicElementType": "",
|
||||
"IfcOpeningStandardCase": "",
|
||||
"IfcRelInterferesElements": "",
|
||||
"IfcRelSpaceBoundary1stLevel": "",
|
||||
"IfcRelSpaceBoundary2ndLevel": "",
|
||||
"IfcSpatialZone": "",
|
||||
"IfcSpatialZoneType": "",
|
||||
"IfcBeamStandardCase": "",
|
||||
"IfcBuildingSystem": "",
|
||||
"IfcChimney": "",
|
||||
"IfcChimneyType": "",
|
||||
"IfcColumnStandardCase": "",
|
||||
"IfcDoorStandardCase": "",
|
||||
"IfcDoorType": "",
|
||||
"IfcMemberStandardCase": "",
|
||||
"IfcPlateStandardCase": "",
|
||||
"IfcRampType": "",
|
||||
"IfcRoofType": "",
|
||||
"IfcShadingDevice": "",
|
||||
"IfcShadingDeviceType": "",
|
||||
"IfcSlabElementedCase": "",
|
||||
"IfcSlabStandardCase": "",
|
||||
"IfcStairType": "",
|
||||
"IfcWallElementedCase": "",
|
||||
"IfcWindowStandardCase": "",
|
||||
"IfcWindowType": "",
|
||||
"IfcDistributionCircuit": "",
|
||||
"IfcDistributionSystem": "",
|
||||
"IfcBuildingElementPartType": "",
|
||||
"IfcFurniture": "",
|
||||
"IfcSystemFurnitureElement": "",
|
||||
"IfcActuator": "",
|
||||
"IfcAlarm": "",
|
||||
"IfcController": "",
|
||||
"IfcFlowInstrument": "",
|
||||
"IfcSensor": "",
|
||||
"IfcUnitaryControlElement": "",
|
||||
"IfcUnitaryControlElementType": "",
|
||||
"IfcConstructionEquipmentResourceType": "",
|
||||
"IfcConstructionMaterialResourceType": "",
|
||||
"IfcConstructionProductResourceType": "",
|
||||
"IfcCrewResourceType": "",
|
||||
"IfcLaborResourceType": "",
|
||||
"IfcSubContractResourceType": "",
|
||||
"IfcAudioVisualAppliance": "",
|
||||
"IfcAudioVisualApplianceType": "",
|
||||
"IfcCableCarrierFitting": "",
|
||||
"IfcCableCarrierSegment": "",
|
||||
"IfcCableFitting": "",
|
||||
"IfcCableFittingType": "",
|
||||
"IfcCableSegment": "",
|
||||
"IfcCommunicationsAppliance": "",
|
||||
"IfcCommunicationsApplianceType": "",
|
||||
"IfcElectricAppliance": "",
|
||||
"IfcElectricDistributionBoard": "",
|
||||
"IfcElectricDistributionBoardType": "",
|
||||
"IfcElectricFlowStorageDevice": "",
|
||||
"IfcElectricGenerator": "",
|
||||
"IfcElectricMotor": "",
|
||||
"IfcElectricTimeControl": "",
|
||||
"IfcJunctionBox": "",
|
||||
"IfcLamp": "",
|
||||
"IfcLightFixture": "",
|
||||
"IfcMotorConnection": "",
|
||||
"IfcOutlet": "",
|
||||
"IfcProtectiveDevice": "",
|
||||
"IfcProtectiveDeviceTrippingUnit": "",
|
||||
"IfcProtectiveDeviceTrippingUnitType": "",
|
||||
"IfcSolarDevice": "",
|
||||
"IfcSolarDeviceType": "",
|
||||
"IfcSwitchingDevice": "",
|
||||
"IfcTransformer": "",
|
||||
"IfcAirTerminal": "",
|
||||
"IfcAirTerminalBox": "",
|
||||
"IfcAirToAirHeatRecovery": "",
|
||||
"IfcBoiler": "",
|
||||
"IfcBurner": "",
|
||||
"IfcBurnerType": "",
|
||||
"IfcChiller": "",
|
||||
"IfcCoil": "",
|
||||
"IfcCompressor": "",
|
||||
"IfcCondenser": "",
|
||||
"IfcCooledBeam": "",
|
||||
"IfcCoolingTower": "",
|
||||
"IfcDamper": "",
|
||||
"IfcDuctFitting": "",
|
||||
"IfcDuctSegment": "",
|
||||
"IfcDuctSilencer": "",
|
||||
"IfcEngine": "",
|
||||
"IfcEngineType": "",
|
||||
"IfcEvaporativeCooler": "",
|
||||
"IfcEvaporator": "",
|
||||
"IfcFan": "",
|
||||
"IfcFilter": "",
|
||||
"IfcFlowMeter": "",
|
||||
"IfcHeatExchanger": "",
|
||||
"IfcHumidifier": "",
|
||||
"IfcMedicalDevice": "",
|
||||
"IfcMedicalDeviceType": "",
|
||||
"IfcPipeFitting": "",
|
||||
"IfcPipeSegment": "",
|
||||
"IfcPump": "",
|
||||
"IfcSpaceHeater": "",
|
||||
"IfcTank": "",
|
||||
"IfcTubeBundle": "",
|
||||
"IfcUnitaryEquipment": "",
|
||||
"IfcValve": "",
|
||||
"IfcVibrationIsolator": "",
|
||||
"IfcFireSuppressionTerminal": "",
|
||||
"IfcInterceptor": "",
|
||||
"IfcInterceptorType": "",
|
||||
"IfcSanitaryTerminal": "",
|
||||
"IfcStackTerminal": "",
|
||||
"IfcWasteTerminal": "",
|
||||
"IfcStructuralCurveAction": "",
|
||||
"IfcStructuralCurveReaction": "",
|
||||
"IfcStructuralLoadCase": "",
|
||||
"IfcStructuralSurfaceAction": "",
|
||||
"IfcStructuralSurfaceReaction": "",
|
||||
"IfcFootingType": "",
|
||||
"IfcPileType": "",
|
||||
"IfcReinforcingBarType": "",
|
||||
"IfcReinforcingMeshType": "",
|
||||
"IfcSurfaceFeature": "",
|
||||
"IfcTendonAnchorType": "",
|
||||
"IfcTendonType": "",
|
||||
"IfcVoidingFeature": "",
|
||||
"IfcResourceApprovalRelationship": "",
|
||||
"IfcAppliedValue": "",
|
||||
"IfcReference": "",
|
||||
"IfcResourceConstraintRelationship": "",
|
||||
"IfcEventTime": "",
|
||||
"IfcLagTime": "",
|
||||
"IfcRecurrencePattern": "",
|
||||
"IfcResourceTime": "",
|
||||
"IfcTaskTime": "",
|
||||
"IfcTaskTimeRecurring": "",
|
||||
"IfcTimePeriod": "",
|
||||
"IfcWorkTime": "",
|
||||
"IfcExternalReferenceRelationship": "",
|
||||
"IfcConnectionVolumeGeometry": "",
|
||||
"IfcAdvancedBrep": "",
|
||||
"IfcAdvancedBrepWithVoids": "",
|
||||
"IfcCartesianPointList3D": "",
|
||||
"IfcExtrudedAreaSolidTapered": "",
|
||||
"IfcFixedReferenceSweptAreaSolid": "",
|
||||
"IfcRevolvedAreaSolidTapered": "",
|
||||
"IfcSweptDiskSolidPolygonal": "",
|
||||
"IfcTriangulatedFaceSet": "",
|
||||
"IfcBoundaryCurve": "",
|
||||
"IfcBSplineCurveWithKnots": "",
|
||||
"IfcBSplineSurfaceWithKnots": "",
|
||||
"IfcCompositeCurveOnSurface": "",
|
||||
"IfcCurveBoundedSurface": "",
|
||||
"IfcCylindricalSurface": "",
|
||||
"IfcOuterBoundaryCurve": "",
|
||||
"IfcPcurve": "",
|
||||
"IfcRationalBSplineCurveWithKnots": "",
|
||||
"IfcRationalBSplineSurfaceWithKnots": "",
|
||||
"IfcReparametrisedCompositeCurveSegment": "",
|
||||
"IfcMaterialConstituent": "",
|
||||
"IfcMaterialConstituentSet": "",
|
||||
"IfcMaterialLayerWithOffsets": "",
|
||||
"IfcMaterialProfile": "",
|
||||
"IfcMaterialProfileSet": "",
|
||||
"IfcMaterialProfileSetUsage": "",
|
||||
"IfcMaterialProfileSetUsageTapering": "",
|
||||
"IfcMaterialProfileWithOffsets": "",
|
||||
"IfcMaterialRelationship": "",
|
||||
"IfcConversionBasedUnitWithOffset": "",
|
||||
"IfcVector": "",
|
||||
"IfcColourRgbList": "",
|
||||
"IfcIndexedColourMap": "",
|
||||
"IfcIndexedTriangleTextureMap": "",
|
||||
"IfcTextureVertexList": "",
|
||||
"IfcMirroredProfileDef": "",
|
||||
"IfcTable": "",
|
||||
"IfcMapConversion": "",
|
||||
"IfcProjectedCRS": "",
|
||||
"IfcStructuralLoadConfiguration": "",
|
||||
"IfcSurfaceReinforcementArea": "",
|
||||
"IfcAdvancedFace": "",
|
||||
"IfcTableColumn": "",
|
||||
"IfcCartesianPointList2D": "",
|
||||
"IfcIndexedPolyCurve": "",
|
||||
"IfcIndexedPolygonalFace": "",
|
||||
"IfcIndexedPolygonalFaceWithVoids": "",
|
||||
"IfcPolygonalFaceSet": "",
|
||||
"IfcIntersectionCurve": "",
|
||||
"IfcSeamCurve": "",
|
||||
"IfcSphericalSurface": "",
|
||||
"IfcSurfaceCurve": "",
|
||||
"IfcToroidalSurface": ""
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
# 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/>.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def get_references(element: ifcopenshell.entity_instance, should_inherit=True) -> set[ifcopenshell.entity_instance]:
|
||||
"""Gets classification references associated with the element
|
||||
|
||||
:param should_inherit: If true, classification references are inherited
|
||||
from the type. Classifications can be overriden per system.
|
||||
:return: A set of IfcClassificationReference
|
||||
"""
|
||||
results = set()
|
||||
if not element.is_a("IfcRoot"):
|
||||
if (references := getattr(element, "HasExternalReferences", None)) is not None or (
|
||||
references := getattr(element, "HasExternalReference", None)
|
||||
) is not None:
|
||||
return {r.RelatingReference for r in references}
|
||||
if should_inherit and element.is_a("IfcObject"):
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type and element_type != element:
|
||||
results = get_references(element_type)
|
||||
occurrence_results = {
|
||||
r.RelatingClassification
|
||||
for r in getattr(element, "HasAssociations", [])
|
||||
if r.is_a("IfcRelAssociatesClassification")
|
||||
}
|
||||
if results:
|
||||
type_references_per_system = {}
|
||||
occurrence_references_per_system = {}
|
||||
for result in results:
|
||||
type_references_per_system.setdefault(get_classification(result), []).append(result)
|
||||
for result in occurrence_results:
|
||||
occurrence_references_per_system.setdefault(get_classification(result), []).append(result)
|
||||
type_references_per_system.update(occurrence_references_per_system)
|
||||
results = set()
|
||||
for values in type_references_per_system.values():
|
||||
[results.add(v) for v in values]
|
||||
return results
|
||||
return occurrence_results
|
||||
|
||||
|
||||
def get_classification(reference: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Get the IfcClassification that a classification reference belongs to
|
||||
|
||||
:param reference: An IfcClassificationReference
|
||||
:return: IfcClassification
|
||||
"""
|
||||
if reference.is_a("IfcClassification"):
|
||||
return reference
|
||||
return get_classification(reference.ReferencedSource) if reference.ReferencedSource is not None else None
|
||||
|
||||
|
||||
def get_inherited_references(reference: Optional[ifcopenshell.entity_instance]) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
while True:
|
||||
if not reference or reference.is_a("IfcClassification"):
|
||||
break
|
||||
results.append(reference)
|
||||
reference = reference.ReferencedSource
|
||||
return results
|
||||
|
||||
|
||||
def get_classification_data(file: ifcopenshell.file) -> Optional[tuple[list[dict], str]]:
|
||||
if not file or not file.by_type("IfcClassification"):
|
||||
return [], ""
|
||||
classification = file.by_type("IfcClassification")[0]
|
||||
classification_name = classification.Name
|
||||
|
||||
def process_references(reference):
|
||||
data = reference.get_info()
|
||||
del data["ReferencedSource"]
|
||||
data["referenced_source"] = reference.ReferencedSource.id() if reference.ReferencedSource else None
|
||||
data["has_references"] = bool(reference.HasReferences)
|
||||
data["references"] = (
|
||||
[process_references(ref) for ref in reference.HasReferences] if reference.HasReferences else []
|
||||
)
|
||||
return data
|
||||
|
||||
classification_data = [process_references(reference) for reference in classification.HasReferences]
|
||||
return classification_data, classification_name
|
||||
@@ -0,0 +1,111 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 Dion Moult, Yassine Oualid <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 Union
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def get_constraints(product: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Retrieves the constraints assigned to the `product`.
|
||||
|
||||
:param product: The IFC element.
|
||||
:return: List of assigned constraints.
|
||||
"""
|
||||
constraints = []
|
||||
for rel in product.HasAssociations or []:
|
||||
if rel.is_a("IfcRelAssociatesConstraint"):
|
||||
constraints.append(rel.RelatingConstraint)
|
||||
return constraints
|
||||
|
||||
|
||||
def get_constrained_elements(constraint: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Retrieves the elements constrained by a `constraint`.
|
||||
|
||||
:param product: The IFC element.
|
||||
:return: Set of elements constrained by a `constrant`.
|
||||
"""
|
||||
elements = set()
|
||||
for rel in constraint.file.get_inverse(constraint):
|
||||
if rel.is_a("IfcRelAssociatesConstraint"):
|
||||
elements.update(rel.RelatedObjects)
|
||||
return elements
|
||||
|
||||
|
||||
def get_metrics(constraint: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Retrieves the list of nested constraints for a IfcObjective `constraint`.
|
||||
|
||||
:param product: IfcObjective constraint.
|
||||
:return: List of nested constraints.
|
||||
"""
|
||||
|
||||
metrics = []
|
||||
for metric in constraint.BenchmarkValues or []:
|
||||
metrics.append(metric)
|
||||
return metrics
|
||||
|
||||
|
||||
def get_metric_reference(metric: ifcopenshell.entity_instance, is_deep=True):
|
||||
def get_reference_Attribute(ref, path):
|
||||
if ref:
|
||||
if is_deep:
|
||||
if not path:
|
||||
path = ref.AttributeIdentifier
|
||||
else:
|
||||
path += ".{}".format(ref.AttributeIdentifier) if ref.AttributeIdentifier else ""
|
||||
return get_reference_Attribute(ref.InnerReference, path)
|
||||
else:
|
||||
return ref.AttributeIdentifier
|
||||
return path
|
||||
|
||||
reference = metric.ReferencePath
|
||||
return get_reference_Attribute(reference, "")
|
||||
|
||||
|
||||
def get_metric_constraints(
|
||||
resource: ifcopenshell.entity_instance, attribute
|
||||
) -> Union[list[ifcopenshell.entity_instance], None]:
|
||||
metrics = []
|
||||
for constraint in get_constraints(resource) or []:
|
||||
for metric in get_metrics(constraint) or []:
|
||||
if bool(
|
||||
get_metric_reference(metric, is_deep=False) == attribute
|
||||
or get_metric_reference(metric, is_deep=True) == attribute
|
||||
):
|
||||
metrics.append(metric)
|
||||
if metrics:
|
||||
return metrics
|
||||
return None
|
||||
|
||||
|
||||
def is_hard_constraint(metric: ifcopenshell.entity_instance) -> bool:
|
||||
return metric.ConstraintGrade == "HARD" and metric.Benchmark == "EQUALTO"
|
||||
|
||||
|
||||
def is_attribute_locked(product: ifcopenshell.entity_instance, attribute) -> bool:
|
||||
is_locked = False
|
||||
metrics = get_metric_constraints(product, attribute)
|
||||
for metric in metrics or []:
|
||||
if is_hard_constraint(metric):
|
||||
is_locked = True
|
||||
return is_locked
|
||||
@@ -0,0 +1,436 @@
|
||||
# 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 collections.abc import Generator
|
||||
from typing import Any, Literal, Optional, Union
|
||||
|
||||
import lark
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell.util.doc import get_predefined_type_doc
|
||||
from ifcopenshell.util.element import get_psets
|
||||
from ifcopenshell.util.unit import get_unit_symbol
|
||||
|
||||
arithmetic_operator_symbols = {"ADD": "+", "DIVIDE": "/", "MULTIPLY": "*", "SUBTRACT": "-"}
|
||||
symbol_arithmetic_operators = {"+": "ADD", "/": "DIVIDE", "*": "MULTIPLY", "-": "SUBTRACT"}
|
||||
FILTER_BY_TYPE = Literal["PRODUCT", "RESOURCE", "PROCESS"]
|
||||
|
||||
|
||||
def get_primitive_applied_value(applied_value: Union[ifcopenshell.entity_instance, float, None]) -> float:
|
||||
if not applied_value:
|
||||
return 0.0
|
||||
elif isinstance(applied_value, float):
|
||||
return applied_value
|
||||
elif hasattr(applied_value, "wrappedValue") and isinstance(applied_value.wrappedValue, float):
|
||||
return applied_value.wrappedValue
|
||||
elif applied_value.is_a("IfcMeasureWithUnit"):
|
||||
return applied_value.ValueComponent
|
||||
assert False, f"Applied value {applied_value} not implemented"
|
||||
|
||||
|
||||
def get_total_quantity(root_element: ifcopenshell.entity_instance) -> Union[float, None]:
|
||||
# 3 IfcPhysicalQuantity Value
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
# Different output for no quantities and zero quantites
|
||||
# as they have different meaning in IFC.
|
||||
quantities = root_element.CostQuantities
|
||||
if not quantities:
|
||||
return None
|
||||
return sum([q[3] for q in quantities])
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
quantity = root_element.BaseQuantity
|
||||
return quantity[3] if quantity else 1.0
|
||||
|
||||
|
||||
def calculate_applied_value(
|
||||
root_element: ifcopenshell.entity_instance,
|
||||
cost_value: ifcopenshell.entity_instance,
|
||||
category_filter: Optional[str] = None,
|
||||
) -> float:
|
||||
if cost_value.ArithmeticOperator and cost_value.Components:
|
||||
component_values = []
|
||||
for component in cost_value.Components:
|
||||
component_values.append(calculate_applied_value(root_element, component, category_filter))
|
||||
if cost_value.ArithmeticOperator == "ADD":
|
||||
return sum(component_values)
|
||||
result = component_values.pop(0)
|
||||
if cost_value.ArithmeticOperator == "DIVIDE":
|
||||
for value in component_values:
|
||||
try:
|
||||
result /= value
|
||||
except ZeroDivisionError:
|
||||
pass
|
||||
elif cost_value.ArithmeticOperator == "MULTIPLY":
|
||||
for value in component_values:
|
||||
result *= value
|
||||
elif cost_value.ArithmeticOperator == "SUBTRACT":
|
||||
for value in component_values:
|
||||
result -= value
|
||||
return result
|
||||
if cost_value.Category is None:
|
||||
return get_primitive_applied_value(cost_value.AppliedValue)
|
||||
elif cost_value.Category == "*":
|
||||
if root_element.IsNestedBy:
|
||||
return sum_child_root_elements(root_element)
|
||||
else:
|
||||
return get_primitive_applied_value(cost_value.AppliedValue)
|
||||
elif cost_value.Category:
|
||||
if root_element.IsNestedBy:
|
||||
return sum_child_root_elements(root_element, category_filter=cost_value.Category)
|
||||
else:
|
||||
return get_primitive_applied_value(cost_value.AppliedValue)
|
||||
return 0.0
|
||||
|
||||
|
||||
def sum_child_root_elements(root_element: ifcopenshell.entity_instance, category_filter: Optional[str] = None) -> float:
|
||||
result = 0.0
|
||||
for rel in root_element.IsNestedBy:
|
||||
for child_root_element in rel.RelatedObjects:
|
||||
if get_assigned_rate_cost_item(child_root_element):
|
||||
new_child_root_element = get_assigned_rate_cost_item(child_root_element)
|
||||
else:
|
||||
new_child_root_element = child_root_element
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
values = new_child_root_element.CostValues
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
values = child_root_element.BaseCosts
|
||||
for child_cost_value in values or []:
|
||||
if category_filter and child_cost_value.Category != category_filter:
|
||||
continue
|
||||
child_applied_value = calculate_applied_value(new_child_root_element, child_cost_value)
|
||||
child_quantity = get_total_quantity(child_root_element)
|
||||
child_quantity = 1.0 if child_quantity is None else child_quantity
|
||||
if child_cost_value.UnitBasis:
|
||||
value_component = child_cost_value.UnitBasis.ValueComponent.wrappedValue
|
||||
result += child_quantity / value_component * child_applied_value
|
||||
else:
|
||||
result += child_quantity * child_applied_value
|
||||
return result
|
||||
|
||||
|
||||
def serialise_cost_value(cost_value: ifcopenshell.entity_instance) -> str:
|
||||
result = _serialise_cost_value(cost_value)
|
||||
if result and result[0] == "(" and result[-1] == ")":
|
||||
return result[1:-1]
|
||||
return result
|
||||
|
||||
|
||||
def get_assigned_rate_cost_item(cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
# same as in tool. Maybe just create one?
|
||||
for assignment in cost_item.HasAssignments:
|
||||
if assignment.RelatingControl.is_a() == "IfcCostItem":
|
||||
return assignment.RelatingControl
|
||||
|
||||
|
||||
def _serialise_cost_value(cost_value: ifcopenshell.entity_instance) -> str:
|
||||
value = ""
|
||||
if cost_value.ArithmeticOperator and cost_value.Components:
|
||||
operator = arithmetic_operator_symbols[cost_value.ArithmeticOperator]
|
||||
serialised_components = []
|
||||
for component in cost_value.Components:
|
||||
serialised_components.append(_serialise_cost_value(component))
|
||||
value = operator.join(serialised_components)
|
||||
elif cost_value.AppliedValue is not None:
|
||||
value = serialise_applied_value(cost_value.AppliedValue)
|
||||
|
||||
category = ""
|
||||
if cost_value.Category == "*":
|
||||
category = "SUM"
|
||||
elif cost_value.Category:
|
||||
category = cost_value.Category
|
||||
|
||||
if not category and not value:
|
||||
value = "0"
|
||||
|
||||
if category:
|
||||
return f"{category}({value})"
|
||||
elif cost_value.Components:
|
||||
return f"({value})"
|
||||
return value
|
||||
|
||||
|
||||
def serialise_applied_value(applied_value: ifcopenshell.entity_instance) -> str:
|
||||
if applied_value.is_a("IfcMonetaryMeasure"):
|
||||
return str(applied_value.wrappedValue)
|
||||
return "?"
|
||||
|
||||
|
||||
def unserialise_cost_value(formula: str, cost_value: ifcopenshell.entity_instance) -> dict[str, Any]:
|
||||
unserialiser = CostValueUnserialiser()
|
||||
result = unserialiser.parse(formula)
|
||||
|
||||
def map_element_to_result(element: ifcopenshell.entity_instance, result: dict):
|
||||
result["ifc"] = element
|
||||
for i, component in enumerate(result.get("Components", [])):
|
||||
if element.Components and i < len(element.Components):
|
||||
map_element_to_result(element.Components[i], result["Components"][i])
|
||||
|
||||
map_element_to_result(cost_value, result)
|
||||
return result
|
||||
|
||||
|
||||
def get_cost_items_for_product(product: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Returns a list of cost items related to the given product.
|
||||
|
||||
:param product: An object of class IfcProduct representing a product.
|
||||
|
||||
:return: A list of IfcCostItem objects representing the cost items related to the product.
|
||||
"""
|
||||
cost_items = []
|
||||
for assignment in product.HasAssignments:
|
||||
if assignment.is_a("IfcRelAssignsToControl") and assignment.RelatingControl.is_a("IfcCostItem"):
|
||||
cost_items.append(assignment.RelatingControl)
|
||||
return cost_items
|
||||
|
||||
|
||||
def get_root_cost_items(cost_schedule: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
return [
|
||||
related_object
|
||||
for rel in cost_schedule.Controls or []
|
||||
for related_object in rel.RelatedObjects
|
||||
if related_object.is_a("IfcCostItem")
|
||||
]
|
||||
|
||||
|
||||
def get_all_nested_cost_items(
|
||||
cost_item: ifcopenshell.entity_instance,
|
||||
) -> Generator[ifcopenshell.entity_instance]:
|
||||
for cost_item in get_nested_cost_items(cost_item):
|
||||
yield cost_item
|
||||
yield from get_all_nested_cost_items(cost_item)
|
||||
|
||||
|
||||
def get_nested_cost_items(cost_item: ifcopenshell.entity_instance, is_deep=False) -> list[ifcopenshell.entity_instance]:
|
||||
if is_deep:
|
||||
return list(get_all_nested_cost_items(cost_item))
|
||||
else:
|
||||
return ifcopenshell.util.element.get_components(cost_item)
|
||||
|
||||
|
||||
def get_schedule_cost_items(
|
||||
cost_schedule: ifcopenshell.entity_instance,
|
||||
) -> Generator[ifcopenshell.entity_instance]:
|
||||
"""Get all cost schedule cost items, including the nested ones."""
|
||||
for cost_item in get_root_cost_items(cost_schedule):
|
||||
yield cost_item
|
||||
yield from get_all_nested_cost_items(cost_item)
|
||||
|
||||
|
||||
def get_cost_assignments_by_type(
|
||||
cost_item: ifcopenshell.entity_instance, filter_by_type: Optional[FILTER_BY_TYPE] = None
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
if filter_by_type is not None:
|
||||
if filter_by_type == "PRODUCT":
|
||||
filter_by_type = "IfcElement"
|
||||
elif filter_by_type == "RESOURCE":
|
||||
filter_by_type = "IfcResource"
|
||||
elif filter_by_type == "PROCESS":
|
||||
filter_by_type = "IfcProcess"
|
||||
return [
|
||||
related_object
|
||||
for r in cost_item.Controls or []
|
||||
for related_object in r.RelatedObjects
|
||||
if not filter_by_type or related_object.is_a(filter_by_type)
|
||||
]
|
||||
|
||||
|
||||
def get_cost_item_assignments(
|
||||
cost_item: ifcopenshell.entity_instance, filter_by_type: Optional[FILTER_BY_TYPE] = None, is_deep: bool = False
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
if not is_deep:
|
||||
return get_cost_assignments_by_type(cost_item, filter_by_type)
|
||||
else:
|
||||
current_assignments = get_cost_assignments_by_type(cost_item, filter_by_type)
|
||||
nested_assignments = [
|
||||
product
|
||||
for nested_cost_item in get_all_nested_cost_items(cost_item)
|
||||
for product in get_cost_assignments_by_type(nested_cost_item, filter_by_type)
|
||||
]
|
||||
return current_assignments + nested_assignments
|
||||
|
||||
|
||||
def get_cost_values(cost_item: ifcopenshell.entity_instance) -> list[dict[str, str]]:
|
||||
results = []
|
||||
for cost_value in cost_item.CostValues or []:
|
||||
label = "{0:.2f}".format(calculate_applied_value(cost_item, cost_value))
|
||||
label += " = {}".format(serialise_cost_value(cost_value))
|
||||
unit_data = {"value_component": None, "unit_component": None, "unit_symbol": ""}
|
||||
if cost_value.UnitBasis:
|
||||
data = cost_value.UnitBasis.get_info()
|
||||
unit_data["value_component"] = data["ValueComponent"].wrappedValue
|
||||
unit_data["unit_component"] = data["UnitComponent"].id()
|
||||
unit_data["unit_symbol"] = get_unit_symbol(cost_value.UnitBasis.UnitComponent)
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": cost_value.id(),
|
||||
"label": label,
|
||||
"name": cost_value.Name,
|
||||
"category": cost_value.Category,
|
||||
"applied_value": (
|
||||
get_primitive_applied_value(cost_value.AppliedValue) if cost_value.AppliedValue else None
|
||||
),
|
||||
"unit_data": unit_data,
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
def get_cost_schedule_types(file: ifcopenshell.file) -> list[dict[str, str]]:
|
||||
schema = ifcopenshell_wrapper.schema_by_name(file.schema_identifier)
|
||||
results: list[dict[str, str]] = []
|
||||
declaration = schema.declaration_by_name("IfcCostSchedule").as_entity()
|
||||
assert declaration
|
||||
version = file.schema_identifier
|
||||
for attribute in declaration.attributes():
|
||||
if attribute.name() == "PredefinedType":
|
||||
for enumeration in ifcopenshell.util.attribute.get_enum_items(attribute):
|
||||
results.append(
|
||||
{
|
||||
"name": enumeration,
|
||||
"description": get_predefined_type_doc(version, "IfcCostSchedule", enumeration),
|
||||
}
|
||||
)
|
||||
break
|
||||
return results
|
||||
|
||||
|
||||
def get_product_quantity_names(elements: list[ifcopenshell.entity_instance]) -> list[str]:
|
||||
names = set()
|
||||
for element in elements or []:
|
||||
potential_names = set()
|
||||
qtos = get_psets(element, qtos_only=True)
|
||||
for qset, quantities in qtos.items():
|
||||
potential_names.update(quantities.keys())
|
||||
names = names.intersection(potential_names) if names else potential_names
|
||||
return [n for n in names if n != "id"]
|
||||
|
||||
|
||||
def get_cost_schedule(cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Returns the cost schedule of a cost item."""
|
||||
for rel in cost_item.HasAssignments or []:
|
||||
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcCostSchedule"):
|
||||
return rel.RelatingControl
|
||||
for rel in cost_item.Nests or []:
|
||||
return get_cost_schedule(rel.RelatingObject)
|
||||
|
||||
|
||||
def get_cost_rate(
|
||||
file: ifcopenshell_wrapper.file, cost_item: ifcopenshell.entity_instance
|
||||
) -> Optional[ifcopenshell.entity_instance]:
|
||||
"""Returns the cost rate of a cost item."""
|
||||
# There is no direct relationship between a cost item and a cost rate in IFC, so we need to infer it, based on the assumption that cost_rate.CostValues == cost_item.CostValues.
|
||||
if get_cost_schedule(cost_item).PredefinedType == "SCHEDULEOFRATES":
|
||||
return None # Cost item is already a cost rate
|
||||
if cost_item.CostValues:
|
||||
potential_rates = file.get_inverse(cost_item.CostValues[0])
|
||||
for potential_rate in potential_rates:
|
||||
schedule = get_cost_schedule(potential_rate)
|
||||
if schedule.PredefinedType == "SCHEDULEOFRATES":
|
||||
return potential_rate
|
||||
return None
|
||||
|
||||
|
||||
class CostValueUnserialiser:
|
||||
def parse(self, formula: str):
|
||||
l = lark.Lark("""start: formula
|
||||
formula: operand (operator operand)*
|
||||
operand: value | category "(" formula ")"
|
||||
value: NUMBER?
|
||||
category: WORD?
|
||||
operator: add | divide | multiply | subtract
|
||||
add: "+"
|
||||
divide: "/"
|
||||
multiply: "*"
|
||||
subtract: "-"
|
||||
|
||||
// Embed common.lark for packaging
|
||||
DIGIT: "0".."9"
|
||||
HEXDIGIT: "a".."f"|"A".."F"|DIGIT
|
||||
INT: DIGIT+
|
||||
SIGNED_INT: ["+"|"-"] INT
|
||||
DECIMAL: INT "." INT? | "." INT
|
||||
_EXP: ("e"|"E") SIGNED_INT
|
||||
FLOAT: INT _EXP | DECIMAL _EXP?
|
||||
SIGNED_FLOAT: ["+"|"-"] FLOAT
|
||||
NUMBER: FLOAT | INT
|
||||
SIGNED_NUMBER: ["+"|"-"] NUMBER
|
||||
_STRING_INNER: /.*?/
|
||||
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
|
||||
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
|
||||
LCASE_LETTER: "a".."z"
|
||||
UCASE_LETTER: "A".."Z"
|
||||
LETTER: UCASE_LETTER | LCASE_LETTER
|
||||
WORD: LETTER+
|
||||
CNAME: ("_"|LETTER) ("_"|LETTER|DIGIT)*
|
||||
WS_INLINE: (" "|/\\t/)+
|
||||
WS: /[ \\t\\f\\r\\n]/+
|
||||
CR : /\\r/
|
||||
LF : /\\n/
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
""")
|
||||
start = l.parse(formula)
|
||||
return self.get_formula(start.children[0])
|
||||
|
||||
def get_formula(self, formula):
|
||||
if len(formula.children) == 1:
|
||||
return self.get_operand(formula.children[0])
|
||||
results = {"Components": []}
|
||||
for child in formula.children:
|
||||
if child.data == "operand":
|
||||
results["Components"].append(self.get_operand(child))
|
||||
elif child.data == "operator":
|
||||
results["ArithmeticOperator"] = self.get_operator(child)
|
||||
return results
|
||||
|
||||
def get_operand(self, operand):
|
||||
child = operand.children[0]
|
||||
if child.data == "value":
|
||||
value = self.get_value(child)
|
||||
return {"AppliedValue": float(value) if value else None}
|
||||
elif child.data == "category":
|
||||
data = {}
|
||||
category = self.get_category(child)
|
||||
if category:
|
||||
if category.lower() == "sum":
|
||||
category = "*"
|
||||
data["Category"] = category
|
||||
formula = self.get_formula(operand.children[1])
|
||||
if formula.get("Components"):
|
||||
data["Components"] = formula["Components"]
|
||||
data["ArithmeticOperator"] = formula["ArithmeticOperator"]
|
||||
else:
|
||||
data["AppliedValue"] = formula["AppliedValue"]
|
||||
return data
|
||||
|
||||
def get_value(self, value):
|
||||
if value.children:
|
||||
return value.children[0].value
|
||||
|
||||
def get_category(self, category):
|
||||
if category.children:
|
||||
return category.children[0].value
|
||||
|
||||
def get_operator(self, operator):
|
||||
return operator.children[0].data.upper()
|
||||
@@ -0,0 +1,102 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>, @Andrej730
|
||||
#
|
||||
# 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 __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
|
||||
|
||||
@dataclass
|
||||
class Clipping:
|
||||
location: tuple[float, float, float]
|
||||
normal: tuple[float, float, float]
|
||||
type: str = "IfcBooleanClippingResult"
|
||||
operand_type: str = "IfcHalfSpaceSolid"
|
||||
|
||||
@classmethod
|
||||
def parse(
|
||||
cls, raw_data: Union[ifcopenshell.entity_instance, Clipping, dict[str, Any]]
|
||||
) -> Union[ifcopenshell.entity_instance, Clipping]:
|
||||
"""Parse various formats into a clipping object
|
||||
|
||||
`raw_data` can be either:
|
||||
|
||||
- IfcBooleanResult IFC entity
|
||||
- `Clipping` instance
|
||||
- dictionary to define `Clipping` - either `location` and `normal`
|
||||
or a `matrix` where XY plane is the clipping boundary and +Z is removed.
|
||||
`matrix` method will be soon to be deprecated completely.
|
||||
"""
|
||||
if isinstance(raw_data, ifcopenshell.entity_instance):
|
||||
if not raw_data.is_a("IfcBooleanResult"):
|
||||
raise Exception(f"Provided clipping of unexpected IFC class: {raw_data}")
|
||||
return raw_data
|
||||
elif isinstance(raw_data, Clipping):
|
||||
return raw_data
|
||||
elif isinstance(raw_data, dict):
|
||||
if "matrix" in raw_data:
|
||||
raw_data = raw_data.copy()
|
||||
matrix = np.array(raw_data["matrix"])[:3]
|
||||
raw_data["normal"] = matrix[:, 2].tolist()
|
||||
raw_data["location"] = matrix[:, 3].tolist()
|
||||
del raw_data["matrix"]
|
||||
clipping_data = cls(**raw_data)
|
||||
if clipping_data.type != "IfcBooleanClippingResult":
|
||||
raise Exception(f'Provided clipping with unexpected result type "{clipping_data.type}"')
|
||||
if clipping_data.operand_type != "IfcHalfSpaceSolid":
|
||||
raise Exception(f'Provided clipping with unexpected operand type "{clipping_data.operand_type}"')
|
||||
return clipping_data
|
||||
raise Exception(f"Unexpected clipping type provided: {raw_data}")
|
||||
|
||||
def apply(
|
||||
self, ifc_file: Union[ifcopenshell.file, None], first_operand: ifcopenshell.entity_instance, unit_scale: float
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Applies the clipping data as an IfcBooleanClippingResult to an operand
|
||||
|
||||
:param ifc_file: The model to create the entities in
|
||||
:param first_operand: The representation item to apply the boolean clipping to.
|
||||
:param unit_scale: The unit scale value to convert from the Clipping's SI units to project units
|
||||
:return: An IfcBooleanClippingResult which uses an IfcHalfSpaceSolid to clip the first operand
|
||||
"""
|
||||
|
||||
if not ifc_file:
|
||||
ifc_file = first_operand.file
|
||||
builder = ShapeBuilder(ifc_file)
|
||||
|
||||
normal = np.array(self.normal)
|
||||
if np.allclose(normal, np.array([0.0, 0.0, 1.0]), atol=1e-2) or np.allclose(
|
||||
normal, np.array([0.0, 0.0, -1.0]), atol=1e-2
|
||||
):
|
||||
arbitrary_vector = np.array([0.0, 1.0, 0.0])
|
||||
else:
|
||||
arbitrary_vector = np.array([0.0, 0.0, 1.0])
|
||||
|
||||
x_axis = np.cross(normal, arbitrary_vector)
|
||||
x_axis /= np.linalg.norm(x_axis)
|
||||
|
||||
placement = builder.create_axis2_placement_3d([i / unit_scale for i in self.location], self.normal, x_axis)
|
||||
plane = ifc_file.create_entity("IfcPlane", placement)
|
||||
|
||||
second_operand = ifc_file.createIfcHalfSpaceSolid(plane, False)
|
||||
return ifc_file.createIfcBooleanClippingResult("DIFFERENCE", first_operand, second_operand)
|
||||
@@ -0,0 +1,270 @@
|
||||
# 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 datetime
|
||||
from re import findall
|
||||
from typing import Any, Literal, Union, overload
|
||||
|
||||
import isodate
|
||||
from dateutil import parser
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def timedelta2duration(timedelta):
|
||||
components = {
|
||||
"days": getattr(timedelta, "days", 0),
|
||||
"hours": 0,
|
||||
"minutes": 0,
|
||||
"seconds": getattr(timedelta, "seconds", 0),
|
||||
}
|
||||
if components["seconds"]:
|
||||
components["hours"], components["minutes"], components["seconds"] = [
|
||||
int(i) for i in str(datetime.timedelta(seconds=components["seconds"])).split(":")
|
||||
]
|
||||
return isodate.Duration(**components)
|
||||
|
||||
|
||||
def ifc2datetime(element: Union[str, int, ifcopenshell.entity_instance]):
|
||||
if isinstance(element, str):
|
||||
if "P" in element[0:2]: # IfcDuration
|
||||
duration = parse_duration(element)
|
||||
if isinstance(duration, datetime.timedelta):
|
||||
return timedelta2duration(duration)
|
||||
return duration
|
||||
elif len(element) > 3 and element[2] == ":": # IfcTime
|
||||
return datetime.time.fromisoformat(element)
|
||||
elif ":" in element: # IfcDateTime
|
||||
return datetime.datetime.fromisoformat(element)
|
||||
else: # IfcDate
|
||||
return datetime.date.fromisoformat(element)
|
||||
|
||||
elif isinstance(element, int): # IfcTimeStamp
|
||||
return datetime.datetime.fromtimestamp(element)
|
||||
|
||||
elif isinstance(element, ifcopenshell.entity_instance):
|
||||
if element.is_a("IfcDateAndTime"):
|
||||
return datetime.datetime(
|
||||
element.DateComponent.YearComponent,
|
||||
element.DateComponent.MonthComponent,
|
||||
element.DateComponent.DayComponent,
|
||||
element.TimeComponent.HourComponent,
|
||||
element.TimeComponent.MinuteComponent,
|
||||
int(element.TimeComponent.SecondComponent),
|
||||
# TODO: implement TimeComponent timezone
|
||||
)
|
||||
elif element.is_a("IfcCalendarDate"):
|
||||
return datetime.date(
|
||||
element.YearComponent,
|
||||
element.MonthComponent,
|
||||
element.DayComponent,
|
||||
)
|
||||
|
||||
|
||||
def readable_ifc_duration(duration: str) -> str:
|
||||
"""Convert ISO duration to more readable string format.
|
||||
|
||||
Examples:
|
||||
- "P2Y3M1W4DT5H45M30S" -> "2 Y 3 M 1 W 4 D 5 h 45 m 30 s"
|
||||
- "P2Y3MT30S" -> "2 Y 3 M 30 s"
|
||||
- "PT2500H" -> "2500 h" (hours are not converted to days)
|
||||
"""
|
||||
# NOTE: we don't use isodate.parseduration as it's going to
|
||||
# represent "PT2500H" as "12w 6d 4h", though user may want
|
||||
# intentionally to use just hours.
|
||||
|
||||
if "T" in duration:
|
||||
period_duration, time_duration = duration.split("T")
|
||||
period_duration = period_duration[1:]
|
||||
else:
|
||||
period_duration = duration[1:]
|
||||
time_duration = ""
|
||||
|
||||
result: list[str] = []
|
||||
for designator in ("Y", "M", "W", "D"):
|
||||
if designator in period_duration:
|
||||
value, period_duration = period_duration.split(designator)
|
||||
if float(value):
|
||||
result.append(f"{value}{designator}")
|
||||
|
||||
if time_duration:
|
||||
for designator in ("H", "M", "S"):
|
||||
if designator in time_duration:
|
||||
value, time_duration = time_duration.split(designator)
|
||||
if float(value):
|
||||
result.append(f"{value}{designator.lower()}")
|
||||
return " ".join(result)
|
||||
|
||||
|
||||
@overload
|
||||
def datetime2ifc(dt: None, ifc_type: Any) -> None: ...
|
||||
@overload
|
||||
def datetime2ifc(
|
||||
dt: Union[datetime.date, str, None],
|
||||
ifc_type: Literal[
|
||||
"IfcDuration",
|
||||
"IfcTimeStamp",
|
||||
"IfcDateTime",
|
||||
"IfcDate",
|
||||
"IfcTime",
|
||||
"IfcCalendarDate",
|
||||
"IfcLocalTime",
|
||||
],
|
||||
) -> Union[int, str, dict[str, Any], None]: ...
|
||||
def datetime2ifc(
|
||||
dt: Union[datetime.date, str, None],
|
||||
ifc_type: Literal[
|
||||
"IfcDuration",
|
||||
"IfcTimeStamp",
|
||||
"IfcDateTime",
|
||||
"IfcDate",
|
||||
"IfcTime",
|
||||
"IfcCalendarDate",
|
||||
"IfcLocalTime",
|
||||
],
|
||||
) -> Union[int, str, dict[str, Any], None]:
|
||||
if isinstance(dt, str):
|
||||
if ifc_type == "IfcDuration":
|
||||
return dt
|
||||
try:
|
||||
dt = datetime.datetime.fromisoformat(dt)
|
||||
except:
|
||||
dt = datetime.time.fromisoformat(dt)
|
||||
elif dt is None:
|
||||
return
|
||||
|
||||
if ifc_type == "IfcDuration":
|
||||
return isodate.duration_isoformat(dt)
|
||||
elif ifc_type == "IfcTimeStamp":
|
||||
return int(dt.timestamp())
|
||||
elif ifc_type == "IfcDateTime":
|
||||
if isinstance(dt, datetime.datetime):
|
||||
return dt.isoformat()
|
||||
elif isinstance(dt, datetime.date):
|
||||
return datetime.datetime.combine(dt, datetime.datetime.min.time()).isoformat()
|
||||
elif ifc_type == "IfcDate":
|
||||
if isinstance(dt, datetime.datetime):
|
||||
return dt.date().isoformat()
|
||||
elif isinstance(dt, datetime.date):
|
||||
return dt.isoformat()
|
||||
elif ifc_type == "IfcTime":
|
||||
if isinstance(dt, datetime.datetime):
|
||||
return dt.time().isoformat()
|
||||
elif isinstance(dt, datetime.time):
|
||||
return dt.isoformat()
|
||||
elif ifc_type == "IfcCalendarDate":
|
||||
return {
|
||||
"DayComponent": dt.day,
|
||||
"MonthComponent": dt.month,
|
||||
"YearComponent": dt.year,
|
||||
}
|
||||
elif ifc_type == "IfcLocalTime":
|
||||
# TODO implement timezones
|
||||
return {
|
||||
"HourComponent": dt.hour,
|
||||
"MinuteComponent": dt.minute,
|
||||
"SecondComponent": dt.second,
|
||||
}
|
||||
|
||||
raise TypeError(f"Unsupported ifc_type for conversion from datetime.datetime = {ifc_type}, value = {dt}")
|
||||
|
||||
|
||||
def string_to_date(string):
|
||||
if not string:
|
||||
return None
|
||||
try:
|
||||
return parser.isoparse(string)
|
||||
except:
|
||||
try:
|
||||
return parser.parse(string, dayfirst=True, fuzzy=True)
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def string_to_duration(duration_string):
|
||||
# TODO support years, months, weeks aswell
|
||||
days = 0
|
||||
hours = 0
|
||||
minutes = 0
|
||||
seconds = 0
|
||||
match = findall(r"(\d+\.?\d*)d", duration_string)
|
||||
if match:
|
||||
days = float(match[0])
|
||||
match = findall(r"(\d+\.?\d*)h", duration_string)
|
||||
if match:
|
||||
hours = float(match[0])
|
||||
match = findall(r"(\d+\.?\d*)m", duration_string)
|
||||
if match:
|
||||
minutes = float(match[0])
|
||||
match = findall(r"(\d+\.?\d*)s", duration_string)
|
||||
if match:
|
||||
seconds = float(match[0])
|
||||
return isodate.duration_isoformat(datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds))
|
||||
|
||||
|
||||
def parse_duration(value: Union[str, None]) -> Union[datetime.timedelta, None]:
|
||||
if not value:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
if "P" in value:
|
||||
try:
|
||||
return isodate.parse_duration(value)
|
||||
except:
|
||||
print("Error parsing ISO string duration")
|
||||
return None
|
||||
else:
|
||||
try:
|
||||
final_string = "P"
|
||||
value_upper = value.upper()
|
||||
for char in value_upper:
|
||||
if char.isdigit():
|
||||
final_string += char
|
||||
elif char == "D":
|
||||
final_string += "D"
|
||||
if "H" in value_upper or "S" in value_upper or "MIN" in value_upper:
|
||||
final_string += "T"
|
||||
elif char == "W":
|
||||
final_string += "W"
|
||||
elif char == "M":
|
||||
final_string += "M"
|
||||
elif char == "Y":
|
||||
final_string += "Y"
|
||||
elif char == "H":
|
||||
final_string = (
|
||||
final_string[:1] + "T" + final_string[1:] if "T" not in final_string else final_string
|
||||
)
|
||||
final_string += "H"
|
||||
elif char == "M":
|
||||
if "MIN" in value_upper and "T" not in final_string:
|
||||
final_string = final_string[:1] + "T" + final_string[1:]
|
||||
final_string += "M"
|
||||
elif char == "S":
|
||||
final_string = (
|
||||
final_string[:1] + "T" + final_string[1:] if "T" not in final_string else final_string
|
||||
)
|
||||
final_string += "S"
|
||||
return isodate.parse_duration(final_string)
|
||||
except:
|
||||
print("error fuzzy parsing duration")
|
||||
return None
|
||||
|
||||
|
||||
def canonicalise_time(time: Union[datetime.datetime, None]) -> str:
|
||||
if not time:
|
||||
return "-"
|
||||
return time.strftime("%d/%m/%y")
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,424 @@
|
||||
{
|
||||
"IfcBeam": [
|
||||
"IfcBeamType"
|
||||
],
|
||||
"IfcBuilding": [
|
||||
"IfcSpaceType"
|
||||
],
|
||||
"IfcBuildingElement": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcBuildingElementComponent": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcBuildingElementPart": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcBuildingElementProxy": [
|
||||
"IfcBuildingElementProxyType"
|
||||
],
|
||||
"IfcBuildingStorey": [
|
||||
"IfcSpaceType"
|
||||
],
|
||||
"IfcChamferEdgeFeature": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcColumn": [
|
||||
"IfcColumnType"
|
||||
],
|
||||
"IfcCovering": [
|
||||
"IfcCoveringType"
|
||||
],
|
||||
"IfcCurtainWall": [
|
||||
"IfcCurtainWallType"
|
||||
],
|
||||
"IfcDiscreteAccessory": [
|
||||
"IfcDiscreteAccessoryType",
|
||||
"IfcVibrationIsolatorType"
|
||||
],
|
||||
"IfcDistributionChamberElement": [
|
||||
"IfcDistributionChamberElementType"
|
||||
],
|
||||
"IfcDistributionControlElement": [
|
||||
"IfcActuatorType",
|
||||
"IfcAlarmType",
|
||||
"IfcControllerType",
|
||||
"IfcFlowInstrumentType",
|
||||
"IfcSensorType"
|
||||
],
|
||||
"IfcDistributionElement": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcDistributionFlowElement": [
|
||||
"IfcDistributionChamberElementType"
|
||||
],
|
||||
"IfcDoor": [
|
||||
"IfcDoorStyle"
|
||||
],
|
||||
"IfcEdgeFeature": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcElectricDistributionPoint": [
|
||||
"IfcAirTerminalBoxType",
|
||||
"IfcDamperType",
|
||||
"IfcElectricTimeControlType",
|
||||
"IfcFlowMeterType",
|
||||
"IfcProtectiveDeviceType",
|
||||
"IfcSwitchingDeviceType",
|
||||
"IfcValveType"
|
||||
],
|
||||
"IfcElectricalElement": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcElement": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcElementAssembly": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcElementComponent": [
|
||||
"IfcDiscreteAccessoryType",
|
||||
"IfcFastenerType"
|
||||
],
|
||||
"IfcEnergyConversionDevice": [
|
||||
"IfcAirToAirHeatRecoveryType",
|
||||
"IfcBoilerType",
|
||||
"IfcChillerType",
|
||||
"IfcCoilType",
|
||||
"IfcCondenserType",
|
||||
"IfcCooledBeamType",
|
||||
"IfcCoolingTowerType",
|
||||
"IfcElectricGeneratorType",
|
||||
"IfcElectricMotorType",
|
||||
"IfcEvaporativeCoolerType",
|
||||
"IfcEvaporatorType",
|
||||
"IfcHeatExchangerType",
|
||||
"IfcHumidifierType",
|
||||
"IfcMotorConnectionType",
|
||||
"IfcSpaceHeaterType",
|
||||
"IfcTransformerType",
|
||||
"IfcTubeBundleType",
|
||||
"IfcUnitaryEquipmentType"
|
||||
],
|
||||
"IfcEquipmentElement": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcFastener": [
|
||||
"IfcFastenerType",
|
||||
"IfcMechanicalFastenerType"
|
||||
],
|
||||
"IfcFeatureElement": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcFeatureElementAddition": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcFeatureElementSubtraction": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcFlowController": [
|
||||
"IfcAirTerminalBoxType",
|
||||
"IfcDamperType",
|
||||
"IfcElectricTimeControlType",
|
||||
"IfcFlowMeterType",
|
||||
"IfcProtectiveDeviceType",
|
||||
"IfcSwitchingDeviceType",
|
||||
"IfcValveType"
|
||||
],
|
||||
"IfcFlowFitting": [
|
||||
"IfcCableCarrierFittingType",
|
||||
"IfcDuctFittingType",
|
||||
"IfcJunctionBoxType",
|
||||
"IfcPipeFittingType"
|
||||
],
|
||||
"IfcFlowMovingDevice": [
|
||||
"IfcCompressorType",
|
||||
"IfcFanType",
|
||||
"IfcPumpType"
|
||||
],
|
||||
"IfcFlowSegment": [
|
||||
"IfcCableCarrierSegmentType",
|
||||
"IfcCableSegmentType",
|
||||
"IfcDuctSegmentType",
|
||||
"IfcPipeSegmentType"
|
||||
],
|
||||
"IfcFlowStorageDevice": [
|
||||
"IfcElectricFlowStorageDeviceType",
|
||||
"IfcTankType"
|
||||
],
|
||||
"IfcFlowTerminal": [
|
||||
"IfcAirTerminalType",
|
||||
"IfcElectricApplianceType",
|
||||
"IfcElectricHeaterType",
|
||||
"IfcFireSuppressionTerminalType",
|
||||
"IfcGasTerminalType",
|
||||
"IfcLampType",
|
||||
"IfcLightFixtureType",
|
||||
"IfcOutletType",
|
||||
"IfcSanitaryTerminalType",
|
||||
"IfcStackTerminalType",
|
||||
"IfcWasteTerminalType"
|
||||
],
|
||||
"IfcFlowTreatmentDevice": [
|
||||
"IfcDuctSilencerType",
|
||||
"IfcFilterType"
|
||||
],
|
||||
"IfcFooting": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcFurnishingElement": [
|
||||
"IfcFurnishingElementType",
|
||||
"IfcFurnitureType",
|
||||
"IfcSystemFurnitureElementType"
|
||||
],
|
||||
"IfcMechanicalFastener": [
|
||||
"IfcMechanicalFastenerType"
|
||||
],
|
||||
"IfcMember": [
|
||||
"IfcMemberType"
|
||||
],
|
||||
"IfcOpeningElement": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcPile": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcPlate": [
|
||||
"IfcPlateType"
|
||||
],
|
||||
"IfcProjectionElement": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcRailing": [
|
||||
"IfcRailingType"
|
||||
],
|
||||
"IfcRamp": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcRampFlight": [
|
||||
"IfcRampFlightType"
|
||||
],
|
||||
"IfcReinforcingBar": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcReinforcingElement": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcReinforcingMesh": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcRoof": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcRoundedEdgeFeature": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcSite": [
|
||||
"IfcSpaceType"
|
||||
],
|
||||
"IfcSlab": [
|
||||
"IfcSlabType"
|
||||
],
|
||||
"IfcSpace": [
|
||||
"IfcSpaceType"
|
||||
],
|
||||
"IfcSpatialStructureElement": [
|
||||
"IfcSpaceType"
|
||||
],
|
||||
"IfcStair": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcStairFlight": [
|
||||
"IfcStairFlightType"
|
||||
],
|
||||
"IfcTendon": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcTendonAnchor": [
|
||||
"IfcBeamType",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcColumnType",
|
||||
"IfcCoveringType",
|
||||
"IfcCurtainWallType",
|
||||
"IfcMemberType",
|
||||
"IfcPlateType",
|
||||
"IfcRailingType",
|
||||
"IfcRampFlightType",
|
||||
"IfcSlabType",
|
||||
"IfcStairFlightType",
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcTransportElement": [
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcVirtualElement": [
|
||||
"IfcDistributionElementType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcWall": [
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcWallStandardCase": [
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcWindow": [
|
||||
"IfcWindowStyle"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
{
|
||||
"IfcActuator": [
|
||||
"IfcActuatorType"
|
||||
],
|
||||
"IfcAirTerminal": [
|
||||
"IfcAirTerminalType"
|
||||
],
|
||||
"IfcAirTerminalBox": [
|
||||
"IfcAirTerminalBoxType"
|
||||
],
|
||||
"IfcAirToAirHeatRecovery": [
|
||||
"IfcAirToAirHeatRecoveryType"
|
||||
],
|
||||
"IfcAlarm": [
|
||||
"IfcAlarmType"
|
||||
],
|
||||
"IfcAudioVisualAppliance": [
|
||||
"IfcAudioVisualApplianceType"
|
||||
],
|
||||
"IfcBeam": [
|
||||
"IfcBeamType"
|
||||
],
|
||||
"IfcBeamStandardCase": [
|
||||
"IfcBeamType"
|
||||
],
|
||||
"IfcBoiler": [
|
||||
"IfcBoilerType"
|
||||
],
|
||||
"IfcBuildingElementPart": [
|
||||
"IfcBuildingElementPartType"
|
||||
],
|
||||
"IfcBuildingElementProxy": [
|
||||
"IfcBuildingElementProxyType"
|
||||
],
|
||||
"IfcBurner": [
|
||||
"IfcBurnerType"
|
||||
],
|
||||
"IfcCableCarrierFitting": [
|
||||
"IfcCableCarrierFittingType"
|
||||
],
|
||||
"IfcCableCarrierSegment": [
|
||||
"IfcCableCarrierSegmentType"
|
||||
],
|
||||
"IfcCableFitting": [
|
||||
"IfcCableFittingType"
|
||||
],
|
||||
"IfcCableSegment": [
|
||||
"IfcCableSegmentType"
|
||||
],
|
||||
"IfcChiller": [
|
||||
"IfcChillerType"
|
||||
],
|
||||
"IfcChimney": [
|
||||
"IfcChimneyType"
|
||||
],
|
||||
"IfcCivilElement": [
|
||||
"IfcCivilElementType"
|
||||
],
|
||||
"IfcCoil": [
|
||||
"IfcCoilType"
|
||||
],
|
||||
"IfcColumn": [
|
||||
"IfcColumnType"
|
||||
],
|
||||
"IfcColumnStandardCase": [
|
||||
"IfcColumnType"
|
||||
],
|
||||
"IfcCommunicationsAppliance": [
|
||||
"IfcCommunicationsApplianceType"
|
||||
],
|
||||
"IfcCompressor": [
|
||||
"IfcCompressorType"
|
||||
],
|
||||
"IfcCondenser": [
|
||||
"IfcCondenserType"
|
||||
],
|
||||
"IfcController": [
|
||||
"IfcControllerType"
|
||||
],
|
||||
"IfcCooledBeam": [
|
||||
"IfcCooledBeamType"
|
||||
],
|
||||
"IfcCoolingTower": [
|
||||
"IfcCoolingTowerType"
|
||||
],
|
||||
"IfcCovering": [
|
||||
"IfcCoveringType"
|
||||
],
|
||||
"IfcCurtainWall": [
|
||||
"IfcCurtainWallType"
|
||||
],
|
||||
"IfcDamper": [
|
||||
"IfcDamperType"
|
||||
],
|
||||
"IfcDiscreteAccessory": [
|
||||
"IfcDiscreteAccessoryType"
|
||||
],
|
||||
"IfcDistributionChamberElement": [
|
||||
"IfcDistributionChamberElementType"
|
||||
],
|
||||
"IfcDistributionControlElement": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcDistributionElement": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcDistributionFlowElement": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcDoor": [
|
||||
"IfcDoorType",
|
||||
"IfcDoorStyle"
|
||||
],
|
||||
"IfcDoorStandardCase": [
|
||||
"IfcDoorType",
|
||||
"IfcDoorStyle"
|
||||
],
|
||||
"IfcDuctFitting": [
|
||||
"IfcDuctFittingType"
|
||||
],
|
||||
"IfcDuctSegment": [
|
||||
"IfcDuctSegmentType"
|
||||
],
|
||||
"IfcDuctSilencer": [
|
||||
"IfcDuctSilencerType"
|
||||
],
|
||||
"IfcElectricAppliance": [
|
||||
"IfcElectricApplianceType"
|
||||
],
|
||||
"IfcElectricDistributionBoard": [
|
||||
"IfcElectricDistributionBoardType"
|
||||
],
|
||||
"IfcElectricFlowStorageDevice": [
|
||||
"IfcElectricFlowStorageDeviceType"
|
||||
],
|
||||
"IfcElectricGenerator": [
|
||||
"IfcElectricGeneratorType"
|
||||
],
|
||||
"IfcElectricMotor": [
|
||||
"IfcElectricMotorType"
|
||||
],
|
||||
"IfcElectricTimeControl": [
|
||||
"IfcElectricTimeControlType"
|
||||
],
|
||||
"IfcElementAssembly": [
|
||||
"IfcElementAssemblyType"
|
||||
],
|
||||
"IfcEnergyConversionDevice": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcEngine": [
|
||||
"IfcEngineType"
|
||||
],
|
||||
"IfcEvaporativeCooler": [
|
||||
"IfcEvaporativeCoolerType"
|
||||
],
|
||||
"IfcEvaporator": [
|
||||
"IfcEvaporatorType"
|
||||
],
|
||||
"IfcFan": [
|
||||
"IfcFanType"
|
||||
],
|
||||
"IfcFastener": [
|
||||
"IfcFastenerType"
|
||||
],
|
||||
"IfcFilter": [
|
||||
"IfcFilterType"
|
||||
],
|
||||
"IfcFireSuppressionTerminal": [
|
||||
"IfcFireSuppressionTerminalType"
|
||||
],
|
||||
"IfcFlowController": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowFitting": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowInstrument": [
|
||||
"IfcFlowInstrumentType"
|
||||
],
|
||||
"IfcFlowMeter": [
|
||||
"IfcFlowMeterType"
|
||||
],
|
||||
"IfcFlowMovingDevice": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowSegment": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowStorageDevice": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowTerminal": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowTreatmentDevice": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFooting": [
|
||||
"IfcFootingType"
|
||||
],
|
||||
"IfcFurnishingElement": [
|
||||
"IfcFurnishingElementType"
|
||||
],
|
||||
"IfcFurniture": [
|
||||
"IfcFurnitureType"
|
||||
],
|
||||
"IfcGeographicElement": [
|
||||
"IfcGeographicElementType"
|
||||
],
|
||||
"IfcHeatExchanger": [
|
||||
"IfcHeatExchangerType"
|
||||
],
|
||||
"IfcHumidifier": [
|
||||
"IfcHumidifierType"
|
||||
],
|
||||
"IfcInterceptor": [
|
||||
"IfcInterceptorType"
|
||||
],
|
||||
"IfcJunctionBox": [
|
||||
"IfcJunctionBoxType"
|
||||
],
|
||||
"IfcLamp": [
|
||||
"IfcLampType"
|
||||
],
|
||||
"IfcLightFixture": [
|
||||
"IfcLightFixtureType"
|
||||
],
|
||||
"IfcMechanicalFastener": [
|
||||
"IfcMechanicalFastenerType"
|
||||
],
|
||||
"IfcMedicalDevice": [
|
||||
"IfcMedicalDeviceType"
|
||||
],
|
||||
"IfcMember": [
|
||||
"IfcMemberType"
|
||||
],
|
||||
"IfcMemberStandardCase": [
|
||||
"IfcMemberType"
|
||||
],
|
||||
"IfcMotorConnection": [
|
||||
"IfcMotorConnectionType"
|
||||
],
|
||||
"IfcOutlet": [
|
||||
"IfcOutletType"
|
||||
],
|
||||
"IfcPile": [
|
||||
"IfcPileType"
|
||||
],
|
||||
"IfcPipeFitting": [
|
||||
"IfcPipeFittingType"
|
||||
],
|
||||
"IfcPipeSegment": [
|
||||
"IfcPipeSegmentType"
|
||||
],
|
||||
"IfcPlate": [
|
||||
"IfcPlateType"
|
||||
],
|
||||
"IfcPlateStandardCase": [
|
||||
"IfcPlateType"
|
||||
],
|
||||
"IfcProtectiveDevice": [
|
||||
"IfcProtectiveDeviceType"
|
||||
],
|
||||
"IfcProtectiveDeviceTrippingUnit": [
|
||||
"IfcProtectiveDeviceTrippingUnitType"
|
||||
],
|
||||
"IfcPump": [
|
||||
"IfcPumpType"
|
||||
],
|
||||
"IfcRailing": [
|
||||
"IfcRailingType"
|
||||
],
|
||||
"IfcRamp": [
|
||||
"IfcRampType"
|
||||
],
|
||||
"IfcRampFlight": [
|
||||
"IfcRampFlightType"
|
||||
],
|
||||
"IfcReinforcingBar": [
|
||||
"IfcReinforcingBarType"
|
||||
],
|
||||
"IfcReinforcingMesh": [
|
||||
"IfcReinforcingMeshType"
|
||||
],
|
||||
"IfcRoof": [
|
||||
"IfcRoofType"
|
||||
],
|
||||
"IfcSanitaryTerminal": [
|
||||
"IfcSanitaryTerminalType"
|
||||
],
|
||||
"IfcSensor": [
|
||||
"IfcSensorType"
|
||||
],
|
||||
"IfcShadingDevice": [
|
||||
"IfcShadingDeviceType"
|
||||
],
|
||||
"IfcSlab": [
|
||||
"IfcSlabType"
|
||||
],
|
||||
"IfcSlabElementedCase": [
|
||||
"IfcSlabType"
|
||||
],
|
||||
"IfcSlabStandardCase": [
|
||||
"IfcSlabType"
|
||||
],
|
||||
"IfcSolarDevice": [
|
||||
"IfcSolarDeviceType"
|
||||
],
|
||||
"IfcSpace": [
|
||||
"IfcSpaceType"
|
||||
],
|
||||
"IfcSpaceHeater": [
|
||||
"IfcSpaceHeaterType"
|
||||
],
|
||||
"IfcSpatialZone": [
|
||||
"IfcSpatialZoneType"
|
||||
],
|
||||
"IfcStackTerminal": [
|
||||
"IfcStackTerminalType"
|
||||
],
|
||||
"IfcStair": [
|
||||
"IfcStairType"
|
||||
],
|
||||
"IfcStairFlight": [
|
||||
"IfcStairFlightType"
|
||||
],
|
||||
"IfcSwitchingDevice": [
|
||||
"IfcSwitchingDeviceType"
|
||||
],
|
||||
"IfcSystemFurnitureElement": [
|
||||
"IfcSystemFurnitureElementType"
|
||||
],
|
||||
"IfcTank": [
|
||||
"IfcTankType"
|
||||
],
|
||||
"IfcTendon": [
|
||||
"IfcTendonType"
|
||||
],
|
||||
"IfcTendonAnchor": [
|
||||
"IfcTendonAnchorType"
|
||||
],
|
||||
"IfcTransformer": [
|
||||
"IfcTransformerType"
|
||||
],
|
||||
"IfcTransportElement": [
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcTubeBundle": [
|
||||
"IfcTubeBundleType"
|
||||
],
|
||||
"IfcUnitaryControlElement": [
|
||||
"IfcUnitaryControlElementType"
|
||||
],
|
||||
"IfcUnitaryEquipment": [
|
||||
"IfcUnitaryEquipmentType"
|
||||
],
|
||||
"IfcValve": [
|
||||
"IfcValveType"
|
||||
],
|
||||
"IfcVibrationIsolator": [
|
||||
"IfcVibrationIsolatorType"
|
||||
],
|
||||
"IfcWall": [
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcWallElementedCase": [
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcWallStandardCase": [
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcWasteTerminal": [
|
||||
"IfcWasteTerminalType"
|
||||
],
|
||||
"IfcWindow": [
|
||||
"IfcWindowType",
|
||||
"IfcWindowStyle"
|
||||
],
|
||||
"IfcWindowStandardCase": [
|
||||
"IfcWindowType",
|
||||
"IfcWindowStyle"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
{
|
||||
"IfcActuator": [
|
||||
"IfcActuatorType"
|
||||
],
|
||||
"IfcAirTerminal": [
|
||||
"IfcAirTerminalType"
|
||||
],
|
||||
"IfcAirTerminalBox": [
|
||||
"IfcAirTerminalBoxType"
|
||||
],
|
||||
"IfcAirToAirHeatRecovery": [
|
||||
"IfcAirToAirHeatRecoveryType"
|
||||
],
|
||||
"IfcAlarm": [
|
||||
"IfcAlarmType"
|
||||
],
|
||||
"IfcAudioVisualAppliance": [
|
||||
"IfcAudioVisualApplianceType"
|
||||
],
|
||||
"IfcBeam": [
|
||||
"IfcBeamType"
|
||||
],
|
||||
"IfcBearing": [
|
||||
"IfcBearingType"
|
||||
],
|
||||
"IfcBoiler": [
|
||||
"IfcBoilerType"
|
||||
],
|
||||
"IfcBuildingElementPart": [
|
||||
"IfcBuildingElementPartType"
|
||||
],
|
||||
"IfcBuildingElementProxy": [
|
||||
"IfcBuildingElementProxyType"
|
||||
],
|
||||
"IfcBuiltElement": [
|
||||
"IfcBuiltElementType"
|
||||
],
|
||||
"IfcBurner": [
|
||||
"IfcBurnerType"
|
||||
],
|
||||
"IfcCableCarrierFitting": [
|
||||
"IfcCableCarrierFittingType"
|
||||
],
|
||||
"IfcCableCarrierSegment": [
|
||||
"IfcCableCarrierSegmentType"
|
||||
],
|
||||
"IfcCableFitting": [
|
||||
"IfcCableFittingType"
|
||||
],
|
||||
"IfcCableSegment": [
|
||||
"IfcCableSegmentType"
|
||||
],
|
||||
"IfcCaissonFoundation": [
|
||||
"IfcCaissonFoundationType"
|
||||
],
|
||||
"IfcChiller": [
|
||||
"IfcChillerType"
|
||||
],
|
||||
"IfcChimney": [
|
||||
"IfcChimneyType"
|
||||
],
|
||||
"IfcCivilElement": [
|
||||
"IfcCivilElementType"
|
||||
],
|
||||
"IfcCoil": [
|
||||
"IfcCoilType"
|
||||
],
|
||||
"IfcColumn": [
|
||||
"IfcColumnType"
|
||||
],
|
||||
"IfcCommunicationsAppliance": [
|
||||
"IfcCommunicationsApplianceType"
|
||||
],
|
||||
"IfcCompressor": [
|
||||
"IfcCompressorType"
|
||||
],
|
||||
"IfcCondenser": [
|
||||
"IfcCondenserType"
|
||||
],
|
||||
"IfcController": [
|
||||
"IfcControllerType"
|
||||
],
|
||||
"IfcConveyorSegment": [
|
||||
"IfcConveyorSegmentType"
|
||||
],
|
||||
"IfcCooledBeam": [
|
||||
"IfcCooledBeamType"
|
||||
],
|
||||
"IfcCoolingTower": [
|
||||
"IfcCoolingTowerType"
|
||||
],
|
||||
"IfcCourse": [
|
||||
"IfcCourseType"
|
||||
],
|
||||
"IfcCovering": [
|
||||
"IfcCoveringType"
|
||||
],
|
||||
"IfcCurtainWall": [
|
||||
"IfcCurtainWallType"
|
||||
],
|
||||
"IfcDamper": [
|
||||
"IfcDamperType"
|
||||
],
|
||||
"IfcDeepFoundation": [
|
||||
"IfcDeepFoundationType"
|
||||
],
|
||||
"IfcDiscreteAccessory": [
|
||||
"IfcDiscreteAccessoryType"
|
||||
],
|
||||
"IfcDistributionBoard": [
|
||||
"IfcDistributionBoardType"
|
||||
],
|
||||
"IfcDistributionChamberElement": [
|
||||
"IfcDistributionChamberElementType"
|
||||
],
|
||||
"IfcDistributionControlElement": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcDistributionElement": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcDistributionFlowElement": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcDoor": [
|
||||
"IfcDoorType"
|
||||
],
|
||||
"IfcDuctFitting": [
|
||||
"IfcDuctFittingType"
|
||||
],
|
||||
"IfcDuctSegment": [
|
||||
"IfcDuctSegmentType"
|
||||
],
|
||||
"IfcDuctSilencer": [
|
||||
"IfcDuctSilencerType"
|
||||
],
|
||||
"IfcEarthworksElement": [
|
||||
"IfcBuiltElementType"
|
||||
],
|
||||
"IfcEarthworksFill": [
|
||||
"IfcBuiltElementType"
|
||||
],
|
||||
"IfcElectricAppliance": [
|
||||
"IfcElectricApplianceType"
|
||||
],
|
||||
"IfcElectricDistributionBoard": [
|
||||
"IfcElectricDistributionBoardType"
|
||||
],
|
||||
"IfcElectricFlowStorageDevice": [
|
||||
"IfcElectricFlowStorageDeviceType"
|
||||
],
|
||||
"IfcElectricFlowTreatmentDevice": [
|
||||
"IfcElectricFlowTreatmentDeviceType"
|
||||
],
|
||||
"IfcElectricGenerator": [
|
||||
"IfcElectricGeneratorType"
|
||||
],
|
||||
"IfcElectricMotor": [
|
||||
"IfcElectricMotorType"
|
||||
],
|
||||
"IfcElectricTimeControl": [
|
||||
"IfcElectricTimeControlType"
|
||||
],
|
||||
"IfcElementAssembly": [
|
||||
"IfcElementAssemblyType"
|
||||
],
|
||||
"IfcEnergyConversionDevice": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcEngine": [
|
||||
"IfcEngineType"
|
||||
],
|
||||
"IfcEvaporativeCooler": [
|
||||
"IfcEvaporativeCoolerType"
|
||||
],
|
||||
"IfcEvaporator": [
|
||||
"IfcEvaporatorType"
|
||||
],
|
||||
"IfcFan": [
|
||||
"IfcFanType"
|
||||
],
|
||||
"IfcFastener": [
|
||||
"IfcFastenerType"
|
||||
],
|
||||
"IfcFilter": [
|
||||
"IfcFilterType"
|
||||
],
|
||||
"IfcFireSuppressionTerminal": [
|
||||
"IfcFireSuppressionTerminalType"
|
||||
],
|
||||
"IfcFlowController": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowFitting": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowInstrument": [
|
||||
"IfcFlowInstrumentType"
|
||||
],
|
||||
"IfcFlowMeter": [
|
||||
"IfcFlowMeterType"
|
||||
],
|
||||
"IfcFlowMovingDevice": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowSegment": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowStorageDevice": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowTerminal": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFlowTreatmentDevice": [
|
||||
"IfcDistributionElementType"
|
||||
],
|
||||
"IfcFooting": [
|
||||
"IfcFootingType"
|
||||
],
|
||||
"IfcFurnishingElement": [
|
||||
"IfcFurnishingElementType"
|
||||
],
|
||||
"IfcFurniture": [
|
||||
"IfcFurnitureType"
|
||||
],
|
||||
"IfcGeographicElement": [
|
||||
"IfcGeographicElementType"
|
||||
],
|
||||
"IfcHeatExchanger": [
|
||||
"IfcHeatExchangerType"
|
||||
],
|
||||
"IfcHumidifier": [
|
||||
"IfcHumidifierType"
|
||||
],
|
||||
"IfcImpactProtectionDevice": [
|
||||
"IfcImpactProtectionDeviceType"
|
||||
],
|
||||
"IfcInterceptor": [
|
||||
"IfcInterceptorType"
|
||||
],
|
||||
"IfcJunctionBox": [
|
||||
"IfcJunctionBoxType"
|
||||
],
|
||||
"IfcKerb": [
|
||||
"IfcKerbType"
|
||||
],
|
||||
"IfcLamp": [
|
||||
"IfcLampType"
|
||||
],
|
||||
"IfcLightFixture": [
|
||||
"IfcLightFixtureType"
|
||||
],
|
||||
"IfcLiquidTerminal": [
|
||||
"IfcLiquidTerminalType"
|
||||
],
|
||||
"IfcMechanicalFastener": [
|
||||
"IfcMechanicalFastenerType"
|
||||
],
|
||||
"IfcMedicalDevice": [
|
||||
"IfcMedicalDeviceType"
|
||||
],
|
||||
"IfcMember": [
|
||||
"IfcMemberType"
|
||||
],
|
||||
"IfcMobileTelecommunicationsAppliance": [
|
||||
"IfcMobileTelecommunicationsApplianceType"
|
||||
],
|
||||
"IfcMooringDevice": [
|
||||
"IfcMooringDeviceType"
|
||||
],
|
||||
"IfcMotorConnection": [
|
||||
"IfcMotorConnectionType"
|
||||
],
|
||||
"IfcNavigationElement": [
|
||||
"IfcNavigationElementType"
|
||||
],
|
||||
"IfcOutlet": [
|
||||
"IfcOutletType"
|
||||
],
|
||||
"IfcPavement": [
|
||||
"IfcPavementType"
|
||||
],
|
||||
"IfcPile": [
|
||||
"IfcPileType"
|
||||
],
|
||||
"IfcPipeFitting": [
|
||||
"IfcPipeFittingType"
|
||||
],
|
||||
"IfcPipeSegment": [
|
||||
"IfcPipeSegmentType"
|
||||
],
|
||||
"IfcPlate": [
|
||||
"IfcPlateType"
|
||||
],
|
||||
"IfcProtectiveDevice": [
|
||||
"IfcProtectiveDeviceType"
|
||||
],
|
||||
"IfcProtectiveDeviceTrippingUnit": [
|
||||
"IfcProtectiveDeviceTrippingUnitType"
|
||||
],
|
||||
"IfcPump": [
|
||||
"IfcPumpType"
|
||||
],
|
||||
"IfcRail": [
|
||||
"IfcRailType"
|
||||
],
|
||||
"IfcRailing": [
|
||||
"IfcRailingType"
|
||||
],
|
||||
"IfcRamp": [
|
||||
"IfcRampType"
|
||||
],
|
||||
"IfcRampFlight": [
|
||||
"IfcRampFlightType"
|
||||
],
|
||||
"IfcReinforcedSoil": [
|
||||
"IfcBuiltElementType"
|
||||
],
|
||||
"IfcReinforcingBar": [
|
||||
"IfcReinforcingBarType"
|
||||
],
|
||||
"IfcReinforcingMesh": [
|
||||
"IfcReinforcingMeshType"
|
||||
],
|
||||
"IfcRoof": [
|
||||
"IfcRoofType"
|
||||
],
|
||||
"IfcSanitaryTerminal": [
|
||||
"IfcSanitaryTerminalType"
|
||||
],
|
||||
"IfcSensor": [
|
||||
"IfcSensorType"
|
||||
],
|
||||
"IfcShadingDevice": [
|
||||
"IfcShadingDeviceType"
|
||||
],
|
||||
"IfcSign": [
|
||||
"IfcSignType"
|
||||
],
|
||||
"IfcSignal": [
|
||||
"IfcSignalType"
|
||||
],
|
||||
"IfcSlab": [
|
||||
"IfcSlabType"
|
||||
],
|
||||
"IfcSolarDevice": [
|
||||
"IfcSolarDeviceType"
|
||||
],
|
||||
"IfcSpace": [
|
||||
"IfcSpaceType"
|
||||
],
|
||||
"IfcSpaceHeater": [
|
||||
"IfcSpaceHeaterType"
|
||||
],
|
||||
"IfcSpatialZone": [
|
||||
"IfcSpatialZoneType"
|
||||
],
|
||||
"IfcStackTerminal": [
|
||||
"IfcStackTerminalType"
|
||||
],
|
||||
"IfcStair": [
|
||||
"IfcStairType"
|
||||
],
|
||||
"IfcStairFlight": [
|
||||
"IfcStairFlightType"
|
||||
],
|
||||
"IfcSwitchingDevice": [
|
||||
"IfcSwitchingDeviceType"
|
||||
],
|
||||
"IfcSystemFurnitureElement": [
|
||||
"IfcSystemFurnitureElementType"
|
||||
],
|
||||
"IfcTank": [
|
||||
"IfcTankType"
|
||||
],
|
||||
"IfcTendon": [
|
||||
"IfcTendonType"
|
||||
],
|
||||
"IfcTendonAnchor": [
|
||||
"IfcTendonAnchorType"
|
||||
],
|
||||
"IfcTendonConduit": [
|
||||
"IfcTendonConduitType"
|
||||
],
|
||||
"IfcTrackElement": [
|
||||
"IfcTrackElementType"
|
||||
],
|
||||
"IfcTransformer": [
|
||||
"IfcTransformerType"
|
||||
],
|
||||
"IfcTransportElement": [
|
||||
"IfcTransportElementType"
|
||||
],
|
||||
"IfcTubeBundle": [
|
||||
"IfcTubeBundleType"
|
||||
],
|
||||
"IfcUnitaryControlElement": [
|
||||
"IfcUnitaryControlElementType"
|
||||
],
|
||||
"IfcUnitaryEquipment": [
|
||||
"IfcUnitaryEquipmentType"
|
||||
],
|
||||
"IfcValve": [
|
||||
"IfcValveType"
|
||||
],
|
||||
"IfcVehicle": [
|
||||
"IfcVehicleType"
|
||||
],
|
||||
"IfcVibrationDamper": [
|
||||
"IfcVibrationDamperType"
|
||||
],
|
||||
"IfcVibrationIsolator": [
|
||||
"IfcVibrationIsolatorType"
|
||||
],
|
||||
"IfcWall": [
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcWallStandardCase": [
|
||||
"IfcWallType"
|
||||
],
|
||||
"IfcWasteTerminal": [
|
||||
"IfcWasteTerminalType"
|
||||
],
|
||||
"IfcWindow": [
|
||||
"IfcWindowType"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
# 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 zipfile
|
||||
from typing import IO, TypedDict, Union
|
||||
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
|
||||
class HeaderMetadata(TypedDict):
|
||||
name: NotRequired[str]
|
||||
# FILE_DESCRIPTION, not the description from FILE_NAME.
|
||||
description: NotRequired[str]
|
||||
implementation_level: NotRequired[str]
|
||||
time_stamp: NotRequired[str]
|
||||
schema_name: NotRequired[str]
|
||||
|
||||
|
||||
class IfcHeaderExtractor:
|
||||
"""An utility class for extracting header information from IFC files.
|
||||
|
||||
This class provides functionality to extract key metadata from the header section of
|
||||
IFC files without recreating the entire file as `ifcopenshell.file`.
|
||||
For optimization, extractor will search only for the first 50 lines
|
||||
of the IFC file for metadata.
|
||||
|
||||
Supported formats: .ifc, .ifczip.
|
||||
|
||||
Metadata available by the extraction is presented in the example below.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from ifcopenshell.util.file import IfcHeaderExtractor
|
||||
|
||||
extractor = IfcHeaderExtractor("path/to/your/file.ifc")
|
||||
# Get dictionary of the extracted metadata.
|
||||
header_info = extractor.extract()
|
||||
|
||||
# Print the extracted information
|
||||
|
||||
# ViewDefinition[DesignTransferView]
|
||||
print("File Description:", header_info.get("description"))
|
||||
|
||||
# 2;1
|
||||
print("Implementation Level:", header_info.get("implementation_level"))
|
||||
|
||||
# file.ifc
|
||||
print("File Name:", header_info.get("name"))
|
||||
|
||||
# 2024-06-25T15:48:10+05:00
|
||||
print("Time Stamp:", header_info.get("time_stamp"))
|
||||
|
||||
# IFC4X3_ADD2
|
||||
print("Schema Name:", header_info.get("schema_name"))
|
||||
"""
|
||||
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
|
||||
def extract(self) -> HeaderMetadata:
|
||||
extension = self.filepath.split(".")[-1]
|
||||
if extension.lower() == "ifc":
|
||||
with open(self.filepath) as ifc_file:
|
||||
return self.extract_ifc_spf(ifc_file)
|
||||
elif extension.lower() == "ifczip":
|
||||
return self.extract_ifc_zip()
|
||||
elif extension.lower() == "ifcsqlite":
|
||||
return {} # TODO
|
||||
raise ValueError(f"Unsupported file extension: '{extension}'.")
|
||||
|
||||
def extract_ifc_spf(self, ifc_file: Union[IO[bytes], IO[str]]) -> HeaderMetadata:
|
||||
# https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
|
||||
data = HeaderMetadata()
|
||||
max_lines_to_parse = 50
|
||||
for _ in range(max_lines_to_parse):
|
||||
line = next(ifc_file)
|
||||
if isinstance(line, bytes):
|
||||
line = line.decode("utf-8")
|
||||
if line.startswith("FILE_DESCRIPTION"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
data["description"] = part
|
||||
elif i == 3:
|
||||
data["implementation_level"] = part
|
||||
elif line.startswith("FILE_NAME"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
data["name"] = part
|
||||
elif i == 3:
|
||||
data["time_stamp"] = part
|
||||
elif line.startswith("FILE_SCHEMA"):
|
||||
data["schema_name"] = line.split("'")[1]
|
||||
break
|
||||
return data
|
||||
|
||||
def extract_ifc_zip(self) -> HeaderMetadata:
|
||||
archive = zipfile.ZipFile(self.filepath, "r")
|
||||
return self.extract_ifc_spf(archive.open(archive.filelist[0]))
|
||||
@@ -0,0 +1,186 @@
|
||||
# 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 Literal
|
||||
|
||||
from typing_extensions import assert_never
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.attribute
|
||||
|
||||
# COBie actually uses an exclusion list, but this inclusion list is equivalent.
|
||||
cobie_type_classes = [
|
||||
"IfcDoorStyle",
|
||||
"IfcBuildingElementProxyType",
|
||||
"IfcChimneyType",
|
||||
"IfcCoveringType",
|
||||
"IfcDoorType",
|
||||
"IfcFootingType",
|
||||
"IfcPileType",
|
||||
"IfcRoofType",
|
||||
"IfcShadingDeviceType",
|
||||
"IfcWindowType",
|
||||
"IfcDistributionControlElementType",
|
||||
"IfcDistributionChamberElementType",
|
||||
"IfcEnergyConversionDeviceType",
|
||||
"IfcFlowControllerType",
|
||||
"IfcFlowMovingDeviceType",
|
||||
"IfcFlowStorageDeviceType",
|
||||
"IfcFlowTerminalType",
|
||||
"IfcFlowTreatmentDeviceType",
|
||||
"IfcElementAssemblyType",
|
||||
"IfcBuildingElementPartType",
|
||||
"IfcDiscreteAccessoryType",
|
||||
"IfcMechanicalFastenerType",
|
||||
"IfcReinforcingElementType",
|
||||
"IfcVibrationIsolatorType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcGeographicElementType",
|
||||
"IfcTransportElementType",
|
||||
"IfcSpatialZoneType",
|
||||
"IfcWindowStyle",
|
||||
]
|
||||
|
||||
cobie_component_classes = [
|
||||
"IfcBuildingElementProxy",
|
||||
"IfcChimney",
|
||||
"IfcCovering",
|
||||
"IfcDoor",
|
||||
"IfcShadingDevice",
|
||||
"IfcWindow",
|
||||
"IfcDistributionControlElement",
|
||||
"IfcDistributionChamberElement",
|
||||
"IfcEnergyConversionDevice",
|
||||
"IfcFlowController",
|
||||
"IfcFlowMovingDevice",
|
||||
"IfcFlowStorageDevice",
|
||||
"IfcFlowTerminal",
|
||||
"IfcFlowTreatmentDevice",
|
||||
"IfcDiscreteAccessory",
|
||||
"IfcTendon",
|
||||
"IfcTendonAnchor",
|
||||
"IfcVibrationIsolator",
|
||||
"IfcFurnishingElement",
|
||||
"IfcGeographicElement",
|
||||
"IfcTransportElement",
|
||||
]
|
||||
|
||||
fmhem_classes_ifc4 = [
|
||||
"IfcDoorType",
|
||||
"IfcWindowType",
|
||||
"IfcShadingDeviceType",
|
||||
"IfcDistributionControlElementType",
|
||||
"IfcEnergyConversionDeviceType",
|
||||
"IfcFlowControllerType",
|
||||
"IfcFlowMovingDeviceType",
|
||||
"IfcFlowStorageDeviceType",
|
||||
"IfcFlowTerminalType",
|
||||
"IfcFlowTreatmentDeviceType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType",
|
||||
]
|
||||
|
||||
fmhem_classes_ifc2x3 = [
|
||||
"IfcDoorStyle",
|
||||
"IfcWindowStyle",
|
||||
"IfcShadingDeviceType",
|
||||
"IfcDistributionControlElementType",
|
||||
"IfcEnergyConversionDeviceType",
|
||||
"IfcFlowControllerType",
|
||||
"IfcFlowMovingDeviceType",
|
||||
"IfcFlowStorageDeviceType",
|
||||
"IfcFlowTerminalType",
|
||||
"IfcFlowTreatmentDeviceType",
|
||||
"IfcFurnishingElementType",
|
||||
"IfcTransportElementType",
|
||||
]
|
||||
|
||||
fmhem_excluded_classes = [
|
||||
"IfcCooledBeamType",
|
||||
"IfcBurnerType",
|
||||
"IfcCoilType",
|
||||
"IfcLampType",
|
||||
]
|
||||
|
||||
|
||||
def get_cobie_types(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
|
||||
elements = []
|
||||
for ifc_class in cobie_type_classes:
|
||||
try:
|
||||
elements += ifc_file.by_type(ifc_class)
|
||||
except:
|
||||
pass
|
||||
return elements
|
||||
|
||||
|
||||
def get_cobie_components(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
|
||||
elements = []
|
||||
for ifc_class in cobie_component_classes:
|
||||
try:
|
||||
elements += ifc_file.by_type(ifc_class)
|
||||
except:
|
||||
pass
|
||||
return elements
|
||||
|
||||
|
||||
def get_fmhem_types(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
|
||||
elements = []
|
||||
if ifc_file.schema == "IFC2X3":
|
||||
fmhem_classes = fmhem_classes_ifc2x3
|
||||
else:
|
||||
fmhem_classes = fmhem_classes_ifc4
|
||||
for ifc_class in fmhem_classes:
|
||||
try:
|
||||
elements += [e for e in ifc_file.by_type(ifc_class) if e.is_a() not in fmhem_excluded_classes]
|
||||
except:
|
||||
pass
|
||||
return elements
|
||||
|
||||
|
||||
def get_fmhem_classes(schema: Literal["IFC4", "IFC2X3"] = "IFC4") -> dict[str, list[str]]:
|
||||
results = {}
|
||||
|
||||
def get_fmhem_class(declaration: W.entity) -> None:
|
||||
if declaration.name() in fmhem_excluded_classes:
|
||||
pass
|
||||
elif declaration.is_abstract():
|
||||
pass
|
||||
else:
|
||||
types = []
|
||||
for attribute in declaration.all_attributes():
|
||||
if attribute.name() == "PredefinedType":
|
||||
types = list(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if "NOTDEFINED" in types:
|
||||
types.remove("NOTDEFINED")
|
||||
results[declaration.name()] = types
|
||||
|
||||
for subtype in declaration.subtypes():
|
||||
get_fmhem_class(subtype)
|
||||
|
||||
if schema == "IFC4":
|
||||
classes = fmhem_classes_ifc4
|
||||
elif schema == "IFC2X3":
|
||||
classes = fmhem_classes_ifc2x3
|
||||
else:
|
||||
assert_never(schema)
|
||||
schema_ = ifcopenshell.schema_by_name(schema)
|
||||
for ifc_class in classes:
|
||||
declaration = schema_.declaration_by_name(ifc_class)
|
||||
get_fmhem_class(declaration)
|
||||
return results
|
||||
@@ -0,0 +1,425 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 @Andrej730
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
RUN_FROM_DEV_REPO = False
|
||||
|
||||
import glob
|
||||
import sys
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from lxml import etree
|
||||
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
|
||||
if not RUN_FROM_DEV_REPO:
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
BASE_MODULE_PATH = Path(__file__).parent
|
||||
|
||||
if not RUN_FROM_DEV_REPO:
|
||||
IFC4x3_HTML_ZIP_LOCATION = BASE_MODULE_PATH / "annex-a-psd.zip"
|
||||
IFC4x3_OUTPUT_PATH = BASE_MODULE_PATH / "schema/Pset_IFC4X3.ifc"
|
||||
else:
|
||||
IFC4x3_PSD_LOCATION = BASE_MODULE_PATH / "../output/psd"
|
||||
try:
|
||||
IFC4x3_OUTPUT_PATH = sys.argv[1]
|
||||
except:
|
||||
IFC4x3_OUTPUT_PATH = BASE_MODULE_PATH / "../output/Pset_IFC4X3.ifc"
|
||||
|
||||
IFC2x3_HTML_ZIP_LOCATION = BASE_MODULE_PATH / "IFC2x3_TC1_HTML_distribution-pset_errata.zip"
|
||||
IFC2x3_OUTPUT_PATH = BASE_MODULE_PATH / "schema/Pset_IFC2X3.ifc"
|
||||
|
||||
PROPERTY_TYPES_DICT = {
|
||||
"TypePropertySingleValue": ("P_SINGLEVALUE", "type"),
|
||||
# in IFC2X3 weirdly xmls have TypeSimpleProperty
|
||||
# which is actually more P_REFERENCEVALUE value, not P_SINGLEVALUE
|
||||
# because it utilizes IfcTimeSeries
|
||||
"TypeSimpleProperty": ("P_REFERENCEVALUE", "type"),
|
||||
"TypePropertyListValue": ("P_LISTVALUE", "type"),
|
||||
"TypePropertyBoundedValue": ("P_BOUNDEDVALUE", "type"),
|
||||
"TypePropertyReferenceValue": ("P_REFERENCEVALUE", "reftype"),
|
||||
"TypePropertyEnumeratedValue": ("P_ENUMERATEDVALUE", ""),
|
||||
"TypePropertyTableValue": ("P_TABLEVALUE", "type"),
|
||||
"TypeComplexProperty": ("P_COMPLEX", ""),
|
||||
}
|
||||
|
||||
|
||||
class PsetTemplatesGenerator:
|
||||
def parse_ifc4x3_data(self):
|
||||
print("Starting parsing data for IFC4X3...")
|
||||
|
||||
if not RUN_FROM_DEV_REPO:
|
||||
if not IFC4x3_HTML_ZIP_LOCATION.is_file():
|
||||
raise Exception(
|
||||
f'ISO release for Ifc4.3.2.0 expected to be located in "{IFC4x3_HTML_ZIP_LOCATION.resolve()}"\n'
|
||||
"For generating ifc pset library please either setup docs as described above \n"
|
||||
"or change IFC4x3_HTML_ZIP_LOCATION in the script accordingly.\n"
|
||||
"You can download docs from the repository: \n"
|
||||
"https://standards.buildingsmart.org/IFC/RELEASE/IFC4_3/HTML/annex-a-psd.zip"
|
||||
)
|
||||
# unzip the data
|
||||
pset_data_location = BASE_MODULE_PATH / "temp/annex-a-psd"
|
||||
with zipfile.ZipFile(IFC4x3_HTML_ZIP_LOCATION, "r") as fi_zip:
|
||||
fi_zip.extractall(pset_data_location)
|
||||
else:
|
||||
if not IFC4x3_PSD_LOCATION.is_dir():
|
||||
raise Exception(
|
||||
f'Psets xmls files expected to be in folder "{IFC4x3_PSD_LOCATION.resolve()}\\"\n'
|
||||
"For generating ifc pset library please either setup docs as described above \n"
|
||||
"or change IFC4x3_PSD_LOCATION in the script accordingly."
|
||||
)
|
||||
pset_data_location = IFC4x3_PSD_LOCATION
|
||||
|
||||
pset_data_glob = f"{pset_data_location}/*.xml"
|
||||
self.parse_psets_data("IFC4X3", pset_data_glob, "IFC4X3 Property Set Templates", str(IFC4x3_OUTPUT_PATH))
|
||||
|
||||
if not RUN_FROM_DEV_REPO:
|
||||
shutil.rmtree(pset_data_location)
|
||||
|
||||
def parse_ifc2x3_data(self):
|
||||
print("Starting parsing data for IFC2X3...")
|
||||
if not IFC2x3_HTML_ZIP_LOCATION.is_file():
|
||||
raise Exception(
|
||||
f'ISO release for IFC2x3 TC1 expected to be located in "{IFC2x3_HTML_ZIP_LOCATION.resolve()}"\n'
|
||||
"For doc extraction please either setup docs as described above \n"
|
||||
"or change IFC2x3_HTML_LOCATION in the script accordingly.\n"
|
||||
"You can download docs from the url: \n"
|
||||
"https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/IFC2x3_TC1_HTML_distribution-pset_errata.zip"
|
||||
)
|
||||
|
||||
# unzip the data
|
||||
pset_data_location = BASE_MODULE_PATH / "temp/ifc2x3_html"
|
||||
with zipfile.ZipFile(IFC2x3_HTML_ZIP_LOCATION, "r") as fi_zip:
|
||||
fi_zip.extractall(pset_data_location)
|
||||
|
||||
pset_data_glob = f"{pset_data_location}/**/pset*.xml"
|
||||
# we're using IFC4X3 since IfcPropertySetTemplate and IfcRelDeclares
|
||||
# are not available in IFC2X3
|
||||
self.parse_psets_data("IFC4X3", pset_data_glob, "IFC2X3 Property Set Templates", str(IFC2x3_OUTPUT_PATH))
|
||||
shutil.rmtree(pset_data_location)
|
||||
|
||||
def ifc_entity(self, entity_name, **kwargs):
|
||||
entity = self.ifc_file.create_entity(entity_name, **kwargs)
|
||||
return entity
|
||||
|
||||
def parse_psets_data(self, schema_name, pset_data_glob, project_name, ifc_output_path):
|
||||
schema_name = schema_name.upper()
|
||||
self.ifc_file = ifcopenshell.api.project.create_file(version=schema_name)
|
||||
self.units = dict()
|
||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.ifc_file.schema_identifier)
|
||||
|
||||
derived_unit_enum = schema.declaration_by_name("IfcDerivedUnitEnum").as_enumeration_type()
|
||||
assert derived_unit_enum
|
||||
self.ifc_derived_unit_enum = derived_unit_enum.enumeration_items()
|
||||
|
||||
ifc_unit_enum = schema.declaration_by_name("IfcUnitEnum").as_enumeration_type()
|
||||
assert ifc_unit_enum
|
||||
self.ifc_unit_enum = ifc_unit_enum.enumeration_items()
|
||||
|
||||
value_select = schema.declaration_by_name("IfcValue").as_select_type()
|
||||
assert value_select
|
||||
select_types = cast(tuple[W.select_type, ...], value_select.select_list())
|
||||
self.ifc_value_types = [t.name() for t in chain(*[select_type.select_list() for select_type in select_types])]
|
||||
|
||||
project = self.ifc_entity("IfcProject", Name=project_name, GlobalId=ifcopenshell.guid.new())
|
||||
psets_list = []
|
||||
if schema_name == "IFC2X3":
|
||||
rel = None
|
||||
else:
|
||||
rel = self.ifc_entity(
|
||||
"IfcRelDeclares",
|
||||
RelatedDefinitions=psets_list,
|
||||
RelatingContext=project,
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
)
|
||||
|
||||
# in IFC4 there is also
|
||||
# IFCLIBRARYREFERENCE after each (sometimes multiple of them if there are several languages involved)
|
||||
# 1) enumeration item (also with IFCRELASSOCIATESLIBRARY)
|
||||
# 2) enumeration
|
||||
# 3) property set (also with IFCRELASSOCIATESLIBRARY)
|
||||
# 4) property definition (also with IFCRELASSOCIATESLIBRARY)
|
||||
# but in ifc4x3 there is no data for those library references
|
||||
# TODO: need to add it to .ifc for IFC4X3 too
|
||||
# if https://github.com/buildingSMART/IFC4.3.x-development/issues/587 is resolved
|
||||
|
||||
# iterate through all xmls files
|
||||
for pset_path in glob.iglob(pset_data_glob, recursive=True):
|
||||
pset_path = Path(pset_path)
|
||||
|
||||
with open(pset_path, "r", encoding="utf-8") as fi:
|
||||
root_xml = etree.fromstring(fi.read())
|
||||
pset_name = root_xml.find("Name").text
|
||||
# pset / qset
|
||||
pset_type = True if pset_name.split("_")[0] == "Pset" else False
|
||||
|
||||
# TODO: if guids provided in xml should use them instead
|
||||
pset_guid = ifcopenshell.guid.new()
|
||||
applicable_entities = [i.text for i in root_xml.find("ApplicableClasses").findall("ClassName")]
|
||||
pset = self.ifc_entity(
|
||||
"IfcPropertySetTemplate",
|
||||
GlobalId=pset_guid,
|
||||
TemplateType=root_xml.get("templatetype"),
|
||||
Name=pset_name,
|
||||
Description=root_xml.find("Definition").text,
|
||||
ApplicableEntity=",".join(applicable_entities).strip(),
|
||||
)
|
||||
if project_name.startswith("IFC2X3"):
|
||||
pset.TemplateType = self.get_pset_template_type_ifc2x3(pset)
|
||||
else:
|
||||
pset.TemplateType = root_xml.get("templatetype")
|
||||
|
||||
# NOTE: there is also Applicability tag
|
||||
# but it's seems always empty in ifc4 and ifc4x3
|
||||
# in ifc2x3 it's present but contains just applicability string description
|
||||
# e.g. "IfcElectricMotorType, IfcFlowMovingDeviceType entities." for "Pset_ElectricMotorTypeCommon"
|
||||
|
||||
pdef_entities = []
|
||||
for pdef in root_xml.find("PropertyDefs" if pset_type else "QtoDefs").getchildren():
|
||||
pset_property = self.get_property_from_pdef(pset_name, pset_type, pdef)
|
||||
if not pset_property:
|
||||
continue
|
||||
pdef_entities.append(pset_property)
|
||||
pset.HasPropertyTemplates = pdef_entities
|
||||
psets_list.append(pset)
|
||||
|
||||
print(f"{len(psets_list)} psets parsed.")
|
||||
if rel:
|
||||
rel.RelatedDefinitions = psets_list
|
||||
self.ifc_file.write(ifc_output_path)
|
||||
|
||||
def get_property_from_pdef(self, pset_name, pset_type, pdef):
|
||||
pset_prop_guid = ifcopenshell.guid.new()
|
||||
pset_property_name = pdef.find("Name").text
|
||||
|
||||
# figuring pset property types and related values
|
||||
if not pset_type:
|
||||
property_type_tag = None
|
||||
else:
|
||||
try:
|
||||
assert len(pdef.find("PropertyType").getchildren()) == 1, "Not implemented case"
|
||||
except AssertionError:
|
||||
warning_message = (
|
||||
f"WARNING: met not handles case with multiple property types "
|
||||
f"- {pset_name}/{pset_property_name}."
|
||||
"This property may have incorrect data parsed."
|
||||
)
|
||||
print(warning_message)
|
||||
|
||||
property_type_tag = pdef.find("PropertyType").getchildren()[0].tag
|
||||
if property_type_tag not in PROPERTY_TYPES_DICT:
|
||||
warning_message = (
|
||||
f"WARNING: not implemented property type "
|
||||
f"{property_type_tag} ({pset_name}/{pset_property_name}). "
|
||||
"This property will be skipped. Need to rework the code to support that type of property."
|
||||
)
|
||||
print(warning_message)
|
||||
return
|
||||
|
||||
# NOTE: there is also ValueDef tag
|
||||
# which could have two subtags - MinValue and MaxValue
|
||||
# but it's seems to be present only in ifc2x3 and everywhere values are either "", "0", "?"
|
||||
# so just ignoring it
|
||||
|
||||
# create pset property
|
||||
if not pset_type or property_type_tag != "TypeComplexProperty":
|
||||
pset_property = self.ifc_entity("IfcSimplePropertyTemplate")
|
||||
# tested on IFC4_ADD2.ifc - all props are READWRITE by default
|
||||
pset_property.AccessState = "READWRITE"
|
||||
else:
|
||||
pset_property = self.ifc_entity("IfcComplexPropertyTemplate")
|
||||
complex_prop_xml = pdef.find("PropertyType/TypeComplexProperty")
|
||||
child_properties = [
|
||||
self.get_property_from_pdef(pset_name, pset_type, child_pdef)
|
||||
for child_pdef in complex_prop_xml.findall("PropertyDef")
|
||||
]
|
||||
pset_property.UsageName = complex_prop_xml.get("name")
|
||||
pset_property.HasPropertyTemplates = child_properties
|
||||
|
||||
pset_property.GlobalId = pset_prop_guid
|
||||
pset_property.Description = pdef.find("Definition").text
|
||||
pset_property.Name = pset_property_name
|
||||
|
||||
self.add_prop_type_params_to_prop(pset_type, pdef, pset_property, property_type_tag)
|
||||
return pset_property
|
||||
|
||||
def get_unit(self, unit_type):
|
||||
# to define USERDEFINED unit type we'd need more info
|
||||
# like UserDefinedType name and elements for IfcDerivedUnit
|
||||
# which is not provided in .xmls
|
||||
if unit_type != "USERDEFINED":
|
||||
return None
|
||||
|
||||
if unit_type in self.units:
|
||||
return self.units[unit_type]
|
||||
|
||||
unit_entity = None
|
||||
if unit_type in self.ifc_derived_unit_enum:
|
||||
# TODO: define derived units if there will be someday api like `ifcopenshell.api.unit.add_derived_unit(...)`
|
||||
# since creating IfcDerivedUnit is more complex and requiring settting up elements it consists of
|
||||
# and related IfcNamedUnits
|
||||
# unit_entity = ifcopenshell.api.unit.add_derived_unit(self.ifc_file, unit_type=unit_type)
|
||||
pass
|
||||
elif unit_type in self.ifc_unit_enum:
|
||||
unit_entity = ifcopenshell.api.unit.add_si_unit(self.ifc_file, unit_type=unit_type)
|
||||
elif unit_type == "IFCMONETARYUNIT":
|
||||
self.ifc_entity("IFCMONETARYUNIT", Currency="")
|
||||
else:
|
||||
print(f"WARNING. Wasn't able to find units {unit_type} in schema.")
|
||||
self.units[unit_type] = unit_entity
|
||||
return unit_entity
|
||||
|
||||
def add_prop_type_params_to_prop(self, pset_type, pdef, pset_property, property_type_tag):
|
||||
if not pset_type:
|
||||
property_type = pdef.find("QtoType").text
|
||||
else:
|
||||
property_type, property_type_parse = PROPERTY_TYPES_DICT[property_type_tag]
|
||||
|
||||
pset_property.TemplateType = property_type
|
||||
|
||||
if not pset_type or property_type == "P_COMPLEX":
|
||||
# qsets and complex props don't have measure types
|
||||
return
|
||||
|
||||
property_type_path = f"PropertyType/{property_type_tag}"
|
||||
property_type_xml = pdef.find(property_type_path)
|
||||
|
||||
# usually it's only for P_TABLEVALUE
|
||||
expression_xml = property_type_xml.find("Expression")
|
||||
if expression_xml is not None and expression_xml.text != "-":
|
||||
pset_property.Expression = expression_xml.text
|
||||
|
||||
# figure measure type for property sets
|
||||
if property_type == "P_ENUMERATEDVALUE":
|
||||
# always create new enumeration for the new properties
|
||||
# even if the same enumeration was already used before
|
||||
# example of reoccuring enumeration in .ifc for ifc4 - PEnum_ElementStatus
|
||||
enum_items = [i.text for i in property_type_xml.findall(f"EnumList/EnumItem")]
|
||||
enum_items = [self.ifc_entity("IfcLabel", wrappedValue=i) for i in enum_items]
|
||||
prop_enumeration = self.ifc_entity(
|
||||
"IfcPropertyEnumeration",
|
||||
Name=property_type_xml.find("EnumList").get("name"),
|
||||
EnumerationValues=enum_items,
|
||||
)
|
||||
pset_property.Enumerators = prop_enumeration
|
||||
pset_property.PrimaryMeasureType = "IfcLabel"
|
||||
|
||||
elif property_type == "P_TABLEVALUE":
|
||||
pset_property.PrimaryMeasureType = property_type_xml.find("DefiningValue/DataType").get("type")
|
||||
pset_property.SecondaryMeasureType = property_type_xml.find("DefinedValue/DataType").get("type")
|
||||
else:
|
||||
if property_type == "P_REFERENCEVALUE":
|
||||
if property_type_tag == "TypeSimpleProperty":
|
||||
type_xml = property_type_xml.find("DataType")
|
||||
primary_measure_type = type_xml.get(property_type_parse)
|
||||
unit_type = property_type_xml.find("UnitType")
|
||||
secondary_measure_type = (
|
||||
unit_type.get(property_type_parse).strip() if unit_type is not None else None
|
||||
)
|
||||
|
||||
if primary_measure_type != "IfcTimeSeries":
|
||||
unit = self.get_unit(secondary_measure_type)
|
||||
secondary_measure_type = primary_measure_type
|
||||
primary_measure_type = "IfcTimeSeries"
|
||||
pset_property.PrimaryUnit = unit
|
||||
|
||||
pset_property.PrimaryMeasureType = primary_measure_type
|
||||
pset_property.SecondaryMeasureType = secondary_measure_type
|
||||
else:
|
||||
pset_property.PrimaryMeasureType = property_type_xml.get(property_type_parse)
|
||||
# TODO: ifc4add2 seems to have some secondary measure types
|
||||
# need to add it in ifc4x3 too
|
||||
# if https://github.com/buildingSMART/IFC4.3.x-development/issues/586 is resolved
|
||||
else:
|
||||
if property_type == "P_LISTVALUE":
|
||||
property_type_node = "ListValue"
|
||||
if property_type in ("P_SINGLEVALUE", "P_BOUNDEDVALUE"):
|
||||
property_type_node = "DataType"
|
||||
|
||||
# only used only in ifc2x3, omitted in ifc4 and ifc4x3
|
||||
unit_type_xml = property_type_xml.find("UnitType")
|
||||
|
||||
if unit_type_xml is not None:
|
||||
unit_type = unit_type_xml.get(property_type_parse).strip()
|
||||
unit_entity = self.get_unit(unit_type)
|
||||
pset_property.PrimaryUnit = unit_entity
|
||||
|
||||
type_xml = property_type_xml.find(property_type_node)
|
||||
if property_type_node == "ListValue":
|
||||
# for ListValue data type is contained in a single <DataType>
|
||||
primary_measure_type = type_xml.find("DataType").get(property_type_parse)
|
||||
else:
|
||||
primary_measure_type = type_xml.get(property_type_parse)
|
||||
|
||||
primary_measure_type = primary_measure_type.strip()
|
||||
if property_type == "P_SINGLEVALUE" and primary_measure_type not in self.ifc_value_types:
|
||||
print(
|
||||
f"Error assinging {primary_measure_type} as PrimaryMeasureType - it's not IfcValue, {property_type_tag}"
|
||||
)
|
||||
|
||||
pset_property.PrimaryMeasureType = primary_measure_type
|
||||
|
||||
def get_pset_template_type_ifc2x3(self, pset_template: ifcopenshell.entity_instance) -> str:
|
||||
def declaration_is_a(declaration: ifcopenshell_wrapper.declaration, ifc_class: str) -> bool:
|
||||
if declaration.name() == ifc_class:
|
||||
return True
|
||||
super_type = declaration.supertype()
|
||||
if not super_type:
|
||||
return False
|
||||
return declaration_is_a(super_type, ifc_class)
|
||||
|
||||
name = pset_template.Name
|
||||
applicability = pset_template.ApplicableEntity
|
||||
schema = ifcopenshell.schema_by_name("IFC2X3")
|
||||
|
||||
if "PHistory" in name:
|
||||
return "PSET_PERFORMANCEDRIVEN"
|
||||
applicable_types = applicability.replace(", ", ",").split(",")
|
||||
for applicable_type in applicable_types:
|
||||
if not applicable_type:
|
||||
continue
|
||||
parts = applicable_type.split("/")
|
||||
assert 3 > len(parts) > 0
|
||||
if parts[0].isupper(): # IFC2X3 thing
|
||||
applicable_type = parts[1]
|
||||
else:
|
||||
applicable_type = parts[0]
|
||||
applicable_type = applicable_type.strip()
|
||||
|
||||
declaration = schema.declaration_by_name(applicable_type)
|
||||
if declaration_is_a(declaration, "IfcTypeObject"):
|
||||
return "PSET_TYPEDRIVENOVERRIDE"
|
||||
# ifc4x3+
|
||||
elif declaration_is_a(declaration, "IfcProfileDef"):
|
||||
return "PSET_PROFILEDRIVEN"
|
||||
elif declaration_is_a(declaration, "IfcMaterialDefinition"):
|
||||
return "PSET_MATERIALDRIVEN"
|
||||
return "PSET_OCCURRENCEDRIVEN"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
templates_generator = PsetTemplatesGenerator()
|
||||
templates_generator.parse_ifc4x3_data()
|
||||
if not RUN_FROM_DEV_REPO:
|
||||
templates_generator.parse_ifc2x3_data()
|
||||
@@ -0,0 +1,691 @@
|
||||
# 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 math
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
from typing import Any, NamedTuple, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
|
||||
MatrixType = ifcopenshell.util.placement.MatrixType
|
||||
|
||||
|
||||
class HelmertTransformation(NamedTuple):
|
||||
e: float
|
||||
n: float
|
||||
h: float
|
||||
xaa: float
|
||||
xao: float
|
||||
scale: float
|
||||
factor_x: float
|
||||
factor_y: float
|
||||
factor_z: float
|
||||
|
||||
|
||||
def dms2dd(degrees: int, minutes: int, seconds: int, us: int = 0) -> float:
|
||||
"""Convert degrees, minutes, and (micro)seconds to decimal degrees
|
||||
All components must be either positive or negative.
|
||||
|
||||
:param degrees: The degrees component
|
||||
:param minutes: The minutes component
|
||||
:param seconds: The seconds component
|
||||
:param us: The microseconds component
|
||||
:return: The angle in decimal degrees.
|
||||
"""
|
||||
all_positive_or_zero = degrees >= 0 and minutes >= 0 and seconds >= 0 and us >= 0
|
||||
all_negative_or_zero = degrees <= 0 and minutes <= 0 and seconds <= 0 and us <= 0
|
||||
assert all_positive_or_zero or all_negative_or_zero
|
||||
return degrees + minutes / 60.0 + seconds / 3600.0 + us / 3600000000.0
|
||||
|
||||
|
||||
def dd2dms(dd: float, use_us: bool = False) -> Union[tuple[int, int, int, int], tuple[int, int, float]]:
|
||||
"""Convert decimal degrees to degrees, minutes, and (micro)seconds format
|
||||
|
||||
:param dd: The decimal degrees
|
||||
:param use_us: True if to include microseconds and false otherwise. Defaults to false.
|
||||
:return: The angle in a tuple of either 3 or 4 values,
|
||||
4 values: integer number of degrees, integer number of minutes, integer number of seconds and integer number of microseconds
|
||||
3 values: integer number of degrees, integer number of minutes, and a float number for seconds
|
||||
:note: the tuple follows the format of IfcCompoundPlaneAngleMeasure. Namely all of its components are either positive or negative.
|
||||
"""
|
||||
dd_decimal = Decimal(str(dd))
|
||||
|
||||
degrees = int(dd_decimal)
|
||||
degrees_decimal = Decimal(degrees)
|
||||
|
||||
fractional_part = dd_decimal - degrees_decimal
|
||||
|
||||
minutes_decimal = fractional_part * Decimal(60)
|
||||
minutes = int(minutes_decimal)
|
||||
minutes_decimal_int = Decimal(minutes)
|
||||
|
||||
seconds_decimal = (minutes_decimal - minutes_decimal_int) * Decimal(60)
|
||||
|
||||
if use_us:
|
||||
seconds = int(seconds_decimal)
|
||||
seconds_decimal_int = Decimal(seconds)
|
||||
microseconds_decimal = (seconds_decimal - seconds_decimal_int) * Decimal(1000000)
|
||||
microseconds = int(microseconds_decimal.quantize(Decimal(1), rounding=ROUND_HALF_UP))
|
||||
return (degrees, minutes, seconds, microseconds)
|
||||
else:
|
||||
seconds_float = float(seconds_decimal)
|
||||
return (degrees, minutes, seconds_float)
|
||||
|
||||
|
||||
def xyz2enh(
|
||||
x: float,
|
||||
y: float,
|
||||
z: float,
|
||||
eastings: float = 0.0,
|
||||
northings: float = 0.0,
|
||||
orthogonal_height: float = 0.0,
|
||||
x_axis_abscissa: float = 1.0,
|
||||
x_axis_ordinate: float = 0.0,
|
||||
scale: float = 1.0,
|
||||
factor_x: float = 1.0,
|
||||
factor_y: float = 1.0,
|
||||
factor_z: float = 1.0,
|
||||
) -> tuple[float, float, float]:
|
||||
"""Manually convert local XYZ coordinates to map eastings, northings, and height
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
helmert transformation parameters (i.e. those typically stored in
|
||||
IfcMapConversion). This manual approach is useful for tests or in case your
|
||||
are setting your helmert transformations in non-standard locations, or if
|
||||
you are applying your own temporary false origin (such as when federating
|
||||
models for digital twins of large cities).
|
||||
|
||||
For most scenarios you should use :func:`auto_xyz2enh` instead.
|
||||
|
||||
:param x: The X local engineering coordinate.
|
||||
:param y: The Y local engineering coordinate.
|
||||
:param z: The Z local engineering coordinate.
|
||||
:param eastings: The eastings offset to apply.
|
||||
:param northings: The northings offset to apply.
|
||||
:param orthogonal_height: The orthogonal height offset to apply.
|
||||
:param x_axis_abscissa: The X axis abscissa (i.e. first coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:param x_axis_ordinate: The X axis ordinate (i.e. second coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:param scale: The unit scale such that local ordinate * scale = map
|
||||
ordinate. E.g. if your project is in millimeters but your CRS is in
|
||||
meters, your scale should be 0.001.
|
||||
:param factor_x: The combined scale factor for the X value to convert from
|
||||
local coordinates to map coordinates. Your surveyor will typically know
|
||||
this number and approximate it as a constant on a small site. Typically
|
||||
factor_x and factor_y will be identical, and factor_z will be 1.
|
||||
:param factor_y: Same but for the Y value.
|
||||
:param factor_z: Same but for the Z value.
|
||||
:return: A tuple of three ordinates representing the easting, northing and height.
|
||||
"""
|
||||
theta = math.atan2(x_axis_ordinate, x_axis_abscissa)
|
||||
eastings = (scale * factor_x * math.cos(theta) * x) - (scale * factor_y * math.sin(theta) * y) + eastings
|
||||
northings = (scale * factor_x * math.sin(theta) * x) + (scale * factor_y * math.cos(theta) * y) + northings
|
||||
height = (scale * factor_z * z) + orthogonal_height
|
||||
return (eastings, northings, height)
|
||||
|
||||
|
||||
def auto_xyz2enh(
|
||||
ifc_file: ifcopenshell.file, x: float, y: float, z: float, should_return_in_map_units: bool = True
|
||||
) -> tuple[float, float, float]:
|
||||
"""Convert from local XYZ coordinates to global map coordinate eastings, northings, and heights
|
||||
|
||||
The necessary georeferencing map conversion is automatically detected from
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the coordinates are returned unchanged.
|
||||
|
||||
For IFC2X3, the map conversion is detected from the IfcProject's
|
||||
ePSet_MapConversion. See the "User Guide for Geo-referencing in IFC":
|
||||
https://www.buildingsmart.org/standards/bsi-standards/standards-library/
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:param x: The X local engineering coordinate provided in project length units.
|
||||
:param y: The Y local engineering coordinate provided in project length units.
|
||||
:param z: The Z local engineering coordinate provided in project length units.
|
||||
:param should_return_in_map_units: If true, the result is given in map units.
|
||||
If false, the result will be converted back into project units.
|
||||
:return: The global map coordinate eastings, northings, and height.
|
||||
"""
|
||||
parameters = get_helmert_transformation_parameters(ifc_file)
|
||||
if not parameters:
|
||||
return x, y, z
|
||||
wcs = get_wcs(ifc_file)
|
||||
if wcs is not None:
|
||||
x, y, z = (np.linalg.inv(wcs) @ np.array((x, y, z, 1)))[:3]
|
||||
enh = xyz2enh(x, y, z, *parameters)
|
||||
if should_return_in_map_units:
|
||||
return enh
|
||||
return enh[0] / parameters.scale, enh[1] / parameters.scale, enh[2] / parameters.scale
|
||||
|
||||
|
||||
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
|
||||
|
||||
The necessary georeferencing map conversion is automatically detected from
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the Z coordinate is returned unchanged.
|
||||
|
||||
For IFC2X3, the map conversion is detected from the IfcProject's
|
||||
ePSet_MapConversion. See the "User Guide for Geo-referencing in IFC":
|
||||
https://www.buildingsmart.org/standards/bsi-standards/standards-library/
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:param easting: The global easting map coordinate provided in map units.
|
||||
:param northing: The global northing map coordinate provided in map units.
|
||||
:param height: The global height map coordinate provided in map units.
|
||||
:param is_specified_in_map_units: True if the input eastings, northing, and height are in map units.
|
||||
:return: The local engineering XYZ coordinates in project length units.
|
||||
"""
|
||||
parameters = get_helmert_transformation_parameters(ifc_file)
|
||||
if not parameters:
|
||||
return easting, northing, height
|
||||
if not is_specified_in_map_units:
|
||||
easting *= parameters.scale
|
||||
northing *= parameters.scale
|
||||
height *= parameters.scale
|
||||
xyz = enh2xyz(easting, northing, height, *parameters)
|
||||
wcs = get_wcs(ifc_file)
|
||||
if wcs is not None:
|
||||
xyz = tuple((wcs @ np.array((*xyz, 1)))[:3])
|
||||
return xyz
|
||||
|
||||
|
||||
def get_helmert_transformation_parameters(ifc_file: ifcopenshell.file) -> Optional[HelmertTransformation]:
|
||||
"""Retrieves the parameters of a helmert transformation that represents a
|
||||
coordinate operation
|
||||
|
||||
This coordinate operation is typically what is used to convert between
|
||||
local engineering coordinates and map coordinates.
|
||||
|
||||
:param ifc_file: The IFC model, typically containing an
|
||||
IfcCoordinateOperation such as an IfcMapConversion.
|
||||
:return: The parameters of the transformation.
|
||||
"""
|
||||
if ifc_file.schema == "IFC2X3":
|
||||
project = ifc_file.by_type("IfcProject")[0]
|
||||
conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not conversion:
|
||||
return
|
||||
e = conversion.get("Eastings", None) or 0
|
||||
n = conversion.get("Northings", None) or 0
|
||||
h = conversion.get("OrthogonalHeight", None) or 0
|
||||
xaa = conversion.get("XAxisAbscissa", None) or 0
|
||||
xao = conversion.get("XAxisOrdinate", None) or 0
|
||||
scale = conversion.get("Scale", None) or 1
|
||||
factor_x = factor_y = factor_z = 1
|
||||
else:
|
||||
conversion = ifc_file.by_type("IfcCoordinateOperation")
|
||||
if not conversion:
|
||||
return
|
||||
conversion = conversion[0]
|
||||
|
||||
if conversion.is_a("IfcMapConversion"):
|
||||
e = conversion.Eastings or 0
|
||||
n = conversion.Northings or 0
|
||||
h = conversion.OrthogonalHeight or 0
|
||||
xaa = conversion.XAxisAbscissa or 0
|
||||
xao = conversion.XAxisOrdinate or 0
|
||||
scale = conversion.Scale or 1
|
||||
if conversion.is_a() == "IfcMapConversionScaled":
|
||||
factor_x = conversion.FactorX
|
||||
factor_y = conversion.FactorY
|
||||
factor_z = conversion.FactorZ
|
||||
else:
|
||||
factor_x = factor_y = factor_z = 1
|
||||
elif conversion.is_a() == "IfcRigidOperation":
|
||||
e = conversion.FirstCoordinate.wrappedValue
|
||||
n = conversion.SecondCoordinate.wrappedValue
|
||||
h = conversion.Height or 0
|
||||
xaa = 1.0
|
||||
xao = 0.0
|
||||
scale = factor_x = factor_y = factor_z = 1
|
||||
|
||||
if not xaa and not xao:
|
||||
xaa = 1.0
|
||||
xao = 0.0
|
||||
|
||||
return HelmertTransformation(e, n, h, xaa, xao, scale, factor_x, factor_y, factor_z)
|
||||
|
||||
|
||||
def get_crs(ifc_file: ifcopenshell.file) -> dict[str, Any]:
|
||||
"""Get CRS information from an IFC file."""
|
||||
if ifc_file.schema == "IFC2X3":
|
||||
return ifcopenshell.util.element.get_pset(ifc_file.by_type("IfcProject")[0], "ePSet_ProjectedCRS")
|
||||
for context in ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if operation := context.HasCoordinateOperation:
|
||||
return operation[0].TargetCRS.get_info()
|
||||
|
||||
|
||||
def auto_z2e(ifc_file: ifcopenshell.file, z: float, should_return_in_map_units: bool = True) -> float:
|
||||
"""Convert a Z coordinate to an elevation using model georeferencing data
|
||||
|
||||
The necessary georeferencing map conversion is automatically detected from
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the Z coordinate is returned unchanged.
|
||||
|
||||
For IFC2X3, the map conversion is detected from the IfcProject's
|
||||
ePSet_MapConversion. See the "User Guide for Geo-referencing in IFC":
|
||||
https://www.buildingsmart.org/standards/bsi-standards/standards-library/
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:param z: The Z local engineering coordinate provided in project length units.
|
||||
:return: The elevation in project length units.
|
||||
"""
|
||||
parameters = get_helmert_transformation_parameters(ifc_file)
|
||||
if not parameters:
|
||||
return z
|
||||
e = z2e(z, parameters.h, parameters.scale, parameters.factor_z)
|
||||
if should_return_in_map_units:
|
||||
return e
|
||||
return e / parameters.scale
|
||||
|
||||
|
||||
def z2e(z: float, orthogonal_height: float = 0.0, scale: float = 1.0, factor_z: float = 1.0) -> float:
|
||||
"""Manually convert a Z coordinate to a map elevation
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
orthogonal height offset and transformation parameters.
|
||||
|
||||
For most scenarios you should use :func:`auto_z2e` instead.
|
||||
|
||||
:param z: The Z local engineering coordinate provided in project length units.
|
||||
:param orthogonal_height: The orthogonal height offset to apply.
|
||||
:param scale: The unit scale such that local ordinate * scale = map
|
||||
ordinate. E.g. if your project is in millimeters but your CRS is in
|
||||
meters, your scale should be 0.001.
|
||||
:param factor_x: The combined scale factor for the Z value to convert from
|
||||
local coordinates to map coordinates. Your surveyor will typically know
|
||||
this number and approximate it as a constant on a small site. This is
|
||||
typically just 1.0, as average combined scale factors usually only
|
||||
affect the XY axes.
|
||||
:return: The elevation in map units.
|
||||
"""
|
||||
return (scale * factor_z * z) + orthogonal_height
|
||||
|
||||
|
||||
def enh2xyz(
|
||||
e: float,
|
||||
n: float,
|
||||
h: float,
|
||||
eastings: float = 0.0,
|
||||
northings: float = 0.0,
|
||||
orthogonal_height: float = 0,
|
||||
x_axis_abscissa: float = 1.0,
|
||||
x_axis_ordinate: float = 0.0,
|
||||
scale: float = 1.0,
|
||||
factor_x: float = 1.0,
|
||||
factor_y: float = 1.0,
|
||||
factor_z: float = 1.0,
|
||||
) -> tuple[float, float, float]:
|
||||
"""Manually convert map eastings, northings, and height to local XYZ coordinates
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
helmert transformation parameters (i.e. those typically stored in
|
||||
IfcMapConversion). This manual approach is useful for tests or in case your
|
||||
are setting your helmert transformations in non-standard locations, or if
|
||||
you are applying your own temporary false origin (such as when federating
|
||||
models for digital twins of large cities).
|
||||
|
||||
For most scenarios you should use :func:`auto_enh2xyz` instead.
|
||||
|
||||
:param e: The global easting map coordinate.
|
||||
:param n: The global northing map coordinate.
|
||||
:param h: The global height map coordinate.
|
||||
:param eastings: The eastings offset to apply.
|
||||
:param northings: The northings offset to apply.
|
||||
:param orthogonal_height: The orthogonal height offset to apply.
|
||||
:param x_axis_abscissa: The X axis abscissa (i.e. first coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:param x_axis_ordinate: The X axis ordinate (i.e. second coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:param scale: The unit scale such that local ordinate * scale = map
|
||||
ordinate. E.g. if your project is in millimeters but your CRS is in
|
||||
meters, your scale should be 0.001.
|
||||
:param factor_x: The combined scale factor for the X value to convert from
|
||||
local coordinates to map coordinates. Your surveyor will typically know
|
||||
this number and approximate it as a constant on a small site. Typically
|
||||
factor_x and factor_y will be identical, and factor_z will be 1.
|
||||
:param factor_y: Same but for the Y value.
|
||||
:param factor_z: Same but for the Z value.
|
||||
:return: A tuple of three ordinates representing XYZ.
|
||||
"""
|
||||
theta = math.atan2(x_axis_ordinate, x_axis_abscissa)
|
||||
sint = math.sin(theta)
|
||||
cost = math.cos(theta)
|
||||
x = (((e - eastings) * cost) + ((n - northings) * sint)) / (scale * factor_x)
|
||||
y = (((eastings - e) * sint) + ((n - northings) * cost)) / (scale * factor_y)
|
||||
z = ((h - orthogonal_height) / scale) / factor_z
|
||||
return (x, y, z)
|
||||
|
||||
|
||||
def local2global(
|
||||
matrix: MatrixType,
|
||||
eastings: float = 0.0,
|
||||
northings: float = 0.0,
|
||||
orthogonal_height: float = 0.0,
|
||||
x_axis_abscissa: float = 1.0,
|
||||
x_axis_ordinate: float = 0.0,
|
||||
scale: float = 1.0,
|
||||
factor_x: float = 1.0,
|
||||
factor_y: float = 1.0,
|
||||
factor_z: float = 1.0,
|
||||
) -> MatrixType:
|
||||
"""Manually convert a 4x4 matrix from local to global coordinates
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
helmert transformation parameters (i.e. those typically stored in
|
||||
IfcMapConversion). This manual approach is useful for tests or in case your
|
||||
are setting your helmert transformations in non-standard locations, or if
|
||||
you are applying your own temporary false origin (such as when federating
|
||||
models for digital twins of large cities).
|
||||
|
||||
For most scenarios you should use :func:`auto_local2global` instead.
|
||||
|
||||
:param matrix: A 4x4 numpy matrix representing local coordinates.
|
||||
:param eastings: The eastings offset to apply.
|
||||
:param northings: The northings offset to apply.
|
||||
:param orthogonal_height: The orthogonal height offset to apply.
|
||||
:param x_axis_abscissa: The X axis abscissa (i.e. first coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:param x_axis_ordinate: The X axis ordinate (i.e. second coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:param scale: The combined scale factor to convert from local coordinates
|
||||
to map coordinates.
|
||||
:return: A numpy 4x4 array matrix representing global coordinates.
|
||||
"""
|
||||
theta = math.atan2(x_axis_ordinate, x_axis_abscissa)
|
||||
scale_and_factor_matrix = np.array(
|
||||
[
|
||||
[scale * factor_x, 0, 0, 0],
|
||||
[0, scale * factor_y, 0, 0],
|
||||
[0, 0, scale * factor_z, 0],
|
||||
[0, 0, 0, 1],
|
||||
]
|
||||
)
|
||||
rotation_matrix = np.array(
|
||||
[
|
||||
[math.cos(theta), -math.sin(theta), 0, 0],
|
||||
[math.sin(theta), math.cos(theta), 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1],
|
||||
]
|
||||
)
|
||||
result = rotation_matrix @ scale_and_factor_matrix @ matrix
|
||||
result[:3, 0] /= np.linalg.norm(result[:3, 0])
|
||||
result[:3, 1] /= np.linalg.norm(result[:3, 1])
|
||||
result[:3, 2] /= np.linalg.norm(result[:3, 2])
|
||||
result[0, 3] += eastings
|
||||
result[1, 3] += northings
|
||||
result[2, 3] += orthogonal_height
|
||||
return result
|
||||
|
||||
|
||||
def auto_local2global(
|
||||
ifc_file: ifcopenshell.file, matrix: MatrixType, should_return_in_map_units: bool = True
|
||||
) -> MatrixType:
|
||||
"""Convert a local matrix to a global map matrix
|
||||
|
||||
The necessary georeferencing map conversion is automatically detected from
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the matrix is returned unchanged.
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:param matrix: A 4x4 numpy matrix representing local coordinates.
|
||||
:param should_return_in_map_units: If true, the result is given in map units.
|
||||
If false, the result will be converted back into project units.
|
||||
:return: A numpy 4x4 array matrix representing global coordinates.
|
||||
"""
|
||||
parameters = get_helmert_transformation_parameters(ifc_file)
|
||||
if not parameters:
|
||||
return matrix.copy()
|
||||
wcs = get_wcs(ifc_file)
|
||||
if wcs is not None:
|
||||
matrix = np.linalg.inv(wcs) @ matrix
|
||||
result = local2global(matrix, *parameters)
|
||||
if should_return_in_map_units:
|
||||
return result
|
||||
result[:3, 3] /= parameters.scale
|
||||
return result
|
||||
|
||||
|
||||
def global2local(
|
||||
matrix: MatrixType,
|
||||
eastings: float = 0.0,
|
||||
northings: float = 0.0,
|
||||
orthogonal_height: float = 0.0,
|
||||
x_axis_abscissa: float = 1.0,
|
||||
x_axis_ordinate: float = 0.0,
|
||||
scale: float = 1.0,
|
||||
factor_x: float = 1.0,
|
||||
factor_y: float = 1.0,
|
||||
factor_z: float = 1.0,
|
||||
) -> MatrixType:
|
||||
"""Manually convert a 4x4 matrix from global to local coordinates
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
helmert transformation parameters (i.e. those typically stored in
|
||||
IfcMapConversion). This manual approach is useful for tests or in case your
|
||||
are setting your helmert transformations in non-standard locations, or if
|
||||
you are applying your own temporary false origin (such as when federating
|
||||
models for digital twins of large cities).
|
||||
|
||||
:param matrix: A 4x4 numpy matrix representing global coordinates.
|
||||
:param eastings: The eastings offset to apply.
|
||||
:param northings: The northings offset to apply.
|
||||
:param orthogonal_height: The orthogonal height offset to apply.
|
||||
:param x_axis_abscissa: The X axis abscissa (i.e. first coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:param x_axis_ordinate: The X axis ordinate (i.e. second coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:param scale: The combined scale factor to convert from local coordinates
|
||||
to map coordinates.
|
||||
:return: A numpy 4x4 array matrix representing local coordinates.
|
||||
"""
|
||||
theta = math.atan2(x_axis_ordinate, x_axis_abscissa)
|
||||
scale_and_factor_matrix = np.array(
|
||||
[
|
||||
[scale * factor_x, 0, 0, 0],
|
||||
[0, scale * factor_y, 0, 0],
|
||||
[0, 0, scale * factor_z, 0],
|
||||
[0, 0, 0, 1],
|
||||
]
|
||||
)
|
||||
rotation_matrix = np.array(
|
||||
[
|
||||
[math.cos(theta), -math.sin(theta), 0, 0],
|
||||
[math.sin(theta), math.cos(theta), 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1],
|
||||
]
|
||||
)
|
||||
result = matrix.copy()
|
||||
result[0, 3] -= eastings
|
||||
result[1, 3] -= northings
|
||||
result[2, 3] -= orthogonal_height
|
||||
result = np.linalg.inv(scale_and_factor_matrix) @ np.linalg.inv(rotation_matrix) @ result
|
||||
result[:3, 0] /= np.linalg.norm(result[:3, 0])
|
||||
result[:3, 1] /= np.linalg.norm(result[:3, 1])
|
||||
result[:3, 2] /= np.linalg.norm(result[:3, 2])
|
||||
return result
|
||||
|
||||
|
||||
def auto_global2local(
|
||||
ifc_file: ifcopenshell.file, matrix: MatrixType, is_specified_in_map_units: bool = True
|
||||
) -> MatrixType:
|
||||
"""Convert a global map matrix to a local matrix
|
||||
|
||||
The necessary georeferencing map conversion is automatically detected from
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the matrix is returned unchanged.
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:param matrix: A 4x4 numpy matrix representing local coordinates.
|
||||
:param should_return_in_map_units: If true, the result is given in map units.
|
||||
If false, the result will be converted back into project units.
|
||||
:param is_specified_in_map_units: True if the input matrix is in map units.
|
||||
:return: A numpy 4x4 array matrix representing global coordinates.
|
||||
"""
|
||||
parameters = get_helmert_transformation_parameters(ifc_file)
|
||||
if not parameters:
|
||||
return matrix.copy()
|
||||
if not is_specified_in_map_units:
|
||||
matrix = matrix.copy()
|
||||
matrix[:3, 3] *= parameters.scale
|
||||
result = global2local(matrix, *parameters)
|
||||
wcs = get_wcs(ifc_file)
|
||||
if wcs is not None:
|
||||
return wcs @ result
|
||||
return result
|
||||
|
||||
|
||||
def xaxis2angle(x: float, y: float) -> float:
|
||||
"""Converts X axis abscissa and ordinates to an angle in decimal degrees
|
||||
|
||||
The X axis abscissa and ordinate is how IFC stores grid north.
|
||||
|
||||
This X axis vector indicates "where is project east, if grid north is up
|
||||
the page?". See the diagram on the IfcGeometricRepresentationContext
|
||||
documentation for clarification.
|
||||
|
||||
The angle indicates "how do I rotate project east to get to grid east?".
|
||||
Alternatively: "how do I rotate project north to get to grid north?".
|
||||
Positive angles are anticlockwise.
|
||||
|
||||
:param x: The X axis abscissa
|
||||
:param y: The X axis ordinate
|
||||
:return: The equivalent angle in decimal degrees from the X axis
|
||||
"""
|
||||
return math.degrees(math.atan2(y, x)) * -1
|
||||
|
||||
|
||||
def yaxis2angle(x: float, y: float) -> float:
|
||||
"""Converts Y axis abscissa and ordinates to an angle in decimal degrees
|
||||
|
||||
The Y axis abscissa and ordinate is how IFC stores true north.
|
||||
|
||||
This Y axis vector indicates "where is true north, if project north is up
|
||||
the page?". See the diagram on the IfcGeometricRepresentationContext
|
||||
documentation for clarification.
|
||||
|
||||
The angle indicates "how do I rotate project north to get to true north?".
|
||||
Positive angles are anticlockwise.
|
||||
|
||||
:param x: The Y axis abscissa
|
||||
:param y: The Y axis ordinate
|
||||
:return: The equivalent angle in decimal degrees from the Y axis
|
||||
"""
|
||||
angle = math.degrees(math.atan2(y, x)) - 90
|
||||
if angle < -180:
|
||||
angle += 360
|
||||
elif angle > 180:
|
||||
angle -= 360
|
||||
return angle
|
||||
|
||||
|
||||
def get_grid_north(ifc_file: ifcopenshell.file) -> float:
|
||||
"""Get an angle pointing to map grid north
|
||||
|
||||
Anticlockwise is positive.
|
||||
|
||||
The necessary georeferencing map conversion is automatically detected from
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the Z coordinate is returned unchanged.
|
||||
|
||||
For IFC2X3, the map conversion is detected from the IfcProject's
|
||||
ePSet_MapConversion. See the "User Guide for Geo-referencing in IFC":
|
||||
https://www.buildingsmart.org/standards/bsi-standards/standards-library/
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:return: An angle to grid north in decimal degrees
|
||||
"""
|
||||
parameters = get_helmert_transformation_parameters(ifc_file)
|
||||
if not parameters:
|
||||
return 0
|
||||
return xaxis2angle(parameters.xaa, parameters.xao)
|
||||
|
||||
|
||||
def get_true_north(ifc_file: ifcopenshell.file) -> float:
|
||||
"""Get an angle pointing to global true north
|
||||
|
||||
Anticlockwise is positive.
|
||||
|
||||
Always remember that true north is not a constant! (Unless you are working
|
||||
in polar coordinates) This true north is only a reference value useful for
|
||||
things like solar analysis on small sites (<1km). If you're after the north
|
||||
that your surveyor is using, you're probably after :func:`get_grid_north`
|
||||
instead.
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:return: An angle to true north in decimal degrees
|
||||
"""
|
||||
try:
|
||||
for context in ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.TrueNorth:
|
||||
return yaxis2angle(*context.TrueNorth.DirectionRatios[0:2])
|
||||
except:
|
||||
return 0
|
||||
return 0
|
||||
|
||||
|
||||
def angle2xaxis(angle: float) -> tuple[float, float]:
|
||||
"""Converts an angle into an X axis abscissa and ordinate
|
||||
|
||||
The inverse of :func:`xaxis2angle`.
|
||||
|
||||
:param angle: The angle in decimal degrees where anticlockwise is positive.
|
||||
:return: A tuple of X axis abscissa and ordinate
|
||||
"""
|
||||
angle_rad = math.radians(angle)
|
||||
x = math.cos(angle_rad)
|
||||
y = -math.sin(angle_rad)
|
||||
return x, y
|
||||
|
||||
|
||||
def angle2yaxis(angle: float) -> tuple[float, float]:
|
||||
"""Converts an angle into an Y axis abscissa and ordinate
|
||||
|
||||
The inverse of :func:`yaxis2angle`.
|
||||
|
||||
:param angle: The angle in decimal degrees where anticlockwise is positive.
|
||||
:return: A tuple of Y axis abscissa and ordinate
|
||||
"""
|
||||
angle_rad = math.radians(angle)
|
||||
x = -math.sin(angle_rad)
|
||||
y = math.cos(angle_rad)
|
||||
return x, y
|
||||
|
||||
|
||||
def get_wcs(ifc_file: ifcopenshell.file) -> Optional[MatrixType]:
|
||||
"""Gets the WCS (prioritising 3D contexts) as a matrix
|
||||
|
||||
:param: The IFC file
|
||||
:return: A 4x4 matrix in project units
|
||||
"""
|
||||
wcs = None
|
||||
for context in ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
wcs = context.WorldCoordinateSystem
|
||||
if context.ContextType == "Model":
|
||||
break
|
||||
if wcs:
|
||||
return ifcopenshell.util.placement.get_axis2placement(wcs)
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"IfcAudioVisualAppliance.CAMERA": "Camera",
|
||||
"IfcUnitaryEquipment.AIRHANDLER": "AHU",
|
||||
"IfcBoiler": "Boiler",
|
||||
"IfcUnitaryEquipment.AIRCONDITIONINGUNIT": "CRAC",
|
||||
"IfcChiller": "Chiller",
|
||||
"IfcCompressor": "Compressor",
|
||||
"IfcCondenser": "Condenser",
|
||||
"IfcCoolingTower": "Cooling_Tower",
|
||||
"IfcDamper": "Damper",
|
||||
"IfcFan": "Fan",
|
||||
"IfcFilter": "Filter",
|
||||
"IfcHeatExchanger": "Heat_Exchanger",
|
||||
"IfcHumidifier": "Humidifier",
|
||||
"IfcPump": "Pump",
|
||||
"IfcAirTerminalBox": "TerminalUnit",
|
||||
"IfcAirTerminalBox.CONSTANTFLOW": "CAV",
|
||||
"IfcAirTerminalBox.VARIABLEFLOWPRESSUREDEPENDANT": "VAV",
|
||||
"IfcAirTerminalBox.VARIABLEFLOWPRESSUREINDEPENDANT": "VAV",
|
||||
"IfcValve": "Valve",
|
||||
"IfcUnitaryControlElement.THERMOSTAT": "Thermostat",
|
||||
"IfcUnitaryControlElement.WEATHERSTATION": "Weather_Station",
|
||||
"IfcLightFixture": "Luminaire",
|
||||
"IfcFlowMeter": "Meter",
|
||||
"IfcFlowMeter.ENERGYMETER": "Electric_Meter",
|
||||
"IfcFlowMeter.GASMETER": "Gas_Meter",
|
||||
"IfcFlowMeter.WATERMETER": "Water_Meter",
|
||||
"IfcElectricMotor": "Motor",
|
||||
"IfcSolarDevice.SOLARPANEL": "PV_Panel",
|
||||
"IfcBuilding": "https://w3id.org/rec#Building",
|
||||
"IfcBuildingStorey": "https://w3id.org/rec#Level",
|
||||
"IfcSpace": "https://w3id.org/rec#Room",
|
||||
"IfcSpatialZone": "https://w3id.org/rec#Zone",
|
||||
"IfcSpatialZone.THERMAL": "https://w3id.org/rec#HVACZone"
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 @Andrej730
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
try:
|
||||
from server import (
|
||||
R,
|
||||
get_resource_path,
|
||||
process_markdown,
|
||||
resource_documentation_builder,
|
||||
)
|
||||
except ModuleNotFoundError as e:
|
||||
print(
|
||||
"ERROR. Failed to import `server.py`.\n"
|
||||
"Make sure you run this script from `/code` folder of https://github.com/buildingSMART/IFC4.3.x-development\n"
|
||||
)
|
||||
raise e
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import operator
|
||||
from collections import Counter
|
||||
from typing import Any, Union
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
# Hacky modified functions from server.py to make parser work
|
||||
def get_definition_from_md(resource: str, mdc: str) -> str:
|
||||
# Only match up to the first h2
|
||||
lines = []
|
||||
for line in mdc.split("\n"):
|
||||
if line.startswith("## "):
|
||||
break
|
||||
if line.startswith("### "):
|
||||
words = line.split(" ")
|
||||
line = " ".join((words[0], "", *words[1:]))
|
||||
lines.append(line)
|
||||
mdc = "\n".join(lines)
|
||||
mdc_splitted = mdc.split("\n\n")
|
||||
return mdc_splitted[1] if len(mdc_splitted) > 1 else ""
|
||||
|
||||
|
||||
def get_type_values(resource: str, mdc: str) -> dict[str, Any]:
|
||||
values = R.type_values.get(resource)
|
||||
if not values:
|
||||
return
|
||||
has_description = values[0] == values[0].upper()
|
||||
if has_description:
|
||||
soup = BeautifulSoup(process_markdown(resource, mdc), features="lxml")
|
||||
described_values = []
|
||||
for value in values:
|
||||
description = None
|
||||
for h in soup.findAll("h3"):
|
||||
if h.text != value:
|
||||
continue
|
||||
description = BeautifulSoup(features="lxml")
|
||||
for sibling in h.find_next_siblings():
|
||||
if sibling.name == "h3":
|
||||
break
|
||||
description.append(sibling)
|
||||
description = str(description)
|
||||
described_values.append({"name": value, "description": description})
|
||||
values = described_values
|
||||
return {"number": 0, "has_description": has_description, "schema_values": values}
|
||||
|
||||
|
||||
def get_attributes_keep_md(resource, builder):
|
||||
attrs = builder.attributes
|
||||
supertype_counts = Counter()
|
||||
supertype_counts.update([a[0] for a in attrs])
|
||||
attrs = [a[1:] for a in attrs]
|
||||
supertype_counts = list(supertype_counts.items())[::-1]
|
||||
insertion_points = [0] + list(itertools.accumulate(map(operator.itemgetter(1), supertype_counts[::-1])))[:-1]
|
||||
group_data = supertype_counts[::-1]
|
||||
|
||||
groups = []
|
||||
for i, attr in enumerate(attrs):
|
||||
if i in insertion_points:
|
||||
name, total_attributes = group_data[insertion_points.index(i)]
|
||||
group = {
|
||||
"name": name,
|
||||
"attributes": [],
|
||||
"is_inherited": insertion_points[-1] != i,
|
||||
"total_attributes": total_attributes,
|
||||
}
|
||||
groups.append(group)
|
||||
|
||||
attribute = {
|
||||
"number": attr[0],
|
||||
"name": attr[1],
|
||||
"type": attr[2][0] if isinstance(attr[2], list) else attr[2],
|
||||
"formal": attr[2][1] if isinstance(attr[2], list) else None,
|
||||
# @nb we're not really talking about markdown anymore
|
||||
# since the new attribute parser operates on a converted
|
||||
# dom, but it appears to work nonetheless.
|
||||
"description": attr[3],
|
||||
"is_inverse": not attr[0],
|
||||
}
|
||||
if attribute["name"] == "PredefinedType" and not attribute["description"]:
|
||||
description = "A list of types to further identify the object. Some property sets may be specifically applicable to one of these types."
|
||||
if "Type" not in group["name"]:
|
||||
description += "\n> NOTE If the object has an associated IfcTypeObject with a _PredefinedType_, then this attribute shall not be used."
|
||||
attribute["description"] = description
|
||||
group["attributes"].append(attribute)
|
||||
|
||||
total_inherited_attributes = sum([g["total_attributes"] for g in groups if g["is_inherited"]])
|
||||
|
||||
inherited_groups_with_attributes = [g for g in groups if g["is_inherited"] and g["total_attributes"]]
|
||||
if inherited_groups_with_attributes:
|
||||
inherited_groups_with_attributes[-1]["is_last_inherited_group"] = True
|
||||
|
||||
return {
|
||||
"number": None,
|
||||
"groups": groups,
|
||||
"total_inherited_attributes": total_inherited_attributes,
|
||||
}
|
||||
|
||||
|
||||
# -------------------------
|
||||
|
||||
# Temporary fix for https://github.com/buildingSMART/IFC4.3.x-development/issues/754.
|
||||
_original_get_resource_path = get_resource_path
|
||||
|
||||
|
||||
def get_resource_path(resource: str, abort_on_error=False) -> Union[str, None]:
|
||||
md = _original_get_resource_path(resource, abort_on_error)
|
||||
if md and resource == "IfcURIReference":
|
||||
md = md.replace("IfcMeasureResource", "IfcExternalReferenceResource")
|
||||
return md
|
||||
|
||||
|
||||
def get_description_json(resource: str) -> str:
|
||||
md = get_resource_path(resource, abort_on_error=False)
|
||||
if not md:
|
||||
raise Exception(f"No resource path found for '{resource}'.")
|
||||
mdc = open(md, "r", encoding="utf-8").read()
|
||||
description = get_definition_from_md(resource, mdc)
|
||||
return description
|
||||
|
||||
|
||||
def get_attributes_json(resource):
|
||||
builder = resource_documentation_builder(resource)
|
||||
attrs = get_attributes_keep_md(resource, builder)
|
||||
if not attrs["groups"]:
|
||||
return []
|
||||
attrs = [a for a in attrs["groups"] if a["name"] == resource]
|
||||
if not attrs:
|
||||
return []
|
||||
|
||||
return attrs[0]["attributes"]
|
||||
|
||||
|
||||
def get_predefined_type_values_json(resource):
|
||||
md = get_resource_path(resource, abort_on_error=False)
|
||||
if not md:
|
||||
raise Exception(f"No resource path found for '{resource}'.")
|
||||
mdc = open(md, "r", encoding="utf-8").read()
|
||||
return get_type_values(resource, mdc)["schema_values"]
|
||||
|
||||
|
||||
def save_entities_data(entities: list[str]) -> None:
|
||||
entities_description = dict()
|
||||
for entity in entities:
|
||||
entity_data = dict()
|
||||
|
||||
try:
|
||||
entity_data["description"] = get_description_json(entity)
|
||||
except Exception as e:
|
||||
md = get_resource_path(entity, abort_on_error=False)
|
||||
if md:
|
||||
print(f"server returned markdown file path ('{md}') but there was some other parsing error, see below.")
|
||||
raise e
|
||||
print(
|
||||
f"WARNING. Cannot find resource path for `{entity}` in DEV DOCUMENTATION even though it's present in ifcopenshell schema. It will be skipped."
|
||||
)
|
||||
continue
|
||||
|
||||
attrs = get_attributes_json(entity)
|
||||
attributes_data = dict()
|
||||
predefined_types_data = dict()
|
||||
for a in attrs:
|
||||
if a["name"] == "PredefinedType":
|
||||
predefined_type_name_enum = a["type"].split()[-1]
|
||||
predef_values = get_predefined_type_values_json(predefined_type_name_enum)
|
||||
for v in predef_values:
|
||||
description = v["description"]
|
||||
description = description.strip() if description else ""
|
||||
if description:
|
||||
description = BeautifulSoup(description, features="lxml").find("p").text
|
||||
predefined_types_data[v["name"]] = description
|
||||
continue
|
||||
|
||||
description = a["description"]
|
||||
description = description.strip() if description else ""
|
||||
if description:
|
||||
description = BeautifulSoup(description, features="lxml").find("p").text
|
||||
attributes_data[a["name"]] = description
|
||||
entity_data["attributes"] = attributes_data
|
||||
entity_data["predefined_types"] = predefined_types_data
|
||||
entities_description[entity] = entity_data
|
||||
|
||||
with open("entities_description.json", "w") as fo:
|
||||
json.dump(entities_description, fo, sort_keys=True, indent=4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC4X3_ADD2")
|
||||
entities: list[str] = [e.name() for e in schema.declarations()]
|
||||
save_entities_data(entities)
|
||||
@@ -0,0 +1,343 @@
|
||||
# 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/>.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from lark import Lark, Transformer
|
||||
from lark.exceptions import UnexpectedCharacters, UnexpectedEOF, UnexpectedToken
|
||||
|
||||
LARK_AVAILABLE = True
|
||||
except ImportError:
|
||||
LARK_AVAILABLE = False
|
||||
|
||||
import re
|
||||
from typing import Union
|
||||
|
||||
if LARK_AVAILABLE:
|
||||
mvd_grammar = r"""
|
||||
start: entry+
|
||||
|
||||
entry: "ViewDefinition" "[" simple_value_list "]" -> view_definition
|
||||
| "Comment" "[" simple_value_list "]" -> comment
|
||||
| "ExchangeRequirement" "[" other_keyword "]" -> exchangerequirement
|
||||
| "Option" "[" other_keyword "]" -> option
|
||||
| GENERIC_KEYWORD "[" dynamic_option_word "]" -> dynamic_option
|
||||
|
||||
GENERIC_KEYWORD: /[A-Za-z0-9_]+/
|
||||
|
||||
simple_value_list: value ("," value)*
|
||||
|
||||
value_list_set: value_set (";" value_set)*
|
||||
|
||||
value_set: set_name ":" simple_value_list
|
||||
|
||||
set_name: /[A-Za-z0-9_]+/
|
||||
|
||||
value: /[A-Za-z0-9 _\.-]+/
|
||||
|
||||
other_keyword: /[^\[\]]+/
|
||||
|
||||
dynamic_option_word: /[^\[\]]+/
|
||||
|
||||
%import common.WS
|
||||
%ignore WS
|
||||
"""
|
||||
|
||||
parser = Lark(mvd_grammar, parser="lalr")
|
||||
|
||||
class DescriptionTransform(Transformer):
|
||||
def __init__(self):
|
||||
self.view_definitions = []
|
||||
self.keywords = set()
|
||||
self.comments = ""
|
||||
self.exchange_requirements = ""
|
||||
self.options = ""
|
||||
self._dynamic = {}
|
||||
|
||||
def view_definition(self, args):
|
||||
self.keywords.add("view_definitions")
|
||||
self.view_definitions.extend(args[0])
|
||||
|
||||
def store_text_attribute(self, args, keyword):
|
||||
self.keywords.add(keyword)
|
||||
setattr(self, keyword, " ".join(" ".join(str(child) for child in args[0].children).split()))
|
||||
|
||||
def comment(self, args):
|
||||
self.keywords.add("comments")
|
||||
self.comments = args[0] if len(args[0]) > 1 else args[0][0]
|
||||
|
||||
def exchangerequirement(self, args):
|
||||
self.store_text_attribute(args, "exchange_requirements")
|
||||
|
||||
def option(self, args):
|
||||
if v := parse_semicolon_separated_kv(" ".join(" ".join(str(child) for child in args[0].children).split())):
|
||||
setattr(self, "options", v)
|
||||
else:
|
||||
self.store_text_attribute(args, "options")
|
||||
|
||||
def dynamic_option(self, args):
|
||||
try:
|
||||
original_keyword = str(args[0])
|
||||
key = original_keyword.lower()
|
||||
raw_text = args[1].children[0].value
|
||||
parsed_value = parse_semicolon_separated_kv(raw_text)
|
||||
self._dynamic[key] = (parsed_value, original_keyword)
|
||||
self.keywords.add(key)
|
||||
setattr(self, key, parsed_value)
|
||||
except Exception:
|
||||
setattr(self, key, None)
|
||||
|
||||
def simple_value_list(self, args):
|
||||
return [str(arg) for arg in args]
|
||||
|
||||
def value_list_set(self, args):
|
||||
return args
|
||||
|
||||
def value_set(self, args):
|
||||
return [str(args[0])] + args[1]
|
||||
|
||||
def value(self, args):
|
||||
return str(args[0])
|
||||
|
||||
def set_name(self, args):
|
||||
return str(args[0])
|
||||
|
||||
def parse_mvd(description):
|
||||
text = " ".join(description)
|
||||
parsed_description = DescriptionTransform()
|
||||
try:
|
||||
if not text:
|
||||
parsed_description.view_definitions = None
|
||||
return parsed_description
|
||||
parse_tree = parser.parse(text)
|
||||
parsed_description.transform(parse_tree)
|
||||
except (UnexpectedCharacters, UnexpectedEOF, UnexpectedToken):
|
||||
parsed_description.view_definitions = None
|
||||
return parsed_description
|
||||
|
||||
def parse_semicolon_separated_kv(text: str) -> dict[str, str | list[str]] | None:
|
||||
if not re.search(r"\w+\s*:\s*[^:]+", text):
|
||||
return None
|
||||
result = {}
|
||||
try:
|
||||
pairs = text.split(";")
|
||||
for pair in pairs:
|
||||
if ":" in pair:
|
||||
key, value = pair.split(":", 1)
|
||||
key = key.strip()
|
||||
values = [v.strip() for v in value.split(",")]
|
||||
result[key] = values[0] if len(values) == 1 else values
|
||||
return result
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
else:
|
||||
|
||||
def parse_mvd(description):
|
||||
return None
|
||||
|
||||
|
||||
class MvdInfo:
|
||||
def __init__(self, header):
|
||||
self._header = header
|
||||
self._parsed = None
|
||||
|
||||
def _ensure_parsed(self):
|
||||
if not LARK_AVAILABLE:
|
||||
return
|
||||
if self._parsed is None:
|
||||
self._parsed = parse_mvd(self._header.file_description.description)
|
||||
|
||||
@property
|
||||
def description(self) -> list[str]:
|
||||
return self._header.file_description.description
|
||||
|
||||
@description.setter
|
||||
def description(self, new_description: list[str]):
|
||||
self._header.file_description.description = tuple(new_description)
|
||||
self._parsed = None
|
||||
|
||||
@property
|
||||
def view_definitions(self):
|
||||
self._ensure_parsed()
|
||||
if not self._parsed or self._parsed.view_definitions is None:
|
||||
return None #
|
||||
|
||||
vd = self._parsed.view_definitions
|
||||
vd_list = vd if isinstance(vd, list) else [vd] if vd else []
|
||||
return AutoCommitList(
|
||||
vd_list,
|
||||
callback=lambda val: (self._update_keyword("ViewDefinition", val), setattr(self, "_parsed", None)),
|
||||
formatter=lambda lst: ",".join(str(i) for i in lst),
|
||||
)
|
||||
|
||||
@view_definitions.setter
|
||||
def view_definitions(self, new_value: Union[str, list[str]]):
|
||||
if isinstance(new_value, list):
|
||||
value = ", ".join(new_value)
|
||||
else:
|
||||
value = str(new_value)
|
||||
self._update_keyword("ViewDefinition", value)
|
||||
|
||||
@property
|
||||
def comments(self):
|
||||
self._ensure_parsed()
|
||||
comments = self._parsed.comments
|
||||
comment_list = comments if isinstance(comments, list) else [comments] if comments else []
|
||||
return AutoCommitList(
|
||||
comment_list,
|
||||
callback=lambda val: self._update_keyword("Comment", val),
|
||||
formatter=lambda lst: ", ".join(str(i) for i in lst),
|
||||
)
|
||||
|
||||
@comments.setter
|
||||
def comments(self, new_value: Union[str, list[str]]):
|
||||
if isinstance(new_value, list):
|
||||
value = ", ".join(new_value)
|
||||
else:
|
||||
value = str(new_value)
|
||||
self._update_keyword("Comment", value)
|
||||
|
||||
@property
|
||||
def exchange_requirements(self):
|
||||
self._ensure_parsed()
|
||||
return self._parsed.exchange_requirements if self._parsed else None
|
||||
|
||||
@exchange_requirements.setter
|
||||
def exchange_requirements(self, new_value: str):
|
||||
self._update_keyword("ExchangeRequirement", new_value)
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
self._ensure_parsed()
|
||||
if isinstance(self._parsed.options, dict):
|
||||
return DictionaryHandler(self._parsed.options, self, "Option")
|
||||
return self._parsed.options if self._parsed else None
|
||||
|
||||
@options.setter
|
||||
def options(self, new_value: str):
|
||||
self._update_keyword("Option", new_value)
|
||||
|
||||
@property
|
||||
def keywords(self):
|
||||
self._ensure_parsed()
|
||||
return self._parsed.keywords if self._parsed else set()
|
||||
|
||||
def _update_keyword(self, keyword: str, new_value: str):
|
||||
updated = False
|
||||
new_line = f"{keyword} [{new_value}]"
|
||||
lines = []
|
||||
for line in self.description:
|
||||
if line.strip().startswith(f"{keyword} ["):
|
||||
lines.append(new_line)
|
||||
updated = True
|
||||
else:
|
||||
lines.append(line)
|
||||
if not updated:
|
||||
lines.append(new_line)
|
||||
self.description = lines
|
||||
|
||||
def __getattr__(self, name):
|
||||
self._ensure_parsed()
|
||||
if hasattr(self._parsed, "_dynamic"):
|
||||
name_lc = name.lower()
|
||||
if name_lc in self._parsed._dynamic:
|
||||
value, original_keyword = self._parsed._dynamic[name_lc]
|
||||
return DictionaryHandler(value, self, original_keyword)
|
||||
raise AttributeError(f"'MvdInfo' object has no attribute '{name}'")
|
||||
|
||||
def __dir__(self):
|
||||
base = super().__dir__()
|
||||
if self._parsed and hasattr(self._parsed, "_dynamic"):
|
||||
return base + [kw for _, kw in self._parsed._dynamic.values()]
|
||||
return base
|
||||
|
||||
|
||||
class DictionaryHandler(dict):
|
||||
def __init__(self, initial_data, mvdinfo, keyword):
|
||||
super().__init__()
|
||||
self._mvdinfo = mvdinfo
|
||||
self._keyword = keyword
|
||||
for k, v in initial_data.items():
|
||||
if isinstance(v, list):
|
||||
super().__setitem__(k, AutoCommitList(v, self._commit))
|
||||
else:
|
||||
super().__setitem__(k, v)
|
||||
|
||||
def _commit(self):
|
||||
new_value = "; ".join(f"{k}: {', '.join(v) if isinstance(v, list) else v}" for k, v in self.items())
|
||||
self._mvdinfo._update_keyword(self._keyword, new_value)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(value, list):
|
||||
value = AutoCommitList(value, self._commit)
|
||||
super().__setitem__(key, value)
|
||||
self._commit()
|
||||
|
||||
def __delitem__(self, key):
|
||||
super().__delitem__(key)
|
||||
self._commit()
|
||||
|
||||
|
||||
class AutoCommitList(list):
|
||||
"ensures keyword attributes are written back to ifcopenshell.file.header"
|
||||
|
||||
def __init__(self, iterable, callback, formatter=None):
|
||||
super().__init__(iterable)
|
||||
self._callback = callback
|
||||
self._formatter = formatter
|
||||
|
||||
def _commit(self):
|
||||
if self._formatter:
|
||||
self._callback(self._formatter(self))
|
||||
else:
|
||||
self._callback()
|
||||
|
||||
def append(self, item):
|
||||
super().append(item)
|
||||
self._commit()
|
||||
|
||||
def extend(self, iterable):
|
||||
super().extend(iterable)
|
||||
self._commit()
|
||||
|
||||
def insert(self, index, item):
|
||||
super().insert(index, item)
|
||||
self._commit()
|
||||
|
||||
def remove(self, item):
|
||||
super().remove(item)
|
||||
self._commit()
|
||||
|
||||
def pop(self, index=-1):
|
||||
item = super().pop(index)
|
||||
self._commit()
|
||||
return item
|
||||
|
||||
def clear(self):
|
||||
super().clear()
|
||||
self._commit()
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
super().__setitem__(index, value)
|
||||
self._commit()
|
||||
|
||||
def __delitem__(self, index):
|
||||
super().__delitem__(index)
|
||||
self._commit()
|
||||
@@ -0,0 +1,237 @@
|
||||
# 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 collections.abc import Iterable
|
||||
from typing import Literal, Optional
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
MatrixType = npt.NDArray[np.float64]
|
||||
"""`npt.NDArray[np.float64]`"""
|
||||
|
||||
|
||||
def a2p(o: Iterable[float], z: Iterable[float], x: Iterable[float]) -> MatrixType:
|
||||
"""Converts a location, X, and Z axis vector to a 4x4 transformation matrix
|
||||
|
||||
IFC uses a right-handed coordinate system, so it is not necessary to
|
||||
provide the Y axis.
|
||||
|
||||
:param o: The origin (i.e. location) of the matrix
|
||||
:param z: The +Z vector / axis of the matrix
|
||||
:param x: The +X vector / axis of the matrix
|
||||
:return: A 4x4 numpy matrix
|
||||
"""
|
||||
x = x / np.linalg.norm(x)
|
||||
z = z / np.linalg.norm(z)
|
||||
y = np.cross(z, x)
|
||||
y = y / np.linalg.norm(y)
|
||||
r = np.eye(4)
|
||||
r[:-1, :-1] = x, y, z
|
||||
r[-1, :-1] = o
|
||||
return r.T
|
||||
|
||||
|
||||
def get_axis2placement(placement: ifcopenshell.entity_instance) -> MatrixType:
|
||||
"""Parses an IfcAxis2Placement (2D or 3D) to a 4x4 transformation matrix
|
||||
|
||||
Note that this function only parses a single placement axis. If you want to
|
||||
get the placement of an element instead, element placements often are made
|
||||
out of multiple placement axes or other alternative placement methods. You
|
||||
should use ``get_local_placement`` instead.
|
||||
|
||||
:param placement: The IfcLocalPlacement enitity
|
||||
:return: A 4x4 numpy matrix
|
||||
"""
|
||||
ifc_class = placement.is_a()
|
||||
if ifc_class in ("IfcAxis2Placement3D", "IfcAxis2PlacementLinear"):
|
||||
z = np.array(placement.Axis.DirectionRatios if placement.Axis else (0, 0, 1))
|
||||
x = np.array(placement.RefDirection.DirectionRatios if placement.RefDirection else (1, 0, 0))
|
||||
location = placement.Location
|
||||
if coordinates := getattr(location, "Coordinates", None):
|
||||
o = coordinates
|
||||
else:
|
||||
import ifcopenshell.geom
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set("convert-back-units", True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, placement)
|
||||
return np.array(shape.matrix).reshape((4, 4), order="F")
|
||||
elif ifc_class == "IfcAxis2Placement2D":
|
||||
z = np.array((0, 0, 1))
|
||||
if placement.RefDirection:
|
||||
x = np.array(placement.RefDirection.DirectionRatios)
|
||||
x.resize(3)
|
||||
else:
|
||||
x = np.array((1, 0, 0))
|
||||
o = (*placement.Location.Coordinates, 0.0)
|
||||
|
||||
elif ifc_class == "IfcAxis1Placement":
|
||||
axis = placement.Axis
|
||||
z = np.array(axis.DirectionRatios if axis else (0, 0, 1))
|
||||
x = np.array((1, 0, 0))
|
||||
o = placement.Location.Coordinates
|
||||
|
||||
return a2p(o, z, x)
|
||||
|
||||
|
||||
def get_local_placement(placement: Optional[ifcopenshell.entity_instance] = None) -> MatrixType:
|
||||
"""Parse a local placement into a 4x4 transformation matrix
|
||||
|
||||
This is typically used to find the location and rotation of an element. The
|
||||
transformation matrix takes the form of:
|
||||
|
||||
.. code::
|
||||
|
||||
[ [ x_x, y_x, z_x, x ]
|
||||
[ x_y, y_y, z_y, y ]
|
||||
[ x_z, y_z, z_z, z ]
|
||||
[ 0.0, 0.0, 0.0, 1.0 ] ]
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
placement = file.by_type("IfcBeam")[0].ObjectPlacement
|
||||
matrix = ifcopenshell.util.placement.get_local_placement(placement)
|
||||
|
||||
:param placement: The IfcLocalPlacement entity
|
||||
:return: A 4x4 numpy matrix
|
||||
"""
|
||||
if placement is None:
|
||||
return np.eye(4)
|
||||
if (rel_to := placement.PlacementRelTo) is None:
|
||||
parent = np.eye(4)
|
||||
else:
|
||||
parent = get_local_placement(rel_to)
|
||||
return np.dot(parent, get_axis2placement(placement.RelativePlacement))
|
||||
|
||||
|
||||
def get_cartesiantransformationoperator3d(inst: ifcopenshell.entity_instance) -> MatrixType:
|
||||
"""Parses an IfcCartesianTransformationOperator into a 4x4 transformation matrix
|
||||
|
||||
Note that in general you will not need to call this directly. See
|
||||
``get_mappeditem_transformation`` instead.
|
||||
|
||||
:param item: The IfcCartesianTransformationOperator entity
|
||||
:return: A 4x4 numpy transformation matrix
|
||||
"""
|
||||
origin = np.array(inst.LocalOrigin.Coordinates)
|
||||
axis1 = np.array((1.0, 0.0, 0.0))
|
||||
axis2 = np.array((0.0, 1.0, 0.0))
|
||||
axis3 = np.array((0.0, 0.0, 1.0))
|
||||
|
||||
if inst.Axis1:
|
||||
axis1[0:3] = inst.Axis1.DirectionRatios
|
||||
if inst.Axis2:
|
||||
axis2[0:3] = inst.Axis2.DirectionRatios
|
||||
if inst.Axis3:
|
||||
axis3[0:3] = inst.Axis3.DirectionRatios
|
||||
|
||||
m4 = a2p(origin, axis3, axis1)
|
||||
# Negate axis2 (introduce mirroring) when supplied axis2
|
||||
# is opposite of constructed axis2, but remains orthogonal
|
||||
if m4.T[1][0:3].dot(axis2) < 0.0:
|
||||
m4.T[1] *= -1.0
|
||||
|
||||
scale1 = scale2 = scale3 = 1.0
|
||||
|
||||
if inst.Scale:
|
||||
scale1 = inst.Scale
|
||||
|
||||
if inst.is_a("IfcCartesianTransformationOperator3DnonUniform"):
|
||||
scale2 = inst.Scale2 if inst.Scale2 is not None else scale1
|
||||
scale3 = inst.Scale3 if inst.Scale3 is not None else scale1
|
||||
else:
|
||||
scale2 = scale3 = scale1
|
||||
|
||||
m4.T[0] *= scale1
|
||||
m4.T[1] *= scale2
|
||||
m4.T[2] *= scale3
|
||||
|
||||
return m4
|
||||
|
||||
|
||||
def get_mappeditem_transformation(item: ifcopenshell.entity_instance) -> MatrixType:
|
||||
"""Parse an IfcMappedItem into a 4x4 transformation matrix
|
||||
|
||||
Mapped items take a representation with an origin and transform them with a
|
||||
cartesian transformation operation. This function returns the final
|
||||
transformation matrix.
|
||||
|
||||
:param item: The IfcMappedItem entity
|
||||
:return: A 4x4 numpy transformation matrix
|
||||
"""
|
||||
m4 = get_axis2placement(item.MappingSource.MappingOrigin)
|
||||
# TODO 2d
|
||||
if item.MappingTarget.is_a("IfcCartesianTransformationOperator3D"):
|
||||
return get_cartesiantransformationoperator3d(item.MappingTarget) @ m4
|
||||
|
||||
|
||||
def get_storey_elevation(storey: ifcopenshell.entity_instance) -> float:
|
||||
"""Get the Z elevation in project units of a buildling storey
|
||||
|
||||
Building storeys store elevation in two possible locations: the Z value of
|
||||
its placement, or as a fallback the ``Elevation`` attribute.
|
||||
|
||||
:param storey: The IfcBuildingStorey entity
|
||||
:return: The elevation in project units
|
||||
"""
|
||||
if storey.ObjectPlacement:
|
||||
matrix = get_local_placement(storey.ObjectPlacement)
|
||||
return matrix[2][3]
|
||||
return getattr(storey, "Elevation", 0.0) or 0.0
|
||||
|
||||
|
||||
def rotation(angle: float, axis: Literal["X", "Y", "Z"], is_degrees=True) -> MatrixType:
|
||||
"""Create a 4x4 numpy matrix representing an euler rotation
|
||||
|
||||
:param angle: The angle of rotation
|
||||
:param axis: The axis to rotate around, either X, Y, or Z.
|
||||
:param is_degrees: Whether or not the angle is specified in degrees or
|
||||
radians. Defaults to true (i.e. degrees).
|
||||
:return: A 4x4 numpy rotation matrix
|
||||
"""
|
||||
theta = np.radians(angle) if is_degrees else angle
|
||||
cos, sin = np.cos(theta), np.sin(theta)
|
||||
|
||||
# fmt: off
|
||||
if axis == "X":
|
||||
return np.array([
|
||||
[1, 0, 0, 0],
|
||||
[0, cos, -sin, 0],
|
||||
[0, sin, cos, 0],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
elif axis == "Y":
|
||||
return np.array([
|
||||
[cos, 0, sin, 0],
|
||||
[0, 1, 0, 0],
|
||||
[-sin, 0, cos, 0],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
elif axis == "Z":
|
||||
return np.array([
|
||||
[cos, -sin, 0, 0],
|
||||
[sin, cos, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
# fmt: on
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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 timeit import default_timer as timer
|
||||
|
||||
|
||||
class Profiler:
|
||||
"""
|
||||
A python context manager timing utility, useful for measure functions performances
|
||||
"""
|
||||
|
||||
def __init__(self, task):
|
||||
self.task = task
|
||||
|
||||
def __enter__(self):
|
||||
self.start = timer()
|
||||
|
||||
def __exit__(self, *args):
|
||||
print(f"{self.task} {timer() - self.start:.6f} s")
|
||||
@@ -0,0 +1,250 @@
|
||||
# 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 pathlib
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from typing import Literal, NamedTuple, Optional, Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.type
|
||||
from ifcopenshell.entity_instance import entity_instance
|
||||
|
||||
templates: dict[ifcopenshell.util.schema.IFC_SCHEMA, "PsetQto"] = {}
|
||||
|
||||
|
||||
def get_template(schema_identiier: str) -> "PsetQto":
|
||||
"""
|
||||
:param schema_identiier: As in ``file.schema_identifier``, not ``file.schema``.
|
||||
"""
|
||||
global templates
|
||||
schema = ifcopenshell.util.schema.get_fallback_schema(schema_identiier)
|
||||
if schema not in templates:
|
||||
templates[schema] = PsetQto(schema)
|
||||
return templates[schema]
|
||||
|
||||
|
||||
class PsetQto:
|
||||
# fmt: off
|
||||
templates_path: dict[ifcopenshell.util.schema.IFC_SCHEMA, str] = {
|
||||
"IFC2X3": "Pset_IFC2X3.ifc",
|
||||
"IFC4": "Pset_IFC4_ADD2.ifc",
|
||||
"IFC4X3": "Pset_IFC4X3.ifc"
|
||||
}
|
||||
# fmt: on
|
||||
templates: list[ifcopenshell.file]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
schema: ifcopenshell.util.schema.IFC_SCHEMA,
|
||||
templates: Optional[list[ifcopenshell.file]] = None,
|
||||
) -> None:
|
||||
self.schema = ifcopenshell.schema_by_name(schema)
|
||||
if not templates:
|
||||
folder_path = pathlib.Path(__file__).parent.absolute()
|
||||
path = str(folder_path.joinpath("schema", self.templates_path[schema]))
|
||||
ifc_file: ifcopenshell.file = ifcopenshell.open(path)
|
||||
templates = [ifc_file]
|
||||
# See bug 3583. We backport this change from IFC4X3 because it just makes sense.
|
||||
# Users aren't forced to use it.
|
||||
if schema == "IFC4":
|
||||
for element in templates[0].by_type("IfcPropertySetTemplate"):
|
||||
if element.TemplateType == "QTO_OCCURRENCEDRIVEN":
|
||||
element.TemplateType = "QTO_TYPEDRIVENOVERRIDE"
|
||||
self.templates = templates
|
||||
|
||||
@lru_cache
|
||||
def get_applicable(
|
||||
self,
|
||||
ifc_class="",
|
||||
predefined_type="",
|
||||
pset_only=False,
|
||||
qto_only=False,
|
||||
schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4",
|
||||
) -> list[entity_instance]:
|
||||
"""Get applicable property set templates."""
|
||||
any_class = not ifc_class
|
||||
entity = None
|
||||
if not any_class:
|
||||
entity = self.schema.declaration_by_name(ifc_class).as_entity()
|
||||
assert entity
|
||||
result = []
|
||||
for template in self.templates:
|
||||
for prop_set in template.by_type("IfcPropertySetTemplate"):
|
||||
if pset_only:
|
||||
if prop_set.TemplateType and prop_set.TemplateType.startswith("QTO_"):
|
||||
continue
|
||||
if qto_only:
|
||||
if prop_set.TemplateType and prop_set.TemplateType.startswith("PSET_"):
|
||||
continue
|
||||
if any_class or (
|
||||
entity
|
||||
and self.is_applicable(
|
||||
entity, prop_set.ApplicableEntity or "IfcRoot", predefined_type, prop_set.TemplateType, schema
|
||||
)
|
||||
):
|
||||
result.append(prop_set)
|
||||
return result
|
||||
|
||||
@lru_cache
|
||||
def get_applicable_names(
|
||||
self,
|
||||
ifc_class: str,
|
||||
predefined_type: str = "",
|
||||
pset_only: bool = False,
|
||||
qto_only: bool = False,
|
||||
schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4",
|
||||
) -> list[str]:
|
||||
"""Return names instead of objects for other use eg. enum"""
|
||||
return [
|
||||
prop_set.Name for prop_set in self.get_applicable(ifc_class, predefined_type, pset_only, qto_only, schema)
|
||||
]
|
||||
|
||||
def is_applicable(
|
||||
self,
|
||||
entity: W.entity,
|
||||
applicables: str,
|
||||
predefined_type: str = "",
|
||||
template_type: str = "NOTDEFINED",
|
||||
schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4",
|
||||
) -> bool:
|
||||
"""
|
||||
|
||||
applicables can have multiple possible patterns :
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
IfcBoilerType (IfcClass)
|
||||
IfcBoilerType/STEAM (IfcClass/PREDEFINEDTYPE)
|
||||
IfcBoilerType[PerformanceHistory] (IfcClass[PerformanceHistory])
|
||||
IfcBoilerType/STEAM[PerformanceHistory] (IfcClass/PREDEFINEDTYPE[PerformanceHistory])
|
||||
|
||||
"""
|
||||
for applicable in applicables.split(","):
|
||||
match = re.match(r"(\w+)(\[\w+\])*/*(\w+)*(\[\w+\])*", applicable)
|
||||
if not match:
|
||||
continue
|
||||
# Uncomment if usage found
|
||||
# applicable_perf_history = match.group(2) or match.group(4)
|
||||
matched_type = match.group(3)
|
||||
if matched_type and not predefined_type:
|
||||
continue
|
||||
# Case insensitive to handle things like material categories
|
||||
elif matched_type and predefined_type.lower() != match.group(3).lower():
|
||||
continue
|
||||
|
||||
applicable_class = match.group(1)
|
||||
if ifcopenshell.util.schema.is_a(entity, applicable_class):
|
||||
return True
|
||||
# There is an implementer agreement that if the template type is
|
||||
# type based, the type need not be explicitly mentioned
|
||||
# https://github.com/buildingSMART/IFC4.3.x-development/issues/22
|
||||
# This will be fixed in IFC4.3
|
||||
template_type = template_type or ""
|
||||
if "TYPE" in template_type and ifcopenshell.util.schema.is_a(entity, "IfcTypeObject"):
|
||||
types = ifcopenshell.util.type.get_applicable_types(applicable_class, schema)
|
||||
if not types:
|
||||
# Abstract classes will not have an "applicable type" but
|
||||
# the implementer agreement still applies to them.
|
||||
occurrence_class = None
|
||||
try:
|
||||
occurrence_class = self.schema.declaration_by_name(applicable_class + "Type")
|
||||
except:
|
||||
try:
|
||||
occurrence_class = self.schema.declaration_by_name("IfcType" + applicable_class[3:])
|
||||
except:
|
||||
pass
|
||||
if occurrence_class:
|
||||
types = [occurrence_class.name()]
|
||||
for ifc_type in types:
|
||||
if ifcopenshell.util.schema.is_a(entity, ifc_type):
|
||||
return True
|
||||
return False
|
||||
|
||||
@lru_cache
|
||||
def get_by_name(self, name: str) -> Optional[entity_instance]:
|
||||
for template in self.templates:
|
||||
for prop_set in template.by_type("IfcPropertySetTemplate"):
|
||||
if prop_set.Name == name:
|
||||
return prop_set
|
||||
return None
|
||||
|
||||
def is_templated(self, name: str) -> bool:
|
||||
return bool(self.get_by_name(name))
|
||||
|
||||
|
||||
def get_pset_template_type(pset_template: entity_instance) -> Literal["PSET", "QTO", None]:
|
||||
"""Get the type of the pset template.
|
||||
If type is mixed or not defined, return None."""
|
||||
|
||||
# Try to identify whether it's pset or qto from the template type.
|
||||
template_type = pset_template.TemplateType
|
||||
if template_type:
|
||||
if template_type.startswith("PSET_"):
|
||||
return "PSET"
|
||||
elif template_type.startswith("QTO_"):
|
||||
return "QTO"
|
||||
# Can also be 'NOTDEFINED'.
|
||||
|
||||
pset_types = set()
|
||||
for prop in pset_template.HasPropertyTemplates:
|
||||
prop_template_type = prop.TemplateType
|
||||
if prop_template_type:
|
||||
if prop_template_type.startswith("P_"):
|
||||
pset_types.add("PSET")
|
||||
else: # All other values are Q_.
|
||||
pset_types.add("QTO")
|
||||
|
||||
pset_type = next(iter(pset_types)) if len(pset_types) == 1 else None
|
||||
return pset_type
|
||||
|
||||
|
||||
class ApplicableEntity(NamedTuple):
|
||||
value: str
|
||||
ifc_class: str
|
||||
predefined_type: Union[str, None]
|
||||
performance_history: bool
|
||||
|
||||
|
||||
def parse_applicable_entity(applicable_entity: str) -> list[ApplicableEntity]:
|
||||
"""Parse ApplicableEntity string query to tuples.
|
||||
|
||||
:param applicable_entity: IfcPropertySetTemplate.ApplicableEntity query.
|
||||
:return: List of ApplicableEntity tuples.
|
||||
"""
|
||||
items: list[ApplicableEntity] = []
|
||||
for item in applicable_entity.split(","):
|
||||
value = item
|
||||
item, predefined_type = parts if len(parts := item.split("/")) > 1 else (item, None)
|
||||
ifc_class, performance_history = (parts[0], True) if len(parts := item.split("[")) > 1 else (item, False)
|
||||
items.append(ApplicableEntity(value, ifc_class, predefined_type, performance_history))
|
||||
return items
|
||||
|
||||
|
||||
def convert_applicable_entities_to_query(applicable_entities: list[ApplicableEntity]) -> str:
|
||||
"""Get query supported by :func:`ifcopenshell.util.selector.filter_elements`."""
|
||||
parts: list[str] = []
|
||||
for entity in applicable_entities:
|
||||
# NOTE: selector currently doesn't support checking if element has performance history.
|
||||
part = entity.ifc_class
|
||||
if entity.predefined_type:
|
||||
part += f', PredefinedType="{entity.predefined_type}"'
|
||||
parts.append(part)
|
||||
return " + ".join(parts)
|
||||
@@ -0,0 +1,523 @@
|
||||
# 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 collections.abc import Generator, Sequence
|
||||
from typing import Literal, Optional, TypedDict, Union
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.shape
|
||||
|
||||
CONTEXT_TYPE = Literal["Model", "Plan", "NotDefined"]
|
||||
REPRESENTATION_IDENTIFIER = Literal[
|
||||
"CoG",
|
||||
"Box",
|
||||
"Annotation",
|
||||
"Axis",
|
||||
"FootPrint",
|
||||
"Profile",
|
||||
"Surface",
|
||||
"Reference",
|
||||
"Body",
|
||||
"Body-Fallback",
|
||||
"Clearance",
|
||||
"Lighting",
|
||||
]
|
||||
TARGET_VIEW = Literal[
|
||||
"ELEVATION_VIEW",
|
||||
"GRAPH_VIEW",
|
||||
"MODEL_VIEW",
|
||||
"PLAN_VIEW",
|
||||
"REFLECTED_PLAN_VIEW",
|
||||
"SECTION_VIEW",
|
||||
"SKETCH_VIEW",
|
||||
"USERDEFINED",
|
||||
"NOTDEFINED",
|
||||
]
|
||||
|
||||
|
||||
def get_context(
|
||||
ifc_file: ifcopenshell.file,
|
||||
context: CONTEXT_TYPE,
|
||||
subcontext: Optional[REPRESENTATION_IDENTIFIER] = None,
|
||||
target_view: Optional[TARGET_VIEW] = None,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Get IfcGeometricRepresentationSubContext by the provided context type, identifier, and target view.
|
||||
|
||||
:param context: ContextType.
|
||||
:param subcontext: A ContextIdentifier string, or any if left blank.
|
||||
:param target_view: A TargetView string, or any if left blank.
|
||||
"""
|
||||
|
||||
if subcontext or target_view:
|
||||
elements = ifc_file.by_type("IfcGeometricRepresentationSubContext")
|
||||
else:
|
||||
elements = ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
|
||||
for element in elements:
|
||||
if context and element.ContextType != context:
|
||||
continue
|
||||
if subcontext and getattr(element, "ContextIdentifier") != subcontext:
|
||||
continue
|
||||
if target_view and getattr(element, "TargetView") != target_view:
|
||||
continue
|
||||
return element
|
||||
|
||||
|
||||
def is_representation_of_context(
|
||||
representation: ifcopenshell.entity_instance,
|
||||
context: Union[ifcopenshell.entity_instance, CONTEXT_TYPE],
|
||||
subcontext: Optional[REPRESENTATION_IDENTIFIER] = None,
|
||||
target_view: Optional[TARGET_VIEW] = None,
|
||||
) -> bool:
|
||||
"""Check if representation has specified context or context type, identifier, and target view.
|
||||
|
||||
:param representation: IfcShapeRepresentation.
|
||||
:param context: Either a specific IfcGeometricRepresentationContext or a ContextType.
|
||||
:param subcontext: A ContextIdentifier string, or any if left blank.
|
||||
:param target_view: A TargetView string, or any if left blank.
|
||||
"""
|
||||
|
||||
if isinstance(context, ifcopenshell.entity_instance):
|
||||
return representation.ContextOfItems == context
|
||||
|
||||
if target_view is not None:
|
||||
return (
|
||||
representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext")
|
||||
and representation.ContextOfItems.TargetView == target_view
|
||||
and representation.ContextOfItems.ContextIdentifier == subcontext
|
||||
and representation.ContextOfItems.ContextType == context
|
||||
)
|
||||
elif subcontext is not None:
|
||||
return (
|
||||
representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext")
|
||||
and representation.ContextOfItems.ContextIdentifier == subcontext
|
||||
and representation.ContextOfItems.ContextType == context
|
||||
)
|
||||
|
||||
return representation.ContextOfItems.ContextType == context
|
||||
|
||||
|
||||
def get_representations_iter(
|
||||
element: ifcopenshell.entity_instance,
|
||||
) -> Generator[ifcopenshell.entity_instance, None, None]:
|
||||
"""Get an iterator with element's IfcShapeRepresentations.
|
||||
|
||||
:param element: An IfcProduct or IfcTypeProduct
|
||||
"""
|
||||
if element.is_a("IfcProduct") and (rep := element.Representation):
|
||||
for r in rep.Representations:
|
||||
yield r
|
||||
elif element.is_a("IfcTypeProduct") and (maps := element.RepresentationMaps):
|
||||
for r in maps:
|
||||
yield r.MappedRepresentation
|
||||
|
||||
|
||||
def get_representation(
|
||||
element: ifcopenshell.entity_instance,
|
||||
context: Union[ifcopenshell.entity_instance, CONTEXT_TYPE],
|
||||
subcontext: Optional[REPRESENTATION_IDENTIFIER] = None,
|
||||
target_view: Optional[TARGET_VIEW] = None,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Gets a IfcShapeRepresentation filtered by the context type, identifier, and target view
|
||||
|
||||
:param element: An IfcProduct or IfcTypeProduct
|
||||
:param context: Either a specific IfcGeometricRepresentationContext or a ContextType
|
||||
:param subcontext: A ContextIdentifier string, or any if left blank.
|
||||
:param target_view: A TargetView string, or any if left blank.
|
||||
:return: The first IfcShapeRepresentation matching the criteria.
|
||||
"""
|
||||
for r in get_representations_iter(element):
|
||||
if is_representation_of_context(r, context, subcontext, target_view):
|
||||
return r
|
||||
|
||||
|
||||
def guess_type(items: Sequence[ifcopenshell.entity_instance]) -> Union[str, None]:
|
||||
"""Guesses the appropriate RepresentationType attribute based on a list of items
|
||||
|
||||
:param items: A list of IfcRepresentationItem, typically in an IfcShapeRepresentation
|
||||
:return: The appropriate RepresentationType value, or None if no valid value
|
||||
"""
|
||||
if all([True if i.is_a("IfcMappedItem") else False for i in items]):
|
||||
return "MappedRepresentation"
|
||||
elif all([True if i.is_a("IfcPoint") or i.is_a("IfcCartesianPointList") else False for i in items]):
|
||||
return "Point"
|
||||
elif all([True if i.is_a("IfcCartesianPointList3d") else False for i in items]):
|
||||
return "PointCloud"
|
||||
elif all([True if i.is_a("IfcCurve") and i.Dim == 2 else False for i in items]):
|
||||
return "Curve2D"
|
||||
elif all([True if i.is_a("IfcCurve") and i.Dim == 3 else False for i in items]):
|
||||
return "Curve3D"
|
||||
elif all([True if i.is_a("IfcCurve") else False for i in items]):
|
||||
return "Curve"
|
||||
elif all([True if i.is_a("IfcSegment") else False for i in items]):
|
||||
return "Segment"
|
||||
elif all([True if i.is_a("IfcSurface") and i.Dim == 2 else False for i in items]):
|
||||
return "Surface2D"
|
||||
elif all([True if i.is_a("IfcSurface") and i.Dim == 3 else False for i in items]):
|
||||
return "Surface3D"
|
||||
elif all([True if i.is_a("IfcSurface") else False for i in items]):
|
||||
return "Surface"
|
||||
elif all([True if i.is_a("IfcSectionedSurface") else False for i in items]):
|
||||
return "SectionedSurface"
|
||||
elif all([True if i.is_a("IfcAnnotationFillArea") else False for i in items]):
|
||||
return "FillArea"
|
||||
elif all([True if i.is_a("IfcTextLiteral") else False for i in items]):
|
||||
return "Text"
|
||||
elif all([True if i.is_a("IfcBSplineSurface") else False for i in items]):
|
||||
return "AdvancedSurface"
|
||||
elif all(
|
||||
[
|
||||
(
|
||||
True
|
||||
if i.is_a("IfcGeometricSet") or i.is_a("IfcPoint") or i.is_a("IfcCurve") or i.is_a("IfcSurface")
|
||||
else False
|
||||
)
|
||||
for i in items
|
||||
]
|
||||
):
|
||||
return "GeometricSet"
|
||||
elif all(
|
||||
[
|
||||
(
|
||||
True
|
||||
if i.is_a("IfcGeometricCurveSet")
|
||||
or (i.is_a("IfcGeometricSet") and all([e.is_a("IfcSurface") for e in i.Elements]))
|
||||
or i.is_a("IfcPoint")
|
||||
or i.is_a("IfcCurve")
|
||||
else False
|
||||
)
|
||||
for i in items
|
||||
]
|
||||
):
|
||||
return "GeometricCurveSet"
|
||||
elif all(
|
||||
[
|
||||
(
|
||||
True
|
||||
if i.is_a("IfcPoint")
|
||||
or i.is_a("IfcCurve")
|
||||
or i.is_a("IfcGeometricCurveSet")
|
||||
or i.is_a("IfcAnnotationFillArea")
|
||||
or i.is_a("IfcTextLiteral")
|
||||
else False
|
||||
)
|
||||
for i in items
|
||||
]
|
||||
):
|
||||
return "Annotation2D"
|
||||
elif all([True if i.is_a("IfcTessellatedItem") else False for i in items]):
|
||||
return "Tessellation"
|
||||
elif all(
|
||||
[
|
||||
(
|
||||
True
|
||||
if i.is_a("IfcTessellatedItem")
|
||||
or i.is_a("IfcShellBasedSurfaceModel")
|
||||
or i.is_a("IfcFaceBasedSurfaceModel")
|
||||
else False
|
||||
)
|
||||
for i in items
|
||||
]
|
||||
):
|
||||
return "SurfaceModel"
|
||||
elif all(
|
||||
[True if i.is_a() == "IfcExtrudedAreaSolid" or i.is_a() == "IfcRevolvedAreaSolid" else False for i in items]
|
||||
):
|
||||
return "SweptSolid"
|
||||
elif all([True if i.is_a("IfcSolidModel") else False for i in items]):
|
||||
return "SolidModel"
|
||||
elif all(
|
||||
[
|
||||
(
|
||||
True
|
||||
if i.is_a("IfcTessellatedItem")
|
||||
or i.is_a("IfcShellBasedSurfaceModel")
|
||||
or i.is_a("IfcFaceBasedSurfaceModel")
|
||||
or i.is_a("IfcSolidModel")
|
||||
else False
|
||||
)
|
||||
for i in items
|
||||
]
|
||||
):
|
||||
return "SurfaceOrSolidModel"
|
||||
elif all(
|
||||
[
|
||||
(
|
||||
True
|
||||
if i.is_a("IfcSweptAreaSolid") or i.is_a("IfcSweptDiskSolid") or i.is_a("IfcSectionedSolidHorizontal")
|
||||
else False
|
||||
)
|
||||
for i in items
|
||||
]
|
||||
):
|
||||
return "AdvancedSweptSolid"
|
||||
elif all([True if i.is_a("IfcCsgSolid") or i.is_a("IfcBooleanClippingResult") else False for i in items]):
|
||||
return "Clipping"
|
||||
elif all(
|
||||
[
|
||||
True if i.is_a("IfcBooleanResult") or i.is_a("IfcCsgPrimitive3d") or i.is_a("IfcCsgSolid") else False
|
||||
for i in items
|
||||
]
|
||||
):
|
||||
return "CSG"
|
||||
elif all([True if i.is_a("IfcFacetedBrep") else False for i in items]):
|
||||
return "Brep"
|
||||
elif all([True if i.is_a("IfcManifoldSolidBrep") else False for i in items]):
|
||||
return "AdvancedBrep"
|
||||
elif all([True if i.is_a("IfcBoundingBox") else False for i in items]):
|
||||
return "BoundingBox"
|
||||
elif all([True if i.is_a("IfcSectionedSpine") else False for i in items]):
|
||||
return "SectionedSpine"
|
||||
elif all([True if i.is_a("IfcLightSource") else False for i in items]):
|
||||
return "LightSource"
|
||||
elif all([True if i.is_a("IfcVertex") else False for i in items]):
|
||||
return "Vertex"
|
||||
elif all([True if i.is_a("IfcEdge") else False for i in items]):
|
||||
return "Edge"
|
||||
elif all([True if i.is_a("IfcPath") else False for i in items]):
|
||||
return "Path"
|
||||
elif all([True if i.is_a("IfcFace") else False for i in items]):
|
||||
return "Face"
|
||||
elif all([True if i.is_a("IfcOpenShell") else False for i in items]):
|
||||
return "Shell"
|
||||
|
||||
|
||||
def resolve_representation(representation: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Resolve possibly mapped representation.
|
||||
|
||||
:param representation: IfcRepresentation
|
||||
:return: Representation resolved from mappings
|
||||
"""
|
||||
# Tekla 2023 has missing items and mapped representation, though it's invalid IFC.
|
||||
if (
|
||||
len(representation.Items or []) == 1
|
||||
and representation.Items[0].is_a("IfcMappedItem")
|
||||
and (mapped_rep := representation.Items[0].MappingSource.MappedRepresentation)
|
||||
):
|
||||
return resolve_representation(mapped_rep)
|
||||
return representation
|
||||
|
||||
|
||||
class ResolvedItemDict(TypedDict):
|
||||
matrix: npt.NDArray[np.float64]
|
||||
item: ifcopenshell.entity_instance
|
||||
|
||||
|
||||
def resolve_items(
|
||||
representation: ifcopenshell.entity_instance, matrix: Optional[npt.NDArray[np.float64]] = None
|
||||
) -> list[ResolvedItemDict]:
|
||||
if matrix is None:
|
||||
matrix = np.eye(4)
|
||||
results: list[ResolvedItemDict] = []
|
||||
for item in representation.Items or []: # Be forgiving of invalid IFCs because Revit :(
|
||||
if item.is_a("IfcMappedItem"):
|
||||
rep_matrix = ifcopenshell.util.placement.get_mappeditem_transformation(item)
|
||||
if not np.allclose(rep_matrix, np.eye(4)):
|
||||
rep_matrix = rep_matrix @ matrix.copy()
|
||||
results.extend(resolve_items(item.MappingSource.MappedRepresentation, rep_matrix))
|
||||
else:
|
||||
results.append(ResolvedItemDict(matrix=matrix.copy(), item=item))
|
||||
return results
|
||||
|
||||
|
||||
def resolve_base_items(
|
||||
representation: ifcopenshell.entity_instance,
|
||||
) -> Generator[ifcopenshell.entity_instance, None, None]:
|
||||
"""Resolve representation to it's base items resolving mapped items and boolean results to it's operands."""
|
||||
queue: list[ifcopenshell.entity_instance] = list(representation.Items)
|
||||
while queue:
|
||||
item = queue.pop()
|
||||
if item.is_a("IfcMappedItem"):
|
||||
yield from resolve_base_items(item.MappingSource.MappedRepresentation)
|
||||
elif item.is_a("IfcBooleanResult"):
|
||||
queue.append(item.FirstOperand)
|
||||
queue.append(item.SecondOperand)
|
||||
else:
|
||||
yield item
|
||||
|
||||
|
||||
def get_prioritised_contexts(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
|
||||
"""Gets a list of contexts ordered from high priority to low priority
|
||||
|
||||
Models can contain multiple geometric contexts. When visualising models,
|
||||
you may want to prioritise visualising certain contexts over others,
|
||||
determined by the context type, identifier, target view, and target scale.
|
||||
|
||||
The default prioritises 3D, then 2D. It then prioritises subcontexts, then
|
||||
contexts. It then prioritises bodies, then others. It also prioritises
|
||||
model views, then plan views, then others.
|
||||
|
||||
:param ifc_file: The model containing contexts
|
||||
:return: A list of IfcGeometricRepresentationContext (or SubContext) from
|
||||
high priority to low priority.
|
||||
"""
|
||||
# Annotation ContextType is to accommodate broken Revit files
|
||||
# See https://github.com/Autodesk/revit-ifc/issues/187
|
||||
type_priority = ["Model", "Plan", "Annotation"]
|
||||
identifier_priority = [
|
||||
"Body",
|
||||
"Body-FallBack",
|
||||
"Facetation",
|
||||
"FootPrint",
|
||||
"Profile",
|
||||
"Surface",
|
||||
"Reference",
|
||||
"Axis",
|
||||
"Clearance",
|
||||
"Box",
|
||||
"Lighting",
|
||||
"Annotation",
|
||||
"CoG",
|
||||
]
|
||||
target_view_priority = [
|
||||
"MODEL_VIEW",
|
||||
"PLAN_VIEW",
|
||||
"REFLECTED_PLAN_VIEW",
|
||||
"ELEVATION_VIEW",
|
||||
"SECTION_VIEW",
|
||||
"GRAPH_VIEW",
|
||||
"SKETCH_VIEW",
|
||||
"USERDEFINED",
|
||||
"NOTDEFINED",
|
||||
]
|
||||
|
||||
def sort_context(context):
|
||||
priority = []
|
||||
|
||||
if context.ContextType in type_priority:
|
||||
priority.append(len(type_priority) - type_priority.index(context.ContextType))
|
||||
else:
|
||||
priority.append(0)
|
||||
|
||||
if context.ContextIdentifier in identifier_priority:
|
||||
priority.append(len(identifier_priority) - identifier_priority.index(context.ContextIdentifier))
|
||||
else:
|
||||
priority.append(0)
|
||||
|
||||
if getattr(context, "TargetView", None) in target_view_priority:
|
||||
priority.append(len(target_view_priority) - target_view_priority.index(context.TargetView))
|
||||
else:
|
||||
priority.append(0)
|
||||
|
||||
priority.append(getattr(context, "TargetScale", None) or 0) # Big then small
|
||||
|
||||
return tuple(priority)
|
||||
|
||||
return sorted(ifc_file.by_type("IfcGeometricRepresentationContext"), key=sort_context, reverse=True)
|
||||
|
||||
|
||||
def get_part_of_product(
|
||||
element: ifcopenshell.entity_instance, context: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Gets the product definition or representation map of an element
|
||||
|
||||
This is typically used for setting shape aspects. Note that this will
|
||||
return None for IFC2X3 element types.
|
||||
|
||||
:param element: An IfcProduct or IfcTypeProduct
|
||||
:param context: A IfcGeometricRepresentationContext
|
||||
:return: IfcProductRepresentationSelect
|
||||
"""
|
||||
if element.is_a("IfcProduct"):
|
||||
return element.Representation
|
||||
elif element.is_a("IfcTypeProduct") and element.file.schema != "IFX2X3":
|
||||
if maps := [r for r in element.RepresentationMaps if r.MappedRepresentation.ContextOfItems == context]:
|
||||
return maps[0]
|
||||
|
||||
|
||||
def get_item_shape_aspect(
|
||||
representation: ifcopenshell.entity_instance, item: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Gets the shape aspect relating to an item
|
||||
|
||||
:param representation: The IfcShapeRepresentation that the item is part of
|
||||
:param item: The IfcRepresentationItem you want to get the shape aspect of
|
||||
:return: IfcShapeAspect, or None if none exists
|
||||
"""
|
||||
for inverse in item.file.get_inverse(item):
|
||||
if (
|
||||
inverse.is_a("IfcShapeRepresentation")
|
||||
and inverse.ContextOfItems == representation.ContextOfItems
|
||||
and (of_shape_aspect := inverse.OfShapeAspect)
|
||||
):
|
||||
return of_shape_aspect[0]
|
||||
|
||||
|
||||
def get_material_style(
|
||||
material: ifcopenshell.entity_instance, context: ifcopenshell.entity_instance, ifc_class: str = "IfcSurfaceStyle"
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Get a presentation style associated with a material
|
||||
|
||||
:param material: the IfcMaterial
|
||||
:param context: IfcGeometricRepresentationContext that the style belongs to
|
||||
:param ifc_class: The class name of the type of style you need, typically
|
||||
IfcSurfaceStyle for 3D styling.
|
||||
:return: IfcPresentationStyle
|
||||
"""
|
||||
if definition_representation := material.HasRepresentation:
|
||||
for styled_rep in definition_representation[0].Representations:
|
||||
if styled_rep.ContextOfItems == context:
|
||||
for item in styled_rep.Items:
|
||||
for style in item.Styles:
|
||||
if style.is_a(ifc_class):
|
||||
return style
|
||||
|
||||
|
||||
def get_reference_line(wall: ifcopenshell.entity_instance, fallback_length: float = 1.0) -> list[npt.NDArray]:
|
||||
"""Fetch the reference axis that goes in the +X direction
|
||||
|
||||
A base line will then be offset from this reference line based on the
|
||||
material usage. From that base line, the layer thicknesses will offset
|
||||
again, and be extruded to form the body representation.
|
||||
|
||||
:param wall: ifcopenshell.entity_instance
|
||||
:param fallback_length: If there is no reference axis, assume it starts at
|
||||
the object placement (i.e. 0.0, 0.0) and extends for this fallback
|
||||
length along the +X axis.
|
||||
:return: A list of two 2D coordinates representing the start and end of the
|
||||
axis. The axis always goes in the +X direction.
|
||||
"""
|
||||
if axis := ifcopenshell.util.representation.get_representation(wall, "Plan", "Axis", "GRAPH_VIEW"):
|
||||
for item in ifcopenshell.util.representation.resolve_representation(axis).Items:
|
||||
if item.is_a("IfcPolyline"):
|
||||
points = [p[0] for p in item.Points]
|
||||
elif item.is_a("IfcIndexedPolyCurve"):
|
||||
points = item.Points.CoordList
|
||||
else:
|
||||
continue
|
||||
if points[0][0] < points[1][0]: # An axis always goes in the +X direction
|
||||
return [np.array(points[0]), np.array(points[1])]
|
||||
return [np.array(points[1]), np.array(points[0])]
|
||||
elif extrusions := ifcopenshell.util.shape.get_base_extrusions(wall):
|
||||
for extrusion in extrusions:
|
||||
profile = extrusion.SweptArea
|
||||
curve = getattr(profile, "OuterCurve", None)
|
||||
if not curve:
|
||||
continue
|
||||
elif curve.is_a("IfcPolyline"):
|
||||
x = [p[0][0] for p in curve.Points]
|
||||
elif curve.is_a("IfcIndexedPolyCurve"):
|
||||
x = [p[0] for p in curve.Points.CoordList]
|
||||
else:
|
||||
continue
|
||||
return [np.array((min(x), 0.0)), np.array((max(x), 0.0))]
|
||||
return [np.array((0.0, 0.0)), np.array((fallback_length, 0.0))]
|
||||
@@ -0,0 +1,171 @@
|
||||
# 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, Union
|
||||
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.element
|
||||
|
||||
PRODUCTIVITY_PSET_DATA = Union[dict[str, Any], None]
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcConstructionResource.htm#Table-7.3.3.7.1.3.H
|
||||
RESOURCES_TO_QUANTITIES: dict[str, tuple[str, ...]] = {
|
||||
"IfcCrewResource": ("IfcQuantityTime",),
|
||||
"IfcLaborResource": ("IfcQuantityTime",),
|
||||
"IfcSubContractResource": ("IfcQuantityTime",),
|
||||
"IfcConstructionEquipmentResource": ("IfcQuantityTime",),
|
||||
"IfcConstructionMaterialResource": (
|
||||
"IfcQuantityVolume",
|
||||
"IfcQuantityArea",
|
||||
"IfcQuantityLength",
|
||||
"IfcQuantityWeight",
|
||||
),
|
||||
"IfcConstructionProductResource": ("IfcQuantityCount",),
|
||||
}
|
||||
|
||||
|
||||
def get_productivity(resource: ifcopenshell.entity_instance, should_inherit: bool = True) -> PRODUCTIVITY_PSET_DATA:
|
||||
productivity = ifcopenshell.util.element.get_psets(resource).get("EPset_Productivity", None)
|
||||
if should_inherit and not productivity:
|
||||
# Note: This is not part of the Schema - but it makes sense to inherit from parent
|
||||
productivity = get_parent_productivity(resource)
|
||||
return productivity
|
||||
|
||||
|
||||
def get_parent_productivity(resource: ifcopenshell.entity_instance) -> PRODUCTIVITY_PSET_DATA:
|
||||
if not resource.Nests:
|
||||
return
|
||||
else:
|
||||
parent_resource = resource.Nests[0].RelatingObject
|
||||
productivity = ifcopenshell.util.element.get_psets(parent_resource).get("EPset_Productivity", None)
|
||||
return productivity
|
||||
|
||||
|
||||
def get_unit_consumed(productivity: PRODUCTIVITY_PSET_DATA) -> Union[Any, None]:
|
||||
duration = productivity.get("BaseQuantityConsumed", None)
|
||||
if not duration:
|
||||
return
|
||||
return ifcopenshell.util.date.ifc2datetime(duration)
|
||||
|
||||
|
||||
def get_quantity_produced(productivity: PRODUCTIVITY_PSET_DATA) -> float:
|
||||
if not productivity:
|
||||
return 0.0
|
||||
return productivity.get("BaseQuantityProducedValue", 0.0)
|
||||
|
||||
|
||||
def get_quantity_produced_name(productivity: PRODUCTIVITY_PSET_DATA):
|
||||
if not productivity:
|
||||
return ""
|
||||
return productivity.get("BaseQuantityProducedName", "")
|
||||
|
||||
|
||||
def get_total_quantity_produced(resource: ifcopenshell.entity_instance, quantity_name_in_process: str) -> float:
|
||||
def get_product_quantity(product: ifcopenshell.entity_instance, quantity_name: str):
|
||||
psets = ifcopenshell.util.element.get_psets(product)
|
||||
for pset in psets.values():
|
||||
for name, value in pset.items():
|
||||
if name == quantity_name:
|
||||
return float(value)
|
||||
|
||||
total = 0.0
|
||||
products = get_parametric_resource_products(resource)
|
||||
if quantity_name_in_process == "Count":
|
||||
total = len(products)
|
||||
else:
|
||||
for product in products:
|
||||
total += get_product_quantity(product, quantity_name_in_process) or 0
|
||||
return total
|
||||
|
||||
|
||||
def get_parametric_resource_products(resource: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
products = []
|
||||
for rel in resource.HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToProcess"):
|
||||
continue
|
||||
for rel2 in rel.RelatingProcess.HasAssignments or []:
|
||||
if not rel2.is_a("IfcRelAssignsToProduct"):
|
||||
continue
|
||||
products.append(rel2.RelatingProduct)
|
||||
return products
|
||||
|
||||
|
||||
def get_task_assignments(resource: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
for rel in resource.HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToProcess"):
|
||||
continue
|
||||
return rel.RelatingProcess
|
||||
|
||||
|
||||
def get_resource_required_work(resource: ifcopenshell.entity_instance) -> Union[str, None]:
|
||||
productivity = get_productivity(resource)
|
||||
if productivity:
|
||||
quantity_produced = get_quantity_produced(productivity)
|
||||
time_consumed = get_unit_consumed(productivity)
|
||||
quantity_name_in_process = get_quantity_produced_name(productivity)
|
||||
total_quantity_to_produce = get_total_quantity_produced(resource, quantity_name_in_process)
|
||||
if not time_consumed or not quantity_produced or not total_quantity_to_produce:
|
||||
return
|
||||
iso_string = ""
|
||||
if "T" in productivity.get("BaseQuantityConsumed", ""):
|
||||
seconds = (time_consumed.days * 24 * 60 * 60) + time_consumed.seconds
|
||||
productivity_ratio = seconds / quantity_produced
|
||||
required_work = total_quantity_to_produce * productivity_ratio
|
||||
iso_string = f"PT{required_work / 60 / 60}H"
|
||||
else:
|
||||
days = time_consumed.days + (time_consumed.seconds / (24 * 60 * 60))
|
||||
productivity_ratio = days / quantity_produced
|
||||
required_work = total_quantity_to_produce * productivity_ratio
|
||||
iso_string = f"P{required_work}D"
|
||||
return iso_string
|
||||
|
||||
|
||||
def get_nested_resources(resource: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
return [object for rel in resource.IsNestedBy or [] for object in rel.RelatedObjects]
|
||||
|
||||
|
||||
def get_cost(resource: ifcopenshell.entity_instance) -> tuple[Union[float, None], Union[str, None]]:
|
||||
"""Get cost data for IfcConstructionResource.
|
||||
|
||||
:return: a tuple of cost and unit.
|
||||
"""
|
||||
cost, unit = None, None
|
||||
if base_costs := resource.BaseCosts:
|
||||
costs = [ifcopenshell.util.cost.calculate_applied_value(resource, cost_value) for cost_value in base_costs]
|
||||
cost = sum(costs)
|
||||
unit_basis = next((unit_basis for cost_value in base_costs if (unit_basis := cost_value.UnitBasis)), None)
|
||||
if unit_basis and (unit_component := unit_basis.UnitComponent).is_a("IfcConversionBasedUnit"):
|
||||
unit = unit_component.Name
|
||||
return cost, unit
|
||||
|
||||
|
||||
def get_quantity(resource: ifcopenshell.entity_instance) -> float:
|
||||
if resource.Usage and resource.Usage.ScheduleWork:
|
||||
duration = ifcopenshell.util.date.ifc2datetime(resource.Usage.ScheduleWork)
|
||||
return duration.total_seconds() / 3600
|
||||
# TODO: is it safe to assume None quantity should be treated as 1.0? See #910 in ifc4x3dev.
|
||||
quantity = resource.BaseQuantity
|
||||
return 1.0 if quantity is None else quantity[3]
|
||||
|
||||
|
||||
def get_parent_cost(resource: ifcopenshell.entity_instance) -> Union[tuple[Union[float, None], Union[str, None]], None]:
|
||||
if not (nests := resource.Nests):
|
||||
return
|
||||
else:
|
||||
cost = get_cost(nests[0].RelatingObject)
|
||||
return cost
|
||||
@@ -0,0 +1,562 @@
|
||||
# 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 json
|
||||
import os
|
||||
import time
|
||||
from typing import Any, Literal, Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import ifcopenshell.util.attribute
|
||||
|
||||
# This is highly experimental and incomplete, however, it may work for simple datasets.
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
IFC_SCHEMA = Literal["IFC2X3", "IFC4", "IFC4X3"]
|
||||
|
||||
|
||||
def get_fallback_schema(version: str) -> IFC_SCHEMA:
|
||||
"""Fallback to the schema version we do have docs and mapping for.
|
||||
|
||||
Needed to support IFC versions like 4X3_RC1, 4X1 etc.
|
||||
|
||||
:param version: Typically a string from ``ifcopenshell.file.schema_identifier``, e.g. IFC4X3_ADD2
|
||||
"""
|
||||
if version.startswith("IFC4X3"):
|
||||
version = "IFC4X3"
|
||||
elif version.startswith("IFC4"):
|
||||
version = "IFC4"
|
||||
elif version.startswith("IFC2X3"):
|
||||
version = "IFC2X3"
|
||||
else:
|
||||
assert False, f"Unexpected schema version: {version}."
|
||||
return version
|
||||
|
||||
|
||||
def get_declaration(element: ifcopenshell.entity_instance):
|
||||
"""Get the schema declaration of an actively used entity instance
|
||||
|
||||
IFC models are made out of instances (e.g. with a STEP ID) of entities
|
||||
(e.g. IfcWall). Those entities are defined through a **Schema
|
||||
Declaration**.
|
||||
|
||||
**Schema Declaration** objects can be used to query information about the
|
||||
IFC schema itself, such as data types, enumeration values, and inheritance.
|
||||
|
||||
:param element: Any instance, typically from a loaded or created IFC model
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
wall = model.createIfcWall()
|
||||
declaration = ifcopenshell.util.schema.get_declaration(wall)
|
||||
print(declaration.name()) # IfcWall
|
||||
print(declaration.is_abstract()) # False
|
||||
print(declaration.supertype().name()) # IfcBuildingElement
|
||||
"""
|
||||
return element.wrapped_data.declaration().as_entity()
|
||||
|
||||
|
||||
def is_a(declaration: ifcopenshell.ifcopenshell_wrapper.declaration, ifc_class: str) -> bool:
|
||||
"""Checks if a schema declaration is a class
|
||||
|
||||
:param declaration: The declaration from the schema.
|
||||
:param ifc_class: A case insensitive IFC class name (e.g. IfcRoot)
|
||||
:return: True is the declaration is of that class
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
wall = model.createIfcWall()
|
||||
declaration = ifcopenshell.util.schema.get_declaration(wall)
|
||||
ifcopenshell.util.schema.is_a(declaration, "IfcRoot") # True
|
||||
"""
|
||||
return declaration._is(ifc_class)
|
||||
|
||||
|
||||
def get_supertypes(
|
||||
declaration: ifcopenshell.ifcopenshell_wrapper.entity,
|
||||
) -> list[ifcopenshell.ifcopenshell_wrapper.entity]:
|
||||
"""Gets a list of supertype declarations
|
||||
|
||||
:param declaration: The declaration from the schema, as an entity.
|
||||
:return: A list of supertypes in order from parent to grandparent.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
wall = model.createIfcWall()
|
||||
results = ifcopenshell.util.schema.get_supertypes(wall.wrapped_data.declaration().as_entity())
|
||||
# [<entity IfcBuildingElement>, <entity IfcElement>, ..., <entity IfcRoot>]
|
||||
"""
|
||||
results = []
|
||||
while True:
|
||||
if not (declaration := declaration.supertype()):
|
||||
break
|
||||
results.append(declaration)
|
||||
return results
|
||||
|
||||
|
||||
def get_subtypes(
|
||||
declaration: ifcopenshell.ifcopenshell_wrapper.entity,
|
||||
) -> list[ifcopenshell.ifcopenshell_wrapper.entity]:
|
||||
"""Get a flat list of subtype declarations, recursively.
|
||||
|
||||
Abstract classes are skipped.
|
||||
|
||||
Inconsistently, the declaration itself is also added to this list. This
|
||||
should be fixed exclude the declaration itself.
|
||||
|
||||
:param declaration: The declaration from the schema, as an entity.
|
||||
:return: A list of subtypes in order from child to grandchild.
|
||||
|
||||
.. code:: python
|
||||
|
||||
schema = ifcopenshell.schema_by_name("IFC4")
|
||||
declaration = schema.declaration_by_name("IfcFlowSegment")
|
||||
print(ifcopenshell.util.schema.get_subtypes(declaration))
|
||||
[<entity IfcFlowSegment>, <entity IfcCableCarrierSegment>, ..., <entity IfcPipeSegment>]
|
||||
"""
|
||||
|
||||
def get_classes(decl: ifcopenshell_wrapper.entity) -> list[ifcopenshell_wrapper.entity]:
|
||||
results: list[ifcopenshell_wrapper.entity] = []
|
||||
if not decl.is_abstract():
|
||||
results.append(decl)
|
||||
for subtype in decl.subtypes():
|
||||
results.extend(get_classes(subtype))
|
||||
return results
|
||||
|
||||
return get_classes(declaration)
|
||||
|
||||
|
||||
def reassign_class(
|
||||
ifc_file: Union[ifcopenshell.file, None], element: ifcopenshell.entity_instance, new_class: str
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""
|
||||
Attempts to change the class (entity name) of `element` to `new_class` by
|
||||
removing element and recreating a similar instance of type `new_class`
|
||||
with the same id.
|
||||
|
||||
In certain cases it may affect the structure of inversely related instances:
|
||||
- Multiple occurrences of reassigned instance within the same aggregate
|
||||
(such as start and end-point of polyline)
|
||||
- Occurrences of reassigned instance within an ordered aggregate
|
||||
(such as IfcRelNests)
|
||||
|
||||
It's unlikely that this affects real-world usage of this function.
|
||||
|
||||
:raises ValueError: If ``new_class`` does not exist in the provided file schema.
|
||||
"""
|
||||
|
||||
if element.is_a() == new_class:
|
||||
return element
|
||||
|
||||
if not ifc_file:
|
||||
ifc_file = element.file
|
||||
|
||||
schema = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier)
|
||||
try:
|
||||
declaration = schema.declaration_by_name(new_class)
|
||||
except RuntimeError:
|
||||
raise ValueError(
|
||||
f"Class of {element} could not be changed to {new_class} as the class does not exist in schema {ifc_file.schema_identifier}."
|
||||
)
|
||||
|
||||
info = element.get_info()
|
||||
|
||||
new_attributes = {}
|
||||
for attribute in declaration.all_attributes():
|
||||
name = attribute.name()
|
||||
old_attribute = info.get(name, None)
|
||||
if old_attribute:
|
||||
if ifcopenshell.util.attribute.get_primitive_type(attribute) == "enum":
|
||||
if old_attribute in ifcopenshell.util.attribute.get_enum_items(attribute):
|
||||
new_attributes[name] = old_attribute
|
||||
else:
|
||||
new_attributes[name] = old_attribute
|
||||
|
||||
inverse_pairs = ifc_file.get_inverse(element, allow_duplicate=True, with_attribute_indices=True)
|
||||
ifc_file.remove(element)
|
||||
|
||||
try:
|
||||
new_element = ifc_file.create_entity(new_class, id=info["id"], **new_attributes)
|
||||
except:
|
||||
print(f"Class of {element} could not be changed to {new_class}")
|
||||
old_class = info.pop("type")
|
||||
return ifc_file.create_entity(old_class, **info)
|
||||
|
||||
for inverse_pair in inverse_pairs:
|
||||
inverse, index = inverse_pair
|
||||
if inverse[index] is None:
|
||||
inverse[index] = new_element
|
||||
elif isinstance(inverse[index], tuple):
|
||||
item = list(inverse[index])
|
||||
item.append(new_element)
|
||||
inverse[index] = item
|
||||
|
||||
return new_element
|
||||
|
||||
|
||||
class BatchReassignClass:
|
||||
def __init__(self, file: ifcopenshell.file):
|
||||
self.file = file
|
||||
self.purge()
|
||||
|
||||
def reassign(self, element: ifcopenshell.entity_instance, new_class: str) -> ifcopenshell.entity_instance:
|
||||
try:
|
||||
new_element = self.file.create_entity(new_class)
|
||||
except:
|
||||
print(f"Class of {element} could not be changed to {new_class}")
|
||||
return element
|
||||
new_attributes = [new_element.attribute_name(i) for i, attribute in enumerate(new_element)]
|
||||
for i, attribute in enumerate(element):
|
||||
try:
|
||||
new_element[new_attributes.index(element.attribute_name(i))] = attribute
|
||||
except:
|
||||
continue
|
||||
for inverse_pair in self.file.get_inverse(element, allow_duplicate=True, with_attribute_indices=True):
|
||||
inverse, index = inverse_pair
|
||||
self.replacements.setdefault(inverse, {}).setdefault(index, {})[element] = new_element
|
||||
self.to_delete.add(element)
|
||||
return new_element
|
||||
|
||||
def unbatch(self):
|
||||
for inverse, replacements in self.replacements.items():
|
||||
for index, element_map in replacements.items():
|
||||
value = inverse[index]
|
||||
new = inverse.walk(lambda x: True, lambda v: element_map.get(v, v), value)
|
||||
if value != new:
|
||||
inverse[index] = new
|
||||
|
||||
for element in self.to_delete:
|
||||
self.file.remove(element)
|
||||
self.purge()
|
||||
|
||||
def purge(self) -> None:
|
||||
# mapping {inverse: {attribute_index: {old_element: new_element} } }
|
||||
self.replacements: dict[
|
||||
ifcopenshell.entity_instance, dict[int, dict[ifcopenshell.entity_instance, ifcopenshell.entity_instance]]
|
||||
] = {}
|
||||
self.to_delete: set[ifcopenshell.entity_instance] = set()
|
||||
|
||||
|
||||
class Migrator:
|
||||
migrated_ids: dict[int, int]
|
||||
attribute_overrides: dict[int, dict[int, str]]
|
||||
|
||||
def __init__(self):
|
||||
self.migrated_ids = {}
|
||||
self.attribute_overrides = {}
|
||||
self.class_4_to_2x3 = json.load(open(os.path.join(cwd, "class_4_to_2x3.json"), "r"))
|
||||
self.class_2x3_to_4 = json.load(open(os.path.join(cwd, "class_2x3_to_4.json"), "r"))
|
||||
|
||||
# IFC classes, and their IFC attributes mapping
|
||||
self.attributes_mapping = {
|
||||
("IFC4", "IFC2X3"): json.load(open(os.path.join(cwd, "attribute_4_to_2x3.json"), "r")),
|
||||
("IFC4X3", "IFC4"): json.load(open(os.path.join(cwd, "attribute_4x3_to_4.json"), "r")),
|
||||
}
|
||||
|
||||
self.default_values = {
|
||||
"ChangeAction": "NOCHANGE",
|
||||
"CompositionType": "ELEMENT",
|
||||
"CrossSectionArea": 1,
|
||||
"DataValue": 0,
|
||||
"DefinedValues": [0],
|
||||
"DefiningValues": [0],
|
||||
"DestabilizingLoad": False,
|
||||
"Edition": "",
|
||||
"EndParam": 1.0,
|
||||
"EnumerationValues": [0],
|
||||
"GeodeticDatum": "",
|
||||
"Intent": "",
|
||||
"IsHeading": False,
|
||||
"ListValues": [0],
|
||||
"LongitudinalBarCrossSectionArea": 1,
|
||||
"LongitudinalBarNominalDiameter": 1,
|
||||
"LongitudinalBarSpacing": 1,
|
||||
"Name": "",
|
||||
"NominalDiameter": 1,
|
||||
"PredefinedType": "NOTDEFINED",
|
||||
"RowCells": [0],
|
||||
"SequenceType": "NOTDEFINED",
|
||||
"Source": "",
|
||||
"StartParam": 0.0,
|
||||
"TransverseBarCrossSectionArea": 1,
|
||||
"TransverseBarNominalDiameter": 1,
|
||||
"TransverseBarSpacing": 1,
|
||||
# Manual additions from experience
|
||||
"InteriorOrExteriorSpace": "NOTDEFINED",
|
||||
"AssemblyPlace": "NOTDEFINED", # See bug https://github.com/Autodesk/revit-ifc/issues/395
|
||||
}
|
||||
self.default_entities = {
|
||||
"CurrentValue": None,
|
||||
"DepreciatedValue": None,
|
||||
"Jurisdiction": None,
|
||||
"OriginalValue": None,
|
||||
"Owner": None,
|
||||
"OwnerHistory": None,
|
||||
"Position": None,
|
||||
"PropertyReference": None,
|
||||
"RepresentationContexts": None,
|
||||
"ResponsiblePerson": None,
|
||||
"ResponsiblePersons": None,
|
||||
"Rows": None,
|
||||
"TotalReplacementCost": None,
|
||||
"UnitsInContext": None,
|
||||
"User": None,
|
||||
}
|
||||
|
||||
def preprocess(self, old_file: ifcopenshell.file, new_file: ifcopenshell.file) -> None:
|
||||
new_file.assign_header_from(old_file)
|
||||
to_delete = set()
|
||||
|
||||
if old_file.schema == "IFC2X3" and new_file.schema == "IFC4":
|
||||
# IfcCalendarDate is deprecated in IFC4
|
||||
for element in old_file.by_type("IfcCalendarDate"):
|
||||
for inverse, attribute_index in old_file.get_inverse(
|
||||
element, allow_duplicate=True, with_attribute_indices=True
|
||||
):
|
||||
self.attribute_overrides.setdefault(inverse.id(), {})[
|
||||
attribute_index
|
||||
] = f"{element[2]}-{element[1]}-{element[0]}"
|
||||
to_delete.add(element)
|
||||
|
||||
if old_file.schema == "IFC4" and new_file.schema == "IFC4X3":
|
||||
# IfcPresentationStyleAssignment is deprecated
|
||||
for assignment in old_file.by_type("IfcPresentationStyleAssignment"):
|
||||
for styled_item in old_file.get_inverse(assignment):
|
||||
if not styled_item.is_a("IfcStyledItem"):
|
||||
continue
|
||||
styled_item.Styles = [s for s in styled_item.Styles if s.is_a("IfcPresentationStyle")] + list(
|
||||
assignment.Styles
|
||||
)
|
||||
to_delete.add(assignment)
|
||||
|
||||
for element in to_delete:
|
||||
old_file.remove(element)
|
||||
|
||||
def migrate(
|
||||
self, element: ifcopenshell.entity_instance, new_file: ifcopenshell.file
|
||||
) -> ifcopenshell.entity_instance:
|
||||
if element.id() == 0:
|
||||
ifc_class = element.is_a()
|
||||
if ifc_class == "IfcCountMeasure" and new_file.schema == "IFC4X3":
|
||||
value = element.wrappedValue
|
||||
if isinstance(value, float):
|
||||
ifc_class = "IfcNumericMeasure"
|
||||
return new_file.create_entity(ifc_class, element.wrappedValue)
|
||||
try:
|
||||
return new_file.by_id(self.migrated_ids[element.id()])
|
||||
except:
|
||||
pass
|
||||
# print("Migrating", element)
|
||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(new_file.schema_identifier)
|
||||
new_element = self.migrate_class(element, new_file)
|
||||
# print("Migrated class from {} to {}".format(element, new_element))
|
||||
new_element_schema = schema.declaration_by_name(new_element.is_a())
|
||||
if not hasattr(new_element_schema, "all_attributes"):
|
||||
return element # The element has no attributes, so migration is done
|
||||
new_element = self.migrate_attributes(element, new_file, new_element, new_element_schema)
|
||||
self.migrated_ids[element.id()] = new_element.id()
|
||||
return new_element
|
||||
|
||||
def migrate_class(
|
||||
self, element: ifcopenshell.entity_instance, new_file: ifcopenshell.file
|
||||
) -> ifcopenshell.entity_instance:
|
||||
ifc_class = element.is_a()
|
||||
if ifc_class == "IfcQuantityCount" and new_file.schema == "IFC4X3":
|
||||
# 3 IfcPhysicalSimpleQuantity Value
|
||||
value = element[3]
|
||||
if isinstance(value, float):
|
||||
ifc_class = "IfcQuantityNumber"
|
||||
try:
|
||||
new_element = new_file.create_entity(ifc_class)
|
||||
except:
|
||||
# The element does not exist in this schema
|
||||
# Complex migration is not yet supported (e.g. polygonal face set to faceted brep)
|
||||
if new_file.schema == "IFC2X3":
|
||||
new_element = new_file.create_entity(self.class_4_to_2x3[ifc_class])
|
||||
elif new_file.schema == "IFC4":
|
||||
new_element = new_file.create_entity(self.class_2x3_to_4[ifc_class])
|
||||
return new_element
|
||||
|
||||
def migrate_attributes(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
new_file: ifcopenshell.file,
|
||||
new_element: ifcopenshell.entity_instance,
|
||||
new_element_schema: ifcopenshell_wrapper.declaration,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
for attribute_index, value in self.attribute_overrides.get(element.id(), {}).items():
|
||||
new_element[attribute_index] = value
|
||||
for i, attribute in enumerate(new_element_schema.all_attributes()):
|
||||
if new_element_schema.derived()[i]:
|
||||
continue
|
||||
self.migrate_attribute(attribute, element, new_file, new_element, new_element_schema)
|
||||
return new_element
|
||||
|
||||
def find_equivalent_attribute(
|
||||
self,
|
||||
new_element: ifcopenshell.entity_instance,
|
||||
attribute: ifcopenshell_wrapper.attribute,
|
||||
element: ifcopenshell.entity_instance,
|
||||
attributes_mapping: dict[str, dict[str, str]],
|
||||
reverse_mapping: bool = False,
|
||||
) -> Union[Any, None]:
|
||||
# print("Searching for an equivalent", element, new_element, attribute.name())
|
||||
ifc_class = new_element.is_a()
|
||||
attr_name = attribute.name()
|
||||
try:
|
||||
if reverse_mapping:
|
||||
equivalent_map = attributes_mapping[ifc_class]
|
||||
equivalent = list(equivalent_map.keys())[list(equivalent_map.values()).index(attr_name)]
|
||||
else:
|
||||
equivalent = attributes_mapping[ifc_class][attr_name]
|
||||
if hasattr(element, equivalent):
|
||||
# print("Equivalent found", equivalent)
|
||||
return getattr(element, equivalent)
|
||||
else:
|
||||
return
|
||||
except Exception as e:
|
||||
if (
|
||||
ifc_class == "IfcQuantityNumber"
|
||||
and attr_name == "NumberValue"
|
||||
and new_element.file.schema == "IFC4X3"
|
||||
and element.is_a("IfcQuantityCount")
|
||||
):
|
||||
# 3 IfcPhysicalSimpleQuantity Value
|
||||
return element[3]
|
||||
|
||||
print(
|
||||
"Unable to find equivalent attribute of {} to migrate from {} to {}".format(
|
||||
attr_name, element, new_element
|
||||
)
|
||||
)
|
||||
raise e
|
||||
|
||||
def migrate_attribute(
|
||||
self,
|
||||
attribute: ifcopenshell_wrapper.attribute,
|
||||
element: ifcopenshell.entity_instance,
|
||||
new_file: ifcopenshell.file,
|
||||
new_element: ifcopenshell.entity_instance,
|
||||
new_element_schema: ifcopenshell_wrapper.declaration,
|
||||
) -> None:
|
||||
# NOTE: `attribute` is an attribute in new file schema
|
||||
# print("Migrating attribute", element, new_element, attribute.name())
|
||||
old_file = element.wrapped_data.file
|
||||
if hasattr(element, attribute.name()):
|
||||
value = getattr(element, attribute.name())
|
||||
# print("Attribute names matched", value)
|
||||
|
||||
elif new_file.schema == "IFC2X3" and old_file.schema == "IFC4":
|
||||
# IFC4 to IFC2X3: We know the IFC2X3 attribute name, but not its IFC4 equivalent
|
||||
try:
|
||||
value = self.find_equivalent_attribute(
|
||||
new_element, attribute, element, self.attributes_mapping[("IFC4", "IFC2X3")], reverse_mapping=True
|
||||
)
|
||||
except: # We tried our best
|
||||
return
|
||||
|
||||
elif new_file.schema == "IFC4" and old_file.schema == "IFC2X3":
|
||||
# IFC2X3 to IFC4: We know the IFC4 attribute name, but not its IFC2X3 equivalent
|
||||
try:
|
||||
value = self.find_equivalent_attribute(
|
||||
new_element, attribute, element, self.attributes_mapping[("IFC4", "IFC2X3")]
|
||||
)
|
||||
except: # We tried our best
|
||||
return
|
||||
|
||||
elif new_file.schema == "IFC4X3" and old_file.schema == "IFC4":
|
||||
try:
|
||||
value = self.find_equivalent_attribute(
|
||||
new_element, attribute, element, self.attributes_mapping[("IFC4X3", "IFC4")]
|
||||
)
|
||||
except: # We tried our best
|
||||
return
|
||||
|
||||
elif new_file.schema == "IFC4" and old_file.schema == "IFC4X3":
|
||||
try:
|
||||
value = self.find_equivalent_attribute(
|
||||
new_element, attribute, element, self.attributes_mapping[("IFC4X3", "IFC4")], reverse_mapping=True
|
||||
)
|
||||
except: # We tried our best
|
||||
return
|
||||
|
||||
try:
|
||||
value
|
||||
except UnboundLocalError:
|
||||
print(
|
||||
f"Couldn't match attribute {attribute.name()} by name to migrate from {element} "
|
||||
f"to {new_element} and there is no special mapping to handle migration "
|
||||
f"from {old_file.schema} -> {new_file.schema}"
|
||||
)
|
||||
return
|
||||
|
||||
# print("Continuing migration of {} to migrate from {} to {}".format(attribute.name(), element, new_element))
|
||||
if value is None and not attribute.optional():
|
||||
value = self.generate_default_value(attribute, new_file)
|
||||
if value is None:
|
||||
print("Failed to generate default value for {} on {}".format(attribute.name(), element))
|
||||
elif isinstance(value, ifcopenshell.entity_instance):
|
||||
value = self.migrate(value, new_file)
|
||||
elif isinstance(value, (list, tuple)):
|
||||
if value and isinstance(value[0], ifcopenshell.entity_instance):
|
||||
new_value = []
|
||||
for item in value:
|
||||
new_value.append(self.migrate(item, new_file))
|
||||
value = new_value
|
||||
if value is not None:
|
||||
setattr(new_element, attribute.name(), value)
|
||||
|
||||
def generate_default_value(self, attribute: ifcopenshell_wrapper.attribute, new_file: ifcopenshell.file) -> Any:
|
||||
if attribute.name() in self.default_values:
|
||||
return self.default_values[attribute.name()]
|
||||
elif attribute.name() == "OwnerHistory":
|
||||
self.default_entities[attribute.name()] = new_file.create_entity(
|
||||
"IfcOwnerHistory",
|
||||
**{
|
||||
"OwningUser": new_file.create_entity(
|
||||
"IfcPersonAndOrganization",
|
||||
**{
|
||||
"ThePerson": new_file.create_entity("IfcPerson"),
|
||||
"TheOrganization": new_file.create_entity(
|
||||
"IfcOrganization", **{"Name": "IfcOpenShell Migrator"}
|
||||
),
|
||||
},
|
||||
),
|
||||
"OwningApplication": new_file.create_entity(
|
||||
"IfcApplication",
|
||||
**{
|
||||
"ApplicationDeveloper": new_file.create_entity(
|
||||
"IfcOrganization", **{"Name": "IfcOpenShell Migrator"}
|
||||
),
|
||||
"Version": "Works for me",
|
||||
"ApplicationFullName": "IfcOpenShell Migrator",
|
||||
"ApplicationIdentifier": "IfcOpenShell Migrator",
|
||||
},
|
||||
),
|
||||
"ChangeAction": "NOCHANGE",
|
||||
"CreationDate": int(time.time()),
|
||||
},
|
||||
)
|
||||
return self.default_entities.get(attribute.name(), None)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+319
@@ -0,0 +1,319 @@
|
||||
{
|
||||
"Pset_ActionRequest": "IfcFacilitiesMgmtDomain",
|
||||
"Pset_ActorCommon": "IfcKernel",
|
||||
"Pset_ActuatorTypeCommon": "IfcBuildingControlsDomain",
|
||||
"Pset_ActuatorTypeElectricActuator": "IfcBuildingControlsDomain",
|
||||
"Pset_ActuatorTypeHydraulicActuator": "IfcBuildingControlsDomain",
|
||||
"Pset_ActuatorTypeLinearActuation": "IfcBuildingControlsDomain",
|
||||
"Pset_ActuatorTypePneumaticActuator": "IfcBuildingControlsDomain",
|
||||
"Pset_ActuatorTypeRotationalActuation": "IfcBuildingControlsDomain",
|
||||
"Pset_AirSideSystemInformation": "IfcSharedBldgServiceElements",
|
||||
"Pset_AirTerminalBoxPHistory": "IfcHvacDomain",
|
||||
"Pset_AirTerminalBoxTypeCommon": "IfcHvacDomain",
|
||||
"Pset_AirTerminalPHistory": "IfcHvacDomain",
|
||||
"Pset_AirTerminalTypeCommon": "IfcHvacDomain",
|
||||
"Pset_AirTerminalTypeRectangular": "IfcHvacDomain",
|
||||
"Pset_AirTerminalTypeRound": "IfcHvacDomain",
|
||||
"Pset_AirTerminalTypeSlot": "IfcHvacDomain",
|
||||
"Pset_AirTerminalTypeSquare": "IfcHvacDomain",
|
||||
"Pset_AirToAirHeatRecoveryPHist": "IfcHvacDomain",
|
||||
"Pset_AirToAirHeatRecoveryTypeCommon": "IfcHvacDomain",
|
||||
"Pset_AnalogInput": "IfcBuildingControlsDomain",
|
||||
"Pset_AnalogOutput": "IfcBuildingControlsDomain",
|
||||
"Pset_Asset": "IfcSharedFacilitiesElements",
|
||||
"Pset_BeamCommon": "IfcSharedBldgElements",
|
||||
"Pset_BinaryInput": "IfcBuildingControlsDomain",
|
||||
"Pset_BinaryOutput": "IfcBuildingControlsDomain",
|
||||
"Pset_BoilerPHistory": "IfcHvacDomain",
|
||||
"Pset_BoilerTypeCommon": "IfcHvacDomain",
|
||||
"Pset_BoilerTypeSteam": "IfcHvacDomain",
|
||||
"Pset_BuildingCommon": "IfcProductExtension",
|
||||
"Pset_BuildingElementProxyCommon": "IfcProductExtension",
|
||||
"Pset_BuildingStoreyCommon": "IfcProductExtension",
|
||||
"Pset_BuildingUse": "IfcProductExtension",
|
||||
"Pset_BuildingUseAdjacent": "IfcProductExtension",
|
||||
"Pset_BuildingWaterStorage": "IfcProductExtension",
|
||||
"Pset_CableCarrierSegmentTypeCableLadderSegment": "IfcElectricalDomain",
|
||||
"Pset_CableCarrierSegmentTypeCableTraySegment": "IfcElectricalDomain",
|
||||
"Pset_CableCarrierSegmentTypeCableTrunkingSegment": "IfcElectricalDomain",
|
||||
"Pset_CableCarrierSegmentTypeConduitSegment": "IfcElectricalDomain",
|
||||
"Pset_CableSegmentTypeCableSegment": "IfcElectricalDomain",
|
||||
"Pset_CableSegmentTypeConductorSegment": "IfcElectricalDomain",
|
||||
"Pset_ChillerPHistory": "IfcHvacDomain",
|
||||
"Pset_ChillerTypeCommon": "IfcHvacDomain",
|
||||
"Pset_CoilPHistory": "IfcHvacDomain",
|
||||
"Pset_CoilTypeCommon": "IfcHvacDomain",
|
||||
"Pset_CoilTypeHydronic": "IfcHvacDomain",
|
||||
"Pset_ColumnCommon": "IfcSharedBldgElements",
|
||||
"Pset_CompressorPHistory": "IfcHvacDomain",
|
||||
"Pset_CompressorTypeCommon": "IfcHvacDomain",
|
||||
"Pset_ConcreteElementGeneral": "IfcStructuralElementsDomain",
|
||||
"Pset_ConcreteElementQuantityGeneral": "IfcStructuralElementsDomain",
|
||||
"Pset_ConcreteElementSurfaceFinishQuantityGeneral": "IfcStructuralElementsDomain",
|
||||
"Pset_CondenserPHistory": "IfcHvacDomain",
|
||||
"Pset_CondenserTypeCommon": "IfcHvacDomain",
|
||||
"Pset_ControllerTypeCommon": "IfcBuildingControlsDomain",
|
||||
"Pset_ControllerTypeProportional": "IfcBuildingControlsDomain",
|
||||
"Pset_ControllerTypeTwoPosition": "IfcBuildingControlsDomain",
|
||||
"Pset_CooledBeamPHistory": "IfcHvacDomain",
|
||||
"Pset_CooledBeamPHistoryActive": "IfcHvacDomain",
|
||||
"Pset_CooledBeamTypeActive": "IfcHvacDomain",
|
||||
"Pset_CooledBeamTypeCommon": "IfcHvacDomain",
|
||||
"Pset_CoolingTowerPHistory": "IfcHvacDomain",
|
||||
"Pset_CoolingTowerTypeCommon": "IfcHvacDomain",
|
||||
"Pset_CoveringCeiling": "IfcProductExtension",
|
||||
"Pset_CoveringCommon": "IfcProductExtension",
|
||||
"Pset_CoveringFlooring": "IfcProductExtension",
|
||||
"Pset_CurtainWallCommon": "IfcSharedBldgElements",
|
||||
"Pset_DamperPHistory": "IfcHvacDomain",
|
||||
"Pset_DamperTypeCommon": "IfcHvacDomain",
|
||||
"Pset_DamperTypeControlDamper": "IfcHvacDomain",
|
||||
"Pset_DamperTypeFireDamper": "IfcHvacDomain",
|
||||
"Pset_DamperTypeFireSmokeDamper": "IfcHvacDomain",
|
||||
"Pset_DamperTypeSmokeDamper": "IfcHvacDomain",
|
||||
"Pset_DesignPoint": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_DiscreteAccessoryAnchorBolt": "IfcSharedComponentElements",
|
||||
"Pset_DiscreteAccessoryColumnShoe": "IfcSharedComponentElements",
|
||||
"Pset_DiscreteAccessoryCornerFixingPlate": "IfcSharedComponentElements",
|
||||
"Pset_DiscreteAccessoryDiagonalTrussConnector": "IfcSharedComponentElements",
|
||||
"Pset_DiscreteAccessoryEdgeFixingPlate": "IfcSharedComponentElements",
|
||||
"Pset_DiscreteAccessoryFixingSocket": "IfcSharedComponentElements",
|
||||
"Pset_DiscreteAccessoryLadderTrussConnector": "IfcSharedComponentElements",
|
||||
"Pset_DiscreteAccessoryStandardFixingPlate": "IfcSharedComponentElements",
|
||||
"Pset_DiscreteAccessoryWireLoop": "IfcSharedComponentElements",
|
||||
"Pset_DistributionChamberElementTypeFormedDuct": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionChamberElementTypeInspectionChamber": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionChamberElementTypeInspectionPit": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionChamberElementTypeManhole": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionChamberElementTypeMeterChamber": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionChamberElementTypeSump": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionChamberElementTypeTrench": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionChamberElementTypeValveChamber": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionFlowElementCommon": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionPortDuct": "IfcSharedBldgServiceElements",
|
||||
"Pset_DistributionPortPipe": "IfcSharedBldgServiceElements",
|
||||
"Pset_DoorCommon": "IfcSharedBldgElements",
|
||||
"Pset_DoorWindowGlazingType": "IfcSharedBldgElements",
|
||||
"Pset_DoorWindowShadingType": "IfcSharedBldgElements",
|
||||
"Pset_DrainageCatchment": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_DrainageCulvert": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_DrainageOutfall": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_DrainageReserve": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_Draughting": "IfcProductExtension",
|
||||
"Pset_DuctConnection": "IfcHvacDomain",
|
||||
"Pset_DuctDesignCriteria": "IfcHvacDomain",
|
||||
"Pset_DuctFittingPHistory": "IfcHvacDomain",
|
||||
"Pset_DuctFittingTypeCommon": "IfcHvacDomain",
|
||||
"Pset_DuctSegmentPHistory": "IfcHvacDomain",
|
||||
"Pset_DuctSegmentTypeCommon": "IfcHvacDomain",
|
||||
"Pset_DuctSilencerPHistory": "IfcHvacDomain",
|
||||
"Pset_DuctSilencerTypeCommon": "IfcHvacDomain",
|
||||
"Pset_ElectricDistributionPointCommon": "IfcElectricalDomain",
|
||||
"Pset_ElectricGeneratorTypeCommon": "IfcElectricalDomain",
|
||||
"Pset_ElectricHeaterTypeElectricalCableHeater": "IfcElectricalDomain",
|
||||
"Pset_ElectricHeaterTypeElectricalMatHeater": "IfcElectricalDomain",
|
||||
"Pset_ElectricHeaterTypeElectricalPointHeater": "IfcElectricalDomain",
|
||||
"Pset_ElectricMotorTypeCommon": "IfcElectricalDomain",
|
||||
"Pset_ElectricalCircuit": "IfcElectricalDomain",
|
||||
"Pset_ElectricalDeviceCommon": "IfcElectricalDomain",
|
||||
"Pset_ElementShading": "IfcProductExtension",
|
||||
"Pset_EnergyConsumptionPHistoryElectricity": "IfcHvacDomain",
|
||||
"Pset_EnergyConsumptionPHistoryFuel": "IfcHvacDomain",
|
||||
"Pset_EnergyConsumptionPHistorySteam": "IfcHvacDomain",
|
||||
"Pset_EnergyConversionDeviceCoil": "IfcSharedBldgServiceElements",
|
||||
"Pset_EnergyConversionDeviceSpaceHeaterPanel": "IfcSharedBldgServiceElements",
|
||||
"Pset_EnergyConversionDeviceSpaceHeaterSectional": "IfcSharedBldgServiceElements",
|
||||
"Pset_EvaporativeCoolerPHistory": "IfcHvacDomain",
|
||||
"Pset_EvaporativeCoolerTypeCommon": "IfcHvacDomain",
|
||||
"Pset_EvaporatorPHistory": "IfcHvacDomain",
|
||||
"Pset_EvaporatorTypeCommon": "IfcHvacDomain",
|
||||
"Pset_FanPHistory": "IfcHvacDomain",
|
||||
"Pset_FanTypeCommon": "IfcHvacDomain",
|
||||
"Pset_FanTypeSmokeControl": "IfcHvacDomain",
|
||||
"Pset_FilterPHistory": "IfcHvacDomain",
|
||||
"Pset_FilterTypeAirParticleFilter": "IfcHvacDomain",
|
||||
"Pset_FilterTypeCommon": "IfcHvacDomain",
|
||||
"Pset_FireRatingProperties": "IfcSharedBldgServiceElements",
|
||||
"Pset_FireSuppressionTerminalTypeBreechingInlet": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_FireSuppressionTerminalTypeFireHydrant": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_FireSuppressionTerminalTypeHoseReel": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_FireSuppressionTerminalTypeSprinkler": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_FlowControllerDamper": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowControllerFlowMeter": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowFittingDuctFitting": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowFittingPipeFitting": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowInstrumentTypePressureGauge": "IfcBuildingControlsDomain",
|
||||
"Pset_FlowInstrumentTypeThermometer": "IfcBuildingControlsDomain",
|
||||
"Pset_FlowMeterTypeCommon": "IfcHvacDomain",
|
||||
"Pset_FlowMeterTypeEnergyMeter": "IfcHvacDomain",
|
||||
"Pset_FlowMeterTypeGasMeter": "IfcHvacDomain",
|
||||
"Pset_FlowMeterTypeOilMeter": "IfcHvacDomain",
|
||||
"Pset_FlowMeterTypeWaterMeter": "IfcHvacDomain",
|
||||
"Pset_FlowMovingDeviceCompressor": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowMovingDeviceFan": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowMovingDeviceFanCentrifugal": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowMovingDevicePump": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowSegmentDuctSegment": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowSegmentPipeSegment": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowStorageDeviceTank": "IfcSharedBldgServiceElements",
|
||||
"Pset_FlowTerminalAirTerminal": "IfcSharedBldgServiceElements",
|
||||
"Pset_FurnitureTypeChair": "IfcSharedFacilitiesElements",
|
||||
"Pset_FurnitureTypeCommon": "IfcSharedFacilitiesElements",
|
||||
"Pset_FurnitureTypeDesk": "IfcSharedFacilitiesElements",
|
||||
"Pset_FurnitureTypeFileCabinet": "IfcSharedFacilitiesElements",
|
||||
"Pset_FurnitureTypeTable": "IfcSharedFacilitiesElements",
|
||||
"Pset_GasTerminalPHistory": "IfcHvacDomain",
|
||||
"Pset_GasTerminalTypeCommon": "IfcHvacDomain",
|
||||
"Pset_GasTerminalTypeGasAppliance": "IfcHvacDomain",
|
||||
"Pset_GasTerminalTypeGasBurner": "IfcHvacDomain",
|
||||
"Pset_HeatExchangerTypeCommon": "IfcHvacDomain",
|
||||
"Pset_HeatExchangerTypePlate": "IfcHvacDomain",
|
||||
"Pset_HumidifierPHistory": "IfcHvacDomain",
|
||||
"Pset_HumidifierTypeCommon": "IfcHvacDomain",
|
||||
"Pset_LampTypeCommon": "IfcElectricalDomain",
|
||||
"Pset_LightFixtureTypeCommon": "IfcElectricalDomain",
|
||||
"Pset_LightFixtureTypeExitSign": "IfcElectricalDomain",
|
||||
"Pset_LightFixtureTypeThermal": "IfcElectricalDomain",
|
||||
"Pset_ManufacturerOccurrence": "IfcSharedFacilitiesElements",
|
||||
"Pset_ManufacturerTypeInformation": "IfcSharedFacilitiesElements",
|
||||
"Pset_MemberCommon": "IfcSharedBldgElements",
|
||||
"Pset_MultiStateInput": "IfcBuildingControlsDomain",
|
||||
"Pset_MultiStateOutput": "IfcBuildingControlsDomain",
|
||||
"Pset_OpeningElementCommon": "IfcProductExtension",
|
||||
"Pset_OutletTypeCommon": "IfcElectricalDomain",
|
||||
"Pset_OutsideDesignCriteria": "IfcSharedBldgServiceElements",
|
||||
"Pset_PackingInstructions": "IfcFacilitiesMgmtDomain",
|
||||
"Pset_Permit": "IfcFacilitiesMgmtDomain",
|
||||
"Pset_PipeConnection": "IfcHvacDomain",
|
||||
"Pset_PipeConnectionFlanged": "IfcHvacDomain",
|
||||
"Pset_PipeFittingPHistory": "IfcHvacDomain",
|
||||
"Pset_PipeFittingTypeCommon": "IfcHvacDomain",
|
||||
"Pset_PipeSegmentPHistory": "IfcHvacDomain",
|
||||
"Pset_PipeSegmentTypeCommon": "IfcHvacDomain",
|
||||
"Pset_PipeSegmentTypeGutter": "IfcHvacDomain",
|
||||
"Pset_PlateCommon": "IfcSharedBldgElements",
|
||||
"Pset_PrecastConcreteElementGeneral": "IfcStructuralElementsDomain",
|
||||
"Pset_ProductRequirements": "IfcKernel",
|
||||
"Pset_ProjectCommon": "IfcKernel",
|
||||
"Pset_ProjectOrderChangeOrder": "IfcSharedMgmtElements",
|
||||
"Pset_ProjectOrderMaintenanceWorkOrder": "IfcSharedMgmtElements",
|
||||
"Pset_ProjectOrderMoveOrder": "IfcSharedMgmtElements",
|
||||
"Pset_ProjectOrderPurchaseOrder": "IfcSharedMgmtElements",
|
||||
"Pset_ProjectOrderWorkOrder": "IfcSharedMgmtElements",
|
||||
"Pset_ProjectionElementShadingDevicePHistory": "IfcHvacDomain",
|
||||
"Pset_PropertyAgreement": "IfcSharedFacilitiesElements",
|
||||
"Pset_ProtectiveDeviceTypeCircuitBreaker": "IfcElectricalDomain",
|
||||
"Pset_ProtectiveDeviceTypeCommon": "IfcElectricalDomain",
|
||||
"Pset_ProtectiveDeviceTypeEarthFailureDevice": "IfcElectricalDomain",
|
||||
"Pset_ProtectiveDeviceTypeFuseDisconnector": "IfcElectricalDomain",
|
||||
"Pset_ProtectiveDeviceTypeResidualCurrentCircuitBreaker": "IfcElectricalDomain",
|
||||
"Pset_ProtectiveDeviceTypeResidualCurrentSwitch": "IfcElectricalDomain",
|
||||
"Pset_ProtectiveDeviceTypeVaristor": "IfcElectricalDomain",
|
||||
"Pset_PumpPHistory": "IfcHvacDomain",
|
||||
"Pset_PumpTypeCommon": "IfcHvacDomain",
|
||||
"Pset_QuantityTakeOff": "IfcProductExtension",
|
||||
"Pset_RailingCommon": "IfcSharedBldgElements",
|
||||
"Pset_RampCommon": "IfcSharedBldgElements",
|
||||
"Pset_RampFlightCommon": "IfcSharedBldgElements",
|
||||
"Pset_ReinforcementBarCountOfIndependentFooting": "IfcStructuralElementsDomain",
|
||||
"Pset_ReinforcementBarPitchOfBeam": "IfcStructuralElementsDomain",
|
||||
"Pset_ReinforcementBarPitchOfColumn": "IfcStructuralElementsDomain",
|
||||
"Pset_ReinforcementBarPitchOfContinuousFooting": "IfcStructuralElementsDomain",
|
||||
"Pset_ReinforcementBarPitchOfSlab": "IfcStructuralElementsDomain",
|
||||
"Pset_ReinforcementBarPitchOfWall": "IfcStructuralElementsDomain",
|
||||
"Pset_ReinforcingBarBendingsBECCommon": "IfcStructuralElementsDomain",
|
||||
"Pset_ReinforcingBarBendingsBS8666Common": "IfcStructuralElementsDomain",
|
||||
"Pset_ReinforcingBarBendingsDIN135610Common": "IfcStructuralElementsDomain",
|
||||
"Pset_ReinforcingBarBendingsISOCD3766Common": "IfcStructuralElementsDomain",
|
||||
"Pset_Reliability": "IfcSharedFacilitiesElements",
|
||||
"Pset_Risk": "IfcSharedFacilitiesElements",
|
||||
"Pset_RoofCommon": "IfcSharedBldgElements",
|
||||
"Pset_SanitaryTerminalTypeBath": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SanitaryTerminalTypeBidet": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SanitaryTerminalTypeCistern": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SanitaryTerminalTypeSanitaryFountain": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SanitaryTerminalTypeShower": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SanitaryTerminalTypeSink": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SanitaryTerminalTypeToiletPan": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SanitaryTerminalTypeUrinal": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SanitaryTerminalTypeWCSeat": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SanitaryTerminalTypeWashHandBasin": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_SensorTypeCO2Sensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypeFireSensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypeGasSensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypeHeatSensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypeHumiditySensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypeLightSensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypeMovementSensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypePressureSensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypeSmokeSensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypeSoundSensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SensorTypeTemperatureSensor": "IfcBuildingControlsDomain",
|
||||
"Pset_SiteCommon": "IfcProductExtension",
|
||||
"Pset_SlabCommon": "IfcSharedBldgElements",
|
||||
"Pset_SpaceCommon": "IfcProductExtension",
|
||||
"Pset_SpaceFireSafetyRequirements": "IfcProductExtension",
|
||||
"Pset_SpaceHeaterPHistoryCommon": "IfcHvacDomain",
|
||||
"Pset_SpaceHeaterTypeCommon": "IfcHvacDomain",
|
||||
"Pset_SpaceHeaterTypeHydronic": "IfcHvacDomain",
|
||||
"Pset_SpaceLightingRequirements": "IfcProductExtension",
|
||||
"Pset_SpaceOccupancyRequirements": "IfcProductExtension",
|
||||
"Pset_SpaceParking": "IfcProductExtension",
|
||||
"Pset_SpaceParkingAisle": "IfcProductExtension",
|
||||
"Pset_SpaceProgramCommon": "IfcArchitectureDomain",
|
||||
"Pset_SpaceThermalDesign": "IfcSharedBldgServiceElements",
|
||||
"Pset_SpaceThermalPHistory": "IfcHvacDomain",
|
||||
"Pset_SpaceThermalRequirements": "IfcProductExtension",
|
||||
"Pset_StairCommon": "IfcSharedBldgElements",
|
||||
"Pset_StairFlightCommon": "IfcSharedBldgElements",
|
||||
"Pset_SwitchingDeviceTypeCommon": "IfcElectricalDomain",
|
||||
"Pset_SwitchingDeviceTypeContactor": "IfcElectricalDomain",
|
||||
"Pset_SwitchingDeviceTypeEmergencyStop": "IfcElectricalDomain",
|
||||
"Pset_SwitchingDeviceTypeStarter": "IfcElectricalDomain",
|
||||
"Pset_SwitchingDeviceTypeSwitchDisconnector": "IfcElectricalDomain",
|
||||
"Pset_SwitchingDeviceTypeToggleSwitch": "IfcElectricalDomain",
|
||||
"Pset_SystemFurnitureElementTypeCommon": "IfcSharedFacilitiesElements",
|
||||
"Pset_SystemFurnitureElementTypePanel": "IfcSharedFacilitiesElements",
|
||||
"Pset_SystemFurnitureElementTypeWorkSurface": "IfcSharedFacilitiesElements",
|
||||
"Pset_TankTypeCommon": "IfcHvacDomain",
|
||||
"Pset_TankTypeExpansion": "IfcHvacDomain",
|
||||
"Pset_TankTypePreformed": "IfcHvacDomain",
|
||||
"Pset_TankTypePressureVessel": "IfcHvacDomain",
|
||||
"Pset_TankTypeSectional": "IfcHvacDomain",
|
||||
"Pset_ThermalLoadAggregate": "IfcSharedBldgServiceElements",
|
||||
"Pset_ThermalLoadDesignCriteria": "IfcSharedBldgServiceElements",
|
||||
"Pset_TransformerTypeCommon": "IfcElectricalDomain",
|
||||
"Pset_TransportElementCommon": "IfcProductExtension",
|
||||
"Pset_TransportElementElevator": "IfcProductExtension",
|
||||
"Pset_TubeBundleTypeCommon": "IfcHvacDomain",
|
||||
"Pset_TubeBundleTypeFinned": "IfcHvacDomain",
|
||||
"Pset_UnitaryEquipmentTypeAirConditioningUnit": "IfcHvacDomain",
|
||||
"Pset_UnitaryEquipmentTypeAirHandler": "IfcHvacDomain",
|
||||
"Pset_UtilityConsumption": "IfcSharedBldgServiceElements",
|
||||
"Pset_ValvePHistory": "IfcHvacDomain",
|
||||
"Pset_ValveTypeAirRelease": "IfcHvacDomain",
|
||||
"Pset_ValveTypeCommon": "IfcHvacDomain",
|
||||
"Pset_ValveTypeDrawOffCock": "IfcHvacDomain",
|
||||
"Pset_ValveTypeFaucet": "IfcHvacDomain",
|
||||
"Pset_ValveTypeFlushing": "IfcHvacDomain",
|
||||
"Pset_ValveTypeGasTap": "IfcHvacDomain",
|
||||
"Pset_ValveTypeIsolating": "IfcHvacDomain",
|
||||
"Pset_ValveTypeMixing": "IfcHvacDomain",
|
||||
"Pset_ValveTypePressureReducing": "IfcHvacDomain",
|
||||
"Pset_ValveTypePressureRelief": "IfcHvacDomain",
|
||||
"Pset_VibrationIsolatorTypeCommon": "IfcHvacDomain",
|
||||
"Pset_WallCommon": "IfcSharedBldgElements",
|
||||
"Pset_Warranty": "IfcSharedFacilitiesElements",
|
||||
"Pset_WasteTerminalTypeFloorTrap": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WasteTerminalTypeFloorWaste": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WasteTerminalTypeGreaseInterceptor": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WasteTerminalTypeGullySump": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WasteTerminalTypeGullyTrap": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WasteTerminalTypeOilInterceptor": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WasteTerminalTypePetrolInterceptor": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WasteTerminalTypeRoofDrain": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WasteTerminalTypeWasteDisposalUnit": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WasteTerminalTypeWasteTrap": "IfcPlumbingFireProtectionDomain",
|
||||
"Pset_WindowCommon": "IfcSharedBldgElements",
|
||||
"Pset_ZoneCommon": "IfcProductExtension"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+515
@@ -0,0 +1,515 @@
|
||||
{
|
||||
"pset_actionrequest": "ifcsharedmgmtelements",
|
||||
"pset_actorcommon": "ifckernel",
|
||||
"pset_actuatorphistory": "ifcbuildingcontrolsdomain",
|
||||
"pset_actuatortypecommon": "ifcbuildingcontrolsdomain",
|
||||
"pset_actuatortypeelectricactuator": "ifcbuildingcontrolsdomain",
|
||||
"pset_actuatortypehydraulicactuator": "ifcbuildingcontrolsdomain",
|
||||
"pset_actuatortypelinearactuation": "ifcbuildingcontrolsdomain",
|
||||
"pset_actuatortypepneumaticactuator": "ifcbuildingcontrolsdomain",
|
||||
"pset_actuatortyperotationalactuation": "ifcbuildingcontrolsdomain",
|
||||
"pset_airsidesysteminformation": "ifcsharedbldgserviceelements",
|
||||
"pset_airterminalboxphistory": "ifchvacdomain",
|
||||
"pset_airterminalboxtypecommon": "ifchvacdomain",
|
||||
"pset_airterminaloccurrence": "ifchvacdomain",
|
||||
"pset_airterminalphistory": "ifchvacdomain",
|
||||
"pset_airterminaltypecommon": "ifchvacdomain",
|
||||
"pset_airtoairheatrecoveryphistory": "ifchvacdomain",
|
||||
"pset_airtoairheatrecoverytypecommon": "ifchvacdomain",
|
||||
"pset_alarmphistory": "ifcbuildingcontrolsdomain",
|
||||
"pset_alarmtypecommon": "ifcbuildingcontrolsdomain",
|
||||
"pset_annotationcontourline": "ifcproductextension",
|
||||
"pset_annotationlineofsight": "ifcproductextension",
|
||||
"pset_annotationsurveyarea": "ifcproductextension",
|
||||
"pset_asset": "ifcsharedfacilitieselements",
|
||||
"pset_audiovisualappliancephistory": "ifcelectricaldomain",
|
||||
"pset_audiovisualappliancetypeamplifier": "ifcelectricaldomain",
|
||||
"pset_audiovisualappliancetypecamera": "ifcelectricaldomain",
|
||||
"pset_audiovisualappliancetypecommon": "ifcelectricaldomain",
|
||||
"pset_audiovisualappliancetypedisplay": "ifcelectricaldomain",
|
||||
"pset_audiovisualappliancetypeplayer": "ifcelectricaldomain",
|
||||
"pset_audiovisualappliancetypeprojector": "ifcelectricaldomain",
|
||||
"pset_audiovisualappliancetypereceiver": "ifcelectricaldomain",
|
||||
"pset_audiovisualappliancetypespeaker": "ifcelectricaldomain",
|
||||
"pset_audiovisualappliancetypetuner": "ifcelectricaldomain",
|
||||
"pset_beamcommon": "ifcsharedbldgelements",
|
||||
"pset_boilerphistory": "ifchvacdomain",
|
||||
"pset_boilertypecommon": "ifchvacdomain",
|
||||
"pset_boilertypesteam": "ifchvacdomain",
|
||||
"pset_boilertypewater": "ifchvacdomain",
|
||||
"pset_buildingcommon": "ifcproductextension",
|
||||
"pset_buildingelementproxycommon": "ifcsharedbldgelements",
|
||||
"pset_buildingelementproxyprovisionforvoid": "ifcsharedbldgelements",
|
||||
"pset_buildingstoreycommon": "ifcproductextension",
|
||||
"pset_buildingsystemcommon": "ifcsharedbldgelements",
|
||||
"pset_buildinguse": "ifcproductextension",
|
||||
"pset_buildinguseadjacent": "ifcproductextension",
|
||||
"pset_burnertypecommon": "ifchvacdomain",
|
||||
"pset_cablecarrierfittingtypecommon": "ifcelectricaldomain",
|
||||
"pset_cablecarriersegmenttypecableladdersegment": "ifcelectricaldomain",
|
||||
"pset_cablecarriersegmenttypecabletraysegment": "ifcelectricaldomain",
|
||||
"pset_cablecarriersegmenttypecabletrunkingsegment": "ifcelectricaldomain",
|
||||
"pset_cablecarriersegmenttypecommon": "ifcelectricaldomain",
|
||||
"pset_cablecarriersegmenttypeconduitsegment": "ifcelectricaldomain",
|
||||
"pset_cablefittingtypecommon": "ifcelectricaldomain",
|
||||
"pset_cablesegmentoccurrence": "ifcelectricaldomain",
|
||||
"pset_cablesegmenttypebusbarsegment": "ifcelectricaldomain",
|
||||
"pset_cablesegmenttypecablesegment": "ifcelectricaldomain",
|
||||
"pset_cablesegmenttypecommon": "ifcelectricaldomain",
|
||||
"pset_cablesegmenttypeconductorsegment": "ifcelectricaldomain",
|
||||
"pset_cablesegmenttypecoresegment": "ifcelectricaldomain",
|
||||
"pset_chillerphistory": "ifchvacdomain",
|
||||
"pset_chillertypecommon": "ifchvacdomain",
|
||||
"pset_chimneycommon": "ifcsharedbldgelements",
|
||||
"pset_civilelementcommon": "ifcproductextension",
|
||||
"pset_coiloccurrence": "ifchvacdomain",
|
||||
"pset_coilphistory": "ifchvacdomain",
|
||||
"pset_coiltypecommon": "ifchvacdomain",
|
||||
"pset_coiltypehydronic": "ifchvacdomain",
|
||||
"pset_columncommon": "ifcsharedbldgelements",
|
||||
"pset_communicationsappliancephistory": "ifcelectricaldomain",
|
||||
"pset_communicationsappliancetypecommon": "ifcelectricaldomain",
|
||||
"pset_compressorphistory": "ifchvacdomain",
|
||||
"pset_compressortypecommon": "ifchvacdomain",
|
||||
"pset_concreteelementgeneral": "ifcstructuralelementsdomain",
|
||||
"pset_condenserphistory": "ifchvacdomain",
|
||||
"pset_condensertypecommon": "ifchvacdomain",
|
||||
"pset_condition": "ifcsharedfacilitieselements",
|
||||
"pset_constructionresource": "ifcconstructionmgmtdomain",
|
||||
"pset_controllerphistory": "ifcbuildingcontrolsdomain",
|
||||
"pset_controllertypecommon": "ifcbuildingcontrolsdomain",
|
||||
"pset_controllertypefloating": "ifcbuildingcontrolsdomain",
|
||||
"pset_controllertypemultiposition": "ifcbuildingcontrolsdomain",
|
||||
"pset_controllertypeprogrammable": "ifcbuildingcontrolsdomain",
|
||||
"pset_controllertypeproportional": "ifcbuildingcontrolsdomain",
|
||||
"pset_controllertypetwoposition": "ifcbuildingcontrolsdomain",
|
||||
"pset_cooledbeamphistory": "ifchvacdomain",
|
||||
"pset_cooledbeamphistoryactive": "ifchvacdomain",
|
||||
"pset_cooledbeamtypeactive": "ifchvacdomain",
|
||||
"pset_cooledbeamtypecommon": "ifchvacdomain",
|
||||
"pset_coolingtowerphistory": "ifchvacdomain",
|
||||
"pset_coolingtowertypecommon": "ifchvacdomain",
|
||||
"pset_coveringceiling": "ifcsharedbldgelements",
|
||||
"pset_coveringcommon": "ifcsharedbldgelements",
|
||||
"pset_coveringflooring": "ifcsharedbldgelements",
|
||||
"pset_curtainwallcommon": "ifcsharedbldgelements",
|
||||
"pset_damperoccurrence": "ifchvacdomain",
|
||||
"pset_damperphistory": "ifchvacdomain",
|
||||
"pset_dampertypecommon": "ifchvacdomain",
|
||||
"pset_dampertypecontroldamper": "ifchvacdomain",
|
||||
"pset_dampertypefiredamper": "ifchvacdomain",
|
||||
"pset_dampertypefiresmokedamper": "ifchvacdomain",
|
||||
"pset_dampertypesmokedamper": "ifchvacdomain",
|
||||
"pset_discreteaccessorycolumnshoe": "ifcsharedcomponentelements",
|
||||
"pset_discreteaccessorycornerfixingplate": "ifcsharedcomponentelements",
|
||||
"pset_discreteaccessorydiagonaltrussconnector": "ifcsharedcomponentelements",
|
||||
"pset_discreteaccessoryedgefixingplate": "ifcsharedcomponentelements",
|
||||
"pset_discreteaccessoryfixingsocket": "ifcsharedcomponentelements",
|
||||
"pset_discreteaccessoryladdertrussconnector": "ifcsharedcomponentelements",
|
||||
"pset_discreteaccessorystandardfixingplate": "ifcsharedcomponentelements",
|
||||
"pset_discreteaccessorywireloop": "ifcsharedcomponentelements",
|
||||
"pset_distributionchamberelementcommon": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionchamberelementtypeformedduct": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionchamberelementtypeinspectionchamber": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionchamberelementtypeinspectionpit": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionchamberelementtypemanhole": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionchamberelementtypemeterchamber": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionchamberelementtypesump": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionchamberelementtypetrench": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionchamberelementtypevalvechamber": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionportcommon": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionportphistorycable": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionportphistoryduct": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionportphistorypipe": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionporttypecable": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionporttypeduct": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionporttypepipe": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionsystemcommon": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionsystemtypeelectrical": "ifcsharedbldgserviceelements",
|
||||
"pset_distributionsystemtypeventilation": "ifcsharedbldgserviceelements",
|
||||
"pset_doorcommon": "ifcsharedbldgelements",
|
||||
"pset_doorwindowglazingtype": "ifcsharedbldgelements",
|
||||
"pset_ductfittingoccurrence": "ifchvacdomain",
|
||||
"pset_ductfittingphistory": "ifchvacdomain",
|
||||
"pset_ductfittingtypecommon": "ifchvacdomain",
|
||||
"pset_ductsegmentoccurrence": "ifchvacdomain",
|
||||
"pset_ductsegmentphistory": "ifchvacdomain",
|
||||
"pset_ductsegmenttypecommon": "ifchvacdomain",
|
||||
"pset_ductsilencerphistory": "ifchvacdomain",
|
||||
"pset_ductsilencertypecommon": "ifchvacdomain",
|
||||
"pset_electricaldevicecommon": "ifcelectricaldomain",
|
||||
"pset_electricappliancephistory": "ifcelectricaldomain",
|
||||
"pset_electricappliancetypecommon": "ifcelectricaldomain",
|
||||
"pset_electricappliancetypedishwasher": "ifcelectricaldomain",
|
||||
"pset_electricappliancetypeelectriccooker": "ifcelectricaldomain",
|
||||
"pset_electricdistributionboardoccurrence": "ifcelectricaldomain",
|
||||
"pset_electricdistributionboardtypecommon": "ifcelectricaldomain",
|
||||
"pset_electricflowstoragedevicephistory": "ifcelectricaldomain",
|
||||
"pset_electricflowstoragedevicetypecommon": "ifcelectricaldomain",
|
||||
"pset_electricgeneratortypecommon": "ifcelectricaldomain",
|
||||
"pset_electricmotortypecommon": "ifcelectricaldomain",
|
||||
"pset_electrictimecontroltypecommon": "ifcelectricaldomain",
|
||||
"pset_elementassemblycommon": "ifcproductextension",
|
||||
"pset_elementcomponentcommon": "ifcsharedcomponentelements",
|
||||
"pset_enginetypecommon": "ifchvacdomain",
|
||||
"pset_environmentalimpactindicators": "ifcproductextension",
|
||||
"pset_environmentalimpactvalues": "ifcproductextension",
|
||||
"pset_evaporativecoolerphistory": "ifchvacdomain",
|
||||
"pset_evaporativecoolertypecommon": "ifchvacdomain",
|
||||
"pset_evaporatorphistory": "ifchvacdomain",
|
||||
"pset_evaporatortypecommon": "ifchvacdomain",
|
||||
"pset_fancentrifugal": "ifchvacdomain",
|
||||
"pset_fanoccurrence": "ifchvacdomain",
|
||||
"pset_fanphistory": "ifchvacdomain",
|
||||
"pset_fantypecommon": "ifchvacdomain",
|
||||
"pset_fastenerweld": "ifcsharedcomponentelements",
|
||||
"pset_filterphistory": "ifchvacdomain",
|
||||
"pset_filtertypeairparticlefilter": "ifchvacdomain",
|
||||
"pset_filtertypecommon": "ifchvacdomain",
|
||||
"pset_filtertypecompressedairfilter": "ifchvacdomain",
|
||||
"pset_filtertypewaterfilter": "ifchvacdomain",
|
||||
"pset_firesuppressionterminaltypebreechinginlet": "ifcplumbingfireprotectiondomain",
|
||||
"pset_firesuppressionterminaltypecommon": "ifcplumbingfireprotectiondomain",
|
||||
"pset_firesuppressionterminaltypefirehydrant": "ifcplumbingfireprotectiondomain",
|
||||
"pset_firesuppressionterminaltypehosereel": "ifcplumbingfireprotectiondomain",
|
||||
"pset_firesuppressionterminaltypesprinkler": "ifcplumbingfireprotectiondomain",
|
||||
"pset_flowinstrumentphistory": "ifcbuildingcontrolsdomain",
|
||||
"pset_flowinstrumenttypecommon": "ifcbuildingcontrolsdomain",
|
||||
"pset_flowinstrumenttypepressuregauge": "ifcbuildingcontrolsdomain",
|
||||
"pset_flowinstrumenttypethermometer": "ifcbuildingcontrolsdomain",
|
||||
"pset_flowmeteroccurrence": "ifchvacdomain",
|
||||
"pset_flowmetertypecommon": "ifchvacdomain",
|
||||
"pset_flowmetertypeenergymeter": "ifchvacdomain",
|
||||
"pset_flowmetertypegasmeter": "ifchvacdomain",
|
||||
"pset_flowmetertypeoilmeter": "ifchvacdomain",
|
||||
"pset_flowmetertypewatermeter": "ifchvacdomain",
|
||||
"pset_footingcommon": "ifcstructuralelementsdomain",
|
||||
"pset_furnituretypechair": "ifcsharedfacilitieselements",
|
||||
"pset_furnituretypecommon": "ifcsharedfacilitieselements",
|
||||
"pset_furnituretypedesk": "ifcsharedfacilitieselements",
|
||||
"pset_furnituretypefilecabinet": "ifcsharedfacilitieselements",
|
||||
"pset_furnituretypetable": "ifcsharedfacilitieselements",
|
||||
"pset_heatexchangertypecommon": "ifchvacdomain",
|
||||
"pset_heatexchangertypeplate": "ifchvacdomain",
|
||||
"pset_humidifierphistory": "ifchvacdomain",
|
||||
"pset_humidifiertypecommon": "ifchvacdomain",
|
||||
"pset_interceptortypecommon": "ifcplumbingfireprotectiondomain",
|
||||
"pset_junctionboxtypecommon": "ifcelectricaldomain",
|
||||
"pset_lamptypecommon": "ifcelectricaldomain",
|
||||
"pset_landregistration": "ifcproductextension",
|
||||
"pset_lightfixturetypecommon": "ifcelectricaldomain",
|
||||
"pset_lightfixturetypesecuritylighting": "ifcelectricaldomain",
|
||||
"pset_manufactureroccurrence": "ifcsharedfacilitieselements",
|
||||
"pset_manufacturertypeinformation": "ifcsharedfacilitieselements",
|
||||
"pset_materialcombustion": "ifcmaterialresource",
|
||||
"pset_materialcommon": "ifcmaterialresource",
|
||||
"pset_materialconcrete": "ifcmaterialresource",
|
||||
"pset_materialenergy": "ifcmaterialresource",
|
||||
"pset_materialfuel": "ifcmaterialresource",
|
||||
"pset_materialhygroscopic": "ifcmaterialresource",
|
||||
"pset_materialmechanical": "ifcmaterialresource",
|
||||
"pset_materialoptical": "ifcmaterialresource",
|
||||
"pset_materialsteel": "ifcmaterialresource",
|
||||
"pset_materialthermal": "ifcmaterialresource",
|
||||
"pset_materialwater": "ifcmaterialresource",
|
||||
"pset_materialwood": "ifcmaterialresource",
|
||||
"pset_materialwoodbasedbeam": "ifcmaterialresource",
|
||||
"pset_materialwoodbasedpanel": "ifcmaterialresource",
|
||||
"pset_mechanicalfasteneranchorbolt": "ifcsharedcomponentelements",
|
||||
"pset_mechanicalfastenerbolt": "ifcsharedcomponentelements",
|
||||
"pset_mechanicalfastenercommon": "ifcsharedcomponentelements",
|
||||
"pset_medicaldevicetypecommon": "ifchvacdomain",
|
||||
"pset_membercommon": "ifcsharedbldgelements",
|
||||
"pset_motorconnectiontypecommon": "ifcelectricaldomain",
|
||||
"pset_openingelementcommon": "ifcproductextension",
|
||||
"pset_outlettypecommon": "ifcelectricaldomain",
|
||||
"pset_outsidedesigncriteria": "ifcsharedbldgserviceelements",
|
||||
"pset_packinginstructions": "ifcsharedmgmtelements",
|
||||
"pset_permit": "ifcsharedmgmtelements",
|
||||
"pset_pilecommon": "ifcstructuralelementsdomain",
|
||||
"pset_pipeconnectionflanged": "ifchvacdomain",
|
||||
"pset_pipefittingoccurrence": "ifchvacdomain",
|
||||
"pset_pipefittingphistory": "ifchvacdomain",
|
||||
"pset_pipefittingtypebend": "ifchvacdomain",
|
||||
"pset_pipefittingtypecommon": "ifchvacdomain",
|
||||
"pset_pipefittingtypejunction": "ifchvacdomain",
|
||||
"pset_pipesegmentoccurrence": "ifchvacdomain",
|
||||
"pset_pipesegmentphistory": "ifchvacdomain",
|
||||
"pset_pipesegmenttypecommon": "ifchvacdomain",
|
||||
"pset_pipesegmenttypeculvert": "ifchvacdomain",
|
||||
"pset_pipesegmenttypegutter": "ifchvacdomain",
|
||||
"pset_platecommon": "ifcsharedbldgelements",
|
||||
"pset_precastconcreteelementfabrication": "ifcstructuralelementsdomain",
|
||||
"pset_precastconcreteelementgeneral": "ifcstructuralelementsdomain",
|
||||
"pset_precastslab": "ifcstructuralelementsdomain",
|
||||
"pset_profilearbitrarydoublet": "ifcprofileresource",
|
||||
"pset_profilearbitraryhollowcore": "ifcprofileresource",
|
||||
"pset_profilemechanical": "ifcprofileresource",
|
||||
"pset_projectorderchangeorder": "ifcsharedmgmtelements",
|
||||
"pset_projectordermaintenanceworkorder": "ifcsharedmgmtelements",
|
||||
"pset_projectordermoveorder": "ifcsharedmgmtelements",
|
||||
"pset_projectorderpurchaseorder": "ifcsharedmgmtelements",
|
||||
"pset_projectorderworkorder": "ifcsharedmgmtelements",
|
||||
"pset_propertyagreement": "ifcsharedfacilitieselements",
|
||||
"pset_protectivedevicebreakeruniti2tcurve": "ifcelectricaldomain",
|
||||
"pset_protectivedevicebreakeruniti2tfusecurve": "ifcelectricaldomain",
|
||||
"pset_protectivedevicebreakerunitipicurve": "ifcelectricaldomain",
|
||||
"pset_protectivedevicebreakerunittypemcb": "ifcelectricaldomain",
|
||||
"pset_protectivedevicebreakerunittypemotorprotection": "ifcelectricaldomain",
|
||||
"pset_protectivedeviceoccurrence": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingcurve": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingfunctiongcurve": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingfunctionicurve": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingfunctionlcurve": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingfunctionscurve": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingunitcurrentadjustment": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingunittimeadjustment": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingunittypecommon": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingunittypeelectromagnetic": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingunittypeelectronic": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingunittyperesidualcurrent": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetrippingunittypethermal": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetypecircuitbreaker": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetypecommon": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetypeearthleakagecircuitbreaker": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetypefusedisconnector": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetyperesidualcurrentcircuitbreaker": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetyperesidualcurrentswitch": "ifcelectricaldomain",
|
||||
"pset_protectivedevicetypevaristor": "ifcelectricaldomain",
|
||||
"pset_pumpoccurrence": "ifchvacdomain",
|
||||
"pset_pumpphistory": "ifchvacdomain",
|
||||
"pset_pumptypecommon": "ifchvacdomain",
|
||||
"pset_railingcommon": "ifcsharedbldgelements",
|
||||
"pset_rampcommon": "ifcsharedbldgelements",
|
||||
"pset_rampflightcommon": "ifcsharedbldgelements",
|
||||
"pset_reinforcementbarcountofindependentfooting": "ifcstructuralelementsdomain",
|
||||
"pset_reinforcementbarpitchofbeam": "ifcstructuralelementsdomain",
|
||||
"pset_reinforcementbarpitchofcolumn": "ifcstructuralelementsdomain",
|
||||
"pset_reinforcementbarpitchofcontinuousfooting": "ifcstructuralelementsdomain",
|
||||
"pset_reinforcementbarpitchofslab": "ifcstructuralelementsdomain",
|
||||
"pset_reinforcementbarpitchofwall": "ifcstructuralelementsdomain",
|
||||
"pset_reinforcingbarcommon": "ifcstructuralelementsdomain",
|
||||
"pset_reinforcingmeshcommon": "ifcstructuralelementsdomain",
|
||||
"pset_risk": "ifcsharedfacilitieselements",
|
||||
"pset_roofcommon": "ifcsharedbldgelements",
|
||||
"pset_sanitaryterminaltypebath": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sanitaryterminaltypebidet": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sanitaryterminaltypecistern": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sanitaryterminaltypecommon": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sanitaryterminaltypesanitaryfountain": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sanitaryterminaltypeshower": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sanitaryterminaltypesink": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sanitaryterminaltypetoiletpan": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sanitaryterminaltypeurinal": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sanitaryterminaltypewashhandbasin": "ifcplumbingfireprotectiondomain",
|
||||
"pset_sensorphistory": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypeco2sensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypecommon": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypeconductancesensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypecontactsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypefiresensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypeflowsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypefrostsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypegassensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypeheatsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypehumiditysensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypeidentifiersensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypeionconcentrationsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypelevelsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypelightsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypemoisturesensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypemovementsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypephsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypepressuresensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortyperadiationsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortyperadioactivitysensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypesmokesensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypesoundsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypetemperaturesensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_sensortypewindsensor": "ifcbuildingcontrolsdomain",
|
||||
"pset_servicelife": "ifcsharedfacilitieselements",
|
||||
"pset_servicelifefactors": "ifcsharedfacilitieselements",
|
||||
"pset_shadingdevicecommon": "ifcsharedbldgelements",
|
||||
"pset_shadingdevicephistory": "ifchvacdomain",
|
||||
"pset_sitecommon": "ifcproductextension",
|
||||
"pset_slabcommon": "ifcsharedbldgelements",
|
||||
"pset_solardevicetypecommon": "ifcelectricaldomain",
|
||||
"pset_soundattenuation": "ifcsharedbldgserviceelements",
|
||||
"pset_soundgeneration": "ifcsharedbldgserviceelements",
|
||||
"pset_spacecommon": "ifcproductextension",
|
||||
"pset_spacecoveringrequirements": "ifcproductextension",
|
||||
"pset_spacefiresafetyrequirements": "ifcproductextension",
|
||||
"pset_spaceheaterphistory": "ifchvacdomain",
|
||||
"pset_spaceheatertypecommon": "ifchvacdomain",
|
||||
"pset_spaceheatertypeconvector": "ifchvacdomain",
|
||||
"pset_spaceheatertyperadiator": "ifchvacdomain",
|
||||
"pset_spacelightingrequirements": "ifcproductextension",
|
||||
"pset_spaceoccupancyrequirements": "ifcproductextension",
|
||||
"pset_spaceparking": "ifcproductextension",
|
||||
"pset_spacethermaldesign": "ifcsharedbldgserviceelements",
|
||||
"pset_spacethermalload": "ifcsharedbldgserviceelements",
|
||||
"pset_spacethermalloadphistory": "ifcsharedbldgserviceelements",
|
||||
"pset_spacethermalphistory": "ifchvacdomain",
|
||||
"pset_spacethermalrequirements": "ifcproductextension",
|
||||
"pset_spatialzonecommon": "ifcproductextension",
|
||||
"pset_stackterminaltypecommon": "ifcplumbingfireprotectiondomain",
|
||||
"pset_staircommon": "ifcsharedbldgelements",
|
||||
"pset_stairflightcommon": "ifcsharedbldgelements",
|
||||
"pset_structuralsurfacemembervaryingthickness": "ifcstructuralanalysisdomain",
|
||||
"pset_switchingdevicetypecommon": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypecontactor": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypedimmerswitch": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypeemergencystop": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypekeypad": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypemomentaryswitch": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypephistory": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypeselectorswitch": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypestarter": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypeswitchdisconnector": "ifcelectricaldomain",
|
||||
"pset_switchingdevicetypetoggleswitch": "ifcelectricaldomain",
|
||||
"pset_systemfurnitureelementtypecommon": "ifcsharedfacilitieselements",
|
||||
"pset_systemfurnitureelementtypepanel": "ifcsharedfacilitieselements",
|
||||
"pset_systemfurnitureelementtypeworksurface": "ifcsharedfacilitieselements",
|
||||
"pset_tankoccurrence": "ifchvacdomain",
|
||||
"pset_tankphistory": "ifchvacdomain",
|
||||
"pset_tanktypecommon": "ifchvacdomain",
|
||||
"pset_tanktypeexpansion": "ifchvacdomain",
|
||||
"pset_tanktypepreformed": "ifchvacdomain",
|
||||
"pset_tanktypepressurevessel": "ifchvacdomain",
|
||||
"pset_tanktypesectional": "ifchvacdomain",
|
||||
"pset_tendonanchorcommon": "ifcstructuralelementsdomain",
|
||||
"pset_tendoncommon": "ifcstructuralelementsdomain",
|
||||
"pset_thermalloadaggregate": "ifcsharedbldgserviceelements",
|
||||
"pset_thermalloaddesigncriteria": "ifcsharedbldgserviceelements",
|
||||
"pset_transformertypecommon": "ifcelectricaldomain",
|
||||
"pset_transportelementcommon": "ifcproductextension",
|
||||
"pset_transportelementelevator": "ifcproductextension",
|
||||
"pset_tubebundletypecommon": "ifchvacdomain",
|
||||
"pset_tubebundletypefinned": "ifchvacdomain",
|
||||
"pset_unitarycontrolelementphistory": "ifcbuildingcontrolsdomain",
|
||||
"pset_unitarycontrolelementtypecommon": "ifcbuildingcontrolsdomain",
|
||||
"pset_unitarycontrolelementtypeindicatorpanel": "ifcbuildingcontrolsdomain",
|
||||
"pset_unitarycontrolelementtypethermostat": "ifcbuildingcontrolsdomain",
|
||||
"pset_unitaryequipmenttypeairconditioningunit": "ifchvacdomain",
|
||||
"pset_unitaryequipmenttypeairhandler": "ifchvacdomain",
|
||||
"pset_unitaryequipmenttypecommon": "ifchvacdomain",
|
||||
"pset_utilityconsumptionphistory": "ifcsharedbldgserviceelements",
|
||||
"pset_valvephistory": "ifchvacdomain",
|
||||
"pset_valvetypeairrelease": "ifchvacdomain",
|
||||
"pset_valvetypecommon": "ifchvacdomain",
|
||||
"pset_valvetypedrawoffcock": "ifchvacdomain",
|
||||
"pset_valvetypefaucet": "ifchvacdomain",
|
||||
"pset_valvetypeflushing": "ifchvacdomain",
|
||||
"pset_valvetypegastap": "ifchvacdomain",
|
||||
"pset_valvetypeisolating": "ifchvacdomain",
|
||||
"pset_valvetypemixing": "ifchvacdomain",
|
||||
"pset_valvetypepressurereducing": "ifchvacdomain",
|
||||
"pset_valvetypepressurerelief": "ifchvacdomain",
|
||||
"pset_vibrationisolatortypecommon": "ifchvacdomain",
|
||||
"pset_wallcommon": "ifcsharedbldgelements",
|
||||
"pset_warranty": "ifcsharedfacilitieselements",
|
||||
"pset_wasteterminaltypecommon": "ifcplumbingfireprotectiondomain",
|
||||
"pset_wasteterminaltypefloortrap": "ifcplumbingfireprotectiondomain",
|
||||
"pset_wasteterminaltypefloorwaste": "ifcplumbingfireprotectiondomain",
|
||||
"pset_wasteterminaltypegullysump": "ifcplumbingfireprotectiondomain",
|
||||
"pset_wasteterminaltypegullytrap": "ifcplumbingfireprotectiondomain",
|
||||
"pset_wasteterminaltyperoofdrain": "ifcplumbingfireprotectiondomain",
|
||||
"pset_wasteterminaltypewastedisposalunit": "ifcplumbingfireprotectiondomain",
|
||||
"pset_wasteterminaltypewastetrap": "ifcplumbingfireprotectiondomain",
|
||||
"pset_windowcommon": "ifcsharedbldgelements",
|
||||
"pset_workcontrolcommon": "ifcprocessextension",
|
||||
"pset_zonecommon": "ifcproductextension",
|
||||
"qto_actuatorbasequantities": "ifcbuildingcontrolsdomain",
|
||||
"qto_airterminalbasequantities": "ifchvacdomain",
|
||||
"qto_airterminalboxtypebasequantities": "ifchvacdomain",
|
||||
"qto_airtoairheatrecoverybasequantities": "ifchvacdomain",
|
||||
"qto_alarmbasequantities": "ifcbuildingcontrolsdomain",
|
||||
"qto_audiovisualappliancebasequantities": "ifcelectricaldomain",
|
||||
"qto_beambasequantities": "ifcsharedbldgelements",
|
||||
"qto_boilerbasequantities": "ifchvacdomain",
|
||||
"qto_buildingbasequantities": "ifcproductextension",
|
||||
"qto_buildingelementproxyquantities": "ifcsharedbldgelements",
|
||||
"qto_buildingstoreybasequantities": "ifcproductextension",
|
||||
"qto_burnerbasequantities": "ifchvacdomain",
|
||||
"qto_cablecarrierfittingbasequantities": "ifcelectricaldomain",
|
||||
"qto_cablecarriersegmentbasequantities": "ifcelectricaldomain",
|
||||
"qto_cablefittingbasequantities": "ifcelectricaldomain",
|
||||
"qto_cablesegmentbasequantities": "ifcelectricaldomain",
|
||||
"qto_chillerbasequantities": "ifchvacdomain",
|
||||
"qto_chimneybasequantities": "ifcsharedbldgelements",
|
||||
"qto_coilbasequantities": "ifchvacdomain",
|
||||
"qto_columnbasequantities": "ifcsharedbldgelements",
|
||||
"qto_communicationsappliancebasequantities": "ifcelectricaldomain",
|
||||
"qto_compressorbasequantities": "ifchvacdomain",
|
||||
"qto_condenserbasequantities": "ifchvacdomain",
|
||||
"qto_constructionequipmentresourcebasequantities": "ifcconstructionmgmtdomain",
|
||||
"qto_constructionmaterialresourcebasequantities": "ifcconstructionmgmtdomain",
|
||||
"qto_controllerbasequantities": "ifcbuildingcontrolsdomain",
|
||||
"qto_cooledbeambasequantities": "ifchvacdomain",
|
||||
"qto_coolingtowerbasequantities": "ifchvacdomain",
|
||||
"qto_coveringbasequantities": "ifcsharedbldgelements",
|
||||
"qto_curtainwallquantities": "ifcsharedbldgelements",
|
||||
"qto_damperbasequantities": "ifchvacdomain",
|
||||
"qto_distributionchamberelementbasequantities": "ifcsharedbldgserviceelements",
|
||||
"qto_doorbasequantities": "ifcsharedbldgelements",
|
||||
"qto_ductfittingbasequantities": "ifchvacdomain",
|
||||
"qto_ductsegmentbasequantities": "ifchvacdomain",
|
||||
"qto_ductsilencerbasequantities": "ifchvacdomain",
|
||||
"qto_electricappliancebasequantities": "ifcelectricaldomain",
|
||||
"qto_electricdistributionboardbasequantities": "ifcelectricaldomain",
|
||||
"qto_electricflowstoragedevicebasequantities": "ifcelectricaldomain",
|
||||
"qto_electricgeneratorbasequantities": "ifcelectricaldomain",
|
||||
"qto_electricmotorbasequantities": "ifcelectricaldomain",
|
||||
"qto_electrictimecontrolbasequantities": "ifcelectricaldomain",
|
||||
"qto_evaporativecoolerbasequantities": "ifchvacdomain",
|
||||
"qto_evaporatorbasequantities": "ifchvacdomain",
|
||||
"qto_fanbasequantities": "ifchvacdomain",
|
||||
"qto_filterbasequantities": "ifchvacdomain",
|
||||
"qto_firesuppressionterminalbasequantities": "ifcplumbingfireprotectiondomain",
|
||||
"qto_flowinstrumentbasequantities": "ifcbuildingcontrolsdomain",
|
||||
"qto_flowmeterbasequantities": "ifchvacdomain",
|
||||
"qto_footingbasequantities": "ifcstructuralelementsdomain",
|
||||
"qto_heatexchangerbasequantities": "ifchvacdomain",
|
||||
"qto_humidifierbasequantities": "ifchvacdomain",
|
||||
"qto_interceptorbasequantities": "ifcplumbingfireprotectiondomain",
|
||||
"qto_junctionboxbasequantities": "ifcelectricaldomain",
|
||||
"qto_laborresourcebasequantities": "ifcconstructionmgmtdomain",
|
||||
"qto_lampbasequantities": "ifcelectricaldomain",
|
||||
"qto_lightfixturebasequantities": "ifcelectricaldomain",
|
||||
"qto_memberbasequantities": "ifcsharedbldgelements",
|
||||
"qto_motorconnectionbasequantities": "ifcelectricaldomain",
|
||||
"qto_openingelementbasequantities": "ifcproductextension",
|
||||
"qto_outletbasequantities": "ifcelectricaldomain",
|
||||
"qto_pilebasequantities": "ifcstructuralelementsdomain",
|
||||
"qto_pipefittingbasequantities": "ifchvacdomain",
|
||||
"qto_pipesegmentbasequantities": "ifchvacdomain",
|
||||
"qto_platebasequantities": "ifcsharedbldgelements",
|
||||
"qto_projectionelementbasequantities": "ifcproductextension",
|
||||
"qto_protectivedevicebasequantities": "ifcelectricaldomain",
|
||||
"qto_protectivedevicetrippingunitbasequantities": "ifcelectricaldomain",
|
||||
"qto_pumpbasequantities": "ifchvacdomain",
|
||||
"qto_railingbasequantities": "ifcsharedbldgelements",
|
||||
"qto_rampflightbasequantities": "ifcsharedbldgelements",
|
||||
"qto_reinforcingelementbasequantities": "ifcstructuralelementsdomain",
|
||||
"qto_roofbasequantities": "ifcsharedbldgelements",
|
||||
"qto_sanitaryterminalbasequantities": "ifcplumbingfireprotectiondomain",
|
||||
"qto_sensorbasequantities": "ifcbuildingcontrolsdomain",
|
||||
"qto_sitebasequantities": "ifcproductextension",
|
||||
"qto_slabbasequantities": "ifcsharedbldgelements",
|
||||
"qto_solardevicebasequantities": "ifcelectricaldomain",
|
||||
"qto_spacebasequantities": "ifcproductextension",
|
||||
"qto_spaceheaterbasequantities": "ifchvacdomain",
|
||||
"qto_stackterminalbasequantities": "ifcplumbingfireprotectiondomain",
|
||||
"qto_stairflightbasequantities": "ifcsharedbldgelements",
|
||||
"qto_switchingdevicebasequantities": "ifcelectricaldomain",
|
||||
"qto_tankbasequantities": "ifchvacdomain",
|
||||
"qto_transformerbasequantities": "ifcelectricaldomain",
|
||||
"qto_tubebundlebasequantities": "ifchvacdomain",
|
||||
"qto_unitarycontrolelementbasequantities": "ifcbuildingcontrolsdomain",
|
||||
"qto_unitaryequipmentbasequantities": "ifchvacdomain",
|
||||
"qto_valvebasequantities": "ifchvacdomain",
|
||||
"qto_vibrationisolatorbasequantities": "ifchvacdomain",
|
||||
"qto_wallbasequantities": "ifcsharedbldgelements",
|
||||
"qto_wasteterminalbasequantities": "ifcplumbingfireprotectiondomain",
|
||||
"qto_windowbasequantities": "ifcsharedbldgelements"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,212 @@
|
||||
{
|
||||
"IfcActuator": [
|
||||
{
|
||||
"name": "Electric Strike",
|
||||
"predefined_type": "NOTDEFINED"
|
||||
}
|
||||
],
|
||||
"IfcAirTerminal": [
|
||||
{
|
||||
"name": "Commercial Kitchen Hood"
|
||||
}
|
||||
],
|
||||
"IfcAirTerminalType": [
|
||||
{
|
||||
"name": "Commercial Kitchen Hood"
|
||||
}
|
||||
],
|
||||
"IfcAirTerminalBox": [
|
||||
{
|
||||
"name": "VAV Box"
|
||||
}
|
||||
],
|
||||
"IfcCableSegment": [
|
||||
{
|
||||
"name": "Lighting Rod"
|
||||
}
|
||||
],
|
||||
"IfcCovering": [
|
||||
{
|
||||
"name": "Flashing"
|
||||
},
|
||||
{
|
||||
"name": "Capping"
|
||||
},
|
||||
{
|
||||
"name": "Screed",
|
||||
"predefined_type": "FLOORING"
|
||||
},
|
||||
{
|
||||
"name": "Screed",
|
||||
"predefined_type": "TOPPING"
|
||||
}
|
||||
],
|
||||
"IfcElectricDistributionBoard": [
|
||||
{
|
||||
"name": "Circuit Breaker Panel",
|
||||
"predefined_type": "DISTRIBUTIONBOARD"
|
||||
},
|
||||
{
|
||||
"name": "Electrical Panel",
|
||||
"predefined_type": "DISTRIBUTIONBOARD"
|
||||
}
|
||||
],
|
||||
"IfcElectricDistributionBoardType": [
|
||||
{
|
||||
"name": "Circuit Breaker Panel",
|
||||
"predefined_type": "DISTRIBUTIONBOARD"
|
||||
},
|
||||
{
|
||||
"name": "Electrical Panel",
|
||||
"predefined_type": "DISTRIBUTIONBOARD"
|
||||
}
|
||||
],
|
||||
"IfcFireSuppressionTerminal": [
|
||||
{
|
||||
"name": "Fire Extinguisher"
|
||||
}
|
||||
],
|
||||
"IfcFireSuppressionTerminalType": [
|
||||
{
|
||||
"name": "Fire Extinguisher"
|
||||
}
|
||||
],
|
||||
"IfcFurniture": [
|
||||
{
|
||||
"name": "Casework"
|
||||
},
|
||||
{
|
||||
"name": "Millwork"
|
||||
},
|
||||
{
|
||||
"name": "Signage"
|
||||
}
|
||||
],
|
||||
"IfcFurnitureType": [
|
||||
{
|
||||
"name": "Casework"
|
||||
},
|
||||
{
|
||||
"name": "Millwork"
|
||||
},
|
||||
{
|
||||
"name": "Signage"
|
||||
}
|
||||
],
|
||||
"IfcGeographicElement": [
|
||||
{
|
||||
"name": "Trees"
|
||||
},
|
||||
{
|
||||
"name": "Plants"
|
||||
},
|
||||
{
|
||||
"name": "Shrubs"
|
||||
}
|
||||
],
|
||||
"IfcGeographicElementType": [
|
||||
{
|
||||
"name": "Trees"
|
||||
},
|
||||
{
|
||||
"name": "Plants"
|
||||
},
|
||||
{
|
||||
"name": "Shrubs"
|
||||
}
|
||||
],
|
||||
"IfcPlate": [
|
||||
{
|
||||
"name": "Glazing"
|
||||
},
|
||||
{
|
||||
"name": "Glass"
|
||||
},
|
||||
{
|
||||
"name": "Pane"
|
||||
}
|
||||
],
|
||||
"IfcSensor": [
|
||||
{
|
||||
"name": "Card Reader"
|
||||
},
|
||||
{
|
||||
"name": "Fob Reader"
|
||||
}
|
||||
],
|
||||
"IfcSlab": [
|
||||
{
|
||||
"name": "Hob"
|
||||
}
|
||||
],
|
||||
"IfcSwitchingDevice": [
|
||||
{
|
||||
"name": "Reed Switch"
|
||||
},
|
||||
{
|
||||
"name": "Electric Isolating Switch"
|
||||
}
|
||||
],
|
||||
"IfcUnitaryEquipment": [
|
||||
{
|
||||
"name": "Fan Coil Unit"
|
||||
},
|
||||
{
|
||||
"name": "Roof Top Unit (RTU)",
|
||||
"predefined_type": "ROOFTOPUNIT"
|
||||
},
|
||||
{
|
||||
"name": "Furnace"
|
||||
},
|
||||
{
|
||||
"name": "Central air conditioner (split or package)",
|
||||
"predefined_type": "AIRCONDITIONINGUNIT"
|
||||
},
|
||||
{
|
||||
"name": "Mini-split system",
|
||||
"predefined_type": "SPLITSYSTEM"
|
||||
}
|
||||
],
|
||||
"IfcUnitaryEquipmentType": [
|
||||
{
|
||||
"name": "Fan Coil Unit"
|
||||
},
|
||||
{
|
||||
"name": "Roof Top Unit (RTU)",
|
||||
"predefined_type": "ROOFTOPUNIT"
|
||||
},
|
||||
{
|
||||
"name": "Furnace"
|
||||
},
|
||||
{
|
||||
"name": "Central air conditioner (split or package)",
|
||||
"predefined_type": "AIRCONDITIONINGUNIT"
|
||||
},
|
||||
{
|
||||
"name": "Mini-split system",
|
||||
"predefined_type": "SPLITSYSTEM"
|
||||
}
|
||||
],
|
||||
"IfcWall": [
|
||||
{
|
||||
"name": "Glazing"
|
||||
},
|
||||
{
|
||||
"name": "Glass"
|
||||
},
|
||||
{
|
||||
"name": "Pane"
|
||||
}
|
||||
],
|
||||
"IfcWindow": [
|
||||
{
|
||||
"name": "Glazing"
|
||||
},
|
||||
{
|
||||
"name": "Glass"
|
||||
},
|
||||
{
|
||||
"name": "Pane"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,274 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Thomas Krijnen <thomas@aecgeeks.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/>.
|
||||
|
||||
"""Script to ensure ifcopenshell_wrapper.pyi and ifcopenshell_wrapper.py work in sync.
|
||||
|
||||
Things we do check:
|
||||
- all symbols from the wrapper present in the stub and vice versa
|
||||
- functions and methods signatures
|
||||
- read-only and settable properties, staticmethods
|
||||
- class hierarchy
|
||||
"""
|
||||
|
||||
import ast
|
||||
import difflib
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
from typing_extensions import assert_never
|
||||
|
||||
|
||||
def format_diff(lines: list[str]) -> None:
|
||||
RED = "\033[91m"
|
||||
GREEN = "\033[92m"
|
||||
CYAN = "\033[96m"
|
||||
RESET = "\033[0m"
|
||||
for line in lines:
|
||||
if line.startswith("+") and not line.startswith("+++"):
|
||||
print(f"{GREEN}{line}{RESET}")
|
||||
elif line.startswith("-") and not line.startswith("---"):
|
||||
print(f"{RED}{line}{RESET}")
|
||||
elif line.startswith("@@"):
|
||||
print(f"{CYAN}{line}{RESET}")
|
||||
else:
|
||||
print(line)
|
||||
|
||||
|
||||
SubnameType = Union[str, tuple[str, str]]
|
||||
|
||||
|
||||
def get_function_node_name(node: ast.FunctionDef) -> Union[SubnameType, None]:
|
||||
"""
|
||||
:return: Function node name as ``SubnameType`` or ``None``, if function wasn't processed and can be skipped.
|
||||
"""
|
||||
node_name = node.name
|
||||
is_init = node_name == "__init__"
|
||||
|
||||
if node_name.startswith("_") and node_name not in ("_is",) and not is_init:
|
||||
return None
|
||||
arg_nodes = node.args.args
|
||||
defaults = [None] * (len(arg_nodes) - len(node.args.defaults)) + node.args.defaults
|
||||
args: list[str] = []
|
||||
for arg, default in zip(arg_nodes, defaults):
|
||||
if default is None:
|
||||
args.append(arg.arg)
|
||||
else:
|
||||
args.append(f"{arg.arg}={ast.unparse(default)}")
|
||||
|
||||
if arg := node.args.vararg:
|
||||
args.append(f"*{arg.arg}")
|
||||
|
||||
if arg := node.args.kwarg:
|
||||
args.append(f"**{arg.arg}")
|
||||
|
||||
# Skip non-informative constructors.
|
||||
if is_init and args == ["self"]:
|
||||
return None
|
||||
|
||||
node_name = f"def {node.name}"
|
||||
node_name = f"{node_name}({', '.join(args)}): ..."
|
||||
|
||||
decorators = tuple(f"@{d.id}" for d in node.decorator_list if isinstance(d, ast.Name))
|
||||
if len(decorators) == 1:
|
||||
return decorators + (node_name,)
|
||||
return node_name
|
||||
|
||||
|
||||
def get_names_tree_lines(tree: ast.Module) -> list[str]:
|
||||
# Get class tree.
|
||||
names_tree: dict[str, set[SubnameType]] = {}
|
||||
for node in tree.body:
|
||||
subnames: set[SubnameType] = set()
|
||||
node_name = None
|
||||
if isinstance(node, ast.ClassDef):
|
||||
# Skip `object_` as it's just a reference to `object`,
|
||||
# which is implied by default.
|
||||
bases = [b.id for b in node.bases if isinstance(b, ast.Name) and b.id not in ("_object", "object")]
|
||||
bases_str = f"({', '.join(bases)})" if bases else ""
|
||||
node_name = f"class {node.name}{bases_str}:"
|
||||
|
||||
for subnode in node.body:
|
||||
subname = None
|
||||
|
||||
if isinstance(subnode, ast.AnnAssign):
|
||||
target = subnode.target
|
||||
assert isinstance(target, ast.Name)
|
||||
subname = target.id
|
||||
|
||||
elif isinstance(subnode, ast.FunctionDef):
|
||||
subname = get_function_node_name(subnode)
|
||||
|
||||
elif isinstance(subnode, ast.Assign):
|
||||
targets = subnode.targets
|
||||
if not len(targets) == 1 or not isinstance(target := targets[0], ast.Name):
|
||||
continue
|
||||
subname_ = target.id
|
||||
|
||||
if subname_.startswith(("_", "thisown")):
|
||||
continue
|
||||
|
||||
value = subnode.value
|
||||
if not isinstance(value, ast.Call):
|
||||
subname = subname_
|
||||
else:
|
||||
# Catching wrappers like:
|
||||
# - `matrix = property(matrix_getter)`
|
||||
# - `matrix = property(matrix_getter, matrix_setter)`
|
||||
# - `operation_str = staticmethod(operation_str)`
|
||||
func = value.func
|
||||
if not isinstance(func, ast.Name) or ((func_id := func.id) not in ("property", "staticmethod")):
|
||||
continue
|
||||
args = [arg.id for arg in value.args if isinstance(arg, ast.Name)]
|
||||
len_args = len(args)
|
||||
if len_args in (1, 2):
|
||||
|
||||
def find_method_by_name(name: str) -> Union[str, None]:
|
||||
function_def = f"def {name}("
|
||||
return next(
|
||||
(
|
||||
func_
|
||||
for func_ in subnames
|
||||
if isinstance(func_, str) and func_.startswith(function_def)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
# Use `set` for cases like `description = property(description, description)`.
|
||||
wrapped_function = None
|
||||
for arg in set(args):
|
||||
assert (wrapped_function := find_method_by_name(arg))
|
||||
subnames.remove(wrapped_function)
|
||||
|
||||
# TODO: sort it out in wrapper.py
|
||||
# There's one annoying case in Element.product
|
||||
# when property is overriding existing function, without using it.
|
||||
# We should probably just exclude that function from the wrapper.
|
||||
overridden_name = find_method_by_name(subname_)
|
||||
if overridden_name:
|
||||
subnames.remove(overridden_name)
|
||||
|
||||
if len_args == 2:
|
||||
# Has both getter and setter, can be defined as a simple attribute.
|
||||
subname = subname_
|
||||
elif len_args == 1:
|
||||
if func_id == "property":
|
||||
# Has just getter, read-only, need to define it using a wrapper.
|
||||
subname = (f"@{func_id}", f"def {subname_}(self): ...")
|
||||
elif func_id == "staticmethod":
|
||||
assert wrapped_function is not None
|
||||
subname = (f"@{func_id}", f"def {subname_}({wrapped_function.split('(')[1]}")
|
||||
else:
|
||||
assert_never(func_id)
|
||||
else:
|
||||
assert_never(len_args)
|
||||
else:
|
||||
attr_args = [
|
||||
arg
|
||||
for arg in value.args
|
||||
if isinstance(arg, ast.Attribute)
|
||||
and isinstance(arg.value, ast.Name)
|
||||
and arg.value.id == "_ifcopenshell_wrapper"
|
||||
]
|
||||
assert len(attr_args) == 2
|
||||
subname = subname_
|
||||
|
||||
if subname is not None:
|
||||
subnames.add(subname)
|
||||
if not subnames:
|
||||
node_name += " ..."
|
||||
|
||||
elif isinstance(node, ast.FunctionDef):
|
||||
if node.name.startswith("_"):
|
||||
continue
|
||||
node_name = get_function_node_name(node)
|
||||
assert isinstance(node_name, str)
|
||||
|
||||
elif isinstance(node, ast.Assign):
|
||||
targets = node.targets
|
||||
if not len(targets) == 1 or not isinstance(target := targets[0], ast.Name):
|
||||
continue
|
||||
node_name = target.id
|
||||
|
||||
elif isinstance(node, ast.AnnAssign):
|
||||
target = node.target
|
||||
assert isinstance(target, ast.Name)
|
||||
node_name = target.id
|
||||
|
||||
if node_name is not None:
|
||||
names_tree[node_name] = subnames
|
||||
|
||||
# Convert names tree to lines.
|
||||
lines: list[str] = []
|
||||
indent = " " * 4
|
||||
|
||||
def subname_sort(subname: SubnameType) -> str:
|
||||
if isinstance(subname, str):
|
||||
if subname.startswith("def "):
|
||||
return subname.split("(")[0].removeprefix("def ")
|
||||
return subname
|
||||
return subname_sort(subname[1])
|
||||
|
||||
for name, subnames in sorted(names_tree.items(), key=lambda x: x[0]):
|
||||
lines.append(name)
|
||||
for subname in sorted(subnames, key=subname_sort):
|
||||
subitems = (subname,) if isinstance(subname, str) else subname
|
||||
for item in subitems:
|
||||
lines.append(f"{indent}{item}")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def main() -> None:
|
||||
package = Path(__file__).parent.parent.parent
|
||||
stub_path = package / "ifcopenshell_wrapper.pyi"
|
||||
wrapper_path = package / "ifcopenshell_wrapper.py"
|
||||
|
||||
# Parse files
|
||||
stub_tree = ast.parse(stub_path.read_text())
|
||||
wrapper_tree = ast.parse(wrapper_path.read_text())
|
||||
|
||||
# Extract class names
|
||||
stub_classes = get_names_tree_lines(stub_tree)
|
||||
wrapper_classes = get_names_tree_lines(wrapper_tree)
|
||||
|
||||
# Use difflib to create a unified diff of class names
|
||||
diff = difflib.unified_diff(
|
||||
stub_classes,
|
||||
wrapper_classes,
|
||||
fromfile="stub.pyi classes",
|
||||
tofile="wrapper.py classes",
|
||||
lineterm="",
|
||||
n=10,
|
||||
)
|
||||
diff = list(diff)
|
||||
|
||||
format_diff(diff)
|
||||
diff_no_header = diff[2:]
|
||||
added = len([l for l in diff_no_header if l.startswith("+")])
|
||||
removed = len([l for l in diff_no_header if l.startswith("-")])
|
||||
|
||||
if added or removed:
|
||||
print(f"Added lines: {added}")
|
||||
print(f"Removed lines: {removed}")
|
||||
raise Exception("Found discrepancies between stub and wrapper.")
|
||||
else:
|
||||
print(f"All good, no discrepancies between stub and wrapper. 🎉🎉")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,465 @@
|
||||
# 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 datetime
|
||||
from collections.abc import Generator
|
||||
from functools import cache
|
||||
from math import floor
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.element
|
||||
|
||||
DURATION_TYPE = Literal["ELAPSEDTIME", "WORKTIME", "NOTDEFINED"]
|
||||
RECURRENCE_TYPE = Literal[
|
||||
"BY_DAY_COUNT",
|
||||
"BY_WEEKDAY_COUNT",
|
||||
"DAILY",
|
||||
"MONTHLY_BY_DAY_OF_MONTH",
|
||||
"MONTHLY_BY_POSITION",
|
||||
"WEEKLY",
|
||||
"YEARLY_BY_DAY_OF_MONTH",
|
||||
"YEARLY_BY_POSITION",
|
||||
]
|
||||
|
||||
|
||||
def derive_date(
|
||||
task: ifcopenshell.entity_instance,
|
||||
attribute_name: str,
|
||||
date=None,
|
||||
is_earliest: bool = False,
|
||||
is_latest: bool = False,
|
||||
):
|
||||
"""
|
||||
|
||||
:param task: IfcTask.
|
||||
|
||||
"""
|
||||
if task.TaskTime:
|
||||
current_date = (
|
||||
ifcopenshell.util.date.ifc2datetime(getattr(task.TaskTime, attribute_name))
|
||||
if getattr(task.TaskTime, attribute_name)
|
||||
else ""
|
||||
)
|
||||
if current_date:
|
||||
return current_date
|
||||
for subtask in get_all_nested_tasks(task):
|
||||
current_date = derive_date(
|
||||
subtask,
|
||||
attribute_name,
|
||||
date=date,
|
||||
is_earliest=is_earliest,
|
||||
is_latest=is_latest,
|
||||
)
|
||||
if is_earliest:
|
||||
if current_date and (date is None or current_date < date):
|
||||
date = current_date
|
||||
if is_latest:
|
||||
if current_date and (date is None or current_date > date):
|
||||
date = current_date
|
||||
return date
|
||||
|
||||
|
||||
def derive_calendar(task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
calendar = get_calendar(task)
|
||||
if calendar:
|
||||
return calendar
|
||||
for rel in task.Nests or []:
|
||||
return derive_calendar(rel.RelatingObject)
|
||||
|
||||
|
||||
def get_calendar(task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
calendar = [
|
||||
rel.RelatingControl
|
||||
for rel in task.HasAssignments or []
|
||||
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkCalendar")
|
||||
]
|
||||
if calendar:
|
||||
return calendar[0]
|
||||
|
||||
|
||||
def count_working_days(start, finish, calendar: ifcopenshell.entity_instance) -> int:
|
||||
result = 0
|
||||
if start == finish:
|
||||
return 0
|
||||
current_date = datetime.date(start.year, start.month, start.day)
|
||||
finish_date = datetime.date(finish.year, finish.month, finish.day)
|
||||
while current_date <= finish_date:
|
||||
if calendar and calendar.WorkingTimes and is_working_day(current_date, calendar):
|
||||
result += 1
|
||||
elif not calendar or not is_calendar_applicable(current_date, calendar):
|
||||
result += 1
|
||||
current_date += datetime.timedelta(days=1)
|
||||
return result
|
||||
|
||||
|
||||
def get_start_or_finish_date(
|
||||
start,
|
||||
duration,
|
||||
duration_type: DURATION_TYPE,
|
||||
calendar: ifcopenshell.entity_instance,
|
||||
date_type: Literal["START", "FINISH"] = "FINISH",
|
||||
):
|
||||
if not duration.days:
|
||||
# Typically a milestone will have zero duration, so the start == finish
|
||||
return start
|
||||
# We minus 1 because the start day itself is counted as a day
|
||||
months = int(getattr(duration, "months", 0))
|
||||
years = int(getattr(duration, "years", 0))
|
||||
total_duration = duration.days + months * 30 + years * 12 * 30
|
||||
duration = datetime.timedelta(days=total_duration - 1)
|
||||
|
||||
if date_type == "START":
|
||||
duration = -duration
|
||||
result = offset_date(start, duration, duration_type, calendar)
|
||||
if date_type == "START":
|
||||
return datetime.datetime.combine(result, datetime.time(9))
|
||||
return datetime.datetime.combine(result, datetime.time(17))
|
||||
|
||||
|
||||
def offset_date(start, duration, duration_type: DURATION_TYPE, calendar: ifcopenshell.entity_instance):
|
||||
current_date = start
|
||||
months = getattr(duration, "months", 0)
|
||||
years = getattr(duration, "years", 0)
|
||||
|
||||
abs_duration = abs(duration.days + months * 30 + years * 12 * 30)
|
||||
date_offset = datetime.timedelta(days=1 if duration.days > 0 else -1)
|
||||
while abs_duration > 0:
|
||||
if duration_type == "ELAPSEDTIME" or not is_calendar_applicable(current_date, calendar):
|
||||
abs_duration -= 1
|
||||
elif is_working_day(current_date, calendar):
|
||||
abs_duration -= 1
|
||||
current_date += date_offset
|
||||
if duration.days > 0:
|
||||
current_date = get_soonest_working_day(current_date, duration_type, calendar)
|
||||
else:
|
||||
current_date = get_recent_working_day(current_date, duration_type, calendar)
|
||||
return current_date
|
||||
|
||||
|
||||
def get_soonest_working_day(start, duration_type: DURATION_TYPE, calendar: ifcopenshell.entity_instance):
|
||||
if duration_type == "ELAPSEDTIME" or not is_calendar_applicable(start, calendar):
|
||||
return start
|
||||
while not is_working_day(start, calendar):
|
||||
if not is_calendar_applicable(start, calendar):
|
||||
break
|
||||
start += datetime.timedelta(days=1)
|
||||
return start
|
||||
|
||||
|
||||
def get_recent_working_day(start, duration_type: DURATION_TYPE, calendar: ifcopenshell.entity_instance):
|
||||
if duration_type == "ELAPSEDTIME" or not is_calendar_applicable(start, calendar):
|
||||
return start
|
||||
while not is_working_day(start, calendar):
|
||||
if not is_calendar_applicable(start, calendar):
|
||||
break
|
||||
start -= datetime.timedelta(days=1)
|
||||
return start
|
||||
|
||||
|
||||
@cache
|
||||
def is_working_day(day, calendar: ifcopenshell.entity_instance) -> bool:
|
||||
is_working_day = False
|
||||
for work_time in calendar.WorkingTimes or []:
|
||||
if is_work_time_applicable_to_day(work_time, day):
|
||||
is_working_day = True
|
||||
break
|
||||
if not is_working_day:
|
||||
return is_working_day
|
||||
for work_time in calendar.ExceptionTimes or []:
|
||||
if is_work_time_applicable_to_day(work_time, day):
|
||||
is_working_day = False
|
||||
break
|
||||
return is_working_day
|
||||
|
||||
|
||||
@cache
|
||||
def is_calendar_applicable(day, calendar: ifcopenshell.entity_instance) -> bool:
|
||||
if not calendar or not calendar.WorkingTimes:
|
||||
return False
|
||||
is_applicable = False
|
||||
for work_time in calendar.WorkingTimes or []:
|
||||
if is_day_in_work_time(day, work_time):
|
||||
is_applicable = True
|
||||
break
|
||||
return is_applicable
|
||||
|
||||
|
||||
def is_day_in_work_time(day, work_time: ifcopenshell.entity_instance) -> bool:
|
||||
is_day_in_work_time = True
|
||||
if isinstance(day, datetime.datetime):
|
||||
day = datetime.date(day.year, day.month, day.day)
|
||||
# 4 IfcWorktime Start
|
||||
if start := work_time[4]:
|
||||
start = ifcopenshell.util.date.ifc2datetime(start)
|
||||
if day > start:
|
||||
is_day_in_work_time = True
|
||||
else:
|
||||
is_day_in_work_time = False
|
||||
# 5 IfcWorktime Finish
|
||||
if finish := work_time[5]:
|
||||
finish = ifcopenshell.util.date.ifc2datetime(finish)
|
||||
if day < finish:
|
||||
is_day_in_work_time = True
|
||||
else:
|
||||
is_day_in_work_time = False
|
||||
return is_day_in_work_time
|
||||
|
||||
|
||||
def is_work_time_applicable_to_day(work_time: ifcopenshell.entity_instance, day) -> bool:
|
||||
if not is_day_in_work_time(day, work_time):
|
||||
return False
|
||||
if not work_time.RecurrencePattern:
|
||||
return True
|
||||
|
||||
if isinstance(day, datetime.datetime):
|
||||
day = datetime.date(day.year, day.month, day.day)
|
||||
recurrence = work_time.RecurrencePattern
|
||||
recurrence_type: RECURRENCE_TYPE = recurrence.RecurrenceType
|
||||
if recurrence_type == "DAILY":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return True
|
||||
# 4 IfcWorktime Start
|
||||
if not work_time[4]:
|
||||
return False
|
||||
return False # TODO
|
||||
elif recurrence_type == "WEEKLY":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return (day.weekday() + 1) in recurrence.WeekdayComponent
|
||||
# 4 IfcWorktime Start
|
||||
if not work_time[4]:
|
||||
return False
|
||||
return False # TODO
|
||||
elif recurrence_type == "MONTHLY_BY_DAY_OF_MONTH":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return day.day in recurrence.DayComponent
|
||||
return False # TODO
|
||||
elif recurrence_type == "MONTHLY_BY_POSITION":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return (day.weekday() + 1) in recurrence.WeekdayComponent and floor(day.day / 7) + 1 == recurrence[
|
||||
"Position"
|
||||
]
|
||||
return False # TODO
|
||||
elif recurrence_type == "YEARLY_BY_DAY_OF_MONTH":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return day.month in recurrence.MonthComponent and day.day in recurrence.DayComponent
|
||||
return False # TODO
|
||||
elif recurrence_type == "YEARLY_BY_POSITION":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return (
|
||||
day.month in recurrence.MonthComponent
|
||||
and (day.weekday() + 1) in recurrence.WeekdayComponent
|
||||
and floor(day.day / 7) + 1 == recurrence.Position
|
||||
)
|
||||
return False # TODO
|
||||
|
||||
|
||||
def get_task_work_schedule(task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
parent_task = get_parent_task(task)
|
||||
if parent_task:
|
||||
return get_task_work_schedule(parent_task) or get_task_work_schedule(task)
|
||||
else:
|
||||
for rel in task.HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"):
|
||||
return rel.RelatingControl
|
||||
return None
|
||||
|
||||
|
||||
def get_nested_tasks(task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
return [obj for obj in ifcopenshell.util.element.get_components(task) if obj.is_a("IfcTask")]
|
||||
|
||||
|
||||
def get_parent_task(task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
nests = task.Nests
|
||||
if nests and (obj := nests[0].RelatingObject).is_a("IfcTask"):
|
||||
return obj
|
||||
|
||||
|
||||
def get_all_nested_tasks(task: ifcopenshell.entity_instance) -> Generator[ifcopenshell.entity_instance]:
|
||||
for nested_task in get_nested_tasks(task):
|
||||
yield nested_task
|
||||
yield from get_all_nested_tasks(nested_task)
|
||||
|
||||
|
||||
def get_work_schedule_tasks(work_schedule: ifcopenshell.entity_instance) -> Generator[ifcopenshell.entity_instance]:
|
||||
"""Get all work schedule tasks, including the nested ones."""
|
||||
for root_task in get_root_tasks(work_schedule):
|
||||
yield root_task
|
||||
yield from get_all_nested_tasks(root_task)
|
||||
|
||||
|
||||
def get_root_tasks(work_schedule: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
return [obj for rel in work_schedule.Controls for obj in rel.RelatedObjects if obj.is_a("IfcTask")]
|
||||
|
||||
|
||||
def guess_date_range(work_schedule: ifcopenshell.entity_instance):
|
||||
earliest = None
|
||||
latest = None
|
||||
root_tasks = get_root_tasks(work_schedule)
|
||||
tasks_with_assignements = []
|
||||
for task in root_tasks:
|
||||
if has_task_outputs(task) or has_task_inputs(task):
|
||||
tasks_with_assignements.append(task)
|
||||
for sub_task in get_all_nested_tasks(task):
|
||||
if has_task_outputs(sub_task) or has_task_inputs(sub_task):
|
||||
tasks_with_assignements.append(sub_task)
|
||||
|
||||
for task in tasks_with_assignements:
|
||||
derived_start = derive_date(task, "ScheduleStart", is_earliest=True)
|
||||
derived_finish = derive_date(task, "ScheduleFinish", is_latest=True)
|
||||
if derived_start and (not earliest or derived_start < earliest):
|
||||
earliest = derived_start
|
||||
if derived_finish and (not latest or derived_finish > latest):
|
||||
latest = derived_finish
|
||||
return earliest, latest
|
||||
|
||||
|
||||
def get_task_outputs(
|
||||
task: ifcopenshell.entity_instance, is_recursive: bool = False
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
if is_recursive:
|
||||
return {o for subtask in [task] + list(get_all_nested_tasks(task)) for o in get_task_outputs(subtask)}
|
||||
return {rel.RelatingProduct for rel in task.HasAssignments if rel.is_a("IfcRelAssignsToProduct")}
|
||||
|
||||
|
||||
def get_task_inputs(
|
||||
task: ifcopenshell.entity_instance, is_recursive: bool = False
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
if is_recursive:
|
||||
return {o for subtask in [task] + list(get_all_nested_tasks(task)) for o in get_task_inputs(subtask)}
|
||||
return {o for rel in task.OperatesOn for o in rel.RelatedObjects if o.is_a("IfcProduct")}
|
||||
|
||||
|
||||
def get_task_resources(
|
||||
task: ifcopenshell.entity_instance, is_recursive: bool = False
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
if is_recursive:
|
||||
return {r for subtask in [task] + list(get_all_nested_tasks(task)) for r in get_task_resources(subtask)}
|
||||
return {o for rel in task.OperatesOn for o in rel.RelatedObjects if o.is_a("IfcResource")}
|
||||
|
||||
|
||||
def has_task_outputs(task: ifcopenshell.entity_instance) -> bool:
|
||||
return len(get_task_outputs(task)) > 0
|
||||
|
||||
|
||||
def has_task_inputs(task: ifcopenshell.entity_instance) -> bool:
|
||||
return len(get_task_inputs(task)) > 0
|
||||
|
||||
|
||||
def get_tasks_for_product(
|
||||
product: ifcopenshell.entity_instance, schedule: Optional[ifcopenshell.entity_instance] = None
|
||||
) -> tuple[list[ifcopenshell.entity_instance], list[ifcopenshell.entity_instance]]:
|
||||
"""
|
||||
Get all tasks assigned to or referenced by the given product.
|
||||
|
||||
:param product: An object that is assigned tasks or references tasks.
|
||||
:param schedule: An optional string representing the schedule name to filter tasks by.
|
||||
|
||||
:return: A tuple of two lists:
|
||||
- The first list contains all tasks assigned to the product.
|
||||
- The second list contains all tasks referenced by the product that are part of the given schedule.
|
||||
"""
|
||||
inputs = [
|
||||
assignement.RelatingProcess
|
||||
for assignement in product.HasAssignments
|
||||
if assignement.is_a("IfcRelAssignsToProcess") and assignement.RelatingProcess.is_a("IfcTask")
|
||||
]
|
||||
outputs = [
|
||||
obj
|
||||
for ref in product.ReferencedBy
|
||||
if ref.is_a("IfcRelAssignsToProduct")
|
||||
for obj in ref.RelatedObjects
|
||||
if obj.is_a("IfcTask")
|
||||
]
|
||||
|
||||
if schedule:
|
||||
inputs = [task for task in inputs if get_task_work_schedule(task).id() == schedule.id()]
|
||||
outputs = [task for task in outputs if get_task_work_schedule(task).id() == schedule.id()]
|
||||
|
||||
return inputs, outputs
|
||||
|
||||
|
||||
def get_sequence_assignment(task: ifcopenshell.entity_instance, sequence="successor"):
|
||||
if sequence == "successor":
|
||||
relationship_attr = "IsPredecessorTo"
|
||||
elif sequence == "predecessor":
|
||||
relationship_attr = "IsSuccessorFrom"
|
||||
else:
|
||||
return []
|
||||
|
||||
relationship = getattr(task, relationship_attr, None)
|
||||
if relationship:
|
||||
return relationship
|
||||
|
||||
for rel in task.Nests or []:
|
||||
result = get_sequence_assignment(rel.RelatingObject, sequence)
|
||||
if result:
|
||||
return result
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def get_related_products(
|
||||
relating_product: Optional[ifcopenshell.entity_instance] = None,
|
||||
related_object: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
"""Gets the related products being output by a task
|
||||
|
||||
:param relating_product: One of the products already output by the task.
|
||||
:param related_object: The IfcTask that you want to get all the related
|
||||
products for.
|
||||
:return: A set of IfcProducts output by the IfcTask.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's create a construction task. Note that the predefined type is
|
||||
# important to distinguish types of tasks.
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Build wall", identification="A", predefined_type="CONSTRUCTION")
|
||||
|
||||
# Let's say we have a wall somewhere.
|
||||
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
|
||||
|
||||
# Let's construct that wall!
|
||||
ifcopenshell.api.sequence.assign_product(relating_product=wall, related_object=task)
|
||||
|
||||
# This will give us a set with that wall in it.
|
||||
products = ifcopenshell.util.sequence.get_related_products(related_object=task)
|
||||
"""
|
||||
|
||||
assert relating_product or related_object, "Either relating_product or related_object must be provided."
|
||||
|
||||
products = set()
|
||||
if not related_object and relating_product:
|
||||
for reference in relating_product.ReferencedBy:
|
||||
if reference.is_a("IfcRelAssignsToProduct"):
|
||||
related_object = reference.RelatedObjects[0]
|
||||
|
||||
if related_object:
|
||||
assignments = related_object.HasAssignments
|
||||
for assignment in assignments:
|
||||
if assignment.is_a("IfcRelAssignsToProduct"):
|
||||
products.add(assignment.RelatingProduct.id())
|
||||
|
||||
return products
|
||||
@@ -0,0 +1,754 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 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 __future__ import annotations
|
||||
|
||||
from math import cos, radians
|
||||
from typing import TYPE_CHECKING, Literal, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import shapely
|
||||
import shapely.ops
|
||||
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
from ifcopenshell.geom import ShapeElementType
|
||||
from ifcopenshell.util.shape_builder import VectorType
|
||||
|
||||
AXIS_LITERAL = Literal["X", "Y", "Z"]
|
||||
|
||||
VECTOR_3D = tuple[float, float, float]
|
||||
|
||||
# Used only for typing, but reused by `shape.py` users.
|
||||
MatrixType = npt.NDArray[np.float64]
|
||||
"""`npt.NDArray[np.float64]`"""
|
||||
|
||||
tol = 1e-6
|
||||
|
||||
# NOTE: See IfcGeomRepresentation.h for W.Triangulation buffer types.
|
||||
|
||||
# NOTE: For functions that return a single scalar ensure to use .item() to
|
||||
# return the Python float instead of numpy float
|
||||
# as it's less intrusive (doesn't promote numpy arrays on interactions),
|
||||
# doesn't fail saving to IFC
|
||||
# and precise enough anyway (internally Python floats are doubles).
|
||||
|
||||
|
||||
def is_x(value: float, x: float, tolerance: Optional[float] = None) -> bool:
|
||||
"""Checks whether a value is equivalent to X given a tolerance
|
||||
|
||||
:param value: Input value
|
||||
:param x: The value to compare to
|
||||
:param tolerance: The tolerance to use. Defaults to 1e-6.
|
||||
:return: True or false
|
||||
"""
|
||||
if tolerance is None:
|
||||
tolerance = tol
|
||||
return abs(x - value) < tolerance
|
||||
|
||||
|
||||
def get_volume(geometry: W.Triangulation) -> float:
|
||||
"""Calculates the total internal volume of a geometry
|
||||
|
||||
Volumes of non-manifold geometry will be unpredictable.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The volume in m3
|
||||
"""
|
||||
|
||||
# https://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up
|
||||
def signed_triangle_volume(p1, p2, p3):
|
||||
v321 = p3[0] * p2[1] * p1[2]
|
||||
v231 = p2[0] * p3[1] * p1[2]
|
||||
v312 = p3[0] * p1[1] * p2[2]
|
||||
v132 = p1[0] * p3[1] * p2[2]
|
||||
v213 = p2[0] * p1[1] * p3[2]
|
||||
v123 = p1[0] * p2[1] * p3[2]
|
||||
return (1.0 / 6.0) * (-v321 + v231 + v312 - v132 - v213 + v123)
|
||||
|
||||
# Can't optimize it using buffers - performance seems to get only worse.
|
||||
verts = geometry.verts
|
||||
faces = geometry.faces
|
||||
grouped_verts = [[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)]
|
||||
volumes = [
|
||||
signed_triangle_volume(grouped_verts[faces[i]], grouped_verts[faces[i + 1]], grouped_verts[faces[i + 2]])
|
||||
for i in range(0, len(faces), 3)
|
||||
]
|
||||
return abs(sum(volumes))
|
||||
|
||||
|
||||
def get_x(geometry: W.Triangulation) -> float:
|
||||
"""Calculates the X length of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The X dimension
|
||||
"""
|
||||
verts_flat = get_vertices(geometry).ravel()
|
||||
return (np.max(verts_flat[0::3]) - np.min(verts_flat[0::3])).item()
|
||||
|
||||
|
||||
def get_y(geometry: W.Triangulation) -> float:
|
||||
"""Calculates the Y length of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The Y dimension
|
||||
"""
|
||||
verts_flat = get_vertices(geometry).ravel()
|
||||
return (np.max(verts_flat[1::3]) - np.min(verts_flat[1::3])).item()
|
||||
|
||||
|
||||
def get_z(geometry: W.Triangulation) -> float:
|
||||
"""Calculates the Z length of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The Z dimension
|
||||
"""
|
||||
verts_flat = get_vertices(geometry).ravel()
|
||||
return (np.max(verts_flat[2::3]) - np.min(verts_flat[2::3])).item()
|
||||
|
||||
|
||||
def get_max_xy(geometry: W.Triangulation) -> float:
|
||||
"""Gets the maximum X or Y length of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The maximum possible value out of the X and Y dimension
|
||||
"""
|
||||
return max(get_x(geometry), get_y(geometry))
|
||||
|
||||
|
||||
def get_max_xyz(geometry: W.Triangulation) -> float:
|
||||
"""Gets the maximum X, Y, or Z length of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The maximum possible value out of the X, Y, and Z dimension
|
||||
"""
|
||||
return max(get_x(geometry), get_y(geometry), get_z(geometry))
|
||||
|
||||
|
||||
def get_min_xyz(geometry: W.Triangulation) -> float:
|
||||
"""Gets the minimum X, Y, or Z length of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The minimum possible value out of the X, Y, and Z dimension
|
||||
"""
|
||||
return min(get_x(geometry), get_y(geometry), get_z(geometry))
|
||||
|
||||
|
||||
def get_shape_matrix(shape: ShapeElementType) -> MatrixType:
|
||||
"""Formats the transformation matrix of a shape as a 4x4 numpy array
|
||||
|
||||
:param shape: Shape output calculated by IfcOpenShell
|
||||
:return: A 4x4 numpy array representing the transformation matrix
|
||||
"""
|
||||
return np.frombuffer(shape.transformation_buffer, "d").reshape((4, 4), order="F")
|
||||
|
||||
|
||||
def get_bbox_centroid(geometry: W.Triangulation) -> tuple[float, float, float]:
|
||||
"""Calculates the bounding box centroid of the geometry
|
||||
|
||||
The centroid is in local coordinates relative to the object's placement.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: A tuple representing the XYZ centroid
|
||||
"""
|
||||
vertices_array = get_vertices(geometry)
|
||||
return (np.min(vertices_array, axis=0) + np.max(vertices_array, axis=0)) / 2
|
||||
|
||||
|
||||
def get_vert_centroid(geometry: W.Triangulation) -> tuple[float, float, float]:
|
||||
"""Calculates the average vertex centroid of the geometry
|
||||
|
||||
The centroid is in local coordinates relative to the object's placement.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: A tuple representing the XYZ centroid
|
||||
"""
|
||||
return np.mean(get_vertices(geometry), axis=0)
|
||||
|
||||
|
||||
def get_element_bbox_centroid(
|
||||
element: ifcopenshell.entity_instance, geometry: W.Triangulation
|
||||
) -> npt.NDArray[np.float64]:
|
||||
"""Calculates the element's bounding box centroid
|
||||
|
||||
The centroid is in global coordinates. Note that if you have the shape, it
|
||||
is more efficient to use :func:`get_shape_bbox_centroid`.
|
||||
|
||||
:param element: The element occurrence
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: A tuple representing the XYZ centroid
|
||||
"""
|
||||
centroid = get_bbox_centroid(geometry)
|
||||
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
|
||||
return np.array(centroid)
|
||||
mat = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
return (mat @ np.array([*centroid, 1.0]))[0:3]
|
||||
|
||||
|
||||
def get_shape_bbox_centroid(shape: ShapeElementType, geometry: W.Triangulation) -> npt.NDArray[np.float64]:
|
||||
"""Calculates the shape's bounding box centroid
|
||||
|
||||
The centroid is in global coordinates. Note that if you do not have the
|
||||
shape, you can use :func:`get_element_bbox_centroid`.
|
||||
|
||||
:param shape: Shape output calculated by IfcOpenShell
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: A tuple representing the XYZ centroid
|
||||
"""
|
||||
centroid = get_bbox_centroid(geometry)
|
||||
return (get_shape_matrix(shape) @ np.array([*centroid, 1.0]))[0:3]
|
||||
|
||||
|
||||
def get_vertices(geometry: W.Triangulation, is_2d: bool = False) -> npt.NDArray[np.float64]:
|
||||
"""Get all the vertices as a numpy array
|
||||
|
||||
Vertices are in local coordinates.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:param is_2d: Set to True to to get XY coordinates only.
|
||||
:return: A numpy array listing all the vertices and their coordinates.
|
||||
Array shape: (n, 3), where n - number of vertices.
|
||||
"""
|
||||
if is_2d:
|
||||
return np.frombuffer(geometry.verts_buffer, "d").reshape(-1, 3)[:, :2]
|
||||
return np.frombuffer(geometry.verts_buffer, "d").reshape(-1, 3)
|
||||
|
||||
|
||||
def get_edges(geometry: W.Triangulation) -> npt.NDArray[np.int32]:
|
||||
"""Get all the edges as a numpy array
|
||||
|
||||
Results are a nested numpy array e.g. [[e1v1, e1v2], [e2v1, e2v2], ...]
|
||||
|
||||
Note that although geometry always holds triangulated faces, edges will
|
||||
represent the original tessellation or BRep's faces, which may be quads or
|
||||
ngons.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: A numpy array listing all the edges.
|
||||
Array shape: (n, 2), where n - number of edges.
|
||||
"""
|
||||
return np.frombuffer(geometry.edges_buffer, dtype="i").reshape(-1, 2)
|
||||
|
||||
|
||||
def get_faces(geometry: W.Triangulation) -> npt.NDArray[np.int32]:
|
||||
"""Get all the faces as a numpy array
|
||||
|
||||
Faces are always triangulated. If the shape is a BRep and you want to get
|
||||
the original untriangulated output, refer to :func:`get_edges`.
|
||||
|
||||
Results are a nested numpy array e.g. [[f1v1, f1v2, f1v3], [f2v1, f2v2, f2v3], ...]
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: A numpy array listing all the faces.
|
||||
Array shape: (n, 3), where n - number of faces.
|
||||
"""
|
||||
return np.frombuffer(geometry.faces_buffer, dtype="i").reshape(-1, 3)
|
||||
|
||||
|
||||
def get_material_colors(geometry: W.Triangulation) -> npt.NDArray[np.float64]:
|
||||
"""Get material colors as a numpy array.
|
||||
|
||||
:return: A numpy array listing RGBA color for each shape's material.
|
||||
Array shape: (1, 4).
|
||||
"""
|
||||
# colors_buffer comes from geometry.materials and doesn't account
|
||||
# for colors that can be set by some other way (e.g. IfcIndexedColourMap).
|
||||
return np.frombuffer(geometry.colors_buffer, dtype="d").reshape(-1, 4)
|
||||
|
||||
|
||||
def get_normals(geometry: W.Triangulation) -> npt.NDArray[np.float64]:
|
||||
"""Get vertex normals as a numpy array.
|
||||
|
||||
See geometry settings documentation for settings that affect normals.
|
||||
|
||||
:return: A numpy array listing normal for each shape vertex.
|
||||
Array shape: (1, 3).
|
||||
"""
|
||||
return np.frombuffer(geometry.normals_buffer, dtype="d").reshape(-1, 3)
|
||||
|
||||
|
||||
def get_shape_material_styles(geometry: W.Triangulation) -> tuple[W.style, ...]:
|
||||
"""Get list of material styles."""
|
||||
return geometry.materials
|
||||
|
||||
|
||||
def get_faces_material_style_ids(geometry: W.Triangulation) -> npt.NDArray[np.int32]:
|
||||
"""Get material styles ids for the geometry faces.
|
||||
|
||||
Return a list of corresponding indices of styles from get_shape_material_styles for each face.
|
||||
If face has no style assigned, index -1 is used.
|
||||
"""
|
||||
return np.frombuffer(geometry.material_ids_buffer, dtype="i")
|
||||
|
||||
|
||||
def get_faces_representation_item_ids(geometry: W.Triangulation) -> npt.NDArray[np.int32]:
|
||||
"""Get representation item ids for the geometry faces."""
|
||||
return np.frombuffer(geometry.item_ids_buffer, dtype="i")
|
||||
|
||||
|
||||
def get_edges_representation_item_ids(geometry: W.Triangulation) -> npt.NDArray[np.int32]:
|
||||
"""Get representation item ids for the geometry edges.
|
||||
|
||||
Can be useful for geometry without faces and in general is more universal
|
||||
since it's possible that geometry will have elements with and without faces.
|
||||
"""
|
||||
return np.frombuffer(geometry.edges_item_ids_buffer, dtype="i")
|
||||
|
||||
|
||||
def get_shape_vertices(shape: ShapeElementType, geometry: W.Triangulation) -> npt.NDArray[np.float64]:
|
||||
"""Get the shape's vertices as a numpy array
|
||||
|
||||
Vertices are in global coordinates. If you do not have the shape, you can
|
||||
use :func:`get_element_vertices`.
|
||||
|
||||
Results are a nested numpy array e.g. [[v1x, v1y, v1z], [v2x, v2y, v2z], ...]
|
||||
|
||||
:param shape: Shape output calculated by IfcOpenShell
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: A numpy array listing all the vertices. Each vertex is a numpy array with XYZ coordinates.
|
||||
Array shape: (n, 3), where n - number of vertices.
|
||||
"""
|
||||
verts = get_vertices(geometry)
|
||||
mat = get_shape_matrix(shape)
|
||||
return np.delete((mat @ np.hstack((verts, np.ones((len(verts), 1)))).T).T, -1, axis=1)
|
||||
|
||||
|
||||
def get_element_vertices(element: ifcopenshell.entity_instance, geometry: W.Triangulation) -> npt.NDArray[np.float64]:
|
||||
"""Get the element's vertices as a numpy array
|
||||
|
||||
Vertices are in global coordinates. Note that if you have the shape, it is
|
||||
more efficient to use :func:`get_shape_vertices`.
|
||||
|
||||
Results are a nested numpy array e.g. [[v1x, v1y, v1z], [v2x, v2y, v2z], ...]
|
||||
|
||||
:param element: The element occurrence
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: A numpy array listing all the vertices. Each vertex is a numpy array with XYZ coordinates.
|
||||
"""
|
||||
verts = get_vertices(geometry)
|
||||
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
|
||||
return verts
|
||||
mat = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
return np.delete((mat @ np.hstack((verts, np.ones((len(verts), 1)))).T).T, -1, axis=1)
|
||||
|
||||
|
||||
def get_bottom_elevation(geometry: W.Triangulation) -> float:
|
||||
"""Gets the lowest local Z ordinate of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The Z value
|
||||
"""
|
||||
verts_flat = get_vertices(geometry).ravel()
|
||||
return np.min(verts_flat[2::3]).item()
|
||||
|
||||
|
||||
def get_top_elevation(geometry: W.Triangulation) -> float:
|
||||
"""Gets the highest local Z ordinate of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The Z value
|
||||
"""
|
||||
verts_flat = get_vertices(geometry).ravel()
|
||||
return np.max(verts_flat[2::3]).item()
|
||||
|
||||
|
||||
def get_shape_bottom_elevation(shape: ShapeElementType, geometry: W.Triangulation) -> float:
|
||||
"""Gets the lowest global Z ordinate of the shape
|
||||
|
||||
If you do not have the shape, you can use :func:`get_element_bottom_elevation`
|
||||
instead.
|
||||
|
||||
:param shape: Shape output calculated by IfcOpenShell
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The Z value
|
||||
"""
|
||||
return min([v[2] for v in get_shape_vertices(shape, geometry)])
|
||||
|
||||
|
||||
def get_shape_top_elevation(shape: ShapeElementType, geometry: W.Triangulation) -> float:
|
||||
"""Gets the highest global Z ordinate of the shape
|
||||
|
||||
If you do not have the shape, you can use :func:`get_element_top_elevation`
|
||||
instead.
|
||||
|
||||
:param shape: Shape output calculated by IfcOpenShell
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The Z value
|
||||
"""
|
||||
return max([v[2] for v in get_shape_vertices(shape, geometry)])
|
||||
|
||||
|
||||
def get_element_bottom_elevation(element: ifcopenshell.entity_instance, geometry: W.Triangulation) -> float:
|
||||
"""Gets the lowest global Z ordinate of the element
|
||||
|
||||
Note that if you have the shape, it is more efficient to use
|
||||
:func:`get_shape_bottom_elevation`.
|
||||
|
||||
:param element: The element occurrence
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The Z value
|
||||
"""
|
||||
return min([v[2] for v in get_element_vertices(element, geometry)])
|
||||
|
||||
|
||||
def get_element_top_elevation(element: ifcopenshell.entity_instance, geometry: W.Triangulation) -> float:
|
||||
"""Gets the highest global Z ordinate of the element
|
||||
|
||||
Note that if you have the shape, it is more efficient to use
|
||||
:func:`get_shape_top_elevation`.
|
||||
|
||||
:param element: The element occurrence
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The Z value
|
||||
"""
|
||||
return max([v[2] for v in get_element_vertices(element, geometry)])
|
||||
|
||||
|
||||
def get_bbox(vertices: npt.NDArray[np.float64]) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
|
||||
"""Gets the bounding box of vertices
|
||||
|
||||
:param vertices: An iterable of vertices
|
||||
:return: The bounding box value represented as a tuple of two numpy arrays.
|
||||
The first holds the bottom left corner and the second holds the top
|
||||
right. E.g. (np.array([minx, miny, minz]), np.array([maxx, maxy,
|
||||
maxz]))
|
||||
"""
|
||||
return (np.min(vertices, axis=0), np.max(vertices, axis=0))
|
||||
|
||||
|
||||
def get_area_vf(vertices: npt.NDArray[np.float64], faces: npt.NDArray[np.int32]) -> float:
|
||||
"""Calculates the surface area given a list of vertices and triangulated faces
|
||||
|
||||
:param vertices: A list of 3D vertices, such as returned from get_vertices.
|
||||
:param faces: A list of faces, such as returned from get_faces.
|
||||
:return: The surface area.
|
||||
"""
|
||||
# Calculate the triangle normal vectors
|
||||
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
||||
v2 = vertices[faces[:, 2]] - vertices[faces[:, 0]]
|
||||
triangle_normals = np.cross(v1, v2)
|
||||
|
||||
# Normalize the normal vectors to get their length (i.e., triangle area)
|
||||
triangle_areas = np.linalg.norm(triangle_normals, axis=1) / 2
|
||||
|
||||
# Sum up the areas to get the total area of the mesh
|
||||
mesh_area = np.sum(triangle_areas)
|
||||
|
||||
return mesh_area.item()
|
||||
|
||||
|
||||
def get_area(geometry: W.Triangulation) -> float:
|
||||
"""Calculates the surface area of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The surface area.
|
||||
"""
|
||||
vertices = get_vertices(geometry)
|
||||
faces = get_faces(geometry)
|
||||
return get_area_vf(vertices, faces)
|
||||
|
||||
|
||||
def get_side_area(
|
||||
geometry: W.Triangulation,
|
||||
axis: AXIS_LITERAL = "Y",
|
||||
direction: Optional[VectorType] = None,
|
||||
angle: float = 90.0,
|
||||
) -> float:
|
||||
"""Calculates the total surface area of surfaces that are visible from the specified axis
|
||||
|
||||
This is typically useful for calculating elevational areas. For example,
|
||||
you might want to calculate the side area of a wall (i.e. only one side,
|
||||
not both).
|
||||
|
||||
Surfaces do not need to be exactly perpendicular in the direction of the
|
||||
specified axis. A surface is counted so long as it is visible from that
|
||||
axis.
|
||||
|
||||
Note that this calculates the actual area, not the projected 2D area. If
|
||||
you want the projected area, use :func:`get_footprint_area`.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:param axis: Either X, Y, or Z. Defaults to Y, which is used for standard
|
||||
walls.
|
||||
:param angle: Accept angle difference between face and axis, in degrees.
|
||||
E.g. default angle 90 will find all faces with angle < 90 degrees.
|
||||
:return: The surface area.
|
||||
"""
|
||||
if direction is None:
|
||||
direction = {"X": (1.0, 0.0, 0.0), "Y": (0.0, 1.0, 0.0), "Z": (0.0, 0.0, 1.0)}[axis]
|
||||
|
||||
vertices = get_vertices(geometry)
|
||||
faces = get_faces(geometry)
|
||||
|
||||
# Calculate the triangle normal vectors
|
||||
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
||||
v2 = vertices[faces[:, 2]] - vertices[faces[:, 0]]
|
||||
triangle_normals = np.cross(v1, v2)
|
||||
|
||||
# Normalize the normal vectors
|
||||
triangle_normals = triangle_normals / np.linalg.norm(triangle_normals, axis=1)[:, np.newaxis]
|
||||
direction = np.array(direction) / np.linalg.norm(direction)
|
||||
|
||||
# Find the faces with a normal vector pointing in the desired +Y normal direction
|
||||
# normal_tol < 0 is pointing away, = 0 is perpendicular, and > 0 is pointing towards.
|
||||
normal_tol = 0.01 # For angle 90 it's close to perpendicular, but with a fuzz for numerical tolerance
|
||||
acceptable_dot = cos(radians(angle)) + normal_tol
|
||||
dot_products = np.dot(triangle_normals, direction)
|
||||
filtered_face_indices = np.where(dot_products > acceptable_dot)[0]
|
||||
filtered_faces = faces[filtered_face_indices]
|
||||
return get_area_vf(vertices, filtered_faces)
|
||||
|
||||
|
||||
def get_max_side_area(geometry: W.Triangulation) -> float:
|
||||
"""Returns the maximum X, Y, or Z side area
|
||||
|
||||
See :func:`get_side_area` for how side area is calculated.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The maximum surface area from either the X, Y, or Z axis.
|
||||
"""
|
||||
return max(get_side_area(geometry, axis="X"), get_side_area(geometry, axis="Y"), get_side_area(geometry, axis="Z"))
|
||||
|
||||
|
||||
def get_top_area(geometry: W.Triangulation) -> float:
|
||||
return get_side_area(geometry, axis="Z", angle=45)
|
||||
|
||||
|
||||
def get_footprint_area(
|
||||
geometry: W.Triangulation,
|
||||
axis: AXIS_LITERAL = "Z",
|
||||
direction: Optional[VECTOR_3D] = None,
|
||||
) -> float:
|
||||
"""Calculates the total footprint (i.e. projected) surface area visible from along an axis
|
||||
|
||||
This is typically useful for calculating footprint areas. For example, you
|
||||
might want to calculate the top-down footprint area of a slab, ignoring
|
||||
slopes in the slab.
|
||||
|
||||
Surfaces do not need to be exactly perpendicular in the direction of the
|
||||
specified axis. A surface is counted so long as it is visible from that
|
||||
axis.
|
||||
|
||||
Note that this calculates the 2D projected area, not the actual surface
|
||||
area. If you want the actual area, use :func:`get_side_area`.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:param axis: Either X, Y, or Z. Defaults to Z.
|
||||
:param direction: An XYZ iterable (e.g. (0., 0., 1.)). If a direction
|
||||
vector is specified, this overrides the axis argument.
|
||||
:return: The surface area.
|
||||
"""
|
||||
if direction is None:
|
||||
direction = {"X": (1.0, 0.0, 0.0), "Y": (0.0, 1.0, 0.0), "Z": (0.0, 0.0, 1.0)}[axis]
|
||||
|
||||
vertices = get_vertices(geometry)
|
||||
faces = get_faces(geometry)
|
||||
|
||||
# Calculate the triangle normal vectors
|
||||
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
||||
v2 = vertices[faces[:, 2]] - vertices[faces[:, 0]]
|
||||
triangle_normals = np.cross(v1, v2)
|
||||
|
||||
# Normalize the normal vectors
|
||||
triangle_normals = triangle_normals / np.linalg.norm(triangle_normals, axis=1)[:, np.newaxis]
|
||||
direction = np.array(direction) / np.linalg.norm(direction)
|
||||
|
||||
# Find the faces with a normal vector pointing in the desired direction using dot product
|
||||
# normal_tol < 0 is pointing away, = 0 is perpendicular, and > 0 is pointing towards.
|
||||
normal_tol = 0.01 # Close to perpendicular, but with a fuzz for numerical tolerance
|
||||
dot_products = np.dot(triangle_normals, direction)
|
||||
filtered_face_indices = np.where(dot_products > normal_tol)[0]
|
||||
filtered_faces = faces[filtered_face_indices]
|
||||
|
||||
# Flatten vertices along the direction
|
||||
vertices = vertices.copy() # Buffers are read-only.
|
||||
for idx in range(len(vertices)):
|
||||
vertices[idx] = vertices[idx] - np.dot(vertices[idx], direction) * direction
|
||||
|
||||
# Now flatten 3D vertices into 2D polygons which can be unioned to find a footprint.
|
||||
|
||||
# Create an orthonormal basis using the direction
|
||||
d = np.array(direction) / np.linalg.norm(direction)
|
||||
|
||||
# Find a vector not parallel to d
|
||||
a = np.array(d)
|
||||
if not np.isclose(a[2], 1.0, atol=0.01): # If d is not along the Z-axis
|
||||
a[2] += 0.01 # Small perturbation to make it not parallel
|
||||
else:
|
||||
a = np.array([1, 0, 0])
|
||||
|
||||
# First basis vector
|
||||
b = np.cross(d, a)
|
||||
b /= np.linalg.norm(b)
|
||||
|
||||
# Second basis vector
|
||||
c = np.cross(d, b)
|
||||
|
||||
# Project the flattened vertices onto the basis to get 2D coordinates
|
||||
vertices_2d = np.array([[np.dot(v, b), np.dot(v, c)] for v in vertices])
|
||||
|
||||
polygons = [shapely.Polygon(vertices_2d[face]) for face in filtered_faces]
|
||||
unioned_polygon = shapely.ops.unary_union(polygons)
|
||||
|
||||
return unioned_polygon.area
|
||||
|
||||
|
||||
def get_outer_surface_area(geometry: W.Triangulation) -> float:
|
||||
"""Calculates the outer surface area (i.e. all sides except for top and bottom)
|
||||
|
||||
This is typically useful for calculating painted areas of beams which
|
||||
exclude the end faces (at the minimum and maximum local Z).
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The surface area.
|
||||
"""
|
||||
vertices = get_vertices(geometry)
|
||||
faces = get_faces(geometry)
|
||||
|
||||
# Calculate the triangle normal vectors
|
||||
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
||||
v2 = vertices[faces[:, 2]] - vertices[faces[:, 0]]
|
||||
triangle_normals = np.cross(v1, v2)
|
||||
|
||||
# Normalize the normal vectors
|
||||
triangle_normals = triangle_normals / np.linalg.norm(triangle_normals, axis=1)[:, np.newaxis]
|
||||
|
||||
# Find the faces with a normal vector that isn't +Z or -Z
|
||||
filtered_face_indices = np.where(abs(triangle_normals[:, 2]) < tol)[0]
|
||||
filtered_faces = faces[filtered_face_indices]
|
||||
return get_area_vf(vertices, filtered_faces)
|
||||
|
||||
|
||||
def get_footprint_perimeter(geometry: W.Triangulation) -> float:
|
||||
"""Calculates the footprint perimeter of the geometry
|
||||
|
||||
All faces with a negative Z normal are considered and the distance of all
|
||||
perimeter edges are totaled.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The perimeter length
|
||||
"""
|
||||
vertices = get_vertices(geometry)
|
||||
faces = get_faces(geometry)
|
||||
|
||||
# Calculate the triangle normal vectors
|
||||
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
||||
v2 = vertices[faces[:, 2]] - vertices[faces[:, 0]]
|
||||
triangle_normals = np.cross(v1, v2)
|
||||
|
||||
# Normalize the normal vectors
|
||||
triangle_normals = triangle_normals / np.linalg.norm(triangle_normals, axis=1)[:, np.newaxis]
|
||||
|
||||
# Find the faces with a normal vector pointing in the negative Z direction
|
||||
negative_z_face_indices = np.where(triangle_normals[:, 2] < -tol)[0]
|
||||
negative_z_faces = faces[negative_z_face_indices]
|
||||
|
||||
# Initialize the set of counted edges and the perimeter
|
||||
all_edges = set()
|
||||
shared_edges = set()
|
||||
perimeter = 0
|
||||
|
||||
# Loop through each face
|
||||
for face in negative_z_faces:
|
||||
# Loop through each edge of the face
|
||||
for i in range(3):
|
||||
# Get the indices of the two vertices that define the edge
|
||||
edge = (face[i], face[(i + 1) % 3])
|
||||
# Keep track of shared edges. Perimeter edges are unshared.
|
||||
if (edge[1], edge[0]) in all_edges or (edge[0], edge[1]) in all_edges:
|
||||
shared_edges.add((edge[0], edge[1]))
|
||||
shared_edges.add((edge[1], edge[0]))
|
||||
else:
|
||||
all_edges.add(edge)
|
||||
|
||||
return np.sum([np.linalg.norm(vertices[e[0]] - vertices[e[1]]) for e in (all_edges - shared_edges)]).item()
|
||||
|
||||
|
||||
def get_profiles(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
"""Gets all 2D profiles used in the definition of a parametric shape
|
||||
|
||||
Profiles may be retrieved either from material profile sets or from swept
|
||||
solid extrusions. This is useful for later doing 2D take-off from profiles.
|
||||
|
||||
:param element: The element occurrence
|
||||
:return: A list of profiles
|
||||
"""
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
return [mp.Profile for mp in material.MaterialProfiles]
|
||||
return [e.SweptArea for e in get_extrusions(element)]
|
||||
|
||||
|
||||
def get_extrusions(element: ifcopenshell.entity_instance) -> Union[list[ifcopenshell.entity_instance], None]:
|
||||
"""Gets all extruded area solids used to define an element's model body geometry
|
||||
|
||||
:param element: The element occurrence
|
||||
:return: A list of extrusion representation items or `None` if element has no representation.
|
||||
"""
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
return
|
||||
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
extrusions = []
|
||||
for item in representation.Items:
|
||||
while True:
|
||||
if item.is_a("IfcExtrudedAreaSolid"):
|
||||
extrusions.append(item)
|
||||
break
|
||||
elif item.is_a("IfcBooleanResult"):
|
||||
item = item.FirstOperand
|
||||
else:
|
||||
break
|
||||
return extrusions
|
||||
|
||||
|
||||
def get_base_extrusions(element: ifcopenshell.entity_instance) -> Union[list[ifcopenshell.entity_instance], None]:
|
||||
"""Gets all base extrusions used to define an element's model body geometry
|
||||
|
||||
A base extrusion is assumed to be an extrusion prior to all boolean
|
||||
results.
|
||||
|
||||
:param element: The element occurrence
|
||||
:return: A list of extrusion representation items or `None` if element has no representation.
|
||||
"""
|
||||
if not (rep := ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")):
|
||||
return
|
||||
extrusions = []
|
||||
for item in ifcopenshell.util.representation.resolve_representation(rep).Items:
|
||||
while item.is_a("IfcBooleanResult"):
|
||||
item = item.FirstOperand
|
||||
if item.is_a("IfcExtrudedAreaSolid"):
|
||||
extrusions.append(item)
|
||||
return extrusions
|
||||
|
||||
|
||||
def get_total_edge_length(geometry: W.Triangulation) -> float:
|
||||
"""Calculates the total length of edges in a given geometry.
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
:return: The total length of all edges in the geometry.
|
||||
"""
|
||||
vertices = get_vertices(geometry)
|
||||
vertices = vertices[get_edges(geometry)]
|
||||
return np.linalg.norm(vertices[:, 1] - vertices[:, 0], axis=1).sum().item()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,155 @@
|
||||
# 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/>.
|
||||
|
||||
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
import ifcopenshell.util.system
|
||||
|
||||
group_types: dict[str, tuple[str, ...]] = {
|
||||
"IfcZone": ("IfcZone", "IfcSpace", "IfcSpatialZone"),
|
||||
"IfcBuiltSystem": (
|
||||
"IfcBuiltElement",
|
||||
"IfcFurnishingElement",
|
||||
"IfcElementAssembly",
|
||||
"IfcTransportElement",
|
||||
),
|
||||
"IfcBuildingSystem": (
|
||||
"IfcBuildingElement",
|
||||
"IfcFurnishingElement",
|
||||
"IfcElementAssembly",
|
||||
"IfcTransportElement",
|
||||
),
|
||||
"IfcDistributionSystem": ("IfcDistributionElement",),
|
||||
"IfcStructuralAnalysisModel": ("IfcStructuralMember", "IfcStructuralConnection"),
|
||||
"IfcSystem": ("IfcProduct",),
|
||||
"IfcGroup": ("IfcObjectDefinition",),
|
||||
}
|
||||
# Subclasses.
|
||||
group_types["IfcDistributionCircuit"] = group_types["IfcDistributionSystem"]
|
||||
# Replaced by IfcDistributionCircuit in IFC4, though it wasn't limited to IfcDistributionElements:
|
||||
# "Usage of IfcElectricalCircuit is as for the supertype IfcSystem".
|
||||
group_types["IfcElectricalCircuit"] = group_types["IfcSystem"]
|
||||
|
||||
|
||||
FLOW_DIRECTION = Literal["SINK", "SOURCE", "SOURCEANDSINK", "NOTEDEFINED"]
|
||||
|
||||
|
||||
def is_assignable(product: ifcopenshell.entity_instance, system: ifcopenshell.entity_instance) -> bool:
|
||||
for assignable in group_types.get(system.is_a(), ()):
|
||||
if product.is_a(assignable):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_system_elements(system: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
for rel in system.IsGroupedBy:
|
||||
results.extend(rel.RelatedObjects)
|
||||
return results
|
||||
|
||||
|
||||
def get_element_systems(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
for rel in element.HasAssignments:
|
||||
if not rel.is_a("IfcRelAssignsToGroup"):
|
||||
continue
|
||||
group = rel.RelatingGroup
|
||||
if not group.is_a("IfcSystem") or group.is_a() in ("IfcStructuralAnalysisModel", "IfcZone"):
|
||||
continue
|
||||
results.append(group)
|
||||
return results
|
||||
|
||||
|
||||
def get_element_zones(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
for rel in element.HasAssignments:
|
||||
if not rel.is_a("IfcRelAssignsToGroup"):
|
||||
continue
|
||||
group = rel.RelatingGroup
|
||||
if not group.is_a("IfcZone"):
|
||||
continue
|
||||
results.append(group)
|
||||
return results
|
||||
|
||||
|
||||
def get_ports(
|
||||
element: ifcopenshell.entity_instance, flow_direction: Optional[FLOW_DIRECTION] = None
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
for rel in getattr(element, "IsNestedBy", []) or []:
|
||||
for port in rel.RelatedObjects:
|
||||
if not port.is_a("IfcDistributionPort"):
|
||||
continue
|
||||
if flow_direction and port.FlowDirection != flow_direction:
|
||||
continue
|
||||
results.append(port)
|
||||
# IFC2X3 only, deprecated in IFC4
|
||||
for rel in getattr(element, "HasPorts", []) or []:
|
||||
port = rel.RelatingPort
|
||||
if flow_direction and port.FlowDirection != flow_direction:
|
||||
continue
|
||||
results.append(port)
|
||||
return results
|
||||
|
||||
|
||||
def get_connected_port(port: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
for rel in port.ConnectedTo:
|
||||
return rel.RelatedPort
|
||||
for rel in port.ConnectedFrom:
|
||||
return rel.RelatingPort
|
||||
|
||||
|
||||
def get_port_element(port: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
if hasattr(port, "Nests"):
|
||||
for rel in port.Nests:
|
||||
return rel.RelatingObject
|
||||
# IFC2X3 only, deprecated in IFC4
|
||||
elif hasattr(port, "ContainedIn"):
|
||||
for rel in port.ContainedIn:
|
||||
return rel.RelatedElement
|
||||
|
||||
|
||||
def get_connected_to(
|
||||
element: ifcopenshell.entity_instance, flow_direction: Optional[FLOW_DIRECTION] = None
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
for port in ifcopenshell.util.system.get_ports(element, flow_direction=flow_direction):
|
||||
for rel in port.ConnectedTo:
|
||||
for other_port in [rel.RelatedPort, rel.RelatingPort]:
|
||||
if other_port == port:
|
||||
continue
|
||||
other_element = get_port_element(other_port)
|
||||
if other_element:
|
||||
results.append(other_element)
|
||||
return results
|
||||
|
||||
|
||||
def get_connected_from(
|
||||
element: ifcopenshell.entity_instance, flow_direction: Optional[FLOW_DIRECTION] = None
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
for port in ifcopenshell.util.system.get_ports(element, flow_direction=flow_direction):
|
||||
for rel in port.ConnectedFrom:
|
||||
for other_port in [rel.RelatedPort, rel.RelatingPort]:
|
||||
if other_port == port:
|
||||
continue
|
||||
other_element = get_port_element(other_port)
|
||||
if other_element:
|
||||
results.append(other_element)
|
||||
return results
|
||||
@@ -0,0 +1,81 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021, 2023 Dion Moult <dion@thinkmoult.com>, @Andrej730
|
||||
#
|
||||
# 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 json
|
||||
import os
|
||||
|
||||
import ifcopenshell.util.schema
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
entity_to_type_map: dict[ifcopenshell.util.schema.IFC_SCHEMA, dict[str, list[str]]] = {}
|
||||
type_to_entity_map: dict[ifcopenshell.util.schema.IFC_SCHEMA, dict[str, list[str]]] = {}
|
||||
|
||||
mapped_schemas = {
|
||||
"IFC2X3": "entity_to_type_map_2x3.json",
|
||||
"IFC4": "entity_to_type_map_4.json",
|
||||
"IFC4X3": "entity_to_type_map_4x3.json",
|
||||
}
|
||||
for schema in mapped_schemas:
|
||||
# load entity maps from json
|
||||
schema_path = os.path.join(cwd, mapped_schemas[schema])
|
||||
with open(schema_path) as f:
|
||||
entity_to_type_map[schema] = json.load(f)
|
||||
|
||||
# create type_to_entity map
|
||||
type_to_entity_map[schema] = {}
|
||||
for element, element_types in entity_to_type_map[schema].items():
|
||||
for element_type in element_types:
|
||||
type_to_entity_map[schema].setdefault(element_type, []).append(element)
|
||||
|
||||
if schema == "IFC2X3":
|
||||
# Prioritize IfcBuildingElementProxyType if it's available as it seems to be the most generic type.
|
||||
# Otherwise classes that don't have a special type in IFC2X3 (e.g. IfcBuildingElementPart, IfcRoof)
|
||||
# have IfcBeamType as their first matching type, which can be confusing.
|
||||
for occurrence_type, element_types in entity_to_type_map[schema].items():
|
||||
if "IfcBuildingElementProxyType" in element_types:
|
||||
element_types.sort(key=lambda x: x == "IfcBuildingElementProxyType", reverse=True)
|
||||
|
||||
# There is no official mapping for IFC2X3 but this method gets us something that looks correct
|
||||
#
|
||||
# NOTE: currently `type_to_entity_map` in IFC2X3 doesn't completely match `entity_to_type_map`,
|
||||
# e.g. `get_applicabl_types(IfcRoof)` returns `[IfcBuildingElementProxyType, IfcBeamType, ...]`
|
||||
# but `get_applicable_entities(IfcBuildingElementProxyType)` returns `[IfcBuildingElementProxy`].
|
||||
for element_type, elements in type_to_entity_map[schema].items():
|
||||
# need to take both Type (4 symbols) and Style (5 symbols) into account
|
||||
guessed_element = element_type[:-5] if element_type.endswith("Style") else element_type[:-4]
|
||||
if guessed_element in elements:
|
||||
type_to_entity_map[schema][element_type] = [e for e in elements if guessed_element in e]
|
||||
|
||||
|
||||
def get_applicable_types(ifc_class: str, schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4") -> list[str]:
|
||||
"""Get applicable types IFC classes for the occurrence IFC class.
|
||||
|
||||
E.g. "IfcWindow" -> ["IfcWindowType"].
|
||||
"""
|
||||
schema = ifcopenshell.util.schema.get_fallback_schema(schema.upper())
|
||||
return entity_to_type_map[schema].get(ifc_class, [])
|
||||
|
||||
|
||||
def get_applicable_entities(ifc_type_class: str, schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4") -> list[str]:
|
||||
"""Get applicable occurrence IFC classes for the type IFC class.
|
||||
|
||||
E.g. "IfcWindowType" -> ["IfcWindow"].
|
||||
"""
|
||||
schema = ifcopenshell.util.schema.get_fallback_schema(schema.upper())
|
||||
return type_to_entity_map[schema].get(ifc_type_class, [])
|
||||
@@ -0,0 +1,935 @@
|
||||
# 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 collections.abc import Generator
|
||||
from fractions import Fraction
|
||||
from math import pi
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
|
||||
prefixes = {
|
||||
"EXA": 1e18,
|
||||
"PETA": 1e15,
|
||||
"TERA": 1e12,
|
||||
"GIGA": 1e9,
|
||||
"MEGA": 1e6,
|
||||
"KILO": 1e3,
|
||||
"HECTO": 1e2,
|
||||
"DECA": 1e1,
|
||||
"DECI": 1e-1,
|
||||
"CENTI": 1e-2,
|
||||
"MILLI": 1e-3,
|
||||
"MICRO": 1e-6,
|
||||
"NANO": 1e-9,
|
||||
"PICO": 1e-12,
|
||||
"FEMTO": 1e-15,
|
||||
"ATTO": 1e-18,
|
||||
}
|
||||
|
||||
unit_names = [
|
||||
"AMPERE",
|
||||
"BECQUEREL",
|
||||
"CANDELA",
|
||||
"COULOMB",
|
||||
"CUBIC_METRE",
|
||||
"DEGREE_CELSIUS",
|
||||
"FARAD",
|
||||
"GRAM",
|
||||
"GRAY",
|
||||
"HENRY",
|
||||
"HERTZ",
|
||||
"JOULE",
|
||||
"KELVIN",
|
||||
"LUMEN",
|
||||
"LUX",
|
||||
"MOLE",
|
||||
"NEWTON",
|
||||
"OHM",
|
||||
"PASCAL",
|
||||
"RADIAN",
|
||||
"SECOND",
|
||||
"SIEMENS",
|
||||
"SIEVERT",
|
||||
"SQUARE_METRE",
|
||||
"METRE",
|
||||
"STERADIAN",
|
||||
"TESLA",
|
||||
"VOLT",
|
||||
"WATT",
|
||||
"WEBER",
|
||||
]
|
||||
|
||||
si_dimensions = {
|
||||
"METRE": (1, 0, 0, 0, 0, 0, 0),
|
||||
"SQUARE_METRE": (2, 0, 0, 0, 0, 0, 0),
|
||||
"CUBIC_METRE": (3, 0, 0, 0, 0, 0, 0),
|
||||
"GRAM": (0, 1, 0, 0, 0, 0, 0),
|
||||
"SECOND": (0, 0, 1, 0, 0, 0, 0),
|
||||
"AMPERE": (0, 0, 0, 1, 0, 0, 0),
|
||||
"KELVIN": (0, 0, 0, 0, 1, 0, 0),
|
||||
"MOLE": (0, 0, 0, 0, 0, 1, 0),
|
||||
"CANDELA": (0, 0, 0, 0, 0, 0, 1),
|
||||
"RADIAN": (0, 0, 0, 0, 0, 0, 0),
|
||||
"STERADIAN": (0, 0, 0, 0, 0, 0, 0),
|
||||
"HERTZ": (0, 0, -1, 0, 0, 0, 0),
|
||||
"NEWTON": (1, 1, -2, 0, 0, 0, 0),
|
||||
"PASCAL": (-1, 1, -2, 0, 0, 0, 0),
|
||||
"JOULE": (2, 1, -2, 0, 0, 0, 0),
|
||||
"WATT": (2, 1, -3, 0, 0, 0, 0),
|
||||
"COULOMB": (0, 0, 1, 1, 0, 0, 0),
|
||||
"VOLT": (2, 1, -3, -1, 0, 0, 0),
|
||||
"FARAD": (-2, -1, 4, 2, 0, 0, 0),
|
||||
"OHM": (2, 1, -3, -2, 0, 0, 0),
|
||||
"SIEMENS": (-2, -1, 3, 2, 0, 0, 0),
|
||||
"WEBER": (2, 1, -2, -1, 0, 0, 0),
|
||||
"TESLA": (0, 1, -2, -1, 0, 0, 0),
|
||||
"HENRY": (2, 1, -2, -2, 0, 0, 0),
|
||||
"DEGREE_CELSIUS": (0, 0, 0, 0, 1, 0, 0),
|
||||
"LUMEN": (0, 0, 0, 0, 0, 0, 1),
|
||||
"LUX": (-2, 0, 0, 0, 0, 0, 1),
|
||||
"BECQUEREL": (0, 0, -1, 0, 0, 0, 0),
|
||||
"GRAY": (2, 0, -2, 0, 0, 0, 0),
|
||||
"SIEVERT": (2, 0, -2, 0, 0, 0, 0),
|
||||
"OTHERWISE": (0, 0, 0, 0, 0, 0, 0),
|
||||
}
|
||||
|
||||
# See https://github.com/buildingSMART/IFC4.3.x-development/issues/72
|
||||
si_type_names = {
|
||||
"ABSORBEDDOSEUNIT": "GRAY",
|
||||
"AMOUNTOFSUBSTANCEUNIT": "MOLE",
|
||||
"AREAUNIT": "SQUARE_METRE",
|
||||
"DOSEEQUIVALENTUNIT": "SIEVERT",
|
||||
"ELECTRICCAPACITANCEUNIT": "FARAD",
|
||||
"ELECTRICCHARGEUNIT": "COULOMB",
|
||||
"ELECTRICCONDUCTANCEUNIT": "SIEMENS",
|
||||
"ELECTRICCURRENTUNIT": "AMPERE",
|
||||
"ELECTRICRESISTANCEUNIT": "OHM",
|
||||
"ELECTRICVOLTAGEUNIT": "VOLT",
|
||||
"ENERGYUNIT": "JOULE",
|
||||
"FORCEUNIT": "NEWTON",
|
||||
"FREQUENCYUNIT": "HERTZ",
|
||||
"ILLUMINANCEUNIT": "LUX",
|
||||
"INDUCTANCEUNIT": "HENRY",
|
||||
"LENGTHUNIT": "METRE",
|
||||
"LUMINOUSFLUXUNIT": "LUMEN",
|
||||
"LUMINOUSINTENSITYUNIT": "CANDELA",
|
||||
"MAGNETICFLUXDENSITYUNIT": "TESLA",
|
||||
"MAGNETICFLUXUNIT": "WEBER",
|
||||
"MASSUNIT": "GRAM",
|
||||
"PLANEANGLEUNIT": "RADIAN",
|
||||
"POWERUNIT": "WATT",
|
||||
"PRESSUREUNIT": "PASCAL",
|
||||
"RADIOACTIVITYUNIT": "BECQUEREL",
|
||||
"SOLIDANGLEUNIT": "STERADIAN",
|
||||
"THERMODYNAMICTEMPERATUREUNIT": "KELVIN", # Or, DEGREE_CELSIUS, but this is a quirk of IFC
|
||||
"TIMEUNIT": "SECOND",
|
||||
"VOLUMEUNIT": "CUBIC_METRE",
|
||||
"USERDEFINED": "METRE",
|
||||
}
|
||||
|
||||
# See IfcDimensionalExponents:
|
||||
# (Length, Mass, Time, ElectricCurrent, ThermodynamicTemperature, AmountOfSubstance, LuminousIntensity)
|
||||
named_dimensions = {
|
||||
"ABSORBEDDOSEUNIT": (2, 0, -2, 0, 0, 0, 0),
|
||||
"AMOUNTOFSUBSTANCEUNIT": (0, 0, 0, 0, 0, 1, 0),
|
||||
"AREAUNIT": (2, 0, 0, 0, 0, 0, 0),
|
||||
"DOSEEQUIVALENTUNIT": (2, 0, -2, 0, 0, 0, 0),
|
||||
"ELECTRICCAPACITANCEUNIT": (-2, -1, 4, 2, 0, 0, 0),
|
||||
"ELECTRICCHARGEUNIT": (0, 0, 1, 1, 0, 0, 0),
|
||||
"ELECTRICCONDUCTANCEUNIT": (-2, -1, 3, 2, 0, 0, 0),
|
||||
"ELECTRICCURRENTUNIT": (0, 0, 0, 1, 0, 0, 0),
|
||||
"ELECTRICRESISTANCEUNIT": (2, 1, -3, -2, 0, 0, 0),
|
||||
"ELECTRICVOLTAGEUNIT": (2, 1, -3, -1, 0, 0, 0),
|
||||
"ENERGYUNIT": (2, 1, -2, 0, 0, 0, 0),
|
||||
"FORCEUNIT": (1, 1, -2, 0, 0, 0, 0),
|
||||
"FREQUENCYUNIT": (0, 0, -1, 0, 0, 0, 0),
|
||||
"ILLUMINANCEUNIT": (-2, 0, 0, 0, 0, 1, 1),
|
||||
"INDUCTANCEUNIT": (2, 1, -2, -2, 0, 0, 0),
|
||||
"LENGTHUNIT": (1, 0, 0, 0, 0, 0, 0),
|
||||
"LUMINOUSFLUXUNIT": (0, 0, 0, 0, 0, 1, 1),
|
||||
"LUMINOUSINTENSITYUNIT": (0, 0, 0, 0, 0, 0, 1),
|
||||
"MAGNETICFLUXDENSITYUNIT": (0, 1, -2, -1, 0, 0, 0),
|
||||
"MAGNETICFLUXUNIT": (2, 1, -2, -1, 0, 0, 0),
|
||||
"MASSUNIT": (0, 1, 0, 0, 0, 0, 0),
|
||||
"PLANEANGLEUNIT": (0, 0, 0, 0, 0, 0, 0),
|
||||
"POWERUNIT": (2, 1, -3, 0, 0, 0, 0),
|
||||
"PRESSUREUNIT": (-1, 1, -2, 0, 0, 0, 0),
|
||||
"RADIOACTIVITYUNIT": (0, 0, -1, 0, 0, 0, 0),
|
||||
"SOLIDANGLEUNIT": (0, 0, 0, 0, 0, 0, 0),
|
||||
"THERMODYNAMICTEMPERATUREUNIT": (0, 0, 0, 0, 1, 0, 0),
|
||||
"TIMEUNIT": (0, 0, 1, 0, 0, 0, 0),
|
||||
"VOLUMEUNIT": (3, 0, 0, 0, 0, 0, 0),
|
||||
"USERDEFINED": (0, 0, 0, 0, 0, 0, 0),
|
||||
}
|
||||
|
||||
si_conversions = {
|
||||
"thou": 0.0000254,
|
||||
"inch": 0.0254,
|
||||
"foot": 0.3048,
|
||||
"yard": 0.914,
|
||||
"mile": 1609,
|
||||
"square thou": 6.4516e-10,
|
||||
"square inch": 0.0006452,
|
||||
"square foot": 0.09290304,
|
||||
"square yard": 0.83612736,
|
||||
"acre": 4046.86,
|
||||
"square mile": 2588881,
|
||||
"cubic thou": 1.6387064e-14,
|
||||
"cubic inch": 0.00001639,
|
||||
"cubic foot": 0.02831684671168849,
|
||||
"cubic yard": 0.7636,
|
||||
"cubic mile": 4165509529,
|
||||
"litre": 0.001,
|
||||
"fluid ounce UK": 0.0000284130625,
|
||||
"fluid ounce US": 0.00002957353,
|
||||
"pint UK": 0.000568,
|
||||
"pint US": 0.000473,
|
||||
"gallon UK": 0.004546,
|
||||
"gallon US": 0.003785,
|
||||
"degree": pi / 180,
|
||||
"ounce": 0.02835,
|
||||
"pound": 0.454,
|
||||
"ton UK": 1016.0469088,
|
||||
"ton US": 907.18474,
|
||||
"tonne": 1000.0,
|
||||
"lbf": 4.4482216153,
|
||||
"kip": 4448.2216153,
|
||||
"psi": 6894.7572932,
|
||||
"ksi": 6894757.2932,
|
||||
"minute": 60,
|
||||
"hour": 3600,
|
||||
"day": 86400,
|
||||
"btu": 1055.056,
|
||||
"fahrenheit": 1.8,
|
||||
}
|
||||
|
||||
si_offsets = {
|
||||
"fahrenheit": -459.67,
|
||||
}
|
||||
|
||||
imperial_types = {
|
||||
"thou": "LENGTHUNIT",
|
||||
"inch": "LENGTHUNIT",
|
||||
"foot": "LENGTHUNIT",
|
||||
"yard": "LENGTHUNIT",
|
||||
"mile": "LENGTHUNIT",
|
||||
"square thou": "AREAUNIT",
|
||||
"square inch": "AREAUNIT",
|
||||
"square foot": "AREAUNIT",
|
||||
"square yard": "AREAUNIT",
|
||||
"acre": "AREAUNIT",
|
||||
"square mile": "AREAUNIT",
|
||||
"cubic thou": "VOLUMEUNIT",
|
||||
"cubic inch": "VOLUMEUNIT",
|
||||
"cubic foot": "VOLUMEUNIT",
|
||||
"cubic yard": "VOLUMEUNIT",
|
||||
"cubic mile": "VOLUMEUNIT",
|
||||
"litre": "VOLUMEUNIT",
|
||||
"fluid ounce UK": "VOLUMEUNIT",
|
||||
"fluid ounce US": "VOLUMEUNIT",
|
||||
"pint UK": "VOLUMEUNIT",
|
||||
"pint US": "VOLUMEUNIT",
|
||||
"gallon UK": "VOLUMEUNIT",
|
||||
"gallon US": "VOLUMEUNIT",
|
||||
"degree": "PLANEANGLEUNIT",
|
||||
"ounce": "MASSUNIT",
|
||||
"pound": "MASSUNIT",
|
||||
"ton UK": "MASSUNIT",
|
||||
"ton US": "MASSUNIT",
|
||||
"tonne": "MASSUNIT",
|
||||
"lbf": "FORCEUNIT",
|
||||
"kip": "FORCEUNIT",
|
||||
"psi": "PRESSUREUNIT",
|
||||
"ksi": "PRESSUREUNIT",
|
||||
"minute": "TIMEUNIT",
|
||||
"hour": "TIMEUNIT",
|
||||
"day": "TIMEUNIT",
|
||||
"btu": "ENERGYUNIT",
|
||||
"fahrenheit": "THERMODYNAMICTEMPERATUREUNIT",
|
||||
}
|
||||
|
||||
prefix_symbols = {
|
||||
"EXA": "E",
|
||||
"PETA": "P",
|
||||
"TERA": "T",
|
||||
"GIGA": "G",
|
||||
"MEGA": "M",
|
||||
"KILO": "k",
|
||||
"HECTO": "h",
|
||||
"DECA": "da",
|
||||
"DECI": "d",
|
||||
"CENTI": "c",
|
||||
"MILLI": "m",
|
||||
"MICRO": "μ",
|
||||
"NANO": "n",
|
||||
"PICO": "p",
|
||||
"FEMTO": "f",
|
||||
"ATTO": "a",
|
||||
}
|
||||
|
||||
unit_symbols = {
|
||||
# si units
|
||||
"CUBIC_METRE": "m3",
|
||||
"GRAM": "g",
|
||||
"SECOND": "s",
|
||||
"SQUARE_METRE": "m2",
|
||||
"METRE": "m",
|
||||
"NEWTON": "N",
|
||||
"PASCAL": "Pa",
|
||||
# conversion based units
|
||||
"pound-force": "lbf",
|
||||
"pound-force per square inch": "psi",
|
||||
"thou": "th",
|
||||
"inch": "in",
|
||||
"foot": "ft",
|
||||
"yard": "yd",
|
||||
"mile": "mi",
|
||||
"square thou": "th2",
|
||||
"square inch": "in2",
|
||||
"square foot": "ft2",
|
||||
"square yard": "yd2",
|
||||
"acre": "ac",
|
||||
"square mile": "mi2",
|
||||
"cubic thou": "th3",
|
||||
"cubic inch": "in3",
|
||||
"cubic foot": "ft3",
|
||||
"cubic yard": "yd3",
|
||||
"cubic mile": "mi3",
|
||||
"litre": "L",
|
||||
"fluid ounce UK": "fl oz",
|
||||
"fluid ounce US": "fl oz",
|
||||
"pint UK": "pt",
|
||||
"pint US": "pt",
|
||||
"gallon UK": "gal",
|
||||
"gallon US": "gal",
|
||||
"degree": "°",
|
||||
"ounce": "oz",
|
||||
"pound": "lb",
|
||||
"ton UK": "ton",
|
||||
"ton US": "ton",
|
||||
"tonne": "t",
|
||||
"lbf": "lbf",
|
||||
"kip": "kip",
|
||||
"psi": "psi",
|
||||
"ksi": "ksi",
|
||||
"minute": "min",
|
||||
"hour": "hr",
|
||||
"day": "day",
|
||||
"btu": "btu",
|
||||
"fahrenheit": "°F",
|
||||
}
|
||||
|
||||
QUANTITY_CLASS = Literal[
|
||||
"IfcQuantityCount",
|
||||
"IfcQuantityNumber",
|
||||
"IfcQuantityLength",
|
||||
"IfcQuantityArea",
|
||||
"IfcQuantityVolume",
|
||||
"IfcQuantityWeight",
|
||||
"IfcQuantityTime",
|
||||
"IfcQuantityCount",
|
||||
]
|
||||
|
||||
MEASURE_CLASS = Literal[
|
||||
"IfcNumericMeasure",
|
||||
"IfcLengthMeasure",
|
||||
"IfcAreaMeasure",
|
||||
"IfcVolumeMeasure",
|
||||
"IfcMassMeasure",
|
||||
]
|
||||
|
||||
|
||||
def get_prefix(text):
|
||||
if text:
|
||||
for prefix in prefixes.keys():
|
||||
if prefix in text.upper():
|
||||
return prefix
|
||||
|
||||
|
||||
def get_prefix_multiplier(text):
|
||||
if not text:
|
||||
return 1
|
||||
prefix = get_prefix(text)
|
||||
if prefix:
|
||||
return prefixes[prefix]
|
||||
return 1
|
||||
|
||||
|
||||
def get_unit_name(text: str) -> Union[str, None]:
|
||||
"""Get unit name from str, if unit is in SI."""
|
||||
text = text.upper().replace("METER", "METRE")
|
||||
for name in unit_names:
|
||||
if name.replace("_", " ") in text:
|
||||
return name
|
||||
|
||||
|
||||
def get_unit_name_universal(text: str) -> Union[str, None]:
|
||||
"""Get unit name from str, supports both SI and imperial system.
|
||||
|
||||
Can be used to provide units for `convert()`"""
|
||||
text = text.upper().replace("METER", "METRE")
|
||||
for name in unit_names:
|
||||
if name.replace("_", " ") in text:
|
||||
return name
|
||||
for name in imperial_types:
|
||||
if name.upper() in text:
|
||||
return name
|
||||
|
||||
|
||||
def get_full_unit_name(unit: ifcopenshell.entity_instance) -> str:
|
||||
prefix = getattr(unit, "Prefix", None) or ""
|
||||
return prefix + unit.Name.upper()
|
||||
|
||||
|
||||
def get_si_dimensions(name):
|
||||
return si_dimensions.get(name, si_dimensions["OTHERWISE"])
|
||||
|
||||
|
||||
def get_named_dimensions(name):
|
||||
return named_dimensions.get(name, (0, 0, 0, 0, 0, 0, 0))
|
||||
|
||||
|
||||
def get_unit_assignment(ifc_file: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
|
||||
return ifc_file.by_type("IfcProject")[0].UnitsInContext
|
||||
|
||||
|
||||
def cache_units(ifc_file: ifcopenshell.file) -> None:
|
||||
"""Cache the default units for performance
|
||||
|
||||
Repetitively fetching project units (such as for determining the unit of a
|
||||
property) can be costly. This enables a cache to make it faster. If the
|
||||
project units change, you can update the cache by rerunning this function.
|
||||
|
||||
:param ifc_file: The IFC file.
|
||||
"""
|
||||
ifc_file.units = {}
|
||||
if assignment := get_unit_assignment(ifc_file):
|
||||
ifc_file.units = {u.UnitType: u for u in assignment.Units if getattr(u, "UnitType", None)}
|
||||
|
||||
|
||||
def clear_unit_cache(ifc_file: ifcopenshell.file) -> None:
|
||||
"""Clears the unit cache of the project
|
||||
|
||||
:param ifc_file: The IFC file.
|
||||
"""
|
||||
ifc_file.units = {}
|
||||
|
||||
|
||||
def get_project_unit(
|
||||
ifc_file: ifcopenshell.file, unit_type: str, use_cache: bool = False
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Get the default project unit of a particular unit type
|
||||
|
||||
:param ifc_file: The IFC file.
|
||||
:param unit_type: The type of unit, taken from the list of IFC unit types,
|
||||
such as "LENGTHUNIT".
|
||||
:return: The IFC unit entity, or nothing if there is no default project
|
||||
unit defined.
|
||||
"""
|
||||
if use_cache and not ifc_file.units:
|
||||
cache_units(ifc_file)
|
||||
if units := ifc_file.units:
|
||||
return units.get(unit_type, None)
|
||||
if unit_assignment := get_unit_assignment(ifc_file):
|
||||
for unit in unit_assignment.Units or []:
|
||||
if getattr(unit, "UnitType", None) == unit_type:
|
||||
return unit
|
||||
|
||||
|
||||
def get_property_unit(
|
||||
prop: ifcopenshell.entity_instance, ifc_file: Union[ifcopenshell.file, None], use_cache: bool = False
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Gets the unit definition of a property or quantity
|
||||
|
||||
Properties and quantities in psets and qtos can be associated with a unit.
|
||||
This unit may be defined at the property itself explicitly, or if not
|
||||
specified, fallback to the project default.
|
||||
|
||||
:param prop: The IfcProperty instance. You can fetch this via the instance
|
||||
ID if doing :func:`ifcopenshell.util.element.get_psets` with
|
||||
``verbose=True``.
|
||||
:param ifc_file: The IFC file being used. This is necessary to check
|
||||
default project units.
|
||||
:return: The IFC unit entity, or nothing if there is no default project
|
||||
unit defined.
|
||||
"""
|
||||
if unit := getattr(prop, "Unit", None):
|
||||
return unit
|
||||
|
||||
value = None
|
||||
measure_class = None
|
||||
|
||||
if prop.is_a("IfcPhysicalSimpleQuantity"):
|
||||
entity = prop.wrapped_data.declaration().as_entity()
|
||||
measure_class = entity.attribute_by_index(3).type_of_attribute().declared_type().name()
|
||||
elif prop.is_a("IfcPropertySingleValue"):
|
||||
measure_class = prop.NominalValue.is_a()
|
||||
elif prop.is_a("IfcPropertyEnumeratedValue"):
|
||||
if prop.EnumerationReference:
|
||||
if unit := prop.EnumerationReference.Unit:
|
||||
return unit
|
||||
if value := next(iter(prop.EnumerationReference.EnumerationValues or ()), None):
|
||||
measure_class = value.is_a()
|
||||
if value := next(iter(prop.EnumerationValues or ()), None):
|
||||
measure_class = value.is_a()
|
||||
elif prop.is_a("IfcPropertyListValue"):
|
||||
if value := next(iter(prop.ListValues or ()), None):
|
||||
measure_class = value.is_a()
|
||||
elif prop.is_a("IfcPropertyBoundedValue"):
|
||||
if value := (prop.UpperBoundValue or prop.LowerBoundValue or prop.SetPointValue):
|
||||
measure_class = value.is_a()
|
||||
|
||||
if measure_class and (unit_type := get_measure_unit_type(measure_class)):
|
||||
if not ifc_file:
|
||||
ifc_file = prop.file
|
||||
return get_project_unit(ifc_file, unit_type, use_cache=use_cache)
|
||||
|
||||
|
||||
def get_property_table_unit(
|
||||
prop: ifcopenshell.entity_instance, ifc_file: Union[ifcopenshell.file, None], use_cache: bool = False
|
||||
) -> dict[str, Union[ifcopenshell.entity_instance, None]]:
|
||||
"""
|
||||
Gets the unit definition of a property table
|
||||
|
||||
Properties and quantities in psets and qtos can be associated with a unit.
|
||||
This unit may be defined at the property itself explicitly, or if not
|
||||
specified, fallback to the project default.
|
||||
|
||||
:param prop: The property instance. You can fetch this via the instance ID
|
||||
if doing :func:`ifcopenshell.util.element.get_psets` with
|
||||
``verbose=True``.
|
||||
|
||||
:param ifc_file: The IFC file being used. This is necessary to check
|
||||
default project units.
|
||||
|
||||
:return: A dictionary containing IFC unit entity by keyword.
|
||||
If a unit-entity is missing,
|
||||
the value associated to the key is `null`.
|
||||
"""
|
||||
if not ifc_file:
|
||||
ifc_file = prop.file
|
||||
defining_unit = None
|
||||
if unit := prop.DefiningUnit:
|
||||
defining_unit = unit
|
||||
elif value := next(iter(prop.DefiningValues or ()), None):
|
||||
if unit_type := get_measure_unit_type(value.is_a()):
|
||||
defining_unit = get_project_unit(ifc_file, unit_type, use_cache=use_cache)
|
||||
|
||||
defined_unit = None
|
||||
if unit := prop.DefinedUnit:
|
||||
defined_unit = unit
|
||||
elif value := next(iter(prop.DefinedValues or ()), None):
|
||||
if unit_type := get_measure_unit_type(value.is_a()):
|
||||
defined_unit = get_project_unit(ifc_file, unit_type, use_cache=use_cache)
|
||||
|
||||
return {
|
||||
"DefiningUnit": defining_unit,
|
||||
"DefinedUnit": defined_unit,
|
||||
}
|
||||
|
||||
|
||||
def get_unit_measure_class(unit_type: str) -> MEASURE_CLASS:
|
||||
"""Get the IFC measure class for a unit type.
|
||||
|
||||
IFC has specific classes used to measure different units. An example of an
|
||||
IFC measure class is ``IfcLengthMeasure``. An example of the correlating
|
||||
unit type (i.e. the IfcUnitEnum) is ``LENGTHUNIT``.
|
||||
|
||||
The inverse function of this is :func:`get_measure_unit_type`
|
||||
|
||||
:param unit_type: A string chosen from IfcUnitEnum, such as LENGTHUNIT
|
||||
"""
|
||||
if unit_type == "USERDEFINED":
|
||||
# See https://github.com/buildingSMART/IFC4.3.x-development/issues/71
|
||||
return "IfcNumericMeasure"
|
||||
return "Ifc" + unit_type[0:-4].lower().capitalize() + "Measure"
|
||||
|
||||
|
||||
def get_measure_unit_type(measure_class: MEASURE_CLASS) -> str:
|
||||
"""Get the unit type of an IFC measure class
|
||||
|
||||
IFC has different unit types which can be associated with units (e.g. SI
|
||||
units, imperial units, derived units, etc). An example of a unit type (i.e.
|
||||
an IfcUnitEnum) is ``LENGTHUNIT``. An example of the correlating measure
|
||||
class used to store length data is ``IfcLengthMeasure``.
|
||||
|
||||
The inverse fucntion of this is :func:`get_unit_measure_class`
|
||||
|
||||
:param measure_class: The measure class, such as ``IfcLengthMeasure``. If
|
||||
you have an ``IfcPropertySingleValue``, you can get this using
|
||||
``prop.NominalValue.is_a()``.
|
||||
:return: The unit type, as an uppercase value of IfcUnitEnum.
|
||||
"""
|
||||
if measure_class == "IfcNumericMeasure":
|
||||
# See https://github.com/buildingSMART/IFC4.3.x-development/issues/71
|
||||
return "USERDEFINED"
|
||||
for text in ("Ifc", "Measure", "Non", "Positive", "Negative"):
|
||||
measure_class = measure_class.replace(text, "")
|
||||
return measure_class.upper() + "UNIT"
|
||||
|
||||
|
||||
def get_symbol_measure_class(symbol: Optional[str] = None) -> MEASURE_CLASS:
|
||||
# Dumb, but everybody gets it, unlike regex golf
|
||||
if not symbol:
|
||||
return "IfcNumericMeasure"
|
||||
symbol = symbol.lower()
|
||||
if symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]:
|
||||
return "IfcLengthMeasure"
|
||||
elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]:
|
||||
return "IfcAreaMeasure"
|
||||
elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]:
|
||||
return "IfcVolumeMeasure"
|
||||
elif symbol in ["kg", "g", "mt", "kt", "t"]:
|
||||
return "IfcMassMeasure"
|
||||
elif symbol in ["day", "d", "hour", "hr", "h", "minute", "min", "m", "second", "sec", "s"]:
|
||||
return "IfcTimeMeasure"
|
||||
return "IfcNumericMeasure"
|
||||
|
||||
|
||||
def get_symbol_quantity_class(symbol: Optional[str] = None) -> QUANTITY_CLASS:
|
||||
# Dumb, but everybody gets it, unlike regex golf
|
||||
if not symbol:
|
||||
return "IfcQuantityCount"
|
||||
symbol = symbol.lower()
|
||||
if symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]:
|
||||
return "IfcQuantityLength"
|
||||
elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]:
|
||||
return "IfcQuantityArea"
|
||||
elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]:
|
||||
return "IfcQuantityVolume"
|
||||
elif symbol in ["kg", "g", "mt", "kt", "t"]:
|
||||
return "IfcQuantityWeight"
|
||||
elif symbol in ["day", "d", "hour", "hr", "h", "minute", "min", "m", "second", "sec", "s"]:
|
||||
return "IfcQuantityTime"
|
||||
return "IfcQuantityCount"
|
||||
|
||||
|
||||
def get_unit_symbol(unit: ifcopenshell.entity_instance) -> str:
|
||||
symbol: str = ""
|
||||
if unit.is_a("IfcSIUnit"):
|
||||
symbol += prefix_symbols.get(unit.Prefix, "")
|
||||
symbol += unit_symbols.get(unit.Name.replace("METER", "METRE"), "?")
|
||||
if unit.is_a("IfcContextDependentUnit") and unit.UnitType == "USERDEFINED":
|
||||
symbol = unit.Name
|
||||
return symbol
|
||||
|
||||
|
||||
def convert_unit(value: float, from_unit: ifcopenshell.entity_instance, to_unit: ifcopenshell.entity_instance) -> float:
|
||||
"""Convert from one unit to another unit
|
||||
|
||||
:param value: The numeric value you want to convert
|
||||
:param from_unit: The IfcNamedUnit to confirm from.
|
||||
:param to_unit: The IfcNamedUnit to confirm from.
|
||||
:return: The converted value.
|
||||
"""
|
||||
return convert(
|
||||
value, getattr(from_unit, "Prefix", None), from_unit.Name, getattr(to_unit, "Prefix", None), to_unit.Name
|
||||
)
|
||||
|
||||
|
||||
def convert(value: float, from_prefix: Optional[str], from_unit: str, to_prefix: Optional[str], to_unit: str) -> float:
|
||||
"""Converts between length, area, and volume units
|
||||
|
||||
In this case, you manually specify the names and (optionally) prefixes to
|
||||
convert to and from. In case you want to automatically convert to units
|
||||
already available as IFC entities, consider using convert_unit() instead.
|
||||
|
||||
:param value: The numeric value you want to convert
|
||||
:param from_prefix: A prefix from IfcSIPrefix. Can be None
|
||||
:param from_unit: IfcSIUnitName or IfcConversionBasedUnit.Name
|
||||
:param to_prefix: A prefix from IfcSIPrefix. Can be None
|
||||
:param to_unit: IfcSIUnitName or IfcConversionBasedUnit.Name
|
||||
:return: The converted value.
|
||||
"""
|
||||
if from_unit.lower() in si_conversions:
|
||||
value *= si_conversions[from_unit.lower()]
|
||||
elif from_prefix:
|
||||
value *= get_prefix_multiplier(from_prefix)
|
||||
if "SQUARE" in from_unit:
|
||||
value *= get_prefix_multiplier(from_prefix)
|
||||
elif "CUBIC" in from_unit:
|
||||
value *= get_prefix_multiplier(from_prefix)
|
||||
value *= get_prefix_multiplier(from_prefix)
|
||||
if to_unit.lower() in si_conversions:
|
||||
return value * (1 / si_conversions[to_unit.lower()])
|
||||
elif to_prefix:
|
||||
value *= 1 / get_prefix_multiplier(to_prefix)
|
||||
if "SQUARE" in from_unit:
|
||||
value *= 1 / get_prefix_multiplier(to_prefix)
|
||||
elif "CUBIC" in from_unit:
|
||||
value *= 1 / get_prefix_multiplier(to_prefix)
|
||||
value *= 1 / get_prefix_multiplier(to_prefix)
|
||||
return value
|
||||
|
||||
|
||||
def calculate_unit_scale(ifc_file: ifcopenshell.file, unit_type: str = "LENGTHUNIT") -> float:
|
||||
"""Returns a unit scale factor to convert to and from IFC project units and SI units.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifc_project_length * unit_scale = si_meters
|
||||
si_meters / unit_scale = ifc_project_length
|
||||
|
||||
:param ifc_file: The IFC file.
|
||||
:param unit_type: The type of SI unit, defaults to "LENGTHUNIT"
|
||||
:returns: The scale factor
|
||||
"""
|
||||
if (
|
||||
type(ifc_file) is ifcopenshell.file
|
||||
and unit_type
|
||||
not in ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier)
|
||||
.declaration_by_name("IfcUnitEnum")
|
||||
.enumeration_items()
|
||||
):
|
||||
raise ValueError(f"Unit type {unit_type!r} does not name a valid type")
|
||||
|
||||
# Currently we assume that all ifc projects must have IfcProject.
|
||||
if not (projects := ifc_file.by_type("IfcProject")) or not (units := projects[0].UnitsInContext):
|
||||
return 1
|
||||
unit_scale = 1
|
||||
unit: ifcopenshell.entity_instance
|
||||
for unit in units.Units:
|
||||
if getattr(unit, "UnitType", ...) != unit_type:
|
||||
continue
|
||||
while unit.is_a("IfcConversionBasedUnit"):
|
||||
conversion_factor = unit.ConversionFactor
|
||||
unit_scale *= conversion_factor.ValueComponent.wrappedValue
|
||||
unit = conversion_factor.UnitComponent
|
||||
if unit.is_a("IfcSIUnit"):
|
||||
unit_scale *= get_prefix_multiplier(unit.Prefix)
|
||||
return unit_scale
|
||||
|
||||
|
||||
def format_length(
|
||||
value: float,
|
||||
precision: float,
|
||||
decimal_places: int = 2,
|
||||
suppress_zero_inches=True,
|
||||
unit_system: Literal["metric", "imperial"] = "imperial",
|
||||
input_unit: Literal["foot", "inch"] = "foot",
|
||||
output_unit: Literal["foot", "inch"] = "foot",
|
||||
) -> str:
|
||||
"""Formats a length for readability and imperial formatting
|
||||
|
||||
:param value: The value in meters if metric, or either decimal feet or
|
||||
inches if imperial depending on input_unit.
|
||||
:param precision: How precise the format should be. I.e. round to nearest.
|
||||
For imperial, it is 1/Nth. E.g. 12 means to the nearest 1/12th of an
|
||||
inch.
|
||||
:param decimal_places: How many decimal places to display. Defaults to 2.
|
||||
:param suppress_zero_inches: If imperial, whether or not to supress the
|
||||
inches if the inches is zero.
|
||||
:param unit_system: Choose whether your value is "metric" or "imperial"
|
||||
:param input_unit: If imperial, specify whether your value is "foot" or
|
||||
"inch".
|
||||
:param output_unit: If imperial, specify whether your value is "foot" to
|
||||
format as both feet and inches, or "inch" if only inches should be
|
||||
shown.
|
||||
:returns: The formatted string, such as 1' - 5 1/2".
|
||||
"""
|
||||
if unit_system == "imperial":
|
||||
if input_unit == "foot":
|
||||
feet = int(value)
|
||||
inches = (value - feet) * 12
|
||||
elif input_unit == "inch":
|
||||
inches = value % 12
|
||||
feet = int(round((value - inches) / 12))
|
||||
|
||||
# Round to the nearest 1/N
|
||||
nearest = round(inches * precision)
|
||||
|
||||
# Create a fraction based on the rounded value and the precision
|
||||
frac = Fraction(nearest, precision)
|
||||
|
||||
# If fraction is a whole number, format it accordingly
|
||||
if frac.denominator == 1:
|
||||
if suppress_zero_inches and frac.numerator == 0:
|
||||
if output_unit == "foot":
|
||||
return f"{feet}'"
|
||||
return f'{feet * 12}"'
|
||||
if output_unit == "foot":
|
||||
return f"{feet}' - {frac.numerator}\""
|
||||
return f'{(feet * 12) + frac.numerator}"'
|
||||
if frac.numerator > frac.denominator:
|
||||
remainder = frac.numerator % frac.denominator
|
||||
whole = int((frac.numerator - remainder) / frac.denominator)
|
||||
if output_unit == "foot":
|
||||
return f"{feet}' - {whole} {remainder}/{frac.denominator}\""
|
||||
return f'{(feet * 12) + whole} {remainder}/{frac.denominator}"'
|
||||
# When we have a proper fraction (numerator < denominator), show "0 frac"
|
||||
if output_unit == "foot":
|
||||
return f"{feet}' - 0 {frac.numerator}/{frac.denominator}\""
|
||||
return f'{feet * 12} {frac.numerator}/{frac.denominator}"'
|
||||
elif unit_system == "metric":
|
||||
rounded_val = round(value / precision) * precision
|
||||
return f"{rounded_val:.{decimal_places}f}"
|
||||
|
||||
|
||||
def is_attr_type(
|
||||
content_type: ifcopenshell_wrapper.parameter_type,
|
||||
ifc_unit_type_name: str,
|
||||
include_select_types: bool = True,
|
||||
) -> Union[ifcopenshell_wrapper.type_declaration, None]:
|
||||
cur_decl = content_type
|
||||
|
||||
if hasattr(cur_decl, "name") and cur_decl.name() == ifc_unit_type_name:
|
||||
return cur_decl
|
||||
|
||||
if include_select_types:
|
||||
if hasattr(cur_decl, "select_list"):
|
||||
for select_item in cur_decl.select_list():
|
||||
if is_attr_type(select_item, ifc_unit_type_name):
|
||||
return select_item
|
||||
|
||||
if hasattr(cur_decl, "declared_type"):
|
||||
return is_attr_type(cur_decl.declared_type(), ifc_unit_type_name, include_select_types)
|
||||
|
||||
if isinstance(cur_decl, ifcopenshell_wrapper.aggregation_type):
|
||||
# support aggregate of aggregates, as in IfcCartesianPointList3D.CoordList
|
||||
def get_declared_type_from_aggregate(cur_decl):
|
||||
cur_decl = cur_decl.type_of_element()
|
||||
if not isinstance(cur_decl, ifcopenshell_wrapper.aggregation_type):
|
||||
return cur_decl.declared_type()
|
||||
return get_declared_type_from_aggregate(cur_decl)
|
||||
|
||||
cur_decl = get_declared_type_from_aggregate(cur_decl)
|
||||
return is_attr_type(cur_decl, ifc_unit_type_name, include_select_types)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
FloatOrSequenceOfFloats = Union[float, tuple["FloatOrSequenceOfFloats", ...]]
|
||||
|
||||
|
||||
def iter_element_and_attributes_per_type(ifc_file: ifcopenshell.file, attr_type_name: str) -> Generator[
|
||||
tuple[
|
||||
ifcopenshell.entity_instance,
|
||||
ifcopenshell_wrapper.attribute,
|
||||
Union[FloatOrSequenceOfFloats, ifcopenshell.entity_instance],
|
||||
],
|
||||
None,
|
||||
None,
|
||||
]:
|
||||
schema = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier)
|
||||
|
||||
for element in ifc_file:
|
||||
entity = schema.declaration_by_name(element.is_a()).as_entity()
|
||||
assert entity
|
||||
attrs = entity.all_attributes()
|
||||
attrs_derived = entity.derived()
|
||||
for attr, val, is_derived in zip(attrs, list(element), attrs_derived):
|
||||
if is_derived:
|
||||
continue
|
||||
|
||||
# Get all methods and attributes of the element
|
||||
attr_type = attr.type_of_attribute()
|
||||
base_type = is_attr_type(attr_type, attr_type_name)
|
||||
if base_type is None:
|
||||
continue
|
||||
|
||||
if val is None:
|
||||
continue
|
||||
|
||||
if isinstance(val, ifcopenshell.entity_instance) and not val.is_a(attr_type_name):
|
||||
continue
|
||||
elif isinstance(val, tuple):
|
||||
if not val:
|
||||
continue
|
||||
val_ = val[0]
|
||||
# If it's a tuple of entities, just yield the entities we need to edit.
|
||||
if isinstance(val_, ifcopenshell.entity_instance):
|
||||
for val_ in val:
|
||||
if not val_.is_a(attr_type_name):
|
||||
continue
|
||||
yield element, attr, val_
|
||||
continue
|
||||
|
||||
yield element, attr, val
|
||||
|
||||
|
||||
def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str = "METER") -> ifcopenshell.file:
|
||||
"""Converts all units in an IFC file to the specified target units. Returns a new file."""
|
||||
import ifcopenshell.api.georeference
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
|
||||
prefix = get_prefix(target_units)
|
||||
si_unit = get_unit_name(target_units)
|
||||
|
||||
# Copy all elements from the original file to the patched file
|
||||
file_patched = ifcopenshell.file.from_string(ifc_file.wrapped_data.to_string())
|
||||
|
||||
old_length = get_project_unit(file_patched, "LENGTHUNIT")
|
||||
if si_unit:
|
||||
new_length = ifcopenshell.api.unit.add_si_unit(file_patched, unit_type="LENGTHUNIT", prefix=prefix)
|
||||
else:
|
||||
target_units = target_units.lower()
|
||||
if imperial_types.get(target_units) != "LENGTHUNIT":
|
||||
raise Exception(
|
||||
f'Couldn\'t identify target units "{target_units}". '
|
||||
'The method supports singular unit names like "CENTIMETER", "METER", "FOOT", etc.'
|
||||
)
|
||||
new_length = ifcopenshell.api.unit.add_conversion_based_unit(file_patched, name=target_units)
|
||||
|
||||
# support tuple of tuples, as in IfcCartesianPointList3D.CoordList
|
||||
def convert_value(value: FloatOrSequenceOfFloats) -> FloatOrSequenceOfFloats:
|
||||
if not isinstance(value, tuple):
|
||||
return convert_unit(value, old_length, new_length)
|
||||
return tuple(convert_value(v) for v in value)
|
||||
|
||||
# Traverse all elements and their nested attributes in the file and convert them
|
||||
for element, attr, val in iter_element_and_attributes_per_type(file_patched, "IfcLengthMeasure"):
|
||||
# NOTE: There is no risk of editing same entities twice as they're all recreated
|
||||
# after file is reloaded as `file_patched`.
|
||||
if isinstance(val, ifcopenshell.entity_instance):
|
||||
val.wrappedValue = convert_value(val.wrappedValue)
|
||||
else:
|
||||
new_value = convert_value(val)
|
||||
setattr(element, attr.name(), new_value)
|
||||
|
||||
has_map_unit = False
|
||||
if (
|
||||
ifc_file.schema == "IFC2X3"
|
||||
and (crs := ifcopenshell.util.element.get_pset(ifc_file.by_type("IfcProject")[0], name="ePSet_ProjectedCRS"))
|
||||
and crs.get("MapUnit")
|
||||
) or (ifc_file.schema != "IFC2X3" and (crs := ifc_file.by_type("IfcProjectedCRS")) and crs[0].MapUnit):
|
||||
has_map_unit = True
|
||||
|
||||
if has_map_unit:
|
||||
parameters = ifcopenshell.util.geolocation.get_helmert_transformation_parameters(ifc_file)
|
||||
ifcopenshell.api.georeference.edit_georeferencing(
|
||||
file_patched,
|
||||
coordinate_operation={
|
||||
"Eastings": parameters.e,
|
||||
"Northings": parameters.n,
|
||||
"OrthogonalHeight": parameters.h,
|
||||
"Scale": parameters.scale / convert_value(1),
|
||||
},
|
||||
)
|
||||
|
||||
unit_assignment = get_unit_assignment(file_patched)
|
||||
unit_assignment.Units = [new_length, *(u for u in unit_assignment.Units if u.UnitType != new_length.UnitType)]
|
||||
if not file_patched.get_total_inverses(old_length):
|
||||
ifcopenshell.util.element.remove_deep2(file_patched, old_length)
|
||||
|
||||
return file_patched
|
||||
Reference in New Issue
Block a user