Files
IfcOpenShell/src/ifcopenshell-python/ifcopenshell/util/representation.py
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

253 lines
9.3 KiB
Python
Raw Normal View History

# IfcOpenShell - IFC toolkit and geometry engine
2023-01-08 13:32:13 +05:00
# 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 numpy as np
2024-06-03 16:48:14 +05:00
import numpy.typing as npt
import ifcopenshell
2024-03-21 14:17:02 +05:00
import ifcopenshell.util.placement
2024-07-15 14:40:33 +05:00
from typing import Optional, Union, TypedDict, Literal
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",
]
2024-03-21 14:17:02 +05:00
def get_context(
ifc_file: ifcopenshell.file,
2024-07-15 14:40:33 +05:00
context: CONTEXT_TYPE,
subcontext: Optional[REPRESENTATION_IDENTIFIER] = None,
target_view: Optional[TARGET_VIEW] = None,
2024-03-21 14:17:02 +05:00
) -> Union[ifcopenshell.entity_instance, None]:
2024-07-15 14:40:33 +05:00
"""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
2024-03-21 14:17:02 +05:00
def is_representation_of_context(
representation: ifcopenshell.entity_instance,
2024-07-15 14:40:33 +05:00
context: Union[ifcopenshell.entity_instance, CONTEXT_TYPE],
subcontext: Optional[REPRESENTATION_IDENTIFIER] = None,
target_view: Optional[TARGET_VIEW] = None,
2024-03-21 14:17:02 +05:00
) -> bool:
2024-07-15 14:40:33 +05:00
"""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
)
2024-03-21 14:17:02 +05:00
return representation.ContextOfItems.ContextType == context
2024-03-21 14:17:02 +05:00
def get_representation(
element: ifcopenshell.entity_instance,
2024-07-15 14:40:33 +05:00
context: Union[ifcopenshell.entity_instance, CONTEXT_TYPE],
subcontext: Optional[REPRESENTATION_IDENTIFIER] = None,
target_view: Optional[TARGET_VIEW] = None,
2024-03-21 14:17:02 +05:00
) -> 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.
"""
if element.is_a("IfcProduct") and element.Representation:
for r in element.Representation.Representations:
if is_representation_of_context(r, context, subcontext, target_view):
return r
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
for r in element.RepresentationMaps:
if is_representation_of_context(r.MappedRepresentation, context, subcontext, target_view):
return r.MappedRepresentation
2024-03-21 14:17:02 +05:00
def resolve_representation(representation: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
"""Resolve possibly mapped representation.
:param representation: IfcRepresentation
:return: Representation resolved from mappings
"""
if len(representation.Items) == 1 and representation.Items[0].is_a("IfcMappedItem"):
return resolve_representation(representation.Items[0].MappingSource.MappedRepresentation)
return representation
2024-03-21 14:17:02 +05:00
class ResolvedItemDict(TypedDict):
2024-06-03 16:48:14 +05:00
matrix: npt.NDArray[np.float64]
2024-03-21 14:17:02 +05:00
item: ifcopenshell.entity_instance
def resolve_items(
2024-06-03 16:48:14 +05:00
representation: ifcopenshell.entity_instance, matrix: Optional[npt.NDArray[np.float64]] = None
2024-03-21 14:17:02 +05:00
) -> list[ResolvedItemDict]:
if matrix is None:
matrix = np.eye(4)
2024-03-21 14:17:02 +05:00
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:
2024-03-21 14:17:02 +05:00
results.append(ResolvedItemDict(matrix=matrix.copy(), item=item))
return results
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 subcontexts, then contexts. It then prioritises 3D,
then 2D. 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)
return tuple(priority)
def sort_subcontext(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 context.TargetView in target_view_priority:
priority.append(len(target_view_priority) - target_view_priority.index(context.TargetView))
else:
priority.append(0)
priority.append(context.TargetScale or 0) # Big then small
return tuple(priority)
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
return sorted(ifc_file.by_type("IfcGeometricRepresentationSubContext"), key=sort_subcontext, reverse=True) + sorted(
ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False),
key=sort_context,
reverse=True,
)