2022-01-19 12:18:33 +11:00
|
|
|
# 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/>.
|
|
|
|
|
|
2024-05-07 17:32:47 +10:00
|
|
|
"""Welcome to IfcOpenShell! IfcOpenShell provides a way to read and write IFCs.
|
2022-05-09 15:35:52 +10:00
|
|
|
|
2024-05-07 17:32:47 +10:00
|
|
|
IfcOpenShell can open IFC files, read entities (such as walls, buildings,
|
|
|
|
|
properties, systems, etc), edit attributes, write out ``.ifc`` files and more.
|
|
|
|
|
|
|
|
|
|
This module provides primitive functions to interact with IFC, including:
|
|
|
|
|
|
|
|
|
|
- For most users, you can open and read IFC models, see docs for :func:`open`.
|
|
|
|
|
This returns an :class:`file` object representing the IFC model. You can then
|
|
|
|
|
query the model to filter elements.
|
|
|
|
|
- For developers, you can query the schema itself, see docs for
|
|
|
|
|
:func:`schema_by_name`. This returns a schema object which you can use to
|
|
|
|
|
analyse the definitions of IFC classes and data types.
|
|
|
|
|
|
|
|
|
|
You may also be interested in:
|
|
|
|
|
|
|
|
|
|
- For model authoring and editing operations, see :mod:`ifcopenshell.api`.
|
|
|
|
|
- For extracting information from models, see :mod:`ifcopenshell.util`.
|
|
|
|
|
- For processing geometry, see :mod:`ifcopenshell.geom`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For more details, consult https://docs.ifcopenshell.org/
|
2022-05-09 15:35:52 +10:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-05-09 15:35:52 +10:00
|
|
|
|
|
|
|
|
import ifcopenshell
|
2024-05-07 17:32:47 +10:00
|
|
|
|
2022-05-09 15:35:52 +10:00
|
|
|
print(ifcopenshell.version) # v0.7.0-1b1fd1e6
|
2024-05-07 17:32:47 +10:00
|
|
|
|
2022-05-09 15:35:52 +10:00
|
|
|
model = ifcopenshell.open("/path/to/model.ifc")
|
2024-05-07 17:32:47 +10:00
|
|
|
walls = model.by_type("IfcWall")
|
|
|
|
|
|
|
|
|
|
for wall in walls:
|
|
|
|
|
print(wall.Name)
|
2022-05-09 15:35:52 +10:00
|
|
|
"""
|
2015-01-05 14:11:42 +00:00
|
|
|
|
|
|
|
|
import os
|
2015-02-19 13:37:37 +00:00
|
|
|
import sys
|
2022-11-21 01:27:03 +01:00
|
|
|
import zipfile
|
2024-05-07 10:32:02 +10:00
|
|
|
import tempfile
|
2022-11-21 01:27:03 +01:00
|
|
|
from pathlib import Path
|
2024-05-07 14:58:12 +10:00
|
|
|
from typing import Optional, Union
|
2015-02-19 13:37:37 +00:00
|
|
|
|
2022-11-26 05:19:01 +01:00
|
|
|
|
2020-11-01 20:08:27 +07:00
|
|
|
if hasattr(os, "uname"):
|
2017-03-09 10:16:22 +01:00
|
|
|
platform_system = os.uname()[0].lower()
|
|
|
|
|
else:
|
2020-11-01 20:08:27 +07:00
|
|
|
platform_system = "windows"
|
2017-11-06 09:10:28 +01:00
|
|
|
|
2017-03-09 10:16:22 +01:00
|
|
|
if sys.maxsize == (1 << 31) - 1:
|
2020-11-01 20:08:27 +07:00
|
|
|
platform_architecture = "32bit"
|
2017-03-09 10:16:22 +01:00
|
|
|
else:
|
2020-11-01 20:08:27 +07:00
|
|
|
platform_architecture = "64bit"
|
2017-11-06 09:10:28 +01:00
|
|
|
|
2020-11-01 20:08:27 +07:00
|
|
|
python_version_tuple = tuple(sys.version.split(" ")[0].split("."))
|
2017-03-09 10:16:22 +01:00
|
|
|
|
2020-11-01 20:08:27 +07:00
|
|
|
python_distribution = os.path.join(platform_system, platform_architecture, "python%s.%s" % python_version_tuple[:2])
|
|
|
|
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "lib", python_distribution)))
|
2015-02-19 13:37:37 +00:00
|
|
|
|
|
|
|
|
try:
|
2016-07-18 15:53:20 +02:00
|
|
|
from . import ifcopenshell_wrapper
|
2024-05-07 14:58:12 +10:00
|
|
|
except Exception:
|
2016-07-18 15:53:20 +02:00
|
|
|
raise ImportError("IfcOpenShell not built for '%s'" % python_distribution)
|
2017-11-06 09:10:28 +01:00
|
|
|
|
2016-06-22 15:03:18 +02:00
|
|
|
from .file import file
|
2022-01-12 15:09:22 +01:00
|
|
|
from .entity_instance import entity_instance, register_schema_attributes
|
2023-06-16 16:16:22 +10:00
|
|
|
from .sql import sqlite, sqlite_entity
|
2024-05-07 10:32:02 +10:00
|
|
|
|
2024-05-09 13:35:29 +05:00
|
|
|
# explicitly specify available imported symbols
|
|
|
|
|
# (it's a requirement for a typed library)
|
|
|
|
|
__all__ = [
|
|
|
|
|
"ifcopenshell_wrapper",
|
|
|
|
|
"file",
|
|
|
|
|
"entity_instance",
|
|
|
|
|
"sqlite",
|
|
|
|
|
"sqlite_entity",
|
|
|
|
|
"stream",
|
|
|
|
|
"stream_entity",
|
|
|
|
|
]
|
|
|
|
|
|
2023-06-29 22:04:18 +02:00
|
|
|
try:
|
|
|
|
|
from .stream import stream, stream_entity
|
2024-05-07 10:32:02 +10:00
|
|
|
except:
|
|
|
|
|
pass
|
2015-01-05 14:11:42 +00:00
|
|
|
|
2021-02-15 11:11:14 +01:00
|
|
|
READ_ERROR = ifcopenshell_wrapper.file_open_status.READ_ERROR
|
|
|
|
|
NO_HEADER = ifcopenshell_wrapper.file_open_status.NO_HEADER
|
|
|
|
|
UNSUPPORTED_SCHEMA = ifcopenshell_wrapper.file_open_status.UNSUPPORTED_SCHEMA
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Error(Exception):
|
2022-05-09 15:35:52 +10:00
|
|
|
"""Error used when a generic problem occurs"""
|
2024-05-07 10:32:02 +10:00
|
|
|
|
2021-02-15 11:11:14 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SchemaError(Error):
|
2022-05-09 15:35:52 +10:00
|
|
|
"""Error used when an IFC schema related problem occurs"""
|
2024-05-07 10:32:02 +10:00
|
|
|
|
2021-02-15 11:11:14 +01:00
|
|
|
pass
|
|
|
|
|
|
2017-11-06 09:10:28 +01:00
|
|
|
|
2024-05-07 14:58:12 +10:00
|
|
|
def open(path: Union[os.PathLike, str], format: Optional[str] = None, should_stream: bool = False) -> file:
|
2022-11-22 00:40:27 +01:00
|
|
|
"""Loads an IFC dataset from a filepath
|
|
|
|
|
|
2024-05-07 14:58:12 +10:00
|
|
|
You can specify a file format. If no format is given, it is guessed from
|
|
|
|
|
its extension. Currently supported specified format: .ifc | .ifcZIP |
|
|
|
|
|
.ifcXML.
|
2022-11-22 00:40:27 +01:00
|
|
|
|
2024-04-08 12:34:59 +10:00
|
|
|
You can then filter by element ID, class, etc, and subscript by id or guid.
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
2022-11-22 00:40:27 +01:00
|
|
|
model = ifcopenshell.open("/path/to/model.ifc")
|
|
|
|
|
model = ifcopenshell.open("/path/to/model.ifcXML")
|
|
|
|
|
model = ifcopenshell.open("/path/to/model.any_extension", ".ifc")
|
2024-04-08 12:34:59 +10:00
|
|
|
|
|
|
|
|
products = model.by_type("IfcProduct")
|
|
|
|
|
print(products[0].id(), products[0].GlobalId) # 122 2XQ$n5SLP5MBLyL442paFx
|
|
|
|
|
print(products[0] == model[122] == model["2XQ$n5SLP5MBLyL442paFx"]) # True
|
2022-11-22 00:40:27 +01:00
|
|
|
"""
|
2022-11-21 01:27:03 +01:00
|
|
|
path = Path(path)
|
|
|
|
|
if format is None:
|
2024-05-07 10:32:02 +10:00
|
|
|
format = guess_format(path)
|
2022-11-21 01:27:03 +01:00
|
|
|
if format == ".ifcXML":
|
|
|
|
|
f = ifcopenshell_wrapper.parse_ifcxml(str(path.absolute()))
|
|
|
|
|
if f:
|
|
|
|
|
return file(f)
|
|
|
|
|
raise IOError(f"Failed to parse .ifcXML file from {path}")
|
|
|
|
|
if format == ".ifcZIP":
|
|
|
|
|
with tempfile.TemporaryDirectory() as unzipped_path:
|
|
|
|
|
with zipfile.ZipFile(path) as zf:
|
|
|
|
|
for name in zf.namelist():
|
|
|
|
|
if Path(name).suffix.lower() in (".ifc", ".ifcxml"):
|
2022-11-22 00:40:27 +01:00
|
|
|
return open(zf.extract(name, unzipped_path))
|
2022-11-21 01:27:03 +01:00
|
|
|
else:
|
|
|
|
|
raise LookupError(f"No .ifc or .ifcXML file found in {path}")
|
2023-06-16 16:16:22 +10:00
|
|
|
if format == ".ifcSQLite":
|
|
|
|
|
return sqlite(path)
|
2023-06-27 21:02:50 +10:00
|
|
|
if should_stream:
|
|
|
|
|
return stream(path)
|
2022-11-21 01:27:03 +01:00
|
|
|
f = ifcopenshell_wrapper.open(str(path.absolute()))
|
2018-01-01 11:22:12 +01:00
|
|
|
if f.good():
|
|
|
|
|
return file(f)
|
|
|
|
|
else:
|
2021-02-15 11:11:14 +01:00
|
|
|
exc, msg = {
|
|
|
|
|
READ_ERROR: (IOError, "Unable to open file for reading"),
|
|
|
|
|
NO_HEADER: (Error, "Unable to parse IFC SPF header"),
|
|
|
|
|
UNSUPPORTED_SCHEMA: (
|
|
|
|
|
SchemaError,
|
2024-05-07 10:32:02 +10:00
|
|
|
"Unsupported schema: %s" % ",".join(f.header.file_schema.schema_identifiers),
|
2021-02-15 11:11:14 +01:00
|
|
|
),
|
|
|
|
|
}[f.good().value()]
|
|
|
|
|
raise exc(msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_entity(type, schema="IFC4", *args, **kwargs):
|
2022-05-09 15:35:52 +10:00
|
|
|
"""Creates a new IFC entity that does not belong to an IFC file object
|
|
|
|
|
|
|
|
|
|
Note that it is more common to create entities within a existing file
|
2024-05-07 19:08:13 +10:00
|
|
|
object. See :meth:`ifcopenshell.file.create_entity`.
|
2022-05-09 15:35:52 +10:00
|
|
|
|
|
|
|
|
:param type: Case insensitive name of the IFC class
|
|
|
|
|
:type type: string
|
|
|
|
|
:param schema: The IFC schema identifier
|
|
|
|
|
:type schema: string
|
|
|
|
|
:param args: The positional arguments of the IFC class
|
|
|
|
|
:param kwargs: The keyword arguments of the IFC class
|
|
|
|
|
:returns: An entity instance
|
2024-05-07 19:08:13 +10:00
|
|
|
:rtype: ifcopenshell.entity_instance
|
2022-05-09 15:35:52 +10:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-05-09 15:35:52 +10:00
|
|
|
|
|
|
|
|
person = ifcopenshell.create_entity("IfcPerson") # #0=IfcPerson($,$,$,$,$,$,$,$)
|
|
|
|
|
model = ifcopenshell.file()
|
|
|
|
|
model.add(person) # #1=IfcPerson($,$,$,$,$,$,$,$)
|
|
|
|
|
"""
|
2021-01-09 11:59:34 +01:00
|
|
|
e = entity_instance((schema, type))
|
2020-11-01 20:08:27 +07:00
|
|
|
attrs = list(enumerate(args)) + [(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()]
|
2017-11-06 09:10:28 +01:00
|
|
|
for idx, arg in attrs:
|
|
|
|
|
e[idx] = arg
|
2016-07-18 15:53:20 +02:00
|
|
|
return e
|
2020-11-01 20:08:27 +07:00
|
|
|
|
|
|
|
|
|
2020-09-08 14:40:27 +02:00
|
|
|
def register_schema(schema):
|
2022-05-09 15:35:52 +10:00
|
|
|
"""Registers a custom IFC schema
|
|
|
|
|
|
|
|
|
|
:param schema: A schema object
|
|
|
|
|
:type schema: ifcopenshell.express.schema_class.SchemaClass
|
|
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-05-09 15:35:52 +10:00
|
|
|
|
|
|
|
|
schema = ifcopenshell.express.parse("/path/to/ifc-custom.exp")
|
|
|
|
|
ifcopenshell.register_schema(schema)
|
|
|
|
|
ifcopenshell.file(schema="IFC_CUSTOM")
|
|
|
|
|
"""
|
2023-04-10 22:01:53 +02:00
|
|
|
schema.schema.this.disown()
|
|
|
|
|
schema.disown()
|
2020-09-08 14:40:27 +02:00
|
|
|
ifcopenshell_wrapper.register_schema(schema.schema)
|
2022-01-12 15:09:22 +01:00
|
|
|
register_schema_attributes(schema.schema)
|
2017-11-06 09:10:28 +01:00
|
|
|
|
2020-11-01 20:08:27 +07:00
|
|
|
|
2024-04-22 17:01:28 +05:00
|
|
|
def schema_by_name(
|
|
|
|
|
schema: Optional[str] = None, schema_version: Optional[tuple[int, ...]] = None
|
|
|
|
|
) -> ifcopenshell_wrapper.schema_definition:
|
2023-07-06 22:40:15 +10:00
|
|
|
"""Returns an object allowing you to query the IFC schema itself
|
|
|
|
|
|
|
|
|
|
:param schema: Which IFC schema to use, chosen from "IFC2X3", "IFC4",
|
|
|
|
|
or "IFC4X3". These refer to the ISO approved versions of IFC.
|
2024-04-22 17:01:28 +05:00
|
|
|
:type schema: string, optional
|
2023-07-06 22:40:15 +10:00
|
|
|
:param schema_version: If you want to specify an exact version of IFC
|
|
|
|
|
that may not be an ISO approved version, use this argument instead
|
|
|
|
|
of ``schema``. IFC versions on technical.buildingsmart.org are
|
|
|
|
|
described using 4 integers representing the major, minor, addendum,
|
|
|
|
|
and corrigendum number. For example, (4, 0, 2, 1) refers to IFC4
|
|
|
|
|
ADD2 TC1, which is the official version approved by ISO when people
|
|
|
|
|
refer to "IFC4". Generally you should not use this argument unless
|
|
|
|
|
you are testing non-ISO IFC releases.
|
2024-04-22 17:01:28 +05:00
|
|
|
:type schema_version: tuple[int, ...], optional
|
|
|
|
|
:return: Schema definition object.
|
|
|
|
|
:rtype: ifocpenshell_wrapper.schema_definition
|
2023-07-06 22:40:15 +10:00
|
|
|
"""
|
|
|
|
|
if schema_version:
|
|
|
|
|
prefixes = ("IFC", "X", "_ADD", "_TC")
|
|
|
|
|
schema = "".join("".join(map(str, t)) if t[1] else "" for t in zip(prefixes, schema_version))
|
|
|
|
|
else:
|
2024-04-22 16:55:22 +05:00
|
|
|
schema = {"IFC4X3": "IFC4X3_ADD2"}.get(schema, schema)
|
2023-07-06 22:40:15 +10:00
|
|
|
return ifcopenshell_wrapper.schema_by_name(schema)
|
|
|
|
|
|
|
|
|
|
|
2024-05-23 16:42:19 +05:00
|
|
|
def guess_format(path: Path) -> Union[str, None]:
|
2024-05-07 17:32:47 +10:00
|
|
|
"""Guesses the IFC format using file extension
|
2024-05-07 10:32:02 +10:00
|
|
|
|
|
|
|
|
IFCs may be serialised as different formats. The most common is a ``.ifc``
|
|
|
|
|
file, which is plaintext and stores data using the STEP Physical File
|
|
|
|
|
format. IFC can also be stored as a Zipfile, XML, JSON, or SQL.
|
|
|
|
|
|
|
|
|
|
This will return the canonical form of the format. For example, if a path
|
|
|
|
|
has the extension of .xml or .ifcxml (case insensitive), it will return
|
|
|
|
|
.ifcXML.
|
|
|
|
|
|
2024-05-07 17:32:47 +10:00
|
|
|
Users generally won't call this function. The :func:`open` function uses
|
|
|
|
|
this internally to guess the file format.
|
|
|
|
|
|
2024-05-07 10:32:02 +10:00
|
|
|
:return: Either .ifc, .ifcZIP, .ifcXML, .ifcJSON, .ifcSQLite, or None.
|
|
|
|
|
"""
|
|
|
|
|
suffix = path.suffix.lower()
|
|
|
|
|
if suffix == ".ifc":
|
|
|
|
|
return ".ifc"
|
|
|
|
|
elif suffix in (".ifczip", ".zip"):
|
|
|
|
|
return ".ifcZIP"
|
|
|
|
|
elif suffix in (".ifcxml", ".xml"):
|
|
|
|
|
return ".ifcXML"
|
|
|
|
|
elif suffix in (".ifcjson", ".json"):
|
|
|
|
|
return ".ifcJSON"
|
|
|
|
|
elif suffix in (".ifcsqlite", ".sqlite", ".db"):
|
|
|
|
|
return ".ifcSQLite"
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2024-06-11 13:11:14 +10:00
|
|
|
version_core = ifcopenshell_wrapper.version()
|
|
|
|
|
__version__ = version = "0.0.0"
|
2024-05-07 17:32:47 +10:00
|
|
|
get_log = ifcopenshell_wrapper.get_log
|