Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ee07fb8cc | |||
| 36cf458498 | |||
| e7c25bf99f | |||
| 50a3c8c2f3 | |||
| f18298d1fd | |||
| d4770bdee3 | |||
| 1fb107c361 | |||
| 0fa1e2d585 | |||
| dc13f4c5cc | |||
| d197c3f49a | |||
| c05200a5c6 | |||
| 235bb198da | |||
| 40f332d674 | |||
| e8a272dc97 | |||
| 0b308770a6 | |||
| 96c8323a06 | |||
| 25573cd0e0 | |||
| ff3c7786f6 | |||
| 5f4dfcf215 | |||
| 9a89555a53 | |||
| fc69a2acbe | |||
| 85a2a5ad12 | |||
| 49c3d28a0b | |||
| a8f20b2c6b | |||
| 66fa1cedd9 | |||
| eb971d3d73 | |||
| a30a796c07 | |||
| 9e46bacc12 | |||
| b33378e135 | |||
| d42e19f491 | |||
| 2597827ab3 | |||
| 3f71efea6d | |||
| 2d279b7a7f | |||
| e5fe039fd0 | |||
| 9e14829cc1 | |||
| e867602d9d | |||
| aecc3d56b8 | |||
| e3f5d3d2b7 | |||
| 65f8b1a532 | |||
| 96b4df10ba | |||
| 98b56aea42 | |||
| ba6ba29da8 | |||
| 1112b102f7 | |||
| 04b8172ea9 | |||
| f5f3edbc79 | |||
| 30029e3dc7 | |||
| ed836f5b4a | |||
| a88b708442 | |||
| 764c3bbb85 | |||
| 382d3f1f9f | |||
| c92f607a15 | |||
| b57b7080fd | |||
| 860ff4e513 | |||
| 3546f13593 | |||
| 663966e08d | |||
| f077ebdc81 | |||
| eb888fd116 | |||
| 40b6b3bf17 | |||
| 2ef2f923d0 | |||
| 75fab58831 | |||
| ce26c6e19b | |||
| 8728fa38b9 | |||
| 3037795199 | |||
| 39ee9cd8b1 | |||
| b375bbf558 | |||
| f2cae79dea | |||
| aadc77f6b6 | |||
| 35223d968c | |||
| e106dc4502 | |||
| 2995b73c2d | |||
| 770bc6ea61 | |||
| f5736a7144 | |||
| b4120b1cab | |||
| f44ca67ab9 | |||
| 77eae1c8e8 | |||
| 02d79f0687 | |||
| 83d364574a | |||
| 688d2b7bd1 | |||
| bf35239979 | |||
| ca8e014647 | |||
| b5f63ec4f3 | |||
| aec2141128 | |||
| 778344c804 | |||
| 3ede9b254f | |||
| 8df40dc4a6 | |||
| 6569d0031f | |||
| bfaa61e592 | |||
| 1a09ce40e9 | |||
| ff251b7a6a | |||
| dbce57ffc5 | |||
| 8ff9bcef32 | |||
| 82b2006686 | |||
| ca4181c714 | |||
| 3928dd7c9c | |||
| 925964ee2a |
@@ -7,34 +7,26 @@
|
||||
|
||||
# output directories
|
||||
/cmake/out/
|
||||
/docs/out/
|
||||
/src/examples/out/
|
||||
/src/ifcmax/out/
|
||||
/src/ifcwrap/out/
|
||||
/src/qtviewer/out/
|
||||
|
||||
|
||||
/win/BuildDepsCache*.txt
|
||||
# IfcExpressParser residue
|
||||
/src/ifcexpressparser/express_parser.py
|
||||
|
||||
# General Python residue
|
||||
__pycache__
|
||||
*.py.bak
|
||||
|
||||
# Visual Studio Code files
|
||||
.vscode
|
||||
.vs
|
||||
|
||||
# PyCharm files
|
||||
.idea
|
||||
|
||||
#Virtual Env Files
|
||||
Pipfile
|
||||
Pipfile.lock
|
||||
|
||||
# Docs
|
||||
/docs/output
|
||||
/docs/rst_files
|
||||
/docs/doxygen
|
||||
/src/ifcblenderexport/docs/_build
|
||||
/docs/cpp-api/output
|
||||
|
||||
# gettext binary translation files
|
||||
*.mo
|
||||
|
||||
@@ -10,4 +10,7 @@
|
||||
url = https://github.com/IfcOpenShell/svgfill
|
||||
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
|
||||
path = src/ifcopenshell-python/test/Sample-BIM-Files
|
||||
url = https://github.com/IfcOpenShell/ids-test-files
|
||||
url = https://github.com/IfcOpenShell/ids-test-files
|
||||
[submodule "docs/cpp-api/assets/doxygen-awesome-css"]
|
||||
path = docs/cpp-api/assets/doxygen-awesome-css
|
||||
url = https://github.com/jothepro/doxygen-awesome-css.git
|
||||
|
||||
@@ -7,13 +7,13 @@ IfcOpenShell
|
||||
</p>
|
||||
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
|
||||
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x2, and IFC4x3. Extensive geometric support
|
||||
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x2, and [IFC4x3 Add2]. Extensive geometric support
|
||||
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
|
||||
is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
In addition to a C++ and Python API, IfcOpenShell comes with an ecosystem of tools, notably including IfcConvert (an application to convert IFC models to
|
||||
other formats), the BlenderBIM Add-on (an add-on to Blender providing a graphical IFC authoring platform), and many
|
||||
other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
|
||||
In addition to a C++ and Python API, IfcOpenShell comes with an ecosystem of tools, notably including IfcConvert (an application
|
||||
to convert IFC models to other formats), the BlenderBIM Add-on (an add-on to Blender providing a graphical IFC authoring platform),
|
||||
and many other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
|
||||
|
||||
For more information, see:
|
||||
|
||||
@@ -32,8 +32,8 @@ For more information, see:
|
||||
| Anaconda Daily Build | [](https://anaconda.org/ifcopenshell/ifcopenshell) |
|
||||
| Anaconda v0.7.0 Stable | [](https://anaconda.org/conda-forge/ifcopenshell) |
|
||||
| PyPi Daily Build | [](https://pypi.org/project/ifcopenshell/) |
|
||||
| ArchLinux AUR Package Stable | [](https://aur.archlinux.org/packages/ifcopenshell) |
|
||||
| ArchLinux AUR Package git | [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| ArchLinux AUR Package Stable | [](https://aur.archlinux.org/packages/ifcopenshell) |
|
||||
| ArchLinux AUR Package git | [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| BlenderBIM Add-on Chocolatey (under moderation) | [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| Sponsor development on OpenCollective | [](https://opencollective.com/opensourcebim/) |
|
||||
| Docker hub | [](https://hub.docker.com/r/aecgeeks/ifcopenshell) |
|
||||
@@ -44,7 +44,7 @@ Contents
|
||||
Those marked with an asterisk are part of IfcOpenShell.
|
||||
|
||||
| Name | Description | License |
|
||||
| ------------------------- | ----------------------------------------------------------------------| ------------------- |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ------------------- |
|
||||
| bcf | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later |
|
||||
| blenderbim | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later |
|
||||
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later |
|
||||
@@ -77,6 +77,7 @@ Those marked with an asterisk are part of IfcOpenShell.
|
||||
[IFC]: https://technical.buildingsmart.org/standards/ifc/ "IFC"
|
||||
[IFC2x3 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ "IFC2x3 TC1"
|
||||
[IFC4 Add2 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ "IFC4 Add2 TC1"
|
||||
[IFC4x3 Add2]: https://standards.buildingsmart.org/IFC/RELEASE/IFC4_3/ "IFC4x3 Add2"
|
||||
[Visual Studio]: https://www.visualstudio.com/ "Visual Studio"
|
||||
[Visual C++ Build Tools]: http://landinghub.visualstudio.com/visual-cpp-build-tools "Visual C++ Build Tools"
|
||||
[MSYS2]: https://msys2.github.io/ "MSYS2"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#Look for an executable called sphinx-build
|
||||
find_program(SPHINX_EXECUTABLE
|
||||
NAMES sphinx-build
|
||||
DOC "Path to sphinx-build executable")
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
#Handle standard arguments to find_package like REQUIRED and QUIET
|
||||
find_package_handle_standard_args(Sphinx
|
||||
"Failed to find sphinx-build executable"
|
||||
SPHINX_EXECUTABLE)
|
||||
@@ -1,11 +0,0 @@
|
||||
#Look for an executable called sphinx-build
|
||||
find_program(SPHINX_EXECUTABLE
|
||||
NAMES sphinx-build
|
||||
DOC "Path to sphinx-build executable")
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
#Handle standard arguments to find_package like REQUIRED and QUIET
|
||||
find_package_handle_standard_args(Sphinx
|
||||
"Failed to find sphinx-build executable"
|
||||
SPHINX_EXECUTABLE)
|
||||
@@ -0,0 +1,33 @@
|
||||
# IfcOpenShell C++ API documentation
|
||||
|
||||
This folder contains the setup to build the IfcOpenShell C++ API documentation from the source code.
|
||||
|
||||
## Generating the documentation
|
||||
|
||||
> Prerequisites:
|
||||
>
|
||||
> Make sure to have [Doxygen](https://www.doxygen.nl) and [Graphviz](https://graphviz.org) installed into your `$PATH` variable.
|
||||
>
|
||||
> The documentation also use the [doxygen-awesome](https://jothepro.github.io/doxygen-awesome-css) theme as a git submodule.
|
||||
|
||||
Build with the command (from within the `/docs/cpp-api` folder):
|
||||
|
||||
```shell
|
||||
$ doxygen
|
||||
```
|
||||
|
||||
To include the current git commit hash into the build documentation, use the following command:
|
||||
|
||||
```shell
|
||||
$ PROJECT_NUMBER=$(git rev-parse --short HEAD) doxygen
|
||||
```
|
||||
|
||||
This will extract the current commit hash in short version and sets the propper ENV variable used by doxygen.
|
||||
|
||||
The generation of the documentation might take a while depending on your systems hardware, as it is configured to generate the Class graphs using .
|
||||
|
||||
The resulting documentation is located unter `/cpp-api/output/html` and can be directly accessed with your browser:
|
||||
|
||||
```shell
|
||||
$ open ./output/html/index.html
|
||||
```
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 120 KiB |
@@ -1,240 +0,0 @@
|
||||
APPLY_DEFAULT_MATERIALS
|
||||
-----------------------
|
||||
|
||||
Given the command invocation:
|
||||
|
||||
Duplex_A_20110907_optimized.ifc d.dae -yv --include attribute GlobalId 3bXiCStxP6Fgxdej$yc50U
|
||||
|
||||
You will find log messages along the lines of
|
||||
|
||||
[Warning] {3bXiCStxP6Fgxdej$yc50U} No material and surface styles for:
|
||||
#333=IfcCovering('3bXiCStxP6Fgxdej$yc50U',#1,'Compound Ceiling:Gypsum Board:187483',$,'Compound Ceiling:Gypsum Board',#17840,#17052,'187483',.CEILING.)
|
||||
|
||||
This means that there is no IfcStyledItem associated to the representation items and that the element does not have an IfcMaterial association with IfcMaterialRepresentation from which we can derive a style (colour) for the element.
|
||||
|
||||
The interactive session below shows how with this setting enabled you will get a default generated material from the IFC element entity type and material indices of 0 pointing to that. With this setting disabled the material index would be -1 to indicate a missing style. Note that there is one material index for every triangle in the list of `shp.geometry.faces`.
|
||||
|
||||
>>> import ifcopenshell, ifcopenshell.geom
|
||||
>>> f = ifcopenshell.open("Duplex_A_20110907_optimized.ifc")
|
||||
>>> s = ifcopenshell.geom.settings()
|
||||
>>> c = f["3bXiCStxP6Fgxdej$yc50U"]
|
||||
>>>
|
||||
>>> shp = ifcopenshell.geom.create_shape(s, c)
|
||||
>>> shp.geometry.material_ids
|
||||
(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1)
|
||||
>>> [(m.name, m.diffuse) for m in shp.geometry.materials]
|
||||
[]
|
||||
>>>
|
||||
>>> s.set(s.APPLY_DEFAULT_MATERIALS, True)
|
||||
>>> shp = ifcopenshell.geom.create_shape(s, c)
|
||||
>>> shp.geometry.material_ids
|
||||
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
>>> [(m.name, m.diffuse) for m in shp.geometry.materials]
|
||||
[('IfcCovering', (0.7, 0.7, 0.7))]
|
||||
|
||||
This is enabled by default for the IfcConvert serializers as they will not gracefully handle -1 material indices and allows users to quickly assign colours based on entity types in their modelling applications.
|
||||
|
||||
APPLY_LAYERSETS
|
||||
---------------
|
||||
|
||||
This setting is available in IfcConvert as `--enable-layerset-slicing`.
|
||||
|
||||
For IfcWall and IfcSlab elements, takes the associated IfcMaterialLayerSet and builds a set of surfaces to segment the building element geometry.
|
||||
|
||||
Note that enabling this settings is computationally intensive as it involves 3D Boolean operations.
|
||||
|
||||
Duplex_A_20110907_optimized.ifc d1.dae -yv --include attribute GlobalId 2O2Fr$t4X7Zf8NOew3FNr2
|
||||
|
||||

|
||||
|
||||
Duplex_A_20110907_optimized.ifc d2.dae --enable-layerset-slicing -yv --include attribute GlobalId 2O2Fr$t4X7Zf8NOew3FNr2
|
||||
|
||||

|
||||
|
||||
BUILDING_LOCAL_PLACEMENT
|
||||
------------------------
|
||||
|
||||
This setting is available in IfcConvert using `--building-local-placement`.
|
||||
|
||||
In the typical IfcSite > IfcBuilding > IfcBuildingStorey > ... hierarchy of elements, don't incorporate the ObjectPlacement of the IfcBuilding and above in the placement of elements in the output. This is useful when there is a large offset in this placement that reduces precision in further processing.
|
||||
|
||||
CONVERT_BACK_UNITS
|
||||
------------------
|
||||
|
||||
This setting is available in IfcConvert using `--convert-back-units`.
|
||||
|
||||
Internally IfcOpenShell uses meters as the global length unit to do calculations. This setting restores the coordinate positions after conversion by multiplying the factor of the IfcUnit with UnitType=LENGTHUNIT into the output geometry coordinate values.
|
||||
|
||||
DISABLE_OPENING_SUBTRACTIONS
|
||||
----------------------------
|
||||
|
||||
This setting is available in IfcConvert using `--disable-opening-subtraction`.
|
||||
|
||||
As in most viewer applications, IfcOpeningElement geometry is subtracted from their host elements. This setting disables this behavior.
|
||||
|
||||
Duplex_A_20110907_optimized.ifc d1.dae -yv --include attribute GlobalId 2O2Fr$t4X7Zf8NOew3FNr2
|
||||
|
||||

|
||||
|
||||
Duplex_A_20110907_optimized.ifc d3.dae --disable-opening-subtraction -yv --include attribute GlobalId 2O2Fr$t4X7Zf8NOew3FNr2
|
||||
|
||||

|
||||
|
||||
Note that disabling this settings will reduce processing time and improve robustness as it involves 3D Boolean operations.
|
||||
|
||||
DISABLE_TRIANGULATION
|
||||
---------------------
|
||||
|
||||
To be used in conjunction with `USE_BREP_DATA`. Do not apply the triangulation and - when `USE_BREP_DATA` is set - return a OpenCASCADE serialized TopoDS_Shape from `create_shape()` and `iterator`.
|
||||
|
||||
>>> import ifcopenshell, ifcopenshell.geom
|
||||
>>> s = ifcopenshell.geom.settings()
|
||||
>>> s.set(s.DISABLE_TRIANGULATION, True)
|
||||
>>> s.set(s.USE_BREP_DATA, True)
|
||||
>>> f = ifcopenshell.open("Duplex_A_20110907_optimized.ifc")
|
||||
>>> c = f["3bXiCStxP6Fgxdej$yc50U"]
|
||||
>>> shp = ifcopenshell.geom.create_shape(s, c)
|
||||
>>> print(shp.geometry.brep_data)
|
||||
|
||||
CASCADE Topology V1, (c) Matra-Datavision
|
||||
Locations 0
|
||||
Curve2ds 0
|
||||
Curves 12
|
||||
1 4.6750000000000034 -8.0749999999999904 2.657 -2.0455514041918775e-15 -1 0
|
||||
1 4.6750000000000034 -8.0749999999999904 2.657 1 -3.435893306383461e-15 0
|
||||
1 6.2260000000000044 -8.0749999999999957 2.657 -2.0455514041918724e-15 -1 0
|
||||
1 6.226 -10.246000000000031 2.657 -1 6.8717866127669219e-15 0
|
||||
...
|
||||
|
||||
EDGE_ARROWS
|
||||
-----------
|
||||
|
||||
When `INCLUDE_CURVES` is true and geometric elements include curves (such as the wall axis), add arrow heads to the edges to indicate direction of the curve.
|
||||
|
||||
Duplex_A_20110907_optimized.ifc d4.dae --model --plan --edge-arrows -yv --include attribute GlobalId 2O2Fr$t4X7Zf8NOew3FNr2
|
||||
|
||||

|
||||
|
||||
EXCLUDE_SOLIDS_AND_SURFACES
|
||||
---------------------------
|
||||
|
||||
Exclude faces, shells and solids from geometrical output. Implied when using `--plan` without `--model` in IfcConvert.
|
||||
|
||||
FASTER_BOOLEANS
|
||||
---------------
|
||||
|
||||
NOTE: Only applicable when using OCCT 6.9 and earlier.
|
||||
|
||||
This setting is available in IfcConvert using `--merge-boolean-operands`.
|
||||
|
||||
Fuse the collection of all boolean operands into a single union before applying the boolean subtraction, as opposed to doing individual subtractions. This likely improves performance. From OCCT 7.0 onwards the boolean operations with multiple arguments is used.
|
||||
|
||||
GENERATE_UVS
|
||||
------------
|
||||
|
||||
This setting is available in IfcConvert using `--generate-uvs`.
|
||||
|
||||
Applies a box projection on the generated geometry for the element to obtain UV coordinates. This is purely generated, it does not involve texture coordinates stored in the IFC model.
|
||||
|
||||
Duplex_A_20110907_optimized.ifc d5.dae --generate-uvs -yv --include attribute GlobalId 2O2Fr$t4X7Zf8NOew3FNr2
|
||||
|
||||

|
||||
|
||||
INCLUDE_CURVES
|
||||
--------------
|
||||
|
||||
This setting is available in IfcConvert using `--plan`.
|
||||
|
||||
Include edge and wire geometries in the geometric output.
|
||||
|
||||
LAYERSET_FIRST
|
||||
--------------
|
||||
|
||||
This setting is available in IfcConvert using `--layerset-first`.
|
||||
|
||||
When not using APPLY_LAYERSETS, take the first material layer from the set to use as the material for the overall element.
|
||||
|
||||
NO_NORMALS
|
||||
----------
|
||||
|
||||
This setting is available in IfcConvert using `--no-normals`.
|
||||
|
||||
Do not emit normals on geometric output
|
||||
|
||||
SEARCH_FLOOR
|
||||
------------
|
||||
|
||||
Note: Only applicable to Collada .DAE output when used from IfcConvert.
|
||||
|
||||
This setting is available in IfcConvert using `--use-element-hierarchy`.
|
||||
|
||||
Include the spatial hierarchy in the elements.
|
||||
|
||||
SEW_SHELLS
|
||||
----------
|
||||
|
||||
This setting is available in IfcConvert using `--orient-shells`.
|
||||
|
||||
Re-orient or sew connected face sets to have a consistent outwards orientation.
|
||||
|
||||
SITE_LOCAL_PLACEMENT
|
||||
--------------------
|
||||
|
||||
This setting is available in IfcConvert using `--site-local-placement`.
|
||||
|
||||
See `BUILDING_LOCAL_PLACEMENT`, but exclude also the ObjectPlacement of the IfcSite.
|
||||
|
||||
USE_BREP_DATA
|
||||
-------------
|
||||
|
||||
See `DISABLE_TRIANGULATION`.
|
||||
|
||||
USE_PYTHON_OPENCASCADE
|
||||
----------------------
|
||||
|
||||
Note: Only available when an import of `OCC.Core.BRepTools` or `OCC.BRepTools` succeeds.
|
||||
|
||||
This implies `USE_WORLD_COORDS` `USE_BREP_DATA` and `DISABLE_TRIANGULATION`. The serialized TopoDS_Shape of `USE_BREP_DATA` is deserialized by Python OpenCASCADE.
|
||||
|
||||
USE_WORLD_COORDS
|
||||
----------------
|
||||
|
||||
Apply the ObjectPlacement of the building elements to the geometric output. This is implied when using the Wavefront .OBJ output in IfcConvert. Note that this also eliminates the possibility for geometric elements to point to the same interpreted geometry result.
|
||||
|
||||
VALIDATE_QUANTITIES
|
||||
-------------------
|
||||
|
||||
This setting is available in IfcConvert using `--validate`.
|
||||
|
||||
Running IfcConvert with `--validate` will set a non-zero exit code when ever a log message with severity equal or greater than ERROR has been emitted.
|
||||
|
||||
Currently for internal use only. For every building element geometry converted, looks for an associated quantity set where the OwnerHistory's organization name is IfcOpenShell. And looks for the quantities "Total Surface Area", "Volume", "Shape Validation Properties.Surface Genus" and validates these according to the interpreted geometry definition. Emit Logger::Error when calculated values are outside of the tolerance range for the value stored in the model.
|
||||
|
||||
WELD_VERTICES
|
||||
-------------
|
||||
|
||||
Note: In Python, this setting is *on* by default.
|
||||
|
||||
Note: This setting only affects triangulated output.
|
||||
|
||||
This setting is available in IfcConvert using `--weld-vertices`.
|
||||
|
||||
Discards normals and joins vertices solely based on position. This is useful when output is to be modified in a modeling application.
|
||||
|
||||
>>> import ifcopenshell, ifcopenshell.geom
|
||||
>>> s = ifcopenshell.geom.settings()
|
||||
>>> s.set(s.WELD_VERTICES, False)
|
||||
>>> f = ifcopenshell.open("Duplex_A_20110907_optimized.ifc")
|
||||
>>> c = f["3bXiCStxP6Fgxdej$yc50U"]
|
||||
>>> shp = ifcopenshell.geom.create_shape(s, c)
|
||||
>>> shp.geometry.verts
|
||||
(4.675000000000003, -8.07499999999999, 2.657, 4.674999999999999, -10.24600000000002, 2.657, 6.226000000000004, -8.074999999999996, 2.657, 6.226, -10.24600000000003, 2.657, 4.675000000000003, -8.07499999999999, 2.6, 4.674999999999999, -10.24600000000002, 2.6, 6.226000000000004, -8.074999999999996, 2.6, 6.226, -10.24600000000003, 2.6, 4.674999999999999, -10.24600000000002, 2.657, 4.674999999999999, -10.24600000000002, 2.6, 4.675000000000003, -8.07499999999999, 2.657, 4.675000000000003, -8.07499999999999, 2.6, 6.226, -10.24600000000003, 2.657, 4.674999999999999, -10.24600000000002, 2.657, 6.226, -10.24600000000003, 2.6, 4.674999999999999, -10.24600000000002, 2.6, 6.226000000000004, -8.074999999999996, 2.657, 6.226, -10.24600000000003, 2.657, 6.226000000000004, -8.074999999999996, 2.6, 6.226, -10.24600000000003, 2.6, 4.675000000000003, -8.07499999999999, 2.657, 4.675000000000003, -8.07499999999999, 2.6, 6.226000000000004, -8.074999999999996, 2.657, 6.226000000000004, -8.074999999999996, 2.6)
|
||||
>>> shp.geometry.normals
|
||||
(3.059754518198021e-17, 0.0, -1.0, 3.059754518198021e-17, 0.0, -1.0, 3.059754518198021e-17, 0.0, -1.0, 3.059754518198021e-17, 0.0, -1.0, 2.110175529791737e-16, 0.0, -1.0, 2.110175529791737e-16, 0.0, -1.0, 2.110175529791737e-16, 0.0, -1.0, 2.110175529791737e-16, 0.0, -1.0, -1.0, 1.79434333701042e-15, 0.0, -1.0, 1.79434333701042e-15, 0.0, -1.0, 1.79434333701042e-15, 0.0, -1.0, 1.79434333701042e-15, 0.0, 6.8717866127669046e-15, 1.0, 0.0, 6.8717866127669046e-15, 1.0, 0.0, 6.8717866127669046e-15, 1.0, 0.0, 6.8717866127669046e-15, 1.0, 0.0, -1.0, 1.79434333701042e-15, 0.0, -1.0, 1.79434333701042e-15, 0.0, -1.0, 1.79434333701042e-15, 0.0, -1.0, 1.79434333701042e-15, 0.0, 3.4358933063834523e-15, 1.0, 0.0, 3.4358933063834523e-15, 1.0, 0.0, 3.4358933063834523e-15, 1.0, 0.0, 3.4358933063834523e-15, 1.0, 0.0)
|
||||
>>>
|
||||
>>> s.set(s.WELD_VERTICES, True)
|
||||
>>> shp = ifcopenshell.geom.create_shape(s, c)
|
||||
>>> shp.geometry.verts
|
||||
(4.675000000000003, -8.07499999999999, 2.657, 4.674999999999999, -10.24600000000002, 2.657, 6.226000000000004, -8.074999999999996, 2.657, 6.226, -10.24600000000003, 2.657, 4.675000000000003, -8.07499999999999, 2.6, 4.674999999999999, -10.24600000000002, 2.6, 6.226000000000004, -8.074999999999996, 2.6, 6.226, -10.24600000000003, 2.6)
|
||||
>>> shp.geometry.normals
|
||||
()
|
||||
@@ -696,6 +696,11 @@ endif
|
||||
# Required for Desktop icon and file association
|
||||
cp -r blenderbim/libs/desktop dist/blenderbim/libs/
|
||||
|
||||
# generate translations module for BBIM build
|
||||
git clone https://github.com/IfcOpenShell/blenderbim-translations.git dist/working
|
||||
python scripts/bbim_translations.py -i "dist/working" -o "dist/blenderbim"
|
||||
rm -rf dist/working
|
||||
|
||||
# Remove dependencies also bundled with Blender
|
||||
rm -rf dist/blenderbim/libs/site/packages/numpy
|
||||
rm -rf dist/blenderbim/libs/site/packages/numpy.libs
|
||||
|
||||
@@ -24,6 +24,10 @@ import blenderbim
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator, helper
|
||||
|
||||
try:
|
||||
from blenderbim.translations import translations_dict
|
||||
except ImportError:
|
||||
translations_dict = {}
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
modules = {
|
||||
@@ -92,23 +96,22 @@ for name in modules.keys():
|
||||
classes = [
|
||||
operator.AddIfcFile,
|
||||
operator.BIM_OT_add_section_plane,
|
||||
operator.BIM_OT_open_webbrowser,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.EditBlenderCollection,
|
||||
operator.FileAssociate,
|
||||
operator.FileUnassociate,
|
||||
operator.OpenUpstream,
|
||||
operator.OpenUri,
|
||||
operator.SwitchTab,
|
||||
operator.SetTab,
|
||||
operator.ReloadIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.ReloadSelectedIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
operator.FileAssociate,
|
||||
operator.FileUnassociate,
|
||||
operator.SelectURIAttribute,
|
||||
operator.EditBlenderCollection,
|
||||
operator.BIM_OT_open_webbrowser,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.SetTab,
|
||||
operator.SwitchTab,
|
||||
prop.StrProperty,
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
prop.ObjProperty,
|
||||
@@ -250,6 +253,8 @@ def register():
|
||||
except:
|
||||
pass
|
||||
|
||||
bpy.app.translations.register("blenderbim", translations_dict)
|
||||
|
||||
|
||||
def unregister():
|
||||
global icons
|
||||
@@ -292,3 +297,5 @@ def unregister():
|
||||
bpy.utils.unregister_class(override_panel)
|
||||
bpy.utils.register_class(original_panel)
|
||||
del overridden_scene_panels[panel]
|
||||
|
||||
bpy.app.translations.unregister("blenderbim")
|
||||
|
||||
@@ -22,6 +22,7 @@ import logging
|
||||
import shapely
|
||||
import mathutils
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.shape
|
||||
@@ -30,7 +31,7 @@ import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
from math import pi, inf
|
||||
from math import pi, inf, degrees, acos, radians
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
@@ -586,6 +587,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
if parent_boundary:
|
||||
parent_boundaries.append(parent_boundary)
|
||||
elif len(objs) == 1:
|
||||
# New prototype, old code not yet removed. Still testing.
|
||||
space = tool.Ifc.get_entity(objs[0])
|
||||
if space.is_a("IfcSpace"):
|
||||
self.auto_generate_boundaries(space, objs[0])
|
||||
elif len(objs) == 1:
|
||||
# Optionally the user may select just the space, and the building element shall be auto-detected
|
||||
# TODO : refactor to be able to generate all boundaries for selected space automatically or with an option
|
||||
@@ -632,6 +638,227 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = tool.Ifc.get_object(parent_boundary)
|
||||
obj.select_set(True)
|
||||
|
||||
def auto_generate_boundaries(self, space, space_obj):
|
||||
# Identify all potential building elements
|
||||
# TODO: don't select everything, use AABB culling in Blender
|
||||
building_elements = (
|
||||
tool.Ifc.get().by_type("IfcWall")
|
||||
+ tool.Ifc.get().by_type("IfcSlab")
|
||||
+ tool.Ifc.get().by_type("IfcVirtualElement")
|
||||
)
|
||||
|
||||
# Don't generate boundaries of building elements that we've already got bounaries for.
|
||||
for boundary in space.BoundedBy:
|
||||
if boundary.RelatedBuildingElement in building_elements:
|
||||
building_elements.remove(boundary.RelatedBuildingElement)
|
||||
|
||||
# Create tree of gross shapes of all potential related building elements
|
||||
include = building_elements + [space]
|
||||
tree = ifcopenshell.geom.tree()
|
||||
shapes = {}
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.STRICT_TOLERANCE, True)
|
||||
settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True)
|
||||
iterator = ifcopenshell.geom.iterator(settings, tool.Ifc.get(), multiprocessing.cpu_count(), include=include)
|
||||
if iterator.initialize():
|
||||
while True:
|
||||
tree.add_element(iterator.get_native())
|
||||
shape = iterator.get()
|
||||
shapes[shape.id] = shape
|
||||
if not iterator.next():
|
||||
break
|
||||
|
||||
# Spatially query all potential boundary elements via a 100mm extension of the space
|
||||
building_elements = [e for e in tree.select(space, extend=0.1) if e != space]
|
||||
|
||||
if not building_elements:
|
||||
return
|
||||
|
||||
# Create a dissolved bmesh for the space
|
||||
space_bm = bmesh.new()
|
||||
space_bm.from_mesh(space_obj.data)
|
||||
bmesh.ops.dissolve_limit(space_bm, angle_limit=pi * 2 / 360, verts=space_bm.verts, edges=space_bm.edges)
|
||||
|
||||
# Create dissolved bmeshes for all boundary elements
|
||||
building_element_bms = {}
|
||||
for building_element in building_elements:
|
||||
bm = bmesh.new()
|
||||
shape = shapes[building_element.id()]
|
||||
|
||||
verts = ifcopenshell.util.shape.get_vertices(shape.geometry)
|
||||
for vert in verts:
|
||||
bm.verts.new(Vector(vert))
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
faces = ifcopenshell.util.shape.get_faces(shape.geometry)
|
||||
for face in faces:
|
||||
bm.faces.new([bm.verts[i] for i in face])
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
bm.normal_update() # Needed so that dissolve_limit will work.
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=radians(1), verts=bm.verts[:], edges=bm.edges[:])
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
building_element_bms[building_element.id()] = bm
|
||||
|
||||
# Compare space faces and building element faces to see if they relate to one another
|
||||
for space_face in space_bm.faces:
|
||||
space_face_normal = space_obj.matrix_world.to_3x3() @ space_face.normal
|
||||
space_face_vert = space_obj.matrix_world @ space_face.verts[0].co
|
||||
for building_element in building_elements:
|
||||
for face in building_element_bms[building_element.id()].faces:
|
||||
building_obj = tool.Ifc.get_object(building_element)
|
||||
face_normal = building_obj.matrix_world.to_3x3() @ face.normal
|
||||
angle = degrees(acos(max(min(space_face_normal.dot(face_normal), 1), -1)))
|
||||
if tool.Cad.is_x(angle, 180, tolerance=2):
|
||||
pass # Faces need to be parallel and have opposite normals to be related.
|
||||
elif building_element.is_a("IfcVirtualElement") and tool.Cad.is_x(angle, 0, tolerance=2):
|
||||
pass # Virtual elements only need to be parallel to be related, since they are planes.
|
||||
else:
|
||||
continue
|
||||
|
||||
# Both faces should be close to one another. Say within 50mm.
|
||||
space_vert = building_obj.matrix_world.inverted() @ space_face_vert
|
||||
dist = mathutils.geometry.distance_point_to_plane(space_vert, face.verts[0].co, face.normal)
|
||||
if abs(dist) > 0.05:
|
||||
continue
|
||||
|
||||
# Project the building element face onto the space face
|
||||
space_face_verts = [v.co.copy() for v in space_face.verts]
|
||||
space_face_matrix = self.get_face_matrix(*[v.copy() for v in space_face_verts[0:3]])
|
||||
space_face_matrix_i = space_face_matrix.inverted()
|
||||
|
||||
space_face_polygon = shapely.Polygon(
|
||||
[tuple((space_face_matrix_i @ v).xy) for v in space_face_verts]
|
||||
)
|
||||
|
||||
space_matrix_world_i = space_obj.matrix_world.inverted()
|
||||
face_verts = [space_matrix_world_i @ building_obj.matrix_world @ v.co.copy() for v in face.verts]
|
||||
face_polygon = shapely.Polygon([tuple((space_face_matrix_i @ v).xy) for v in face_verts])
|
||||
|
||||
gross_boundary_polygon = space_face_polygon.intersection(face_polygon)
|
||||
|
||||
if type(gross_boundary_polygon) == shapely.GeometryCollection:
|
||||
for geom in gross_boundary_polygon.geoms:
|
||||
if type(geom) == shapely.Polygon:
|
||||
gross_boundary_polygon = geom
|
||||
break
|
||||
|
||||
if (
|
||||
not (isinstance(gross_boundary_polygon, shapely.Polygon) and gross_boundary_polygon.is_valid)
|
||||
or gross_boundary_polygon.is_empty
|
||||
):
|
||||
continue
|
||||
|
||||
# The gross boundary polygon may not be a true gross boundary since it
|
||||
# may have openings already removed, such as in IFC4 Reference View. So
|
||||
# we cheat by using the exterior boundary to mean "gross".
|
||||
exterior_boundary_polygon = shapely.Polygon(gross_boundary_polygon.exterior.coords)
|
||||
|
||||
parent_boundary = tool.Ifc.run(
|
||||
"root.create_entity", ifc_class=bpy.context.scene.BIMModelProperties.boundary_class
|
||||
)
|
||||
if building_element.is_a("IfcVirtualElement"):
|
||||
parent_boundary.PhysicalOrVirtualBoundary = "VIRTUAL"
|
||||
else:
|
||||
parent_boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
|
||||
parent_boundary.InternalOrExternalBoundary = "NOTDEFINED"
|
||||
if building_element.is_a("IfcWall"):
|
||||
is_external = ifcopenshell.util.element.get_pset(
|
||||
building_element, "Pset_WallCommon", "IsExternal"
|
||||
)
|
||||
if is_external is True:
|
||||
parent_boundary.InternalOrExternalBoundary = "EXTERNAL"
|
||||
elif is_external is False:
|
||||
parent_boundary.InternalOrExternalBoundary = "INTERNAL"
|
||||
elif building_element.is_a("IfcSlab"):
|
||||
predefined_type = ifcopenshell.util.element.get_predefined_type(building_element)
|
||||
if predefined_type == "BASESLAB":
|
||||
parent_boundary.InternalOrExternalBoundary = "EXTERNAL_EARTH"
|
||||
else:
|
||||
is_external = ifcopenshell.util.element.get_pset(
|
||||
building_element, "Pset_SlabCommon", "IsExternal"
|
||||
)
|
||||
if is_external is True:
|
||||
parent_boundary.InternalOrExternalBoundary = "EXTERNAL"
|
||||
elif is_external is False:
|
||||
parent_boundary.InternalOrExternalBoundary = "INTERNAL"
|
||||
parent_boundary.RelatingSpace = space
|
||||
parent_boundary.RelatedBuildingElement = building_element
|
||||
parent_boundary.ConnectionGeometry = self.create_connection_geometry_from_polygon(
|
||||
exterior_boundary_polygon, space_face_matrix
|
||||
)
|
||||
self.set_boundary_name(parent_boundary)
|
||||
|
||||
for rel in getattr(building_element, "HasOpenings", []):
|
||||
opening = rel.RelatedOpeningElement
|
||||
filling = opening.HasFillings[0].RelatedBuildingElement if opening.HasFillings else None
|
||||
|
||||
# Create shape of opening as a dissolved BMesh
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.STRICT_TOLERANCE, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, opening)
|
||||
m = shape.transformation.matrix.data
|
||||
mat = Matrix(
|
||||
(
|
||||
[m[0], m[3], m[6], m[9]],
|
||||
[m[1], m[4], m[7], m[10]],
|
||||
[m[2], m[5], m[8], m[11]],
|
||||
[0, 0, 0, 1],
|
||||
)
|
||||
)
|
||||
opening_bm = bmesh.new()
|
||||
verts = ifcopenshell.util.shape.get_vertices(shape.geometry)
|
||||
for vert in verts:
|
||||
opening_bm.verts.new(Vector(vert))
|
||||
opening_bm.verts.ensure_lookup_table()
|
||||
faces = ifcopenshell.util.shape.get_faces(shape.geometry)
|
||||
for face in faces:
|
||||
opening_bm.faces.new([opening_bm.verts[i] for i in face])
|
||||
opening_bm.verts.ensure_lookup_table()
|
||||
opening_bm.faces.ensure_lookup_table()
|
||||
opening_bm.normal_update() # Needed so that dissolve_limit will work.
|
||||
bmesh.ops.dissolve_limit(
|
||||
opening_bm, angle_limit=radians(1), verts=opening_bm.verts[:], edges=opening_bm.edges[:]
|
||||
)
|
||||
opening_bm.verts.ensure_lookup_table()
|
||||
opening_bm.faces.ensure_lookup_table()
|
||||
|
||||
# Get relevant faces of BMesh that can turn into boundaries
|
||||
opening_polygons = []
|
||||
for opening_face in opening_bm.faces:
|
||||
opening_face_normal = mat.to_3x3() @ opening_face.normal
|
||||
angle = degrees(acos(max(min(opening_face_normal.dot(face_normal), 1), -1)))
|
||||
if not tool.Cad.is_x(angle, 180, tolerance=2):
|
||||
continue # Any non-parallel faces are not relevant
|
||||
opening_face_verts = [space_matrix_world_i @ mat @ v.co.copy() for v in opening_face.verts]
|
||||
polygon = shapely.Polygon([tuple((space_face_matrix_i @ v).xy) for v in opening_face_verts])
|
||||
opening_polygons.append(polygon)
|
||||
|
||||
# Merge them all into a single opening polygon for our boundary
|
||||
opening_polygon = shapely.ops.unary_union(opening_polygons)
|
||||
|
||||
# Only openings that are projected onto our exterior boundary are relevant.
|
||||
if opening_polygon.intersection(exterior_boundary_polygon).area == 0:
|
||||
continue
|
||||
|
||||
boundary = tool.Ifc.run(
|
||||
"root.create_entity", ifc_class=bpy.context.scene.BIMModelProperties.boundary_class
|
||||
)
|
||||
boundary.RelatingSpace = space
|
||||
boundary.RelatedBuildingElement = filling or opening
|
||||
boundary.ConnectionGeometry = self.create_connection_geometry_from_polygon(
|
||||
opening_polygon, space_face_matrix
|
||||
)
|
||||
if filling:
|
||||
boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
|
||||
else:
|
||||
boundary.PhysicalOrVirtualBoundary = "VIRTUAL"
|
||||
boundary.InternalOrExternalBoundary = parent_boundary.InternalOrExternalBoundary
|
||||
if boundary.is_a() != "IfcRelSpaceBoundary":
|
||||
boundary.ParentBoundary = parent_boundary
|
||||
self.set_boundary_name(boundary)
|
||||
|
||||
def create_element_boundary(
|
||||
self, context, relating_space, relating_space_obj, related_building_element, related_building_element_obj
|
||||
):
|
||||
|
||||
@@ -37,12 +37,10 @@ class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
object_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_cost_schedule(
|
||||
tool.Ifc,
|
||||
name=self.name,
|
||||
predefined_type=context.scene.BIMCostProperties.cost_schedule_predefined_types,
|
||||
object_type=self.object_type,
|
||||
)
|
||||
predefined_type = context.scene.BIMCostProperties.cost_schedule_predefined_types
|
||||
if predefined_type == "USERDEFINED":
|
||||
predefined_type = self.object_type
|
||||
core.add_cost_schedule(tool.Ifc, name=self.name, predefined_type=predefined_type)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -24,6 +24,7 @@ import logging
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.handler import refresh_ui_data
|
||||
|
||||
@@ -92,15 +93,26 @@ class ImportCsvAttributes(bpy.types.Operator):
|
||||
data = json.load(open(self.filepath))
|
||||
tool.Search.import_filter_query(data["query"], props.filter_groups)
|
||||
|
||||
for prop in [
|
||||
"should_generate_svg",
|
||||
"should_preserve_existing",
|
||||
"include_global_id",
|
||||
"null_value",
|
||||
"empty_value",
|
||||
"true_value",
|
||||
"false_value",
|
||||
"concat_value",
|
||||
"csv_delimiter",
|
||||
"format",
|
||||
"csv_custom_delimiter",
|
||||
]:
|
||||
setattr(props, prop, data["settings"][prop])
|
||||
|
||||
props.csv_attributes.clear()
|
||||
for attribute in data["attributes"]:
|
||||
new = props.csv_attributes.add()
|
||||
new.name = attribute["name"]
|
||||
new.header = attribute["header"]
|
||||
new.sort = attribute["sort"]
|
||||
new.group = attribute["group"]
|
||||
new.summary = attribute["summary"]
|
||||
new.formatting = attribute["formatting"]
|
||||
for prop in ["name", "header", "sort", "group", "summary", "formatting"]:
|
||||
setattr(new, prop, attribute[prop])
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -120,6 +132,22 @@ class ExportCsvAttributes(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = context.scene.CsvProperties
|
||||
|
||||
settings = {}
|
||||
for prop in [
|
||||
"should_generate_svg",
|
||||
"should_preserve_existing",
|
||||
"include_global_id",
|
||||
"null_value",
|
||||
"empty_value",
|
||||
"true_value",
|
||||
"false_value",
|
||||
"concat_value",
|
||||
"csv_delimiter",
|
||||
"format",
|
||||
"csv_custom_delimiter",
|
||||
]:
|
||||
settings[prop] = getattr(props, prop)
|
||||
|
||||
data = {
|
||||
"query": tool.Search.export_filter_query(props.filter_groups),
|
||||
"attributes": [
|
||||
@@ -133,6 +161,7 @@ class ExportCsvAttributes(bpy.types.Operator):
|
||||
}
|
||||
for a in props.csv_attributes
|
||||
],
|
||||
"settings": settings,
|
||||
}
|
||||
|
||||
with open(self.filepath, "w") as outfile:
|
||||
@@ -213,6 +242,10 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
summaries=summaries,
|
||||
formatting=formatting,
|
||||
)
|
||||
|
||||
if props.format != "csv" and props.should_generate_svg:
|
||||
schedule_creator = scheduler.Scheduler()
|
||||
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ class CsvProperties(PropertyGroup):
|
||||
ifc_selector: StringProperty(default="", name="IFC Selector")
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
|
||||
csv_attributes: CollectionProperty(name="CSV Attributes", type=CsvAttribute)
|
||||
should_generate_svg: BoolProperty(default=False, name="Generate SVG")
|
||||
should_preserve_existing: BoolProperty(default=False, name="Preserve Existing")
|
||||
include_global_id: BoolProperty(default=True, name="Include GlobalId")
|
||||
null_value: StringProperty(default="N/A", name="Null Value")
|
||||
|
||||
@@ -67,6 +67,8 @@ class BIM_PT_ifccsv(Panel):
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "should_generate_svg")
|
||||
row = layout.row()
|
||||
row.prop(props, "should_preserve_existing")
|
||||
row = layout.row()
|
||||
|
||||
@@ -213,7 +213,7 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
|
||||
failures = []
|
||||
excludes = () # For the developer to debug with
|
||||
for i, element in enumerate(elements):
|
||||
for i, element in enumerate(elements, 1):
|
||||
if element.GlobalId in excludes:
|
||||
continue
|
||||
print(f"{i}/{total}:", element)
|
||||
@@ -510,8 +510,116 @@ class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"Will find all elements of class that have no inverse refernces and will remove them, use very carefully"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMDebugProperties
|
||||
purged_elements = core.purge_unused_elements(tool.Ifc, tool.Debug, props.ifc_class_purge)
|
||||
self.report({"INFO"}, f"{purged_elements} unused elements found and removed.")
|
||||
if props.ifc_class_purge:
|
||||
purged_elements = core.purge_unused_elements(tool.Ifc, tool.Debug, props.ifc_class_purge)
|
||||
self.report({"INFO"}, f"{purged_elements} unused elements found and removed.")
|
||||
tool.Ifc.get().write(self.filepath)
|
||||
return
|
||||
|
||||
# A whitelisted class is a class that only contains simple data that
|
||||
# has no meaning by itself (only has meaning when combined with other
|
||||
# data). E.g. a colour by itself has no meaning so is whitelisted, but
|
||||
# a material by itself may be part of your materials library.
|
||||
|
||||
# There are classes I don't know enough about to decide. Not whitelisting in case.
|
||||
# IfcAlignmentParameterSegment
|
||||
# IfcBoundaryCondition
|
||||
# IfcStructuralConnectionCondition
|
||||
# IfcStructuralLoad
|
||||
|
||||
whitelisted_classes = [
|
||||
"IfcActorRole",
|
||||
"IfcAddress",
|
||||
"IfcApplication",
|
||||
"IfcAppliedValue",
|
||||
"IfcConnectionGeometry",
|
||||
"IfcCoordinateReferenceSystem",
|
||||
"IfcDerivedUnit",
|
||||
"IfcDerivedUnitElement",
|
||||
"IfcDimensionalExponents",
|
||||
"IfcExternalReference",
|
||||
"IfcGridAxis",
|
||||
"IfcIrregularTimeSeriesValue",
|
||||
"IfcLightDistributionData",
|
||||
"IfcLightIntensityDistribution",
|
||||
"IfcMeasureWithUnit",
|
||||
"IfcMonetaryUnit",
|
||||
"IfcNamedUnit",
|
||||
"IfcObjectPlacement",
|
||||
"IfcOrganization", # Should be referenced as part of an actor
|
||||
"IfcOwnerHistory",
|
||||
"IfcPerson", # Should be referenced as part of an actor
|
||||
"IfcPersonAndOrganization", # Should be referenced as part of an actor
|
||||
"IfcPhysicalQuantity",
|
||||
"IfcPresentationItem",
|
||||
"IfcProductDefinitionShape",
|
||||
"IfcPropertyAbstraction",
|
||||
"IfcRecurrencePattern",
|
||||
"IfcReference",
|
||||
"IfcRepresentation",
|
||||
"IfcRepresentationContext",
|
||||
"IfcRepresentationItem",
|
||||
"IfcRepresentationMap",
|
||||
"IfcPropertyDefinition", # A bit of a questionable one, and the odd one out from IfcRoot.
|
||||
"IfcSchedulingTime",
|
||||
"IfcShapeAspect",
|
||||
"IfcTable",
|
||||
"IfcTableColumn",
|
||||
"IfcTableRow",
|
||||
"IfcTextureCoordinateIndices",
|
||||
"IfcTimePeriod",
|
||||
"IfcTimeSeries",
|
||||
"IfcTimeSeriesValue",
|
||||
"IfcUnitAssignment",
|
||||
"IfcVirtualGridIntersection",
|
||||
]
|
||||
|
||||
total_purged = 0
|
||||
while True:
|
||||
total_batches = 0
|
||||
print("*" * 100)
|
||||
total_batch_purged = 0
|
||||
for ifc_class in whitelisted_classes:
|
||||
total_class_purged = 0
|
||||
try:
|
||||
elements = tool.Ifc.get().by_type(ifc_class)
|
||||
except:
|
||||
continue # Probably not in this schema?
|
||||
to_purge = set()
|
||||
for element in elements:
|
||||
try:
|
||||
if ifc_class == "IfcRepresentationItem" and element.is_a("IfcStyledItem") and element.Item:
|
||||
continue
|
||||
except:
|
||||
to_purge.add(element.id()) # It's invalid, definitely purge it.
|
||||
if tool.Ifc.get().get_total_inverses(element) == 0:
|
||||
to_purge.add(element.id())
|
||||
for element_id in to_purge:
|
||||
try:
|
||||
element = tool.Ifc.get().by_id(element_id)
|
||||
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), element)
|
||||
total_class_purged += 1
|
||||
except:
|
||||
continue
|
||||
if total_class_purged > 0:
|
||||
total_batch_purged += total_class_purged
|
||||
print(f"Auto purged {total_class_purged} {ifc_class}")
|
||||
if total_batch_purged > 0:
|
||||
total_purged += total_batch_purged
|
||||
print(f"Auto purged in batch: {total_batch_purged}")
|
||||
total_batches += 1
|
||||
if total_batch_purged == 0:
|
||||
break
|
||||
elif total_batches > 20:
|
||||
print("Finished 20 batches. Manually stopping in case of infinite loop.")
|
||||
self.report({"INFO"}, f"Auto purged {total_purged} orphaned elements")
|
||||
tool.Ifc.get().write(self.filepath)
|
||||
|
||||
@@ -37,7 +37,6 @@ class BIM_PT_debug(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.BIMProperties, "ifc_file", text="")
|
||||
row.operator("bim.validate_ifc_file", icon="CHECKMARK", text="")
|
||||
row.operator("bim.reload_selected_ifc_file", icon="FILE_REFRESH", text="")
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
@@ -45,6 +44,9 @@ class BIM_PT_debug(Panel):
|
||||
row.operator("bim.parse_express", icon="IMPORT", text="")
|
||||
row.operator("bim.select_express_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.reload_ifc_file", text="Incrementally Reload Changes")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.print_ifc_file")
|
||||
|
||||
@@ -87,6 +89,13 @@ class BIM_PT_debug(Panel):
|
||||
row.prop(props, "display_type", text="")
|
||||
row.operator("bim.override_display_type").display = context.scene.BIMDebugProperties.display_type
|
||||
|
||||
layout.operator("bim.purge_unused_representations")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene.BIMDebugProperties, "ifc_class_purge", text="")
|
||||
row.operator("bim.purge_unused_elements_by_class", text="Purge Orphaned", icon="TRASH")
|
||||
row.operator("bim.print_unused_elements_stats", text="", icon="INFO")
|
||||
|
||||
if context.active_object and context.active_object.data:
|
||||
mprops = context.active_object.data.BIMMeshProperties
|
||||
row = layout.row()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
|
||||
@@ -494,6 +494,11 @@ class CreateDrawing(bpy.types.Operator):
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
if ifc.by_id(body_context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
geom_settings.offset = offset
|
||||
geom_settings.set_context_ids(body_context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
geom_settings, ifc, multiprocessing.cpu_count(), include=elements
|
||||
@@ -513,6 +518,11 @@ class CreateDrawing(bpy.types.Operator):
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
if ifc.by_id(annotation_context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
geom_settings.offset = offset
|
||||
geom_settings.set_context_ids(annotation_context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
geom_settings, ifc, multiprocessing.cpu_count(), include=elements
|
||||
@@ -1996,11 +2006,11 @@ class AddReferenceToSheet(bpy.types.Operator, Operator):
|
||||
props = context.scene.DocProperties
|
||||
active_reference = props.references[props.active_reference_index]
|
||||
active_sheet = tool.Drawing.get_active_sheet(context)
|
||||
reference = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
|
||||
extref = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
reference_location = tool.Drawing.get_path_with_ext(reference.DocumentReferences[0].Location, "svg")
|
||||
extref_location = tool.Drawing.get_path_with_ext(extref.DocumentReferences[0].Location, "svg")
|
||||
else:
|
||||
reference_location = tool.Drawing.get_path_with_ext(reference.HasDocumentReferences[0].Location, "svg")
|
||||
extref_location = tool.Drawing.get_path_with_ext(extref.HasDocumentReferences[0].Location, "svg")
|
||||
|
||||
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
||||
if not sheet.is_a("IfcDocumentInformation"):
|
||||
@@ -2008,30 +2018,30 @@ class AddReferenceToSheet(bpy.types.Operator, Operator):
|
||||
|
||||
references = tool.Drawing.get_document_references(sheet)
|
||||
|
||||
has_reference = False
|
||||
has_extref = False
|
||||
for reference in references:
|
||||
if reference.Location == reference_location:
|
||||
has_reference = True
|
||||
if reference.Location == extref_location:
|
||||
has_extref = True
|
||||
break
|
||||
if has_reference:
|
||||
if has_extref:
|
||||
return
|
||||
|
||||
if not tool.Drawing.does_file_exist(tool.Ifc.resolve_uri(reference_location)):
|
||||
self.report({"ERROR"}, f"Cannot find reference svg by path {reference_location}.")
|
||||
if not tool.Drawing.does_file_exist(tool.Ifc.resolve_uri(extref_location)):
|
||||
self.report({"ERROR"}, f"Cannot find reference svg by path {extref_location}.")
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
|
||||
attributes = {
|
||||
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "REFERENCE")]) + 1),
|
||||
"Location": reference_location,
|
||||
"Location": extref_location,
|
||||
"Description": "REFERENCE",
|
||||
}
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
sheet_builder.add_document(reference, reference, sheet)
|
||||
sheet_builder.add_document(reference, extref, sheet)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ class Scheduler:
|
||||
|
||||
column_dimensions = {k: v.width for k, v in sheet.column_dimensions.items()}
|
||||
row_dimensions = {k: v.height for k, v in sheet.row_dimensions.items()}
|
||||
print(row_dimensions)
|
||||
default_width = 8.43 # 8.43 characters wide
|
||||
default_height = 15 # 15pt seems to be the default height
|
||||
|
||||
@@ -83,7 +82,13 @@ class Scheduler:
|
||||
pass
|
||||
|
||||
y = self.margin
|
||||
for row in sheet.iter_rows():
|
||||
rows = list(sheet.iter_rows())
|
||||
total_rows = len(rows)
|
||||
for i, row in enumerate(rows):
|
||||
# The last row may contain only null values
|
||||
if i == (total_rows - 1) and not [c for c in row if c.value is not None]:
|
||||
continue
|
||||
|
||||
x = self.margin
|
||||
for cell in row:
|
||||
if isinstance(cell, openpyxl.cell.cell.MergedCell):
|
||||
@@ -135,6 +140,10 @@ class Scheduler:
|
||||
)
|
||||
)
|
||||
|
||||
if not cell.value:
|
||||
x += unmerged_width
|
||||
continue
|
||||
|
||||
font_size = cell.font.size or 11 # 11pt default
|
||||
font_size = font_size / FONT_SIZE_PT * FONT_SIZE # Magic?
|
||||
|
||||
@@ -528,6 +537,10 @@ class Scheduler:
|
||||
if len(text_lines) == 1 and not wrap_text:
|
||||
text_params.update(box_alignment_params)
|
||||
text_tag = self.svg.text(text_lines[0], insert=(x, y), **(text_params))
|
||||
if self.is_url(text_lines[0]):
|
||||
anchor = self.svg.a(text_lines[0].strip())
|
||||
anchor.add(text_tag)
|
||||
text_tag = anchor
|
||||
self.svg.add(text_tag)
|
||||
return
|
||||
|
||||
@@ -562,11 +575,18 @@ class Scheduler:
|
||||
tspan = self.svg.tspan(text_line, insert=(x, y), **text_params)
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
tspan.update({"dy": f"{line_number}em"})
|
||||
if self.is_url(text_line):
|
||||
anchor = self.svg.a(text_line.strip())
|
||||
anchor.add(tspan)
|
||||
tspan = anchor
|
||||
text_tag.add(tspan)
|
||||
line_number += dy_dir
|
||||
|
||||
self.svg.add(text_tag)
|
||||
|
||||
def is_url(self, value):
|
||||
return value.strip().startswith("http") and " " not in value.strip() and "://" in value # Wrong but fast
|
||||
|
||||
def convert_character_width_to_mm(self, value):
|
||||
# Excel measures widths in terms of "number of characters of the
|
||||
# default font", which is most commonly 11pt Calibri. The width of the
|
||||
|
||||
@@ -187,11 +187,11 @@ class SheetBuilder:
|
||||
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def add_document(self, reference, schedule, sheet):
|
||||
view_path = tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(schedule), "svg")
|
||||
def add_document(self, reference, document, sheet):
|
||||
view_path = tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(document), "svg")
|
||||
if not os.path.exists(view_path):
|
||||
tool.Drawing.create_svg_schedule(schedule)
|
||||
schedule_name = os.path.splitext(os.path.basename(view_path))[0]
|
||||
tool.Drawing.create_svg_document(document)
|
||||
document_name = os.path.splitext(os.path.basename(view_path))[0]
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
layout_dir = os.path.dirname(layout_path)
|
||||
|
||||
@@ -207,12 +207,12 @@ class SheetBuilder:
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
|
||||
view = ET.SubElement(layout_root, "g")
|
||||
view.attrib["data-type"] = "schedule"
|
||||
view.attrib["data-id"] = str(reference.id())
|
||||
view.attrib["data-schedule"] = str(schedule.id())
|
||||
view.attrib["data-type"] = document.Scope.lower()
|
||||
view.attrib["data-document"] = str(document.id())
|
||||
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "table"
|
||||
foreground.attrib["data-type"] = "content"
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(view_path, layout_dir)
|
||||
foreground.attrib["x"] = str(DEFAULT_POSITION.x)
|
||||
foreground.attrib["y"] = str(DEFAULT_POSITION.y)
|
||||
@@ -263,7 +263,7 @@ class SheetBuilder:
|
||||
|
||||
self.build_titleblock(root, sheet)
|
||||
self.build_drawings(root, sheet)
|
||||
self.build_schedules(root, sheet)
|
||||
self.build_documents(root, sheet)
|
||||
|
||||
with open(sheet_path, "wb") as output:
|
||||
tree.write(output)
|
||||
@@ -390,11 +390,14 @@ class SheetBuilder:
|
||||
for image in images:
|
||||
view.remove(image)
|
||||
|
||||
def build_schedules(self, root, sheet):
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'):
|
||||
def build_documents(self, root, sheet):
|
||||
schedules = root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]')
|
||||
references = root.findall('{http://www.w3.org/2000/svg}g[@data-type="reference"]')
|
||||
documents = schedules + references
|
||||
for view in documents:
|
||||
try:
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
|
||||
document = tool.Ifc.get().by_id(int(view.attrib["data-document"]))
|
||||
except:
|
||||
# Perhaps the SVG has outdated content or is edited externally which we cannot control.
|
||||
continue
|
||||
@@ -405,7 +408,7 @@ class SheetBuilder:
|
||||
view_title = None
|
||||
|
||||
for image in images:
|
||||
if image.attrib["data-type"] == "table":
|
||||
if image.attrib["data-type"] == "content":
|
||||
table = image
|
||||
elif image.attrib["data-type"] == "view-title":
|
||||
view_title = image
|
||||
@@ -418,7 +421,7 @@ class SheetBuilder:
|
||||
data = reference.get_info()
|
||||
data.update({"Sheet" + k: v for k, v in sheet.get_info().items()})
|
||||
if not data["Name"]:
|
||||
data["Name"] = schedule.Name or "Unnamed"
|
||||
data["Name"] = document.Name or "Unnamed"
|
||||
view.append(self.parse_embedded_svg(view_title, data))
|
||||
|
||||
for image in images:
|
||||
|
||||
@@ -88,7 +88,7 @@ class IfcGitData:
|
||||
try:
|
||||
if tool.IfcGitRepo.repo.branches:
|
||||
return tool.IfcGit.branches_by_hexsha(tool.IfcGitRepo.repo)
|
||||
except:
|
||||
except AttributeError:
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -204,7 +204,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
if (
|
||||
building_obj
|
||||
and building_element
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering"]
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering", "IfcElementAssembly"]
|
||||
and instance_class in ["IfcWindow", "IfcDoor"]
|
||||
):
|
||||
# Fills should be a sibling to the building element
|
||||
@@ -234,7 +234,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
if (
|
||||
building_obj
|
||||
and building_element
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering"]
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering", "IfcElementAssembly"]
|
||||
):
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
# TODO For now we are hardcoding windows and doors as a prototype
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
import math
|
||||
from blenderbim.bim.prop import ObjProperty
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from bpy.types import PropertyGroup, NodeTree
|
||||
@@ -692,6 +693,7 @@ class BIMRoofProperties(PropertyGroup):
|
||||
"roof_type",
|
||||
"generation_method",
|
||||
"angle",
|
||||
"percentage",
|
||||
"rafter_edge_angle",
|
||||
)
|
||||
roof_types = (("HIP/GABLE ROOF", "HIP/GABLE ROOF", ""),)
|
||||
@@ -710,7 +712,8 @@ class BIMRoofProperties(PropertyGroup):
|
||||
height: bpy.props.FloatProperty(
|
||||
name="Height", default=1.0, description="Maximum height of the roof to be generated.", subtype="DISTANCE"
|
||||
)
|
||||
angle: bpy.props.FloatProperty(name="Slope Angle", default=pi / 18, subtype="ANGLE")
|
||||
angle: bpy.props.FloatProperty(name="Slope Angle", default=pi / 18, subtype="ANGLE", update=lambda self, context: self.update_percentage())
|
||||
percentage: bpy.props.FloatProperty(name="Slope %", default=17.63269754733197, subtype="PERCENTAGE", update=lambda self, context: self.update_angle())
|
||||
roof_thickness: bpy.props.FloatProperty(name="Roof Thickness", default=0.1, subtype="DISTANCE")
|
||||
rafter_edge_angle: bpy.props.FloatProperty(name="Rafter Edge Angle", min=0, max=pi, default=pi / 2, subtype="ANGLE")
|
||||
|
||||
@@ -727,6 +730,7 @@ class BIMRoofProperties(PropertyGroup):
|
||||
kwargs["height"] = self.height
|
||||
else:
|
||||
kwargs["angle"] = self.angle
|
||||
kwargs["percentage"] = self.percentage
|
||||
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
@@ -736,3 +740,9 @@ class BIMRoofProperties(PropertyGroup):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
def update_angle(self):
|
||||
self.angle = math.atan(self.percentage / 100)
|
||||
|
||||
def update_percentage(self):
|
||||
self.percentage = math.tan(self.angle)*100
|
||||
@@ -683,7 +683,7 @@ class RevertProject(bpy.types.Operator, IFCFileSelector):
|
||||
bl_idname = "bim.revert_project"
|
||||
bl_label = "Revert IFC Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Reload currently opened IFC project discarding all unsaved changes"
|
||||
bl_description = "Revert to a fresh session discarding all unsaved changes"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -211,7 +211,6 @@ class BIM_PT_project(Panel):
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "ifc_file", text="")
|
||||
row.operator("bim.reload_ifc_file", icon="FILE_REFRESH", text="")
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
row.operator("bim.unload_project", text="", icon="CANCEL")
|
||||
else:
|
||||
@@ -399,11 +398,3 @@ class BIM_PT_purge(Panel):
|
||||
layout = self.layout
|
||||
layout.operator("bim.purge_unused_profiles")
|
||||
layout.operator("bim.purge_unused_types")
|
||||
layout.operator("bim.purge_unused_representations")
|
||||
layout.separator()
|
||||
|
||||
layout.label(text="Purge unused elements by class:")
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene.BIMDebugProperties, "ifc_class_purge", text="")
|
||||
row.operator("bim.purge_unused_elements_by_class", text="", icon="TRASH")
|
||||
row.operator("bim.print_unused_elements_stats", text="", icon="INFO")
|
||||
|
||||
@@ -91,7 +91,9 @@ class ObjectPsetsData(Data):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return []
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(element.is_a(), pset_only=True)
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(
|
||||
element.is_a(), ifcopenshell.util.element.get_predefined_type(element), pset_only=True
|
||||
)
|
||||
psetnames = cls.format_pset_enum(psets)
|
||||
assigned_names = ifcopenshell.util.element.get_psets(element, psets_only=True, should_inherit=False).keys()
|
||||
return [p for p in psetnames if p[0] not in assigned_names]
|
||||
@@ -102,7 +104,9 @@ class ObjectPsetsData(Data):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return []
|
||||
qtos = blenderbim.bim.schema.ifc.psetqto.get_applicable(element.is_a(), qto_only=True)
|
||||
qtos = blenderbim.bim.schema.ifc.psetqto.get_applicable(
|
||||
element.is_a(), ifcopenshell.util.element.get_predefined_type(element), qto_only=True
|
||||
)
|
||||
return cls.format_pset_enum(qtos)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -32,6 +32,10 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
import gettext
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
def get_style_types(self, context):
|
||||
if not StylesData.is_loaded:
|
||||
@@ -88,9 +92,9 @@ def update_shader_graph(self, context):
|
||||
|
||||
|
||||
UV_MODES = [
|
||||
("UV", "UV", "Actual UV data presented on the geometry"),
|
||||
("Generated", "Generated", "Automatically-generated UV from the vertex positions of the mesh"),
|
||||
("Camera", "Camera", "UV from position coordinate in camera space"),
|
||||
("UV", "UV", _("Actual UV data presented on the geometry")),
|
||||
("Generated", "Generated", _("Automatically-generated UV from the vertex positions of the mesh")),
|
||||
("Camera", "Camera", _("UV from position coordinate in camera space")),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -142,23 +142,6 @@ class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ReloadSelectedIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.reload_selected_ifc_file"
|
||||
bl_label = "Reload selected IFC File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Reload currently selected IFC file"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
filepath = context.scene.BIMProperties.ifc_file
|
||||
valid_file = os.path.exists(filepath) and "ifc" in os.path.splitext(filepath)[1].lower()
|
||||
if not valid_file:
|
||||
self.report({"ERROR"}, f"Couldn't find .ifc file by the path '{filepath}'")
|
||||
return {"CANCELLED"}
|
||||
context.scene.BIMProperties.ifc_file = context.scene.BIMProperties.ifc_file
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectDataDir(bpy.types.Operator):
|
||||
bl_idname = "bim.select_data_dir"
|
||||
bl_label = "Select Data Directory"
|
||||
|
||||
@@ -489,6 +489,8 @@ class BIMFacet(PropertyGroup):
|
||||
("<=", "lesser than or equal to", ""),
|
||||
(">", "greater than", ""),
|
||||
("<", "less than", ""),
|
||||
("*=", "contains", ""),
|
||||
("!*=", "does not contain", ""),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -65,16 +65,20 @@ class IFCFileSelector:
|
||||
split = box.split()
|
||||
split.label(text=key.title().replace("_", " "))
|
||||
split.label(text=str(value))
|
||||
if (
|
||||
key.lower() == "schema_name"
|
||||
and str(value).lower() == "ifc2x3"
|
||||
and filepath[-4:].lower() == ".ifc"
|
||||
):
|
||||
row = box.row()
|
||||
op = row.operator("bim.run_migrate_patch", text="Upgrade to IFC4")
|
||||
op.infile = filepath
|
||||
op.outfile = filepath[0:-4] + "-IFC4.ifc"
|
||||
op.schema = "IFC4"
|
||||
if key.lower() == "schema_name" and filepath[-4:].lower() == ".ifc":
|
||||
schema_lower = str(value).lower()
|
||||
if schema_lower == "ifc2x3":
|
||||
row = box.row()
|
||||
op = row.operator("bim.run_migrate_patch", text="Upgrade to IFC4")
|
||||
op.infile = filepath
|
||||
op.outfile = filepath[0:-4] + "-IFC4.ifc"
|
||||
op.schema = "IFC4"
|
||||
elif schema_lower == "ifc4":
|
||||
row = box.row()
|
||||
op = row.operator("bim.run_migrate_patch", text="Upgrade to IFC4X3")
|
||||
op.infile = filepath
|
||||
op.outfile = filepath[0:-4] + "-IFC4X3.ifc"
|
||||
op.schema = "IFC4X3"
|
||||
|
||||
if bpy.data.is_saved:
|
||||
layout.prop(self, "use_relative_path")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
def add_cost_schedule(ifc, name, predefined_type, object_type):
|
||||
ifc.run("cost.add_cost_schedule", name=name, predefined_type=predefined_type, object_type=object_type)
|
||||
def add_cost_schedule(ifc, name, predefined_type):
|
||||
ifc.run("cost.add_cost_schedule", name=name, predefined_type=predefined_type)
|
||||
|
||||
|
||||
def edit_cost_schedule(ifc, cost, cost_schedule):
|
||||
|
||||
@@ -166,6 +166,13 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
|
||||
bpy.context.window_manager.popup_menu(message_ui, title=message_type.capitalize(), icon=message_type)
|
||||
|
||||
@classmethod
|
||||
def get_view3d_area(cls):
|
||||
for window in bpy.context.window_manager.windows:
|
||||
for area in window.screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
return area
|
||||
|
||||
@classmethod
|
||||
def get_blender_prop_default_value(cls, props, prop_name):
|
||||
prop_bl_rna = props.bl_rna.properties[prop_name]
|
||||
@@ -184,7 +191,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
It's a bit naive since it's just taking the first available `VIEW_3D` area
|
||||
when in real life you can have a couple of those but should work for the most cases.
|
||||
"""
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area = cls.get_view3d_area()
|
||||
region = next(region for region in area.regions if region.type == "WINDOW")
|
||||
space = next(space for space in area.spaces if space.type == "VIEW_3D")
|
||||
context_override = {"area": area, "region": region, "space_data": space}
|
||||
|
||||
@@ -1592,12 +1592,8 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
@classmethod
|
||||
def is_drawing_active(cls):
|
||||
camera = bpy.context.scene.camera
|
||||
return (
|
||||
camera
|
||||
and camera.type == "CAMERA"
|
||||
and camera.BIMObjectProperties.ifc_definition_id
|
||||
and any(a.type == "VIEW_3D" for a in bpy.context.screen.areas)
|
||||
)
|
||||
area = tool.Blender.get_view3d_area()
|
||||
return camera and camera.type == "CAMERA" and camera.BIMObjectProperties.ifc_definition_id and area
|
||||
|
||||
@classmethod
|
||||
def is_camera_orthographic(cls):
|
||||
@@ -1614,7 +1610,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def activate_drawing(cls, camera):
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area = tool.Blender.get_view3d_area()
|
||||
is_local_view = area.spaces[0].local_view is not None
|
||||
if is_local_view:
|
||||
bpy.ops.view3d.localview()
|
||||
@@ -1742,8 +1738,8 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
local_x = [v.x for v in local_bbox]
|
||||
local_y = [v.y for v in local_bbox]
|
||||
local_z = [v.z for v in local_bbox]
|
||||
aabb1_min = (-x/2, -y/2, -clip_end)
|
||||
aabb1_max = (x/2, y/2, -clip_start)
|
||||
aabb1_min = (-x / 2, -y / 2, -clip_end)
|
||||
aabb1_max = (x / 2, y / 2, -clip_start)
|
||||
aabb2_min = (min(local_x), min(local_y), min(local_z))
|
||||
aabb2_max = (max(local_x), max(local_y), max(local_z))
|
||||
for i in range(3):
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
# allows git import even if git executable isn't found
|
||||
os.environ["GIT_PYTHON_REFRESH"] = "quiet"
|
||||
try:
|
||||
import git
|
||||
except:
|
||||
print("Warning: GitPython not available.")
|
||||
import bpy
|
||||
import logging
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.tool as tool
|
||||
|
||||
# allows git import even if git executable isn't found
|
||||
os.environ["GIT_PYTHON_REFRESH"] = "quiet"
|
||||
try:
|
||||
import git
|
||||
except ImportError:
|
||||
print("Warning: GitPython not available.")
|
||||
|
||||
class IfcGit:
|
||||
@classmethod
|
||||
@@ -54,17 +53,19 @@ class IfcGit:
|
||||
else:
|
||||
return None
|
||||
|
||||
if IfcGitRepo.repo != None and IfcGitRepo.repo.working_dir == path_dir:
|
||||
if IfcGitRepo.repo is not None and IfcGitRepo.repo.working_dir == path_dir:
|
||||
return IfcGitRepo.repo
|
||||
|
||||
try:
|
||||
repo = git.Repo(path_dir)
|
||||
except:
|
||||
except git.exc.InvalidGitRepositoryError:
|
||||
parentdir_path = os.path.abspath(os.path.join(path_dir, os.pardir))
|
||||
if parentdir_path == path_dir:
|
||||
# root folder
|
||||
return None
|
||||
return cls.repo_from_path(parentdir_path)
|
||||
except git.exc.NoSuchPathError:
|
||||
return None
|
||||
if repo:
|
||||
IfcGitRepo.repo = repo
|
||||
return repo
|
||||
@@ -181,9 +182,9 @@ class IfcGit:
|
||||
|
||||
for commit in commits:
|
||||
|
||||
if props.ifcgit_filter == "tagged" and not commit.hexsha in lookup:
|
||||
if props.ifcgit_filter == "tagged" and commit.hexsha not in lookup:
|
||||
continue
|
||||
elif props.ifcgit_filter == "relevant" and not commit in commits_relevant:
|
||||
elif props.ifcgit_filter == "relevant" and commit not in commits_relevant:
|
||||
continue
|
||||
|
||||
props.ifcgit_commits.add()
|
||||
@@ -485,8 +486,7 @@ class IfcGit:
|
||||
cls.dos2unix(path_ifc)
|
||||
repo.index.add(path_ifc)
|
||||
repo.git.commit("--no-edit")
|
||||
except:
|
||||
|
||||
except git.exc.GitError:
|
||||
operator.report({"ERROR"}, "Unknown IFC Merge failure")
|
||||
return False
|
||||
|
||||
@@ -506,7 +506,7 @@ class IfcGit:
|
||||
query = "/^#" + str(step_id) + "[ =]/,/;/:" + relpath_ifc
|
||||
try:
|
||||
logtext = repo.git.log("-L", query, "-s")
|
||||
except:
|
||||
except git.exc.CommandError:
|
||||
logtext = "No Git history found :("
|
||||
return logtext
|
||||
|
||||
|
||||
@@ -56,7 +56,12 @@ class Pset(blenderbim.core.tool.Pset):
|
||||
|
||||
@classmethod
|
||||
def is_pset_applicable(cls, element, pset_name):
|
||||
return bool(pset_name in blenderbim.bim.schema.ifc.psetqto.get_applicable_names(element.is_a(), pset_only=True))
|
||||
return bool(
|
||||
pset_name
|
||||
in blenderbim.bim.schema.ifc.psetqto.get_applicable_names(
|
||||
element.is_a(), ifcopenshell.util.element.get_predefined_type(element), pset_only=True
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def is_pset_empty(cls, pset):
|
||||
@@ -69,7 +74,7 @@ class Pset(blenderbim.core.tool.Pset):
|
||||
|
||||
@classmethod
|
||||
def enable_pset_editing(cls, pset_id=None, pset_name=None, pset_type=None, obj=None, obj_type=None):
|
||||
#TODO REFACTOR ONCE toll/CORE functions are available
|
||||
# TODO REFACTOR ONCE toll/CORE functions are available
|
||||
bpy.ops.bim.enable_pset_editing(
|
||||
pset_id=0, pset_name=tool.Pset.get_pset_name(obj, obj_type), pset_type="PSET", obj=obj, obj_type=obj_type
|
||||
)
|
||||
)
|
||||
|
||||
@@ -70,7 +70,9 @@ class Qto(blenderbim.core.tool.Qto):
|
||||
def get_applicable_base_quantity_name(cls, product=None):
|
||||
if not product:
|
||||
return
|
||||
applicable_qto_names = blenderbim.bim.schema.ifc.psetqto.get_applicable_names(product.is_a(), qto_only=True)
|
||||
applicable_qto_names = blenderbim.bim.schema.ifc.psetqto.get_applicable_names(
|
||||
product.is_a(), ifcopenshell.util.element.get_predefined_type(product), qto_only=True
|
||||
)
|
||||
return next((qto_name for qto_name in applicable_qto_names if "Qto_" in qto_name and "Base" in qto_name), None)
|
||||
|
||||
@classmethod
|
||||
@@ -205,12 +207,12 @@ class Qto(blenderbim.core.tool.Qto):
|
||||
if quantity == cost_item_quantity:
|
||||
result.append(
|
||||
{
|
||||
"cost_item_id" : cost_item.id(),
|
||||
"cost_item_name" : cost_item.Name,
|
||||
"quantity_id" : quantity.id(),
|
||||
"quantity_name" : quantity.Name,
|
||||
"quantity_value" : quantity[3],
|
||||
"quantity_type" : quantity.is_a(),
|
||||
"cost_item_id": cost_item.id(),
|
||||
"cost_item_name": cost_item.Name,
|
||||
"quantity_id": quantity.id(),
|
||||
"quantity_name": quantity.Name,
|
||||
"quantity_value": quantity[3],
|
||||
"quantity_type": quantity.is_a(),
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -144,7 +144,7 @@ class ImportFilterQueryTransformer(lark.Transformer):
|
||||
new2.pset = arg["pset"]
|
||||
if "comparison" in arg:
|
||||
new2.comparison = arg["comparison"]
|
||||
######################################################################
|
||||
|
||||
def facet(self, args):
|
||||
return args[0]
|
||||
|
||||
@@ -169,8 +169,7 @@ class ImportFilterQueryTransformer(lark.Transformer):
|
||||
|
||||
def property(self, args):
|
||||
pset, prop, comparison, value = args
|
||||
#return {"type": "property", "pset": pset, "name": prop, "value": f"{comparison}{value}"}
|
||||
return {"type": "property", "pset": pset, "name": prop, "comparison" : comparison, "value": f"{value}"}
|
||||
return {"type": "property", "pset": pset, "name": prop, "comparison": comparison, "value": f"{value}"}
|
||||
|
||||
def classification(self, args):
|
||||
comparison, value = args
|
||||
@@ -189,19 +188,24 @@ class ImportFilterQueryTransformer(lark.Transformer):
|
||||
return {"type": "query", "name": keys, "value": f"{comparison}{value}"}
|
||||
|
||||
def comparison(self, args):
|
||||
if args[0].data == "equals":
|
||||
return "="
|
||||
elif args[0].data == "morethanequalto":
|
||||
return ">="
|
||||
elif args[0].data == "lessthanequalto":
|
||||
return "<="
|
||||
elif args[0].data == "morethan":
|
||||
return ">"
|
||||
elif args[0].data == "lessthan":
|
||||
return "<"
|
||||
if args[0].data == "not":
|
||||
comparison = args[1].data
|
||||
is_not = "!"
|
||||
else:
|
||||
return "!="
|
||||
#return "" if args[0].data == "equals" else "!="
|
||||
comparison = args[0].data
|
||||
is_not = ""
|
||||
|
||||
return (
|
||||
is_not
|
||||
+ {
|
||||
"equals": "=",
|
||||
"morethanequalto": ">=",
|
||||
"lessthanequalto": "<=",
|
||||
"morethan": ">",
|
||||
"lessthan": "<",
|
||||
"contains": "*=",
|
||||
}[comparison]
|
||||
)
|
||||
|
||||
def keys(self, args):
|
||||
return self.value(args)
|
||||
|
||||
@@ -16,19 +16,24 @@ you will need to categorise your 3D elements (such as "Wall", "Column",
|
||||
Creating a single object
|
||||
------------------------
|
||||
|
||||
In the **Properties** panel in the bottom right, activate the icon for the
|
||||
**Scene Properties** tab, and find the **IFC Project** subpanel. Click on
|
||||
**Create Project** to create a blank IFC project.
|
||||
In the **Properties** panel on the right, ensure the icon for the **Scene
|
||||
Properties** tab is active showing the **Project Overview**. Click on **Create
|
||||
Project** to create a blank IFC project.
|
||||
|
||||
.. image:: images/create-project.png
|
||||
|
||||
In the top right **Outliner** panel, you will see a hierarchy of spaces that has
|
||||
been automatically created for you. This hierarchy is known is the **Spatial
|
||||
Tree**. The Camera, Cube, and Light come with a fresh new Blender session by
|
||||
default, and are not part of your IFC project.
|
||||
In the left **Outliner** panel, you will see a hierarchy of spaces that has
|
||||
been automatically created for you. This hierarchy is known is the **Spatial
|
||||
Tree**.
|
||||
|
||||
.. image:: images/default-spatial-tree.png
|
||||
|
||||
There are many ways to create objects. In practice, you should use an element
|
||||
type library, and we will see how to do this later. For now, we will only add a
|
||||
single element. In the **Add** menu, add a cube.
|
||||
|
||||
.. image:: images/add-cube.png
|
||||
|
||||
Any Blender object that you want to be part of IFC project must be converted
|
||||
into a IFC object by assigning a category. This category is known as the **IFC
|
||||
Class**.
|
||||
@@ -39,11 +44,11 @@ Class**.
|
||||
<https://blenderbim.org/search-ifc-class.html>`__ to help choose an **IFC
|
||||
Class**!
|
||||
|
||||
Select only the default Blender Cube (selected objects are highlighted in
|
||||
orange, careful not to select anything else!), switch to the **Object
|
||||
Properties** tab, and find the **IFC Class** panel. Let's pretend our Cube is a
|
||||
column, so select **IfcColumn** from the **Class** drop-down, and press **Assign
|
||||
IFC Class**.
|
||||
Select the cube (selected objects are highlighted in orange, careful not to
|
||||
select anything else!) and switch to the **Object Information** tab. Let's
|
||||
pretend our Cube is a column, so select **IfcElement** from the **Products**
|
||||
dropdown, **IfcColumn** from the **Class** drop-down, and press **Assign IFC
|
||||
Class**.
|
||||
|
||||
.. image:: images/assign-class.png
|
||||
|
||||
@@ -53,13 +58,12 @@ IFC Class**.
|
||||
the shape of your object. You can have a monkey-shaped wall if you want!
|
||||
|
||||
All IFC objects must also belong inside the **Spatial Tree**. In the
|
||||
**Outliner** panel, you can drag and drop your newly created **IfcColumn/Cube**
|
||||
inside the **IfcBuildingStorey/Ground Floor**. Wasn't that simple?
|
||||
**Outliner** panel, you will see that your newly created **IfcColumn/Cube** has
|
||||
been automatically placed in **IfcBuildingStorey/My Storey**.
|
||||
|
||||
.. image:: images/drag-drop-outliner-tree.png
|
||||
.. image:: images/outliner-cube.png
|
||||
|
||||
Go back to the **Scene Properties** tab and to the **IFC Project** panel and
|
||||
press **Save Project**. Save your new IFC model somewhere on your computer.
|
||||
In the top left **File** menu, Save your new IFC model on your computer.
|
||||
|
||||
.. image:: images/save-project.png
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,375 @@
|
||||
try:
|
||||
import bpy
|
||||
import bl_i18n_utils
|
||||
import addon_utils
|
||||
|
||||
BPY_IS_LOADED = True
|
||||
except ModuleNotFoundError:
|
||||
BPY_IS_LOADED = False
|
||||
|
||||
import shutil
|
||||
import tempfile
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
bl_info = {
|
||||
"name": "BlenderBIM Translations",
|
||||
"description": "",
|
||||
"author": "IfcOpenShell Contributors",
|
||||
"blender": (2, 80, 0),
|
||||
"version": (0, 0, 999999),
|
||||
"location": "Properties -> Render -> BBIM Update Translation",
|
||||
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
|
||||
"category": "System",
|
||||
}
|
||||
|
||||
# NOTE: list of available languages in Blender can be retrieved
|
||||
# by the command below:
|
||||
# bpy.context.preferences.view.language = "test"
|
||||
ADDON_NAME = "blenderbim"
|
||||
TRANSLATION_UI_IS_LOADED = False
|
||||
|
||||
|
||||
def is_addon_loaded(addon_name) -> bool:
|
||||
loaded_default, loaded_state = addon_utils.check(addon_name)
|
||||
return loaded_state
|
||||
|
||||
|
||||
def get_branches_directory():
|
||||
return Path(bpy.context.preferences.filepaths.i18n_branches_directory)
|
||||
|
||||
|
||||
def get_addon_directory() -> Path:
|
||||
addon_module = importlib.import_module(ADDON_NAME)
|
||||
return Path(addon_module.__file__).parent
|
||||
|
||||
|
||||
def rearrange_files_for_po_import(po_dir_path: Path, temp_directory: tempfile.TemporaryDirectory):
|
||||
"""I18n directory also has a bit different format then `ui_translate.export/import`,
|
||||
every .po file has a parent folder with the same name.
|
||||
So we rearrange the data that way"""
|
||||
# NOTE: TemporaryDirectory is cleared automatically as variable goes out of scope
|
||||
# so we expect it as an argument so it won't get cleared right away
|
||||
|
||||
temp_po_dir_path = Path(temp_directory.name)
|
||||
for file in po_dir_path.glob("**/*"):
|
||||
if file.suffix != ".po":
|
||||
continue
|
||||
shutil.copy(file, temp_po_dir_path / file.name)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
msgid: str
|
||||
msgctxt: str | None
|
||||
sources: Optional[List[str]] = field(default_factory=list)
|
||||
# mapping languages to translated strings
|
||||
translations: Optional[Dict[str, str]] = field(default_factory=dict)
|
||||
|
||||
|
||||
def blenderbim_strings_parse(addon_directory=None, po_directory=None):
|
||||
# NOTE: we decided to use our own parser due bug in Blender parser
|
||||
# as it tends to pick up strings from other addons and other Blender parts
|
||||
# and this bug probably would be to low of a priority for Blender to fix
|
||||
# ref: https://projects.blender.org/blender/blender/issues/116579
|
||||
|
||||
if addon_directory is None:
|
||||
addon_directory = get_addon_directory()
|
||||
directory = addon_directory / "bim"
|
||||
|
||||
if po_directory is None:
|
||||
po_directory = get_branches_directory()
|
||||
|
||||
patterns = [
|
||||
r'bl_label\s*=\s*"(.*)"', # operator labels
|
||||
r'bl_description\s*=\s*"(.*)"', # operator descriptions
|
||||
r'text\s*=\s*"(.*?)"', # UI labels
|
||||
r'Property\(.*?name\s*=\s*"(.*?)"', # property names
|
||||
r'report\(\{.*?\}\s*,\s*"(.*?)"', # operator reports
|
||||
r'info\(\{.*?\}\s*,\s*"(.*?)"', # operator info
|
||||
r'\b_\("(.*?)"\)', # gettext called with `_`
|
||||
]
|
||||
regexes = [re.compile(pattern) for pattern in patterns]
|
||||
matched_dict: Dict[str, Message] = dict()
|
||||
|
||||
# NOTE: currently there is no special handling same message with different contexts
|
||||
for root, dirs, files in os.walk(directory):
|
||||
root = Path(root)
|
||||
for file in files:
|
||||
if not file.endswith(".py"):
|
||||
continue
|
||||
filepath = root / file
|
||||
source_rel_path = filepath.relative_to(addon_directory).as_posix()
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
for i, regex in enumerate(regexes):
|
||||
# NOTE: operator tooltips doesnt seem to need Operator context
|
||||
is_operator = i < 1
|
||||
for regex_match in regex.finditer(content):
|
||||
string = regex_match.group(1)
|
||||
if string == "":
|
||||
continue
|
||||
elif "{" in string:
|
||||
# Includes a formatting string. To fix.
|
||||
# print(match.group(1))
|
||||
continue
|
||||
elif is_operator:
|
||||
ctx = "Operator"
|
||||
else:
|
||||
ctx = None
|
||||
message = matched_dict.get(string)
|
||||
if message is None:
|
||||
message = Message(string, ctx)
|
||||
matched_dict[string] = message
|
||||
elif ctx != message.msgctxt and False:
|
||||
print(
|
||||
f'WARNING. Message "{string}" was already registered with different context {message.context}. '
|
||||
f"Current context: {ctx}. File: {source_rel_path}"
|
||||
)
|
||||
message.sources.append(source_rel_path)
|
||||
|
||||
pot_filepath = po_directory / "blenderbim.pot"
|
||||
with open(pot_filepath, "w", encoding="utf-8") as fo:
|
||||
for msg in matched_dict.values():
|
||||
# make sure sources are unique but keep the order
|
||||
sources = list(dict.fromkeys(msg.sources))
|
||||
for source in sources:
|
||||
fo.write(f"#: {source}\n")
|
||||
if msg.msgctxt != None:
|
||||
fo.write(f'msgctxt "{msg.msgctxt}"\n')
|
||||
fo.write(f'msgid "{msg.msgid}"\n')
|
||||
fo.write('msgstr ""\n')
|
||||
fo.write("\n")
|
||||
|
||||
|
||||
def update_translations_from_po(po_directory: Path, translations_module: Path):
|
||||
translation_data: Dict[str, Message] = dict()
|
||||
|
||||
def process_po_entry(language, current_chunk: list[str]):
|
||||
sources = []
|
||||
msgid = None
|
||||
msgstr = None
|
||||
msgctxt = None
|
||||
parse_message_attr = lambda line, attr_name: line.removeprefix(f'{attr_name} "').removesuffix('"')
|
||||
|
||||
for line in current_chunk:
|
||||
line = line.strip()
|
||||
if line.startswith('msgid "'):
|
||||
msgid = parse_message_attr(line, "msgid")
|
||||
elif line.startswith('msgstr "'):
|
||||
msgstr = parse_message_attr(line, "msgstr")
|
||||
elif line.startswith('msgctxt "'):
|
||||
msgctxt = parse_message_attr(line, "msgctxt")
|
||||
elif line.startswith("#:"):
|
||||
sources.append(line.removeprefix("# ").strip())
|
||||
|
||||
msg = translation_data.get(msgid)
|
||||
if msg is None:
|
||||
msg = Message(msgid, msgctxt, sources, {language: msgstr})
|
||||
translation_data[msgid] = msg
|
||||
else:
|
||||
msg.sources.extend(sources)
|
||||
msg.translations[language] = msgstr
|
||||
|
||||
# load data from .po files
|
||||
langs = set()
|
||||
for po_file_path in po_directory.glob("**/*.po"):
|
||||
lang = po_file_path.stem
|
||||
langs.add(lang)
|
||||
with open(po_file_path, "r") as po_file:
|
||||
current_chunk = []
|
||||
for line in po_file:
|
||||
current_chunk.append(line)
|
||||
if line.startswith("msgstr"):
|
||||
process_po_entry(lang, current_chunk)
|
||||
current_chunk = []
|
||||
|
||||
# generate translations.py file
|
||||
tab = " "
|
||||
default_context = "*"
|
||||
ret = ["translations_dict = {"]
|
||||
|
||||
for lang in langs:
|
||||
ret.append(f'{tab}"{lang}": {{')
|
||||
for msgid, msg in translation_data.items():
|
||||
if (msgstr := msg.translations[lang]) in (None, ""):
|
||||
continue
|
||||
msgctxt = msg.msgctxt
|
||||
if not msgctxt:
|
||||
msgctxt = default_context
|
||||
ret.append(f"{tab*2}({msgctxt!r}, {msgid!r}): {msgstr!r},")
|
||||
ret.append(f"{tab}}},")
|
||||
|
||||
ret.append("}")
|
||||
|
||||
with open(translations_module / "translations.py", "w") as fo:
|
||||
fo.write("\n".join(ret))
|
||||
|
||||
|
||||
if BPY_IS_LOADED:
|
||||
|
||||
class SetupTranslationUI(bpy.types.Operator):
|
||||
bl_idname = "bim.setup_translation_ui"
|
||||
bl_label = "Setup Translation UI"
|
||||
bl_options = set()
|
||||
|
||||
def execute(self, context):
|
||||
if not is_addon_loaded("ui_translate"):
|
||||
addon_utils.enable("ui_translate", default_set=True)
|
||||
self.report({"INFO"}, '"Manage UI translations" addon was not enabled, it\'s enabled now.')
|
||||
|
||||
addon_prefs = context.preferences.addons["ui_translate"].preferences
|
||||
|
||||
def is_valid_path(path: Path):
|
||||
return path.is_dir() and path.is_absolute()
|
||||
|
||||
# check branches directory
|
||||
branches_dir = get_branches_directory()
|
||||
if not is_valid_path(branches_dir):
|
||||
raise Exception(
|
||||
f"I18n Branches directory is not set up or doesn't exist ({branches_dir}).\n"
|
||||
"Setup I18n branches directory below (or in Preferences > File Paths > Development > I18n Branches) "
|
||||
"to a folder containing (or that will contain) .po files."
|
||||
)
|
||||
|
||||
# check translations directory
|
||||
i18n_dir = Path(addon_prefs.I18N_DIR)
|
||||
# we won't really use the translations directory, we just need addon to stop complaining about it
|
||||
if not is_valid_path(i18n_dir):
|
||||
addon_prefs.I18N_DIR = str(branches_dir)
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f'Translations directory ({i18n_dir}) doesn\'t exist. It was reset to I18n branches directory: "{branches_dir}"',
|
||||
)
|
||||
|
||||
# check source directory
|
||||
source_dir = Path(addon_prefs.SOURCE_DIR)
|
||||
default_source_path = Path(bpy.app.binary_path).parent / ".".join([str(i) for i in bpy.app.version[:2]])
|
||||
|
||||
if not is_valid_path(source_dir):
|
||||
addon_prefs.SOURCE_DIR = str(default_source_path)
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f'Source directory ({source_dir}) doesn\'t exist. It was reset to default directory: "{default_source_path}"',
|
||||
)
|
||||
source_dir = default_source_path
|
||||
|
||||
source_locale_path = source_dir / "locale/po"
|
||||
# Blender UI translations also expect "scripts/presets/keyconfig" to be present in SOURCE_DIR
|
||||
# but default directory has it by default
|
||||
if not source_locale_path.is_dir():
|
||||
source_locale_path.mkdir(parents=True, exist_ok=True)
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f"Couldn't find locale path in the source directory, creating dummy directory: {source_locale_path}.",
|
||||
)
|
||||
|
||||
from ui_translate.settings import settings as ui_translate_settings
|
||||
from ui_translate.update_ui import UI_OT_i18n_updatetranslation_init_settings
|
||||
|
||||
i18n_settings = context.window_manager.i18n_update_settings
|
||||
if not i18n_settings.is_init:
|
||||
# if it's not loaded yet, we'll try to reload it one more time
|
||||
# since we default values we set up during the current operator might helped
|
||||
UI_OT_i18n_updatetranslation_init_settings.execute_static(context, ui_translate_settings)
|
||||
if not i18n_settings.is_init:
|
||||
raise Exception(
|
||||
"UI Translation settings are not initalized. Make sure the following directories exist:\n"
|
||||
f" - {ui_translate_settings.WORK_DIR}\n"
|
||||
f" - {ui_translate_settings.BLENDER_I18N_PO_DIR}\n"
|
||||
)
|
||||
|
||||
global TRANSLATION_UI_IS_LOADED
|
||||
TRANSLATION_UI_IS_LOADED = True
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
class ParseBlenderBIMStrings(bpy.types.Operator):
|
||||
bl_idname = "bim.parse_blenderbim_strings"
|
||||
bl_label = "Parse BlenderBIM strings To .pot"
|
||||
bl_description = "Parse strings from BlenderBIM and save to .pot"
|
||||
bl_options = set()
|
||||
|
||||
def execute(self, context):
|
||||
blenderbim_strings_parse()
|
||||
self.report({"INFO"}, "String were parsed and saved to .pot file.")
|
||||
return {"FINISHED"}
|
||||
|
||||
class UpdateTranslationsFromPo(bpy.types.Operator):
|
||||
bl_idname = "bim.update_translations_from_po"
|
||||
bl_label = "Update Translations From .po"
|
||||
bl_description = (
|
||||
"Load translation strings from po files at I18n Branches back to translations.py\n"
|
||||
"Also updates current addon translations in UI"
|
||||
)
|
||||
bl_options = set()
|
||||
|
||||
def execute(self, context):
|
||||
branches_dir = get_branches_directory()
|
||||
update_translations_from_po(branches_dir, get_addon_directory())
|
||||
|
||||
if is_addon_loaded(ADDON_NAME):
|
||||
bpy.app.translations.unregister(ADDON_NAME)
|
||||
addon_module = importlib.import_module(ADDON_NAME)
|
||||
translations_module = getattr(addon_module, "translations")
|
||||
importlib.reload(translations_module)
|
||||
bpy.app.translations.register(ADDON_NAME, translations_module.translations_dict)
|
||||
|
||||
self.report({"INFO"}, f"Addon's translation updated from .po in {branches_dir}")
|
||||
return {"FINISHED"}
|
||||
|
||||
class BBIM_PT_translations(bpy.types.Panel):
|
||||
bl_label = "BlenderBIM Translations"
|
||||
bl_idname = "BBIM_PT_translations"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(context.preferences.view, "language")
|
||||
layout.prop(context.preferences.filepaths, "i18n_branches_directory")
|
||||
|
||||
if not TRANSLATION_UI_IS_LOADED:
|
||||
layout.operator("bim.setup_translation_ui")
|
||||
return
|
||||
|
||||
layout.label(text="Developer UI:")
|
||||
layout.operator("bim.parse_blenderbim_strings", icon="FILE_REFRESH")
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Translator UI:")
|
||||
layout.operator("bim.update_translations_from_po", icon="IMPORT")
|
||||
|
||||
classes = (
|
||||
ParseBlenderBIMStrings,
|
||||
UpdateTranslationsFromPo,
|
||||
SetupTranslationUI,
|
||||
BBIM_PT_translations,
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
def unregister():
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example:
|
||||
# py src/blenderbim/scripts/bbim_translations.py -i "C:/blenderbim-translations" -o "C:/Blender/4.0/scripts/addons/blenderbim/translations.py"
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Converts .po files to translations.py")
|
||||
parser.add_argument("-i", "--input", type=str, required=True, help="Directory with .po files")
|
||||
parser.add_argument(
|
||||
"-o", "--output", type=str, required=True, help="translations.py module location (file may not exist yet)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
update_translations_from_po(po_directory=Path(args.input), translations_module=Path(args.output))
|
||||
@@ -114,14 +114,14 @@ std::string format_string(const Argument* argument) {
|
||||
template <typename Schema, typename T>
|
||||
void process_pset(element_properties& props, const T* inst) {
|
||||
// Process an individual Property or Quantity set.
|
||||
if (auto pset = inst->as<Schema::IfcPropertySet>()) {
|
||||
if (auto pset = inst->template as<typename Schema::IfcPropertySet>()) {
|
||||
if (!pset->Name()) {
|
||||
return;
|
||||
}
|
||||
auto ps = pset->HasProperties();
|
||||
for (auto it = ps->begin(); it != ps->end(); ++it) {
|
||||
auto& p = *it;
|
||||
if (auto singleval = p->as<typename Schema::IfcPropertySingleValue>()) {
|
||||
if (auto singleval = p->template as<typename Schema::IfcPropertySingleValue>()) {
|
||||
std::string propname, propvalue;
|
||||
if constexpr (is_ifc4_or_higher<Schema>::value) {
|
||||
if (!singleval->Name()) {
|
||||
@@ -135,26 +135,26 @@ void process_pset(element_properties& props, const T* inst) {
|
||||
if (!singleval->NominalValue()) {
|
||||
propvalue = "-";
|
||||
} else {
|
||||
props[*pset->Name()][propname] = format_string(singleval->NominalValue()->as<IfcUtil::IfcBaseClass>()->data().getArgument(0));
|
||||
props[*pset->Name()][propname] = format_string(singleval->NominalValue()->template as<IfcUtil::IfcBaseClass>()->data().getArgument(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto qset = inst->as<Schema::IfcElementQuantity>()) {
|
||||
if (auto qset = inst->template as<typename Schema::IfcElementQuantity>()) {
|
||||
if (!qset->Name()) {
|
||||
return;
|
||||
}
|
||||
auto qs = qset->Quantities();
|
||||
for (auto it = qs->begin(); it != qs->end(); ++it) {
|
||||
auto& q = *it;
|
||||
if (q->as<typename Schema::IfcPhysicalSimpleQuantity>() && q->data().getArgument(3)->type() == IfcUtil::Argument_DOUBLE) {
|
||||
if (q->template as<typename Schema::IfcPhysicalSimpleQuantity>() && q->data().getArgument(3)->type() == IfcUtil::Argument_DOUBLE) {
|
||||
double v = *q->data().getArgument(3);
|
||||
props[*qset->Name()][q->Name()] = std::to_string(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
if constexpr (is_ifc4_or_higher<Schema>::value) {
|
||||
if (auto extprops = inst->as<Schema::IfcExtendedProperties>()) {
|
||||
if (auto extprops = inst->template as<Schema::IfcExtendedProperties>()) {
|
||||
// @todo
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ void process_pset(element_properties& props, const T* inst) {
|
||||
template <typename Schema>
|
||||
void get_psets_s(element_properties& props, const typename Schema::IfcObjectDefinition* inst) {
|
||||
// Extracts the property definitions for an IFC instance.
|
||||
if (auto tyob = inst->as<typename Schema::IfcTypeObject>()) {
|
||||
if (auto tyob = inst->template as<typename Schema::IfcTypeObject>()) {
|
||||
if (tyob->HasPropertySets()) {
|
||||
auto defs = *tyob->HasPropertySets();
|
||||
for (auto it = defs->begin(); it != defs->end(); ++it) {
|
||||
@@ -173,14 +173,14 @@ void get_psets_s(element_properties& props, const typename Schema::IfcObjectDefi
|
||||
}
|
||||
}
|
||||
if constexpr (is_ifc4_or_higher<Schema>::value) {
|
||||
if (auto mdef = inst->as<typename Schema::IfcMaterialDefinition>()) {
|
||||
if (auto mdef = inst->template as<typename Schema::IfcMaterialDefinition>()) {
|
||||
auto defs = mdef->HasProperties();
|
||||
for (auto it = defs->begin(); it != defs->end(); ++it) {
|
||||
auto& def = *it;
|
||||
process_pset<Schema>(props, def);
|
||||
}
|
||||
}
|
||||
if (auto pdef = inst->as<typename Schema::IfcProfileDef>()) {
|
||||
if (auto pdef = inst->template as<typename Schema::IfcProfileDef>()) {
|
||||
auto defs = pdef->HasProperties();
|
||||
for (auto it = defs->begin(); it != defs->end(); ++it) {
|
||||
auto& def = *it;
|
||||
@@ -188,7 +188,7 @@ void get_psets_s(element_properties& props, const typename Schema::IfcObjectDefi
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto ob = inst->as<typename Schema::IfcObject>()) {
|
||||
if (auto ob = inst->template as<typename Schema::IfcObject>()) {
|
||||
if constexpr (is_ifc4_or_higher<Schema>::value) {
|
||||
auto rels = ob->IsTypedBy();
|
||||
for (auto it = rels->begin(); it != rels->end(); ++it) {
|
||||
@@ -200,9 +200,9 @@ void get_psets_s(element_properties& props, const typename Schema::IfcObjectDefi
|
||||
auto rels = ob->IsDefinedBy();
|
||||
for (auto it = rels->begin(); it != rels->end(); ++it) {
|
||||
auto& rel = *it;
|
||||
if (auto bytype = rel->as<typename Schema::IfcRelDefinesByType>()) {
|
||||
if (auto bytype = rel->template as<typename Schema::IfcRelDefinesByType>()) {
|
||||
get_psets_s<Schema>(props, bytype->RelatingType());
|
||||
} else if (auto byprops = rel->as<typename Schema::IfcRelDefinesByProperties>()) {
|
||||
} else if (auto byprops = rel->template as<typename Schema::IfcRelDefinesByProperties>()) {
|
||||
process_pset<Schema>(props, byprops->RelatingPropertyDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Use Miniconda base image
|
||||
FROM continuumio/miniconda3:4.10.3
|
||||
|
||||
# Update Conda, install necessary libraries, and then install Mamba
|
||||
RUN conda update -n base -c defaults conda && \
|
||||
conda install libarchive -c conda-forge -y && \
|
||||
conda install mamba -c conda-forge -y
|
||||
|
||||
# Install Code_Aster and Python dependencies with Conda
|
||||
RUN mamba install -c conda-forge code-aster python=3.10 -y
|
||||
@@ -0,0 +1,19 @@
|
||||
# Ifc2CA - IFC Code_Aster utility
|
||||
# Copyright (C) 2020, 2021, 2023, 2024 Ioannis P. Christovasilis <ipc@aethereng.com>
|
||||
#
|
||||
# This file is part of Ifc2CA.
|
||||
#
|
||||
# Ifc2CA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ifc2CA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Ifc2CA. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .ifc2ca import Ifc2CA
|
||||
@@ -0,0 +1,525 @@
|
||||
|
||||
# Ifc2CA - IFC Code_Aster utility
|
||||
# Copyright (C) 2020, 2021 Ioannis P. Christovasilis <ipc@aethereng.com>
|
||||
#
|
||||
# This file is part of Ifc2CA.
|
||||
#
|
||||
# Ifc2CA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ifc2CA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Ifc2CA. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
import ifcopenshell
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class CA2IFC:
|
||||
def __init__(self, inputFilename, outputFilename):
|
||||
self.inputFilename = inputFilename
|
||||
self.outputFilename = outputFilename
|
||||
self.data = None
|
||||
self.f = None
|
||||
self.reps = {}
|
||||
self.origin = None
|
||||
self.xAxis = None
|
||||
self.yAxis = None
|
||||
self.zAxis = None
|
||||
|
||||
def convert(self):
|
||||
# load json file
|
||||
with open(self.inputFilename) as dataFile:
|
||||
self.data = json.load(dataFile)
|
||||
|
||||
# initiate ifc file
|
||||
self.f = ifcopenshell.file()
|
||||
|
||||
# create header
|
||||
self.create_header()
|
||||
|
||||
# create global axes
|
||||
globalAxes = self.create_global_axes()
|
||||
localPlacement = self.f.createIfcLocalPlacement(None, globalAxes)
|
||||
|
||||
# TODO: create units
|
||||
lengthUnit = self.f.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")
|
||||
unitAssignment = self.f.createIfcUnitAssignment((lengthUnit,))
|
||||
|
||||
# create owner history
|
||||
ownerHistory = self.create_owner_history()
|
||||
|
||||
# create representations and subrepresentations
|
||||
self.reps = self.create_reference_subrep(globalAxes)
|
||||
|
||||
# create project and model
|
||||
project = self.f.createIfcProject(
|
||||
self.guid(), ownerHistory, "A Project", None, None, None, None, (self.reps["model"],), unitAssignment
|
||||
)
|
||||
model = self.f.createIfcStructuralAnalysisModel(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
self.data["name"],
|
||||
None,
|
||||
None,
|
||||
"NOTDEFINED",
|
||||
globalAxes,
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
)
|
||||
self.f.createIfcRelDeclares(self.guid(), ownerHistory, None, None, project, (model,))
|
||||
|
||||
# create materials
|
||||
ifcMaterials = [None for _ in range(len(self.data["db"]["materials"]))]
|
||||
for i, material in enumerate(self.data["db"]["materials"]):
|
||||
ifcMaterials[i] = self.create_material(material)
|
||||
|
||||
# create profiles
|
||||
ifcProfiles = [None for _ in range(len(self.data["db"]["profiles"]))]
|
||||
for i, profile in enumerate(self.data["db"]["profiles"]):
|
||||
ifcProfiles[i] = self.create_profile(profile)
|
||||
|
||||
# create material-profile sets
|
||||
mpSets = list(
|
||||
set([el["material"] + "-" + el["profile"] for el in self.data["elements"] if el["geometryType"] == "line"])
|
||||
)
|
||||
ifcMaterialProfileSets = [None for _ in range(len(mpSets))]
|
||||
for i, mpSet in enumerate(mpSets):
|
||||
materialIndex = [mat["referenceName"] for mat in self.data["db"]["materials"]].index(mpSet.split("-")[0])
|
||||
profileIndex = [prof["referenceName"] for prof in self.data["db"]["profiles"]].index(mpSet.split("-")[1])
|
||||
material = ifcMaterials[materialIndex]
|
||||
profile = ifcProfiles[profileIndex]
|
||||
matProf = self.f.createIfcMaterialProfile(
|
||||
self.data["db"]["materials"][materialIndex]["name"]
|
||||
+ " | "
|
||||
+ self.data["db"]["profiles"][profileIndex]["profileName"],
|
||||
None,
|
||||
material,
|
||||
profile,
|
||||
)
|
||||
ifcMaterialProfileSets[i] = self.f.createIfcMaterialProfileSet(None, None, (matProf,))
|
||||
|
||||
# create structural elements
|
||||
ifcElements = [None for _ in range(len(self.data["elements"]))]
|
||||
for i, el in enumerate(self.data["elements"]):
|
||||
# geometry - product definition shape
|
||||
prodDefShape = self.create_geometry(el)
|
||||
|
||||
if el["geometryType"] == "line":
|
||||
# z axis TODO: group by elements
|
||||
localZAxis = self.f.createIfcDirection(tuple(el["orientation"][2]))
|
||||
# element
|
||||
ifcElements[i] = self.f.createIfcStructuralCurveMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
el["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
el["predefinedType"],
|
||||
localZAxis,
|
||||
)
|
||||
|
||||
if el["geometryType"] == "surface":
|
||||
ifcElements[i] = self.f.createIfcStructuralSurfaceMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
el["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
el["predefinedType"],
|
||||
el["thickness"],
|
||||
)
|
||||
|
||||
# create structural point connections
|
||||
ifcConnections = [None for _ in range(len(self.data["connections"]))]
|
||||
for i, conn in enumerate(self.data["connections"]):
|
||||
# geometry - product definition shape
|
||||
prodDefShape = self.create_geometry(conn)
|
||||
|
||||
# boundary conditions
|
||||
if conn["appliedCondition"]:
|
||||
bc = self.create_applied_conditions(conn["appliedCondition"], conn["geometryType"])
|
||||
if conn["geometryType"] == "point":
|
||||
appliedCondition = self.f.createIfcBoundaryNodeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if conn["geometryType"] == "line":
|
||||
appliedCondition = self.f.createIfcBoundaryEdgeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if conn["geometryType"] == "surface":
|
||||
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc["dx"], bc["dy"], bc["dz"])
|
||||
else:
|
||||
appliedCondition = None
|
||||
|
||||
if conn["geometryType"] == "point":
|
||||
# local axes
|
||||
localAxes = self.create_orientation(conn["orientation"])
|
||||
# connection
|
||||
ifcConnections[i] = self.f.createIfcStructuralPointConnection(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
conn["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
appliedCondition,
|
||||
localAxes,
|
||||
)
|
||||
|
||||
if conn["geometryType"] == "line":
|
||||
# z axis TODO: group by elements
|
||||
localZAxis = self.f.createIfcDirection(tuple(conn["orientation"][2]))
|
||||
# connection
|
||||
ifcConnections[i] = self.f.createIfcStructuralCurveConnection(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
conn["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
appliedCondition,
|
||||
localZAxis,
|
||||
)
|
||||
|
||||
if conn["geometryType"] == "surface":
|
||||
ifcConnections[i] = self.f.createIfcStructuralSurfaceConnection(
|
||||
self.guid(), ownerHistory, conn["name"], None, None, localPlacement, prodDefShape, appliedCondition
|
||||
)
|
||||
|
||||
# assign material-profile-sets
|
||||
for i, mpSet in enumerate(mpSets):
|
||||
groupOfElements = []
|
||||
for j, el in enumerate(self.data["elements"]):
|
||||
if el["geometryType"] == "line" and el["material"] + "-" + el["profile"] == mpSet:
|
||||
groupOfElements.append(ifcElements[j])
|
||||
|
||||
if groupOfElements:
|
||||
self.f.createIfcRelAssociatesMaterial(
|
||||
self.guid(), ownerHistory, None, None, tuple(groupOfElements), ifcMaterialProfileSets[i]
|
||||
)
|
||||
|
||||
# assign materials
|
||||
for i, mat in enumerate(self.data["db"]["materials"]):
|
||||
groupOfElements = []
|
||||
for j, el in enumerate(self.data["elements"]):
|
||||
if el["geometryType"] == "surface" and el["material"] == mat["referenceName"]:
|
||||
groupOfElements.append(ifcElements[j])
|
||||
if groupOfElements:
|
||||
self.f.createIfcRelAssociatesMaterial(
|
||||
self.guid(), ownerHistory, None, None, tuple(groupOfElements), ifcMaterials[i]
|
||||
)
|
||||
|
||||
# create connections with elements
|
||||
for i, el in enumerate(self.data["elements"]):
|
||||
for conn in el["connections"]:
|
||||
j = [c["referenceName"] for c in self.data["connections"]].index(conn["relatedConnection"])
|
||||
geometryType = self.data["connections"][j]["geometryType"]
|
||||
|
||||
if conn["appliedCondition"]:
|
||||
bc = self.create_applied_conditions(conn["appliedCondition"], geometryType)
|
||||
if geometryType == "point":
|
||||
appliedCondition = self.f.createIfcBoundaryNodeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if geometryType == "line":
|
||||
appliedCondition = self.f.createIfcBoundaryEdgeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if geometryType == "surface":
|
||||
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc["dx"], bc["dy"], bc["dz"])
|
||||
else:
|
||||
appliedCondition = None
|
||||
|
||||
# local axes
|
||||
localAxes = self.create_orientation(conn["orientation"])
|
||||
|
||||
if geometryType == "point":
|
||||
if not conn["eccentricity"]:
|
||||
self.f.createIfcRelConnectsStructuralMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
None,
|
||||
None,
|
||||
ifcElements[i],
|
||||
ifcConnections[j],
|
||||
appliedCondition,
|
||||
None,
|
||||
None,
|
||||
localAxes,
|
||||
)
|
||||
else:
|
||||
pointOnElement = self.f.createIfcCartesianPoint(tuple(conn["eccentricity"]["pointOnElement"]))
|
||||
vector = conn["eccentricity"]["vector"]
|
||||
connPointEcc = self.f.createIfcConnectionPointEccentricity(
|
||||
pointOnElement, None, vector[0], vector[1], vector[2]
|
||||
)
|
||||
self.f.createIfcRelConnectsWithEccentricity(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
None,
|
||||
None,
|
||||
ifcElements[i],
|
||||
ifcConnections[j],
|
||||
appliedCondition,
|
||||
None,
|
||||
None,
|
||||
localAxes,
|
||||
connPointEcc,
|
||||
)
|
||||
|
||||
if geometryType in ["line", "surface"]:
|
||||
self.f.createIfcRelConnectsStructuralMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
None,
|
||||
None,
|
||||
ifcElements[i],
|
||||
ifcConnections[j],
|
||||
appliedCondition,
|
||||
None,
|
||||
None,
|
||||
localAxes,
|
||||
)
|
||||
|
||||
# assign elements and connections to group
|
||||
self.f.createIfcRelAssignsToGroup(
|
||||
self.guid(), ownerHistory, None, None, tuple(ifcElements + ifcConnections), None, model
|
||||
)
|
||||
|
||||
# finalize ifc file
|
||||
self.f.write(self.outputFilename)
|
||||
|
||||
def guid(self):
|
||||
return ifcopenshell.guid.new()
|
||||
|
||||
def create_header(self):
|
||||
self.f.wrapped_data.header.file_name.name = os.path.basename(self.outputFilename)
|
||||
|
||||
def create_global_axes(self):
|
||||
self.xAxis = self.f.createIfcDirection((1.0, 0.0, 0.0))
|
||||
self.yAxis = self.f.createIfcDirection((0.0, 1.0, 0.0))
|
||||
self.zAxis = self.f.createIfcDirection((0.0, 0.0, 1.0))
|
||||
self.origin = self.f.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||
axes = self.f.createIfcAxis2Placement3D(self.origin, self.zAxis, self.xAxis)
|
||||
|
||||
return axes
|
||||
|
||||
def create_orientation(self, orientation):
|
||||
xAxis = self.f.createIfcDirection(tuple(orientation[0]))
|
||||
zAxis = self.f.createIfcDirection(tuple(orientation[2]))
|
||||
axes = self.f.createIfcAxis2Placement3D(self.origin, zAxis, xAxis)
|
||||
|
||||
return axes
|
||||
|
||||
def create_owner_history(self):
|
||||
actor = self.f.createIfcActorRole("ENGINEER", None, None)
|
||||
person = self.f.createIfcPerson("Christovasilis", None, "Ioannis", None, None, None, (actor,))
|
||||
organization = self.f.createIfcOrganization(
|
||||
None,
|
||||
"IfcOpenShell",
|
||||
"IfcOpenShell, an open source (LGPL) software library that helps users and software developers to work with the IFC file format.",
|
||||
)
|
||||
p_o = self.f.createIfcPersonAndOrganization(person, organization)
|
||||
application = self.f.createIfcApplication(organization, "v0.0.x", "IFC2CA", "IFC2CA")
|
||||
timestamp = int(datetime.now().timestamp())
|
||||
ownerHistory = self.f.createIfcOwnerHistory(p_o, application, "READWRITE", None, None, None, None, timestamp)
|
||||
|
||||
return ownerHistory
|
||||
|
||||
def create_reference_subrep(self, globalAxes):
|
||||
modelRep = self.f.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, globalAxes, None)
|
||||
bodySubRep = self.f.createIfcGeometricRepresentationSubContext(
|
||||
"Body", "Model", None, None, None, None, modelRep, None, "MODEL_VIEW", None
|
||||
)
|
||||
refSubRep = self.f.createIfcGeometricRepresentationSubContext(
|
||||
"Reference", "Model", None, None, None, None, modelRep, None, "GRAPH_VIEW", None
|
||||
)
|
||||
|
||||
return {"model": modelRep, "body": bodySubRep, "reference": refSubRep}
|
||||
|
||||
def create_material(self, material):
|
||||
ifcMaterial = self.f.createIfcMaterial(material["name"], None, material["category"])
|
||||
|
||||
mechProps = []
|
||||
if "youngModulus" in material["mechProps"]:
|
||||
youngModulus = self.f.createIfcPropertySingleValue(
|
||||
"YoungModulus", None, self.f.createIfcModulusOfElasticityMeasure(material["mechProps"]["youngModulus"])
|
||||
)
|
||||
mechProps.append(youngModulus)
|
||||
if "shearModulus" in material["mechProps"]:
|
||||
shearModulus = self.f.createIfcPropertySingleValue(
|
||||
"ShearModulus", None, self.f.createIfcModulusOfElasticityMeasure(material["mechProps"]["shearModulus"])
|
||||
)
|
||||
mechProps.append(shearModulus)
|
||||
if "poissonRatio" in material["mechProps"]:
|
||||
poissonRatio = self.f.createIfcPropertySingleValue(
|
||||
"PoissonRatio", None, self.f.createIfcPositiveRatioMeasure(material["mechProps"]["poissonRatio"])
|
||||
)
|
||||
mechProps.append(poissonRatio)
|
||||
if mechProps:
|
||||
self.f.createIfcMaterialProperties(
|
||||
"Pset_MaterialMechanical", material["name"], tuple(mechProps), ifcMaterial
|
||||
)
|
||||
|
||||
commonProps = []
|
||||
if "massDensity" in material["commonProps"]:
|
||||
massDensity = self.f.createIfcPropertySingleValue(
|
||||
"MassDensity", None, self.f.createIfcMassDensityMeasure(material["commonProps"]["massDensity"])
|
||||
)
|
||||
commonProps.append(massDensity)
|
||||
if commonProps:
|
||||
self.f.createIfcMaterialProperties("Pset_MaterialCommon", material["name"], tuple(commonProps), ifcMaterial)
|
||||
|
||||
return ifcMaterial
|
||||
|
||||
def create_profile(self, profile):
|
||||
if profile["profileShape"] == "rectangular":
|
||||
ifcProfile = self.f.createIfcRectangleProfileDef(
|
||||
profile["profileType"], profile["profileName"], None, profile["xDim"], profile["yDim"]
|
||||
)
|
||||
|
||||
if profile["profileShape"] == "iSymmetrical":
|
||||
ifcProfile = self.f.createIfcIShapeProfileDef(
|
||||
profile["profileType"],
|
||||
profile["profileName"],
|
||||
None,
|
||||
profile["commonProps"]["overallWidth"],
|
||||
profile["commonProps"]["overallDepth"],
|
||||
profile["commonProps"]["webThickness"],
|
||||
profile["commonProps"]["flangeThickness"],
|
||||
profile["commonProps"]["filletRadius"],
|
||||
)
|
||||
|
||||
mechProps = []
|
||||
if "massPerLength" in profile["mechProps"]:
|
||||
massPerLength = self.f.createIfcPropertySingleValue(
|
||||
"MassPerLength", None, self.f.createIfcMassPerLengthMeasure(profile["mechProps"]["massPerLength"])
|
||||
)
|
||||
mechProps.append(massPerLength)
|
||||
if "crossSectionArea" in profile["mechProps"]:
|
||||
crossSectionArea = self.f.createIfcPropertySingleValue(
|
||||
"CrossSectionArea", None, self.f.createIfcAreaMeasure(profile["mechProps"]["crossSectionArea"])
|
||||
)
|
||||
mechProps.append(crossSectionArea)
|
||||
if "momentOfInertiaY" in profile["mechProps"]:
|
||||
momentOfInertiaY = self.f.createIfcPropertySingleValue(
|
||||
"MomentOfInertiaY",
|
||||
None,
|
||||
self.f.createIfcMomentOfInertiaMeasure(profile["mechProps"]["momentOfInertiaY"]),
|
||||
)
|
||||
mechProps.append(momentOfInertiaY)
|
||||
if "momentOfInertiaZ" in profile["mechProps"]:
|
||||
momentOfInertiaZ = self.f.createIfcPropertySingleValue(
|
||||
"MomentOfInertiaZ",
|
||||
None,
|
||||
self.f.createIfcMomentOfInertiaMeasure(profile["mechProps"]["momentOfInertiaZ"]),
|
||||
)
|
||||
mechProps.append(momentOfInertiaZ)
|
||||
if "torsionalConstantX" in profile["mechProps"]:
|
||||
torsionalConstantX = self.f.createIfcPropertySingleValue(
|
||||
"TorsionalConstantX",
|
||||
None,
|
||||
self.f.createIfcMomentOfInertiaMeasure(profile["mechProps"]["torsionalConstantX"]),
|
||||
)
|
||||
mechProps.append(torsionalConstantX)
|
||||
if mechProps:
|
||||
self.f.createIfcProfileProperties(
|
||||
"Pset_ProfileMechanical", profile["profileName"], tuple(mechProps), ifcProfile
|
||||
)
|
||||
|
||||
return ifcProfile
|
||||
|
||||
def create_geometry(self, object):
|
||||
if object["geometryType"] == "point":
|
||||
point = self.f.createIfcCartesianPoint(tuple(object["geometry"]))
|
||||
vertex = self.f.createIfcVertexPoint(point)
|
||||
vertexTopologyRep = self.f.createIfcTopologyRepresentation(
|
||||
self.reps["reference"], "Reference", "Vertex", (vertex,)
|
||||
)
|
||||
vertexProdDefShape = self.f.createIfcProductDefinitionShape(None, None, (vertexTopologyRep,))
|
||||
|
||||
return vertexProdDefShape
|
||||
|
||||
if object["geometryType"] == "line":
|
||||
startPoint = self.f.createIfcCartesianPoint(tuple(object["geometry"][0]))
|
||||
startVertex = self.f.createIfcVertexPoint(startPoint)
|
||||
endPoint = self.f.createIfcCartesianPoint(tuple(object["geometry"][1]))
|
||||
endVertex = self.f.createIfcVertexPoint(endPoint)
|
||||
edge = self.f.createIfcEdge(startVertex, endVertex)
|
||||
edgeTopologyRep = self.f.createIfcTopologyRepresentation(
|
||||
self.reps["reference"], "Reference", "Edge", (edge,)
|
||||
)
|
||||
edgeProdDefShape = self.f.createIfcProductDefinitionShape(None, None, (edgeTopologyRep,))
|
||||
|
||||
return edgeProdDefShape
|
||||
|
||||
if object["geometryType"] == "surface":
|
||||
verts = [None for _ in range(len(object["geometry"]))]
|
||||
for i, p in enumerate(object["geometry"]):
|
||||
point = self.f.createIfcCartesianPoint(tuple(p))
|
||||
verts[i] = self.f.createIfcVertexPoint(point)
|
||||
|
||||
orientedEdges = [None for _ in range(len(object["geometry"]))]
|
||||
for i, v in enumerate(verts):
|
||||
v2Index = (i + 1) if i < len(verts) - 1 else 0
|
||||
edge = self.f.createIfcEdge(v, verts[v2Index])
|
||||
orientedEdges[i] = self.f.createIfcOrientedEdge(None, None, edge, True)
|
||||
|
||||
edgeLoop = self.f.createIfcEdgeLoop(tuple(orientedEdges))
|
||||
localAxes = self.create_orientation(object["orientation"])
|
||||
plane = self.f.createIfcPlane(localAxes)
|
||||
faceBound = self.f.createIfcFaceBound(edgeLoop, True)
|
||||
face = self.f.createIfcFaceSurface((faceBound,), plane, True)
|
||||
faceTopologyRep = self.f.createIfcTopologyRepresentation(
|
||||
self.reps["reference"], "Reference", "Face", (face,)
|
||||
)
|
||||
faceProdDefShape = self.f.createIfcProductDefinitionShape(None, None, (faceTopologyRep,))
|
||||
|
||||
return faceProdDefShape
|
||||
|
||||
def create_applied_conditions(self, bc, geometryType):
|
||||
for dof in ["dx", "dy", "dz"]:
|
||||
if isinstance(bc[dof], bool):
|
||||
bc[dof] = self.f.createIfcBoolean(bc[dof])
|
||||
else:
|
||||
if geometryType == "point":
|
||||
bc[dof] = self.f.createIfcLinearStiffnessMeasure(bc[dof])
|
||||
if geometryType == "line":
|
||||
bc[dof] = self.f.createIfcModulusOfLinearSubgradeReactionMeasure(bc[dof])
|
||||
if geometryType == "surface":
|
||||
bc[dof] = self.f.createIfcModulusOfSubgradeReactionMeasure(bc[dof])
|
||||
|
||||
for dof in ["drx", "dry", "drz"]:
|
||||
if isinstance(bc[dof], bool):
|
||||
bc[dof] = self.f.createIfcBoolean(bc[dof])
|
||||
else:
|
||||
if geometryType == "point":
|
||||
bc[dof] = self.f.createIfcRotationalStiffnessMeasure(bc[dof])
|
||||
if geometryType == "line":
|
||||
bc[dof] = self.f.createIfcModulusOfRotationalSubgradeReactionMeasure(bc[dof])
|
||||
|
||||
return bc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
inputFilename = "grid_of_beams.json"
|
||||
outputFilename = "grid_of_beams.ifc"
|
||||
|
||||
ca2ifc = CA2IFC(inputFilename, outputFilename)
|
||||
ca2ifc.convert()
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
# Ifc2CA - IFC Code_Aster utility
|
||||
# Copyright (C) 2020, 2021 Ioannis P. Christovasilis <ipc@aethereng.com>
|
||||
# Copyright (C) 2020, 2021, 2023, 2024 Ioannis P. Christovasilis <ipc@aethereng.com>
|
||||
#
|
||||
# This file is part of Ifc2CA.
|
||||
#
|
||||
@@ -17,509 +17,435 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Ifc2CA. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
import ifcopenshell
|
||||
import os
|
||||
from datetime import datetime
|
||||
import itertools
|
||||
|
||||
import ifcopenshell as ios
|
||||
import meshio
|
||||
import numpy as np
|
||||
|
||||
flatten = itertools.chain.from_iterable
|
||||
|
||||
|
||||
class CA2IFC:
|
||||
def __init__(self, inputFilename, outputFilename):
|
||||
self.inputFilename = inputFilename
|
||||
self.outputFilename = outputFilename
|
||||
self.data = None
|
||||
self.f = None
|
||||
self.reps = {}
|
||||
self.origin = None
|
||||
self.xAxis = None
|
||||
self.yAxis = None
|
||||
self.zAxis = None
|
||||
def get_element_data(model, name, element):
|
||||
if element["geometry_type"] == "Edge":
|
||||
for i, cell_block in enumerate(model.cells):
|
||||
if cell_block.type == "line":
|
||||
cell_tags = model.cell_data["cell_tags"][i]
|
||||
break
|
||||
rows = []
|
||||
for i_row, i in enumerate(cell_tags):
|
||||
if i == 0:
|
||||
continue
|
||||
tags = model.cell_tags[i]
|
||||
for tag in tags:
|
||||
if tag == name:
|
||||
# print(i_row, i)
|
||||
rows.append(i_row)
|
||||
break
|
||||
|
||||
def convert(self):
|
||||
# load json file
|
||||
with open(self.inputFilename) as dataFile:
|
||||
self.data = json.load(dataFile)
|
||||
points = list(set(flatten([cell_block.data[c] for c in rows])))
|
||||
points.sort(key=lambda p: np.linalg.norm(model.points[p] - np.array(element["origin"])))
|
||||
coords = [np.round(model.points[p], 4).tolist() for p in points]
|
||||
local_coords = [
|
||||
[float(round(np.linalg.norm(model.points[p] - np.array(element["origin"])), 4))] for p in points
|
||||
]
|
||||
|
||||
# initiate ifc file
|
||||
self.f = ifcopenshell.file()
|
||||
return {
|
||||
"name": name,
|
||||
"points": points,
|
||||
"coords": coords,
|
||||
"local_coords": local_coords,
|
||||
}
|
||||
|
||||
# create header
|
||||
self.create_header()
|
||||
elif element["geometry_type"] == "Face":
|
||||
triangle_cell_tags = None
|
||||
quad_cell_tags = None
|
||||
for i, cell_block in enumerate(model.cells):
|
||||
if cell_block.type == "triangle":
|
||||
triangle_cell_tags = model.cell_data["cell_tags"][i]
|
||||
break
|
||||
|
||||
# create global axes
|
||||
globalAxes = self.create_global_axes()
|
||||
localPlacement = self.f.createIfcLocalPlacement(None, globalAxes)
|
||||
|
||||
# TODO: create units
|
||||
lengthUnit = self.f.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")
|
||||
unitAssignment = self.f.createIfcUnitAssignment((lengthUnit,))
|
||||
|
||||
# create owner history
|
||||
ownerHistory = self.create_owner_history()
|
||||
|
||||
# create representations and subrepresentations
|
||||
self.reps = self.create_reference_subrep(globalAxes)
|
||||
|
||||
# create project and model
|
||||
project = self.f.createIfcProject(
|
||||
self.guid(), ownerHistory, "A Project", None, None, None, None, (self.reps["model"],), unitAssignment
|
||||
)
|
||||
model = self.f.createIfcStructuralAnalysisModel(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
self.data["name"],
|
||||
None,
|
||||
None,
|
||||
"NOTDEFINED",
|
||||
globalAxes,
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
)
|
||||
self.f.createIfcRelDeclares(self.guid(), ownerHistory, None, None, project, (model,))
|
||||
|
||||
# create materials
|
||||
ifcMaterials = [None for _ in range(len(self.data["db"]["materials"]))]
|
||||
for i, material in enumerate(self.data["db"]["materials"]):
|
||||
ifcMaterials[i] = self.create_material(material)
|
||||
|
||||
# create profiles
|
||||
ifcProfiles = [None for _ in range(len(self.data["db"]["profiles"]))]
|
||||
for i, profile in enumerate(self.data["db"]["profiles"]):
|
||||
ifcProfiles[i] = self.create_profile(profile)
|
||||
|
||||
# create material-profile sets
|
||||
mpSets = list(
|
||||
set([el["material"] + "-" + el["profile"] for el in self.data["elements"] if el["geometryType"] == "line"])
|
||||
)
|
||||
ifcMaterialProfileSets = [None for _ in range(len(mpSets))]
|
||||
for i, mpSet in enumerate(mpSets):
|
||||
materialIndex = [mat["referenceName"] for mat in self.data["db"]["materials"]].index(mpSet.split("-")[0])
|
||||
profileIndex = [prof["referenceName"] for prof in self.data["db"]["profiles"]].index(mpSet.split("-")[1])
|
||||
material = ifcMaterials[materialIndex]
|
||||
profile = ifcProfiles[profileIndex]
|
||||
matProf = self.f.createIfcMaterialProfile(
|
||||
self.data["db"]["materials"][materialIndex]["name"]
|
||||
+ " | "
|
||||
+ self.data["db"]["profiles"][profileIndex]["profileName"],
|
||||
None,
|
||||
material,
|
||||
profile,
|
||||
)
|
||||
ifcMaterialProfileSets[i] = self.f.createIfcMaterialProfileSet(None, None, (matProf,))
|
||||
|
||||
# create structural elements
|
||||
ifcElements = [None for _ in range(len(self.data["elements"]))]
|
||||
for i, el in enumerate(self.data["elements"]):
|
||||
# geometry - product definition shape
|
||||
prodDefShape = self.create_geometry(el)
|
||||
|
||||
if el["geometryType"] == "line":
|
||||
# z axis TODO: group by elements
|
||||
localZAxis = self.f.createIfcDirection(tuple(el["orientation"][2]))
|
||||
# element
|
||||
ifcElements[i] = self.f.createIfcStructuralCurveMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
el["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
el["predefinedType"],
|
||||
localZAxis,
|
||||
)
|
||||
|
||||
if el["geometryType"] == "surface":
|
||||
ifcElements[i] = self.f.createIfcStructuralSurfaceMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
el["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
el["predefinedType"],
|
||||
el["thickness"],
|
||||
)
|
||||
|
||||
# create structural point connections
|
||||
ifcConnections = [None for _ in range(len(self.data["connections"]))]
|
||||
for i, conn in enumerate(self.data["connections"]):
|
||||
# geometry - product definition shape
|
||||
prodDefShape = self.create_geometry(conn)
|
||||
|
||||
# boundary conditions
|
||||
if conn["appliedCondition"]:
|
||||
bc = self.create_applied_conditions(conn["appliedCondition"], conn["geometryType"])
|
||||
if conn["geometryType"] == "point":
|
||||
appliedCondition = self.f.createIfcBoundaryNodeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if conn["geometryType"] == "line":
|
||||
appliedCondition = self.f.createIfcBoundaryEdgeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if conn["geometryType"] == "surface":
|
||||
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc["dx"], bc["dy"], bc["dz"])
|
||||
if triangle_cell_tags is not None:
|
||||
rows = []
|
||||
for i_row, i in enumerate(triangle_cell_tags):
|
||||
if i == 0:
|
||||
continue
|
||||
tags = model.cell_tags[i]
|
||||
for tag in tags:
|
||||
if tag == name:
|
||||
# print(i_row, i)
|
||||
rows.append(i_row)
|
||||
break
|
||||
if not len(rows):
|
||||
points = []
|
||||
else:
|
||||
appliedCondition = None
|
||||
points = list(flatten([cell_block.data[c] for c in rows]))
|
||||
|
||||
if conn["geometryType"] == "point":
|
||||
# local axes
|
||||
localAxes = self.create_orientation(conn["orientation"])
|
||||
# connection
|
||||
ifcConnections[i] = self.f.createIfcStructuralPointConnection(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
conn["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
appliedCondition,
|
||||
localAxes,
|
||||
)
|
||||
for i, cell_block in enumerate(model.cells):
|
||||
if cell_block.type == "quad":
|
||||
quad_cell_tags = model.cell_data["cell_tags"][i]
|
||||
break
|
||||
|
||||
if conn["geometryType"] == "line":
|
||||
# z axis TODO: group by elements
|
||||
localZAxis = self.f.createIfcDirection(tuple(conn["orientation"][2]))
|
||||
# connection
|
||||
ifcConnections[i] = self.f.createIfcStructuralCurveConnection(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
conn["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
appliedCondition,
|
||||
localZAxis,
|
||||
)
|
||||
if quad_cell_tags is not None:
|
||||
rows = []
|
||||
for i_row, i in enumerate(quad_cell_tags):
|
||||
if i == 0:
|
||||
continue
|
||||
tags = model.cell_tags[i]
|
||||
for tag in tags:
|
||||
if tag == name:
|
||||
# print(i_row, i)
|
||||
rows.append(i_row)
|
||||
break
|
||||
if len(rows):
|
||||
points.extend(list(flatten([cell_block.data[c] for c in rows])))
|
||||
|
||||
if conn["geometryType"] == "surface":
|
||||
ifcConnections[i] = self.f.createIfcStructuralSurfaceConnection(
|
||||
self.guid(), ownerHistory, conn["name"], None, None, localPlacement, prodDefShape, appliedCondition
|
||||
)
|
||||
points = list(set(points))
|
||||
points.sort()
|
||||
coords = [model.points[p].tolist() for p in points]
|
||||
local_coords = [
|
||||
np.round(np.array(element["orientation"]).dot(model.points[p] - np.array(element["origin"])), 4).tolist()[
|
||||
:2
|
||||
]
|
||||
for p in points
|
||||
]
|
||||
|
||||
# assign material-profile-sets
|
||||
for i, mpSet in enumerate(mpSets):
|
||||
groupOfElements = []
|
||||
for j, el in enumerate(self.data["elements"]):
|
||||
if el["geometryType"] == "line" and el["material"] + "-" + el["profile"] == mpSet:
|
||||
groupOfElements.append(ifcElements[j])
|
||||
return {
|
||||
"name": name,
|
||||
"points": points,
|
||||
"coords": coords,
|
||||
"local_coords": local_coords,
|
||||
}
|
||||
|
||||
if groupOfElements:
|
||||
self.f.createIfcRelAssociatesMaterial(
|
||||
self.guid(), ownerHistory, None, None, tuple(groupOfElements), ifcMaterialProfileSets[i]
|
||||
)
|
||||
|
||||
# assign materials
|
||||
for i, mat in enumerate(self.data["db"]["materials"]):
|
||||
groupOfElements = []
|
||||
for j, el in enumerate(self.data["elements"]):
|
||||
if el["geometryType"] == "surface" and el["material"] == mat["referenceName"]:
|
||||
groupOfElements.append(ifcElements[j])
|
||||
if groupOfElements:
|
||||
self.f.createIfcRelAssociatesMaterial(
|
||||
self.guid(), ownerHistory, None, None, tuple(groupOfElements), ifcMaterials[i]
|
||||
)
|
||||
def get_element_result_data(model, field_label, name, element, field_type):
|
||||
points = get_element_data(model, name, element)["points"]
|
||||
if field_type == "InternalForces":
|
||||
if element["geometry_type"] == "Edge":
|
||||
return {
|
||||
"N": [round(model.point_data[field_label][p][0], 4) for p in points],
|
||||
"VY": [round(model.point_data[field_label][p][1], 4) for p in points],
|
||||
"VZ": [round(model.point_data[field_label][p][2], 4) for p in points],
|
||||
"MT": [round(model.point_data[field_label][p][3], 4) for p in points],
|
||||
"MFY": [round(model.point_data[field_label][p][4], 4) for p in points],
|
||||
"MFZ": [round(model.point_data[field_label][p][5], 4) for p in points],
|
||||
}
|
||||
|
||||
# create connections with elements
|
||||
for i, el in enumerate(self.data["elements"]):
|
||||
for conn in el["connections"]:
|
||||
j = [c["referenceName"] for c in self.data["connections"]].index(conn["relatedConnection"])
|
||||
geometryType = self.data["connections"][j]["geometryType"]
|
||||
elif element["geometry_type"] == "Face":
|
||||
if len(model.point_data[field_label][points[0]]) == 8:
|
||||
offset = 0
|
||||
elif len(model.point_data[field_label][points[0]]) == 14:
|
||||
offset = 6
|
||||
else:
|
||||
assert (
|
||||
False
|
||||
), f"Internal force field with {len(model.point_data[field_label][points[0]])} field values for {field_label} and {element['Name']} "
|
||||
|
||||
if conn["appliedCondition"]:
|
||||
bc = self.create_applied_conditions(conn["appliedCondition"], geometryType)
|
||||
if geometryType == "point":
|
||||
appliedCondition = self.f.createIfcBoundaryNodeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if geometryType == "line":
|
||||
appliedCondition = self.f.createIfcBoundaryEdgeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if geometryType == "surface":
|
||||
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc["dx"], bc["dy"], bc["dz"])
|
||||
else:
|
||||
appliedCondition = None
|
||||
return {
|
||||
"NXX": [round(model.point_data[field_label][p][offset + 0], 4) for p in points],
|
||||
"NYY": [round(model.point_data[field_label][p][offset + 1], 4) for p in points],
|
||||
"NXY": [round(model.point_data[field_label][p][offset + 2], 4) for p in points],
|
||||
"MXX": [round(model.point_data[field_label][p][offset + 3], 4) for p in points],
|
||||
"MYY": [round(model.point_data[field_label][p][offset + 4], 4) for p in points],
|
||||
"MXY": [round(model.point_data[field_label][p][offset + 5], 4) for p in points],
|
||||
"QX": [round(model.point_data[field_label][p][offset + 6], 4) for p in points],
|
||||
"QY": [round(model.point_data[field_label][p][offset + 7], 4) for p in points],
|
||||
}
|
||||
|
||||
# local axes
|
||||
localAxes = self.create_orientation(conn["orientation"])
|
||||
if field_type == "Displacements":
|
||||
return {
|
||||
"DX": [round(model.point_data[field_label][p][0], 4) for p in points],
|
||||
"DY": [round(model.point_data[field_label][p][1], 4) for p in points],
|
||||
"DZ": [round(model.point_data[field_label][p][2], 4) for p in points],
|
||||
"DRX": [round(model.point_data[field_label][p][3], 4) for p in points],
|
||||
"DRY": [round(model.point_data[field_label][p][4], 4) for p in points],
|
||||
"DRZ": [round(model.point_data[field_label][p][5], 4) for p in points],
|
||||
}
|
||||
|
||||
if geometryType == "point":
|
||||
if not conn["eccentricity"]:
|
||||
self.f.createIfcRelConnectsStructuralMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
None,
|
||||
None,
|
||||
ifcElements[i],
|
||||
ifcConnections[j],
|
||||
appliedCondition,
|
||||
None,
|
||||
None,
|
||||
localAxes,
|
||||
)
|
||||
else:
|
||||
pointOnElement = self.f.createIfcCartesianPoint(tuple(conn["eccentricity"]["pointOnElement"]))
|
||||
vector = conn["eccentricity"]["vector"]
|
||||
connPointEcc = self.f.createIfcConnectionPointEccentricity(
|
||||
pointOnElement, None, vector[0], vector[1], vector[2]
|
||||
)
|
||||
self.f.createIfcRelConnectsWithEccentricity(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
None,
|
||||
None,
|
||||
ifcElements[i],
|
||||
ifcConnections[j],
|
||||
appliedCondition,
|
||||
None,
|
||||
None,
|
||||
localAxes,
|
||||
connPointEcc,
|
||||
)
|
||||
|
||||
if geometryType in ["line", "surface"]:
|
||||
self.f.createIfcRelConnectsStructuralMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
None,
|
||||
None,
|
||||
ifcElements[i],
|
||||
ifcConnections[j],
|
||||
appliedCondition,
|
||||
None,
|
||||
None,
|
||||
localAxes,
|
||||
def results_to_ifc(ifc_file, ifc_model, rmed_path, global_case, field_types, data):
|
||||
if not rmed_path.exists():
|
||||
print(f"Med file with results not found for case_instant: {global_case}")
|
||||
return
|
||||
|
||||
result = meshio.read(rmed_path, "med")
|
||||
if global_case == "LC":
|
||||
model_cases = data["load_cases"]
|
||||
elif global_case == "COMB":
|
||||
model_cases = data["load_combinations"]
|
||||
for field in field_types:
|
||||
if field == "InternalForces":
|
||||
_parsed_data = internal_forces_to_ifc(ifc_file, ifc_model, result, model_cases, data["elements"])
|
||||
elif field == "Displacements":
|
||||
_parsed_data = displacements_to_ifc(ifc_file, ifc_model, result, model_cases, data["elements"])
|
||||
|
||||
|
||||
def internal_forces_to_ifc(ifc_file, ifc_model, result, model_cases, elements):
|
||||
result_cases = [dict() for _ in model_cases]
|
||||
field_cases = [f"ELEMENT_FORCE[{i}] - {i + 1}" for i in range(len(result_cases))]
|
||||
|
||||
# Create Result Groups for load case_instance combinations
|
||||
for iCase, case_instance in enumerate(model_cases):
|
||||
result_cases[iCase]["case_instance"] = ifc_file.create_entity(
|
||||
"IfcStructuralResultGroup",
|
||||
**{
|
||||
"GlobalId": ios.guid.new(),
|
||||
"Name": "Internal Forces for " + case_instance["Name"],
|
||||
"TheoryType": "FIRST_ORDER_THEORY",
|
||||
"ResultForLoadGroup": ifc_file.by_id(case_instance["id"]),
|
||||
"IsLinear": True,
|
||||
},
|
||||
)
|
||||
|
||||
result_cases[iCase]["assignment"] = ifc_file.create_entity(
|
||||
"IfcRelAssignsToGroup",
|
||||
**{
|
||||
"GlobalId": ios.guid.new(),
|
||||
"RelatedObjects": [],
|
||||
"RelatingGroup": result_cases[iCase]["case_instance"],
|
||||
},
|
||||
)
|
||||
|
||||
if ifc_model.HasResults:
|
||||
ifc_model.HasResults += tuple([result["case_instance"] for result in result_cases])
|
||||
else:
|
||||
ifc_model.HasResults = tuple([result["case_instance"] for result in result_cases])
|
||||
|
||||
data = []
|
||||
for _, element in enumerate(elements):
|
||||
group_name = getGroupName(element["ref_id"])
|
||||
name = element["Name"]
|
||||
info = get_element_data(result, group_name, element)
|
||||
assert len(info["coords"]) >= 2
|
||||
for iCase, field_case in enumerate(field_cases):
|
||||
forces = get_element_result_data(result, field_case, group_name, element, field_type="InternalForces")
|
||||
reaction = ifc_file.create_entity(
|
||||
"IfcStructuralCurveReaction" if element["geometry_type"] == "Edge" else "IfcStructuralSurfaceReaction",
|
||||
**{
|
||||
"GlobalId": ios.guid.new(),
|
||||
"Name": "Internal Forces for " + model_cases[iCase]["Name"] + f" on {name}",
|
||||
# "AppliedLoad": load["ifcLoad"],
|
||||
"GlobalOrLocal": "LOCAL_COORDS",
|
||||
"PredefinedType": "DISCRETE",
|
||||
},
|
||||
)
|
||||
result_cases[iCase]["assignment"].RelatedObjects += (reaction,)
|
||||
|
||||
ifc_file.create_entity(
|
||||
"IfcRelConnectsStructuralActivity",
|
||||
**{
|
||||
"GlobalId": ios.guid.new(),
|
||||
"RelatingElement": ifc_file.by_id(element["id"]),
|
||||
"RelatedStructuralActivity": reaction,
|
||||
},
|
||||
)
|
||||
|
||||
reaction.AppliedLoad = ifc_file.create_entity(
|
||||
"IfcStructuralLoadConfiguration",
|
||||
**{
|
||||
"Name": "Internal Forces for " + model_cases[iCase]["Name"] + f" on {name}",
|
||||
"Values": [],
|
||||
"Locations": tuple([tuple(node) for node in info["local_coords"]]),
|
||||
},
|
||||
)
|
||||
|
||||
if element["geometry_type"] == "Edge":
|
||||
for iNode, node in enumerate(info["coords"]):
|
||||
location = f"({node[0]}, {node[1]}, {node[2]})"
|
||||
distance = info["local_coords"][iNode][0]
|
||||
|
||||
N = forces["N"][iNode]
|
||||
VY = forces["VY"][iNode]
|
||||
VZ = forces["VZ"][iNode]
|
||||
MT = forces["MT"][iNode]
|
||||
MFY = forces["MFY"][iNode]
|
||||
MFZ = forces["MFZ"][iNode]
|
||||
|
||||
data.append([name, f"LCC-{iCase + 1} @ {distance}", location, N, VY, VZ, MT, MFY, MFZ])
|
||||
|
||||
pointValue = ifc_file.create_entity(
|
||||
"IfcStructuralLoadSingleForce",
|
||||
**{
|
||||
"Name": "Internal Forces for " + model_cases[iCase]["Name"] + f" @ {distance} on {name}",
|
||||
"ForceX": N,
|
||||
"ForceY": VY,
|
||||
"ForceZ": VZ,
|
||||
"MomentX": MT,
|
||||
"MomentY": MFY,
|
||||
"MomentZ": MFZ,
|
||||
},
|
||||
)
|
||||
reaction.AppliedLoad.Values += (pointValue,)
|
||||
|
||||
# assign elements and connections to group
|
||||
self.f.createIfcRelAssignsToGroup(
|
||||
self.guid(), ownerHistory, None, None, tuple(ifcElements + ifcConnections), None, model
|
||||
elif element["geometry_type"] == "Face":
|
||||
for iNode, node in enumerate(info["coords"]):
|
||||
location = f"({node[0]}, {node[1]}, {node[2]})"
|
||||
distance = tuple(info["local_coords"][iNode])
|
||||
|
||||
NXX = forces["NXX"][iNode]
|
||||
NYY = forces["NYY"][iNode]
|
||||
NXY = forces["NXY"][iNode]
|
||||
MXX = forces["MXX"][iNode]
|
||||
MYY = forces["MYY"][iNode]
|
||||
MXY = forces["MXY"][iNode]
|
||||
|
||||
data.append([name, f"LCC-{iCase + 1} @ {distance}", location, NXX, NYY, NXY, MXX, MYY, MXY])
|
||||
|
||||
pointValue = ifc_file.create_entity(
|
||||
"IfcStructuralLoadSingleForce",
|
||||
**{
|
||||
"Name": "Internal Forces for " + model_cases[iCase]["Name"] + f" @ {distance} on {name}",
|
||||
"ForceX": NXX,
|
||||
"ForceY": NYY,
|
||||
"ForceZ": NXY,
|
||||
"MomentX": MXX,
|
||||
"MomentY": MYY,
|
||||
"MomentZ": MXY,
|
||||
},
|
||||
)
|
||||
reaction.AppliedLoad.Values += (pointValue,)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def displacements_to_ifc(ifc_file, ifc_model, result, model_cases, elements):
|
||||
result_cases = [dict() for _ in model_cases]
|
||||
field_cases = [f"MODEL_DISP[{i}] - {i + 1}" for i in range(len(result_cases))]
|
||||
|
||||
# Create Result Groups for load case_instance combinations
|
||||
for iCase, case_instance in enumerate(model_cases):
|
||||
result_cases[iCase]["case_instance"] = ifc_file.create_entity(
|
||||
"IfcStructuralResultGroup",
|
||||
**{
|
||||
"GlobalId": ios.guid.new(),
|
||||
"Name": "Global Displacements for " + case_instance["Name"],
|
||||
"TheoryType": "FIRST_ORDER_THEORY",
|
||||
"ResultForLoadGroup": ifc_file.by_id(case_instance["id"]),
|
||||
"IsLinear": True,
|
||||
},
|
||||
)
|
||||
|
||||
# finalize ifc file
|
||||
self.f.write(self.outputFilename)
|
||||
|
||||
def guid(self):
|
||||
return ifcopenshell.guid.new()
|
||||
|
||||
def create_header(self):
|
||||
self.f.wrapped_data.header.file_name.name = os.path.basename(self.outputFilename)
|
||||
|
||||
def create_global_axes(self):
|
||||
self.xAxis = self.f.createIfcDirection((1.0, 0.0, 0.0))
|
||||
self.yAxis = self.f.createIfcDirection((0.0, 1.0, 0.0))
|
||||
self.zAxis = self.f.createIfcDirection((0.0, 0.0, 1.0))
|
||||
self.origin = self.f.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||
axes = self.f.createIfcAxis2Placement3D(self.origin, self.zAxis, self.xAxis)
|
||||
|
||||
return axes
|
||||
|
||||
def create_orientation(self, orientation):
|
||||
xAxis = self.f.createIfcDirection(tuple(orientation[0]))
|
||||
zAxis = self.f.createIfcDirection(tuple(orientation[2]))
|
||||
axes = self.f.createIfcAxis2Placement3D(self.origin, zAxis, xAxis)
|
||||
|
||||
return axes
|
||||
|
||||
def create_owner_history(self):
|
||||
actor = self.f.createIfcActorRole("ENGINEER", None, None)
|
||||
person = self.f.createIfcPerson("Christovasilis", None, "Ioannis", None, None, None, (actor,))
|
||||
organization = self.f.createIfcOrganization(
|
||||
None,
|
||||
"IfcOpenShell",
|
||||
"IfcOpenShell, an open source (LGPL) software library that helps users and software developers to work with the IFC file format.",
|
||||
)
|
||||
p_o = self.f.createIfcPersonAndOrganization(person, organization)
|
||||
application = self.f.createIfcApplication(organization, "v0.0.x", "IFC2CA", "IFC2CA")
|
||||
timestamp = int(datetime.now().timestamp())
|
||||
ownerHistory = self.f.createIfcOwnerHistory(p_o, application, "READWRITE", None, None, None, None, timestamp)
|
||||
|
||||
return ownerHistory
|
||||
|
||||
def create_reference_subrep(self, globalAxes):
|
||||
modelRep = self.f.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, globalAxes, None)
|
||||
bodySubRep = self.f.createIfcGeometricRepresentationSubContext(
|
||||
"Body", "Model", None, None, None, None, modelRep, None, "MODEL_VIEW", None
|
||||
)
|
||||
refSubRep = self.f.createIfcGeometricRepresentationSubContext(
|
||||
"Reference", "Model", None, None, None, None, modelRep, None, "GRAPH_VIEW", None
|
||||
result_cases[iCase]["assignment"] = ifc_file.create_entity(
|
||||
"IfcRelAssignsToGroup",
|
||||
**{
|
||||
"GlobalId": ios.guid.new(),
|
||||
"RelatedObjects": [],
|
||||
"RelatingGroup": result_cases[iCase]["case_instance"],
|
||||
},
|
||||
)
|
||||
|
||||
return {"model": modelRep, "body": bodySubRep, "reference": refSubRep}
|
||||
if ifc_model.HasResults:
|
||||
ifc_model.HasResults += tuple([result["case_instance"] for result in result_cases])
|
||||
else:
|
||||
ifc_model.HasResults = tuple([result["case_instance"] for result in result_cases])
|
||||
|
||||
def create_material(self, material):
|
||||
ifcMaterial = self.f.createIfcMaterial(material["name"], None, material["category"])
|
||||
data = []
|
||||
for _, element in enumerate(elements):
|
||||
group_name = getGroupName(element["ref_id"])
|
||||
name = element["Name"]
|
||||
info = get_element_data(result, group_name, element)
|
||||
assert len(info["coords"]) >= 2
|
||||
for iCase, case_instance in enumerate(field_cases):
|
||||
displacements = get_element_result_data(
|
||||
result, case_instance, group_name, element, field_type="Displacements"
|
||||
)
|
||||
reaction = ifc_file.create_entity(
|
||||
"IfcStructuralCurveReaction" if element["geometry_type"] == "Edge" else "IfcStructuralSurfaceReaction",
|
||||
**{
|
||||
"GlobalId": ios.guid.new(),
|
||||
"Name": "Global Displacements for " + model_cases[iCase]["Name"] + f" on {name}",
|
||||
# "AppliedLoad": load["ifcLoad"],
|
||||
"GlobalOrLocal": "LOCAL_COORDS",
|
||||
"PredefinedType": "DISCRETE",
|
||||
},
|
||||
)
|
||||
result_cases[iCase]["assignment"].RelatedObjects += (reaction,)
|
||||
|
||||
mechProps = []
|
||||
if "youngModulus" in material["mechProps"]:
|
||||
youngModulus = self.f.createIfcPropertySingleValue(
|
||||
"YoungModulus", None, self.f.createIfcModulusOfElasticityMeasure(material["mechProps"]["youngModulus"])
|
||||
)
|
||||
mechProps.append(youngModulus)
|
||||
if "shearModulus" in material["mechProps"]:
|
||||
shearModulus = self.f.createIfcPropertySingleValue(
|
||||
"ShearModulus", None, self.f.createIfcModulusOfElasticityMeasure(material["mechProps"]["shearModulus"])
|
||||
)
|
||||
mechProps.append(shearModulus)
|
||||
if "poissonRatio" in material["mechProps"]:
|
||||
poissonRatio = self.f.createIfcPropertySingleValue(
|
||||
"PoissonRatio", None, self.f.createIfcPositiveRatioMeasure(material["mechProps"]["poissonRatio"])
|
||||
)
|
||||
mechProps.append(poissonRatio)
|
||||
if mechProps:
|
||||
self.f.createIfcMaterialProperties(
|
||||
"Pset_MaterialMechanical", material["name"], tuple(mechProps), ifcMaterial
|
||||
ifc_file.create_entity(
|
||||
"IfcRelConnectsStructuralActivity",
|
||||
**{
|
||||
"GlobalId": ios.guid.new(),
|
||||
"RelatingElement": ifc_file.by_id(element["id"]),
|
||||
"RelatedStructuralActivity": reaction,
|
||||
},
|
||||
)
|
||||
|
||||
commonProps = []
|
||||
if "massDensity" in material["commonProps"]:
|
||||
massDensity = self.f.createIfcPropertySingleValue(
|
||||
"MassDensity", None, self.f.createIfcMassDensityMeasure(material["commonProps"]["massDensity"])
|
||||
)
|
||||
commonProps.append(massDensity)
|
||||
if commonProps:
|
||||
self.f.createIfcMaterialProperties("Pset_MaterialCommon", material["name"], tuple(commonProps), ifcMaterial)
|
||||
|
||||
return ifcMaterial
|
||||
|
||||
def create_profile(self, profile):
|
||||
if profile["profileShape"] == "rectangular":
|
||||
ifcProfile = self.f.createIfcRectangleProfileDef(
|
||||
profile["profileType"], profile["profileName"], None, profile["xDim"], profile["yDim"]
|
||||
reaction.AppliedLoad = ifc_file.create_entity(
|
||||
"IfcStructuralLoadConfiguration",
|
||||
**{
|
||||
"Name": "Global Displacements for " + model_cases[iCase]["Name"] + f" on {name}",
|
||||
"Values": [],
|
||||
"Locations": tuple([tuple(node) for node in info["local_coords"]]),
|
||||
},
|
||||
)
|
||||
|
||||
if profile["profileShape"] == "iSymmetrical":
|
||||
ifcProfile = self.f.createIfcIShapeProfileDef(
|
||||
profile["profileType"],
|
||||
profile["profileName"],
|
||||
None,
|
||||
profile["commonProps"]["overallWidth"],
|
||||
profile["commonProps"]["overallDepth"],
|
||||
profile["commonProps"]["webThickness"],
|
||||
profile["commonProps"]["flangeThickness"],
|
||||
profile["commonProps"]["filletRadius"],
|
||||
)
|
||||
if element["geometry_type"] == "Edge":
|
||||
for iNode, node in enumerate(info["coords"]):
|
||||
location = f"({node[0]}, {node[1]}, {node[2]})"
|
||||
distance = info["local_coords"][iNode][0]
|
||||
|
||||
mechProps = []
|
||||
if "massPerLength" in profile["mechProps"]:
|
||||
massPerLength = self.f.createIfcPropertySingleValue(
|
||||
"MassPerLength", None, self.f.createIfcMassPerLengthMeasure(profile["mechProps"]["massPerLength"])
|
||||
)
|
||||
mechProps.append(massPerLength)
|
||||
if "crossSectionArea" in profile["mechProps"]:
|
||||
crossSectionArea = self.f.createIfcPropertySingleValue(
|
||||
"CrossSectionArea", None, self.f.createIfcAreaMeasure(profile["mechProps"]["crossSectionArea"])
|
||||
)
|
||||
mechProps.append(crossSectionArea)
|
||||
if "momentOfInertiaY" in profile["mechProps"]:
|
||||
momentOfInertiaY = self.f.createIfcPropertySingleValue(
|
||||
"MomentOfInertiaY",
|
||||
None,
|
||||
self.f.createIfcMomentOfInertiaMeasure(profile["mechProps"]["momentOfInertiaY"]),
|
||||
)
|
||||
mechProps.append(momentOfInertiaY)
|
||||
if "momentOfInertiaZ" in profile["mechProps"]:
|
||||
momentOfInertiaZ = self.f.createIfcPropertySingleValue(
|
||||
"MomentOfInertiaZ",
|
||||
None,
|
||||
self.f.createIfcMomentOfInertiaMeasure(profile["mechProps"]["momentOfInertiaZ"]),
|
||||
)
|
||||
mechProps.append(momentOfInertiaZ)
|
||||
if "torsionalConstantX" in profile["mechProps"]:
|
||||
torsionalConstantX = self.f.createIfcPropertySingleValue(
|
||||
"TorsionalConstantX",
|
||||
None,
|
||||
self.f.createIfcMomentOfInertiaMeasure(profile["mechProps"]["torsionalConstantX"]),
|
||||
)
|
||||
mechProps.append(torsionalConstantX)
|
||||
if mechProps:
|
||||
self.f.createIfcProfileProperties(
|
||||
"Pset_ProfileMechanical", profile["profileName"], tuple(mechProps), ifcProfile
|
||||
)
|
||||
DX = displacements["DX"][iNode]
|
||||
DY = displacements["DY"][iNode]
|
||||
DZ = displacements["DZ"][iNode]
|
||||
DRX = displacements["DRX"][iNode]
|
||||
DRY = displacements["DRY"][iNode]
|
||||
DRZ = displacements["DRZ"][iNode]
|
||||
|
||||
return ifcProfile
|
||||
data.append([name, f"LCC-{iCase + 1} @ {distance}", location, DX, DY, DZ, DRX, DRY, DRZ])
|
||||
|
||||
def create_geometry(self, object):
|
||||
if object["geometryType"] == "point":
|
||||
point = self.f.createIfcCartesianPoint(tuple(object["geometry"]))
|
||||
vertex = self.f.createIfcVertexPoint(point)
|
||||
vertexTopologyRep = self.f.createIfcTopologyRepresentation(
|
||||
self.reps["reference"], "Reference", "Vertex", (vertex,)
|
||||
)
|
||||
vertexProdDefShape = self.f.createIfcProductDefinitionShape(None, None, (vertexTopologyRep,))
|
||||
pointValue = ifc_file.create_entity(
|
||||
"IfcStructuralLoadSingleDisplacement",
|
||||
**{
|
||||
"Name": "Global Displacements for "
|
||||
+ model_cases[iCase]["Name"]
|
||||
+ f" @ {distance} on {name}",
|
||||
"DisplacementX": DX,
|
||||
"DisplacementY": DY,
|
||||
"DisplacementZ": DZ,
|
||||
"RotationalDisplacementRX": DRX,
|
||||
"RotationalDisplacementRY": DRY,
|
||||
"RotationalDisplacementRZ": DRZ,
|
||||
},
|
||||
)
|
||||
reaction.AppliedLoad.Values += (pointValue,)
|
||||
|
||||
return vertexProdDefShape
|
||||
elif element["geometry_type"] == "Face":
|
||||
for iNode, node in enumerate(info["coords"]):
|
||||
location = f"({node[0]}, {node[1]}, {node[2]})"
|
||||
distance = tuple(info["local_coords"][iNode])
|
||||
|
||||
if object["geometryType"] == "line":
|
||||
startPoint = self.f.createIfcCartesianPoint(tuple(object["geometry"][0]))
|
||||
startVertex = self.f.createIfcVertexPoint(startPoint)
|
||||
endPoint = self.f.createIfcCartesianPoint(tuple(object["geometry"][1]))
|
||||
endVertex = self.f.createIfcVertexPoint(endPoint)
|
||||
edge = self.f.createIfcEdge(startVertex, endVertex)
|
||||
edgeTopologyRep = self.f.createIfcTopologyRepresentation(
|
||||
self.reps["reference"], "Reference", "Edge", (edge,)
|
||||
)
|
||||
edgeProdDefShape = self.f.createIfcProductDefinitionShape(None, None, (edgeTopologyRep,))
|
||||
DX = displacements["DX"][iNode]
|
||||
DY = displacements["DY"][iNode]
|
||||
DZ = displacements["DZ"][iNode]
|
||||
DRX = displacements["DRX"][iNode]
|
||||
DRY = displacements["DRY"][iNode]
|
||||
DRZ = displacements["DRZ"][iNode]
|
||||
|
||||
return edgeProdDefShape
|
||||
data.append([name, f"LCC-{iCase + 1} @ {distance}", location, DX, DY, DZ, DRX, DRY, DRZ])
|
||||
|
||||
if object["geometryType"] == "surface":
|
||||
verts = [None for _ in range(len(object["geometry"]))]
|
||||
for i, p in enumerate(object["geometry"]):
|
||||
point = self.f.createIfcCartesianPoint(tuple(p))
|
||||
verts[i] = self.f.createIfcVertexPoint(point)
|
||||
pointValue = ifc_file.create_entity(
|
||||
"IfcStructuralLoadSingleDisplacement",
|
||||
**{
|
||||
"Name": "Global Displacements for "
|
||||
+ model_cases[iCase]["Name"]
|
||||
+ f" @ {distance} on {name}",
|
||||
"DisplacementX": DX,
|
||||
"DisplacementY": DY,
|
||||
"DisplacementZ": DZ,
|
||||
"RotationalDisplacementRX": DRX,
|
||||
"RotationalDisplacementRY": DRY,
|
||||
"RotationalDisplacementRZ": DRZ,
|
||||
},
|
||||
)
|
||||
reaction.AppliedLoad.Values += (pointValue,)
|
||||
|
||||
orientedEdges = [None for _ in range(len(object["geometry"]))]
|
||||
for i, v in enumerate(verts):
|
||||
v2Index = (i + 1) if i < len(verts) - 1 else 0
|
||||
edge = self.f.createIfcEdge(v, verts[v2Index])
|
||||
orientedEdges[i] = self.f.createIfcOrientedEdge(None, None, edge, True)
|
||||
|
||||
edgeLoop = self.f.createIfcEdgeLoop(tuple(orientedEdges))
|
||||
localAxes = self.create_orientation(object["orientation"])
|
||||
plane = self.f.createIfcPlane(localAxes)
|
||||
faceBound = self.f.createIfcFaceBound(edgeLoop, True)
|
||||
face = self.f.createIfcFaceSurface((faceBound,), plane, True)
|
||||
faceTopologyRep = self.f.createIfcTopologyRepresentation(
|
||||
self.reps["reference"], "Reference", "Face", (face,)
|
||||
)
|
||||
faceProdDefShape = self.f.createIfcProductDefinitionShape(None, None, (faceTopologyRep,))
|
||||
|
||||
return faceProdDefShape
|
||||
|
||||
def create_applied_conditions(self, bc, geometryType):
|
||||
for dof in ["dx", "dy", "dz"]:
|
||||
if isinstance(bc[dof], bool):
|
||||
bc[dof] = self.f.createIfcBoolean(bc[dof])
|
||||
else:
|
||||
if geometryType == "point":
|
||||
bc[dof] = self.f.createIfcLinearStiffnessMeasure(bc[dof])
|
||||
if geometryType == "line":
|
||||
bc[dof] = self.f.createIfcModulusOfLinearSubgradeReactionMeasure(bc[dof])
|
||||
if geometryType == "surface":
|
||||
bc[dof] = self.f.createIfcModulusOfSubgradeReactionMeasure(bc[dof])
|
||||
|
||||
for dof in ["drx", "dry", "drz"]:
|
||||
if isinstance(bc[dof], bool):
|
||||
bc[dof] = self.f.createIfcBoolean(bc[dof])
|
||||
else:
|
||||
if geometryType == "point":
|
||||
bc[dof] = self.f.createIfcRotationalStiffnessMeasure(bc[dof])
|
||||
if geometryType == "line":
|
||||
bc[dof] = self.f.createIfcModulusOfRotationalSubgradeReactionMeasure(bc[dof])
|
||||
|
||||
return bc
|
||||
return data
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
inputFilename = "grid_of_beams.json"
|
||||
outputFilename = "grid_of_beams.ifc"
|
||||
|
||||
ca2ifc = CA2IFC(inputFilename, outputFilename)
|
||||
ca2ifc.convert()
|
||||
def getGroupName(name):
|
||||
if "|" in name:
|
||||
info = name.split("|")
|
||||
sortName = "".join(c for c in info[0] if c.isupper())
|
||||
return f"{sortName[2:]}_{info[1]}"
|
||||
else:
|
||||
return name
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# STEP: DEFINE SUPPORTS AND CONSTRAINTS
|
||||
connection = AFFE_CHAR_MECA(
|
||||
MODELE = model,
|
||||
{%- if vertexConnections %}
|
||||
LIAISON_DDL = (
|
||||
{%- for conn in vertexConnections %}
|
||||
{%- if conn.appliedCondition %}
|
||||
{%- for i in range(len(conn.liaisons.coeffs)) %}
|
||||
_F(
|
||||
GROUP_NO = {{ conn.liaisons.groupNames }},
|
||||
DDL = {{ conn.liaisons.dofs[i] }},
|
||||
COEF_MULT = {{ conn.liaisons.coeffs[i] }},
|
||||
COEF_IMPO = 0.0
|
||||
),
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- for rel in conn.related_elements %}
|
||||
{%- for i in range(len(rel.liaisons.coeffs)) %}
|
||||
_F(
|
||||
GROUP_NO = {{ rel.liaisons.groupNames }},
|
||||
DDL = {{ rel.liaisons.dofs[i] }},
|
||||
COEF_MULT = {{ rel.liaisons.coeffs[i] }},
|
||||
COEF_IMPO = 0.0
|
||||
),
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
),
|
||||
{%- endif %}
|
||||
{%- if edgeConnections %}
|
||||
LIAISON_GROUP = (
|
||||
{%- for conn in edgeConnections %}
|
||||
{%- if conn.appliedCondition %}
|
||||
{%- for i in range(len(conn.liaisons.coeffs)) %}
|
||||
_F(
|
||||
GROUP_NO_1 = {{ tuple([conn.liaisons.groupNames[0]]) }},
|
||||
GROUP_NO_2 = {{ tuple([conn.liaisons.groupNames[0]]) }},
|
||||
DDL_1 = {{ conn.liaisons.dofs[i] }},
|
||||
DDL_2 = {{ conn.liaisons.dofs[i] }},
|
||||
COEF_MULT_1 = {{ conn.liaisons.coeffs[i] }},
|
||||
COEF_MULT_2 = (0.0, 0.0, 0.0),
|
||||
COEF_IMPO = 0.0
|
||||
),
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- for rel in conn.related_elements %}
|
||||
{%- for i in range(len(rel.liaisons.coeffs)) %}
|
||||
_F(
|
||||
GROUP_NO_1 = {{ tuple([rel.liaisons.groupNames[0]]) }},
|
||||
GROUP_NO_2 = {{ tuple([rel.liaisons.groupNames[3]]) }},
|
||||
DDL_1 = {{ tuple(rel.liaisons.dofs[i][:3]) }},
|
||||
DDL_2 = {{ tuple(rel.liaisons.dofs[i][:3]) }},
|
||||
COEF_MULT_1 = {{ tuple(rel.liaisons.coeffs[i][:3]) }},
|
||||
COEF_MULT_2 = {{ tuple(rel.liaisons.coeffs[i][3:]) }},
|
||||
COEF_IMPO = 0.0
|
||||
),
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
),
|
||||
{%- endif %}
|
||||
{%- if unifiedConnections %}
|
||||
LIAISON_UNIF = (
|
||||
{%- for conn in unifiedConnections %}
|
||||
_F(
|
||||
GROUP_NO = {{ conn.unifiedGroupNames }},
|
||||
DDL = ('DX', 'DY', 'DZ', 'DRX', 'DRY', 'DRZ')
|
||||
),
|
||||
{%- endfor %}
|
||||
),
|
||||
{%- endif %}
|
||||
{%- if rigidLinkGroupNames %}
|
||||
LIAISON_SOLIDE = (
|
||||
{%- for groupName in rigidLinkGroupNames %}
|
||||
_F(GROUP_MA = {{ tuple([groupName]) }}),
|
||||
{%- endfor %}
|
||||
),
|
||||
{%- endif %}
|
||||
)
|
||||
{{ "\n" }}
|
||||
@@ -0,0 +1,115 @@
|
||||
# STEP: DEFINE ELEMENTS
|
||||
element = AFFE_CARA_ELEM(
|
||||
MODELE = model,
|
||||
POUTRE = (
|
||||
{%- for _, profile in profiles.items() %}
|
||||
{%- if profile.properties %}
|
||||
_F(
|
||||
GROUP_MA = {{ profile.groupNames }},
|
||||
SECTION = 'GENERALE',
|
||||
CARA = ('A', 'IY', 'IZ', 'JX'),
|
||||
VALE = ({{ profile.properties.CrossSectionArea }}, {{ profile.properties.MomentOfInertiaY }}, {{ profile.properties.MomentOfInertiaZ }}, {{ profile.properties.TorsionalConstantX }})
|
||||
),
|
||||
{%- elif profile.type == "IfcRectangleProfileDef" and profile.ProfileType == "AREA" %}
|
||||
_F(
|
||||
GROUP_MA = {{ profile.groupNames }},
|
||||
SECTION = 'RECTANGLE',
|
||||
CARA = ('HY', 'HZ'),
|
||||
VALE = ({{ profile.XDim }}, {{ profile.YDim }})
|
||||
),
|
||||
{%- elif profile.type == "IfcRectangleHollowProfileDef" and profile.ProfileType == "AREA" %}
|
||||
_F(
|
||||
GROUP_MA = {{ profile.groupNames }},
|
||||
SECTION = 'RECTANGLE',
|
||||
CARA = ('HY', 'HZ', 'EPY', 'EPZ'),
|
||||
VALE = ({{ profile.XDim }}, {{ profile.YDim }}, {{ profile.WallThickness }}, {{ profile.WallThickness }})
|
||||
),
|
||||
{%- else %}
|
||||
_F(
|
||||
GROUP_MA = {{ profile.groupNames }},
|
||||
SECTION = 'GENERALE',
|
||||
CARA = ('A', 'IY', 'IZ', 'JX'),
|
||||
VALE = ({{ profile.properties.CrossSectionArea }}, {{ profile.properties.MomentOfInertiaY }}, {{ profile.properties.MomentOfInertiaZ }}, {{ profile.properties.TorsionalConstantX }})
|
||||
),
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- if rigidLinkGroupNames %}
|
||||
_F(
|
||||
GROUP_MA = {{ rigidLinkGroupNames }},
|
||||
SECTION = 'RECTANGLE',
|
||||
CARA = ('HY', 'HZ'),
|
||||
VALE = (1.0, 1.0)
|
||||
),
|
||||
{%- endif %}
|
||||
),
|
||||
COQUE = (
|
||||
{%- for el in shellElements %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([getGroupName(el.ref_id)]) }},
|
||||
EPAIS = {{ el.Thickness }},
|
||||
VECTEUR = {{ tuple(el.orientation[0]) }}
|
||||
),
|
||||
{%- endfor %}
|
||||
),
|
||||
DISCRET = (
|
||||
{%- for conn in vertexConnections %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([getGroupName(conn.ref_id) + "_0D"]) }},
|
||||
CARA = 'K_TR_D_N',
|
||||
VALE = {{ conn.stiffnesses }},
|
||||
REPERE = 'LOCAL'
|
||||
),
|
||||
{%- if includeZeroLength1DSprings %}
|
||||
{%- for rel in conn.related_elements %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([rel.springGroupName]) }},
|
||||
CARA = 'K_TR_D_L',
|
||||
VALE = {{ rel.stiffnesses }},
|
||||
REPERE = 'LOCAL'
|
||||
),
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- for conn in edgeConnections %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([getGroupName(conn.ref_id) + "_0D"]) }},
|
||||
CARA = 'K_TR_D_N',
|
||||
VALE = {{ conn.stiffnesses }},
|
||||
REPERE = 'LOCAL'
|
||||
),
|
||||
{%- endfor %}
|
||||
),
|
||||
ORIENTATION = (
|
||||
{%- for el in beamElements %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([getGroupName(el.ref_id)]) }},
|
||||
CARA = 'VECT_Y',
|
||||
VALE = {{ tuple(el.orientation[1]) }}
|
||||
),
|
||||
{%- endfor %}
|
||||
{%- for conn in vertexConnections %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([getGroupName(conn.ref_id) + "_0D"]) }},
|
||||
CARA = 'VECT_X_Y',
|
||||
VALE = {{ tuple(conn.orientation[0] + conn.orientation[1]) }}
|
||||
),
|
||||
{%- if includeZeroLength1DSprings %}
|
||||
{%- for rel in conn.related_elements %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([rel.springGroupName]) }},
|
||||
CARA = 'VECT_X_Y',
|
||||
VALE = {{ tuple(rel.orientation[0] + rel.orientation[1]) }},
|
||||
),
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- for conn in edgeConnections %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([getGroupName(conn.ref_id) + "_0D"]) }},
|
||||
CARA = 'VECT_X_Y',
|
||||
VALE = {{ tuple(conn.orientation[0] + conn.orientation[1]) }}
|
||||
),
|
||||
{%- endfor %}
|
||||
),
|
||||
)
|
||||
{{ "\n" }}
|
||||
@@ -0,0 +1,44 @@
|
||||
# STEP: DEFINE TIME
|
||||
{{ analysis_time }} = DEFI_LIST_REEL(
|
||||
DEBUT = {{ start }},
|
||||
INTERVALLE = _F(
|
||||
JUSQU_A = {{ end }},
|
||||
NOMBRE = {{ steps }}
|
||||
)
|
||||
)
|
||||
|
||||
# STEP: DEFINE LOADS
|
||||
{{ load }} = AFFE_CHAR_MECA_F(
|
||||
MODELE = model,
|
||||
FORCE_NODALE = (
|
||||
{%- for el in vertexLoadElements %}
|
||||
_F(
|
||||
GROUP_NO = {{ tuple([getGroupName(el.ref_id)]) }},
|
||||
{%- for key, load in el.loads[load_key].items() %}
|
||||
{{ key }} = DEFI_FONCTION(NOM_PARA='INST', ABSCISSE={{ time }}, ORDONNEE={{ tuple(load) }}),
|
||||
{%- endfor %}
|
||||
),
|
||||
{%- endfor %}
|
||||
),
|
||||
FORCE_POUTRE = (
|
||||
{%- for el in edgeLoadElements %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([getGroupName(el.ref_id)]) }},
|
||||
{%- for key, load in el.loads[load_key].items() %}
|
||||
{{ key }} = DEFI_FONCTION(NOM_PARA='INST', ABSCISSE={{ time }}, ORDONNEE={{ tuple(load) }}),
|
||||
{%- endfor %}
|
||||
),
|
||||
{%- endfor %}
|
||||
),
|
||||
FORCE_COQUE = (
|
||||
{%- for el in faceLoadElements %}
|
||||
_F(
|
||||
GROUP_MA = {{ tuple([getGroupName(el.ref_id)]) }},
|
||||
{%- for key, load in el.loads[load_key].items() %}
|
||||
{{ key }} = DEFI_FONCTION(NOM_PARA='INST', ABSCISSE={{ time }}, ORDONNEE={{ tuple(load) }}),
|
||||
{%- endfor %}
|
||||
),
|
||||
{%- endfor %}
|
||||
),
|
||||
)
|
||||
{{ "\n" }}
|
||||
@@ -0,0 +1,28 @@
|
||||
# STEP: DEFINE MATERIALS
|
||||
{%- for i, (_, material) in enumerate(materials.items()) %}
|
||||
{{ "mat" + "_%s" % i }} = DEFI_MATERIAU(
|
||||
ELAS = _F(
|
||||
E = {{ material.properties.YoungModulus }},
|
||||
NU = {{ material.properties.PoissonRatio }},
|
||||
RHO = {{ material.properties.MassDensity }}
|
||||
)
|
||||
)
|
||||
{% endfor %}
|
||||
material = AFFE_MATERIAU(
|
||||
MAILLAGE = mesh,
|
||||
AFFE = (
|
||||
{%- for i, (_, material) in enumerate(materials.items()) %}
|
||||
_F(
|
||||
GROUP_MA = {{ material.groupNames }},
|
||||
MATER = {{ "mat" + "_%s" % i }},
|
||||
),
|
||||
{%- endfor %}
|
||||
{%- if rigidLinkGroupNames %}
|
||||
_F(
|
||||
GROUP_MA = {{ rigidLinkGroupNames }},
|
||||
MATER = {{ "mat_0" }},
|
||||
),
|
||||
{%- endif %}
|
||||
)
|
||||
)
|
||||
{{ "\n" }}
|
||||
@@ -0,0 +1,47 @@
|
||||
# STEP: DEFINE MODEL
|
||||
model = AFFE_MODELE(
|
||||
MAILLAGE = mesh,
|
||||
AFFE = (
|
||||
_F(
|
||||
TOUT = 'OUI',
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = '3D'
|
||||
),
|
||||
{%- if faceGroupNames %}
|
||||
_F(
|
||||
GROUP_MA = {{ faceGroupNames }},
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = 'DKT'
|
||||
),
|
||||
{%- endif %}
|
||||
{%- if edgeGroupNames %}
|
||||
_F(
|
||||
GROUP_MA = {{ edgeGroupNames }},
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = 'POU_D_E'
|
||||
),
|
||||
{%- endif %}
|
||||
{%- if point0DGroupNames %}
|
||||
_F(
|
||||
GROUP_MA = {{ point0DGroupNamesPlus }},
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = 'DIS_TR'
|
||||
),
|
||||
{%- endif %}
|
||||
{%- if point1DGroupNames %}
|
||||
_F(
|
||||
GROUP_MA = {{ point1DGroupNames }},
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = 'DIS_TR'
|
||||
),
|
||||
{%- endif %}
|
||||
{%- if rigidLinkGroupNames %}
|
||||
_F(
|
||||
GROUP_MA = {{ rigidLinkGroupNames }},
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = 'POU_D_E'
|
||||
),
|
||||
{%- endif %}
|
||||
)
|
||||
)
|
||||
{{ "\n" }}
|
||||
@@ -0,0 +1,13 @@
|
||||
P actions make_etude
|
||||
P memory_limit {{ allocated_memory }}
|
||||
P time_limit {{ time_limit }}
|
||||
P version stable
|
||||
F comm {{ model_name }}_{{ run_label }}.comm D 1
|
||||
F libr {{ model_name }}.med D 20
|
||||
F mess {{ model_name }}_{{ run_label }}.mess R 6
|
||||
{%- if "LC" in cases %}
|
||||
F rmed {{ model_name + "_LC" }}.rmed R 80
|
||||
{%- endif %}
|
||||
{%- if "COMB" in cases %}
|
||||
F rmed {{ model_name + "_COMB" }}.rmed R 81
|
||||
{%- endif %}
|
||||
@@ -0,0 +1,11 @@
|
||||
# STEP: RESULT EXTRACTION
|
||||
IMPR_RESU(
|
||||
FORMAT="MED",
|
||||
UNITE={{unit_number}},
|
||||
RESU=_F(
|
||||
RESULTAT={{res_Bld}},
|
||||
NOM_CHAM=("DEPL", "EFGE_NOEU"),
|
||||
NOM_CHAM_MED=("MODEL_DISP", "ELEMENT_FORCE"),
|
||||
),
|
||||
)
|
||||
{{"\n"}}
|
||||
@@ -0,0 +1,4 @@
|
||||
# STEP: CONCLUDE STUDY
|
||||
# code_aster.close()
|
||||
FIN()
|
||||
{{"\n"}}
|
||||
@@ -0,0 +1,3 @@
|
||||
# STEP: READ MED FILE
|
||||
mesh = LIRE_MAILLAGE(FORMAT="MED", UNITE=20)
|
||||
{{"\n"}}
|
||||
@@ -0,0 +1,27 @@
|
||||
# STEP: RUN ANALYSIS
|
||||
{{ res_Bld }} = MECA_STATIQUE(
|
||||
MODELE = model,
|
||||
CHAM_MATER = material,
|
||||
CARA_ELEM = element,
|
||||
LIST_INST = {{ analysis_time }},
|
||||
EXCIT = (
|
||||
_F(
|
||||
CHARGE = connection
|
||||
),
|
||||
_F(
|
||||
CHARGE = {{ load }}
|
||||
)
|
||||
),
|
||||
SOLVEUR=_F(
|
||||
NPREC=12,
|
||||
RESI_RELA=1e-1,
|
||||
STOP_SINGULIER='NON',
|
||||
)
|
||||
)
|
||||
|
||||
{{ res_Bld }} = CALC_CHAMP(
|
||||
reuse = {{ res_Bld }},
|
||||
RESULTAT = {{ res_Bld }},
|
||||
CONTRAINTE=('EFGE_NOEU', ),
|
||||
)
|
||||
{{ "\n" }}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Ifc2CA - IFC Code_Aster utility
|
||||
# Copyright (C) 2020, 2021, 2023, 2024 Ioannis P. Christovasilis <ipc@aethereng.com>
|
||||
#
|
||||
# This file is part of Ifc2CA.
|
||||
#
|
||||
# Ifc2CA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ifc2CA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Ifc2CA. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# STEP: INITIALIZE STUDY
|
||||
# import code_aster
|
||||
# code_aster.init()
|
||||
DEBUT()
|
||||
{{"\n"}}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Ifc2CA - IFC Code_Aster utility
|
||||
# Copyright (C) 2020, 2021 Ioannis P. Christovasilis <ipc@aethereng.com>
|
||||
# Copyright (C) 2020, 2021, 2023, 2024 Ioannis P. Christovasilis <ipc@aethereng.com>
|
||||
#
|
||||
# This file is part of Ifc2CA.
|
||||
#
|
||||
@@ -16,26 +16,31 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Ifc2CA. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import salome
|
||||
import salome_notebook
|
||||
import salome_version
|
||||
import numpy as np
|
||||
import itertools
|
||||
from pathlib import Path
|
||||
|
||||
flatten = itertools.chain.from_iterable
|
||||
|
||||
mesh_size = {{ mesh_size }}
|
||||
med_path = r"{{ med_path }}"
|
||||
json_path = r"{{ json_path }}"
|
||||
|
||||
with open(json_path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
|
||||
class MODEL:
|
||||
def __init__(self, dataFilename, medFilename, meshSize):
|
||||
self.dataFilename = dataFilename
|
||||
self.medFilename = medFilename
|
||||
self.meshSize = meshSize
|
||||
def __init__(self):
|
||||
self.medFilename = med_path
|
||||
self.mesh_size = mesh_size
|
||||
self.tolLoc = 0
|
||||
self.mesh = None
|
||||
self.meshNodes = None
|
||||
@@ -82,29 +87,22 @@ class MODEL:
|
||||
|
||||
return self.geompy.MakeFaceWires(LineList, 1)
|
||||
|
||||
def makeObject(self, geometry, geometryType):
|
||||
if geometryType == "point":
|
||||
def makeObject(self, geometry, geometry_type):
|
||||
if geometry_type == "Vertex":
|
||||
return self.makePoint(geometry)
|
||||
if geometryType == "line":
|
||||
if geometry_type == "Edge":
|
||||
return self.makeLine(geometry)
|
||||
if geometryType == "surface":
|
||||
if geometry_type == "Face":
|
||||
return self.makeFace(geometry)
|
||||
|
||||
def makePartition(self, objects, geometryType):
|
||||
if geometryType == "point":
|
||||
def makePartition(self, objects, geometry_type):
|
||||
if geometry_type == "Vertex":
|
||||
shapeType = "VERTEX"
|
||||
if geometryType == "line":
|
||||
if geometry_type == "Edge":
|
||||
shapeType = "EDGE"
|
||||
if geometryType == "surface":
|
||||
if geometry_type == "Face":
|
||||
shapeType = "FACE"
|
||||
return self.geompy.MakePartition(
|
||||
objects, [], [], [], self.geompy.ShapeType[shapeType], 0, [], 1
|
||||
)
|
||||
|
||||
def getLinkGeometry(self, ecc, orientation, finalPoint):
|
||||
vector = np.array(orientation).transpose().dot(ecc["vector"])
|
||||
initialPoint = (np.array(finalPoint) - vector).tolist()
|
||||
return [initialPoint, finalPoint]
|
||||
return self.geompy.MakePartition(objects, [], [], [], self.geompy.ShapeType[shapeType], 0, [], 1)
|
||||
|
||||
def length(self, geometry):
|
||||
return (
|
||||
@@ -115,18 +113,17 @@ class MODEL:
|
||||
|
||||
def create(self):
|
||||
# Read data from input file
|
||||
with open(self.dataFilename) as dataFile:
|
||||
data = json.load(dataFile)
|
||||
# data = data
|
||||
|
||||
elements = data["elements"]
|
||||
connections = data["connections"]
|
||||
self.elements = elements = data["elements"]
|
||||
self.connections = connections = data["connections"]
|
||||
# --> Delete this reference data and repopulate it with the objects
|
||||
# while going through elements
|
||||
for conn in connections:
|
||||
conn["relatedElements"] = []
|
||||
conn["related_elements"] = []
|
||||
# End <--
|
||||
|
||||
meshSize = self.meshSize
|
||||
mesh_size = self.mesh_size
|
||||
|
||||
dec = 7 # 4 decimals for length in mm
|
||||
tol = 10 ** (-dec - 3 + 1)
|
||||
@@ -134,7 +131,7 @@ class MODEL:
|
||||
self.tolLoc = tol * 10 * 2
|
||||
tolLoc = self.tolLoc
|
||||
|
||||
NEW_SALOME = int(salome_version.getVersion()[0]) >= 9
|
||||
self.NEW_SALOME = NEW_SALOME = int(salome_version.getVersion()[0]) >= 9
|
||||
salome.salome_init()
|
||||
theStudy = salome.myStudy
|
||||
notebook = salome_notebook.NoteBook(theStudy)
|
||||
@@ -142,10 +139,11 @@ class MODEL:
|
||||
###
|
||||
### GEOM component
|
||||
###
|
||||
import GEOM
|
||||
from salome.geom import geomBuilder
|
||||
import math
|
||||
|
||||
import GEOM
|
||||
import SALOMEDS
|
||||
from salome.geom import geomBuilder
|
||||
|
||||
gg = salome.ImportComponentGUI("GEOM")
|
||||
if NEW_SALOME:
|
||||
@@ -163,9 +161,9 @@ class MODEL:
|
||||
geompy.addToStudy(OY, "OY")
|
||||
geompy.addToStudy(OZ, "OZ")
|
||||
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
if len([e for e in elements if e["geometry_type"] == "Edge"]) > 0:
|
||||
buildingShapeType = "EDGE"
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
if len([e for e in elements if e["geometry_type"] == "Face"]) > 0:
|
||||
buildingShapeType = "FACE"
|
||||
|
||||
### Define entities ###
|
||||
@@ -175,31 +173,23 @@ class MODEL:
|
||||
|
||||
# Loop 1
|
||||
for el in elements:
|
||||
el["elemObj"] = self.makeObject(el["geometry"], el["geometryType"])
|
||||
el["elemObj"] = self.makeObject(el["geometry"], el["geometry_type"])
|
||||
|
||||
el["connObjs"] = [None for _ in el["connections"]]
|
||||
el["linkObjs"] = [None for _ in el["connections"]]
|
||||
el["linkPointObjs"] = [[None, None] for _ in el["connections"]]
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
conn = [
|
||||
c for c in connections if c["referenceName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
conn = [c for c in connections if c["ref_id"] == rel["related_connection"]][0]
|
||||
if rel["eccentricity"]:
|
||||
rel["index"] = len(conn["relatedElements"]) + 1
|
||||
conn["relatedElements"].append(rel)
|
||||
rel["index"] = len(conn["related_elements"]) + 1
|
||||
conn["related_elements"].append(rel)
|
||||
|
||||
if not rel["eccentricity"]:
|
||||
el["connObjs"][j] = self.makeObject(
|
||||
conn["geometry"], conn["geometryType"]
|
||||
)
|
||||
el["connObjs"][j] = self.makeObject(conn["geometry"], conn["geometry_type"])
|
||||
else:
|
||||
if conn["geometryType"] == "point":
|
||||
geometry = self.getLinkGeometry(
|
||||
rel["eccentricity"], el["orientation"], conn["geometry"]
|
||||
)
|
||||
el["connObjs"][j] = self.makeObject(
|
||||
geometry[0], conn["geometryType"]
|
||||
)
|
||||
if conn["geometry_type"] == "Vertex":
|
||||
geometry = rel["eccentricity"]["point_on_element"], conn["geometry"]
|
||||
el["connObjs"][j] = self.makeObject(geometry[0], conn["geometry_type"])
|
||||
|
||||
el["linkPointObjs"][j][0] = self.geompy.MakeVertex(
|
||||
geometry[0][0], geometry[0][1], geometry[0][2]
|
||||
@@ -211,27 +201,18 @@ class MODEL:
|
||||
el["linkPointObjs"][j][0], el["linkPointObjs"][j][1]
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"Eccentricity defined for a %s geometryType"
|
||||
% conn["geometryType"]
|
||||
)
|
||||
el["partObj"] = self.makePartition(
|
||||
[el["elemObj"]] + el["connObjs"], el["geometryType"]
|
||||
)
|
||||
print("Eccentricity defined for a %s geometry_type" % conn["geometry_type"])
|
||||
el["partObj"] = self.makePartition([el["elemObj"]] + el["connObjs"], el["geometry_type"])
|
||||
el["elemObj"] = geompy.GetInPlace(el["partObj"], el["elemObj"], True)
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
el["connObjs"][j] = geompy.GetInPlace(
|
||||
el["partObj"], el["connObjs"][j], True
|
||||
)
|
||||
el["connObjs"][j] = geompy.GetInPlace(el["partObj"], el["connObjs"][j], True)
|
||||
for conn in connections:
|
||||
conn["connObj"] = self.makeObject(conn["geometry"], conn["geometryType"])
|
||||
conn["connObj"] = self.makeObject(conn["geometry"], conn["geometry_type"])
|
||||
|
||||
# Make assemble of Building Object
|
||||
bldObjs = []
|
||||
bldObjs.extend([el["partObj"] for el in elements])
|
||||
bldObjs.extend(
|
||||
flatten([[link for link in el["linkObjs"] if link] for el in elements])
|
||||
)
|
||||
bldObjs.extend(flatten([[link for link in el["linkObjs"] if link] for el in elements]))
|
||||
bldObjs.extend([conn["connObj"] for conn in connections])
|
||||
|
||||
bldComp = geompy.MakeCompound(bldObjs)
|
||||
@@ -240,59 +221,55 @@ class MODEL:
|
||||
|
||||
# Loop 2
|
||||
for el in elements:
|
||||
# geompy.addToStudy(el['partObj'], self.getGroupName(el['referenceName']))
|
||||
geompy.addToStudyInFather(
|
||||
el["partObj"], el["elemObj"], self.getGroupName(el["referenceName"])
|
||||
)
|
||||
# geompy.addToStudy(el['partObj'], self.getGroupName(el['ref_id']))
|
||||
geompy.addToStudyInFather(el["partObj"], el["elemObj"], self.getGroupName(el["ref_id"]))
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
conn = [
|
||||
c for c in connections if c["referenceName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
conn = [c for c in connections if c["ref_id"] == rel["related_connection"]][0]
|
||||
rel["conn_string"] = None
|
||||
if conn["geometryType"] == "point":
|
||||
if conn["geometry_type"] == "Vertex":
|
||||
rel["conn_string"] = "_0DC_"
|
||||
if conn["geometryType"] == "line":
|
||||
if conn["geometry_type"] == "Edge":
|
||||
rel["conn_string"] = "_1DC_"
|
||||
if conn["geometryType"] == "surface":
|
||||
if conn["geometry_type"] == "Face":
|
||||
rel["conn_string"] = "_2DC_"
|
||||
geompy.addToStudyInFather(
|
||||
el["partObj"],
|
||||
el["connObjs"][j],
|
||||
self.getGroupName(el["referenceName"])
|
||||
+ rel["conn_string"]
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ref_id"]) + rel["conn_string"] + self.getGroupName(rel["related_connection"]),
|
||||
)
|
||||
if rel["eccentricity"]:
|
||||
pass
|
||||
# geompy.addToStudy(el['linkObjs'][j], self.getGroupName(el['referenceName']) + '_1DR_' + self.getGroupName(rel['relatedConnection']))
|
||||
# geompy.addToStudyInFather(el['linkObjs'][j], el['linkPointObjs'][j][0], self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(el['referenceName']))
|
||||
# geompy.addToStudyInFather(el['linkObjs'][j], el['linkPointObjs'][j][0], self.getGroupName(rel['relatedConnection']) + '_0DC_%g' % rel['index'])
|
||||
# geompy.addToStudy(el['linkObjs'][j], self.getGroupName(el['ref_id']) + '_1DR_' + self.getGroupName(rel['related_connection']))
|
||||
# geompy.addToStudyInFather(el['linkObjs'][j], el['linkPointObjs'][j][0], self.getGroupName(rel['related_connection']) + '_0DC_' + self.getGroupName(el['ref_id']))
|
||||
# geompy.addToStudyInFather(el['linkObjs'][j], el['linkPointObjs'][j][0], self.getGroupName(rel['related_connection']) + '_0DC_%g' % rel['index'])
|
||||
|
||||
for conn in connections:
|
||||
# geompy.addToStudy(conn['connObj'], self.getGroupName(conn['referenceName']))
|
||||
geompy.addToStudyInFather(
|
||||
conn["connObj"], conn["connObj"], self.getGroupName(conn["referenceName"])
|
||||
)
|
||||
# geompy.addToStudy(conn['connObj'], self.getGroupName(conn['ref_id']))
|
||||
geompy.addToStudyInFather(conn["connObj"], conn["connObj"], self.getGroupName(conn["ref_id"]))
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
print("Building Geometry Defined in %g sec" % (elapsed_time))
|
||||
|
||||
# Define and add groups for all curve and surface members
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
if len([e for e in elements if e["geometry_type"] == "Edge"]) > 0:
|
||||
# Make compound of requested group
|
||||
compoundTemp = geompy.MakeCompound(
|
||||
[e["elemObj"] for e in elements if e["geometryType"] == "line"]
|
||||
)
|
||||
compoundTemp = geompy.MakeCompound([e["elemObj"] for e in elements if e["geometry_type"] == "Edge"])
|
||||
# Define group object and add to study
|
||||
curveCompound = geompy.GetInPlace(bldComp, compoundTemp, True)
|
||||
geompy.addToStudyInFather(bldComp, curveCompound, "CurveMembers")
|
||||
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
rigid_links = list(flatten([[link for link in el["linkObjs"] if link] for el in elements]))
|
||||
if len(rigid_links) > 0:
|
||||
# Make compound of requested group
|
||||
compoundTemp = geompy.MakeCompound(
|
||||
[e["elemObj"] for e in elements if e["geometryType"] == "surface"]
|
||||
)
|
||||
compoundTemp = geompy.MakeCompound(rigid_links)
|
||||
# Define group object and add to study
|
||||
rigidLinkCompound = geompy.GetInPlace(bldComp, compoundTemp, True)
|
||||
geompy.addToStudyInFather(bldComp, rigidLinkCompound, "RigidLinks")
|
||||
|
||||
if len([e for e in elements if e["geometry_type"] == "Face"]) > 0:
|
||||
# Make compound of requested group
|
||||
compoundTemp = geompy.MakeCompound([e["elemObj"] for e in elements if e["geometry_type"] == "Face"])
|
||||
# Define group object and add to study
|
||||
surfaceCompound = geompy.GetInPlace(bldComp, compoundTemp, True)
|
||||
geompy.addToStudyInFather(bldComp, surfaceCompound, "SurfaceMembers")
|
||||
@@ -300,45 +277,34 @@ class MODEL:
|
||||
# Loop 3
|
||||
for el in elements:
|
||||
# el['partObj'] = geompy.RestoreGivenSubShapes(bldComp, [el['partObj']], GEOM.FSM_GetInPlace, False, False)[0]
|
||||
geompy.addToStudyInFather(
|
||||
bldComp, el["elemObj"], self.getGroupName(el["referenceName"])
|
||||
)
|
||||
geompy.addToStudyInFather(bldComp, el["elemObj"], self.getGroupName(el["ref_id"]))
|
||||
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["connObjs"][j],
|
||||
self.getGroupName(el["referenceName"])
|
||||
+ rel["conn_string"]
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ref_id"]) + rel["conn_string"] + self.getGroupName(rel["related_connection"]),
|
||||
)
|
||||
if rel["eccentricity"]: # point geometry
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkObjs"][j],
|
||||
self.getGroupName(el["referenceName"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ref_id"]) + "_1DR_" + self.getGroupName(rel["related_connection"]),
|
||||
)
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkPointObjs"][j][0],
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_"
|
||||
+ self.getGroupName(el["referenceName"]),
|
||||
self.getGroupName(rel["related_connection"]) + "_0DC_" + self.getGroupName(el["ref_id"]),
|
||||
)
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkPointObjs"][j][1],
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_%g" % rel["index"],
|
||||
self.getGroupName(rel["related_connection"]) + "_0DC_%g" % rel["index"],
|
||||
)
|
||||
|
||||
for conn in connections:
|
||||
# conn['connObj'] = geompy.RestoreGivenSubShapes(bldComp, [conn['connObj']], GEOM.FSM_GetInPlace, False, False)[0]
|
||||
geompy.addToStudyInFather(
|
||||
bldComp, conn["connObj"], self.getGroupName(conn["referenceName"])
|
||||
)
|
||||
geompy.addToStudyInFather(bldComp, conn["connObj"], self.getGroupName(conn["ref_id"]))
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
@@ -359,15 +325,15 @@ class MODEL:
|
||||
smesh = smeshBuilder.New(theStudy)
|
||||
bldMesh = smesh.Mesh(bldComp)
|
||||
Regular_1D = bldMesh.Segment()
|
||||
Local_Length_1 = Regular_1D.LocalLength(meshSize, None, tolLoc)
|
||||
Local_Length_1 = Regular_1D.LocalLength(mesh_size, None, tolLoc)
|
||||
|
||||
if buildingShapeType == "FACE":
|
||||
NETGEN2D_ONLY = bldMesh.Triangle(algo=smeshBuilder.NETGEN_2D)
|
||||
NETGEN2D_Pars = NETGEN2D_ONLY.Parameters()
|
||||
NETGEN2D_Pars.SetMaxSize(meshSize)
|
||||
NETGEN2D_Pars.SetMaxSize(mesh_size)
|
||||
NETGEN2D_Pars.SetOptimize(1)
|
||||
NETGEN2D_Pars.SetFineness(2)
|
||||
NETGEN2D_Pars.SetMinSize(meshSize / 5.0)
|
||||
NETGEN2D_Pars.SetMinSize(mesh_size / 5.0)
|
||||
NETGEN2D_Pars.SetUseSurfaceCurvature(1)
|
||||
NETGEN2D_Pars.SetQuadAllowed(1)
|
||||
NETGEN2D_Pars.SetSecondOrder(0)
|
||||
@@ -383,142 +349,111 @@ class MODEL:
|
||||
smesh.SetName(NETGEN2D_ONLY.GetAlgorithm(), "NETGEN2D_ONLY")
|
||||
smesh.SetName(NETGEN2D_Pars, "NETGEN2D_Pars")
|
||||
|
||||
smesh.SetName(bldMesh.GetMesh(), "bldMesh")
|
||||
smesh.SetName(bldMesh.GetMesh(), "{{ mesh_name }}")
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
print("Meshing Operations Completed in %g sec" % (elapsed_time))
|
||||
|
||||
# Define and add groups for all curve and surface members
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
if len([e for e in elements if e["geometry_type"] == "Edge"]) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(curveCompound, "CurveMembers", SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, "CurveMembers")
|
||||
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
if len(rigid_links) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(rigidLinkCompound, "RigidLinks", SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, "RigidLinks")
|
||||
|
||||
if len([e for e in elements if e["geometry_type"] == "Face"]) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(surfaceCompound, "SurfaceMembers", SMESH.FACE)
|
||||
smesh.SetName(tempgroup, "SurfaceMembers")
|
||||
|
||||
# Define groups in Mesh
|
||||
for el in elements:
|
||||
if el["geometryType"] == "line":
|
||||
if el["geometry_type"] == "Edge":
|
||||
shapeType = SMESH.EDGE
|
||||
if el["geometryType"] == "surface":
|
||||
if el["geometry_type"] == "Face":
|
||||
shapeType = SMESH.FACE
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["elemObj"], self.getGroupName(el["referenceName"]), shapeType
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(el["referenceName"]))
|
||||
tempgroup = bldMesh.GroupOnGeom(el["elemObj"], self.getGroupName(el["ref_id"]), shapeType)
|
||||
smesh.SetName(tempgroup, self.getGroupName(el["ref_id"]))
|
||||
# tempgroup = bldMesh.GroupOnGeom(el["elemObj"], self.getGroupName(el["ref_id"]), SMESH.NODE)
|
||||
# smesh.SetName(tempgroup, self.getGroupName(el["ref_id"]))
|
||||
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["connObjs"][j],
|
||||
self.getGroupName(el["referenceName"])
|
||||
+ rel["conn_string"]
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ref_id"]) + rel["conn_string"] + self.getGroupName(rel["related_connection"]),
|
||||
SMESH.NODE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(el["referenceName"])
|
||||
+ rel["conn_string"]
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ref_id"]) + rel["conn_string"] + self.getGroupName(rel["related_connection"]),
|
||||
)
|
||||
rel["node"] = (
|
||||
bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
|
||||
).GetIDs()[0]
|
||||
rel["node"] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
|
||||
if rel["eccentricity"]:
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["linkObjs"][j],
|
||||
self.getGroupName(el["referenceName"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ref_id"]) + "_1DR_" + self.getGroupName(rel["related_connection"]),
|
||||
SMESH.EDGE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(el["referenceName"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ref_id"]) + "_1DR_" + self.getGroupName(rel["related_connection"]),
|
||||
)
|
||||
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["linkPointObjs"][j][0],
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_"
|
||||
+ self.getGroupName(el["referenceName"]),
|
||||
self.getGroupName(rel["related_connection"]) + "_0DC_" + self.getGroupName(el["ref_id"]),
|
||||
SMESH.NODE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_"
|
||||
+ self.getGroupName(el["referenceName"]),
|
||||
self.getGroupName(rel["related_connection"]) + "_0DC_" + self.getGroupName(el["ref_id"]),
|
||||
)
|
||||
rel["eccNode"] = (
|
||||
bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
|
||||
).GetIDs()[0]
|
||||
rel["eccNode"] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
|
||||
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["linkPointObjs"][j][1],
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
self.getGroupName(rel["related_connection"])
|
||||
+ "_0DC_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
+ self.getGroupName(rel["related_connection"]),
|
||||
SMESH.NODE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_%g" % rel["index"],
|
||||
self.getGroupName(rel["related_connection"]) + "_0DC_%g" % rel["index"],
|
||||
)
|
||||
|
||||
for conn in connections:
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
conn["connObj"], self.getGroupName(conn["referenceName"]), SMESH.NODE
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["referenceName"]))
|
||||
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ref_id"]), SMESH.NODE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ref_id"]))
|
||||
nodesId = bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
|
||||
tempgroup = bldMesh.Add0DElementsToAllNodes(
|
||||
nodesId, self.getGroupName(conn["referenceName"])
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["referenceName"] + "_0D"))
|
||||
if conn["geometryType"] == "point":
|
||||
tempgroup = bldMesh.Add0DElementsToAllNodes(nodesId, self.getGroupName(conn["ref_id"]))
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ref_id"] + "_0D"))
|
||||
if conn["geometry_type"] == "Vertex":
|
||||
conn["node"] = nodesId.GetIDs()[0]
|
||||
if conn["geometryType"] == "line":
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
conn["connObj"], self.getGroupName(conn["referenceName"]), SMESH.EDGE
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["referenceName"]))
|
||||
if conn["geometryType"] == "surface":
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
conn["connObj"], self.getGroupName(conn["referenceName"]), SMESH.FACE
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["referenceName"]))
|
||||
if conn["geometry_type"] == "Edge":
|
||||
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ref_id"]), SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ref_id"]))
|
||||
if conn["geometry_type"] == "Face":
|
||||
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ref_id"]), SMESH.FACE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ref_id"]))
|
||||
|
||||
# create 1D SEG2 spring elements
|
||||
for el in elements:
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
conn = [
|
||||
c for c in connections if c["referenceName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
if conn["geometryType"] == "point":
|
||||
conn = [c for c in connections if c["ref_id"] == rel["related_connection"]][0]
|
||||
if conn["geometry_type"] == "Vertex":
|
||||
grpName = bldMesh.CreateEmptyGroup(
|
||||
SMESH.EDGE,
|
||||
self.getGroupName(el["referenceName"])
|
||||
+ "_1DS_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ref_id"]) + "_1DS_" + self.getGroupName(rel["related_connection"]),
|
||||
)
|
||||
smesh.SetName(
|
||||
grpName,
|
||||
self.getGroupName(el["referenceName"])
|
||||
+ "_1DS_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ref_id"]) + "_1DS_" + self.getGroupName(rel["related_connection"]),
|
||||
)
|
||||
if not rel["eccentricity"]:
|
||||
conn = [
|
||||
conn
|
||||
for conn in connections
|
||||
if conn["referenceName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
conn = [conn for conn in connections if conn["ref_id"] == rel["related_connection"]][0]
|
||||
grpName.Add([bldMesh.AddEdge([conn["node"], rel["node"]])])
|
||||
else:
|
||||
grpName.Add([bldMesh.AddEdge([rel["eccNode"], rel["node"]])])
|
||||
@@ -545,26 +480,30 @@ class MODEL:
|
||||
except:
|
||||
print("ExportMED() failed. Invalid file name?")
|
||||
|
||||
if salome.sg.hasDesktop():
|
||||
if NEW_SALOME:
|
||||
salome.sg.updateObjBrowser()
|
||||
else:
|
||||
salome.sg.updateObjBrowser(1)
|
||||
# if salome.sg.hasDesktop():
|
||||
# if NEW_SALOME:
|
||||
# salome.sg.updateObjBrowser()
|
||||
# else:
|
||||
# salome.sg.updateObjBrowser(1)
|
||||
|
||||
elapsed_time = init_time - start_time
|
||||
print("ALL Operations Completed in %g sec" % (elapsed_time))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fileNames = ["structure_01"]
|
||||
files = fileNames
|
||||
model = MODEL()
|
||||
|
||||
meshSize = 0.1
|
||||
for el in model.elements:
|
||||
for j, conn in enumerate(el["connections"]):
|
||||
d = model.geompy.MinDistance(el["elemObj"], el["connObjs"][j])
|
||||
if d > 0:
|
||||
print(f'NOTE: Element {el["ref_id"]} and connection {conn["ref_id"]} have a distance of {d}')
|
||||
# elif d == 0:
|
||||
# print(
|
||||
# f'SUCCESS: Element {el["ref_id"]} and connection {conn["ref_id"]} have a distance of {d}'
|
||||
# )
|
||||
|
||||
for fileName in files:
|
||||
BASE_PATH = Path(
|
||||
"/home/jesusbill/Dev-Projects/github.com/IfcOpenShell/analysis-models/models/"
|
||||
)
|
||||
DATAFILENAME = BASE_PATH / fileName / f"{fileName}.json"
|
||||
MEDFILENAME = BASE_PATH / fileName / f"{fileName}.med"
|
||||
model = MODEL(DATAFILENAME, str(MEDFILENAME), meshSize)
|
||||
if salome.sg.hasDesktop():
|
||||
if model.NEW_SALOME:
|
||||
salome.sg.updateObjBrowser()
|
||||
else:
|
||||
salome.sg.updateObjBrowser(1)
|
||||
@@ -134,7 +134,7 @@ class Csv2Ifc:
|
||||
category = category.replace("Rate", "")
|
||||
category = category.strip()
|
||||
cost_value.Category = category
|
||||
else:
|
||||
elif cost_item["CostValues"]:
|
||||
cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"])
|
||||
cost_value.AppliedValue = self.file.createIfcMonetaryMeasure(cost_item["CostValues"])
|
||||
if self.is_schedule_of_rates:
|
||||
|
||||
@@ -557,6 +557,45 @@ bar.
|
||||
For more information, consult the :doc:`shape builder documentation
|
||||
<autoapi/ifcopenshell/util/shape_builder/index>`.
|
||||
|
||||
OpenCASCADE representations
|
||||
---------------------------
|
||||
|
||||
If you are familiar with OpenCASCADE's Python bindings, you may also use
|
||||
OpenCASCADE directly to create geometry. Since both OpenCASCADE and IFC are
|
||||
inspired by STEP, the majority of OpenCASCADE shapes will be able to be
|
||||
converted into IFC. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
from OCC.Core.gp import gp_Pnt
|
||||
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
|
||||
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut
|
||||
|
||||
outer = BRepPrimAPI_MakeBox(gp_Pnt(-5000., -180., -2000.), gp_Pnt(5000., 5180., 3000.)).Shape()
|
||||
inner = BRepPrimAPI_MakeBox(gp_Pnt(-4640., 180., 0.), gp_Pnt(4640., 4820., 3000.)).Shape()
|
||||
window1 = BRepPrimAPI_MakeBox(gp_Pnt(-5000., -180., 400.), gp_Pnt( 500., 1180., 2000.)).Shape()
|
||||
window2 = BRepPrimAPI_MakeBox(gp_Pnt( 2070., -180., 400.), gp_Pnt(3930., 180., 2000.)).Shape()
|
||||
building_shell = BRepAlgoAPI_Cut(
|
||||
BRepAlgoAPI_Cut(
|
||||
BRepAlgoAPI_Cut(outer, inner).Shape(),
|
||||
window1
|
||||
).Shape(),
|
||||
window2
|
||||
).Shape()
|
||||
|
||||
model = ifcopenshell.file(schema="IFC2X3")
|
||||
product_definition = ifcopenshell.geom.serialise("IFC2X3", building_shell, False)
|
||||
product_definition = model.add(product_definition)
|
||||
|
||||
.. warning::
|
||||
|
||||
The schema version is significant. IFC2X3 is very limited with regard to
|
||||
curved surfaces, so generally non-planar surfaces will fail to serialise in
|
||||
IFC2X3. Newer versions such as IFC4 can support more serialisations. If a
|
||||
serialisation fails, ``ifcopenshell.geom.serialise`` will return ``None``.
|
||||
|
||||
Manual representations
|
||||
----------------------
|
||||
|
||||
|
||||
@@ -12,6 +12,14 @@ The simplest way to process any geometry in a standardised fashion is to use the
|
||||
IfcOpenShell ``create_shape()`` function. This will provide a list of vertices,
|
||||
edges, and faces, or alternatively an OpenCASCADE BRep.
|
||||
|
||||
.. warning::
|
||||
|
||||
This section describes individual processing only. This is useful for
|
||||
learning how geometry processing works, but is not recommended for practical
|
||||
applications. See the `Geometry iterator`_ section below after reading this
|
||||
to see how to process geometry with multiple threads.
|
||||
|
||||
|
||||
Here is a simple example of processing a single wall into a list of vertices and
|
||||
faces. In this example, a ``shape`` variable is returned, which holds geometry
|
||||
related information in ``shape.geometry``:
|
||||
@@ -158,39 +166,34 @@ In these scenarios, a ``geometry`` is returned directly, equivalent to
|
||||
geometry = geom.create_shape(settings, ifc_file.by_type("IfcProfileDef")[0])
|
||||
|
||||
|
||||
Process individual element with multiple shape representations
|
||||
---------------------
|
||||
|
||||
When an element contains multiple shape representations with the same identifier or when you want more explicit control over which representation is processed (e.g `Body` or `Tesselation`), you can use the third parameter of ``create_shape()`` to nominate a specific shape representation to be processed in the context of a product.
|
||||
The element in your ifc file might look like this.
|
||||
When an element contains multiple shape representations with the same
|
||||
identifier or when you want more explicit control over which representation is
|
||||
processed (e.g ``Body`` or ``Tessellation``), you can use the third parameter of
|
||||
``create_shape()`` to nominate a specific shape representation to be processed
|
||||
in the context of a product. The element in your ifc file might look like
|
||||
this.
|
||||
|
||||
.. code-block:: ifc
|
||||
|
||||
#1618937=IFCSHAPEREPRESENTATION(#4,'Body','BRep',(#1617476));
|
||||
#1618938=IFCSHAPEREPRESENTATION(#4,'Body','BRep',(#1617583));
|
||||
#1618939=IFCSHAPEREPRESENTATION(#4,'Body','BRep',(#1617630));
|
||||
#1618957=IFCPRODUCTDEFINITIONSHAPE($,$,(#1618937,#1618938,#1618939));
|
||||
#1618958=IFCWINDOW('0Rrp2csNr07QrVCrEBJezu',#9,'test','test',$,#1618936,#1618957,'\X2\5EFA7B517A97\X0\',$,$,$,$,$);
|
||||
#1=IFCSHAPEREPRESENTATION(#4,'Body','BRep',(#1617476));
|
||||
#2=IFCSHAPEREPRESENTATION(#4,'Body','BRep',(#1617583));
|
||||
#3=IFCSHAPEREPRESENTATION(#4,'Body','BRep',(#1617630));
|
||||
#5=IFCPRODUCTDEFINITIONSHAPE($,$,(#1,#2,#3));
|
||||
#6=IFCWINDOW('0Rrp2csNr07QrVCrEBJezu',#9,'test','test',$,#7,#5,'test',$,$,$,$,$);
|
||||
|
||||
In order to get the geometry data (e.g. vertices) for this ``IFCWINDOW``, we can use the Python code below:
|
||||
In order to get the geometry data (e.g. vertices) for this ``IfcWindow``, we can use the Python code below:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
settings = ifcopenshell.geom.settings()
|
||||
ifc_file = ifcopenshell.open('window.ifc')
|
||||
window = ifc_file.by_type('IfcWindow')[0] # Get the IFCWINDOW that contains multiple Representations
|
||||
representations = window.Representation.Representations
|
||||
for rep in representations:
|
||||
shape = ifcopenshell.geom.create_shape(settings, window, rep) # The third parameter specifies which representation of the window is handled
|
||||
vertices = shape.geometry.verts
|
||||
print(vertices)
|
||||
for representation in representations:
|
||||
# ... code that filters which representation you want ...
|
||||
shape = ifcopenshell.geom.create_shape(settings, window, representation)
|
||||
|
||||
.. seealso::
|
||||
|
||||
.. note::
|
||||
|
||||
You may still need to determine which representation don't contain geometry data or some type like Box need to be discarded at render time.
|
||||
You may find the ``ifcopenshell.util.representation`` module useful to
|
||||
filter out specific representations.
|
||||
|
||||
|
||||
Geometry iterator
|
||||
@@ -210,11 +213,6 @@ By default, the geometry iterator processes all 3D geometry in a model from all
|
||||
elements, and returns a list of X Y Z vertex ordinates in a flattened list, as
|
||||
well as a flattened list of triangulated faces denoted by vertex indices.
|
||||
|
||||
There are a variety of configuration settings to get different output. For
|
||||
example, you may filter elements from processing, extract 2D data, or return
|
||||
non-triangulated OpenCASCADE BReps. For more information on the various
|
||||
settings, see :doc:`Geometry Settings<../ifcopenshell/geometry_settings>`.
|
||||
|
||||
Here is a simple example in Python:
|
||||
|
||||
.. code-block:: python
|
||||
@@ -241,28 +239,19 @@ Here is a simple example in Python:
|
||||
break
|
||||
|
||||
|
||||
The geometry iterator can process specific elements' geometry by using parameter ``include`` and ``exclude``, ``include`` and ``exclude`` cannot be specified simultaneously. Code below shows how to get specific wall's geometry (e.g. walls[1]):
|
||||
There are a variety of configuration settings to get different output. For
|
||||
example, you may filter elements from processing, extract 2D data, or return
|
||||
non-triangulated OpenCASCADE BReps. For more information on the various
|
||||
settings, see :doc:`Geometry Settings<../ifcopenshell/geometry_settings>`.
|
||||
|
||||
One of the more common settings used is the ``include`` setting, which
|
||||
specifies only to process certain geometry. For example, this iterator will
|
||||
only process wall elements.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import multiprocessing
|
||||
import ifcopenshell.util.shape
|
||||
|
||||
ifc = ifcopenshell.open('model.ifc')
|
||||
walls = ifc.by_type('ifcwall')
|
||||
settings = ifcopenshell.geom.settings()
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc, multiprocessing.cpu_count(), include=[walls[1]])
|
||||
valid_file = iterator.initialize()
|
||||
while True:
|
||||
shape = iterator.get()
|
||||
element = ifc.by_id(shape.id)
|
||||
geometry = shape.geometry
|
||||
verts = geometry.verts
|
||||
print(verts)
|
||||
if not iterator.next():
|
||||
break
|
||||
walls = ifc.by_type('IfcWall')
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc, multiprocessing.cpu_count(), include=walls)
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -297,3 +286,43 @@ specifically pinpoint the Radius parameter.
|
||||
|
||||
Given the advanced nature of manual processing, it is generally not recommended
|
||||
except in specific tasks.
|
||||
|
||||
Geometry serialisation
|
||||
----------------------
|
||||
|
||||
Geometry may be serialised into many different formats using
|
||||
:doc:`IfcConvert<../ifcconvert>`. Alternatively, you may also access the
|
||||
serialiser with Python to customise the conversion, such as by writing a script
|
||||
the modifies the IFC on the fly before converting it, or writing complex
|
||||
include and exclude filters.
|
||||
|
||||
Here is a typical example to serialising to glTF / glb.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import multiprocessing
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.STRICT_TOLERANCE, True)
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
# Setting element GUIDs is optional, but useful to uniquely identify objects in non-semantic formats.
|
||||
settings.set(settings.USE_ELEMENT_GUIDS, True)
|
||||
# Note that applying default materials is required in glTF serialisation.
|
||||
settings.set(settings.APPLY_DEFAULT_MATERIALS, True)
|
||||
|
||||
serialiser = ifcopenshell.geom.serializers.gltf("output.glb", settings)
|
||||
# Alternatively, this is an example for OBJ
|
||||
# serialiser = ifcopenshell.geom.serializers.obj('output.obj', 'output.mtl', settings)
|
||||
serialiser.setFile(self.file)
|
||||
serialiser.setUnitNameAndMagnitude("METER", 1.0)
|
||||
serialiser.writeHeader()
|
||||
|
||||
iterator = ifcopenshell.geom.iterator(settings, self.file, multiprocessing.cpu_count())
|
||||
if iterator.initialize():
|
||||
while True:
|
||||
serialiser.write(iterator.get())
|
||||
if not iterator.next():
|
||||
break
|
||||
serialiser.finalize()
|
||||
|
||||
@@ -27,21 +27,21 @@ the API.
|
||||
1. Choose which version to download based on your operating system, Python
|
||||
version, and computer architecture.
|
||||
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| | Linux 64bit | Windows 32bit | Windows 64bit | MacOS 64bit | MacOS M1 64bit |
|
||||
+=============+================+================+================+================+==================+
|
||||
| Python 3.6 | py36-linux64_ | py36-win32_ | py36-win64_ | py36-macos64_ | N/A |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.7 | py37-linux64_ | py37-win32_ | py37-win64_ | py37-macos64_ | py37-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.8 | py38-linux64_ | py38-win32_ | py38-win64_ | py38-macos64_ | py38-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.9 | py39-linux64_ | py39-win32_ | py39-win64_ | py39-macos64_ | py39-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.10 | py310-linux64_ | py310-win32_ | py310-win64_ | py310-macos64_ | py310-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.11 | py311-linux64_ | py311-win32_ | py311-win64_ | N/A | py311-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
+-------------+----------------+----------------+----------------+-------------------+---------------------+
|
||||
| | Linux 64bit | Windows 32bit | Windows 64bit | MacOS Intel 64bit | MacOS Silicon 64bit |
|
||||
+=============+================+================+================+===================+=====================+
|
||||
| Python 3.6 | py36-linux64_ | py36-win32_ | py36-win64_ | py36-macos64_ | N/A |
|
||||
+-------------+----------------+----------------+----------------+-------------------+---------------------+
|
||||
| Python 3.7 | py37-linux64_ | py37-win32_ | py37-win64_ | py37-macos64_ | py37-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+-------------------+---------------------+
|
||||
| Python 3.8 | py38-linux64_ | py38-win32_ | py38-win64_ | py38-macos64_ | py38-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+-------------------+---------------------+
|
||||
| Python 3.9 | py39-linux64_ | py39-win32_ | py39-win64_ | py39-macos64_ | py39-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+-------------------+---------------------+
|
||||
| Python 3.10 | py310-linux64_ | py310-win32_ | py310-win64_ | py310-macos64_ | py310-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+-------------------+---------------------+
|
||||
| Python 3.11 | py311-linux64_ | py311-win32_ | py311-win64_ | N/A | py311-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+-------------------+---------------------+
|
||||
|
||||
.. _py36-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-6c9e130-linux64.zip
|
||||
.. _py37-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-6c9e130-linux64.zip
|
||||
@@ -121,7 +121,8 @@ Conda
|
||||
|
||||
.. note::
|
||||
|
||||
Installing IfcOpenShell from Conda will also install IfcConvert.
|
||||
Installing IfcOpenShell from Conda will also install IfcConvert. Conda also
|
||||
supports Linux AArch64.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from datetime import datetime
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, name=None, predefined_type="NOTDEFINED", object_type=None):
|
||||
def __init__(self, file, name=None, predefined_type="NOTDEFINED"):
|
||||
"""Add a new cost schedule
|
||||
|
||||
A cost schedule is a group of cost items which typically represent a
|
||||
@@ -54,7 +54,7 @@ class Usecase:
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"name": name, "predefined_type": predefined_type, "object_type": object_type}
|
||||
self.settings = {"name": name, "predefined_type": predefined_type}
|
||||
|
||||
def execute(self):
|
||||
cost_schedule = ifcopenshell.api.run(
|
||||
@@ -65,6 +65,4 @@ class Usecase:
|
||||
name=self.settings["name"],
|
||||
)
|
||||
cost_schedule.UpdateDate = ifcopenshell.util.date.datetime2ifc(datetime.now(), "IfcDateTime")
|
||||
if self.settings["object_type"]:
|
||||
cost_schedule.ObjectType = self.settings["object_type"]
|
||||
return cost_schedule
|
||||
|
||||
@@ -52,7 +52,7 @@ class Usecase:
|
||||
if inverse.RelatingObject == self.settings["cost_item"]:
|
||||
for related_object in inverse.RelatedObjects:
|
||||
ifcopenshell.api.run("cost.remove_cost_item", self.file, cost_item=related_object)
|
||||
elif inverse.RelatedObjects == tuple(self.settings["cost_item"]):
|
||||
elif inverse.RelatedObjects == (self.settings["cost_item"],):
|
||||
history = inverse.OwnerHistory
|
||||
self.file.remove(inverse)
|
||||
if history:
|
||||
|
||||
@@ -58,7 +58,12 @@ class Usecase:
|
||||
if self.file.schema == "IFC2X3":
|
||||
curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points])
|
||||
else:
|
||||
curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList3D(points))
|
||||
dimensions = len(points[0])
|
||||
if dimensions == 2:
|
||||
ifc_points = self.file.createIfcCartesianPointList2D(points)
|
||||
elif dimensions == 3:
|
||||
ifc_points = self.file.createIfcCartesianPointList3D(points)
|
||||
curve = self.file.createIfcIndexedPolyCurve(ifc_points)
|
||||
return self.file.createIfcArbitraryClosedProfileDef("AREA", self.settings["name"], curve)
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
|
||||
@@ -74,7 +74,12 @@ class Usecase:
|
||||
outer_curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList3D(outer_points))
|
||||
inner_curves = []
|
||||
for inner_point in inner_points:
|
||||
inner_curves.append(self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList3D(inner_point)))
|
||||
dimensions = len(inner_point[0])
|
||||
if dimensions == 2:
|
||||
ifc_points = self.file.createIfcCartesianPointList2D(inner_point)
|
||||
elif dimensions == 3:
|
||||
ifc_points = self.file.createIfcCartesianPointList3D(inner_point)
|
||||
inner_curves.append(self.file.createIfcIndexedPolyCurve(ifc_points))
|
||||
return self.file.createIfcArbitraryProfileDefWithVoids("AREA", self.settings["name"], outer_curve, inner_curves)
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
|
||||
@@ -30,8 +30,6 @@ class Usecase:
|
||||
called NetSideArea with a area value of "4.2". Quantities are grouped
|
||||
into quantity sets, so that related quantities are grouped together.
|
||||
|
||||
Quantities can only be assigned to occurrences, not types.
|
||||
|
||||
Quantities are similar to, but different from properties in that they
|
||||
may store a method of measurement or formula. Quantities may also have
|
||||
parametric relationships to other calculated values, such as cost
|
||||
|
||||
@@ -70,7 +70,7 @@ class Usecase:
|
||||
elif self.settings["relating_product"]:
|
||||
for reference in self.settings["relating_product"].ReferencedBy:
|
||||
if reference.is_a("IfcRelAssignsToProduct"):
|
||||
related_object = referenced_by.RelatedObjects[0]
|
||||
related_object = reference.RelatedObjects[0]
|
||||
if related_object:
|
||||
assignments = self.settings["related_object"].HasAssignments
|
||||
for assignment in assignments:
|
||||
|
||||
@@ -139,7 +139,7 @@ class entity_instance(object):
|
||||
# ugh circular imports, name collisions
|
||||
from . import file
|
||||
|
||||
return file.file.from_pointer(self.wrapped_data.file_pointer())
|
||||
return file.from_pointer(self.wrapped_data.file_pointer())
|
||||
|
||||
def __getattr__(self, name):
|
||||
INVALID, FORWARD, INVERSE = range(3)
|
||||
|
||||
@@ -252,8 +252,11 @@ class file(object):
|
||||
self.future = []
|
||||
self.transaction = None
|
||||
|
||||
# Temporarily commented out until bot builds are available and tested to prevent user bugs.
|
||||
# file_dict[self.file_pointer()] = self
|
||||
import weakref
|
||||
file_dict[self.file_pointer()] = weakref.ref(self)
|
||||
|
||||
def __del__(self):
|
||||
del file_dict[self.file_pointer()]
|
||||
|
||||
def set_history_size(self, size):
|
||||
self.history_size = size
|
||||
@@ -575,4 +578,4 @@ class file(object):
|
||||
|
||||
@staticmethod
|
||||
def from_pointer(v):
|
||||
return file_dict.get(v)
|
||||
return file_dict.get(v)()
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"IfcPropertyBoundedValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertyEnumeratedValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertyListValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertyReferenceValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertyTableValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcPropertySingleValue": {
|
||||
"Specification": "Description"
|
||||
},
|
||||
"IfcComplexProperty": {
|
||||
"Specification": "Description"
|
||||
}
|
||||
}
|
||||
@@ -54,16 +54,16 @@ def reassign_class(ifc_file, element, new_class):
|
||||
Attempts to change the class (entity name) of `element` to `new_class` by
|
||||
removing element and recreating a similar instance of type `new_class`
|
||||
with the same id.
|
||||
|
||||
|
||||
In certain cases it may affect the structure of inversely related instances:
|
||||
- Multiple occurrences of reassigned instance within the same aggregate
|
||||
(such as start and end-point of polyline)
|
||||
- Occurrences of reassigned instance within an ordered aggregate
|
||||
(such as IfcRelNests)
|
||||
|
||||
|
||||
It's unlikely that this affects real-world usage of this function.
|
||||
"""
|
||||
|
||||
|
||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema)
|
||||
try:
|
||||
declaration = schema.declaration_by_name(new_class)
|
||||
@@ -132,7 +132,7 @@ class BatchReassignClass:
|
||||
for inverse, replacements in self.replacements.items():
|
||||
for index, element_map in replacements.items():
|
||||
value = inverse[index]
|
||||
new = inverse.walk(lambda x : True, lambda v: element_map.get(v, v), value)
|
||||
new = inverse.walk(lambda x: True, lambda v: element_map.get(v, v), value)
|
||||
if value != new:
|
||||
inverse[index] = new
|
||||
|
||||
@@ -151,8 +151,11 @@ class Migrator:
|
||||
self.class_4_to_2x3 = json.load(open(os.path.join(cwd, "class_4_to_2x3.json"), "r"))
|
||||
self.class_2x3_to_4 = json.load(open(os.path.join(cwd, "class_2x3_to_4.json"), "r"))
|
||||
|
||||
# IFC4 classes, and their IFC4 attribute : IFC2X3 attributes
|
||||
self.attribute_4_to_2x3 = json.load(open(os.path.join(cwd, "attribute_4_to_2x3.json"), "r"))
|
||||
# IFC classes, and their IFC attributes mapping
|
||||
self.attributes_mapping = {
|
||||
("IFC4", "IFC2X3"): json.load(open(os.path.join(cwd, "attribute_4_to_2x3.json"), "r")),
|
||||
("IFC4X3", "IFC4"): json.load(open(os.path.join(cwd, "attribute_4x3_to_4.json"), "r")),
|
||||
}
|
||||
|
||||
self.default_values = {
|
||||
"ChangeAction": "NOCHANGE",
|
||||
@@ -241,46 +244,72 @@ class Migrator:
|
||||
self.migrate_attribute(attribute, element, new_file, new_element, new_element_schema)
|
||||
return new_element
|
||||
|
||||
def migrate_attribute(self, attribute, element, new_file, new_element, new_element_schema):
|
||||
def find_equivalent_attribute(self, new_element, attribute, element, attributes_mapping, reverse_mapping=False):
|
||||
# print("Searching for an equivalent", element, new_element, attribute.name())
|
||||
try:
|
||||
if reverse_mapping:
|
||||
equivalent_map = attributes_mapping[new_element.is_a()]
|
||||
equivalent = list(equivalent_map.keys())[list(equivalent_map.values()).index(attribute.name())]
|
||||
else:
|
||||
equivalent = attributes_mapping[new_element.is_a()][attribute.name()]
|
||||
if hasattr(element, equivalent):
|
||||
# print("Equivalent found", equivalent)
|
||||
value = getattr(element, equivalent)
|
||||
else:
|
||||
return
|
||||
except Exception as e:
|
||||
print(
|
||||
"Unable to find equivalent attribute of {} to migrate from {} to {}".format(
|
||||
attribute.name(), element, new_element
|
||||
)
|
||||
)
|
||||
raise e
|
||||
|
||||
def migrate_attribute(
|
||||
self, attribute, element, new_file: ifcopenshell.file, new_element, new_element_schema
|
||||
):
|
||||
# NOTE: `attribute` is an attribute in new file schema
|
||||
# print("Migrating attribute", element, new_element, attribute.name())
|
||||
old_file = element.wrapped_data.file
|
||||
if hasattr(element, attribute.name()):
|
||||
value = getattr(element, attribute.name())
|
||||
# print("Attribute names matched", value)
|
||||
elif new_file.schema == "IFC2X3":
|
||||
|
||||
elif new_file.schema == "IFC2X3" and old_file.schema == "IFC4":
|
||||
# IFC4 to IFC2X3: We know the IFC2X3 attribute name, but not its IFC4 equivalent
|
||||
# print("Searching for an equivalent", new_element, attribute.name())
|
||||
try:
|
||||
equivalent_map = self.attribute_4_to_2x3[new_element.is_a()]
|
||||
equivalent = list(equivalent_map.keys())[list(equivalent_map.values()).index(attribute.name())]
|
||||
if hasattr(element, equivalent):
|
||||
# print("Equivalent found", equivalent)
|
||||
value = getattr(element, equivalent)
|
||||
else:
|
||||
return
|
||||
except:
|
||||
print(
|
||||
"Unable to find equivalent attribute of {} to migrate from {} to {}".format(
|
||||
attribute.name(), element, new_element
|
||||
)
|
||||
value = self.find_equivalent_attribute(
|
||||
new_element, attribute, element, self.attributes_mapping[("IFC4", "IFC2X3")], reverse_mapping=True
|
||||
)
|
||||
return # We tried our best
|
||||
elif new_file.schema == "IFC4":
|
||||
except: # We tried our best
|
||||
return
|
||||
|
||||
elif new_file.schema == "IFC4" and old_file.schema == "IFC2X3":
|
||||
# IFC2X3 to IFC4: We know the IFC4 attribute name, but not its IFC2X3 equivalent
|
||||
# print("Searching for an equivalent", element, new_element, attribute.name())
|
||||
try:
|
||||
equivalent = self.attribute_4_to_2x3[new_element.is_a()][attribute.name()]
|
||||
# print("Searching for equivalent", equivalent)
|
||||
if hasattr(element, equivalent):
|
||||
value = getattr(element, equivalent)
|
||||
else:
|
||||
return
|
||||
except:
|
||||
print(
|
||||
"Unable to find equivalent attribute of {} to migrate from {} to {}".format(
|
||||
attribute.name(), element, new_element
|
||||
)
|
||||
value = self.find_equivalent_attribute(
|
||||
new_element, attribute, element, self.attributes_mapping[("IFC4", "IFC2X3")]
|
||||
)
|
||||
return # We tried our best
|
||||
except: # We tried our best
|
||||
return
|
||||
|
||||
elif new_file.schema == "IFC4X3" and old_file.schema == "IFC4":
|
||||
try:
|
||||
value = self.find_equivalent_attribute(
|
||||
new_element, attribute, element, self.attributes_mapping[("IFC4X3", "IFC4")]
|
||||
)
|
||||
except: # We tried our best
|
||||
return
|
||||
|
||||
try:
|
||||
value
|
||||
except UnboundLocalError:
|
||||
print(
|
||||
f"Couldn't match attribute {attribute.name()} by name to migrate from {element} "
|
||||
f"to {new_element} and there is no special mapping to handle migration "
|
||||
f"from {old_file.schema} -> {new_file.schema}"
|
||||
)
|
||||
return
|
||||
|
||||
# print("Continuing migration of {} to migrate from {} to {}".format(attribute.name(), element, new_element))
|
||||
if value is None and not attribute.optional():
|
||||
|
||||
@@ -55,19 +55,20 @@ filter_elements_grammar = lark.Lark(
|
||||
ifc_class: /Ifc\\w+/
|
||||
|
||||
value: special | quoted_string | regex_string | unquoted_string
|
||||
unquoted_string: /[^,.=\\s]+/
|
||||
unquoted_string: /[^,.=><*!\\s]+/
|
||||
regex_string: "/" /[^\\/]+/ "/"
|
||||
quoted_string: ESCAPED_STRING
|
||||
|
||||
special: null | true | false
|
||||
|
||||
comparison: not? equals | morethanequalto | lessthanequalto | morethan | lessthan
|
||||
comparison: not? equals | morethanequalto | lessthanequalto | morethan | lessthan | not? contains
|
||||
not: "!"
|
||||
equals: "="
|
||||
morethanequalto: ">="
|
||||
lessthanequalto: "<="
|
||||
morethan: ">"
|
||||
lessthan: "<"
|
||||
contains: "*="
|
||||
null: "NULL"
|
||||
true: "TRUE"
|
||||
false: "FALSE"
|
||||
@@ -557,18 +558,24 @@ class FacetTransformer(lark.Transformer):
|
||||
return tree
|
||||
|
||||
def comparison(self, args):
|
||||
if args[0].data == "equals":
|
||||
return "="
|
||||
elif args[0].data == "morethanequalto":
|
||||
return ">="
|
||||
elif args[0].data == "lessthanequalto":
|
||||
return "<="
|
||||
elif args[0].data == "morethan":
|
||||
return ">"
|
||||
elif args[0].data == "lessthan":
|
||||
return "<"
|
||||
if args[0].data == "not":
|
||||
comparison = args[1].data
|
||||
is_not = "!"
|
||||
else:
|
||||
return "!="
|
||||
comparison = args[0].data
|
||||
is_not = ""
|
||||
|
||||
return (
|
||||
is_not
|
||||
+ {
|
||||
"equals": "=",
|
||||
"morethanequalto": ">=",
|
||||
"lessthanequalto": "<=",
|
||||
"morethan": ">",
|
||||
"lessthan": "<",
|
||||
"contains": "*=",
|
||||
}[comparison]
|
||||
)
|
||||
|
||||
def keys(self, args):
|
||||
return self.value(args)
|
||||
@@ -595,35 +602,42 @@ class FacetTransformer(lark.Transformer):
|
||||
return False
|
||||
|
||||
def compare(self, element_value, comparison, value):
|
||||
if element_value:
|
||||
if isinstance(element_value, (list, tuple)):
|
||||
return any(self.compare(ev, comparison, value) for ev in element_value)
|
||||
elif isinstance(value, re.Pattern):
|
||||
result = bool(value.match(element_value)) if element_value is not None else False
|
||||
elif isinstance(value, str):
|
||||
if isinstance(element_value, int):
|
||||
value = int(value)
|
||||
elif isinstance(element_value, float):
|
||||
value = float(value)
|
||||
|
||||
if comparison == "=":
|
||||
result = element_value == value
|
||||
elif comparison == ">":
|
||||
result = element_value > value
|
||||
elif comparison == "<":
|
||||
result = element_value < value
|
||||
elif comparison == ">=":
|
||||
result = element_value >= value
|
||||
elif comparison == "<= ":
|
||||
result = element_value <= value
|
||||
else:
|
||||
result = element_value != value
|
||||
if isinstance(element_value, (list, tuple)):
|
||||
return any(self.compare(ev, comparison, value) for ev in element_value)
|
||||
elif isinstance(value, str):
|
||||
if isinstance(element_value, int):
|
||||
value = int(value)
|
||||
elif isinstance(element_value, float):
|
||||
value = float(value)
|
||||
|
||||
elif value in (None, True, False):
|
||||
result = element_value is value
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
if isinstance(element_value, (int, float)):
|
||||
operator = comparison.lstrip("!")
|
||||
if operator == ">=":
|
||||
result = element_value >= value
|
||||
elif operator == "<=":
|
||||
result = element_value <= value
|
||||
elif operator == ">":
|
||||
result = element_value > value
|
||||
elif operator == "<":
|
||||
result = element_value < value
|
||||
else:
|
||||
result = element_value == value # Tolerance?
|
||||
elif isinstance(element_value, str):
|
||||
operator = comparison.lstrip("!")
|
||||
if operator == "*=":
|
||||
result = value in element_value
|
||||
else:
|
||||
result = element_value == value
|
||||
else:
|
||||
result = element_value == value
|
||||
elif isinstance(value, re.Pattern):
|
||||
result = bool(value.match(element_value)) if element_value is not None else False
|
||||
elif value in (None, True, False):
|
||||
result = element_value is value
|
||||
|
||||
if comparison.startswith("!"):
|
||||
return not result
|
||||
return result
|
||||
|
||||
|
||||
class Selector:
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import datetime
|
||||
import ifcopenshell.util.date
|
||||
from math import floor
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
@@ -221,7 +222,7 @@ def is_work_time_applicable_to_day(work_time, day):
|
||||
return False # TODO
|
||||
elif recurrence.RecurrenceType == "MONTHLY_BY_POSITION":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return (day.weekday() + 1) in recurrence.WeekdayComponent and math.floor(
|
||||
return (day.weekday() + 1) in recurrence.WeekdayComponent and floor(
|
||||
day.day / 7
|
||||
) + 1 == recurrence["Position"]
|
||||
return False # TODO
|
||||
@@ -237,7 +238,7 @@ def is_work_time_applicable_to_day(work_time, day):
|
||||
return (
|
||||
day.month in recurrence.MonthComponent
|
||||
and (day.weekday() + 1) in recurrence.WeekdayComponent
|
||||
and math.floor(day.day / 7) + 1 == recurrence.Position
|
||||
and floor(day.day / 7) + 1 == recurrence.Position
|
||||
)
|
||||
return False # TODO
|
||||
|
||||
|
||||
@@ -217,7 +217,8 @@ def get_edges(geometry):
|
||||
def get_faces(geometry):
|
||||
"""Get all the faces as a numpy array
|
||||
|
||||
Faces are always triangulated.
|
||||
Faces are always triangulated. If the shape is a BRep and you want to get
|
||||
the original untriangulated output, refer to ``get_edges``.
|
||||
|
||||
Results are a nested numpy array e.g. [[f1v1, f1v2, f1v3], [f2v1, f2v2, f2v3], ...]
|
||||
|
||||
@@ -247,7 +248,7 @@ def get_shape_vertices(shape, geometry):
|
||||
"""
|
||||
verts = get_vertices(geometry)
|
||||
mat = get_shape_matrix(shape)
|
||||
return np.array([mat @ np.array([verts[i], verts[i + 1], verts[i + 2]]) for i in range(0, len(verts), 3)])
|
||||
return np.delete((mat @ np.hstack((verts, np.ones((len(verts), 1)))).T).T, -1, axis=1)
|
||||
|
||||
|
||||
def get_element_vertices(element, geometry):
|
||||
@@ -269,7 +270,7 @@ def get_element_vertices(element, geometry):
|
||||
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
|
||||
return verts
|
||||
mat = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
return np.array([mat @ np.array([verts[i], verts[i + 1], verts[i + 2]]) for i in range(0, len(verts), 3)])
|
||||
return np.delete((mat @ np.hstack((verts, np.ones((len(verts), 1)))).T).T, -1, axis=1)
|
||||
|
||||
|
||||
def get_bottom_elevation(geometry):
|
||||
|
||||
@@ -15,7 +15,7 @@ classifiers = [
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
|
||||
]
|
||||
[project.optional-dependencies]
|
||||
geometry = ["mathutils"]
|
||||
geometry = ["mathutils", "shapely"]
|
||||
date = ["isodate"]
|
||||
[project.urls]
|
||||
"Homepage" = "http://ifcopenshell.org"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
@@ -0,0 +1,17 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
@@ -0,0 +1,36 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class TestAddCostItem(test.bootstrap.IFC4):
|
||||
def test_add_a_cost_item(self):
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="Foo")
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=schedule)
|
||||
assert item1.is_a("IfcCostItem")
|
||||
assert item1.HasAssignments[0].is_a("IfcRelAssignsToControl")
|
||||
assert item1.HasAssignments[0].RelatingControl == schedule
|
||||
|
||||
def test_add_a_sub_cost_item(self):
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="Foo")
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=schedule)
|
||||
item2 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=item1)
|
||||
assert item2.is_a("IfcCostItem")
|
||||
assert item2.Nests[0].RelatingObject == item1
|
||||
@@ -0,0 +1,35 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class TestAddCostSchedule(test.bootstrap.IFC4):
|
||||
def test_add_a_cost_schedule(self):
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="Foo", predefined_type="BUDGET")
|
||||
assert schedule.is_a("IfcCostSchedule")
|
||||
assert schedule.Name == "Foo"
|
||||
assert schedule.PredefinedType == "BUDGET"
|
||||
|
||||
def test_adding_a_userdefined_type(self):
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="Foo", predefined_type="FOO")
|
||||
assert schedule.is_a("IfcCostSchedule")
|
||||
assert schedule.Name == "Foo"
|
||||
assert schedule.PredefinedType == "USERDEFINED"
|
||||
assert schedule.ObjectType == "FOO"
|
||||
@@ -0,0 +1,47 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class TestRemoveCostItem(test.bootstrap.IFC4):
|
||||
def test_remove_a_cost_item(self):
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="Foo", predefined_type="BUDGET")
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=schedule)
|
||||
ifcopenshell.api.run("cost.remove_cost_item", self.file, cost_item=item1)
|
||||
assert not self.file.by_type("IfcCostItem")
|
||||
assert not self.file.by_type("IfcRelAssignsToControl")
|
||||
|
||||
def test_remove_a_sub_cost_item(self):
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="Foo", predefined_type="BUDGET")
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=schedule)
|
||||
item2 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=item1)
|
||||
ifcopenshell.api.run("cost.remove_cost_item", self.file, cost_item=item2)
|
||||
assert self.file.by_type("IfcCostItem") == [item1]
|
||||
assert self.file.by_type("IfcRelAssignsToControl")
|
||||
assert not self.file.by_type("IfcRelNests")
|
||||
|
||||
def test_remove_a_parent_cost_item(self):
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="Foo", predefined_type="BUDGET")
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=schedule)
|
||||
item2 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=item1)
|
||||
ifcopenshell.api.run("cost.remove_cost_item", self.file, cost_item=item1)
|
||||
assert not self.file.by_type("IfcCostItem")
|
||||
assert not self.file.by_type("IfcRelAssignsToControl")
|
||||
assert not self.file.by_type("IfcRelNests")
|
||||
@@ -0,0 +1,37 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class TestRemoveCostSchedule(test.bootstrap.IFC4):
|
||||
def test_remove_a_cost_schedule(self):
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="Foo", predefined_type="BUDGET")
|
||||
ifcopenshell.api.run("cost.remove_cost_schedule", self.file, cost_schedule=schedule)
|
||||
assert not self.file.by_type("IfcCostSchedule")
|
||||
|
||||
def test_remove_a_schedule_with_items(self):
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="Foo", predefined_type="BUDGET")
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=schedule)
|
||||
item2 = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=item1)
|
||||
ifcopenshell.api.run("cost.remove_cost_schedule", self.file, cost_schedule=schedule)
|
||||
assert not self.file.by_type("IfcCostSchedule")
|
||||
assert not self.file.by_type("IfcCostItem")
|
||||
assert not self.file.by_type("IfcRelNests")
|
||||
assert not self.file.by_type("IfcRelAssignsToControl")
|
||||
@@ -46,6 +46,17 @@ class TestPsetQto:
|
||||
names = self.pset_qto.get_applicable_names("IfcWall")
|
||||
assert "Pset_WallCommon" in names
|
||||
names = self.pset_qto.get_applicable_names("IfcWallType")
|
||||
assert len(names) == 6
|
||||
assert len(names) == 12
|
||||
assert "Pset_WallCommon" in names
|
||||
assert "Qto_WallBaseQuantities" in names # Backported fix for IFC4
|
||||
|
||||
def test_getting_applicable_names_by_predefined_type(self):
|
||||
names = self.pset_qto.get_applicable_names("IfcFurniture")
|
||||
assert "Pset_FurnitureTypeTable" not in names
|
||||
names = self.pset_qto.get_applicable_names("IfcFurniture", "TABLE")
|
||||
assert "Pset_FurnitureTypeTable" in names
|
||||
names = self.pset_qto.get_applicable_names("IfcFurnitureType", "TABLE")
|
||||
assert "Pset_FurnitureTypeTable" in names
|
||||
names = self.pset_qto.get_applicable_names("IfcFurnitureType" )
|
||||
names2 = self.pset_qto.get_applicable_names("IfcFurnitureType", "CUSTOM")
|
||||
assert names == names2
|
||||
|
||||
@@ -192,6 +192,20 @@ class TestFilterElements(test.bootstrap.IFC4):
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Status": ["New"]})
|
||||
assert subject.filter_elements(self.file, "IfcWall, Pset_WallCommon.Status=New") == {element}
|
||||
|
||||
def test_selecting_by_property_with_comparisons(self):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foobar")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Baz": 123})
|
||||
assert subject.filter_elements(self.file, "IfcWall, Foobar.Baz>100") == {element}
|
||||
assert subject.filter_elements(self.file, "IfcWall, Foobar.Baz<100") == set()
|
||||
assert subject.filter_elements(self.file, "IfcWall, Foobar.Baz>=100") == {element}
|
||||
assert subject.filter_elements(self.file, "IfcWall, Foobar.Baz<=100") == set()
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"})
|
||||
assert subject.filter_elements(self.file, "IfcWall, Foobar.Foo*=ar") == {element}
|
||||
assert subject.filter_elements(self.file, "IfcWall, Foobar.Foo!*=ar") == {element2}
|
||||
assert subject.filter_elements(self.file, "IfcWall, Foobar.Foo*=Foo") == set()
|
||||
|
||||
def test_selecting_by_classification(self):
|
||||
project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
|
||||