diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0185aa8077..ac5dfe2516 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,7 @@ jobs: -DPYTHON_INCLUDE_DIR:PATH=${{ env.pythonLocation }}/include/python3.11 \ -DPYTHON_LIBRARY:FILEPATH=${{ env.pythonLocation }}/lib/libpython3.11.so \ -DCOLLADA_SUPPORT=Off \ + -DUSE_MMAP=On \ "-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \ -DGLTF_SUPPORT=On \ -DWITH_ROCKSDB=On \ diff --git a/nix/build-all.py b/nix/build-all.py index 5226562557..79a5587408 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -18,6 +18,8 @@ # # ############################################################################### +# ruff: noqa: UP035 + """ Example usage: # Build all targets by default. diff --git a/src/bonsai/bonsai/core/drawing.py b/src/bonsai/bonsai/core/drawing.py index 2e2d07c96c..d769079acc 100644 --- a/src/bonsai/bonsai/core/drawing.py +++ b/src/bonsai/bonsai/core/drawing.py @@ -487,7 +487,11 @@ def sync_references( continue if (obj := ifc.get_object(element)) and ifc.is_moved(obj): drawing_tool.sync_object_placement(obj) - reference_element = drawing_tool.get_assigned_product(element) + if not (reference_element := drawing_tool.get_assigned_product(element)): + if obj := ifc.get_object(element): + drawing_tool.delete_object(obj) + ifc.run("root.remove_product", product=element) + continue reference_obj = ifc.get_object(reference_element) if reference_element not in potential_reference_elements: # It was auto created, so it makes sense to auto delete diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 543106f937..ea6cb176bb 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -786,17 +786,21 @@ class Model(bonsai.core.tool.Model): elif material.is_a("IfcMaterialLayerSet"): axis = ifcopenshell.util.element.get_pset(element, "EPset_Parametric", "LayerSetDirection") if axis is None: - if element.is_a() in [ + if element.is_a() in ( "IfcSlabType", "IfcRoofType", "IfcRampType", "IfcPlateType", - "IfcCovering", - "IfcFurniture", - ]: + "IfcSlab", + "IfcRoof", + "IfcRamp", + "IfcPlate", + ): axis = "AXIS3" - else: + elif element.is_a() in ("IfcWallType", "IfcWall"): axis = "AXIS2" + else: + return return f"LAYER{axis[-1]}" elif material.is_a("IfcMaterialProfileSetUsage"): # TODO: remove after we support editing profile usages with IfcRevolvedAreaSolid. diff --git a/src/ifcfm/ifcfm/__init__.py b/src/ifcfm/ifcfm/__init__.py index ff36e5702c..14fa76146d 100644 --- a/src/ifcfm/ifcfm/__init__.py +++ b/src/ifcfm/ifcfm/__init__.py @@ -24,7 +24,8 @@ import importlib import ifcopenshell.util.selector from pathlib import Path from collections import defaultdict -from typing import Literal, Union, Any, Callable, TYPE_CHECKING +from typing import Literal, Union, Any, TYPE_CHECKING +from collections.abc import Callable try: from openpyxl import Workbook diff --git a/src/ifcgeom/Iterator.cpp b/src/ifcgeom/Iterator.cpp index fa36ce1e34..2ea32947a3 100644 --- a/src/ifcgeom/Iterator.cpp +++ b/src/ifcgeom/Iterator.cpp @@ -367,11 +367,13 @@ void IfcGeom::Iterator::create_element_(ifcopenshell::geometry::Converter* kerne })); if (!brep) { + Logger::SetProduct(boost::none); return; } auto elem = process_based_on_settings(settings, brep); if (!elem) { + Logger::SetProduct(boost::none); return; } @@ -394,6 +396,8 @@ void IfcGeom::Iterator::create_element_(ifcopenshell::geometry::Converter* kerne } } } + + Logger::SetProduct(boost::none); } IfcGeom::Element* IfcGeom::Iterator::process_based_on_settings(ifcopenshell::geometry::Settings settings, IfcGeom::BRepElement* elem, IfcGeom::TriangulationElement* previous) diff --git a/src/ifcopenshell-python/docs/bsdd.rst b/src/ifcopenshell-python/docs/bsdd.rst index 568c685983..2f20e5e95d 100644 --- a/src/ifcopenshell-python/docs/bsdd.rst +++ b/src/ifcopenshell-python/docs/bsdd.rst @@ -43,6 +43,7 @@ Examples Learning how to use the bSDD is best done by reading the official Swagger API docs. .. code-block:: python + from bsdd import Client,apply_ifc_classification_properties from pprint import pprint diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python.rst b/src/ifcopenshell-python/docs/ifcopenshell-python.rst index 9eebe129d4..bbe503c64b 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python.rst @@ -17,4 +17,5 @@ capabilities of the C++ core are available in Python. ifcopenshell-python/geometry_creation ifcopenshell-python/geometry_tree ifcopenshell-python/selector_syntax - ifcopenshell-python/developer_guide + ifcopenshell-python/schema_querying + ifcopenshell-python/validation diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/developer_guide.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/developer_guide.rst deleted file mode 100644 index d664cbe111..0000000000 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/developer_guide.rst +++ /dev/null @@ -1,15 +0,0 @@ -Developer Guide -=============== - -The core module implements low-level functionality to read and write IFC data. This includes: - -- Reading IFC data from different serialisations into Python objects -- Accessing direct and indirect attributes of IFC entities -- Creating IFC entities -- Generating GlobalIds -- Removing IFC entities and all references -- Modifying IFC direct attributes -- Checking IFC class inheritance -- Validating IFC data - -TODO diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/schema_querying.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/schema_querying.rst new file mode 100644 index 0000000000..1cd6fbd1a8 --- /dev/null +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/schema_querying.rst @@ -0,0 +1,73 @@ +Schema querying +=============== + +Schema declarations +------------------- + +IfcOpenShell can query the IFC schema itself without instantiating or loading an IFC dataset. + +.. code-block:: python + + import ifcopenshell + ifc4 = ifcopenshell.schema_by_name("IFC4") + +A schema definition is known as a declaration. You may loop through all declarations or retrieve a declaration by name. All declarations have a name. + +.. code-block:: python + + for declaration in ifc4.declarations(): + print(declaration.name()) # 'IfcAbsorbedDoseMeasure', 'IfcAccelerationMeasure', 'IfcActionRequest', ... + + ifcwall = ifc4.declaration_by_name("IfcWall") + +You can check if an entity is abstract, and retrive both the supertype and subtypes of an entity: + +.. code-block:: python + + print(ifcwall.is_abstract()) # False + print(ifcwall.supertype()) # + print(ifcwall.subtypes()) # (, ) + +You can retrieve only the direct attributes of an entity, or all the direct attributes including inherited attributes, or inverse attributes: + +.. code-block:: python + + print(ifcwall.attributes()) + print(ifcwall.all_attributes()) + print(ifcwall.all_inverse_attributes()) + +buildingSMART property set templates +------------------------------------ + +For each IFC schema version, buildingSMART publishes built in property and quantity set templates for standardised properties. These define property names, property sets, data types, and which IFC class they are applicable to. You can query these templates. + +.. code-block:: python + + import ifcopenshell.util.pset + templates = ifcopenshell.util.pset.PsetQto("IFC4") + +To get just the names of applicable templates for an entity: + +.. code-block:: python + + # ['Pset_EnvironmentalImpactIndicators', 'Pset_EnvironmentalImpactValues', 'Pset_WallCommon', 'Qto_WallBaseQuantities', ...] + print(templates.get_applicable_names("IfcWall")) + +They may also be retrieved as an ``IfcPropertySetTemplate`` entity: + +.. code-block:: python + + print(templates.get_applicable("IfcWall")) + +A single template may be retrieved by name: + +.. code-block:: python + + templates.get_by_name('Pset_WallCommon') + +You may add your own IFC files containing pset template definitions: + +.. code-block:: python + + my_pset_library = ifcopenshell.open('/path/to/library.ifc') + templates.templates.append(my_pset_library) diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/validation.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/validation.rst new file mode 100644 index 0000000000..3a0847d80a --- /dev/null +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/validation.rst @@ -0,0 +1,78 @@ +Validation +========== + +SPF syntax validation +--------------------- + +IfcOpenShell can validate whether or not an IFC-SPF file contains correct SPF syntax. + +.. code-block:: + + $ python -m ifcopenshell.simple_spf path/to/model.ifc + Valid + +Here are some examples of failures: + +.. code-block:: + + $ python -m ifcopenshell.simple_spf fixtures/fail_double_comma.ifc + On line 8 column 21: + Unexpected comma (',') + Expecting one of DBLQUOTE DOT HASH INT LPAR NONE QUOTE REAL STAR UPPER + 00008 | #1=IFCPERSON($,$,'',,$,$,$,$); + ^ + + $ python -m ifcopenshell.simple_spf fixtures/fail_double_semi.ifc + On line 27 column 66: + Unexpected semicolon (';') + Expecting one of ENDSEC HASH + 00027 | #20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);; + ^ + + $ python -m ifcopenshell.simple_spf fixtures/fail_duplicate_id.ifc + On line 27: + Duplicate instance name #19 + 00027 | #19=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + $ python -m ifcopenshell.simple_spf fixtures/fail_no_header.ifc + On line 2 column 1: + Unexpected hex ('F') + Expecting HEADER + 00002 | FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); + ^ + +The optional ``--json`` argument may be used to instead get results in JSON. + +.. code-block:: + + $ python -m ifcopenshell.simple_spf test.ifc + {"type": "unexpected_token", "lineno": 8, "column": 48, "found_type": "semicolon", "found_value": ";", "expected": ["ENDSEC"], "line": "#1= IFCPERSON($,'Nicht definiert',$,$,$,$,$,$);;", "message": "On line 8 column 48:\nUnexpected semicolon (';')\nExpecting ENDSEC\n00008 | #1= IFCPERSON($,'Nicht definiert',$,$,$,$,$,$);;\n ^"} + +IFC schema validation +--------------------- + +IfcOpenShell can validate models against the IFC schema itself. It checks against attributes, entity names, data types, cardinality, and where rules. + +.. code-block:: console + + $ python -m ifcopenshell.validate -h + + usage: validate.py [-h] [--rules] [--json] [--fields] [--spf] files [files ...] + + positional arguments: + files The IFC file to validate. + + options: + -h, --help show this help message and exit + --rules Run express rules. + --json Output in JSON format. + --fields Output more detailed information about failed entities (only with --json). + --spf Output entities in SPF format (only with --json). + + +For example: + +.. code-block:: bash + + python -m ifcopenshell.validate /path/to/model.ifc --rules diff --git a/src/ifcopenshell-python/docs/introduction.rst b/src/ifcopenshell-python/docs/introduction.rst index 76032d334d..58430b6da8 100644 --- a/src/ifcopenshell-python/docs/introduction.rst +++ b/src/ifcopenshell-python/docs/introduction.rst @@ -54,27 +54,27 @@ IfcOpenShell is a modular ecosystem of tools that work together, where each tool .. csv-table:: :header: "Name", "Description" - "**IfcOpenShell**", "The core library for C++ developers. The library includes the ability to parse schemas, tessellate and process implicit geometry." - "**IfcOpenShell-Python**", "Python bindings to the core IfcOpenShell C++ system, as well as high level analysis and authoring functions." - "**IfcConvert**", "A command-line application for converting IFC geometry into file formats such as OBJ, DAE, GLB, STP, IGS, XML, SVG, H5, and IFC itself." - "**Bonsai**", "A graphical add-on for Blender that lets you analyse, author, and modify IFC with Blender. Graphically create BIM models from scratch!" - "**BCF**", "BIM Collaboration Format (BCF) is a standard to manage and exchange coordination topics between disciplines collaborating on a project by changing XML files or querying an API." - "**BIMServer-Plugin**", "A plugin to the open source BIMServer CDE to allow you to use IfcOpenShell to parse, view, and audit models." - "**BIMTester**", "A utility that allows you to write Gherkin-based tests for models." - "**bSDD**", "A Python library to query the buildingSMART Data Dictionary API to search for standardised classifications and properties." - "**Ifc2CA**", "Converts IFC models to FEM structural analytical models to be used in Code_Aster." - "**Ifc4D**", "A series of utilities for converting to and from various 4D software like MS Project, PowerProject, and Oracle P6." - "**Ifc5D**", "A collection of utilities of manipulating cost-related data to and from formats, reports, and optimisation engines." - "**IfcCityJSON**", "A converter for CityJSON files and IFC. It currently only supports one-way conversion from CityJSON to IFC." - "**IfcClash**", "A CLI utility and library that lets you perform clash detection on one or more IFC models. Clashes are defined in terms of clash sets with filters using the IFC query syntax." - "**IfcCSV**", "View and edit IFC data using spreadsheets or tabular datasets, such as CSV, ODS, XLSX, Pandas DataFrames, and regular Python lists." - "**IfcDiff**", "A CLI utility and library that lets you compare the changes between two IFC models." - "**IfcFM**", "A highly standards-compliant tool (e.g. COBie 2.4, COBie 3.0, AOH-BSEM) to convert FM data in IFC databases to spreadsheets and other machine readable formats, such as ODS, XLSX, CSV, Pandas, XML, and JSON." - "**IfcMax**", "A 3ds Max importer plugin able to import the IFC file format." - "**IfcPatch**", "A CLI utility and library that lets you run and distribute predetermined modifications on an IFC file, known as a patch recipe. Useful in deploying a data pipeline or batch-fixing external models." - "**IfcSverchok**", "A node based visual programming add-on for Blender to interact with IFC and Sverchok." - "**IfcTester**", "Author and read Information Delivery Specification (IDS) files. You can validate IFC models against IDS and generate reports in multiple formats. It works from the command line, as a web app, or as a library." - "**VoxelisationToolkit**", "Converts .ifc geometry into voxels, and lets you perform voxel based geometric analysis." + "`IfcOpenShell `_", "The core library for C++ developers. The library includes the ability to parse schemas, tessellate and process implicit geometry." + "`IfcOpenShell-Python `_", "Python bindings to the core IfcOpenShell C++ system, as well as high level analysis and authoring functions." + "`IfcConvert `_", "A command-line application for converting IFC geometry into file formats such as OBJ, DAE, GLB, STP, IGS, XML, SVG, H5, and IFC itself." + "`Bonsai `_", "A graphical add-on for Blender that lets you analyse, author, and modify IFC with Blender. Graphically create BIM models from scratch!" + "`BCF `_", "BIM Collaboration Format (BCF) is a standard to manage and exchange coordination topics between disciplines collaborating on a project by changing XML files or querying an API." + "`BIMServer-Plugin `_", "A plugin to the open source BIMServer CDE to allow you to use IfcOpenShell to parse, view, and audit models." + "`BIMTester `_", "A utility that allows you to write Gherkin-based tests for models." + "`bSDD `_", "A Python library to query the buildingSMART Data Dictionary API to search for standardised classifications and properties." + "`Ifc2CA `_", "Converts IFC models to FEM structural analytical models to be used in Code_Aster." + "`Ifc4D `_", "A series of utilities for converting to and from various 4D software like MS Project, PowerProject, and Oracle P6." + "`Ifc5D `_", "A collection of utilities of manipulating cost-related data to and from formats, reports, and optimisation engines." + "`IfcCityJSON `_", "A converter for CityJSON files and IFC. It currently only supports one-way conversion from CityJSON to IFC." + "`IfcClash `_", "A CLI utility and library that lets you perform clash detection on one or more IFC models. Clashes are defined in terms of clash sets with filters using the IFC query syntax." + "`IfcCSV `_", "View and edit IFC data using spreadsheets or tabular datasets, such as CSV, ODS, XLSX, Pandas DataFrames, and regular Python lists." + "`IfcDiff `_", "A CLI utility and library that lets you compare the changes between two IFC models." + "`IfcFM `_", "A highly standards-compliant tool (e.g. COBie 2.4, COBie 3.0, AOH-BSEM) to convert FM data in IFC databases to spreadsheets and other machine readable formats, such as ODS, XLSX, CSV, Pandas, XML, and JSON." + "`IfcMax `_", "A 3ds Max importer plugin able to import the IFC file format." + "`IfcPatch `_", "A CLI utility and library that lets you run and distribute predetermined modifications on an IFC file, known as a patch recipe. Useful in deploying a data pipeline or batch-fixing external models." + "`IfcSverchok `_", "A node based visual programming add-on for Blender to interact with IFC and Sverchok." + "`IfcTester `_", "Author and read Information Delivery Specification (IDS) files. You can validate IFC models against IDS and generate reports in multiple formats. It works from the command line, as a web app, or as a library." + "`VoxelisationToolkit `_", "Converts .ifc geometry into voxels, and lets you perform voxel based geometric analysis." .. note:: diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index c7350a13da..fd95004aad 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -251,7 +251,7 @@ INVALID_SYNTAX = ifcopenshell_wrapper.file_open_status.INVALID_SYNTAX try: UNKNOWN = ifcopenshell_wrapper.file_open_status.UNKNOWN except: - UNKNOWN = 5 # Workaround + UNKNOWN = 5 # Workaround import struct diff --git a/src/ifcopenshell-python/ifcopenshell/geom/main.py b/src/ifcopenshell-python/ifcopenshell/geom/main.py index 5f3e2079f2..a1f00bb137 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/main.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/main.py @@ -22,7 +22,7 @@ import os import sys import operator -from .. import ifcopenshell_wrapper +from .. import open, ifcopenshell_wrapper from ..file import file from ..entity_instance import entity_instance @@ -307,10 +307,7 @@ class iterator(ifcopenshell_wrapper.Iterator): self.file = file file_or_filename = file_or_filename.wrapped_data else: - # @todo? - self.file = None - # Makes sure people are able to use python's platform agnostic paths - file_or_filename = os.path.abspath(file_or_filename) + file_or_filename = self.file = open(file_or_filename) if include is not None and exclude is not None: raise ValueError("include and exclude cannot be specified simultaneously") @@ -342,7 +339,9 @@ class iterator(ifcopenshell_wrapper.Iterator): geometry_library, self.settings, file_or_filename, include_or_exclude, include is not None, num_threads ) else: - ifcopenshell_wrapper.Iterator.__init__(self, geometry_library, settings, file_or_filename, num_threads) + self.this = ifcopenshell_wrapper.construct_iterator( + geometry_library, self.settings, file_or_filename, num_threads + ) if has_occ: diff --git a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi index 0f71a9ab24..47901a182e 100644 --- a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi +++ b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi @@ -71,6 +71,13 @@ FILTERED_CARTESIAN_QUOTIENT: Any EXACT_PREDICATES: Any EXACT_CONSTRUCTIONS: Any +FT_AUTODETECT: Any +FT_IFCSPF: Any +FT_IFCXML: Any +FT_IFCZIP: Any +FT_ROCKSDB: Any +FT_UNKNOWN: Any + CURVES: Any SURFACES_AND_SOLIDS: Any CURVES_SURFACES_AND_SOLIDS: Any @@ -96,12 +103,9 @@ class IfcSpfHeader: """ def file(self, *args): ... - @property - def file_description(self) -> ifcopenshell.entity_instance: ... - @property - def file_name(self) -> ifcopenshell.entity_instance: ... - @property - def file_schema(self) -> ifcopenshell.entity_instance: ... + def file_description_py(self): ... + def file_name_py(self): ... + def file_schema_py(self): ... def read(self): ... def tryRead(self): ... def write(self, out): ... @@ -145,6 +149,7 @@ class ConversionResult: def Style(self): ... def StylePtr(self): ... def append(self, trsf): ... + def apply_transform(self, unit_scale): ... def hasStyle(self): ... def prepend(self, trsf): ... def setStyle(self, newStyle): ... @@ -157,6 +162,7 @@ class ConversionResultShape: def axis(self): ... def bounding_box(self, *args): ... def box(self): ... + def concat(self, arg2): ... def convex_decomposition(self): ... def convex_tag(self, b): ... def edges(self): ... @@ -178,9 +184,11 @@ class ConversionResultShape: def solid(self): ... def solid_mt(self): ... def subtract(self, arg2): ... + def surface_area_along_direction(self, tol, arg3, along_x, along_y, along_z): ... def surface_genus(self): ... def vertices(self): ... def volume(self): ... + def wrap_in_compound(self): ... class DoubleArray3: def back(self): ... @@ -284,11 +292,6 @@ class HdfSerializer(GeometrySerializer): def write(self, *args): ... def writeHeader(self): ... -class HeaderEntity: - def getArgument(self, index): ... - def getArgumentCount(self): ... - def toString(self, upper): ... - class IfcBaseEntity(entity_instance): def declaration(self): ... def get(self, name): ... @@ -304,9 +307,17 @@ class IfcEntityInstanceData: ... class IfcLateBoundEntity(IfcBaseEntity): def declaration(self): ... -class InstanceReference: - file_offset: Any - v: Any +class InstanceStreamer: + def bypassTypes(self, type_names): ... + def bypassed_instances(self): ... + coerce_attribute_count: bool + def hasSemicolon(self): ... + def inverses(self, *args): ... + def pushPage(self, page): ... + def readInstancePy(self, type_as_declaration_instance): ... + def references(self, *args): ... + def semicolonCount(self): ... + def status(self): ... class Iterator: initialization_outcome_: Any @@ -377,6 +388,18 @@ class Representation: def settings(self): ... +class RocksDBPrefixIterator: + def key(self): ... + def next(self): ... + def valid(self): ... + def value(self): ... + +class RocksDbSerializer: + def finalize(self): ... + def ready(self): ... + def setFile(self, arg2): ... + def writeHeader(self): ... + class Serialization(Representation): @property def brep_data(self): ... @@ -829,6 +852,7 @@ class entity_instance: ... def get_attribute_names(self): ... + def get_attribute_value(self, index): ... def get_inverse(self, a): ... def get_inverse_attribute_names(self): ... def id(self) -> int: ... @@ -851,6 +875,7 @@ class entity_instance: def setArgumentAsLogical(self, i, v): ... def setArgumentAsNull(self, i): ... def setArgumentAsString(self, i, a): ... + def set_attribute_value(self, *args): ... def toString(self, arg2, upper): ... def to_string(self, valid_spf): ... def unset_attribute_value(self, i): ... @@ -888,14 +913,10 @@ class face: def print_impl(self, o, indent): ... class file: - INSTANCE_ID: Any - INSTANCE_TYPE: Any - ATTRIBUTE_INDEX: Any - guid_map_: Any - stream: Any def FreshId(self): ... def add(self, entity: entity_instance, id: int) -> entity_instance: ... def addEntities(self, entities): ... + def add_type_ref(self, new_entity): ... def batch(self) -> None: """Enable batch mode. @@ -919,10 +940,15 @@ class file: ... def build_inverses(self): ... + def build_inverses_(self, arg2): ... def by_guid(self, guid: str) -> entity_instance: ... def by_id(self, id: int) -> entity_instance: ... def by_type(self, *args): ... def by_type_excl_subtypes(self, *args): ... + def bypass_type(self, type_name): ... + calculate_unit_factors: bool + check_existance_before_adding: bool + def create(self, decl): ... @staticmethod def createTimestamp(): ... def entity_names(self) -> tuple[int, ...]: @@ -961,24 +987,27 @@ class file: def get_total_inverses(self, e: entity_instance) -> int: ... def good(self): ... - @staticmethod - def guid_map(*args): ... - def header(self) -> IfcSpfHeader: + def header(self, *args) -> IfcSpfHeader: """Internal IfcSpfHeader instance, always prefer ``ifcopenshell.file.header`` instead.""" def ifcroot_type(self) -> entity: ... - def internal_guid_map(self): ... - def load(self, entity_instance_name, entity, arg4, attribute_index): ... + def initialize(self, *args): ... + instantiate_typed_instances: bool + def key_value_store_iter(self, prefix): ... + def key_value_store_query(self, key): ... + def process_deletion_inverse(self, inst): ... def recalculate_id_counter(self): ... def remove(self, entity: entity_instance) -> None: ... + def remove_type_ref(self, new_entity): ... + def reset_identity_cache(self): ... @property def schema(self): ... + def storage_mode(self): ... def to_string(self): ... @staticmethod def traverse(instance: entity_instance, max_level: int) -> tuple[entity_instance, ...]: ... @staticmethod def traverse_breadth_first(instance: entity_instance, max_level: int) -> tuple[entity_instance, ...]: ... - def try_read_semicolon(self): ... def types(self) -> tuple[str, ...]: """Return a tuple of classes present in the file. @@ -986,16 +1015,15 @@ class file: """ ... - def types_begin(self): ... - def types_end(self): ... def write(self, fn): ... class file_open_status: - SUCCESS: Any - READ_ERROR: Any - NO_HEADER: Any - UNSUPPORTED_SCHEMA: Any - INVALID_SYNTAX: Any + SUCCESS: int + READ_ERROR: int + NO_HEADER: int + UNSUPPORTED_SCHEMA: int + INVALID_SYNTAX: int + UNKNOWN: int def value(self): ... class fn_evaluator: @@ -1037,9 +1065,6 @@ class geometry_conversion_result: products_2: Any representation: Any -class geometry_exception: - def what(self): ... - class gradient_function(function_item): def calc_hash(self): ... def clone_(self): ... @@ -1138,6 +1163,8 @@ class matrix4(item): def is_identity(self): ... def kind(self): ... def translation_part(self): ... + def post_multiply_scale(self, s): ... + def pre_multiply_scale(self, s): ... class named_type(parameter_type): def _is(self, *args): ... @@ -1516,13 +1543,11 @@ class sweep(geom_item): class sweep_along_curve(sweep): curve: Any surface: Any + direction: Any def calc_hash(self): ... def clone_(self): ... def kind(self): ... -class too_many_faces_exception(geometry_exception): ... -class topology_error: ... - class torus(surface): radius1: Any radius2: Any @@ -1568,6 +1593,8 @@ class type_declaration(declaration): def as_type_declaration(self) -> type_declaration: ... def declared_type(self): ... +class uninitialized_tag: ... + def arrange_polygons(polygons): ... def clear_schemas(): ... def construct_iterator_with_include_exclude(geometry_library, settings, file, elems, include, num_threads): ... @@ -1581,6 +1608,7 @@ def flatten(deep): ... def get_feature(x): ... def get_info_cpp(v, include_identifier): ... def get_log(): ... +def guess_file_type(fn): ... def helmert_curve_point(A0, A1, A2, s): ... def kind_to_string(k): ... def less(arg1, arg2): ... @@ -1588,7 +1616,7 @@ def line_segments_to_polygons(s, eps, segments): ... def map_shape(settings, instance): ... def nary_union(sequence): ... def new_IfcBaseClass(schema_identifier: str, name: str) -> entity_instance: ... -def open(fn): ... +def open(fn, readonly): ... def parse_ifcxml(filename): ... def polygons_to_svg(*args): ... def read(data): ... @@ -1599,6 +1627,7 @@ def serialise(schema_name, shape_str, advanced): ... def set_feature(x, v): ... def set_log_format_json(): ... def set_log_format_text(): ... +def stream_from_string(data): ... def svg_to_line_segments(data, class_name): ... def svg_to_polygons(data, class_name): ... def taxonomy_item_repr(i): ... diff --git a/src/ifcopenshell-python/ifcopenshell/util/brick.py b/src/ifcopenshell-python/ifcopenshell/util/brick.py index 8c11dacdfe..04e0be5452 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/brick.py +++ b/src/ifcopenshell-python/ifcopenshell/util/brick.py @@ -53,6 +53,8 @@ def get_brick_type(element: ifcopenshell.entity_instance) -> Union[str, 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"): diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index bf870cb1ed..7bbe8bdc99 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -474,7 +474,7 @@ def get_elements_by_pset(pset: ifcopenshell.entity_instance) -> set[ifcopenshell """Retrieve the elements (or element types) that are using the provided property set.""" is_ifc2x3 = pset.file.schema == "IFC2X3" elements = set() - if pset.is_a("IfcPropertySet") or pset.is_a("IfcElementQuantity"): + if pset.is_a("IfcPropertySet") or pset.is_a("IfcPreDefinedPropertySet") or pset.is_a("IfcElementQuantity"): rels = pset.PropertyDefinitionOf if is_ifc2x3 else pset.DefinesOccurrence for rel in rels: elements.update(rel.RelatedObjects) diff --git a/src/ifcopenshell-python/ifcopenshell/util/ifc4_to_brick.json b/src/ifcopenshell-python/ifcopenshell/util/ifc4_to_brick.json index 5c07e00bf7..d76d2c0c80 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/ifc4_to_brick.json +++ b/src/ifcopenshell-python/ifcopenshell/util/ifc4_to_brick.json @@ -27,10 +27,9 @@ "IfcFlowMeter.WATERMETER": "Water_Meter", "IfcElectricMotor": "Motor", "IfcSolarDevice.SOLARPANEL": "PV_Panel", - "IfcBuilding": "Building", - "IfcBuildingStorey": "Floor", - "IfcSpace": "Space", - "IfcSpatialZone": "Zone", - "IfcSpatialZone.THERMAL": "HVAC_Zone", - "IfcSpatialZone.LIGHTING": "Lighting_Zone" + "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" } diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index aa6789865f..8bb7955d91 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -856,8 +856,10 @@ class FacetTransformer(lark.Transformer): comparison, value = args def filter_function(element: ifcopenshell.entity_instance) -> bool: - element_value = getattr(ifcopenshell.util.element.get_type(element), "Name", None) - return self.compare(element_value, comparison, value) + element_type = ifcopenshell.util.element.get_type(element) + return self.compare(getattr(element_type, "Name", None), comparison, value) or self.compare( + getattr(element_type, "GlobalId", None), comparison, value + ) self.add_default_elements() self.elements = set(filter(filter_function, self.elements)) @@ -940,7 +942,7 @@ class FacetTransformer(lark.Transformer): containers = self.get_container_tree(container) result = False if containers else None for container in containers: - if self.compare(container.Name, "=", value): + if self.compare(container.Name, "=", value) or self.compare(container.GlobalId, "=", value): result = True if result is not None: return result if comparison == "=" else not result @@ -958,6 +960,8 @@ class FacetTransformer(lark.Transformer): if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup: if self.compare(rel.RelatingGroup.Name, "=", value): result = True + elif self.compare(rel.RelatingGroup.GlobalId, "=", value): + result = True return result if comparison == "=" else not result self.add_default_elements() @@ -969,32 +973,44 @@ class FacetTransformer(lark.Transformer): parents = set() for rel in self.file.by_type("IfcRelAggregates"): parent = rel.RelatingObject - if parent and self.compare(parent.Name, comparison, value): + if parent and ( + self.compare(parent.Name, comparison, value) or self.compare(parent.GlobalId, comparison, value) + ): parents.add(parent) for rel in self.file.by_type("IfcRelContainedInSpatialStructure"): parent = rel.RelatingStructure - if parent and self.compare(parent.Name, comparison, value): + if parent and ( + self.compare(parent.Name, comparison, value) or self.compare(parent.GlobalId, comparison, value) + ): parents.add(parent) for rel in self.file.by_type("IfcRelNests"): parent = rel.RelatingObject - if parent and self.compare(parent.Name, comparison, value): + if parent and ( + self.compare(parent.Name, comparison, value) or self.compare(parent.GlobalId, comparison, value) + ): parents.add(parent) for rel in self.file.by_type("IfcRelVoidsElement"): parent = rel.RelatingBuildingElement - if parent and self.compare(parent.Name, comparison, value): + if parent and ( + self.compare(parent.Name, comparison, value) or self.compare(parent.GlobalId, comparison, value) + ): parents.add(parent) for rel in self.file.by_type("IfcRelVoidsElement"): parent = rel.RelatingBuildingElement - if parent and self.compare(parent.Name, comparison, value): + if parent and ( + self.compare(parent.Name, comparison, value) or self.compare(parent.GlobalId, comparison, value) + ): parents.add(parent) for rel in self.file.by_type("IfcRelFillsElement"): parent = rel.RelatingOpeningElement - if parent and self.compare(parent.Name, comparison, value): + if parent and ( + self.compare(parent.Name, comparison, value) or self.compare(parent.GlobalId, comparison, value) + ): parents.add(parent) children: set[ifcopenshell.entity_instance] = set() diff --git a/src/ifcopenshell-python/test/test_create_shape.py b/src/ifcopenshell-python/test/test_create_shape.py index 57ffa0b78b..bc743217bf 100644 --- a/src/ifcopenshell-python/test/test_create_shape.py +++ b/src/ifcopenshell-python/test/test_create_shape.py @@ -1,3 +1,8 @@ +import functools +import itertools +import multiprocessing +import operator +import os import pytest import test.bootstrap import ifcopenshell @@ -12,6 +17,8 @@ import ifcopenshell.util.shape from ifcopenshell.util.shape_builder import ShapeBuilder from typing import get_args +fn = os.path.join(os.path.dirname(__file__), "fixtures/ColumnPSetsOfSets.ifc") + class TestGeomSettings: def test_settings(self): @@ -185,6 +192,28 @@ class TestAssignObject: assert len(set(vs)) == 12 +def test_iterator(): + # just test some permutations of invocation + settings = ifcopenshell.geom.settings() + file_or_filename = [fn, ifcopenshell.open(fn)] + with_or_without_threads = [[], [multiprocessing.cpu_count()]] + includes = [ + {}, + {"include": ["IfcColumn"]}, + {"include": [file_or_filename[1].by_type("IfcColumn")[0]]}, + ] + for args in itertools.product(file_or_filename, with_or_without_threads, includes): + kwargs = functools.reduce(operator.or_, (a for a in args if isinstance(a, dict))) + pargs = [] + for a in (_ for _ in args if not isinstance(_, dict)): + if isinstance(a, list): + pargs.extend(a) + else: + pargs.append(a) + iterator = ifcopenshell.geom.iterator(settings, *pargs, **kwargs) + assert iterator.initialize() + + if __name__ == "__main__": import pytest diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index 50b032fe8e..36d200539a 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -196,6 +196,7 @@ class TestFilterElements(test.bootstrap.IFC4): assert subject.filter_elements(self.file, "IfcWall, type=Foo") == {element} assert subject.filter_elements(self.file, 'IfcWall, type="Foo"') == {element} assert subject.filter_elements(self.file, "IfcWall, type=/Fo.*/") == {element} + assert subject.filter_elements(self.file, f"IfcWall, type={element_type.GlobalId}") == {element} def test_selecting_by_material(self): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") @@ -273,6 +274,7 @@ class TestFilterElements(test.bootstrap.IFC4): assert subject.filter_elements(self.file, "IfcWall, location=G") == {element, element2} assert subject.filter_elements(self.file, "IfcWall, location=Building") == {element, element2} assert subject.filter_elements(self.file, "IfcWall, location!=Space") == {element2} + assert subject.filter_elements(self.file, f"IfcWall, location={space.GlobalId}") == {element} def test_selecting_by_group(self): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") @@ -281,6 +283,7 @@ class TestFilterElements(test.bootstrap.IFC4): ifcopenshell.api.group.assign_group(self.file, products=[element], group=group) assert subject.filter_elements(self.file, "IfcWall, group=Foo") == {element} assert subject.filter_elements(self.file, "IfcWall, group!=Foo") == {element2} + assert subject.filter_elements(self.file, f"IfcWall, group={group.GlobalId}") == {element} def test_selecting_by_parent(self): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall", name="Element1") @@ -300,6 +303,7 @@ class TestFilterElements(test.bootstrap.IFC4): assert subject.filter_elements(self.file, "IfcWall, parent=Space") == {element} assert subject.filter_elements(self.file, "IfcWall, parent=G") == {element, element2, element3} assert subject.filter_elements(self.file, "IfcWall, parent=Element2") == {element3} + assert subject.filter_elements(self.file, "IfcWall, parent=Space") == {element} def test_selecting_multiple_filter_groups(self): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") diff --git a/src/ifcwrap/IfcGeomWrapper.i b/src/ifcwrap/IfcGeomWrapper.i index 5b7b4aba0b..6991abbdaf 100644 --- a/src/ifcwrap/IfcGeomWrapper.i +++ b/src/ifcwrap/IfcGeomWrapper.i @@ -628,6 +628,7 @@ struct ShapeRTTI : public boost::static_visitor $result = boost::apply_visitor(ShapeRTTI(), (boost::variant) $1); } +%newobject construct_iterator; %newobject construct_iterator_with_include_exclude; %newobject construct_iterator_with_include_exclude_globalid; %newobject construct_iterator_with_include_exclude_id; @@ -635,6 +636,10 @@ struct ShapeRTTI : public boost::static_visitor // I couldn't get the vector typemap to be applied when %extending Iterator constructor. // anyway it does not matter as SWIG generates C code without actual constructors %inline %{ + IfcGeom::Iterator* construct_iterator(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, IfcParse::IfcFile* file, int num_threads) { + return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings), settings, file, num_threads); + } + IfcGeom::Iterator* construct_iterator_with_include_exclude(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, IfcParse::IfcFile* file, std::vector elems, bool include, int num_threads) { std::set elems_set(elems.begin(), elems.end()); IfcGeom::entity_filter ef{ include, false, elems_set }; diff --git a/src/ifcwrap/IfcParseWrapper.i b/src/ifcwrap/IfcParseWrapper.i index 858705ebf0..1bc43b422c 100644 --- a/src/ifcwrap/IfcParseWrapper.i +++ b/src/ifcwrap/IfcParseWrapper.i @@ -33,6 +33,10 @@ private: %ignore IfcParse::IfcFile::types_end; %ignore IfcParse::IfcFile::internal_guid_map; %ignore IfcParse::IfcFile::storage_; +%ignore IfcParse::IfcFile::byguid_; +%ignore IfcParse::IfcFile::byid_; +%ignore IfcParse::IfcFile::byref_excl_; +%ignore IfcParse::IfcFile::types_to_bypass_loading_; %ignore IfcParse::InstanceStreamer::InstanceStreamer(const IfcParse::schema_definition* schema, IfcParse::IfcSpfLexer* lexer); diff --git a/src/ifcwrap/IfcPython.i b/src/ifcwrap/IfcPython.i index 7a3fd48139..8595e264e2 100644 --- a/src/ifcwrap/IfcPython.i +++ b/src/ifcwrap/IfcPython.i @@ -93,6 +93,10 @@ %ignore curve_to_face_upgrade_impl; %ignore loop_to_function_item_upgrade_impl; +%ignore IfcGeom::geometry_exception; +%ignore IfcGeom::too_many_faces_exception; +%ignore ifcopenshell::geometry::taxonomy::topology_error; + // settings, can this done more generally? // GeometrySerializer.h %ignore UseElementNames; diff --git a/src/opencdeserver/api/app/api/logging.py b/src/opencdeserver/api/app/api/logging.py index 49131c0ce1..de368ccb9c 100644 --- a/src/opencdeserver/api/app/api/logging.py +++ b/src/opencdeserver/api/app/api/logging.py @@ -3,7 +3,7 @@ from starlette.background import BackgroundTask from starlette.responses import StreamingResponse from fastapi.routing import APIRoute from starlette.types import Message -from typing import Callable +from collections.abc import Callable import logging import httpx