├── .editorconfig ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── docs.yml │ ├── pr-checks.yml │ └── release.yml ├── .gitignore ├── AUTHORS.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── compas_ifc-small.png ├── compas_ifc.png ├── conftest.py ├── data ├── Duplex_A_20110907.ifc ├── PLACEHOLDER ├── layer_template.3dm └── wall-with-opening-and-window.ifc ├── docs ├── _images │ ├── PLACEHOLDER │ ├── architecture.jpg │ ├── compas_ifc.png │ ├── element_view.jpg │ ├── model_view.jpg │ ├── viewer_layout.jpg │ └── visualisation.jpg ├── _static │ ├── PLACEHOLDER │ ├── compas_icon_white.png │ └── custom.css ├── api.rst ├── api │ ├── compas_ifc.entities.generated.rst │ ├── compas_ifc.entities.rst │ └── compas_ifc.model.rst ├── architecture.rst ├── conf.py ├── examples.rst ├── examples │ ├── Advanced.1_units.rst │ ├── Advanced.2_sessions.rst │ ├── Advanced.3_custom_extensions.rst │ ├── Basics.1_overview.rst │ ├── Basics.2_query_entities.rst │ ├── Basics.3_spatial_hierarchy.rst │ ├── Basics.4_element_info.rst │ ├── Basics.5_visualization.rst │ ├── Basics.6_edit_export.rst │ └── Basics.7_create_new.rst ├── index.rst ├── installation.rst ├── license.rst ├── templates │ └── class.rst ├── tutorials.rst └── tutorials │ ├── advanced.custom_extensions.rst │ ├── basics.create_model.rst │ ├── basics.entity_apis.rst │ ├── basics.hello_world.rst │ └── intermediate.multi_story_building.rst ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── scripts ├── 0.1_overview.py ├── 1.1_query_entities.py ├── 1.2_traverse_hierarchy.py ├── 2.1_project_info.py ├── 2.2_site_info.py ├── 2.3_window_info.py ├── 3.1_model_view.py ├── 3.2_element_view.py ├── 4.1_edit_export.py ├── 4.2_create_new.py ├── 5.1_export_from_rhino.py ├── 6.1_custom_extension.py └── PLACEHOLDER ├── src └── compas_ifc │ ├── __init__.py │ ├── __main__.py │ ├── __old │ ├── __init__.py │ ├── attribute.py │ ├── attributes.py │ ├── building.py │ ├── buildingelements.py │ ├── buildingstorey.py │ ├── element.py │ ├── entity.py │ ├── factory.py │ ├── geographicelement.py │ ├── geometricmodel.py │ ├── helpers.py │ ├── model.py │ ├── objectdefinition.py │ ├── product.py │ ├── project.py │ ├── reader.py │ ├── representation.py │ ├── root.py │ ├── site.py │ ├── space.py │ ├── spatialelement.py │ └── writer.py │ ├── brep │ ├── __init__.py │ ├── ifcbrepobject.py │ ├── tessellatedbrep.py │ └── tessellatedbrepobject.py │ ├── conversions │ ├── brep.py │ ├── frame.py │ ├── mesh.py │ ├── primitives.py │ ├── pset.py │ ├── representation.py │ ├── shapes.py │ └── unit.py │ ├── entities │ ├── __init__.py │ ├── base.py │ ├── extensions │ │ ├── IfcBuilding.py │ │ ├── IfcContext.py │ │ ├── IfcElement.py │ │ ├── IfcObject.py │ │ ├── IfcObjectDefinition.py │ │ ├── IfcProduct.py │ │ ├── IfcProject.py │ │ ├── IfcSite.py │ │ ├── IfcSpatialElement.py │ │ ├── IfcSpatialStructureElement.py │ │ └── __init__.py │ ├── generator.py │ ├── generator_enumeration_type.py │ └── generator_type_definition.py │ ├── file.py │ └── model.py ├── tasks.py ├── temp └── PLACEHOLDER └── tests └── test_placeholder.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | charset = utf-8 11 | max_line_length = 179 12 | 13 | [*.{bat,cmd,ps1}] 14 | end_of_line = crlf 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | 19 | [*.yml] 20 | indent_size = 2 21 | 22 | [*.rst] 23 | indent_size = 4 24 | 25 | [Makefile] 26 | indent_style = tab 27 | indent_size = 4 28 | 29 | [LICENSE] 30 | insert_final_newline = false 31 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at brg@arch.ethz.ch. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | 9 | A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** 12 | 13 | Steps to reproduce the behavior: 14 | 15 | 1. Context [e.g. ST3, Rhino, Blender, ...] 16 | 2. Sample script 17 | 3. Sample data 18 | 4. See error 19 | 20 | **Expected behavior** 21 | 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Desktop (please complete the following information):** 29 | 30 | - OS: [e.g. iOS] 31 | - Python version [e.g. 2.7] 32 | - Python package manager [e.g. macports, pip, conda] 33 | 34 | **Additional context** 35 | 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | # Feature Request 7 | 8 | As a [role], I want [something] so that [benefit]. 9 | 10 | ## Details 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | 14 | A clear and concise description of what the problem is. 15 | 16 | **Describe the solution you'd like.** 17 | 18 | A clear and concise description of what you want to happen. 19 | 20 | **Describe alternatives you've considered.** 21 | 22 | A clear and concise description of any alternative solutions or features you've considered. 23 | 24 | **Additional context.** 25 | 26 | Add any other context or screenshots about the feature request here. 27 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ### What type of change is this? 6 | 7 | - [ ] Bug fix in a **backwards-compatible** manner. 8 | - [ ] New feature in a **backwards-compatible** manner. 9 | - [ ] Breaking change: bug fix or new feature that involve incompatible API changes. 10 | - [ ] Other (e.g. doc update, configuration, etc) 11 | 12 | ### Checklist 13 | 14 | _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ 15 | 16 | - [ ] I added a line to the `CHANGELOG.md` file in the `Unreleased` section under the most fitting heading (e.g. `Added`, `Changed`, `Removed`). 17 | - [ ] I ran all tests on my computer and it's all green (i.e. `invoke test`). 18 | - [ ] I ran lint on my computer and there are no errors (i.e. `invoke lint`). 19 | - [ ] I added new functions/classes and made them available on a second-level import, e.g. `compas.datastructures.Mesh`. 20 | - [ ] I have added tests that prove my fix is effective or that my feature works. 21 | - [ ] I have added necessary documentation (if appropriate) 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | Build: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [macos-latest, windows-latest, ubuntu-latest] 17 | python: ['3.10', '3.11'] 18 | 19 | steps: 20 | - uses: compas-dev/compas-actions.build@v4 21 | with: 22 | python: ${{ matrix.python }} 23 | invoke_lint: true 24 | invoke_test: true 25 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | docs: 15 | runs-on: windows-latest 16 | steps: 17 | - uses: compas-dev/compas-actions.docs@v4 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | doc_url: https://compas.dev/compas_ifc 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/pr-checks.yml: -------------------------------------------------------------------------------- 1 | name: verify-pr-checklist 2 | on: 3 | pull_request: 4 | types: [assigned, opened, synchronize, reopened, labeled, unlabeled] 5 | branches: 6 | - main 7 | - master 8 | 9 | jobs: 10 | build: 11 | name: Check Actions 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Changelog check 16 | uses: Zomzog/changelog-checker@v1.2.0 17 | with: 18 | fileName: CHANGELOG.md 19 | checkNotification: Simple 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [macos-latest, windows-latest, ubuntu-latest] 14 | python: ['3.10', '3.11'] 15 | 16 | steps: 17 | - uses: compas-dev/compas-actions.build@v4 18 | with: 19 | python: ${{ matrix.python }} 20 | invoke_lint: true 21 | check_import: true 22 | 23 | publish: 24 | needs: build 25 | runs-on: windows-latest 26 | steps: 27 | - uses: compas-dev/compas-actions.publish@v3 28 | with: 29 | pypi_token: ${{ secrets.PYPI }} 30 | github_token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # ============================================================================== 104 | # COMPAS IFC 105 | # ============================================================================== 106 | 107 | *.3dmbak 108 | *.rhl 109 | *.rui_bak 110 | 111 | temp/** 112 | !temp/PLACEHOLDER 113 | 114 | .DS_Store 115 | 116 | .vscode 117 | 118 | docs/api/generated/ 119 | 120 | conda.recipe/ 121 | 122 | generated/ -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | - tom van mele <> [@brgcode](https://github.com/brgcode) 4 | - li chen <> [@licini](https://github.com/licini) 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ### Added 11 | 12 | ### Changed 13 | 14 | ### Removed 15 | 16 | 17 | ## [1.6.0] 2025-06-04 18 | 19 | ### Added 20 | 21 | ### Changed 22 | 23 | ### Removed 24 | 25 | 26 | ## [1.5.0] 2025-01-21 27 | 28 | ### Added 29 | 30 | * Added `extensions` keyword argument to `Model` to for inserting custom extensions to IFC classes. 31 | 32 | ### Changed 33 | 34 | ### Removed 35 | 36 | 37 | ## [1.4.1] 2024-10-02 38 | 39 | ### Added 40 | 41 | ### Changed 42 | 43 | ### Removed 44 | 45 | 46 | ## [1.4.0] 2024-09-30 47 | 48 | ### Added 49 | 50 | * Added `Model.search_ifc_classes()` and `File.search_ifc_classes()` to search for IFC classes. 51 | * Added `Model.create_wall()`. 52 | * Added `Model.create_slab()`. 53 | * Added `Model.create_window()`. 54 | * Added `Model.create_door()`. 55 | * Added `Model.create_stair()`. 56 | * Added `Model.create_railing()`. 57 | * Added `Model.create_column()`. 58 | * Added `Model.create_beam()`. 59 | * Added `aabb` axis-aligned bounding box to `TessellatedBrep`. 60 | * Added `obb` oriented bounding box to `TessellatedBrep`. 61 | 62 | ### Changed 63 | 64 | ### Removed 65 | 66 | 67 | ## [1.3.1] 2024-08-25 68 | 69 | ### Added 70 | 71 | ### Changed 72 | 73 | ### Removed 74 | 75 | 76 | ## [1.3.0] 2024-08-23 77 | 78 | ### Added 79 | 80 | * Added `Model.create_default_project()`. 81 | * Added `TesselatedBrep.to_mesh()`. 82 | * Added `location` to `IfcSite` extension. 83 | * Added `compas_ifc.resources.IfcCompoundPlaneAngleMeasure_to_degrees()`. 84 | 85 | ### Changed 86 | 87 | ### Removed 88 | 89 | 90 | ## [1.2.4] 2024-08-22 91 | 92 | ### Added 93 | 94 | * Added `max_depth` to `Base.print_properties()`. 95 | 96 | ### Changed 97 | 98 | * Fixed `Model.print_summary()` while the model is empty. 99 | 100 | ### Removed 101 | 102 | 103 | ## [1.2.3] 2024-08-16 104 | 105 | ### Added 106 | 107 | ### 108 | 109 | * Fixed missing `GloabalId` when creating `IfcRoot` based objects. 110 | * Updated `IfcBrepObject` to automatically heal and simplify breps. 111 | 112 | ### Removed 113 | 114 | 115 | ## [1.2.2] 2024-07-31 116 | 117 | ### Added 118 | 119 | ### Changed 120 | 121 | * Fixed `Base.to_dict()` to recursively pass down convert_type_defination. 122 | 123 | ### Removed 124 | 125 | 126 | ## [1.2.1] 2024-07-30 127 | 128 | ### Added 129 | 130 | ### Changed 131 | 132 | * Fixed `verbose` bug. 133 | 134 | ### Removed 135 | 136 | 137 | ## [1.2.0] 2024-07-30 138 | 139 | ### Added 140 | 141 | * Added `compas_ifc.entities.extensions.IfcContext` to extend `IfcContext` class. 142 | * Added `verbose` option to `Model` and `IFCFile`. 143 | * Added `compas_ifc.entities.TypeDefinition` class. 144 | * Added `remove()` to `Model` for removing entities. 145 | 146 | ### Changed 147 | 148 | ### Removed 149 | 150 | 151 | ## [1.1.0] 2024-07-22 152 | 153 | ### Added 154 | 155 | * Added `export` method to `IFCFile` and `Model` to export selected list of entities. 156 | * Added `update_linear_deflection` to `Model`. 157 | * Added `unit` attribute to `Model`. 158 | * Added `unit` keyword argument to `Model.template()`. 159 | * Added `recursive`, `ignore_fields`, `include_fields` options to `Base.to_dict()`. 160 | * Added `quantities` to `compas_ifc.entities.extensions.IfcObject`. 161 | 162 | ### Changed 163 | 164 | * Automatically convert `Brep` to `Mesh` when assigned in `IFC2X3`. 165 | 166 | ### Removed 167 | 168 | 169 | ## [1.0.0] 2024-07-12 170 | 171 | ### Added 172 | 173 | * Added full python class mapping for `IFC4` and `IFC2x3` using `compas_ifc.entities.Generator`. 174 | * All `IFC4` and `IFC2x3` classes are now available in `compas_ifc.entities.generated` module. 175 | * All generated classes are strongly typed and have docstrings. 176 | * Added `compas_ifc.entities.extensions` module to extend generated IFC classes. 177 | * Added `show` function to visualize IFC model and individual entities. 178 | * Added `max_depth` in `print_spatial_hierarchy` functions. 179 | * Added `building_storeys` to `compas_ifc.model.Model`. 180 | * Added `compas_ifc.brep.IFCBrepObject`. 181 | 182 | ### Changed 183 | 184 | * Combined `compas_ifc.reader.Reader` and `compas_ifc.writer.Writer` into `compas_ifc.file.IFCFile`. 185 | * Updated `create` in `compas_ifc.model.Model` to accept snake_case keyword arguments. 186 | 187 | ### Removed 188 | 189 | * Removed all `compas_ifc.entities.Entity` based class wrappers, use fully mapped classes in `compas_ifc.entities.generated` instead. 190 | * Removed `representation.py` and `helper.py`. 191 | 192 | ## [0.6.0] 2024-06-26 193 | 194 | ### Added 195 | 196 | ### Changed 197 | 198 | ### Removed 199 | 200 | 201 | ## [0.5.1] 2024-06-14 202 | 203 | ### Added 204 | 205 | ### Changed 206 | 207 | * Locked `ifcopenshell` to `0.7.0.240406` to avoid mathutils build failures. 208 | 209 | ### Removed 210 | 211 | 212 | ## [0.5.0] 2024-06-13 213 | 214 | ### Added 215 | 216 | ### Changed 217 | 218 | ### Removed 219 | 220 | 221 | ## [0.4.1] 2024-05-15 222 | 223 | ### Added 224 | 225 | ### Changed 226 | 227 | ### Removed 228 | 229 | 230 | ## [0.4.0] 2024-05-14 231 | 232 | ### Added 233 | 234 | * Added support to export to `IFC2x3`. 235 | * Added support pre-load geometries using `multi-processing`. 236 | 237 | ### Changed 238 | 239 | * Updated workflow to not use `conda` anymore. 240 | * Updated `Reader` to re-enable lazy loading. 241 | * Update repo to use `pyproject.toml`. 242 | 243 | ### Removed 244 | 245 | 246 | ## [0.3.0] 2024-02-01 247 | 248 | ### Added 249 | 250 | * Added `entity_opening_geometry`. 251 | * Added `entity_body_with_opening_geometry`. 252 | * Added `opening` attribute to `Product`. 253 | * Added `body_with_opening` attribute to `Product`. 254 | * Added `composite_body` attribute to `BuildingElement`. 255 | * Added `composite_opening` attribute to `BuildingElement`. 256 | * Added `composite_body_with_opening` attribute to `BuildingElement`. 257 | * Added Documentation site. 258 | 259 | ### Changed 260 | 261 | * `entity_body_geometry` no longer includes openings. 262 | * `parent` of `Element` will now also consider `decompose` relation. 263 | * Updated all APIs to COMPAS 2. 264 | 265 | ### Removed 266 | 267 | ## [0.2.0] 2023-03-21 268 | 269 | ### Added 270 | 271 | ### Changed 272 | 273 | ### Removed 274 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome and very much appreciated! 4 | 5 | ## Code contributions 6 | 7 | We accept code contributions through pull requests. 8 | In short, this is how that works. 9 | 10 | 1. Fork [the repository](https://github.com/blockresearchgroup/compas_ifc) and clone the fork. 11 | 2. Create a virtual environment using your tool of choice (e.g. `virtualenv`, `conda`, etc). 12 | 3. Install development dependencies: 13 | 14 | ```bash 15 | pip install -r requirements-dev.txt 16 | ``` 17 | 18 | 4. Make sure all tests pass: 19 | 20 | ```bash 21 | invoke test 22 | ``` 23 | 24 | 5. Start making your changes to the **master** branch (or branch off of it). 25 | 6. Make sure all tests still pass: 26 | 27 | ```bash 28 | invoke test 29 | ``` 30 | 31 | 7. Add yourself to the *Contributors* section of `AUTHORS.md`. 32 | 8. Commit your changes and push your branch to GitHub. 33 | 9. Create a [pull request](https://help.github.com/articles/about-pull-requests/) through the GitHub website. 34 | 35 | During development, use [pyinvoke](http://docs.pyinvoke.org/) tasks on the 36 | command line to ease recurring operations: 37 | 38 | * `invoke clean`: Clean all generated artifacts. 39 | * `invoke check`: Run various code and documentation style checks. 40 | * `invoke docs`: Generate documentation. 41 | * `invoke test`: Run all tests and checks in one swift command. 42 | * `invoke`: Show available tasks. 43 | 44 | ## Bug reports 45 | 46 | When [reporting a bug](https://github.com/blockresearchgroup/compas_ifc/issues) please include: 47 | 48 | * Operating system name and version. 49 | * Any details about your local setup that might be helpful in troubleshooting. 50 | * Detailed steps to reproduce the bug. 51 | 52 | ## Feature requests 53 | 54 | When [proposing a new feature](https://github.com/blockresearchgroup/compas_ifc/issues) please include: 55 | 56 | * Explain in detail how it would work. 57 | * Keep the scope as narrow as possible, to make it easier to implement. 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | ETH Zurich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft src 2 | 3 | prune .github 4 | prune data 5 | prune docs 6 | prune scripts 7 | prune tests 8 | prune temp 9 | 10 | include LICENSE 11 | include README.md 12 | include AUTHORS.md 13 | include CHANGELOG.md 14 | include requirements.txt 15 | 16 | exclude requirements-dev.txt 17 | exclude pytest.ini .bumpversion.cfg .editorconfig 18 | exclude tasks.py 19 | exclude CONTRIBUTING.md 20 | exclude conftest.py 21 | exclude *.png 22 | exclude *.yml 23 | 24 | global-exclude *.py[cod] __pycache__ *.dylib *.nb[ic] .DS_Store 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # COMPAS IFC 2 | 3 | ![compas_ifc](compas_ifc.png) 4 | 5 | Industry Foundation Classes (IFC) is a data model that describes building and construction industry data. It is a platform neutral, open file format specification that is not controlled by a single vendor or group of vendors. It is an object-based file format with a data model developed by buildingSMART (formerly the International Alliance for Interoperability, IAI) to facilitate interoperability in the architecture, engineering and construction (AEC) industry, and is a commonly used collaboration format in Building information modeling (BIM) based projects. The latest IFC documentation and specification can be found at https://ifc43-docs.standards.buildingsmart.org/. 6 | 7 | IFC is now widely adopted by industry and often the required format for BIM data exchange and project delivery. However Working direcly with IFC content is a highly complicated task, due to its multi-layered class inheritances and complex spatial heirarchies. 8 | 9 | COMPAS IFC is a COMPAS extension developed to make our lives easier. It allows us to work with IFC files in an accessible, developer-friendly and pythonic way. By creating a two-way bridge between IFC files and COMPAS data structures, we can immediately benifit from wide range of tools for geometric processing and analysing from COMPAS ecosystem. COMPAS IFC also allows us to export processed data back to valid IFC files. 10 | 11 | COMPAS IFC relies on [IfcOpenShell](https://ifcopenshell.org/) for lower-level entity parsing, schema retriving and file manipulations, On top of that we additionally simplify the workflow to interact with IFC contents. 12 | 13 | Some of the core features of COMPAS IFC are: 14 | 15 | - Prase IFC files and inspect its entities 16 | - Traverse IFC spatial heirarchies in a user-friendly way 17 | - View and edit IFC entity attributes and properties 18 | - Extract geometric representations from IFC entities as COMPAS geometry and data structures 19 | - Export selected IFC entities while maintaining minimal valid spatial structure 20 | - Insert new IFC entities into existing IFC files. 21 | - Create entirely new IFC files from scratch with valid spatial structure (in progress) 22 | 23 | 24 | This package is still early stage of development. The repo structure is subject to change. If you have questions please do not hesitate create a new issue on github repo or contanct me directly at li.chen@arch.ethz.ch . 25 | 26 | 27 | 28 | Please see documentation at: http://compas.dev/compas_ifc/ 29 | -------------------------------------------------------------------------------- /compas_ifc-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/compas_ifc-small.png -------------------------------------------------------------------------------- /compas_ifc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/compas_ifc.png -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import compas 3 | import compas_ifc 4 | import math 5 | import numpy 6 | from compas.geometry import allclose 7 | 8 | 9 | def pytest_ignore_collect(path): 10 | if "rhino" in str(path): 11 | return True 12 | 13 | if "blender" in str(path): 14 | return True 15 | 16 | if "ghpython" in str(path): 17 | return True 18 | 19 | if str(path).endswith("_cli.py"): 20 | return True 21 | 22 | 23 | @pytest.fixture(autouse=True) 24 | def add_compas(doctest_namespace): 25 | doctest_namespace["compas"] = compas 26 | 27 | 28 | @pytest.fixture(autouse=True) 29 | def add_compas_ifc(doctest_namespace): 30 | doctest_namespace["compas_ifc"] = compas_ifc 31 | 32 | 33 | @pytest.fixture(autouse=True) 34 | def add_math(doctest_namespace): 35 | doctest_namespace["math"] = math 36 | 37 | 38 | @pytest.fixture(autouse=True) 39 | def add_np(doctest_namespace): 40 | doctest_namespace["np"] = numpy 41 | 42 | 43 | @pytest.fixture(autouse=True) 44 | def add_allclose(doctest_namespace): 45 | doctest_namespace["allclose"] = allclose 46 | -------------------------------------------------------------------------------- /data/PLACEHOLDER: -------------------------------------------------------------------------------- 1 | # container for sample data files 2 | -------------------------------------------------------------------------------- /data/layer_template.3dm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/data/layer_template.3dm -------------------------------------------------------------------------------- /docs/_images/PLACEHOLDER: -------------------------------------------------------------------------------- 1 | # container for images to be included in the docs 2 | -------------------------------------------------------------------------------- /docs/_images/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/docs/_images/architecture.jpg -------------------------------------------------------------------------------- /docs/_images/compas_ifc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/docs/_images/compas_ifc.png -------------------------------------------------------------------------------- /docs/_images/element_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/docs/_images/element_view.jpg -------------------------------------------------------------------------------- /docs/_images/model_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/docs/_images/model_view.jpg -------------------------------------------------------------------------------- /docs/_images/viewer_layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/docs/_images/viewer_layout.jpg -------------------------------------------------------------------------------- /docs/_images/visualisation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/docs/_images/visualisation.jpg -------------------------------------------------------------------------------- /docs/_static/PLACEHOLDER: -------------------------------------------------------------------------------- 1 | # container for static files, e.g. logo, banner images, javascript, stylesheets, ... 2 | -------------------------------------------------------------------------------- /docs/_static/compas_icon_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/docs/_static/compas_icon_white.png -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This will apply to the main content area */ 2 | .bd-content { 3 | min-width: 800px; 4 | } 5 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | API Reference 3 | ******************************************************************************** 4 | 5 | .. rst-class:: lead 6 | This is the API reference for the Compas IFC package. 7 | 8 | .. Modules 9 | .. ======= 10 | 11 | .. toctree:: 12 | :maxdepth: 1 13 | :titlesonly: 14 | 15 | api/compas_ifc.model 16 | api/compas_ifc.entities 17 | api/compas_ifc.entities.generated 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/api/compas_ifc.entities.generated.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Generated 3 | ******************************************************************************* 4 | This module contains all the auto generated IFC class wrappers. 5 | 6 | IFC4 7 | ==== 8 | 9 | .. .. automodule:: compas_ifc.entities.generated.IFC4 10 | .. :members: 11 | 12 | IFC2x3 13 | ====== 14 | 15 | .. .. automodule:: compas_ifc.entities.generated.IFC2x3 16 | .. :members: 17 | -------------------------------------------------------------------------------- /docs/api/compas_ifc.entities.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Entities 3 | ******************************************************************************* 4 | Generated IFC class wrappers. 5 | 6 | .. currentmodule:: compas_ifc.entities 7 | 8 | Base Class 9 | ========== 10 | 11 | .. autosummary:: 12 | :toctree: generated/ 13 | :nosignatures: 14 | 15 | base.Base 16 | base.TypeDefinition 17 | 18 | Extensions 19 | ========== 20 | 21 | .. autosummary:: 22 | :toctree: generated/ 23 | :nosignatures: 24 | 25 | extensions.IfcBuilding 26 | extensions.IfcContext 27 | extensions.IfcElement 28 | extensions.IfcObject 29 | extensions.IfcObjectDefinition 30 | extensions.IfcProduct 31 | extensions.IfcProject 32 | extensions.IfcSite 33 | extensions.IfcSpatialElement 34 | extensions.IfcSpatialStructureElement 35 | 36 | Generators 37 | ========== 38 | 39 | .. autosummary:: 40 | :toctree: generated/ 41 | :nosignatures: 42 | 43 | generator.Generator 44 | generator.EntityGenerator 45 | generator.AttributeGenerator 46 | generator.InverseAttributeGenerator 47 | generator.TypeDeclarationGenerator 48 | generator.EnumGenerator 49 | -------------------------------------------------------------------------------- /docs/api/compas_ifc.model.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Model 3 | ******************************************************************************* 4 | 5 | .. currentmodule:: compas_ifc 6 | 7 | Classes 8 | ======= 9 | 10 | .. autosummary:: 11 | :toctree: generated/ 12 | :nosignatures: 13 | 14 | model.Model 15 | file.IFCFile -------------------------------------------------------------------------------- /docs/architecture.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Software Architecture 3 | ******************************************************************************** 4 | 5 | In a nutshell, COMPAS IFC is built around a three-layer structure, purposefully designed for different group of users concerned with different level of granularity of the IFC data. 6 | 7 | - **Top-layer**: This layer provides simple usage interfaces for interacting with a BIM model. It abstracts the complexities of the IFC format, allowing non-experts to manipulate IFC data with ease. 8 | 9 | - **Middle-layer**: This layer grants access to individual IFC entities and their properties. It is designed for advanced users who need to work with the geometry and metadata of IFC entities at a more granular level. 10 | 11 | - **Bottom-layer**: This layer deals with the raw IFC data and schema processing. It concerns the most technical users such as contributors to the software itself regarding issues such as performance, memory usage, etc. 12 | 13 | The architecture is designed to separate concerns, allowing users to choose the level of complexity they need, from simple model manipulations to detailed data handling. 14 | 15 | .. figure:: _images/architecture.jpg 16 | :alt: COMPAS IFC architecture 17 | :align: center 18 | 19 | COMPAS IFC architecture 20 | 21 | 22 | 23 | 24 | Top-layer 25 | --------- 26 | 27 | The Top-layer of COMPAS IFC centers on the `Model` class, providing a user-friendly interface for IFC data interaction. This class offers intuitive APIs for simplified BIM model manipulation. 28 | 29 | **Key features of the `Model` class:** 30 | 31 | 1. **Simple file operations**: Load, save, and create IFC files effortlessly. 32 | 2. **Built-in visualization methods**: For easy model inspection. 33 | 3. **High-level querying**: For straightforward element retrieval and manipulation. 34 | 4. **Abstraction of IFC complexities**: Enabling effective BIM model work without deep IFC knowledge. 35 | 36 | This approach lowers the entry barrier for IFC file handling, allowing users to focus on their tasks rather than underlying data structures. For more details on the `Model` class, see the `Tutorials `_ section. 37 | 38 | Middle-layer 39 | ------------ 40 | 41 | The Middle-layer of COMPAS IFC provides a comprehensive suite of native Python classes for interacting with individual IFC entities and their properties. These classes are automatically generated from the official IFC schemas, ensuring a complete and up-to-date mapping of IFC classes and types. 42 | 43 | **Key features of the Middle-layer include:** 44 | 45 | 1. **Strongly typed classes**: Each class is strongly typed, enabling modern IDEs (such as those using Pylance) to provide extensive type hints and analysis. This enhances the development experience by improving code completion, reducing development time, and minimizing the need for constant documentation lookups. 46 | 47 | 2. **Robust extension mechanism**: COMPAS IFC includes a powerful extension mechanism that allows for the enhancement of key classes to simplify their usage and empowers users to create custom extensions for tailored functionality. For instance: 48 | 49 | - The ``IfcProduct`` class is extended with a ``geometry`` property that parses complex IFC geometric representations into COMPAS-based geometry. 50 | - The ``IfcElement`` class is augmented with ``parent`` and ``children`` properties, simplifying navigation of the IFC spatial hierarchy. 51 | 52 | These features provide a powerful and flexible interface for working with IFC data at a granular level, suitable for advanced users who require detailed control over IFC entities and their properties. 53 | 54 | For more information on the extension mechanism, please refer to the `API: extensions `_ section. For a comprehensive overview of the class mapping, see the `API Reference: Full class mapping `_. 55 | 56 | Bottom-layer 57 | ------------ 58 | 59 | Lastly, in the Bottom-layer, COMPAS IFC interacts with the IfcOpenShell library to parse and manage IFC data. This layer is primarily of interest to contributors and advanced users who needs to modify the lower-level functionalities for tasks such as performance optimization, memory usage reduction, etc. 60 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | 4 | import sphinx_compas2_theme 5 | 6 | # -- General configuration ------------------------------------------------ 7 | 8 | project = "COMPAS IFC" 9 | copyright = "COMPAS Association" 10 | author = "Li Chen, Tom Van Mele" 11 | package = "compas_ifc" 12 | organization = "compas-dev" 13 | 14 | master_doc = "index" 15 | source_suffix = {".rst": "restructuredtext", ".md": "markdown"} 16 | templates_path = sphinx_compas2_theme.get_autosummary_templates_path() + ["templates"] 17 | exclude_patterns = sphinx_compas2_theme.default_exclude_patterns + ["reference/**"] 18 | add_module_names = True 19 | language = "en" 20 | 21 | latest_version = sphinx_compas2_theme.get_latest_version() 22 | 23 | if latest_version == "Unreleased": 24 | release = "Unreleased" 25 | version = "latest" 26 | else: 27 | release = latest_version 28 | version = ".".join(release.split(".")[0:2]) # type: ignore 29 | 30 | # -- Extension configuration ------------------------------------------------ 31 | 32 | extensions = sphinx_compas2_theme.default_extensions 33 | extensions.remove("sphinx.ext.linkcode") 34 | 35 | # numpydoc options 36 | 37 | numpydoc_show_class_members = False 38 | numpydoc_class_members_toctree = False 39 | numpydoc_attributes_as_param_list = True 40 | 41 | # bibtex options 42 | 43 | # autodoc options 44 | 45 | autodoc_type_aliases = {} 46 | 47 | autodoc_typehints = "description" 48 | autodoc_typehints_format = "short" 49 | autodoc_typehints_description_target = "documented" 50 | 51 | autodoc_mock_imports = sphinx_compas2_theme.default_mock_imports 52 | 53 | autodoc_default_options = { 54 | "undoc-members": True, 55 | "show-inheritance": True, 56 | } 57 | 58 | autodoc_member_order = "groupwise" 59 | 60 | autoclass_content = "class" 61 | 62 | # autosummary options 63 | 64 | autosummary_generate = True 65 | autosummary_mock_imports = sphinx_compas2_theme.default_mock_imports 66 | 67 | # graph options 68 | 69 | # plot options 70 | 71 | plot_include_source = False 72 | plot_html_show_source_link = False 73 | plot_html_show_formats = False 74 | plot_formats = ["png"] 75 | 76 | # intersphinx options 77 | 78 | intersphinx_mapping = { 79 | "python": ("https://docs.python.org/", None), 80 | "compas": ("https://compas.dev/compas/latest/", None), 81 | "ifcopenshell": ("https://docs.ifcopenshell.org/autoapi/ifcopenshell/", None), 82 | } 83 | 84 | # linkcode 85 | 86 | # linkcode_resolve = sphinx_compas2_theme.get_linkcode_resolve(organization, package) 87 | 88 | # extlinks 89 | 90 | extlinks = {} 91 | 92 | # from pytorch 93 | 94 | from sphinx.writers import html, html5 95 | 96 | 97 | def replace(Klass): 98 | old_call = Klass.visit_reference 99 | 100 | def visit_reference(self, node): 101 | if "refuri" in node: 102 | refuri = node.get("refuri") 103 | if "generated" in refuri: 104 | href_anchor = refuri.split("#") 105 | if len(href_anchor) > 1: 106 | href = href_anchor[0] 107 | anchor = href_anchor[1] 108 | page = href.split("/")[-1] 109 | parts = page.split(".") 110 | if parts[-1] == "html": 111 | pagename = ".".join(parts[:-1]) 112 | if anchor == pagename: 113 | node["refuri"] = href 114 | return old_call(self, node) 115 | 116 | Klass.visit_reference = visit_reference 117 | 118 | 119 | replace(html.HTMLTranslator) 120 | replace(html5.HTML5Translator) 121 | 122 | # -- Options for HTML output ---------------------------------------------- 123 | 124 | html_theme = "sidebaronly" 125 | html_title = project 126 | 127 | html_theme_options = { 128 | "icon_links": [ 129 | { 130 | "name": "GitHub", 131 | "url": f"https://github.com/{organization}/{package}", 132 | "icon": "fa-brands fa-github", 133 | "type": "fontawesome", 134 | }, 135 | { 136 | "name": "Discourse", 137 | "url": "http://forum.compas-framework.org/", 138 | "icon": "fa-brands fa-discourse", 139 | "type": "fontawesome", 140 | }, 141 | { 142 | "name": "PyPI", 143 | "url": f"https://pypi.org/project/{package}/", 144 | "icon": "fa-brands fa-python", 145 | "type": "fontawesome", 146 | }, 147 | ], 148 | "switcher": { 149 | "json_url": f"https://raw.githubusercontent.com/{organization}/{package}/gh-pages/versions.json", 150 | "version_match": version, 151 | }, 152 | "check_switcher": False, 153 | "logo": { 154 | "image_light": "_static/compas_icon_white.png", # relative to parent of conf.py 155 | "image_dark": "_static/compas_icon_white.png", # relative to parent of conf.py 156 | "text": project, 157 | }, 158 | "navigation_depth": 2, 159 | } 160 | 161 | favicons = [ 162 | { 163 | "rel": "icon", 164 | "href": "compas.ico", # relative to the static path 165 | } 166 | ] 167 | 168 | html_context = { 169 | "github_url": "https://github.com", 170 | "github_user": organization, 171 | "github_repo": package, 172 | "github_version": "main", 173 | "doc_path": "docs", 174 | } 175 | 176 | html_static_path = sphinx_compas2_theme.get_html_static_path() + ["_static"] 177 | html_css_files = [] 178 | html_extra_path = [] 179 | html_last_updated_fmt = "" 180 | html_copy_source = False 181 | html_show_sourcelink = True 182 | html_permalinks = False 183 | html_permalinks_icon = "" 184 | html_compact_lists = True 185 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Examples 3 | ******************************************************************************** 4 | 5 | In this section you will find full examples scripts on how to use the COMPAS IFC package. 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | :titlesonly: 11 | :glob: 12 | 13 | examples/Basics.1_overview.rst 14 | examples/Basics.2_query_entities.rst 15 | examples/Basics.3_spatial_hierarchy.rst 16 | examples/Basics.4_element_info.rst 17 | examples/Basics.5_visualization.rst 18 | examples/Basics.6_edit_export.rst 19 | examples/Basics.7_create_new.rst 20 | examples/Advanced.1_units.rst 21 | examples/Advanced.2_sessions.rst 22 | examples/Advanced.3_custom_extensions.rst 23 | -------------------------------------------------------------------------------- /docs/examples/Advanced.1_units.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Advanced.1 Units 3 | ******************************************************************************* 4 | 5 | Comming soon... -------------------------------------------------------------------------------- /docs/examples/Advanced.2_sessions.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Advanced.1 Sessions 3 | ******************************************************************************* 4 | 5 | Comming soon... -------------------------------------------------------------------------------- /docs/examples/Advanced.3_custom_extensions.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Advanced.3 Custom Class Extensions 3 | ******************************************************************************* 4 | 5 | Comming soon... -------------------------------------------------------------------------------- /docs/examples/Basics.1_overview.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Basics.1 Project Overview 3 | ******************************************************************************* 4 | 5 | This example shows how to load an IFC file and print a summary of the model. 6 | 7 | .. code-block:: python 8 | 9 | from compas_ifc.model import Model 10 | 11 | model = Model("data/wall-with-opening-and-window.ifc") 12 | model.print_summary() 13 | 14 | 15 | .. code-block:: none 16 | 17 | ================================================================================ 18 | File: data/wall-with-opening-and-window.ifc 19 | Size: 0.01 MB 20 | Project: Default Project 21 | Description: Description of Default Project 22 | Number of sites: 1 23 | Number of buildings: 1 24 | Number of building elements: 2 25 | ================================================================================ 26 | 27 | 28 | 29 | .. code-block:: python 30 | 31 | from pprint import pprint 32 | from compas_ifc.model import Model 33 | 34 | model = Model("data/wall-with-opening-and-window.ifc") 35 | 36 | project = model.project 37 | 38 | # ============================================================================= 39 | # Info 40 | # ============================================================================= 41 | 42 | print("\n" + "*" * 53) 43 | print("Project") 44 | print("*" * 53 + "\n") 45 | 46 | project.print_inheritance() 47 | 48 | print("\nAttributes") 49 | print("=" * 53 + "\n") 50 | 51 | pprint(project.attributes) 52 | 53 | print("\nProperties") 54 | print("=" * 53 + "\n") 55 | 56 | pprint(project.property_sets) 57 | 58 | print("\nRepresentation Contexts") 59 | print("=" * 53 + "\n") 60 | 61 | pprint(project.contexts) 62 | 63 | print("\nUnits") 64 | print("=" * 53 + "\n") 65 | 66 | pprint(project.units) 67 | 68 | print("\nModel Context") 69 | print("=" * 53 + "\n") 70 | 71 | print(f"Reference Frame: {project.frame}") 72 | print(f"True North: {project.north}") 73 | 74 | print("\nSites") 75 | print("=" * 53 + "\n") 76 | 77 | print(project.sites) 78 | 79 | print() 80 | 81 | Example Output: 82 | 83 | .. code-block:: none 84 | 85 | ***************************************************** 86 | Project 87 | ***************************************************** 88 | 89 | - IfcRoot 90 | -- IfcObjectDefinition 91 | --- IfcContext 92 | ---- IfcProject 93 | 94 | Attributes 95 | ===================================================== 96 | 97 | {'Description': 'Description of Default Project', 98 | 'GlobalId': '28hypXUBvBefc20SI8kfA$', 99 | 'LongName': None, 100 | 'Name': 'Default Project', 101 | 'ObjectType': None, 102 | 'OwnerHistory': , 103 | 'Phase': None, 104 | 'RepresentationContexts': [], 105 | 'UnitsInContext': } 106 | 107 | Properties 108 | ===================================================== 109 | 110 | {} 111 | 112 | Representation Contexts 113 | ===================================================== 114 | 115 | [{'dimension': 3, 116 | 'identifier': None, 117 | 'north': Vector(0.000, 1.000, 0.000), 118 | 'precision': 1e-05, 119 | 'type': 'Model', 120 | 'wcs': Frame(Point(0.000, 0.000, 0.000), Vector(1.000, 0.000, 0.000), Vector(0.000, 1.000, 0.000))}] 121 | 122 | Units 123 | ===================================================== 124 | 125 | [{'name': 'METRE', 'prefix': 'MILLI', 'type': 'LENGTHUNIT'}, 126 | {'name': 'SQUARE_METRE', 'prefix': None, 'type': 'AREAUNIT'}, 127 | {'name': 'CUBIC_METRE', 'prefix': None, 'type': 'VOLUMEUNIT'}, 128 | {'name': 'STERADIAN', 'prefix': None, 'type': 'SOLIDANGLEUNIT'}, 129 | {'name': 'GRAM', 'prefix': None, 'type': 'MASSUNIT'}, 130 | {'name': 'SECOND', 'prefix': None, 'type': 'TIMEUNIT'}, 131 | {'name': 'DEGREE_CELSIUS', 132 | 'prefix': None, 133 | 'type': 'THERMODYNAMICTEMPERATUREUNIT'}, 134 | {'name': 'LUMEN', 'prefix': None, 'type': 'LUMINOUSINTENSITYUNIT'}] 135 | 136 | Model Context 137 | ===================================================== 138 | 139 | Reference Frame: Frame(Point(0.000, 0.000, 0.000), Vector(1.000, 0.000, 0.000), Vector(0.000, 1.000, 0.000)) 140 | True North: Vector(0.000, 1.000, 0.000) 141 | 142 | Sites 143 | ===================================================== 144 | 145 | [] -------------------------------------------------------------------------------- /docs/examples/Basics.2_query_entities.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Basics.2 Query Entities 3 | ******************************************************************************* 4 | 5 | This example shows how to query entities from an IFC model. 6 | 7 | .. code-block:: python 8 | 9 | from compas_ifc.model import Model 10 | 11 | model = Model("data/wall-with-opening-and-window.ifc") 12 | 13 | print("\n" + "*" * 53) 14 | print("Query Examples") 15 | print("*" * 53 + "\n") 16 | 17 | print("\nEntities by type") 18 | print("=" * 53 + "\n") 19 | 20 | all_entities = model.get_all_entities() 21 | spatial_elements = model.get_entities_by_type("IfcSpatialElement") 22 | building_elements = model.get_entities_by_type("IfcBuildingElement") 23 | 24 | print("Total number of entities: ", len(all_entities)) 25 | for entity in all_entities[-5:]: 26 | print(entity) 27 | print("...\n") 28 | 29 | print("Total number of spatial elements: ", len(spatial_elements)) 30 | for entity in spatial_elements[-5:]: 31 | print(entity) 32 | print("...\n") 33 | 34 | print("Total number of building elements: ", len(building_elements)) 35 | for entity in building_elements[-5:]: 36 | print(entity) 37 | print("...\n") 38 | 39 | 40 | print("\nEntities by name") 41 | print("=" * 53 + "\n") 42 | 43 | name = "Window for Test Example" 44 | entities = model.get_entities_by_name(name) 45 | print("Found entities with the name: {}".format(name)) 46 | print(entities) 47 | 48 | 49 | print("\nEntities by id") 50 | print("=" * 53 + "\n") 51 | 52 | global_id = "3ZYW59sxj8lei475l7EhLU" 53 | entity = model.get_entity_by_global_id(global_id) 54 | print("Found entity with the global id: {}".format(global_id)) 55 | print(entity, "\n") 56 | 57 | id = 1 58 | entity = model.get_entity_by_id(id) 59 | print("Found entity with the id: {}".format(id)) 60 | print(entity) 61 | 62 | 63 | Example Output: 64 | 65 | .. code-block:: none 66 | 67 | ***************************************************** 68 | Query Examples 69 | ***************************************************** 70 | 71 | 72 | Entities by type 73 | ===================================================== 74 | 75 | Total number of entities: 133 76 | 77 | 78 | 79 | 80 | 81 | ... 82 | 83 | Total number of spatial elements: 3 84 | 85 | 86 | 87 | ... 88 | 89 | Total number of building elements: 2 90 | 91 | 92 | ... 93 | 94 | 95 | Entities by name 96 | ===================================================== 97 | 98 | Found entities with the name: Window for Test Example 99 | [, ] 100 | 101 | Entities by id 102 | ===================================================== 103 | 104 | Found entity with the global id: 3ZYW59sxj8lei475l7EhLU 105 | 106 | 107 | Found entity with the id: 1 108 | -------------------------------------------------------------------------------- /docs/examples/Basics.3_spatial_hierarchy.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Basics.3 Spatial Hierarchy 3 | ******************************************************************************* 4 | 5 | This example shows how to traverse the spatial and inheritance hierarchy of an IFC model. 6 | 7 | .. code-block:: python 8 | 9 | from compas_ifc.model import Model 10 | 11 | model = Model("data/wall-with-opening-and-window.ifc") 12 | 13 | project = model.project 14 | 15 | print("\n" + "*" * 53) 16 | print("Hierarchy") 17 | print("*" * 53 + "\n") 18 | 19 | 20 | print("\nModel spatial hierarchy") 21 | print("=" * 53 + "\n") 22 | 23 | model.print_spatial_hierarchy() 24 | 25 | 26 | print("\nClass inheritance hierarchy") 27 | print("=" * 53 + "\n") 28 | 29 | project = model.project 30 | project.print_inheritance() 31 | 32 | print("\nShortcut APIs") 33 | print("=" * 53 + "\n") 34 | 35 | print("Project contains:") 36 | print(model.sites) 37 | print(model.buildings) 38 | print(model.building_storeys) 39 | print(model.elements[:3]) 40 | 41 | 42 | print("\nSite contains:") 43 | site = model.sites[0] 44 | print(site.buildings) 45 | print(site.geographic_elements) 46 | 47 | print("\nBuilding contains:") 48 | building = model.buildings[0] 49 | print(building.building_storeys) 50 | print(building.spaces) 51 | print(building.building_elements[:3]) 52 | 53 | 54 | print("\nTraverse spatial hierarchy") 55 | print("=" * 53 + "\n") 56 | 57 | print(building) 58 | 59 | print("\nParent") 60 | print(building.parent) 61 | 62 | print("\nChildren") 63 | print(building.children) 64 | 65 | print("\nAncestors") 66 | for ancestor in building.traverse_ancestor(): 67 | print(ancestor) 68 | 69 | print("\nDescendants") 70 | for descendant in building.traverse(): 71 | print(descendant) 72 | 73 | Example Output: 74 | 75 | .. code-block:: none 76 | 77 | ***************************************************** 78 | Hierarchy 79 | ***************************************************** 80 | 81 | 82 | Model spatial hierarchy 83 | ===================================================== 84 | 85 | 86 | ---- 87 | -------- 88 | ------------ 89 | ---------------- 90 | ---------------- 91 | 92 | Class inheritance hierarchy 93 | ===================================================== 94 | 95 | - IfcRoot 96 | -- IfcObjectDefinition 97 | --- IfcContext 98 | ---- IfcProject 99 | 100 | Shortcut APIs 101 | ===================================================== 102 | 103 | Project contains: 104 | [] 105 | [] 106 | [] 107 | [, , ] 108 | 109 | Site contains: 110 | [] 111 | [] 112 | 113 | Building contains: 114 | [] 115 | [] 116 | [, ] 117 | 118 | Traverse spatial hierarchy 119 | ===================================================== 120 | 121 | 122 | 123 | Parent 124 | 125 | 126 | Children 127 | [] 128 | 129 | Ancestors 130 | 131 | 132 | 133 | Descendants 134 | 135 | 136 | -------------------------------------------------------------------------------- /docs/examples/Basics.4_element_info.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Basics.4 Element info 3 | ******************************************************************************* 4 | 5 | This example shows how to get information of a building element, such as a window. 6 | 7 | .. code-block:: python 8 | 9 | from pprint import pprint 10 | from compas_ifc.model import Model 11 | 12 | model = Model("data/wall-with-opening-and-window.ifc") 13 | 14 | assert len(model.projects) > 0 15 | 16 | project = model.projects[0] 17 | assert len(project.sites) > 0 18 | 19 | site = project.sites[0] 20 | assert len(site.buildings) > 0 21 | 22 | building = site.buildings[0] 23 | assert len(building.building_storeys) > 0 24 | 25 | storey = building.building_storeys[0] 26 | assert len(storey.windows) > 0 27 | 28 | window = storey.windows[0] 29 | 30 | # ============================================================================= 31 | # Info 32 | # ============================================================================= 33 | 34 | print("\n" + "*" * 53) 35 | print("Window") 36 | print("*" * 53 + "\n") 37 | 38 | window.print_inheritance() 39 | 40 | print("\nAttributes") 41 | print("=" * 53 + "\n") 42 | 43 | pprint(window.attributes) 44 | 45 | print("\nProperties") 46 | print("=" * 53 + "\n") 47 | 48 | pprint(window.property_sets) 49 | 50 | 51 | Example Output: 52 | 53 | .. code-block:: none 54 | 55 | ***************************************************** 56 | Window 57 | ***************************************************** 58 | 59 | - IfcRoot 60 | -- IfcObjectDefinition 61 | --- IfcObject 62 | ---- IfcProduct 63 | ----- IfcElement 64 | ------ IfcBuildingElement 65 | ------- IfcWindow 66 | 67 | Attributes 68 | ===================================================== 69 | 70 | {'Description': 'Description of Window', 71 | 'GlobalId': '0tA4DSHd50le6Ov9Yu0I9X', 72 | 'Name': 'Window for Test Example', 73 | 'ObjectPlacement': , 74 | 'ObjectType': None, 75 | 'OverallHeight': 1000.0, 76 | 'OverallWidth': 1000.0, 77 | 'OwnerHistory': , 78 | 'PartitioningType': 'SINGLE_PANEL', 79 | 'PredefinedType': 'WINDOW', 80 | 'Representation': , 81 | 'Tag': None, 82 | 'UserDefinedPartitioningType': None} 83 | 84 | Properties 85 | ===================================================== 86 | 87 | {'AcousticRating': '', 88 | 'FireRating': '', 89 | 'GlazingAreaFraction': 0.7, 90 | 'Infiltration': 0.3, 91 | 'IsExternal': True, 92 | 'Reference': '', 93 | 'SecurityRating': '', 94 | 'SmokeStop': False, 95 | 'ThermalTransmittance': 0.24, 96 | 'id': 113} -------------------------------------------------------------------------------- /docs/examples/Basics.5_visualization.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Basics.5 Visualization 3 | ******************************************************************************* 4 | 5 | This example shows how to load an IFC file and display it in compas_viewer. 6 | 7 | .. code-block:: python 8 | 9 | from compas_ifc.model import Model 10 | 11 | model = Model("data/Duplex_A_20110907.ifc") 12 | model.show() 13 | 14 | Example Output: 15 | 16 | .. image:: ../_images/model_view.jpg 17 | :width: 100% 18 | 19 | Alternatively, you can also display individual elements in the model. 20 | 21 | .. code-block:: python 22 | 23 | from compas_ifc.model import Model 24 | 25 | model = Model("data/Duplex_A_20110907.ifc") 26 | model.get_entities_by_type("IfcWindow")[0].show() 27 | 28 | 29 | .. image:: ../_images/element_view.jpg 30 | :width: 100% -------------------------------------------------------------------------------- /docs/examples/Basics.6_edit_export.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Basics.6 Edit and Export 3 | ******************************************************************************* 4 | 5 | This example shows how to edit attributes of an entity, save to a new file or export the selected entities. 6 | 7 | .. code-block:: python 8 | 9 | from compas_ifc.model import Model 10 | 11 | model = Model("data/wall-with-opening-and-window.ifc") 12 | 13 | 14 | print("\n" + "*" * 53) 15 | print("Export Examples") 16 | print("*" * 53 + "\n") 17 | 18 | 19 | print("\nChange Project Name and Description") 20 | print("=" * 53 + "\n") 21 | 22 | model.project["Name"] = "New Project Name" 23 | model.project["Description"] = "New Project Description" 24 | model.save("temp/change_project_name.ifc") 25 | 26 | print("\nExport selected entities") 27 | print("=" * 53 + "\n") 28 | 29 | window = model.get_entities_by_type("IfcWindow")[0] 30 | model.export([window], "temp/selected_entities.ifc") 31 | -------------------------------------------------------------------------------- /docs/examples/Basics.7_create_new.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | Basics.7 Create New IFC Model 3 | ******************************************************************************* 4 | 5 | This example shows how to create a new IFC model from scratch. 6 | 7 | .. code-block:: python 8 | 9 | import compas 10 | from compas.datastructures import Mesh 11 | from compas.geometry import Box 12 | from compas.geometry import Sphere 13 | from compas.geometry import Frame 14 | from compas_ifc.model import Model 15 | 16 | 17 | model = Model.template(storey_count=1) 18 | 19 | # Default unit is mm 20 | box = Box.from_width_height_depth(5000, 5000, 50) 21 | sphere = Sphere(2000) 22 | mesh = Mesh.from_obj(compas.get("tubemesh.obj")) 23 | mesh.scale(1000) 24 | 25 | storey = model.building_storeys[0] 26 | 27 | element1 = model.create("IfcWall", geometry=box, frame=Frame([0, 0, 0]), Name="Wall", parent=storey) 28 | element3 = model.create("IfcRoof", geometry=mesh, frame=Frame([0, 0, 5000]), Name="Roof", parent=storey) 29 | element2 = model.create(geometry=sphere, frame=Frame([0, 5000, 0]), Name="Sphere", parent=storey) 30 | 31 | model.show() 32 | model.save("temp/create_geometry.ifc") 33 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | COMPAS IFC 3 | ******************************************************************************** 4 | 5 | .. rst-class:: lead 6 | 7 | COMPAS IFC is a high-level python development tool for `Industry Foundation Classes (IFC) `_, a major industry standard and digital format for data-exchange of Building Information Modelling (BIM) applications. 8 | 9 | .. image:: _images/compas_ifc.png 10 | :width: 100% 11 | 12 | 13 | What for ? 14 | ========== 15 | 16 | The primary goal of COMPAS IFC is to provide an accessible, transparent and user-friendly toolkit to interact with the highly intricate IFC format. 17 | So that developers and researchers can easily build custom computational workflows to read and write BIM data in IFC format intuitively with very small amounts of code. 18 | 19 | 20 | Stands on giants' shoulder 21 | ========================== 22 | 23 | As a core extension of `COMPAS framework `_, COMPAS IFC supports bi-directional translation from COMPAS data structures to corresponding IFC entities. 24 | It benefits directly from the extensive range of tools that COMPAS ecosystem already provides, such as `COMPAS CGAL `_ and `COMPAS OCC `_ for geometry processing, and `COMPAS Viewer `_ for visualization. 25 | COMPAS IFC also heavily relies on `IfcOpenShell `_, a fast, powerful and widely-used open source library as its "backend", for low-level functionalities like IFC file entities processing, geometry parsing, schema reading etc. 26 | On top of this, COMPAS IFC additionally provides a large amount of useful APIs to greatly simplify common BIM data operations such as querying element info, inspecting project spatial hierarchy, reading and writing entities, geometry processing, visualization, exporting individual elements and creation of valid new IFC files from scratch. 27 | 28 | .. note:: 29 | For more information about the basic usage of COMPAS IFC, please refer to `Tutorials section `_. 30 | 31 | 32 | Built for simplicity 33 | ==================== 34 | 35 | The biggest challenge to work with IFC stems from its sheer complexity. 36 | The latest `IFC 4.3 Specification `_ contains over two thousand object-oriented classes/type definitions accompanied with a large set of rules on how specific types of elements must relate to each other. 37 | While tools including IfcOpenShell provides fully mapped APIs for each class/type definition, it can still be extremely difficult and cumbersome to build even simplest applications for IFC without significant expertise knowledge about the standard. 38 | Additional work must be done to bridge this gap. 39 | To address this challenge, COMPAS IFC presents a lean and simplified data model as a “front-end” abstraction of the complicated IFC file content. 40 | Users can interact intuitively with such a data model with minimal effort, while maintaining the integrity and validity of the raw IFC data behind. 41 | 42 | .. note:: 43 | For more information about the data model please refer to `Architecture: Simplified Data Model `_. 44 | 45 | 46 | Empower the advanced 47 | ==================== 48 | 49 | COMPAS IFC can also be a powerful tool for advanced users, who want to work at low-level directly with nitty-gritties of raw IFC entities. 50 | Given an official schema, COMPAS IFC automatically generates a full mapping of IFC classes in native python, with all attributes and functions strongly typed. 51 | This enables modern IDEs' language servers such as Pylance to provide extensive type-hints and analysis on all IFC classes and their attributes, which greatly improves the development experience, reduces development time and removes the need of constant lookup on documentations. 52 | In addition, an extension mechanism is provided allowing advanced users customize existing IFC classes, and insert their custom modifications into the class inheritance chain. 53 | 54 | .. note:: 55 | For more information about the full python class mapping mechanism please refer to `Architecture: Full class mapping `_. 56 | 57 | 58 | Moving fast 59 | =========== 60 | 61 | COMPAS IFC is developed for research and innovation, being used both in academia and innovative industry startups. It is currently under active development. If you have questions please do not hesitate create new issues on github repo or contanct us at li.chen@arch.ethz.ch or van.mele@arch.ethz.ch. 62 | 63 | 64 | Table of Contents 65 | ================= 66 | 67 | .. toctree:: 68 | :maxdepth: 2 69 | :titlesonly: 70 | 71 | Introduction 72 | Architecture 73 | Installation 74 | Tutorials 75 | Examples 76 | API 77 | license 78 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ================================ 3 | 4 | A minimal version of COMPAS IFC can be installed directly with pip. 5 | 6 | .. code-block:: bash 7 | 8 | pip install compas_ifc 9 | 10 | If you want to use the built-in viewer, install COMPAS Viewer as well. 11 | 12 | .. code-block:: bash 13 | 14 | pip install compas_viewer 15 | 16 | If you need to interact with IFC geometry using OCC Brep, install COMPAS OCC througn conda-forge. 17 | 18 | .. code-block:: bash 19 | 20 | conda install compas_occ -c conda-forge 21 | 22 | 23 | Next Steps 24 | ---------- 25 | 26 | Now that you have COMPAS IFC installed, proceed to the :doc:`Hello World tutorial ` to learn the basics of working with IFC files. 27 | 28 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | License 3 | ******************************************************************************** 4 | 5 | .. literalinclude:: ../LICENSE 6 | -------------------------------------------------------------------------------- /docs/templates/class.rst: -------------------------------------------------------------------------------- 1 | {{ objname | escape | underline }} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. autoclass:: {{ objname }} 6 | :show-inheritance: 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/tutorials.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Tutorials 3 | ******************************************************************************** 4 | 5 | .. rst-class:: lead 6 | In this section you will find tutorials on how to use the COMPAS IFC package. 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :titlesonly: 12 | :glob: 13 | 14 | tutorials/basics.hello_world 15 | tutorials/basics.entity_apis 16 | .. tutorials/basics.create_model 17 | .. tutorials/intermediate.multi_story_building 18 | .. tutorials/advanced.custom_extensions 19 | -------------------------------------------------------------------------------- /docs/tutorials/advanced.custom_extensions.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Advanced - Custom extensions 3 | ******************************************************************************** 4 | 5 | .. rst-class:: lead 6 | 7 | This tutorial is for advanced users whom are familiar with IFC classes and attributes. 8 | It shows how to create an custom extension to an existing IFC class which is also inherited by its subclasses. 9 | -------------------------------------------------------------------------------- /docs/tutorials/basics.create_model.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Basics - Create Model 3 | ******************************************************************************** 4 | 5 | .. rst-class:: lead 6 | 7 | This tutorial shows how to create an IFC model from scratch. 8 | 9 | Model Template 10 | ============== 11 | 12 | Valid IFC files need to follow a regulated spatial hierarchy like this: 13 | 14 | ``IfcProject`` -> ``IfcSite`` -> ``IfcBuilding`` -> ``IfcBuildingStorey`` -> ``IfcBuildingElement``. 15 | 16 | COMPAS IFC provide a convenient way to create setup this hierarchy in a single function call. 17 | 18 | :: 19 | 20 | >>> from compas_ifc.model import Model 21 | >>> model = Model.template(building_count=1, storey_count=3, unit="m", schema="IFC4") 22 | IFC file created in schema: IFC4 23 | >>> model.print_spatial_hierarchy() 24 | ================================================================================ 25 | Spatial hierarchy of <#1 IfcProject "Default Project"> 26 | ================================================================================ 27 | └── <#1 IfcProject "Default Project"> 28 | └── <#17 IfcSite "Default Site"> 29 | └── <#19 IfcBuilding "Default Building 1"> 30 | ├── <#21 IfcBuildingStorey "Default Storey 1"> 31 | ├── <#23 IfcBuildingStorey "Default Storey 2"> 32 | └── <#25 IfcBuildingStorey "Default Storey 3"> 33 | 34 | The created entities can be easily updated: 35 | 36 | :: 37 | 38 | >>> model.project.Name = "My Custom Project Name" 39 | >>> model.sites[0].Name = "My Custom Site Name" 40 | >>> model.buildings[0].Name = "My Custom Building Name" 41 | >>> model.building_storeys[0].Name = "UnderGroundFloor" 42 | >>> model.building_storeys[1].Name = "GroundFloor" 43 | >>> model.building_storeys[2].Name = "FirstFloor" 44 | >>> model.print_spatial_hierarchy() 45 | ================================================================================ 46 | Spatial hierarchy of <#1 IfcProject "My Custom Project Name"> 47 | ================================================================================ 48 | └── <#1 IfcProject "My Custom Project Name"> 49 | └── <#17 IfcSite "My Custom Site Name"> 50 | └── <#19 IfcBuilding "My Custom Building Name"> 51 | ├── <#21 IfcBuildingStorey "UnderGroundFloor"> 52 | ├── <#23 IfcBuildingStorey "GroundFloor"> 53 | └── <#25 IfcBuildingStorey "FirstFloor"> 54 | 55 | Create Entities 56 | ================= 57 | 58 | COMPAS IFC provides a series of functions to create common building elements in IFC. 59 | 60 | :: 61 | 62 | >>> from compas.geometry import Box 63 | >>> box = Box(xsize=0.2, ysize=5, zsize=3) 64 | >>> box.translate(0, 0, 1.5) # Move the box up so it is above the zero plane 65 | >>> model.create_wall(name="My Wall", parent=model.building_storeys[0], geometry=box) 66 | <#27 IfcWall "My Wall"> 67 | 68 | We can now visualize the model with: 69 | 70 | :: 71 | 72 | >>> model.show() 73 | -------------------------------------------------------------------------------- /docs/tutorials/basics.entity_apis.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Basics - Entity APIs 3 | ******************************************************************************** 4 | 5 | .. rst-class:: lead 6 | This tutorial shows how to use the Entity APIs to inspect and manipulate individual IFC entities. 7 | 8 | 9 | We use a window element as an example. 10 | 11 | :: 12 | 13 | >>> from compas_ifc.model import Model 14 | >>> model = Model.from_ifc("data/Duplex_A_20110907.ifc") 15 | >>> window = model.get_entities_by_type("IfcWindow")[0] 16 | >>> window 17 | <#6426 IfcWindow "M_Fixed:4835mm x 2420mm:4835mm x 2420mm:145788"> 18 | 19 | 20 | Visualization 21 | ================================ 22 | 23 | Similar to the ``Model.show()`` function, you can use the ``Entity.show()`` function to visualize an individual entity. 24 | 25 | :: 26 | 27 | >>> window.show() 28 | 29 | .. image:: ../_images/element_view.jpg 30 | 31 | 32 | Entity Attributes 33 | ================================ 34 | 35 | Each entity in an IFC model has a set of attributes regulated by the `IFC standard `_, 36 | such as general information like "Name", "Description", "Representation" and type specific information like "OverallHeight", "OverallWidth" for classes like ``IfcWindow``. 37 | 38 | 39 | To access these attributes, you can use the ``attributes`` property. 40 | 41 | :: 42 | 43 | >>> window.attributes 44 | {'GlobalId': '1hOSvn6df7F8_7GcBWlR72', 'OwnerHistory': <#33 IfcOwnerHistory>, 'Name': 'M_Fixed:4835mm x 2420mm:4835mm x 2420mm:145788', 'Description': None, 'ObjectType': '4835mm x 2420mm', 'ObjectPlacement': <#6425 IfcLocalPlacement>, 'Representation': <#6420 IfcProductDefinitionShape>, 'Tag': '145788', 'OverallHeight': 2.419999999999998, 'OverallWidth': 4.834999999999996} 45 | 46 | 47 | You can also directly access these attributes by calling them in ``CamelCase``, 48 | meanwhile the ``snake_case`` attributes are added by COMPAS IFC for simplified access to many aspects of IFC entity. 49 | 50 | :: 51 | 52 | >>> window.Name 53 | 'M_Fixed:4835mm x 2420mm:4835mm x 2420mm:145788' 54 | >>> window.ObjectType 55 | '4835mm x 2420mm' 56 | >>> window.OverallHeight 57 | 2.419999999999998 58 | >>> window.OverallWidth 59 | 4.834999999999996 60 | 61 | 62 | Spatial Hierarchy 63 | ================================ 64 | 65 | In IFC, entities are organized in a spatial hierarchy, meaning that each entity can have a parent and children. 66 | 67 | For example an ``IfcWindow`` is a child of a ``IfcBuildingStorey`` and usually have no children. 68 | 69 | :: 70 | 71 | >>> window.parent 72 | <#39 IfcBuildingStorey "Level 1"> 73 | 74 | >>> window.children 75 | [] 76 | 77 | 78 | The parent of an ``IfcBuildingStorey`` must be a ``IfcBuilding`` and can have many type of children, such as ``IfcSpace``, ``IfcWall``, ``IfcWindow``, etc. 79 | 80 | :: 81 | 82 | >>> building_storey = window.parent 83 | >>> building_storey.parent 84 | <#36 IfcBuilding "None"> 85 | 86 | >>> building_storey.children 87 | [<#514 IfcSpace "A101">, <#4553 IfcWallStandardCase "Basic Wall:Interior - Partition (92mm Stud):139783">, <#6921 IfcWindow "M_Fixed:750mm x 2200mm:750mm x 2200mm:146885">, ...] 88 | 89 | You can also use ``Entity.print_spatial_hierarchy()`` to print the spatial hierarchy of an entity in a tree view, with the current entity highlighted with ``**`` signs. 90 | 91 | :: 92 | 93 | >>> building_storey.print_spatial_hierarchy(max_depth=3) 94 | ================================================================================ 95 | Spatial hierarchy of <#39 IfcBuildingStorey "Level 1"> 96 | ================================================================================ 97 | └── <#34 IfcProject "0001"> 98 | └── <#38274 IfcSite "Default"> 99 | └── <#36 IfcBuilding "None"> 100 | ├── <#47 IfcBuildingStorey "T/FDN"> 101 | ├── **<#39 IfcBuildingStorey "Level 1">** 102 | ├── <#43 IfcBuildingStorey "Level 2"> 103 | └── <#51 IfcBuildingStorey "Roof"> 104 | 105 | 106 | Geometric Representation 107 | ================================ 108 | 109 | Entities that represents a physical product, need to be placed in a spatial context and have a geometric representation. 110 | These two aspects are very complex when expressed in raw IFC classes, COMPAS IFC provides simplified access to these two aspects through the ``Entity.frame`` and ``Entity.geometry`` properties, 111 | which uses ``COMPAS`` data structures that are much easier to interact with. 112 | 113 | :: 114 | 115 | >>> window.frame 116 | Frame(point=Point(x=0.0, y=0.0, z=0.0), xaxis=Vector(x=1.0, y=0.0, z=0.0), yaxis=Vector(x=0.0, y=1.0, z=0.0)) 117 | 118 | :: 119 | 120 | >>> window.geometry 121 | 122 | >>> window.geometry.to_mesh() 123 | 124 | 125 | For more details about the ``Entity.frame`` and ``Entity.geometry`` properties, please refer to the :doc:`advanced.geometry` tutorial. 126 | 127 | 128 | Custom Properties 129 | ================================ 130 | 131 | Most IFC entities are allowed to have custom properties or so-called ``Psets``, they can be accessed conveniently through the ``Entity.properties`` dictionary. 132 | 133 | :: 134 | 135 | >>> window.properties 136 | {'Pset_WindowCommon': {'Reference': 'M_Fixed:4835mm x 2420mm', 'IsExternal': True, 'FireRating': 'FireRating'}, ...} 137 | 138 | 139 | 140 | Next Steps 141 | ================================ 142 | 143 | In the next tutorial, we will explore how to create an IFC model from scratch, including creating entities, setting up spatial hierarchy, and adding custom properties. 144 | 145 | :doc:`intermediate.multi_story_building` 146 | 147 | -------------------------------------------------------------------------------- /docs/tutorials/basics.hello_world.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Hello World - Model Basics 3 | ******************************************************************************** 4 | 5 | .. rst-class:: lead 6 | This is a hello-world tutorial for the COMPAS IFC package. It shows how to load an IFC file and and inspect its contents. 7 | 8 | Load an IFC model 9 | ================================ 10 | 11 | An IFC model can be simply loaded given its file path: 12 | 13 | :: 14 | 15 | >>> from compas_ifc.model import Model 16 | >>> model = Model("data/Duplex_A_20110907.ifc") 17 | IFC file loaded: data/Duplex_A_20110907.ifc 18 | Loading geometries... 19 | Time to load all 286 geometries 0.932s 20 | 21 | Visualize the model 22 | ================================ 23 | 24 | With ``Model.show()`` function, the model can be visualised with the built-in viewer. 25 | 26 | :: 27 | 28 | >>> model.show() 29 | 30 | .. image:: ../_images/model_view.jpg 31 | 32 | .. note:: 33 | ``compas_viewer`` needs to be installed to use this function. 34 | 35 | 36 | Model functions 37 | ================================ 38 | 39 | The ``Model`` class provides a set of APIs to interact with the IFC model. For example: 40 | 41 | :: 42 | 43 | >>> model.print_summary() 44 | ================================================================================ 45 | Schema: IFC2X3 46 | File: data/Duplex_A_20110907.ifc 47 | Size: 2.31 MB 48 | Project: 0001 49 | Description: None 50 | Number of sites: 1 51 | Number of buildings: 1 52 | Number of building elements: 157 53 | ================================================================================ 54 | 55 | 56 | An IFC file is essentially a collection of entities, organized in a spatial hierarchy. 57 | ``Model.print_spatial_hierarchy()`` function will print the spatial hierarchy of the model in a tree view. 58 | An optional ``max_depth`` parameter can be used to limit the depth of the hierarchy. 59 | 60 | :: 61 | 62 | >>> model.print_spatial_hierarchy(max_depth=3) 63 | ================================================================================ 64 | Spatial hierarchy of <#34 IfcProject "0001"> 65 | ================================================================================ 66 | └── <#34 IfcProject "0001"> 67 | └── <#38274 IfcSite "Default"> 68 | └── <#36 IfcBuilding "None"> 69 | ├── <#43 IfcBuildingStorey "Level 2"> 70 | ├── <#39 IfcBuildingStorey "Level 1"> 71 | ├── <#51 IfcBuildingStorey "Roof"> 72 | └── <#47 IfcBuildingStorey "T/FDN"> 73 | 74 | ``Model.save()`` function will save the model to a new IFC file. 75 | 76 | :: 77 | 78 | >>> model.save("data/Duplex_A_20110907_COPY.ifc") 79 | IFC file saved: data/Duplex_A_20110907_COPY.ifc 80 | 81 | Find entities 82 | ================================ 83 | 84 | ``Model`` class provides many different ways to find specific entities, through their IFC type, entity name, IFC file ID and entity Global ID. 85 | 86 | :: 87 | 88 | >>> model.get_entities_by_type("IfcRoof") 89 | [<#22475 IfcRoof "Basic Roof:Live Roof over Wood Joist Flat Roof:184483">] 90 | 91 | >>> model.get_entities_by_name("Basic Roof:Live Roof over Wood Joist Flat Roof:184483") 92 | [<#22475 IfcRoof "Basic Roof:Live Roof over Wood Joist Flat Roof:184483">] 93 | 94 | >>> model.get_entity_by_id(22475) 95 | <#22475 IfcRoof "Basic Roof:Live Roof over Wood Joist Flat Roof:184483"> 96 | 97 | >>> model.get_entity_by_global_id("0jf0rYHfX3RAB3bSIRjmxl") 98 | <#22475 IfcRoof "Basic Roof:Live Roof over Wood Joist Flat Roof:184483"> 99 | 100 | Shortcuts 101 | ================================ 102 | 103 | Besides above functions, ``Model`` class also provides a set of shortcuts for quick access to the most commonly used entities. 104 | 105 | :: 106 | 107 | >>> model.project 108 | <#34 IfcProject "0001"> 109 | 110 | >>> model.sites 111 | [<#38274 IfcSite "Default">] 112 | 113 | >>> model.buildings 114 | [<#36 IfcBuilding "None">] 115 | 116 | >>> model.building_storeys 117 | [<#39 IfcBuildingStorey "Level 1">, <#43 IfcBuildingStorey "Level 2">, <#47 IfcBuildingStorey "T/FDN">, <#51 IfcBuildingStorey "Roof">] 118 | 119 | >>> model.building_elements 120 | [<#4131 IfcWallStandardCase "Basic Wall:Interior - Partition (92mm Stud):138584">, <#4287 IfcWallStandardCase "Basic Wall:Party Wall - CMU Residential Unit Dimising Wall:139234">,...] 121 | 122 | 123 | Next Steps 124 | ================================ 125 | 126 | In the next tutorial, we will explore the Entity APIs to inspect and manipulate individual IFC entities. 127 | 128 | :doc:`basics.entity_apis` 129 | 130 | -------------------------------------------------------------------------------- /docs/tutorials/intermediate.multi_story_building.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Intermediate - Multi-storey building 3 | ******************************************************************************** 4 | 5 | .. rst-class:: lead 6 | 7 | This tutorial shows how to paramtrically create a multi-storey building from scratch using COMPAS geometry types, as well adding custom properties to each relevant IFC entity. 8 | 9 | Model Template 10 | ============== 11 | 12 | Valid IFC files need to follow a regulated spatial hierarchy like this: 13 | 14 | ``IfcProject`` -> ``IfcSite`` -> ``IfcBuilding`` -> ``IfcBuildingStorey`` -> ``IfcBuildingElement``. 15 | 16 | COMPAS IFC provide a convenient way to create setup this hierarchy in a single function call. 17 | 18 | :: 19 | 20 | >>> from compas_ifc.model import Model 21 | >>> model = Model.template(building_count=1, storey_count=3, unit="m", schema="IFC4") 22 | IFC file created in schema: IFC4 23 | >>> model.print_spatial_hierarchy() 24 | ================================================================================ 25 | Spatial hierarchy of <#1 IfcProject "Default Project"> 26 | ================================================================================ 27 | └── <#1 IfcProject "Default Project"> 28 | └── <#17 IfcSite "Default Site"> 29 | └── <#19 IfcBuilding "Default Building 1"> 30 | ├── <#21 IfcBuildingStorey "Default Storey 1"> 31 | ├── <#23 IfcBuildingStorey "Default Storey 2"> 32 | └── <#25 IfcBuildingStorey "Default Storey 3"> 33 | 34 | The created entities can be easily updated: 35 | 36 | :: 37 | 38 | >>> model.project.Name = "My Custom Project Name" 39 | >>> model.sites[0].Name = "My Custom Site Name" 40 | >>> model.buildings[0].Name = "My Custom Building Name" 41 | >>> model.building_storeys[0].Name = "UnderGroundFloor" 42 | >>> model.building_storeys[1].Name = "GroundFloor" 43 | >>> model.building_storeys[2].Name = "FirstFloor" 44 | >>> model.print_spatial_hierarchy() 45 | ================================================================================ 46 | Spatial hierarchy of <#1 IfcProject "My Custom Project Name"> 47 | ================================================================================ 48 | └── <#1 IfcProject "My Custom Project Name"> 49 | └── <#17 IfcSite "My Custom Site Name"> 50 | └── <#19 IfcBuilding "My Custom Building Name"> 51 | ├── <#21 IfcBuildingStorey "UnderGroundFloor"> 52 | ├── <#23 IfcBuildingStorey "GroundFloor"> 53 | └── <#25 IfcBuildingStorey "FirstFloor"> 54 | 55 | Creating Entities 56 | ================= 57 | 58 | ``Model.create()`` can be used to create entities in the model. 59 | 60 | :: 61 | 62 | >>> model.create(IfcWall, Name="My Custom slab", parent=model.building_storeys[0]) 63 | 64 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=66.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | # ============================================================================ 6 | # project info 7 | # ============================================================================ 8 | 9 | [project] 10 | name = "compas_ifc" 11 | description = "High-level IFC interface for COMPAS." 12 | keywords = ["compas", "ifc", "bim", "interoperability"] 13 | authors = [{ name = "Tom Van Mele", email = "van.mele@arch.ethz.ch" }, { name = "Li Chen", email = "li.chen@arch.ethz.ch" }] 14 | license = { file = "LICENSE" } 15 | readme = "README.md" 16 | requires-python = ">=3.9" 17 | dynamic = ['dependencies', 'optional-dependencies', 'version'] 18 | classifiers = [ 19 | "Development Status :: 5 - Production/Stable", 20 | "Topic :: Scientific/Engineering", 21 | "Operating System :: Unix", 22 | "Operating System :: POSIX", 23 | "Operating System :: Microsoft :: Windows", 24 | "Programming Language :: Python", 25 | "Programming Language :: Python :: 3", 26 | "Programming Language :: Python :: 3.9", 27 | "Programming Language :: Python :: 3.10", 28 | "Programming Language :: Python :: 3.11", 29 | ] 30 | 31 | [project.urls] 32 | Homepage = "https://github.com/compas-dev/compas_ifc" 33 | Documentation = "https://compas.dev/compas_ifc/" 34 | Repository = "https://github.com/compas-dev/compas_ifc.git" 35 | Changelog = "https://github.com/compas-dev/compas_ifc/blob/main/CHANGELOG.md" 36 | Issues = "https://github.com/compas-dev/compas_ifc/issues" 37 | Forum = "https://forum.compas-framework.org/" 38 | 39 | # ============================================================================ 40 | # setuptools config 41 | # ============================================================================ 42 | 43 | [tool.setuptools] 44 | package-dir = { "" = "src" } 45 | include-package-data = true 46 | zip-safe = false 47 | 48 | [tool.setuptools.dynamic] 49 | version = { attr = "compas_ifc.__version__" } 50 | dependencies = { file = "requirements.txt" } 51 | optional-dependencies = { dev = { file = "requirements-dev.txt" } } 52 | 53 | [tool.setuptools.packages.find] 54 | where = ["src"] 55 | 56 | [tool.setuptools.package-data] 57 | 58 | # ============================================================================ 59 | # replace pytest.ini 60 | # ============================================================================ 61 | 62 | [tool.pytest.ini_options] 63 | minversion = "6.0" 64 | testpaths = ["tests", "src/compas_ifc"] 65 | python_files = ["test_*.py", "*_test.py", "test.py"] 66 | addopts = ["-ra", "--strict-markers", "--doctest-glob=*.rst", "--tb=short"] 67 | doctest_optionflags = [ 68 | "NORMALIZE_WHITESPACE", 69 | "IGNORE_EXCEPTION_DETAIL", 70 | "ALLOW_UNICODE", 71 | "ALLOW_BYTES", 72 | "NUMBER", 73 | ] 74 | 75 | # ============================================================================ 76 | # replace bumpversion.cfg 77 | # ============================================================================ 78 | 79 | [tool.bumpversion] 80 | current_version = "1.6.0" 81 | message = "Bump version to {new_version}" 82 | commit = true 83 | tag = true 84 | 85 | [[tool.bumpversion.files]] 86 | filename = "src/compas_ifc/__init__.py" 87 | search = "{current_version}" 88 | replace = "{new_version}" 89 | 90 | [[tool.bumpversion.files]] 91 | filename = "CHANGELOG.md" 92 | search = "Unreleased" 93 | replace = "[{new_version}] {now:%Y-%m-%d}" 94 | 95 | # ============================================================================ 96 | # replace setup.cfg 97 | # ============================================================================ 98 | 99 | [tool.black] 100 | line-length = 179 101 | 102 | [tool.ruff] 103 | line-length = 179 104 | indent-width = 4 105 | target-version = "py39" 106 | 107 | [tool.ruff.lint] 108 | select = ["E", "F", "I"] 109 | 110 | [tool.ruff.lint.per-file-ignores] 111 | "__init__.py" = ["I001"] 112 | "tests/*" = ["I001"] 113 | "tasks.py" = ["I001"] 114 | 115 | [tool.ruff.lint.isort] 116 | force-single-line = true 117 | known-first-party = [ 118 | "compas_ifc", 119 | ] 120 | 121 | [tool.ruff.lint.pydocstyle] 122 | convention = "numpy" 123 | 124 | [tool.ruff.lint.pycodestyle] 125 | max-doc-length = 179 126 | 127 | [tool.ruff.format] 128 | docstring-code-format = true 129 | docstring-code-line-length = "dynamic" 130 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | attrs >=17.4 2 | black >=22.12.0 3 | bump-my-version 4 | compas_invocations2 5 | invoke >=0.14 6 | ruff 7 | sphinx_compas2_theme 8 | twine 9 | wheel 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | compas >= 2.2 2 | ifcopenshell==0.7.0.240406 3 | lark -------------------------------------------------------------------------------- /scripts/0.1_overview.py: -------------------------------------------------------------------------------- 1 | from compas_ifc.model import Model 2 | 3 | model = Model("data/wall-with-opening-and-window.ifc") 4 | model.print_summary() 5 | -------------------------------------------------------------------------------- /scripts/1.1_query_entities.py: -------------------------------------------------------------------------------- 1 | from compas_ifc.model import Model 2 | 3 | model = Model("data/wall-with-opening-and-window.ifc") 4 | 5 | print("\n" + "*" * 53) 6 | print("Query Examples") 7 | print("*" * 53 + "\n") 8 | 9 | print("\nEntities by type") 10 | print("=" * 53 + "\n") 11 | 12 | 13 | print("Total number of entities: ", len(list(model.entities))) 14 | for i, entity in enumerate(model.entities): 15 | print(entity) 16 | if i > 5: 17 | print("...\n") 18 | break 19 | 20 | spatial_elements = model.get_entities_by_type("IfcSpatialElement") 21 | print("Total number of spatial elements: ", len(spatial_elements)) 22 | for entity in spatial_elements: 23 | print(entity) 24 | print() 25 | 26 | building_elements = model.get_entities_by_type("IfcBuildingElement") 27 | print("Total number of building elements: ", len(building_elements)) 28 | for entity in building_elements: 29 | print(entity) 30 | print() 31 | 32 | 33 | print("\nEntities by name") 34 | print("=" * 53 + "\n") 35 | 36 | name = "Window for Test Example" 37 | entities = model.get_entities_by_name(name) 38 | print("Found entities with the name: {}".format(name)) 39 | print(entities) 40 | 41 | 42 | print("\nEntities by id") 43 | print("=" * 53 + "\n") 44 | 45 | global_id = "3ZYW59sxj8lei475l7EhLU" 46 | entity = model.get_entity_by_global_id(global_id) 47 | print("Found entity with the global id: {}".format(global_id)) 48 | print(entity, "\n") 49 | 50 | id = 1 51 | entity = model.get_entity_by_id(id) 52 | print("Found entity with the id: {}".format(id)) 53 | print(entity) 54 | -------------------------------------------------------------------------------- /scripts/1.2_traverse_hierarchy.py: -------------------------------------------------------------------------------- 1 | from compas_ifc.model import Model 2 | 3 | model = Model("data/wall-with-opening-and-window.ifc") 4 | 5 | project = model.project 6 | 7 | print("\n" + "*" * 53) 8 | print("Hierarchy") 9 | print("*" * 53 + "\n") 10 | 11 | 12 | print("\nModel spatial hierarchy") 13 | print("=" * 53 + "\n") 14 | 15 | model.print_spatial_hierarchy() 16 | 17 | 18 | print("\nClass inheritance hierarchy") 19 | print("=" * 53 + "\n") 20 | 21 | project = model.project 22 | 23 | print("\nShortcut APIs") 24 | print("=" * 53 + "\n") 25 | 26 | print("Project contains:") 27 | print("Sites:", project.sites) 28 | print("Buildings:", project.buildings) 29 | print("Building elements:", project.building_elements) 30 | print("Geographic elements:", project.geographic_elements) 31 | 32 | print("\nSite contains:") 33 | site = model.sites[0] 34 | print("Building elements:", site.building_elements) 35 | print("Geographic elements:", site.geographic_elements) 36 | 37 | print("\nBuilding contains:") 38 | building = model.buildings[0] 39 | print("Building elements:", building.building_elements) 40 | print("Geographic elements:", building.geographic_elements) 41 | 42 | 43 | print("\nTraverse spatial hierarchy") 44 | print("=" * 53 + "\n") 45 | 46 | print(building) 47 | 48 | print("Parent: ", building.parent) 49 | print("Children: ", building.children) 50 | 51 | building.print_spatial_hierarchy() 52 | -------------------------------------------------------------------------------- /scripts/2.1_project_info.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | from compas_ifc.model import Model 3 | 4 | model = Model("data/wall-with-opening-and-window.ifc") 5 | 6 | project = model.project 7 | 8 | # ============================================================================= 9 | # Info 10 | # ============================================================================= 11 | 12 | print("\n" + "*" * 53) 13 | print("Project") 14 | print("*" * 53 + "\n") 15 | 16 | print("\nAttributes") 17 | print("=" * 53 + "\n") 18 | 19 | pprint(project.to_dict()) 20 | 21 | project.print_attributes(max_depth=3) 22 | 23 | 24 | print("\nRepresentation Contexts") 25 | print("=" * 53 + "\n") 26 | 27 | pprint(project.contexts) 28 | 29 | print("\nUnits") 30 | print("=" * 53 + "\n") 31 | 32 | pprint(project.units) 33 | 34 | print("\nModel Context") 35 | print("=" * 53 + "\n") 36 | 37 | print(f"Reference Frame: {project.frame}") 38 | print(f"True North: {project.north}") 39 | 40 | print("\nSites") 41 | print("=" * 53 + "\n") 42 | 43 | print(project.sites) 44 | 45 | print() 46 | -------------------------------------------------------------------------------- /scripts/2.2_site_info.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | from compas_ifc.model import Model 3 | 4 | model = Model("data/wall-with-opening-and-window.ifc") 5 | 6 | site = model.project.sites[0] 7 | 8 | # ============================================================================= 9 | # Info 10 | # ============================================================================= 11 | 12 | print("\n" + "*" * 53) 13 | print("Site") 14 | print("*" * 53 + "\n") 15 | 16 | print("\nSpatial Structure") 17 | print("=" * 53 + "\n") 18 | 19 | site.print_spatial_hierarchy() 20 | 21 | print("\nAttributes") 22 | print("=" * 53 + "\n") 23 | 24 | pprint(site.to_dict()) 25 | 26 | print("\nProperties") 27 | print("=" * 53 + "\n") 28 | 29 | pprint(site.property_sets) 30 | 31 | print("\nBuildings") 32 | print("=" * 53 + "\n") 33 | 34 | print(site.buildings) 35 | -------------------------------------------------------------------------------- /scripts/2.3_window_info.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | from compas_ifc.model import Model 3 | 4 | model = Model("data/wall-with-opening-and-window.ifc") 5 | window = model.get_entities_by_type("IfcWindow")[0] 6 | window.print_spatial_hierarchy(max_depth=5) 7 | 8 | # ============================================================================= 9 | # Info 10 | # ============================================================================= 11 | 12 | print("\n" + "*" * 53) 13 | print("Window") 14 | print("*" * 53 + "\n") 15 | 16 | print("\nAttributes") 17 | print("=" * 53 + "\n") 18 | 19 | pprint(window.to_dict()) 20 | 21 | print("\nProperties") 22 | print("=" * 53 + "\n") 23 | 24 | pprint(window.property_sets) 25 | -------------------------------------------------------------------------------- /scripts/3.1_model_view.py: -------------------------------------------------------------------------------- 1 | from compas_ifc.model import Model 2 | 3 | model = Model("data/Duplex_A_20110907.ifc") 4 | model.show() -------------------------------------------------------------------------------- /scripts/3.2_element_view.py: -------------------------------------------------------------------------------- 1 | from compas_ifc.model import Model 2 | 3 | model = Model("data/Duplex_A_20110907.ifc") 4 | model.get_entities_by_type("IfcWindow")[0].show() -------------------------------------------------------------------------------- /scripts/4.1_edit_export.py: -------------------------------------------------------------------------------- 1 | from compas_ifc.model import Model 2 | 3 | model = Model("data/wall-with-opening-and-window.ifc") 4 | 5 | 6 | print("\n" + "*" * 53) 7 | print("Export Examples") 8 | print("*" * 53 + "\n") 9 | 10 | 11 | print("\nChange Project Name and Description") 12 | print("=" * 53 + "\n") 13 | 14 | model.project.Name = "New Project Name" 15 | model.project.Description = "New Project Description" 16 | model.save("temp/change_project_name.ifc") 17 | 18 | print("\nExport selected entities") 19 | print("=" * 53 + "\n") 20 | 21 | window = model.get_entities_by_type("IfcWindow")[0] 22 | model.export("temp/selected_entities.ifc", [window]) 23 | -------------------------------------------------------------------------------- /scripts/4.2_create_new.py: -------------------------------------------------------------------------------- 1 | import compas 2 | from compas.datastructures import Mesh 3 | from compas.geometry import Box 4 | from compas.geometry import Sphere 5 | from compas.geometry import Frame 6 | from compas_ifc.model import Model 7 | 8 | 9 | model = Model.template(storey_count=1, unit="m") 10 | 11 | box = Box.from_width_height_depth(5, 5, 0.05) 12 | sphere = Sphere(2) 13 | mesh = Mesh.from_obj(compas.get("tubemesh.obj")) 14 | 15 | storey = model.building_storeys[0] 16 | 17 | element1 = model.create_wall(geometry=box, frame=Frame([0, 0, 0]), name="My Wall", parent=storey) 18 | element2 = model.create_roof(geometry=mesh, frame=Frame([0, 0, 5]), name="My Roof", parent=storey) 19 | element3 = model.create(geometry=sphere, frame=Frame([0, 5, 0]), name="Sphere", parent=storey, properties={"Custom_Pset": {"Custom_Property": "Custom Value"}}) 20 | 21 | model.print_spatial_hierarchy(max_depth=5) 22 | model.show() 23 | model.save("temp/create_geometry.ifc") 24 | -------------------------------------------------------------------------------- /scripts/5.1_export_from_rhino.py: -------------------------------------------------------------------------------- 1 | #! python3 2 | 3 | ### This is a draft script to export a model from Rhino to IFC. 4 | ### The example should be run in Rhino8 with COMPAS IFC installed, using template file the "data/layer_template.3dm" 5 | 6 | from compas_rhino.objects import get_objects 7 | from compas_rhino.objects import get_object_layers 8 | from compas_rhino.conversions import brepobject_to_compas 9 | from compas_ifc.model import Model 10 | from compas.datastructures import Mesh 11 | import re 12 | 13 | 14 | def parse_string(input_string): 15 | pattern = r"^(.*)\[(.*)\]$" 16 | match = re.match(pattern, input_string) 17 | if match: 18 | name = match.group(1) 19 | type_ = match.group(2) 20 | else: 21 | name = None 22 | type_ = input_string 23 | 24 | return name, type_ 25 | 26 | 27 | objs = get_objects() 28 | layer_info = get_object_layers(objs) 29 | 30 | 31 | entities = {} 32 | 33 | model = Model() 34 | model.create_default_project() 35 | model.unit = "m" 36 | 37 | for obj, layers in zip(objs, layer_info): 38 | compas_brep = brepobject_to_compas(obj) 39 | 40 | # TODO: add support to convert Rhino brep to IFC brep. 41 | meshes = compas_brep.to_meshes() 42 | mesh = Mesh() 43 | for m in meshes: 44 | mesh.join(m) 45 | 46 | layers = layers.split("::") 47 | 48 | parent_layer = None 49 | for layer in layers: 50 | if layer not in entities: 51 | name, ifc_type = parse_string(layer) 52 | 53 | if ifc_type == "IfcProject": 54 | model.project.Name = name 55 | entities[layer] = model.project 56 | elif name is None: 57 | model.create(ifc_type, name=name, geometry=mesh, parent=entities[parent_layer]) 58 | else: 59 | entities[layer] = model.create(ifc_type, name=name, parent=entities[parent_layer]) 60 | 61 | parent_layer = layer 62 | 63 | 64 | model.print_spatial_hierarchy(max_depth=10) 65 | 66 | # Change the path to the desired output location 67 | model.save("D:/Github/compas_ifc/temp/Rhino/rhino.ifc") 68 | -------------------------------------------------------------------------------- /scripts/6.1_custom_extension.py: -------------------------------------------------------------------------------- 1 | from compas_ifc.entities.generated.IFC4 import IfcBuildingElement 2 | from compas_ifc.model import Model 3 | 4 | 5 | class ExtendedIfcBuildingElement(IfcBuildingElement): 6 | @property 7 | def volume(self): 8 | return self.geometry.volume 9 | 10 | 11 | model = Model("data/Duplex_A_20110907.ifc", use_occ=True, extensions={"IfcBuildingElement": ExtendedIfcBuildingElement}) 12 | 13 | total_wall_volume = 0 14 | for wall in model.get_entities_by_type("IfcWall"): 15 | total_wall_volume += wall.volume 16 | 17 | print("Total wall volume:", total_wall_volume, f"{model.unit}³") 18 | 19 | total_slab_volume = 0 20 | for slab in model.get_entities_by_type("IfcSlab"): 21 | total_slab_volume += slab.volume 22 | 23 | print("Total slab volume:", total_slab_volume, f"{model.unit}³") 24 | -------------------------------------------------------------------------------- /scripts/PLACEHOLDER: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compas-dev/compas_ifc/36891729aa13128b7d09afcfd76884f081c7b899/scripts/PLACEHOLDER -------------------------------------------------------------------------------- /src/compas_ifc/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ******************************************************************************** 3 | compas_ifc 4 | ******************************************************************************** 5 | 6 | .. currentmodule:: compas_ifc 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | 12 | compas_ifc.attributes 13 | compas_ifc.entities 14 | compas_ifc.helpers 15 | compas_ifc.model 16 | compas_ifc.representation 17 | compas_ifc.resources 18 | compas_ifc.viewer 19 | 20 | """ 21 | 22 | from __future__ import print_function 23 | 24 | import os 25 | 26 | 27 | __author__ = ["tom van mele"] 28 | __copyright__ = "ETH Zurich" 29 | __license__ = "MIT License" 30 | __email__ = "van.mele@arch.ethz.ch" 31 | __version__ = "1.6.0" 32 | 33 | 34 | HERE = os.path.dirname(__file__) 35 | 36 | HOME = os.path.abspath(os.path.join(HERE, "../../")) 37 | DATA = os.path.abspath(os.path.join(HOME, "data")) 38 | DOCS = os.path.abspath(os.path.join(HOME, "docs")) 39 | TEMP = os.path.abspath(os.path.join(HOME, "temp")) 40 | 41 | 42 | __all__ = ["HOME", "DATA", "DOCS", "TEMP"] 43 | 44 | __all_plugins__ = [ 45 | "compas_ifc.brep", 46 | ] 47 | -------------------------------------------------------------------------------- /src/compas_ifc/__main__.py: -------------------------------------------------------------------------------- 1 | if __name__ == "__main__": 2 | pass 3 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ******************************************************************************** 3 | entities 4 | ******************************************************************************** 5 | 6 | .. currentmodule:: compas_ifc.entities 7 | 8 | Classes representing rooted entities (entities inheriting from IfcRoot). 9 | Three types of entities are of particular interest: 10 | 11 | * IfcContext :: **IfcProject** 12 | * IfcObject :: IfcProduct :: IfcElement :: **IfcSpatialElement** 13 | 14 | * IfcSite 15 | * IfcBuilding 16 | * IfcBuildingStorey 17 | * IfcSpace 18 | 19 | * IfcObject :: IfcProduct :: IfcElement :: **IfcBuildingElement** 20 | 21 | * IfcBeam 22 | * IfcColumn 23 | * IfcDoor 24 | * IfcRoof 25 | * IfcSlab 26 | * IfcStair 27 | * IfcWall 28 | * IfcWindow 29 | 30 | 31 | Associations 32 | ============ 33 | 34 | Classifications 35 | --------------- 36 | 37 | See also :ifc:`classification`. 38 | 39 | Objects like building and spatial elements can be further described by associating references to external sources of information. 40 | The source of information can be: 41 | 42 | * a classification system; 43 | * a dictionary server; 44 | * any external catalogue that classifies the object further; 45 | * a service that combine the above features. 46 | 47 | These "associations" are called "classifications". 48 | 49 | .. code-block:: python 50 | 51 | for association in entity.HasAssociations: 52 | if association.is_a("IfcRelAssociatesClassification"): 53 | ... 54 | 55 | Material Associations 56 | --------------------- 57 | 58 | See also :ifc:`material-association`. 59 | 60 | Material associations indicate the physical composition of an object. 61 | Materials can have representations for surface styles indicating colors, textures, and light reflectance for 3D rendering. 62 | Materials can have representations for fill styles indicating colors, tiles, and hatch patterns for 2D rendering. 63 | Materials can have properties such as density, elasticity, thermal resistance, and others as defined in this specification. 64 | Materials can also be classified according to a referenced industry standard. 65 | 66 | .. code-block:: python 67 | 68 | for association in entity.HasAssociations: 69 | if association.is_a("IfcRelAssociatesMaterial"): 70 | ... 71 | 72 | Object Composition 73 | ================== 74 | 75 | Objects may be composed into parts to indicate levels of detail, such as a building having multiple storeys, a framed wall having studs, or a task having subtasks. 76 | Composition may form a hierarchy of multiple levels, where an object must have single parent, 77 | or if a top-level object then declared within the single project or a project library. 78 | 79 | Element Composition 80 | ------------------- 81 | 82 | See also :ifc:`element-composition`. 83 | 84 | Provision of an aggregation structure where the element is part of another element representing the composite. 85 | The part then provides, if such concepts are in scope of the Model View Definition, exclusively the following: 86 | 87 | * Body Geometry — The partial body shape representation and its placement; 88 | * Material — The material information for the part. 89 | 90 | The part may also provide, in addition to the aggregate, more specifically the following: 91 | 92 | * Property Sets — The parts may have individual property sets assigned, solely or in addition to the composite; 93 | * Quantity Sets — The parts may have individual quantity sets assigned, solely or in addition to the composite. 94 | 95 | The part should not be contained in the spatial hierarchy, i.e. the concept Spatial Containment shall not be used at the level of parts. 96 | The part is contained in the spatial structure by the spatial containment of its composite. 97 | 98 | .. code-block:: python 99 | 100 | for element in entity.ContainsElements: 101 | # ... 102 | 103 | 104 | Spatial Composition 105 | ------------------- 106 | 107 | ... 108 | 109 | .. code-block:: python 110 | 111 | for element in entity.IsDecomposedBy: 112 | # ... 113 | 114 | 115 | Classes 116 | ======= 117 | 118 | Bases 119 | ----- 120 | 121 | .. autosummary:: 122 | :toctree: generated/ 123 | :nosignatures: 124 | 125 | Entity 126 | 127 | Context 128 | ------- 129 | 130 | .. autosummary:: 131 | :toctree: generated/ 132 | :nosignatures: 133 | 134 | Project 135 | 136 | Spatial Elements 137 | ---------------- 138 | 139 | .. autosummary:: 140 | :toctree: generated/ 141 | :nosignatures: 142 | 143 | Site 144 | Building 145 | BuildingStorey 146 | Space 147 | 148 | Building Elements 149 | ----------------- 150 | 151 | .. autosummary:: 152 | :toctree: generated/ 153 | :nosignatures: 154 | 155 | Beam 156 | Column 157 | Door 158 | Roof 159 | Slab 160 | Stair 161 | Wall 162 | Window 163 | 164 | """ 165 | 166 | from .entity import Entity 167 | from .root import Root 168 | from .objectdefinition import ObjectDefinition 169 | from .element import Element 170 | from .spatialelement import SpatialElement 171 | from .buildingelements import Beam 172 | from .buildingelements import Column 173 | from .buildingelements import Door 174 | from .buildingelements import Roof 175 | from .buildingelements import Railing 176 | from .buildingelements import Slab 177 | from .buildingelements import Stair 178 | from .buildingelements import Wall 179 | from .buildingelements import Window 180 | from .buildingelements import BuildingElementProxy 181 | 182 | from .space import Space 183 | from .buildingstorey import BuildingStorey 184 | from .building import Building 185 | from .site import Site 186 | from .geographicelement import GeographicElement 187 | 188 | from .project import Project 189 | 190 | DEFAULT_ENTITY_TYPES = { 191 | "IfcRoot": Root, 192 | "IfcObjectDefinition": ObjectDefinition, 193 | "IfcElement": Element, 194 | "IfcSpatialElement": SpatialElement, 195 | "IfcBeam": Beam, 196 | "IfcColumn": Column, 197 | "IfcDoor": Door, 198 | "IfcRoof": Roof, 199 | "IfcRailing": Railing, 200 | "IfcSlab": Slab, 201 | "IfcStair": Stair, 202 | "IfcWall": Wall, 203 | "IfcWindow": Window, 204 | "IfcSpace": Space, 205 | "IfcBuildingStorey": BuildingStorey, 206 | "IfcBuilding": Building, 207 | "IfcSite": Site, 208 | "IfcProject": Project, 209 | "IfcBuildingElementProxy": BuildingElementProxy, 210 | "IfcGeographicElement": GeographicElement, 211 | } 212 | 213 | 214 | def factory(entity, model, entity_types=None) -> Entity: 215 | """Factory function for creating an compas_ifc entity object from an Ifc entity, the function finds closest matched class from the bottom of inherentance. 216 | 217 | Parameters 218 | ---------- 219 | entity : IfcEntity 220 | An IfcEntity object. 221 | model : IfcModel 222 | An IfcModel object. 223 | 224 | Returns 225 | ------- 226 | Entity 227 | An Entity object. 228 | 229 | """ 230 | entity_types = entity_types or DEFAULT_ENTITY_TYPES 231 | 232 | declaration = model.schema.declaration_by_name(entity.is_a()) 233 | inheritance = [declaration.name()] 234 | while hasattr(declaration, "supertype") and declaration.supertype(): 235 | inheritance.append(declaration.supertype().name()) 236 | declaration = declaration.supertype() 237 | 238 | for ifc_type in inheritance: 239 | if ifc_type in entity_types: 240 | return entity_types[ifc_type](entity, model) 241 | 242 | dynamic_class = type(entity.is_a(), (Entity,), {}) 243 | return dynamic_class(entity, model) 244 | 245 | 246 | Entity.factory = staticmethod(factory) 247 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/attribute.py: -------------------------------------------------------------------------------- 1 | from ifcopenshell import entity_instance 2 | 3 | 4 | class Attribute: 5 | def __init__(self, name, type, optional): 6 | self.name = name 7 | self.type = type 8 | self.optional = optional 9 | 10 | def __get__(self, obj, objtype=None): 11 | if obj is None: 12 | return self 13 | else: 14 | return self._get_attribute(obj, self.name) 15 | 16 | def __set__(self, obj, value): 17 | pass 18 | 19 | def _get_attribute(self, obj, name): 20 | attr = getattr(obj.entity, name) 21 | if isinstance(attr, (list, tuple)): 22 | return [self._get_attribute_value(obj, item) for item in attr] 23 | else: 24 | return self._get_attribute_value(obj, attr) 25 | 26 | def _get_attribute_value(self, obj, attr): 27 | if isinstance(attr, entity_instance): 28 | return obj.reader.from_entity(attr) 29 | else: 30 | return attr 31 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/attributes.py: -------------------------------------------------------------------------------- 1 | """ 2 | ******************************************************************************** 3 | "attributes" 4 | ******************************************************************************** 5 | 6 | .. currentmodule:: compas_ifc.attributes 7 | 8 | Functions 9 | ========= 10 | 11 | .. autosummary:: 12 | :toctree: generated/ 13 | :nosignatures: 14 | 15 | is_attribute_aggregation 16 | is_attribute_enumeration 17 | attribute_enumeration_items 18 | declared_object_attributes 19 | declared_context_attributes 20 | declared_product_attributes 21 | declared_building_element_attributes 22 | declared_spatial_element_attributes 23 | 24 | """ 25 | 26 | 27 | def is_attribute_aggregation(attribute): 28 | """ 29 | Verify that an attribute is an aggregation type. 30 | 31 | Parameters 32 | ---------- 33 | attribute 34 | 35 | Returns 36 | ------- 37 | bool 38 | 39 | """ 40 | return attribute.type_of_attribute().as_aggregation_type() 41 | 42 | 43 | def is_attribute_enumeration(attribute): 44 | """ 45 | Verify that an attribute is an enumeration type. 46 | 47 | Parameters 48 | ---------- 49 | attribute 50 | 51 | Returns 52 | ------- 53 | bool 54 | 55 | """ 56 | if attribute.type_of_attribute().as_named_type(): 57 | return attribute.type_of_attribute().declared_type().as_enumeration_type() 58 | return False 59 | 60 | 61 | def attribute_enumeration_items(attribute): 62 | """ 63 | Get the value options of an enumeration type. 64 | 65 | Parameters 66 | ---------- 67 | attribute 68 | 69 | Returns 70 | ------- 71 | tuple 72 | 73 | """ 74 | enumeration = attribute.type_of_attribute().declared_type().as_enumeration_type() 75 | return enumeration.enumeration_items() 76 | 77 | 78 | def declared_object_attributes(schema): 79 | """ 80 | Get all declared attributes of an IFC object. 81 | 82 | Parameters 83 | ---------- 84 | schema 85 | 86 | Returns 87 | ------- 88 | tuple 89 | 90 | """ 91 | declaration = schema.declaration_by_name("IfcObject") 92 | return declaration.all_attributes() 93 | 94 | 95 | def declared_context_attributes(schema): 96 | declaration = schema.declaration_by_name("IfcContext") 97 | return declaration.all_attributes() 98 | 99 | 100 | def declared_product_attributes(schema): 101 | declaration = schema.declaration_by_name("IfcProduct") 102 | return declaration.all_attributes() 103 | 104 | 105 | def declared_building_element_attributes(schema, exclude_product=True): 106 | declaration = schema.declaration_by_name("IfcBuildingElement") 107 | if not exclude_product: 108 | return declaration.all_attributes() 109 | 110 | attributes = [] 111 | product = [attr.name() for attr in declared_product_attributes(schema)] 112 | for attribute in declaration.all_attributes(): 113 | if attribute.name() not in product: 114 | attributes.append(attribute) 115 | return attributes 116 | 117 | 118 | def declared_spatial_element_attributes(schema, exclude_product=True): 119 | declaration = schema.declaration_by_name("IfcSpatialElement") 120 | if not exclude_product: 121 | return declaration.all_attributes() 122 | 123 | attributes = [] 124 | product = declared_product_attributes(schema) 125 | for attribute in declaration.all_attributes(): 126 | if attribute not in product: 127 | attributes.append(attribute) 128 | return attributes 129 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/building.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from compas_ifc.entities.buildingelements import BuildingElement 4 | from compas_ifc.entities.buildingstorey import BuildingStorey 5 | from compas_ifc.entities.space import Space 6 | from compas_ifc.entities.spatialelement import SpatialElement 7 | 8 | 9 | class Building(SpatialElement): 10 | """ 11 | Class representing an IFC building. 12 | 13 | Attributes 14 | ---------- 15 | address : str 16 | The compiled address of the building. 17 | building_storeys : List[BuildingStorey] 18 | The building_storeys contained in the building. 19 | If the building_storeys are not properly nested, they can be found at the top level of an IFC model. 20 | spaces : List[Space] 21 | The spaces contained in the building. 22 | building_elements : List[BuildingElement] 23 | The building elements contained in the building. 24 | 25 | Notes 26 | ----- 27 | The order of spatial structure elements being included in the concept for builing projects are from high to low level: 28 | IfcProject, IfcSite, IfcBuilding, IfcBuildingStorey, and IfcSpace with IfcSite, IfcBuildingStorey and IfcSpace being optional levels. 29 | Therefore an spatial structure element can only be part of an element at the same or higher level [1]_. 30 | 31 | This means the minimum spatial structure of a project is: 32 | 33 | * IfcProject 34 | 35 | * IfcBuilding 36 | 37 | * IfcBuildingElement 38 | 39 | Therefore, all building elements found in a model can be accessed through the Building class. 40 | 41 | .. code-block:: python 42 | 43 | model = Model(filepath) 44 | for project in model.projects: 45 | if project.sites: 46 | for site in project.sites: 47 | for building in site.buildings: 48 | for element in building.elements: 49 | # ... 50 | else: 51 | for building in project.buildings: 52 | for element in building.elements: 53 | # ... 54 | 55 | References 56 | ---------- 57 | .. [1] :ifc:`spatial-composition` 58 | 59 | """ 60 | 61 | def __init__(self, entity, model): 62 | super().__init__(entity, model) 63 | self._building_storeys = None 64 | self._spaces = None 65 | self._elements = None 66 | 67 | @property 68 | def building_storeys(self) -> List[BuildingStorey]: 69 | return [entity for entity in self.traverse() if entity.is_a("IfcBuildingStorey")] 70 | 71 | @property 72 | def spaces(self) -> List[Space]: 73 | return [entity for entity in self.traverse() if entity.is_a("IfcSpace")] 74 | 75 | @property 76 | def building_elements(self) -> List[BuildingElement]: 77 | return [entity for entity in self.traverse() if entity.is_a("IfcBuildingElement")] 78 | 79 | @property 80 | def address(self) -> str: 81 | attr = self.attribute("BuildingAddress") 82 | address = "" 83 | address += ", ".join(attr["AddressLines"]) 84 | address += f", {attr['Country']}-{attr['PostalCode']}" 85 | address += f" {attr['Town']}" 86 | return address 87 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/buildingelements.py: -------------------------------------------------------------------------------- 1 | from compas_ifc.entities.element import Element 2 | from compas_ifc.entities.spatialelement import SpatialElement 3 | 4 | 5 | class BuildingElement(Element): 6 | """ 7 | Base class for all building elements. 8 | 9 | References 10 | ---------- 11 | * Spatial Containment: :ifc:`spatial-containment` 12 | 13 | """ 14 | 15 | def __init__(self, entity, model) -> None: 16 | super().__init__(entity, model) 17 | self._composite_body = None 18 | self._composite_opening = None 19 | self._composite_body_with_opening = None 20 | 21 | def is_contained(self) -> bool: 22 | """ 23 | Verify that this building element is contained in a spatial structure. 24 | 25 | Returns 26 | ------- 27 | bool 28 | 29 | """ 30 | return len(self._entity.ContainedInStructure) == 1 31 | 32 | def is_containment_hierarchical(self) -> bool: 33 | """ 34 | Verify that the spatial containment of this building element is hierarchical, 35 | meaning that the element is contained in no more than one (1) spatial element. 36 | 37 | Returns 38 | ------- 39 | bool 40 | 41 | """ 42 | if len(self._entity.ContainedInStructure) > 1: 43 | return False 44 | return True 45 | 46 | def container(self) -> SpatialElement: 47 | """ 48 | Get the container of the building element. 49 | 50 | Containment is hierarchical, meaning that the element can only be contained in one spatial element. 51 | 52 | Returns 53 | ------- 54 | :class:`SpatialElement` 55 | 56 | """ 57 | return self.parent 58 | 59 | @property 60 | def building_element_parts(self): 61 | return [part for part in self.children if part.is_a("IfcBuildingElementPart")] 62 | 63 | @property 64 | def composite_body(self): 65 | bodies = [] 66 | for part in self.building_element_parts: 67 | bodies.extend(part.body) 68 | self._composite_body = bodies 69 | return self._composite_body 70 | 71 | @composite_body.setter 72 | def composite_body(self, value): 73 | self._composite_body = value 74 | 75 | @property 76 | def composite_opening(self): 77 | voids = self.opening 78 | for part in self.building_element_parts: 79 | voids.extend(part.opening) 80 | self._composite_opening = voids 81 | return self._composite_opening 82 | 83 | @property 84 | def composite_body_with_opening(self): 85 | from compas_ifc.representation import entity_body_with_opening_geometry 86 | 87 | if not self._composite_body_with_opening: 88 | self._composite_body_with_opening = entity_body_with_opening_geometry(entity=self, bodies=self.composite_body, voids=self.composite_opening) 89 | return self._composite_body_with_opening 90 | 91 | @composite_body_with_opening.setter 92 | def composite_body_with_opening(self, value): 93 | self._composite_body_with_opening = value 94 | 95 | 96 | class Beam(BuildingElement): 97 | pass 98 | 99 | 100 | class Column(BuildingElement): 101 | pass 102 | 103 | 104 | class Wall(BuildingElement): 105 | @property 106 | def gross_area(self): 107 | self.properties.get("GrossSideArea") 108 | 109 | 110 | class Window(BuildingElement): 111 | pass 112 | 113 | 114 | class Roof(BuildingElement): 115 | pass 116 | 117 | 118 | class Slab(BuildingElement): 119 | pass 120 | 121 | 122 | class Door(BuildingElement): 123 | pass 124 | 125 | 126 | class Railing(BuildingElement): 127 | pass 128 | 129 | 130 | class Stair(BuildingElement): 131 | pass 132 | 133 | 134 | class BuildingElementProxy(BuildingElement): 135 | pass 136 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/buildingstorey.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from compas_ifc.entities.buildingelements import Beam 4 | from compas_ifc.entities.buildingelements import BuildingElementProxy 5 | from compas_ifc.entities.buildingelements import Column 6 | from compas_ifc.entities.buildingelements import Door 7 | from compas_ifc.entities.buildingelements import Roof 8 | from compas_ifc.entities.buildingelements import Slab 9 | from compas_ifc.entities.buildingelements import Stair 10 | from compas_ifc.entities.buildingelements import Wall 11 | from compas_ifc.entities.buildingelements import Window 12 | from compas_ifc.entities.space import Space 13 | from compas_ifc.entities.spatialelement import SpatialElement 14 | 15 | 16 | class BuildingStorey(SpatialElement): 17 | """ 18 | Class representing an IFC building storey. 19 | 20 | Attributes 21 | ---------- 22 | beams : List[:class:`Beam`] 23 | The beams contained in this building storey. 24 | columns : List[:class:`Column`] 25 | The beams contained in this building storey. 26 | doors : List[:class:`Column`] 27 | The beams contained in this building storey. 28 | roofs : List[:class:`Roof`] 29 | The roofs contained in this building storey. 30 | slabs : List[:class:`Slab`] 31 | The slabs contained in this building storey. 32 | stairs : List[:class:`Stairs`] 33 | The stairs contained in this building storey. 34 | walls : List[:class:`Walls`] 35 | The walls contained in this building storey. 36 | windows : List[:class:`Windows`] 37 | The windows contained in this building storey. 38 | spaces : List[:class:`Space`] 39 | The spaces contained in this building storey. 40 | building_elements_proxies : List[:class:`BuildingElementProxy`] 41 | The building element proxies contained in this building storey. 42 | 43 | """ 44 | 45 | @property 46 | def spaces(self) -> List[Space]: 47 | return [entity for entity in self.traverse() if entity.is_a("IfcSpace")] 48 | 49 | @property 50 | def beams(self) -> List[Beam]: 51 | return [entity for entity in self.traverse() if entity.is_a("IfcBeam")] 52 | 53 | @property 54 | def columns(self) -> List[Column]: 55 | return [entity for entity in self.traverse() if entity.is_a("IfcColumn")] 56 | 57 | @property 58 | def doors(self) -> List[Door]: 59 | return [entity for entity in self.traverse() if entity.is_a("IfcDoor")] 60 | 61 | @property 62 | def roofs(self) -> List[Roof]: 63 | return [entity for entity in self.traverse() if entity.is_a("IfcRoof")] 64 | 65 | @property 66 | def slabs(self) -> List[Slab]: 67 | return [entity for entity in self.traverse() if entity.is_a("IfcSlab")] 68 | 69 | @property 70 | def stairs(self) -> List[Stair]: 71 | return [entity for entity in self.traverse() if entity.is_a("IfcStair")] 72 | 73 | @property 74 | def walls(self) -> List[Wall]: 75 | return [entity for entity in self.traverse() if entity.is_a("IfcWall")] 76 | 77 | @property 78 | def windows(self) -> List[Window]: 79 | return [entity for entity in self.traverse() if entity.is_a("IfcWindow")] 80 | 81 | @property 82 | def building_elements_proxies(self) -> List[BuildingElementProxy]: 83 | return [entity for entity in self.traverse() if entity.is_a("IfcBuildingElementProxy")] 84 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/element.py: -------------------------------------------------------------------------------- 1 | from .product import Product 2 | 3 | 4 | class Element(Product): 5 | """ 6 | Class representing an IFC element. An element is a product that is intended to be physically constructed or installed. 7 | 8 | Attributes 9 | ---------- 10 | parent : :class:`compas_ifc.entities.SpatialElement` 11 | The spatial element containing this element. 12 | 13 | """ 14 | 15 | def contained_in_structure(self): 16 | """Return the spatial structure containing this element.""" 17 | if self not in self.model._new_entities: 18 | for rel in self._entity.ContainedInStructure: 19 | return self.model.reader.get_entity(rel) 20 | 21 | @property 22 | def parent(self): 23 | if not self._parent and self._entity: 24 | relation = self.contained_in_structure() 25 | if relation: 26 | self._parent = relation["RelatingStructure"] 27 | else: 28 | relation = self.decomposes() 29 | if relation: 30 | self._parent = relation["RelatingObject"] 31 | return self._parent 32 | 33 | @parent.setter 34 | def parent(self, parent): 35 | self._parent = parent 36 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/factory.py: -------------------------------------------------------------------------------- 1 | import ifcopenshell 2 | 3 | # TODO: pre-generate all classes. 4 | # TODO: separate ifcopenshell APIs 5 | 6 | 7 | class Factory: 8 | EXTENSIONS = {} 9 | 10 | def __init__(self, schema: str = "IFC4") -> None: 11 | self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema) 12 | 13 | def get_extensions(self, name, declaration): 14 | name = declaration.name() 15 | inheritances = [name] 16 | supertype = declaration.supertype() 17 | while supertype: 18 | name = supertype.name() 19 | inheritances.append(name) 20 | supertype = supertype.supertype() 21 | 22 | inheritances = inheritances[::-1] 23 | extensions = [] 24 | for cls in inheritances: 25 | if cls in self.EXTENSIONS: 26 | extensions.append(self.EXTENSIONS[cls]) 27 | return tuple(extensions) 28 | 29 | def create_class(self, name): 30 | declaration = self.schema.declaration_by_name(name) 31 | extensions = self.get_extensions(name, declaration) 32 | 33 | def init(self, entity): 34 | self._entity = entity 35 | 36 | attributes = { 37 | "__init__": init, 38 | "__repr__": lambda self: f"", 39 | } 40 | 41 | for attribute in declaration.all_attributes(): 42 | name = attribute.name() 43 | attributes[name] = self.create_getter(attribute) 44 | 45 | for attribute in declaration.all_inverse_attributes(): 46 | name = attribute.name() 47 | attributes[name] = self.create_getter(attribute, setter=False) 48 | 49 | return type(name, extensions, attributes) 50 | 51 | def create_getter(self, attribute, setter=True): 52 | name = attribute.name() 53 | 54 | @property 55 | def prop(self): 56 | return getattr(self._entity, name) 57 | 58 | if setter: 59 | 60 | @prop.setter 61 | def prop(self, value): 62 | if not hasattr(self._entity, name): 63 | raise AttributeError(f"Attribute <{name}> does not exist") 64 | 65 | # Make checks pluggable functions 66 | 67 | # check aggregation type 68 | attribute_type = attribute.type_of_attribute() 69 | if attribute_type.as_aggregation_type(): 70 | value = list(value) 71 | lower = attribute_type.bound1() 72 | upper = attribute_type.bound2() 73 | if lower > 0 and upper > 0: 74 | if len(value) < lower or len(value) > upper: 75 | raise ValueError(f"Expected <{name}> to have between {lower} and {upper} elements") 76 | # TODO: check type of elements 77 | 78 | # check entity type 79 | if attribute_type.as_named_type(): 80 | declared_type = attribute_type.declared_type() 81 | type_name = declared_type.name() 82 | # TODO: take care of non-entity situations 83 | if declared_type.as_entity() and value.is_a() != type_name: 84 | raise TypeError(f"Expected <{name}> to be of type {type_name}, got {value.is_a()}") 85 | 86 | setattr(self._entity, name, value) 87 | 88 | return prop 89 | 90 | 91 | if __name__ == "__main__": 92 | file = ifcopenshell.open("data/wall-with-opening-and-window.ifc") 93 | _entity = file[34] 94 | declaration = _entity.is_a() 95 | 96 | from extensions import EXTENSIONS 97 | 98 | Factory.EXTENSIONS = EXTENSIONS 99 | print(Factory.EXTENSIONS) 100 | 101 | factory = Factory() 102 | Entity = factory.create_class(declaration) 103 | 104 | entity = Entity(_entity) 105 | entity.test() 106 | entity.test2() 107 | print(entity) 108 | 109 | # print(entity.ThePerson) 110 | # # entity.ThePerson = "some wrong value type" 111 | # entity.ThePerson = file[3] 112 | 113 | # print(entity.ThePerson) 114 | # print(entity) 115 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/geographicelement.py: -------------------------------------------------------------------------------- 1 | from compas_ifc.entities.element import Element 2 | 3 | 4 | class GeographicElement(Element): 5 | """ 6 | Class representing an IFC Geographic Element. 7 | """ 8 | 9 | pass 10 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/geometricmodel.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import ifcopenshell 4 | 5 | # from compas.geometry import Polyline 6 | from compas.geometry import Box 7 | from compas.geometry import Line 8 | from compas.geometry import Point 9 | from compas.geometry import Transformation 10 | 11 | from compas_ifc.resources.geometry import IfcAxis2Placement3D_to_frame 12 | from compas_ifc.resources.geometry import IfcDirection_to_vector 13 | from compas_ifc.resources.geometry import IfcProfileDef_to_curve 14 | 15 | 16 | def IfcAdvancedBrep_to_brep(advanced_brep): 17 | """ 18 | Convert an IFC AdvancedBrep [advancedbrep]_ to a COMPAS brep. 19 | 20 | """ 21 | pass 22 | 23 | 24 | def IfcAdvancedBrepWithVoids_to_brep(advanced_brep_with_voids): 25 | """ 26 | Convert an IFC AdvancedBrepWithVoids [advancedbrepwithvoids]_ to a COMPAS brep. 27 | 28 | """ 29 | pass 30 | 31 | 32 | def IfcBlock_to_box(block) -> Box: 33 | """ 34 | Convert an IFC Block [block]_ to a COMPAS box. 35 | 36 | """ 37 | pass 38 | 39 | 40 | def IfcBooleanClippingResult_to_brep(boolean_clipping_result): 41 | """ 42 | Convert an IFC BooleanClippingResult [booleanclippingresult]_ to a COMPAS brep. 43 | 44 | """ 45 | return IfcBooleanResult_to_brep(boolean_clipping_result) 46 | 47 | 48 | def IfcBooleanResult_to_brep(boolean_result): 49 | """ 50 | Convert an IFC BooleanResult [booleanresult]_ to a COMPAS brep. 51 | 52 | """ 53 | from compas_occ.brep import OCCBrep 54 | 55 | br = boolean_result 56 | 57 | # First Operand 58 | if br.FirstOperand.is_a("IfcBooleanResult"): 59 | A = IfcBooleanResult_to_brep(br.FirstOperand) 60 | elif br.FirstOperand.is_a("IfcExtrudedAreaSolid"): 61 | A = IfcExtrudedAreaSolid_to_brep(br.FirstOperand) 62 | else: 63 | raise NotImplementedError 64 | 65 | # Second Operand 66 | if br.SecondOperand.is_a("IfcBoxedHalfSpace"): 67 | B = IfcBoxedHalfSpace(br.SecondOperand) 68 | elif br.SecondOperand.is_a("IfcPolygonalBoundedHalfSpace"): 69 | B = IfcPolygonalBoundedHalfSpace_to_brep(br.SecondOperand) 70 | elif br.SecondOperand.is_a("IfcPolygonalFaceSet"): 71 | B = IfcPolygonalFaceSet_to_brep(br.SecondOperand) 72 | else: 73 | raise NotImplementedError(br.SecondOperand) 74 | 75 | # Operator 76 | if br.Operator == "UNION": 77 | C: OCCBrep = A + B 78 | elif br.Operator == "INTERSECTION": 79 | C: OCCBrep = A & B 80 | elif br.Operator == "DIFFERENCE": 81 | C: OCCBrep = A - B 82 | else: 83 | raise NotImplementedError(br.Operator) 84 | 85 | C.sew() 86 | C.fix() 87 | C.make_solid() 88 | return C 89 | 90 | 91 | def IfcIndexedPolyCurve_to_lines(axis) -> List[Line]: 92 | """ 93 | Convert an IFC Axis [axis]_ to a COMPAS polyline. 94 | 95 | """ 96 | lines = [] 97 | points = IfcCartesianPointList(axis.Points) 98 | for segment in axis.Segments: 99 | start, end = segment.wrappedValue 100 | start -= 1 101 | end -= 1 102 | lines.append(Line(points[start], points[end])) 103 | return lines 104 | 105 | 106 | def IfcBoundingBox_to_box(bounding_box) -> Box: 107 | """ 108 | Convert an IFC BoundingBox [boundingbox]_ to a COMPAS box. 109 | 110 | """ 111 | a = Point(*bounding_box.Corner.Coordinates) 112 | b = a + [bounding_box.XDim, bounding_box.YDim, 0] 113 | box = Box.from_corner_corner_height(a, b, bounding_box.ZDim) 114 | return box 115 | 116 | 117 | def IfcBoxedHalfSpace(boxed_half_space): 118 | bhs = boxed_half_space 119 | print(bhs.BaseSurface) 120 | print(bhs.Enclosure) 121 | print(bhs.AgreementFlag) 122 | 123 | 124 | def IfcCartesianPointList(cartesian_point_list) -> List[Point]: 125 | return [Point(*p) for p in cartesian_point_list.CoordList] 126 | 127 | 128 | def IfcExtrudedAreaSolid_to_brep(extruded_area_solid): 129 | from compas_occ.brep import OCCBrep 130 | 131 | eas = extruded_area_solid 132 | profile = IfcProfileDef_to_curve(eas.SweptArea) 133 | 134 | position = IfcAxis2Placement3D_to_frame(eas.Position) 135 | transformation = Transformation.from_frame(position) 136 | 137 | def _extrude(profile, eas): 138 | vector = IfcDirection_to_vector(eas.ExtrudedDirection) 139 | vector.scale(eas.Depth) 140 | brep = OCCBrep.from_extrusion(profile, vector) 141 | brep.sew() 142 | brep.fix() 143 | brep.make_solid() 144 | brep.transform(transformation) 145 | return brep 146 | 147 | if isinstance(profile, list): 148 | extrusions = [_extrude(p, eas) for p in profile] 149 | union = extrusions[0] 150 | for e in extrusions[1:]: 151 | union = union + e 152 | union.sew() 153 | union.fix() 154 | union.make_solid() 155 | return union 156 | else: 157 | return _extrude(profile, eas) 158 | 159 | 160 | def IfcFacetedBrep_to_brep(faceted_brep): 161 | pass 162 | 163 | 164 | def IfcFacetedBrepWithVoids_to_brep(faceted_brep_with_voids): 165 | # fbwv = faceted_brep_with_voids 166 | pass 167 | 168 | 169 | def IfcIndexedPolygonalFaceSet_to_brep(): 170 | pass 171 | 172 | 173 | def IfcPolygonalBoundedHalfSpace_to_brep(polygonal_bounded_half_space): 174 | pbhs = polygonal_bounded_half_space 175 | print(pbhs.Position) 176 | print(pbhs.PolygonalBoundary) 177 | print(pbhs.BaseSurface) 178 | 179 | 180 | def IfcPolygonalFaceSet_to_brep(polygonal_face_set): 181 | from compas_occ.brep import OCCBrep 182 | 183 | pfs = polygonal_face_set 184 | 185 | xyz = pfs.Coordinates.CoordList 186 | vertices = [[float(x), float(y), float(z)] for x, y, z in xyz] 187 | faces = [[i - 1 for i in face.CoordIndex] for face in pfs.Faces] 188 | polygons = [[vertices[index] for index in face] for face in faces] 189 | brep = OCCBrep.from_polygons(polygons) 190 | 191 | brep.sew() 192 | brep.fix() 193 | brep.make_solid() 194 | 195 | return brep 196 | 197 | 198 | def IfcTessellatedFaceSet_to_brep(tessellated_face_set): 199 | tfs = tessellated_face_set 200 | 201 | if tfs.is_a("IfcTriangulatedFaceSet"): 202 | return IfcTriangulatedFaceSet_to_brep(tfs) 203 | 204 | if tfs.is_a("IfcPolygonalFaceSet"): 205 | return IfcPolygonalFaceSet_to_brep(tfs) 206 | 207 | raise NotImplementedError(tfs.is_a()) 208 | 209 | 210 | def IfcTriangulatedFaceSet_to_brep(triangulated_face_set): 211 | from compas_occ.brep import OCCBrep 212 | 213 | tfs = triangulated_face_set 214 | 215 | xyz = tfs.Coordinates.CoordList 216 | vertices = [[float(x), float(y), float(z)] for x, y, z in xyz] 217 | faces = [[a - 1, b - 1, c - 1] for a, b, c in tfs.CoordIndex] 218 | triangles = [[vertices[index] for index in face] for face in faces] 219 | brep = OCCBrep.from_polygons(triangles) 220 | 221 | brep.sew() 222 | brep.fix() 223 | brep.make_solid() 224 | 225 | return brep 226 | 227 | 228 | def IfcShape_to_brep(ifc_shape): 229 | from compas_occ.brep import OCCBrep 230 | 231 | settings = ifcopenshell.geom.settings() 232 | settings.set(settings.USE_PYTHON_OPENCASCADE, True) 233 | shape = ifcopenshell.geom.create_shape(settings, ifc_shape) 234 | 235 | brep = OCCBrep.from_shape(shape) 236 | brep.sew() 237 | brep.fix() 238 | brep.make_solid() 239 | 240 | return brep 241 | 242 | 243 | def IfcShape_to_tessellatedbrep(ifc_shape): 244 | from compas_ifc.brep import TessellatedBrep 245 | 246 | settings = ifcopenshell.geom.settings() 247 | shape = ifcopenshell.geom.create_shape(settings, ifc_shape) 248 | return TessellatedBrep(vertices=shape.verts, edges=shape.edges, faces=shape.faces) 249 | 250 | 251 | # # IfcTessellationItem_to_mesh 252 | # def tessellation_to_mesh(item) -> Mesh: 253 | # """ 254 | # Convert a tessellation item to a Mesh. 255 | 256 | # """ 257 | # mesh = Mesh() 258 | # mesh.update_default_face_attributes(colour=None) 259 | 260 | # face_color = {} 261 | 262 | # if item.HasColours: 263 | # colourmap = item.HasColours[0] 264 | # for index, face in zip( 265 | # colourmap.ColourIndex, 266 | # colourmap.MappedTo.Faces, 267 | # ): 268 | # fid = face.id() 269 | # colour = colourmap.Colours.ColourList[index - 1] 270 | # face_color[fid] = Color(*colour) 271 | 272 | # for face in item.Faces: 273 | # vertices = [] 274 | # for index in face.CoordIndex: 275 | # x, y, z = item.Coordinates.CoordList[index - 1] 276 | # vertices.append(mesh.add_vertex(x=x, y=y, z=z)) 277 | # mesh.add_face(vertices, colour=face_color.get(face.id())) 278 | # return mesh 279 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | ******************************************************************************** 3 | helpers 4 | ******************************************************************************** 5 | 6 | .. currentmodule:: compas_ifc.helpers 7 | 8 | Functions 9 | ========= 10 | 11 | .. autosummary:: 12 | :toctree: generated/ 13 | :nosignatures: 14 | 15 | public_attributes 16 | 17 | """ 18 | 19 | import ifcopenshell 20 | 21 | 22 | def public_attributes(o): 23 | public = [name for name in dir(o) if not name.startswith("_")] 24 | return public 25 | 26 | 27 | def print_schema_ref_tree(schema, declaration, indent=0, only_required=True): 28 | """Prints a tree view of the schema references.""" 29 | 30 | if indent == 0: 31 | print(declaration) 32 | 33 | indent += 4 34 | 35 | if declaration.as_entity(): 36 | for attribute in declaration.all_attributes(): 37 | if only_required and attribute.optional(): 38 | continue 39 | 40 | if attribute.type_of_attribute().as_named_type(): 41 | print(" " * indent, attribute) 42 | print_schema_ref_tree(schema, attribute.type_of_attribute().declared_type(), indent) 43 | elif attribute.type_of_attribute().as_aggregation_type(): 44 | print(" " * indent, attribute) 45 | else: 46 | raise NotImplementedError 47 | 48 | 49 | def print_entity_ref_tree(entity, indent=0): 50 | """Prints a tree view of the entity references.""" 51 | info = entity.get_info() 52 | print(" " * indent, "<", entity.id(), entity.is_a(), ">") 53 | for key, value in info.items(): 54 | if key.lower() == "type" or key.lower() == "id": 55 | continue 56 | if isinstance(value, ifcopenshell.entity_instance): 57 | print(" " * indent, key, ":") 58 | print_entity_ref_tree(value, indent + 4) 59 | else: 60 | print(" " * indent, key, ":", value) 61 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/objectdefinition.py: -------------------------------------------------------------------------------- 1 | from compas.datastructures import Tree 2 | from compas.datastructures import TreeNode 3 | 4 | from .root import Root 5 | 6 | 7 | class ObjectDefinition(Root): 8 | """Base class for all object definitions. An object definition is a definition of a thing that is or may be part of a spatial structure. 9 | 10 | Attributes 11 | ---------- 12 | parent : :class:`compas_ifc.entities.ObjectDefinition` 13 | The parent of this element in spatial hierarchy. 14 | children : List[:class:`compas_ifc.entities.ObjectDefinition`] 15 | The children of this element in spatial hierarchy. 16 | 17 | """ 18 | 19 | def __init__(self, entity, model) -> None: 20 | super().__init__(entity, model) 21 | self._parent = None 22 | 23 | def decomposes(self): 24 | """Return the relation that decomposes this element.""" 25 | if self not in self.model._new_entities: 26 | for rel in self._entity.Decomposes: 27 | return self.model.reader.get_entity(rel) 28 | 29 | @property 30 | def parent(self): 31 | if not self._parent and self._entity: 32 | relation = self.decomposes() 33 | if relation: 34 | self._parent = relation["RelatingObject"] 35 | return self._parent 36 | 37 | @parent.setter 38 | def parent(self, parent): 39 | self._parent = parent 40 | 41 | def is_decomposed_by(self): 42 | """Return the relation that this element is decomposed by.""" 43 | return [self.model.reader.get_entity(rel) for rel in self._entity.IsDecomposedBy] 44 | 45 | @property 46 | def children(self): 47 | children = [entity for entity in self.model.get_entities_by_type("IfcObjectDefinition") if entity.parent == self] 48 | for entity in self.model._new_entities: 49 | if entity.parent == self and entity not in children: 50 | children.append(entity) 51 | 52 | # sort children by name 53 | children.sort(key=lambda x: x.name) 54 | return children 55 | 56 | def traverse(self, recursive: bool = True): 57 | """Traverse children of this element. 58 | 59 | Parameters 60 | ---------- 61 | recursive : bool, optional 62 | Whether to traverse down the tree recursively, by default True 63 | 64 | Yields 65 | ------ 66 | :class:`compas_ifc.entities.ObjectDefinition` 67 | The children of this element. 68 | """ 69 | for child in self.children: 70 | yield child 71 | if recursive: 72 | yield from child.traverse(recursive) 73 | 74 | def traverse_ancestor(self, recursive: bool = True): 75 | """Traverse ancestors of this element. 76 | 77 | Parameters 78 | ---------- 79 | recursive : bool, optional 80 | Whether to traverse up the tree recursively, by default True 81 | 82 | Yields 83 | ------ 84 | :class:`compas_ifc.entities.ObjectDefinition` 85 | The ancestors of this element. 86 | """ 87 | parent = self.parent 88 | if parent: 89 | yield from parent.traverse_ancestor(recursive) 90 | yield parent 91 | 92 | def traverse_branch(self, recursive: bool = True): 93 | """Traverse the spatial branch of this element. 94 | 95 | Parameters 96 | ---------- 97 | recursive : bool, optional 98 | Whether to traverse up and down the tree recursively, by default True 99 | 100 | Yields 101 | ------ 102 | :class:`compas_ifc.entities.ObjectDefinition` 103 | The ancestors, self, and children of this element. 104 | """ 105 | yield from self.traverse_ancestor(recursive) 106 | yield self 107 | yield from self.traverse(recursive) 108 | 109 | def print_spatial_hierarchy(self, max_level: int = 4) -> None: 110 | """Print the spatial hierarchy of this element. 111 | 112 | Parameters 113 | ---------- 114 | max_level : int, optional 115 | The maximum level of the hierarchy to print, by default 4 116 | 117 | Returns 118 | ------- 119 | None 120 | """ 121 | print("=" * 80) 122 | print("SPATIAL HIERARCHY") 123 | print(self.spatial_hierarchy.get_hierarchy_string(max_depth=max_level)) 124 | print("=" * 80) 125 | 126 | @property 127 | def spatial_hierarchy(self) -> Tree: 128 | tree = Tree() 129 | 130 | class Node(TreeNode): 131 | def __repr__(self): 132 | return str(self.name) 133 | 134 | root = Node(self) 135 | 136 | tree.add(root) 137 | 138 | def traverse(entity, parent): 139 | node = Node(entity) 140 | parent.add(node) 141 | for child in entity.children: 142 | traverse(child, node) 143 | 144 | traverse(self, root) 145 | return tree 146 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/product.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | from typing import List 3 | 4 | from compas.geometry import Scale 5 | from compas.geometry import Transformation 6 | 7 | from compas_ifc.entities.objectdefinition import ObjectDefinition 8 | from compas_ifc.helpers import public_attributes 9 | 10 | 11 | class Product(ObjectDefinition): 12 | """ 13 | Class representing an IFC product. A product may be a physical object or a virtual object, that has one or more geometric representations. 14 | 15 | Attributes 16 | ---------- 17 | axis : List[:class:`compas.geometry.Line`] 18 | The axis representation of the product. Converted to list of COMPAS lines. 19 | box : :class:`compas.geometry.Box` 20 | The box representation of the product. Converted to COMPAS box. 21 | body : List[:class:`compas_occ.geometry.Shape`] 22 | The body representation of the product. Converted to list of OCC shapes (BRep). 23 | body_with_opening : List[:class:`compas_occ.geometry.Shape`] 24 | The body representation of the product including openings. Converted to list of OCC shapes (BRep). 25 | opening : List[:class:`compas_occ`] 26 | The opening representation of the product. Converted to list of OCC shapes (BRep). 27 | transformation : :class:`compas.geometry.Transformation` 28 | The transformation of the product, Calculated from the stack of transformations of the product's parent entities. Read-only. 29 | style : dict 30 | The style of the product. Converted to dictionary. 31 | frame : :class:`compas.geometry.Frame` 32 | The frame of the product. Converted to COMPAS frame. 33 | 34 | 35 | """ 36 | 37 | def __init__(self, entity, model) -> None: 38 | super().__init__(entity, model) 39 | self._axis = None 40 | self._box = None 41 | self._body = None 42 | self._opening = None 43 | self._body_with_opening = None 44 | self._transformation = None 45 | self._style = None 46 | self._frame = None 47 | 48 | def classifications(self) -> List[Dict[str, str]]: 49 | """ 50 | External sources of information associated with this product. 51 | The source of information can be: 52 | 53 | * a classification system; 54 | * a dictionary server; 55 | * any external catalogue that classifies the product further; 56 | * a service that combines the above. 57 | 58 | Returns 59 | ------- 60 | List[dict[str, str]] 61 | 62 | References 63 | ---------- 64 | :ifc:`classification` 65 | 66 | """ 67 | classifications = [] 68 | if self._entity.HasAssociations: 69 | for association in self._entity.HasAssociations: 70 | if association.is_a("IfcRelAssociatesClassification"): 71 | classifications.append( 72 | { 73 | "name": association.Name, 74 | "identification": association.RelatingClassification.Identification, 75 | "source": association.RelatingClassification.ReferencedSource.Source, 76 | } 77 | ) 78 | return classifications 79 | 80 | def materials(self) -> List[Dict]: 81 | """ 82 | The materials associated with this product. 83 | 84 | Returns 85 | ------- 86 | List[Dict] 87 | 88 | """ 89 | materials = [] 90 | 91 | for association in self._entity.HasAssociations: 92 | if not association.is_a("IfcRelAssociatesMaterial"): 93 | continue 94 | 95 | if association.RelatingMaterial.is_a("IfcMaterialLayerSet"): 96 | print(public_attributes(association.RelatingMaterial)) 97 | 98 | elif association.RelatingMaterial.is_a("IfcMaterialLayerSetUsage"): 99 | print(public_attributes(association.RelatingMaterial)) 100 | 101 | elif association.RelatingMaterial.is_a("IfcMaterialConstituentSet"): 102 | # name = association.RelatingMaterial.Name 103 | for constituent in association.RelatingMaterial.MaterialConstituents: 104 | for pset in constituent.Material.HasProperties: 105 | properties = {} 106 | for prop in pset.Properties: 107 | key = prop.Name 108 | value = prop.NominalValue.get_info()["wrappedValue"] 109 | properties[key] = value 110 | 111 | else: 112 | raise NotImplementedError 113 | 114 | return materials 115 | 116 | @property 117 | def axis(self): 118 | from compas_ifc.representation import entity_axis_geometry 119 | 120 | if not self._axis: 121 | self._axis = entity_axis_geometry(self) 122 | return self._axis 123 | 124 | @property 125 | def box(self): 126 | from compas_ifc.representation import entity_box_geometry 127 | 128 | if not self._box: 129 | self._box = entity_box_geometry(self) or entity_box_geometry(self, context="Plan") 130 | return self._box 131 | 132 | @property 133 | def body(self): 134 | from compas_ifc.representation import entity_body_geometry 135 | 136 | if not self._body: 137 | self._body = entity_body_geometry(self, use_occ=self.model.reader.use_occ) 138 | return self._body 139 | 140 | @body.setter 141 | def body(self, value): 142 | self._body = value 143 | 144 | @property 145 | def opening(self): 146 | from compas_ifc.representation import entity_opening_geometry 147 | 148 | if not self._opening: 149 | self._opening = entity_opening_geometry(self, use_occ=self.model.reader.use_occ) 150 | return self._opening 151 | 152 | @opening.setter 153 | def opening(self, value): 154 | self._opening = value 155 | 156 | @property 157 | def body_with_opening(self): 158 | if self._body_with_opening is None: 159 | if self._entity: 160 | if not self._body_with_opening: 161 | cached_geometry = self.model.reader.get_preloaded_geometry(self) 162 | if cached_geometry: 163 | self._body_with_opening = cached_geometry 164 | else: 165 | return 166 | # TODO: double check if this is still triggered with preloaded geometry 167 | # self._body_with_opening = entity_body_with_opening_geometry(self, use_occ=self.model.reader.use_occ) 168 | 169 | else: 170 | scale = self.model.project.length_scale 171 | T = Transformation.from_frame(self.frame) 172 | S = Scale.from_factors([scale, scale, scale]) 173 | 174 | from compas.geometry import Shape 175 | 176 | if isinstance(self.body, Shape): 177 | geometry = self.body.transformed(S * T) 178 | geometry.scale(scale) 179 | else: 180 | geometry = self.body.transformed(S * T) 181 | 182 | self._body_with_opening = geometry 183 | 184 | return self._body_with_opening 185 | 186 | @body_with_opening.setter 187 | def body_with_opening(self, value): 188 | self._body_with_opening = value 189 | 190 | @property 191 | def geometry(self): 192 | return self.body_with_opening 193 | 194 | @property 195 | def transformation(self): 196 | from compas_ifc.representation import entity_transformation 197 | 198 | if not self._transformation: 199 | self._transformation = entity_transformation(self) 200 | return self._transformation 201 | 202 | @property 203 | def frame(self): 204 | from compas_ifc.representation import entity_frame 205 | 206 | if not self._frame and self._entity: 207 | self._frame = entity_frame(self) 208 | return self._frame 209 | 210 | @frame.setter 211 | def frame(self, value): 212 | self._frame = value 213 | 214 | @property 215 | def style(self): 216 | if not self._style: 217 | if self._entity: 218 | self._style = self.model.reader.get_preloaded_style(self) 219 | else: 220 | self._style = {} 221 | # TODO: handle non-preloaded situation 222 | return self._style 223 | 224 | def show(self): 225 | self.model.show(self) 226 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/project.py: -------------------------------------------------------------------------------- 1 | # from typing import Iterator 2 | from typing import Dict 3 | from typing import List 4 | 5 | from compas.geometry import Frame 6 | from compas.geometry import Vector 7 | 8 | from compas_ifc.entities.objectdefinition import ObjectDefinition 9 | from compas_ifc.entities.site import Building 10 | from compas_ifc.entities.site import Site 11 | from compas_ifc.resources import IfcAxis2Placement3D_to_frame 12 | 13 | 14 | class Project(ObjectDefinition): 15 | """ 16 | Class representing IFC projects. 17 | 18 | Attributes 19 | ---------- 20 | sites : List[:class:`Site`] 21 | The sites contained in the project. 22 | buildings : List[:class:`Building`] 23 | The buildings contained in the project. 24 | Note that this list is empty if ``sites`` is not empty. 25 | units : List[Dict] 26 | The SI units used in the project. 27 | contexts : List[Dict] 28 | The representation contexts included in the project. 29 | Each representation context defines a coordinates system, a "true north" vector, and numerical precision. 30 | frame : :class:`compas.geometry.Frame` or None 31 | The reference frame, defined by the "Model" context, if that context exists. 32 | north : :class:`compas.geometry.Vector` or None 33 | The north vector with respect to the reference frame. 34 | gps : ??? 35 | Positioning of the reference frame on earth. 36 | 37 | Notes 38 | ----- 39 | The order of spatial structure elements being included in the concept for builing projects are from high to low level: 40 | IfcProject, IfcSite, IfcBuilding, IfcBuildingStorey, and IfcSpace with IfcSite, IfcBuildingStorey and IfcSpace being optional levels. 41 | Therefore an spatial structure element can only be part of an element at the same or higher level [2]_. 42 | 43 | This means the minimum spatial structure of a project is: 44 | 45 | * IfcProject 46 | * IfcBuilding 47 | 48 | Therefore, a project will contain one or more sites, or one or more buildings. 49 | If the project defines sites, the buildings will be contained in the sites. 50 | 51 | .. code-block:: python 52 | 53 | model = Model(filepath) 54 | for project in model.projects: 55 | if not project.sites: 56 | for building in project.buildings: 57 | # ... 58 | else: 59 | for site in project.sites: 60 | for building in site.buildings: 61 | # ... 62 | 63 | References 64 | ---------- 65 | .. [1] :ifc:`project-context` 66 | .. [2] :ifc:`spatial-composition` 67 | 68 | """ 69 | 70 | def __init__(self, entity, model): 71 | super().__init__(entity, model) 72 | self._units = None 73 | self._contexts = None 74 | self._sites = None 75 | self._buildings = None 76 | self._length_unit = {"type": "LENGTHUNIT", "name": "METRE", "prefix": "MILLI"} 77 | # TODO: deal with other units 78 | 79 | @property 80 | def sites(self) -> List[Site]: 81 | return [entity for entity in self.children if entity.is_a("IfcSite")] 82 | 83 | @property 84 | def buildings(self) -> List[Building]: 85 | return [entity for entity in self.traverse() if entity.is_a("IfcBuilding")] 86 | 87 | @property 88 | def contexts(self) -> List[Dict]: 89 | if self._contexts is None: 90 | self._contexts = [] 91 | for context in self._entity.RepresentationContexts: 92 | north = Vector(*context.TrueNorth.DirectionRatios) 93 | wcs = IfcAxis2Placement3D_to_frame(context.WorldCoordinateSystem) 94 | self._contexts.append( 95 | { 96 | "identifier": context.ContextIdentifier, 97 | "type": context.ContextType, 98 | "precision": context.Precision, 99 | "dimension": context.CoordinateSpaceDimension, 100 | "north": north, 101 | "wcs": wcs, 102 | } 103 | ) 104 | return self._contexts 105 | 106 | @property 107 | def units(self) -> List[Dict]: 108 | if self._units is None: 109 | self._units = [] 110 | for unit in self._entity.UnitsInContext.Units: 111 | if unit.is_a("IfcSIUnit"): 112 | self._units.append( 113 | { 114 | "type": unit.UnitType, 115 | "name": unit.Name, 116 | "prefix": unit.Prefix, 117 | } 118 | ) 119 | return self._units 120 | 121 | @property 122 | def length_unit(self): 123 | if self._entity: 124 | for unit in self.units: 125 | if unit["type"] == "LENGTHUNIT": 126 | return unit 127 | else: 128 | return self._length_unit 129 | 130 | @length_unit.setter 131 | def length_unit(self, unit): 132 | self._length_unit = unit 133 | 134 | @property 135 | def length_scale(self): 136 | unit = self.length_unit 137 | if unit: 138 | if unit["name"] == "METRE" and not unit["prefix"]: 139 | return 1.0 140 | if unit["name"] == "METRE" and unit["prefix"] == "CENTI": 141 | return 1e-2 142 | if unit["name"] == "METRE" and unit["prefix"] == "MILLI": 143 | return 1e-3 144 | return 1.0 145 | 146 | @property 147 | def frame(self) -> Frame: 148 | if self._entity: 149 | for context in self.contexts: 150 | if context["type"] == "Model": 151 | return context["wcs"] 152 | 153 | @property 154 | def north(self) -> Vector: 155 | for context in self.contexts: 156 | if context["type"] == "Model": 157 | return context["north"] 158 | 159 | @property 160 | def gps(self): 161 | # global positioning of the reference frame on earth 162 | pass 163 | 164 | # @property 165 | # def owner(self): 166 | 167 | def add_site(self, site: Site) -> None: 168 | """ 169 | Add a site to the project. 170 | 171 | Parameters 172 | ---------- 173 | site : :class:`Site` 174 | 175 | Returns 176 | ------- 177 | None 178 | 179 | """ 180 | raise NotImplementedError 181 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/root.py: -------------------------------------------------------------------------------- 1 | import ifcopenshell 2 | 3 | from .entity import Entity 4 | 5 | 6 | class Root(Entity): 7 | """Base class for all IFC entities that are derived from IfcRoot. 8 | 9 | Attributes 10 | ---------- 11 | global_id : str 12 | The global id of the entity. 13 | name : str 14 | The name of the entity. 15 | """ 16 | 17 | def __init__(self, entity, model) -> None: 18 | super().__init__(entity, model) 19 | if not entity: 20 | self["GlobalId"] = ifcopenshell.guid.new() 21 | self["Name"] = None 22 | 23 | def __repr__(self): 24 | return "<{} Name: {}, GlobalId: {}>".format(self.ifc_type, self.name, self.global_id) 25 | 26 | @property 27 | def global_id(self): 28 | return self["GlobalId"] 29 | 30 | @property 31 | def name(self): 32 | return self["Name"] 33 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/site.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from compas_ifc.entities.building import Building 4 | from compas_ifc.entities.geographicelement import GeographicElement 5 | from compas_ifc.entities.spatialelement import SpatialElement 6 | 7 | 8 | class Site(SpatialElement): 9 | """ 10 | Class representing an IFC site. 11 | 12 | Attributes 13 | ---------- 14 | buildings : List[:class:`Building`] 15 | The buildings contained in the site. 16 | If the buildings are not properly nested, they can be found at the top level of an IFC model. 17 | address : str 18 | The compiled address of the site. 19 | geographic_elements : List[:class:`GeographicElement`] 20 | The geographic elements contained in the site. 21 | 22 | """ 23 | 24 | def __init__(self, entity, model): 25 | super().__init__(entity, model) 26 | self._buildings = None 27 | self._geographic_elements = None 28 | 29 | @property 30 | def buildings(self) -> List[Building]: 31 | return [entity for entity in self.traverse() if entity.is_a("IfcBuilding")] 32 | 33 | @property 34 | def geographic_elements(self) -> List[GeographicElement]: 35 | return [entity for entity in self.traverse() if entity.is_a("IfcGeographicElement")] 36 | 37 | @property 38 | def address(self) -> str: 39 | attr = self.attribute("SiteAddress") 40 | address = "" 41 | address += ", ".join(attr["AddressLines"]) 42 | address += f", {attr['Country']}-{attr['PostalCode']}" 43 | address += f" {attr['Town']}" 44 | return address 45 | 46 | def add_building(self, building: Building) -> None: 47 | """ 48 | Add a building to this site. 49 | 50 | Parameters 51 | ---------- 52 | building : :class:`Building` 53 | 54 | Returns 55 | ------- 56 | None 57 | 58 | """ 59 | raise NotImplementedError 60 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/space.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from compas_ifc.entities.buildingelements import BuildingElement 4 | from compas_ifc.entities.spatialelement import SpatialElement 5 | 6 | 7 | class Space(SpatialElement): 8 | """ 9 | Class representing an IFC space. 10 | 11 | Attributes 12 | ---------- 13 | building_elements : List[:class:`BuildingElement`] 14 | The building elements contained in this space. 15 | 16 | """ 17 | 18 | def __init__(self, entity, model): 19 | super().__init__(entity, model) 20 | self._building_elements = None 21 | 22 | @property 23 | def building_elements(self) -> List[BuildingElement]: 24 | if not self._building_elements: 25 | self._building_elements = [entity for entity in self.traverse() if entity.is_a("IfcBuildingElement")] 26 | return self._building_elements 27 | -------------------------------------------------------------------------------- /src/compas_ifc/__old/spatialelement.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from compas_ifc.entities.product import Product 4 | 5 | 6 | class SpatialElement(Product): 7 | """ 8 | Class representing an IFC spatial element. A spatial element is a product that is used to spatially organize other products. 9 | """ 10 | 11 | def is_containment_hierarchical(self) -> bool: 12 | """Return True if the spatial element is hierarchical, which means it can have only one decomposition parent.""" 13 | if len(self._entity.Decomposes) > 1: 14 | return False 15 | return True 16 | 17 | def contains_elements(self) -> List["SpatialElement"]: 18 | """Returen contained elements of a spatial element.""" 19 | return [self.model.reader.get_entity(rel) for rel in self._entity.ContainsElements] 20 | -------------------------------------------------------------------------------- /src/compas_ifc/brep/__init__.py: -------------------------------------------------------------------------------- 1 | from .tessellatedbrep import TessellatedBrep 2 | 3 | try: 4 | from .tessellatedbrepobject import TessellatedBrepObject 5 | except ImportError: 6 | pass 7 | 8 | from compas.plugins import plugin 9 | from compas.scene import register 10 | 11 | 12 | @plugin(category="factories", requires=["compas_viewer"], trylast=True) 13 | def register_scene_objects(): 14 | register(TessellatedBrep, TessellatedBrepObject, context="Viewer") 15 | 16 | try: 17 | from compas_occ.brep import OCCBrep 18 | from .ifcbrepobject import IFCBrepObject 19 | 20 | register(OCCBrep, IFCBrepObject, context="Viewer") 21 | 22 | except ImportError: 23 | pass 24 | 25 | 26 | __all__ = ["TessellatedBrep", "TessellatedBrepObject"] 27 | -------------------------------------------------------------------------------- /src/compas_ifc/brep/ifcbrepobject.py: -------------------------------------------------------------------------------- 1 | try: 2 | import numpy as np 3 | from compas.colors import Color 4 | from compas.tolerance import TOL 5 | from compas_occ.brep import OCCBrep 6 | from compas_viewer.scene.brepobject import BRepObject 7 | 8 | class IFCBrepObject(BRepObject): 9 | def __init__(self, shellcolors=None, **kwargs): 10 | brep = kwargs["item"] 11 | brep.simplify() 12 | brep.heal() 13 | 14 | super().__init__(**kwargs) 15 | self.shells = [shell.to_tesselation(TOL.lineardeflection)[0] for shell in self.brep.shells] 16 | self.shellcolors = shellcolors or [self.facecolor.rgba for _ in self.shells] 17 | self._bounding_box_center = None 18 | 19 | @property 20 | def brep(self) -> OCCBrep: 21 | return self.item 22 | 23 | @property 24 | def bounding_box_center(self): 25 | if not self._bounding_box_center: 26 | self._bounding_box_center = np.mean(self.points, axis=0) 27 | return self._bounding_box_center 28 | 29 | def _read_frontfaces_data(self): 30 | positions = [] 31 | elements = np.array([], dtype=int).reshape(0, 3) 32 | colors = [] 33 | 34 | for shell, color in zip(self.shells, self.shellcolors): 35 | shell_positions, shell_elements = shell.to_vertices_and_faces() 36 | if len(shell_elements) == 0: 37 | continue 38 | shell_elements = np.array(shell_elements) + len(positions) 39 | positions += shell_positions 40 | elements = np.vstack((elements, shell_elements)) 41 | colors += [Color(*color)] * len(shell_positions) # TODO: this is terrible 42 | if color[3] < 1: 43 | self.opacity = 0.999 # NOTE: this is to trigger the object order sorting 44 | 45 | return positions, colors, elements 46 | 47 | def _read_backfaces_data(self): 48 | positions = [] 49 | elements = np.array([], dtype=int).reshape(0, 3) 50 | colors = [] 51 | 52 | for shell, color in zip(self.shells, self.shellcolors): 53 | shell_positions, shell_elements = shell.to_vertices_and_faces() 54 | if len(shell_elements) == 0: 55 | continue 56 | for element in shell_elements: 57 | element.reverse() 58 | shell_elements = np.array(shell_elements) + len(positions) 59 | positions += shell_positions 60 | elements = np.vstack((elements, shell_elements)) 61 | colors += [Color(*color)] * len(shell_positions) 62 | 63 | return positions, colors, elements 64 | 65 | except ImportError: 66 | pass 67 | -------------------------------------------------------------------------------- /src/compas_ifc/brep/tessellatedbrep.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from compas.datastructures import Mesh 3 | from compas.geometry import Geometry 4 | from compas.geometry import bounding_box 5 | from compas.geometry import transform_points_numpy 6 | 7 | 8 | class TessellatedBrep(Geometry): 9 | def __init__(self, vertices=None, edges=None, faces=None, **kwargs): 10 | super().__init__(**kwargs) 11 | if vertices is None: 12 | vertices = [] 13 | if edges is None: 14 | edges = [] 15 | if faces is None: 16 | faces = [] 17 | self.vertices = np.array(vertices).reshape(-1, 3) 18 | self.edges = np.array(edges).reshape(-1, 2) 19 | self.faces = np.array(faces).reshape(-1, 3) 20 | 21 | def transform(self, transformation): 22 | self.vertices = transform_points_numpy(self.vertices, transformation) 23 | 24 | def to_vertices_and_faces(self): 25 | return self.vertices, self.faces 26 | 27 | def to_mesh(self): 28 | mesh = Mesh.from_vertices_and_faces(self.vertices, self.faces) 29 | mesh.name = self.name 30 | return mesh 31 | 32 | @property 33 | def aabb(self): 34 | from compas.geometry import Box 35 | 36 | return Box.from_bounding_box(bounding_box(self.vertices)) 37 | 38 | @property 39 | def obb(self): 40 | from compas.geometry import Box 41 | from compas.geometry import oriented_bounding_box_numpy 42 | 43 | return Box.from_bounding_box(oriented_bounding_box_numpy(self.vertices)) 44 | -------------------------------------------------------------------------------- /src/compas_ifc/brep/tessellatedbrepobject.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from compas.colors import Color 3 | from compas.datastructures import Mesh 4 | from compas_viewer.scene import ViewerSceneObject 5 | 6 | from .tessellatedbrep import TessellatedBrep 7 | 8 | 9 | class TessellatedBrepObject(ViewerSceneObject): 10 | def __init__(self, facecolors=None, **kwargs): 11 | super().__init__(**kwargs) 12 | 13 | # NOTE: it is not facecolors, it is verexcolor 14 | if not facecolors: 15 | self.facecolors = [Color(0.9, 0.9, 0.9) for _ in range(len(self.tessellatedbrep.faces) * 3)] 16 | else: 17 | facecolors = np.array(facecolors) 18 | self.facecolors = facecolors 19 | if np.mean(facecolors[:, 3]) < 1: 20 | # If mean alpha is less than 1, means the object has transparency 21 | self.opacity = 0.999 # Trigger the render order sorting of object 22 | 23 | @property 24 | def tessellatedbrep(self) -> TessellatedBrep: 25 | return self.item 26 | 27 | @property 28 | def bounding_box_center(self): 29 | if self._bounding_box_center is None: 30 | self._bounding_box_center = self.tessellatedbrep.vertices.mean(axis=0) 31 | return self._bounding_box_center 32 | 33 | def _read_points_data(self): 34 | pass 35 | 36 | def _read_lines_data(self): 37 | positions = self.tessellatedbrep.vertices.tolist() 38 | elements = self.tessellatedbrep.edges.tolist() 39 | colors = [Color(0.1, 0.1, 0.1)] * len(positions) 40 | return positions, colors, elements 41 | 42 | def _read_frontfaces_data(self): 43 | positions = self.tessellatedbrep.vertices[self.tessellatedbrep.faces].reshape(-1, 3).tolist() 44 | elements = np.arange(len(positions) * 3).reshape(-1, 3).tolist() 45 | colors = [Color(*color) for color in self.facecolors] 46 | return positions, colors, elements 47 | 48 | def _read_backfaces_data(self): 49 | positions = self.tessellatedbrep.vertices[self.tessellatedbrep.faces].reshape(-1, 3).tolist() 50 | elements = np.arange(len(positions) * 3).reshape(-1, 3) 51 | elements = elements[:, ::-1].tolist() 52 | colors = [Color(*color) for color in self.facecolors] 53 | return positions, colors, elements 54 | 55 | def to_mesh(self): 56 | return Mesh.from_vertices_and_faces(self.tessellatedbrep.vertices, self.tessellatedbrep.faces) 57 | -------------------------------------------------------------------------------- /src/compas_ifc/conversions/frame.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from operator import mul 3 | 4 | from compas.geometry import Frame 5 | from compas.geometry import Point 6 | from compas.geometry import Transformation 7 | from compas.geometry import Vector 8 | 9 | from compas_ifc.conversions.primitives import IfcCartesianPoint_to_point 10 | from compas_ifc.conversions.primitives import IfcDirection_to_vector 11 | from compas_ifc.entities.base import Base 12 | from compas_ifc.model import Model 13 | 14 | 15 | def create_IfcAxis2Placement3D(model: Model, point: Point = None, dir1: Vector = None, dir2: Vector = None) -> Base: 16 | """ 17 | Create an IFC Axis2Placement3D from a point, a direction and a second direction. 18 | """ 19 | point = model.create("IfcCartesianPoint", Coordinates=point or [0.0, 0.0, 0.0]) 20 | dir1 = model.create("IfcDirection", DirectionRatios=dir1 or [0.0, 0.0, 1.0]) 21 | dir2 = model.create("IfcDirection", DirectionRatios=dir2 or [1.0, 0.0, 0.0]) 22 | axis2placement = model.create("IfcAxis2Placement3D", Location=point, Axis=dir1, RefDirection=dir2) 23 | return axis2placement 24 | 25 | 26 | def frame_to_ifc_axis2_placement_3d(model: Model, frame: Frame) -> Base: 27 | return create_IfcAxis2Placement3D(model, point=frame.point, dir1=frame.zaxis, dir2=frame.xaxis) 28 | 29 | 30 | def assign_entity_frame(entity: Base, frame: Frame): 31 | local_placement = frame_to_ifc_axis2_placement_3d(entity.model, frame) 32 | placement = entity.model.create("IfcLocalPlacement", RelativePlacement=local_placement) 33 | entity.ObjectPlacement = placement 34 | 35 | 36 | def IfcLocalPlacement_to_transformation(placement: Base, scale: float = 1) -> Transformation: 37 | """ 38 | Convert an IFC LocalPlacement [localplacement]_ to a COMPAS transformation. 39 | This will resolve all relative placements into one transformation wrt the global coordinate system. 40 | 41 | """ 42 | stack = [] 43 | while True: 44 | Location = placement.RelativePlacement.Location 45 | Axis = placement.RelativePlacement.Axis 46 | RefDirection = placement.RelativePlacement.RefDirection 47 | 48 | if Axis and RefDirection: 49 | zaxis = Vector(*Axis.DirectionRatios) 50 | xaxis = Vector(*RefDirection.DirectionRatios) 51 | yaxis = zaxis.cross(xaxis) 52 | xaxis = yaxis.cross(zaxis) 53 | else: 54 | xaxis = Vector.Xaxis() 55 | yaxis = Vector.Yaxis() 56 | 57 | point = Point(*Location.Coordinates) * scale 58 | frame = Frame(point, xaxis, yaxis) 59 | stack.append(frame) 60 | 61 | if not placement.PlacementRelTo: 62 | break 63 | 64 | placement = placement.PlacementRelTo 65 | 66 | matrices = [Transformation.from_frame(f) for f in stack] 67 | return reduce(mul, matrices[::-1]) 68 | 69 | 70 | def IfcLocalPlacement_to_frame(placement: Base) -> Frame: 71 | """ 72 | Convert an IFC LocalPlacement to a COMPAS frame. 73 | """ 74 | 75 | Location = placement.RelativePlacement.Location 76 | Axis = placement.RelativePlacement.Axis 77 | RefDirection = placement.RelativePlacement.RefDirection 78 | 79 | if Axis and RefDirection: 80 | zaxis = Vector(*Axis.DirectionRatios) 81 | xaxis = Vector(*RefDirection.DirectionRatios) 82 | yaxis = zaxis.cross(xaxis) 83 | xaxis = yaxis.cross(zaxis) 84 | else: 85 | xaxis = Vector.Xaxis() 86 | yaxis = Vector.Yaxis() 87 | 88 | point = Point(*Location.Coordinates) 89 | return Frame(point, xaxis, yaxis) 90 | 91 | 92 | def IfcGridPlacement_to_transformation(placement: Base) -> Transformation: 93 | pass 94 | 95 | 96 | def IfcAxis2Placement2D_to_frame(placement: Base) -> Frame: 97 | """ 98 | Convert an IFC Axis2Placement2D [axis2placement2d]_ to a COMPAS frame. 99 | 100 | An Axis2Placement2D is a 2D placement based on a frame defined by a point and 2 vectors. 101 | 102 | """ 103 | # use the coordinate system of the representation context to replace missing axes 104 | # use defaults if also those not available 105 | point = IfcCartesianPoint_to_point(placement.Location) 106 | zaxis = Vector.Zaxis() 107 | if placement.RefDirection: 108 | xaxis = IfcDirection_to_vector(placement.RefDirection) 109 | else: 110 | xaxis = Vector.Xaxis() 111 | yaxis = zaxis.cross(xaxis) 112 | return Frame(point, xaxis, yaxis) 113 | 114 | 115 | def IfcAxis2Placement3D_to_frame(placement: Base) -> Frame: 116 | """ 117 | Convert an IFC Axis2Placement3D [axis2placement3d]_ to a COMPAS frame. 118 | 119 | An Axis2Placement3D is a 3D placement based on a frame defined by a point and 2 vectors. 120 | 121 | """ 122 | # use the coordinate system of the representation context to replace missing axes 123 | # use defaults if also those not available 124 | point = IfcCartesianPoint_to_point(placement.Location) 125 | if placement.Axis: 126 | zaxis = IfcDirection_to_vector(placement.Axis) 127 | else: 128 | zaxis = Vector.Zaxis() 129 | if placement.RefDirection: 130 | xaxis = IfcDirection_to_vector(placement.RefDirection) 131 | else: 132 | xaxis = Vector.Xaxis() 133 | yaxis = zaxis.cross(xaxis) 134 | xaxis = yaxis.cross(zaxis) 135 | return Frame(point, xaxis, yaxis) 136 | -------------------------------------------------------------------------------- /src/compas_ifc/conversions/mesh.py: -------------------------------------------------------------------------------- 1 | from compas.datastructures import Mesh 2 | 3 | from compas_ifc.entities.base import Base 4 | from compas_ifc.model import Model 5 | 6 | 7 | def mesh_to_IfcPolygonalFaceSet(model: Model, mesh: Mesh) -> Base: 8 | """ 9 | Convert a COMPAS mesh to an IFC PolygonalFaceSet. 10 | """ 11 | keys = sorted(mesh.vertices()) 12 | vertices = [] 13 | for key in keys: 14 | coords = mesh.vertex_coordinates(key) 15 | vertices.append((float(coords[0]), float(coords[1]), float(coords[2]))) 16 | 17 | faces = [] 18 | for fkey in mesh.faces(): 19 | indexes = [keys.index(i) + 1 for i in mesh.face_vertices(fkey)] 20 | faces.append(model.create("IfcIndexedPolygonalFace", CoordIndex=indexes)) 21 | 22 | return model.create( 23 | "IfcPolygonalFaceSet", 24 | Closed=mesh.is_closed(), 25 | Coordinates=model.create("IfcCartesianPointList3D", CoordList=vertices), 26 | Faces=faces, 27 | ) 28 | 29 | 30 | def mesh_to_IfcFaceBasedSurfaceModel(model: Model, mesh: Mesh) -> Base: 31 | """ 32 | Convert a COMPAS mesh to an IFC FaceBasedSurfaceModel. 33 | """ 34 | vertices = {} 35 | for key in mesh.vertices(): 36 | coords = mesh.vertex_coordinates(key) 37 | vertex = model.create("IfcCartesianPoint", Coordinates=(float(coords[0]), float(coords[1]), float(coords[2]))) 38 | vertices[key] = vertex 39 | 40 | faces = [] 41 | for fkey in mesh.faces(): 42 | indexes = [vertices[key] for key in mesh.face_vertices(fkey)] 43 | polyloop = model.create("IfcPolyLoop", Polygon=indexes) 44 | bound = model.create("IfcFaceOuterBound", Bound=polyloop, Orientation=True) 45 | face = model.create("IfcFace", Bounds=[bound]) 46 | faces.append(face) 47 | 48 | face_set = model.create("IfcConnectedFaceSet", CfsFaces=faces) 49 | ifc_face_based_surface_model = model.create("IfcFaceBasedSurfaceModel", FbsmFaces=[face_set]) 50 | 51 | return ifc_face_based_surface_model 52 | -------------------------------------------------------------------------------- /src/compas_ifc/conversions/primitives.py: -------------------------------------------------------------------------------- 1 | from compas.geometry import Frame 2 | from compas.geometry import Line 3 | from compas.geometry import Plane 4 | from compas.geometry import Point 5 | from compas.geometry import Vector 6 | 7 | from compas_ifc.entities.base import Base 8 | from compas_ifc.model import Model 9 | 10 | 11 | def IfcCartesianPoint_to_point(cartesian_point: Base) -> Point: 12 | """ 13 | Convert an IFC CartesianPoint [cartesianpoint]_ to a COMPAS point. 14 | 15 | """ 16 | return Point(*cartesian_point.Coordinates) 17 | 18 | 19 | def IfcDirection_to_vector(direction: Base) -> Vector: 20 | """ 21 | Convert an IFC Direction [direction]_ to a COMPAS vector. 22 | 23 | """ 24 | return Vector(*direction.DirectionRatios) 25 | 26 | 27 | def IfcVector_to_vector(vector: Base) -> Vector: 28 | """ 29 | Convert an IFC Vector [vector]_ to a COMPAS vector. 30 | 31 | """ 32 | direction = IfcDirection_to_vector(vector.Orientation) 33 | direction.scale(vector.Magnitude) 34 | return direction 35 | 36 | 37 | def IfcLine_to_line(line: Base) -> Line: 38 | """ 39 | Convert an IFC Line [line]_ to a COMPAS line. 40 | 41 | """ 42 | point = IfcCartesianPoint_to_point(line.Pnt) 43 | vector = IfcDirection_to_vector(line.Dir) 44 | return Line(point, point + vector) 45 | 46 | 47 | def IfcPlane_to_plane(plane: Base) -> Plane: 48 | """ 49 | Convert an IFC Plane [plane]_ to a COMPAS plane. 50 | 51 | """ 52 | point = IfcCartesianPoint_to_point(plane.Position.Location) 53 | normal = IfcDirection_to_vector(plane.Position.P[3]) 54 | return Plane(point, normal) 55 | 56 | 57 | def point_to_IfcCartesianPoint(model: Model, point: Point) -> Base: 58 | """ 59 | Convert a COMPAS point to an IFC CartesianPoint. 60 | """ 61 | return model.create("IfcCartesianPoint", Coordinates=(float(point.x), float(point.y), float(point.z))) 62 | 63 | 64 | def vector_to_IfcDirection(model: Model, vector: Vector) -> Base: 65 | """ 66 | Convert a COMPAS vector to an IFC Direction. 67 | """ 68 | return model.create("IfcDirection", DirectionRatios=(float(vector.x), float(vector.y), float(vector.z))) 69 | 70 | 71 | def frame_to_IfcAxis2Placement3D(model: Model, frame: Frame) -> Base: 72 | """ 73 | Convert a COMPAS frame to an IFC Axis2Placement3D. 74 | """ 75 | return model.create( 76 | "IfcAxis2Placement3D", 77 | Location=point_to_IfcCartesianPoint(model, frame.point), 78 | Axis=vector_to_IfcDirection(model, frame.zaxis), 79 | RefDirection=vector_to_IfcDirection(model, frame.xaxis), 80 | ) 81 | 82 | 83 | def frame_to_IfcPlane(model: Model, frame: Frame) -> Base: 84 | """ 85 | Convert a COMPAS frame to an IFC Plane. 86 | """ 87 | return model.create("IfcPlane", Position=frame_to_IfcAxis2Placement3D(model, frame)) 88 | -------------------------------------------------------------------------------- /src/compas_ifc/conversions/pset.py: -------------------------------------------------------------------------------- 1 | from ifcopenshell.util.element import get_psets 2 | 3 | from compas_ifc.entities.base import Base 4 | from compas_ifc.model import Model 5 | 6 | PRIMARY_MEASURE_TYPES = { 7 | str: "IfcLabel", 8 | float: "IfcReal", 9 | bool: "IfcBoolean", 10 | int: "IfcInteger", 11 | } 12 | 13 | 14 | def from_dict_to_ifc_properties(model: Model, properties: dict) -> list[Base]: 15 | """Convert a dictionary to a list of IfcProperties""" 16 | 17 | ifc_properties = [] 18 | 19 | for key, value in properties.items(): 20 | if isinstance(value, dict): 21 | subproperties = from_dict_to_ifc_properties(model, value) 22 | ifc_property = model.create("IfcComplexProperty", Name=key, UsageName="{}", HasProperties=subproperties) 23 | ifc_properties.append(ifc_property) 24 | 25 | elif isinstance(value, list): 26 | subproperties = from_dict_to_ifc_properties(model, {str(k): v for k, v in enumerate(value)}) 27 | ifc_property = model.create("IfcComplexProperty", Name=key, UsageName="[]", HasProperties=subproperties) 28 | ifc_properties.append(ifc_property) 29 | 30 | elif isinstance(value, (str, float, bool, int)): 31 | nominal_value = model.create_value(value) 32 | ifc_property = model.create("IfcPropertySingleValue", Name=key, NominalValue=nominal_value) 33 | ifc_properties.append(ifc_property) 34 | else: 35 | raise ValueError(f"Unsupported value type: {type(value)}") 36 | 37 | return ifc_properties 38 | 39 | 40 | def from_dict_to_pset(model: Model, properties: dict, name: str = None) -> Base: 41 | ifc_properties = from_dict_to_ifc_properties(model, properties) 42 | pset = model.create("IfcPropertySet", Name=name, HasProperties=ifc_properties) 43 | return pset 44 | 45 | 46 | def from_psets_to_dict(element: Base) -> dict: 47 | psets = get_psets(element.entity, psets_only=True) 48 | 49 | def _convert_property(property): 50 | if isinstance(property, dict): 51 | if property.get("UsageName", None) == "[]": 52 | return [_convert_property(value) for value in property["properties"].values()] 53 | elif property.get("UsageName", None) == "{}": 54 | return {key: _convert_property(value) for key, value in property["properties"].items()} 55 | else: 56 | if "id" in property: 57 | del property["id"] 58 | return {key: _convert_property(value) for key, value in property.items()} 59 | else: 60 | return property 61 | 62 | return _convert_property(psets) 63 | -------------------------------------------------------------------------------- /src/compas_ifc/conversions/representation.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains functions for converting geometry representations between COMPAS and IFC. 3 | """ 4 | 5 | from typing import Union 6 | 7 | from compas.datastructures import Mesh 8 | from compas.geometry import Box 9 | from compas.geometry import Brep 10 | from compas.geometry import Cone 11 | from compas.geometry import Cylinder 12 | from compas.geometry import Shape 13 | from compas.geometry import Sphere 14 | 15 | from compas_ifc.conversions.brep import brep_to_IfcAdvancedBrep 16 | from compas_ifc.conversions.mesh import mesh_to_IfcFaceBasedSurfaceModel 17 | from compas_ifc.conversions.shapes import box_to_IfcBlock 18 | from compas_ifc.conversions.shapes import cone_to_IfcRightCircularCone 19 | from compas_ifc.conversions.shapes import cylinder_to_IfcRightCircularCylinder 20 | from compas_ifc.conversions.shapes import sphere_to_IfcSphere 21 | from compas_ifc.entities.extensions import IfcProduct 22 | from compas_ifc.model import Model 23 | 24 | REPRESENTATION_CACHE = {} 25 | 26 | 27 | def assign_body_representation(entity: IfcProduct, representation: Union[Shape, Mesh, Brep]): 28 | """ 29 | Assign a representation to an entity. 30 | """ 31 | 32 | model: Model = entity.model 33 | 34 | if id(representation) in REPRESENTATION_CACHE: 35 | entity.Representation = REPRESENTATION_CACHE[id(representation)] 36 | return 37 | 38 | # Convert COMPAS geometries to IFC corresponding representation 39 | if isinstance(representation, Shape): 40 | if isinstance(representation, Box): 41 | ifc_csg_primitive3d = box_to_IfcBlock(model, representation) 42 | elif isinstance(representation, Sphere): 43 | ifc_csg_primitive3d = sphere_to_IfcSphere(model, representation) 44 | elif isinstance(representation, Cone): 45 | ifc_csg_primitive3d = cone_to_IfcRightCircularCone(model, representation) 46 | elif isinstance(representation, Cylinder): 47 | ifc_csg_primitive3d = cylinder_to_IfcRightCircularCylinder(model, representation) 48 | else: 49 | raise NotImplementedError(f"Conversion of {type(representation)} to IFC not implemented.") 50 | 51 | ifc_csg_solid = model.create("IfcCsgSolid", TreeRootExpression=ifc_csg_primitive3d) 52 | 53 | items = [ifc_csg_solid] 54 | representation_type = "CSG" 55 | 56 | elif isinstance(representation, Mesh): 57 | ifc_representation = mesh_to_IfcFaceBasedSurfaceModel(model, representation) 58 | representation_type = "SurfaceModel" 59 | items = [ifc_representation] 60 | 61 | elif isinstance(representation, Brep): 62 | if model.file.use_occ: 63 | try: 64 | items = brep_to_IfcAdvancedBrep(model, representation) 65 | representation_type = "SolidModel" 66 | except Exception as e: 67 | print(f"WARNING BREP conversion failed: {e}") 68 | items = [] 69 | representation_type = "SurfaceModel" 70 | 71 | else: 72 | mesh, _ = representation.to_tesselation() 73 | ifc_representation = mesh_to_IfcFaceBasedSurfaceModel(model, mesh) 74 | representation_type = "SurfaceModel" 75 | items = [ifc_representation] 76 | 77 | else: 78 | raise NotImplementedError(f"Conversion of {type(representation)} to IFC not implemented.") 79 | 80 | # QUESTION: When using OCCBrep from Extrusion, can we still keep the extrusion data? 81 | 82 | ifc_shape_representation = model.create( 83 | "IfcShapeRepresentation", 84 | ContextOfItems=model.file.default_body_context, 85 | RepresentationIdentifier="Body", 86 | RepresentationType=representation_type, 87 | Items=items, 88 | ) 89 | 90 | ifc_product_definition_shape = model.create( 91 | "IfcProductDefinitionShape", 92 | Representations=[ifc_shape_representation], 93 | ) 94 | 95 | entity.Representation = ifc_product_definition_shape 96 | REPRESENTATION_CACHE[id(representation)] = ifc_product_definition_shape 97 | 98 | # TODO: should not overwrite all property sets here 99 | # TODO: alternative 1: restructure the metadata, remove duplicated info like vertices 100 | # TODO: alternative 2: save data as compact json string 101 | # entity.property_sets = { 102 | # "Pset_COMPAS": { 103 | # "representation_id": ifc_product_definition_shape.id(), 104 | # "compas_data": json.loads(representation.to_jsonstring()), 105 | # } 106 | # } 107 | 108 | 109 | def read_representation(model: Model, entity: IfcProduct): 110 | pass 111 | 112 | 113 | if __name__ == "__main__": 114 | import compas 115 | from compas.geometry import Frame 116 | 117 | model = Model.template(schema="IFC2X3", unit="m") 118 | 119 | # geometry = Box.from_width_height_depth(1, 1, 1) 120 | geometry = Mesh.from_ply(compas.get("bunny.ply")) 121 | # geometry = Mesh.from_meshgrid(5, 2, 5, 2) 122 | 123 | product = model.create(geometry=geometry, parent=model.building_storeys[0], name="test", frame=Frame.worldXY()) 124 | 125 | model.show() 126 | 127 | model.save("temp/representations/test.ifc") 128 | -------------------------------------------------------------------------------- /src/compas_ifc/conversions/shapes.py: -------------------------------------------------------------------------------- 1 | import ifcopenshell 2 | from compas.geometry import Box 3 | from compas.geometry import Cone 4 | from compas.geometry import Cylinder 5 | from compas.geometry import Sphere 6 | 7 | from compas_ifc.conversions.frame import create_IfcAxis2Placement3D 8 | from compas_ifc.entities.base import Base 9 | from compas_ifc.model import Model 10 | 11 | 12 | def create_IfcShapeRepresentation(file: ifcopenshell.file, item: ifcopenshell.entity_instance, context: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: 13 | """ 14 | Create an IFC Shape Representation from an IFC item and a context. 15 | """ 16 | return file.create_entity( 17 | "IfcShapeRepresentation", 18 | ContextOfItems=context, 19 | RepresentationIdentifier="Body", 20 | RepresentationType="SolidModel", 21 | Items=[item], 22 | ) 23 | 24 | 25 | def box_to_IfcBlock(model: Model, box: Box) -> Base: 26 | """ 27 | Convert a COMPAS box to an IFC Block. 28 | """ 29 | pt = box.frame.point.copy() 30 | pt -= [box.xsize / 2, box.ysize / 2, box.zsize / 2] 31 | print(pt) 32 | return model.create( 33 | "IfcBlock", 34 | Position=create_IfcAxis2Placement3D(model, pt, box.frame.zaxis, box.frame.xaxis), 35 | XLength=box.xsize, 36 | YLength=box.ysize, 37 | ZLength=box.zsize, 38 | ) 39 | 40 | 41 | def sphere_to_IfcSphere(model: Model, sphere: Sphere) -> ifcopenshell.entity_instance: 42 | """ 43 | Convert a COMPAS sphere to an IFC Sphere. 44 | """ 45 | return model.create( 46 | "IfcSphere", 47 | Position=create_IfcAxis2Placement3D(model, sphere.base), 48 | Radius=sphere.radius, 49 | ) 50 | 51 | 52 | def cone_to_IfcRightCircularCone(model: Model, cone: Cone) -> ifcopenshell.entity_instance: 53 | """ 54 | Convert a COMPAS cone to an IFC Cone. 55 | """ 56 | plane = cone.circle.plane 57 | return model.create( 58 | "IfcRightCircularCone", 59 | Position=create_IfcAxis2Placement3D(model, plane.point, plane.normal), 60 | Height=cone.height, 61 | BottomRadius=cone.circle.radius, 62 | ) 63 | 64 | 65 | def cylinder_to_IfcRightCircularCylinder(model: Model, cylinder: Cylinder) -> ifcopenshell.entity_instance: 66 | """ 67 | Convert a COMPAS cylinder to an IFC Cylinder. 68 | """ 69 | plane = cylinder.circle.plane 70 | return model.create( 71 | "IfcRightCircularCylinder", 72 | Position=create_IfcAxis2Placement3D(model, plane.point, plane.normal), 73 | Height=cylinder.height, 74 | Radius=cylinder.circle.radius, 75 | ) 76 | 77 | 78 | def occ_cylinder_to_ifc_cylindrical_surface(model: Model, occ_cylinder): 79 | location = occ_cylinder.Location().Coord() 80 | xdir = occ_cylinder.XAxis().Direction().Coord() 81 | zdir = occ_cylinder.Axis().Direction().Coord() 82 | IfcAxis2Placement3D = create_IfcAxis2Placement3D(model, location, zdir, xdir) 83 | return model.create("IfcCylindricalSurface", Position=IfcAxis2Placement3D, Radius=occ_cylinder.Radius()) 84 | 85 | 86 | if __name__ == "__main__": 87 | model = Model() 88 | print(create_IfcAxis2Placement3D(model)) 89 | 90 | box = Box(10, 10, 10) 91 | box_to_IfcBlock(model, box).print_attributes(max_depth=5) 92 | -------------------------------------------------------------------------------- /src/compas_ifc/conversions/unit.py: -------------------------------------------------------------------------------- 1 | def IfcCompoundPlaneAngleMeasure_to_degrees(angle_components): 2 | if len(angle_components) != 4: 3 | raise ValueError("Input must be a list or tuple of four elements: [degrees, minutes, seconds, millionths_of_second]") 4 | 5 | degrees, minutes, seconds, millionths = angle_components 6 | 7 | # Determine the sign based on the degrees component 8 | sign = -1 if degrees < 0 else 1 9 | 10 | # Use absolute values to avoid negative components in calculation 11 | degrees = abs(degrees) 12 | minutes = abs(minutes) 13 | seconds = abs(seconds) 14 | millionths = abs(millionths) 15 | 16 | # Convert millionths of a second to fractional seconds 17 | fractional_seconds = millionths / 1_000_000 18 | 19 | # Total seconds including the fractional part 20 | total_seconds = seconds + fractional_seconds 21 | 22 | # Convert everything to decimal degrees 23 | decimal_degrees = sign * (degrees + (minutes / 60) + (total_seconds / 3600)) 24 | 25 | return decimal_degrees 26 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ******************************************************************************** 3 | entities 4 | ******************************************************************************** 5 | """ 6 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcBuilding.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from compas_ifc.entities.generated.IFC4 import IfcBuilding 5 | from compas_ifc.entities.generated.IFC4 import IfcBuildingElement 6 | from compas_ifc.entities.generated.IFC4 import IfcBuildingStorey 7 | from compas_ifc.entities.generated.IFC4 import IfcGeographicElement 8 | else: 9 | IfcBuilding = object 10 | 11 | 12 | class IfcBuilding(IfcBuilding): 13 | """Extension class for :class:`IfcBuilding`. 14 | 15 | Attributes 16 | ---------- 17 | building_elements : list[:class:`IfcBuildingElement`] 18 | The building elements of the building. 19 | geographic_elements : list[:class:`IfcGeographicElement`] 20 | The geographic elements of the building. 21 | storeys : list[:class:`IfcBuildingStorey`] 22 | The storeys of the building. 23 | 24 | """ 25 | 26 | @property 27 | def building_elements(self) -> list["IfcBuildingElement"]: 28 | return self.children_by_type("IfcBuildingElement", recursive=True) 29 | 30 | @property 31 | def geographic_elements(self) -> list["IfcGeographicElement"]: 32 | return self.children_by_type("IfcGeographicElement", recursive=True) 33 | 34 | @property 35 | def storeys(self) -> list["IfcBuildingStorey"]: 36 | return self.children_by_type("IfcBuildingStorey", recursive=True) 37 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcContext.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | import ifcopenshell.guid 4 | from ifcopenshell.api import run 5 | from ifcopenshell.util.element import get_psets 6 | 7 | if TYPE_CHECKING: 8 | from compas_ifc.entities.generated.IFC4 import IfcContext 9 | else: 10 | IfcContext = object 11 | 12 | 13 | class IfcContext(IfcContext): 14 | """Extension class for :class:`IfcContext`. 15 | 16 | Attributes 17 | ---------- 18 | properties : dict 19 | The properties of the context. 20 | 21 | """ 22 | 23 | _psetsmap = {} 24 | 25 | @property 26 | def properties(self): 27 | psets = get_psets(self.entity, psets_only=True) 28 | for pset in psets.values(): 29 | del pset["id"] 30 | return psets 31 | 32 | @properties.setter 33 | def properties(self, psets): 34 | if id(self.file) not in self._psetsmap: 35 | self._psetsmap[id(self.file)] = {} 36 | 37 | psetsmap = self._psetsmap[id(self.file)] 38 | 39 | for name, properties in psets.items(): 40 | if id(properties) in psetsmap: 41 | pset = psetsmap[id(properties)] 42 | # TODO: Check if relation already exists 43 | self.file._create_entity( 44 | "IfcRelDefinesByProperties", 45 | GlobalId=ifcopenshell.guid.new(), 46 | OwnerHistory=self.file.default_owner_history, 47 | RelatingPropertyDefinition=pset, 48 | RelatedObjects=[self.entity], 49 | ) 50 | 51 | else: 52 | pset = run("pset.add_pset", self.file._file, product=self.entity, name=name) 53 | psetsmap[id(properties)] = pset 54 | for key, value in properties.items(): 55 | if not isinstance(value, (str, int, float)): 56 | properties[key] = str(value) 57 | run("pset.edit_pset", self.file._file, pset=pset, properties=properties) 58 | # TODO: remove unused psets 59 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcElement.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from compas_ifc.entities.generated.IFC4 import IfcElement 5 | else: 6 | IfcElement = object 7 | 8 | 9 | class IfcElement(IfcElement): 10 | """Extension class for :class:`IfcElement`. 11 | 12 | Attributes 13 | ---------- 14 | parent : :class:`IfcElement` 15 | The parent element of the element. 16 | """ 17 | 18 | @property 19 | def parent(self): 20 | relations = self.ContainedInStructure() 21 | if relations: 22 | return relations[0].RelatingStructure 23 | else: 24 | return super().parent 25 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcObject.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from ifcopenshell.util.element import get_psets 4 | 5 | from compas_ifc.conversions.pset import from_dict_to_pset 6 | from compas_ifc.conversions.pset import from_psets_to_dict 7 | 8 | if TYPE_CHECKING: 9 | from compas_ifc.entities.generated.IFC4 import IfcObject 10 | else: 11 | IfcObject = object 12 | 13 | 14 | class IfcObject(IfcObject): 15 | """Extension class for :class:`IfcObject`. 16 | 17 | Attributes 18 | ---------- 19 | properties : dict 20 | The property sets of the object. 21 | quantities : dict 22 | The quantity sets of the object. 23 | """ 24 | 25 | _psetsmap = {} 26 | 27 | @property 28 | def psetsmap(self): 29 | if id(self.file) not in self._psetsmap: 30 | self._psetsmap[id(self.file)] = {} 31 | return self._psetsmap[id(self.file)] 32 | 33 | @property 34 | def property_sets(self): 35 | return from_psets_to_dict(self) 36 | 37 | @property_sets.setter 38 | def property_sets(self, psets): 39 | for name, pset in psets.items(): 40 | if id(pset) in self.psetsmap: 41 | ifc_property_set = self.psetsmap[id(pset)] 42 | else: 43 | ifc_property_set = from_dict_to_pset(self.file, pset, name) 44 | self.psetsmap[id(pset)] = ifc_property_set 45 | # TODO: remove unused psets 46 | 47 | self.file.create( 48 | "IfcRelDefinesByProperties", 49 | OwnerHistory=self.file.default_owner_history, 50 | RelatingPropertyDefinition=ifc_property_set, 51 | RelatedObjects=[self], 52 | ) 53 | 54 | @property 55 | def quantity_sets(self): 56 | qtos = get_psets(self.entity, qtos_only=True) 57 | for qto in qtos.values(): 58 | del qto["id"] 59 | return qtos 60 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcObjectDefinition.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from ifcopenshell.util.element import get_material 4 | 5 | if TYPE_CHECKING: 6 | from compas_ifc.entities.generated.IFC4 import IfcObjectDefinition 7 | else: 8 | IfcObjectDefinition = object 9 | 10 | 11 | class IfcObjectDefinition(IfcObjectDefinition): 12 | """Extension class for :class:`IfcObjectDefinition`. 13 | 14 | Attributes 15 | ---------- 16 | parent : :class:`IfcObjectDefinition` 17 | The parent object definition of the object definition. 18 | children : list[:class:`IfcObjectDefinition`] 19 | The children object definitions of the object definition. 20 | material : :class:`IfcMaterial` 21 | The material of the object definition. 22 | """ 23 | 24 | def __repr__(self): 25 | return '<#{} {} "{}">'.format(self.entity.id(), self.__class__.__name__, self.Name) 26 | 27 | @property 28 | def parent(self): 29 | relations = self.Decomposes() 30 | if relations: 31 | return relations[0].RelatingObject 32 | else: 33 | return None 34 | 35 | @property 36 | def children(self): 37 | return sum([relation.RelatedObjects for relation in self.IsDecomposedBy()], []) 38 | 39 | @property 40 | def descendants(self): 41 | descendants = [] 42 | for child in self.children: 43 | descendants.append(child) 44 | descendants.extend(child.descendants) 45 | return descendants 46 | 47 | @property 48 | def material(self): 49 | material = get_material(self.entity) 50 | if material: 51 | return self.file.from_entity(material) 52 | else: 53 | return None 54 | 55 | def children_by_type(self, type_name, recursive=False): 56 | if not recursive: 57 | return [child for child in self.children if child.is_a(type_name)] 58 | else: 59 | return [child for child in self.descendants if child.is_a(type_name)] 60 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcProduct.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from compas_ifc.conversions.frame import IfcLocalPlacement_to_frame 4 | from compas_ifc.conversions.frame import assign_entity_frame 5 | from compas_ifc.conversions.representation import assign_body_representation 6 | 7 | if TYPE_CHECKING: 8 | from compas_ifc.entities.generated.IFC4 import IfcProduct 9 | else: 10 | IfcProduct = object 11 | 12 | 13 | class IfcProduct(IfcProduct): 14 | """Extension class for :class:`IfcProduct`. 15 | 16 | Attributes 17 | ---------- 18 | style : :class:`IfcStyle` 19 | The style of the product. 20 | geometry : :class:`compas_ifc.brep.TessellatedBrep` 21 | The geometry of the product. (OCCBrep is using COMPAS OCC) 22 | frame : :class:`compas.geometry.Frame` 23 | The frame of the product. 24 | """ 25 | 26 | @property 27 | def style(self): 28 | return self.file.get_preloaded_style(self) 29 | 30 | @property 31 | def geometry(self): 32 | if not getattr(self, "_geometry", None): 33 | self._geometry = self.file.get_preloaded_geometry(self) 34 | if self._geometry: 35 | self._geometry.name = self.Name 36 | if self.frame and self._geometry: 37 | # NOTE: preloaded geometry is pre-transformed because of boolean. 38 | # The pre-transformation is not necessarily the same as the frame of entity. 39 | # Therefore, we need to re-transform the geometry back to its original location. 40 | T = self.frame.to_transformation() 41 | self._geometry.transform(T.inverse()) 42 | return self._geometry 43 | 44 | @geometry.setter 45 | def geometry(self, geometry): 46 | self._geometry = geometry 47 | assign_body_representation(self, geometry) 48 | # TODO: delete existing representation 49 | 50 | @property 51 | def frame(self): 52 | if not getattr(self, "_frame", None): 53 | if self.ObjectPlacement: 54 | self._frame = IfcLocalPlacement_to_frame(self.ObjectPlacement) 55 | else: 56 | self._frame = None 57 | 58 | # print(self._frame) 59 | return self._frame 60 | 61 | @frame.setter 62 | def frame(self, frame): 63 | self._frame = frame 64 | # TODO: consider parent frame 65 | assign_entity_frame(self, frame) 66 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcProject.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from compas_ifc.entities.generated.IFC4 import IfcProject 5 | else: 6 | IfcProject = object 7 | 8 | 9 | class IfcProject(IfcProject): 10 | """Extension class for :class:`IfcProject`. 11 | 12 | Attributes 13 | ---------- 14 | sites : list[:class:`IfcSite`] 15 | The sites of the project. 16 | buildings : list[:class:`IfcBuilding`] 17 | The buildings of the project. 18 | building_elements : list[:class:`IfcBuildingElement`] 19 | The building elements of the project. 20 | geographic_elements : list[:class:`IfcGeographicElement`] 21 | The geographic elements of the project. 22 | contexts : list[:class:`IfcContext`] 23 | The contexts of the project. 24 | units : list[:class:`IfcUnit`] 25 | The units of the project. 26 | length_unit : :class:`IfcUnit` 27 | The length unit of the project. 28 | length_scale : float 29 | The length scale of the project. 30 | frame : :class:`compas.geometry.Frame` 31 | The frame of the project. 32 | north : :class:`compas.geometry.Vector` 33 | The north vector of the project. 34 | """ 35 | 36 | @property 37 | def sites(self): 38 | return self.children_by_type("IfcSite", recursive=True) 39 | 40 | @property 41 | def buildings(self): 42 | return self.children_by_type("IfcBuilding", recursive=True) 43 | 44 | @property 45 | def building_elements(self): 46 | return self.children_by_type("IfcBuildingElement", recursive=True) 47 | 48 | @property 49 | def geographic_elements(self): 50 | return self.children_by_type("IfcGeographicElement", recursive=True) 51 | 52 | @property 53 | def contexts(self): 54 | from compas.geometry import Vector 55 | 56 | from compas_ifc.conversions.frame import IfcAxis2Placement3D_to_frame 57 | 58 | contexts = [] 59 | 60 | for context in self.RepresentationContexts: 61 | north = Vector(*context.TrueNorth.DirectionRatios) if context.TrueNorth else None 62 | wcs = IfcAxis2Placement3D_to_frame(context.WorldCoordinateSystem) 63 | contexts.append( 64 | { 65 | "identifier": context.ContextIdentifier, 66 | "type": context.ContextType, 67 | "precision": context.Precision, 68 | "dimension": context.CoordinateSpaceDimension, 69 | "north": north, 70 | "wcs": wcs, 71 | } 72 | ) 73 | return contexts 74 | 75 | @property 76 | def units(self): 77 | units = [] 78 | units_in_context = self.UnitsInContext or self.file.get_entities_by_type("IfcUnitAssignment")[0] 79 | for unit in units_in_context.Units: 80 | if unit.is_a("IfcSIUnit") or unit.is_a("IfcConversionBasedUnit"): 81 | units.append(unit) 82 | return units 83 | 84 | @property 85 | def length_unit(self): 86 | for unit in self.units: 87 | if unit["UnitType"] == "LENGTHUNIT": 88 | return unit 89 | 90 | @property 91 | def length_scale(self): 92 | unit = self.length_unit 93 | if unit: 94 | if unit.Name == "METRE" and not unit.Prefix: 95 | return 1.0 96 | if unit.Name == "METRE" and unit.Prefix == "CENTI": 97 | return 1e-2 98 | if unit.Name == "METRE" and unit.Prefix == "MILLI": 99 | return 1e-3 100 | return 1.0 101 | 102 | @property 103 | def frame(self): 104 | for context in self.contexts: 105 | if context["type"] == "Model": 106 | return context["wcs"] 107 | 108 | @property 109 | def north(self): 110 | for context in self.contexts: 111 | if context["type"] == "Model": 112 | return context["north"] 113 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcSite.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from compas_ifc.conversions.unit import IfcCompoundPlaneAngleMeasure_to_degrees 4 | 5 | if TYPE_CHECKING: 6 | from compas_ifc.entities.generated.IFC4 import IfcSite 7 | else: 8 | IfcSite = object 9 | 10 | 11 | class IfcSite(IfcSite): 12 | """Extension class for :class:`IfcSite`. 13 | 14 | Attributes 15 | ---------- 16 | buildings : list[:class:`IfcBuilding`] 17 | The buildings of the site. 18 | building_elements : list[:class:`IfcBuildingElement`] 19 | The building elements of the site. 20 | geographic_elements : list[:class:`IfcGeographicElement`] 21 | The geographic elements of the site. 22 | location : tuple[float, float] 23 | The location of the site. In degrees (latitude, longitude). 24 | 25 | """ 26 | 27 | @property 28 | def buildings(self): 29 | return self.children_by_type("IfcBuilding", recursive=True) 30 | 31 | @property 32 | def building_elements(self): 33 | return self.children_by_type("IfcBuildingElement", recursive=True) 34 | 35 | @property 36 | def geographic_elements(self): 37 | return self.children_by_type("IfcGeographicElement", recursive=True) 38 | 39 | @property 40 | def location(self): 41 | if self.RefLatitude and self.RefLongitude: 42 | return IfcCompoundPlaneAngleMeasure_to_degrees(self.RefLatitude), IfcCompoundPlaneAngleMeasure_to_degrees(self.RefLongitude) 43 | else: 44 | return None 45 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcSpatialElement.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from compas_ifc.entities.generated.IFC4 import IfcSpatialElement 5 | else: 6 | IfcSpatialElement = object 7 | 8 | 9 | class IfcSpatialElement(IfcSpatialElement): 10 | """Extension class for :class:`IfcSpatialElement`. 11 | 12 | Attributes 13 | ---------- 14 | children : list[:class:`IfcSpatialElement`] 15 | The children spatial elements of the spatial element. 16 | """ 17 | 18 | @property 19 | def children(self): 20 | children = super().children 21 | children += sum([relation.RelatedElements for relation in self.ContainsElements()], []) 22 | # NOTE: in IFC2X3 this is in IfcSpatialStructureElement 23 | return list(set(children)) 24 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/IfcSpatialStructureElement.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from compas_ifc.entities.generated.IFC2X3 import IfcSpatialStructureElement 5 | else: 6 | IfcSpatialStructureElement = object 7 | 8 | 9 | class IfcSpatialStructureElement(IfcSpatialStructureElement): 10 | """Extension class for :class:`IfcSpatialStructureElement`. 11 | 12 | Attributes 13 | ---------- 14 | children : list[:class:`IfcSpatialElement`] 15 | The children spatial elements of the spatial structure element. 16 | """ 17 | 18 | @property 19 | def children(self): 20 | children = super().children 21 | children += sum([relation.RelatedElements for relation in self.ContainsElements()], []) 22 | # NOTE: in IFC4 this is in IfcSpatialElement 23 | return list(set(children)) 24 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/extensions/__init__.py: -------------------------------------------------------------------------------- 1 | from .IfcObjectDefinition import IfcObjectDefinition # noqa: F401 2 | from .IfcObject import IfcObject # noqa: F401 3 | from .IfcContext import IfcContext # noqa: F401 4 | from .IfcElement import IfcElement # noqa: F401 5 | from .IfcSpatialElement import IfcSpatialElement # noqa: F401 6 | from .IfcSpatialStructureElement import IfcSpatialStructureElement # noqa: F401 7 | from .IfcProduct import IfcProduct # noqa: F401 8 | from .IfcProject import IfcProject # noqa: F401 9 | from .IfcSite import IfcSite # noqa: F401 10 | from .IfcBuilding import IfcBuilding # noqa: F401 11 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/generator_enumeration_type.py: -------------------------------------------------------------------------------- 1 | import ifcopenshell 2 | 3 | schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC4") 4 | 5 | 6 | template = """from enum import Enum 7 | 8 | class CLASS_NAME(Enum): 9 | \"\"\"Wrapper class for CLASS_NAME.\"\"\" 10 | """ 11 | 12 | enum_template = " NAME=INDEX\n" 13 | 14 | if __name__ == "__main__": 15 | init_string = "" 16 | 17 | for declaration in schema.declarations(): 18 | class_string = template 19 | 20 | if declaration.as_enumeration_type(): 21 | name = declaration.name() 22 | class_string = class_string.replace("CLASS_NAME", name) 23 | 24 | for index, item in enumerate(declaration.enumeration_items()): 25 | enum_string = enum_template 26 | enum_string = enum_string.replace("NAME", item) 27 | enum_string = enum_string.replace("INDEX", str(index)) 28 | class_string += enum_string 29 | 30 | print(class_string) 31 | # break 32 | 33 | with open(f"src/compas_ifc/entities/generated/{name}.py", "w") as f: 34 | f.write(class_string) 35 | -------------------------------------------------------------------------------- /src/compas_ifc/entities/generator_type_definition.py: -------------------------------------------------------------------------------- 1 | import ifcopenshell 2 | 3 | schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC4") 4 | 5 | init_template = "from .CLASS_NAME import CLASS_NAME\n" 6 | 7 | template = """class CLASS_NAME(TYPE_NAME): 8 | \"\"\"Wrapper class for CLASS_NAME.\"\"\" 9 | """ 10 | 11 | TYPE_MAP = { 12 | "DOUBLE": "float", 13 | "INT": "int", 14 | "STRING": "str", 15 | "LOGICAL": "bool", 16 | "BOOL": "bool", 17 | "BINARY": "bytes", 18 | } 19 | 20 | 21 | if __name__ == "__main__": 22 | init_string = "" 23 | 24 | for declaration in schema.declarations(): 25 | class_string = template 26 | 27 | if declaration.as_type_declaration(): 28 | name = declaration.name() 29 | 30 | print(name) 31 | 32 | class_string = class_string.replace("CLASS_NAME", name) 33 | 34 | ifc_type = declaration.argument_types()[0] 35 | if ifc_type.startswith("AGGREGATE OF"): 36 | value_type = ifc_type.split("OF ")[1] 37 | if value_type == "ENTITY INSTANCE": 38 | python_type = "list" # TODO: handle this 39 | else: 40 | python_type = f"list[{TYPE_MAP[value_type]}]" 41 | else: 42 | python_type = TYPE_MAP[ifc_type] 43 | 44 | class_string = class_string.replace("TYPE_NAME", python_type) 45 | 46 | print(class_string) 47 | # break 48 | 49 | with open(f"src/compas_ifc/entities/generated/{name}.py", "w") as f: 50 | f.write(class_string) 51 | 52 | # with open(f"src/compas_ifc/entities/generated/__init__.py", "w") as f: 53 | # f.write(init_string) 54 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | 5 | from compas_invocations2 import build 6 | from compas_invocations2 import docs 7 | from compas_invocations2 import style 8 | from compas_invocations2 import tests 9 | from invoke import Collection 10 | 11 | ns = Collection( 12 | docs.help, 13 | style.check, 14 | style.lint, 15 | style.format, 16 | docs.docs, 17 | docs.linkcheck, 18 | tests.test, 19 | tests.testdocs, 20 | tests.testcodeblocks, 21 | build.prepare_changelog, 22 | build.clean, 23 | build.release, 24 | build.build_ghuser_components, 25 | ) 26 | ns.configure( 27 | { 28 | "base_folder": os.path.dirname(__file__), 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /temp/PLACEHOLDER: -------------------------------------------------------------------------------- 1 | # container for temorary files 2 | # these will be ignored by the version control system 3 | -------------------------------------------------------------------------------- /tests/test_placeholder.py: -------------------------------------------------------------------------------- 1 | def test_placeholder(): 2 | assert True 3 | --------------------------------------------------------------------------------