mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-22 13:05:59 +00:00
Compare commits
206 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f365cadbd | |||
| 0bc76b1af7 | |||
| efce28c053 | |||
| 071505038d | |||
| 98938acb26 | |||
| 4817360ecd | |||
| b8e3fbba8c | |||
| 49ea774851 | |||
| f4f2a3d24e | |||
| a25ca91ba6 | |||
| fded9b3523 | |||
| 9257f275af | |||
| 40039e65df | |||
| dbce9ec049 | |||
| b9c5c28c54 | |||
| 184a189f2a | |||
| 37f549927d | |||
| a5fe3e77e7 | |||
| 708da97bbd | |||
| f80792091f | |||
| a1e34b0acb | |||
| e618232e70 | |||
| 922fc2beda | |||
| 4564ee3476 | |||
| 510dca1aca | |||
| 5604db8072 | |||
| 5961253c29 | |||
| 624e296d24 | |||
| 6ad605fbeb | |||
| 797a5ec1a0 | |||
| d56869d341 | |||
| 405e8bc5aa | |||
| b607242339 | |||
| f57aa77ac2 | |||
| 01cc1887e0 | |||
| 003441a451 | |||
| 4b64f09aaa | |||
| 4afcca8550 | |||
| 1a7ea1ea05 | |||
| 5616a6ede5 | |||
| 28fa774a9a | |||
| 52d4fa4781 | |||
| 9258dc3104 | |||
| 469fb9c204 | |||
| f7175a67c8 | |||
| ebfaeafeee | |||
| 1f68c3f612 | |||
| aceb2140ad | |||
| 310d13ac75 | |||
| bc2ac6b31a | |||
| 059c405652 | |||
| 28d168b96c | |||
| e4640df6f0 | |||
| 5c3642e1d1 | |||
| 615f567ebf | |||
| a56ca661e8 | |||
| 8ee9835e27 | |||
| b26147c1c7 | |||
| c3847ff38a | |||
| 8299505672 | |||
| 0db743d3f7 | |||
| e7558d0008 | |||
| c9f40126c3 | |||
| b0554e71d5 | |||
| 1bed25408f | |||
| 603a034ac6 | |||
| 6f802ed770 | |||
| c1b9062fa4 | |||
| e4f591babf | |||
| e9c84d1c11 | |||
| e62b7e94a4 | |||
| e89d540236 | |||
| fa23f64e9f | |||
| cfaa90b1fb | |||
| dc909f1fc6 | |||
| 30fc2ad580 | |||
| c542fb2480 | |||
| 25717d002f | |||
| 0181bd7e56 | |||
| 71c6bba36d | |||
| eb539666d9 | |||
| e43eec6203 | |||
| 4ca64ac42e | |||
| f51e0db7fe | |||
| b1e8e0a968 | |||
| be66a98cfa | |||
| 76487ea3bf | |||
| 13fe011c75 | |||
| 266a341c6b | |||
| a5bcbe046f | |||
| ab057e3eff | |||
| 6aa0651190 | |||
| 0f296c486f | |||
| 29f64f7c11 | |||
| d2d4a1b976 | |||
| 249ff95920 | |||
| 4c8d12d978 | |||
| 04d02feb86 | |||
| e6700ea29b | |||
| 9c7c3d7078 | |||
| 0d3175c6ef | |||
| 04abadac22 | |||
| 0b39226869 | |||
| 9e31991e75 | |||
| b40bd681b1 | |||
| 63384a730b | |||
| b9f81b8014 | |||
| c34f357301 | |||
| beb58b89fe | |||
| 97df037c93 | |||
| a7d8bffca5 | |||
| b5f58dc113 | |||
| 4e889b75d7 | |||
| 36cfb0d3f5 | |||
| 5921bcb63d | |||
| 23ad1a8005 | |||
| f257aef8f4 | |||
| 3eacd809b0 | |||
| e1b90ed3b2 | |||
| 28c2d7248e | |||
| 3bee746f49 | |||
| eeb27bb1c7 | |||
| d50c89abee | |||
| 15cf3f5afd | |||
| 1e22dbca1e | |||
| 28fbf0ea99 | |||
| d874a0089d | |||
| 441d842f67 | |||
| 667ac307c3 | |||
| c81031819c | |||
| 7cbb87284c | |||
| d0a9edd186 | |||
| 425379aaad | |||
| 25d86a50c5 | |||
| 0286cd370d | |||
| 0178804feb | |||
| 1892e9cc86 | |||
| 1b46f5c83a | |||
| 3a0edab8c1 | |||
| 7032ee9f11 | |||
| fd19b2d730 | |||
| a65b9f2ba7 | |||
| 2db4a6f89f | |||
| 517b819504 | |||
| a0f79cc0db | |||
| 67666ed70e | |||
| 6bcfa5946f | |||
| 52691b7ee5 | |||
| 780ea707b0 | |||
| 7e7cfbdd5f | |||
| ab102176cc | |||
| d7f289bc8a | |||
| 95a34cb8d0 | |||
| 8369818b88 | |||
| 5251bdabc6 | |||
| 83c33dd5b0 | |||
| bfc695c756 | |||
| 459faec01a | |||
| 4fb65199b8 | |||
| 08576e28d6 | |||
| b3f581d78d | |||
| 89d6b3d21a | |||
| d2bf2cf46e | |||
| 43b2982dbb | |||
| e750a134d0 | |||
| 6bbbc528fe | |||
| 7f4a436708 | |||
| e6592ca706 | |||
| cc2138463e | |||
| 6de3e55d19 | |||
| 595131e6e8 | |||
| 6966da3bdf | |||
| 4e897a0c21 | |||
| 73a9015801 | |||
| 1b2bebecb0 | |||
| e4dee2258d | |||
| 7de3eec1b1 | |||
| 20fe48bd6a | |||
| 1d6b2dcb37 | |||
| 45f3e677df | |||
| 65160f9bb4 | |||
| 333ba08841 | |||
| 2cb3b0817f | |||
| 4ea95960a2 | |||
| 10f4bca5d2 | |||
| d4a01618cd | |||
| c9c6529978 | |||
| 2829a0764b | |||
| 76d61cb4d4 | |||
| e9d2a39166 | |||
| d08f4171e3 | |||
| 23483eafb3 | |||
| 686ba2536b | |||
| 7d5ac99f34 | |||
| 9b8d224aca | |||
| 8939d93333 | |||
| 04aad216ba | |||
| 27eb9d340a | |||
| fbb53f93d8 | |||
| af6aed80da | |||
| 92ba412c95 | |||
| 110c382973 | |||
| 977df60f05 | |||
| 539d77b5ad | |||
| ea34c3de85 | |||
| 9374594920 |
@@ -0,0 +1,66 @@
|
||||
name: Publish-blenderbim-multiplatform
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'src/ifcblenderexport/**'
|
||||
- '.github/workflows/ci-blenderbim-matrix.yml'
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: blenderbim
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py37, py39]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
short_name: win,
|
||||
}
|
||||
- {
|
||||
name: "Linux Build",
|
||||
short_name: linux
|
||||
}
|
||||
- {
|
||||
name: "MacOS Build",
|
||||
short_name: macos
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.7.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: echo ${{ env.DATE }}
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%y%m%d')"
|
||||
- name: Compile
|
||||
run: |
|
||||
cp -r src/ifcblenderexport src/ifcblenderexport_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
|
||||
cd src/ifcblenderexport_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
|
||||
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
|
||||
- name: Upload Zip file to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: src/ifcblenderexport_${{ matrix.config.short_name }}_${{ matrix.pyver }}/dist/blender28-bim-${{steps.date.outputs.date}}-${{ matrix.pyver }}-${{ matrix.config.short_name }}.zip
|
||||
asset_name: blender28-bim-${{steps.date.outputs.date}}-${{ matrix.pyver }}-${{ matrix.config.short_name }}.zip
|
||||
tag: "blenderbim-${{steps.date.outputs.date}}"
|
||||
overwrite: true
|
||||
body: "Package release blenderbim-${{steps.date.outputs.date}}"
|
||||
@@ -0,0 +1,107 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell' &&
|
||||
!startsWith(github.event.head_commit.message, 'Release ') &&
|
||||
!contains(github.event.head_commit.message, 'ci skip')
|
||||
steps:
|
||||
- run: echo ok go
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: activate
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install --no-install-recommends \
|
||||
git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev \
|
||||
liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
|
||||
|
||||
-
|
||||
name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
|
||||
-
|
||||
name: Build ifcopenshell
|
||||
run: |
|
||||
mkdir build && cd build
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
-DBUILD_PACKAGE=On \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/oce \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
|
||||
-DLIBXML2_LIBRARIES=/usr/lib/x86_64-linux-gnu/libxml2.so \
|
||||
../cmake
|
||||
make -j $(nproc)
|
||||
make install
|
||||
-
|
||||
name: Package
|
||||
run: |
|
||||
make package
|
||||
working-directory: build
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
# Directory containing files to upload
|
||||
path: build/assets/Ifc*
|
||||
|
||||
deliver:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: build
|
||||
name: Docker Build, Tag, Push
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Download
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
path: artifacts/
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to Dockerhub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: aecgeeks
|
||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
-
|
||||
name: Build container image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: artifacts
|
||||
repository: aecgeeks/ifcopenshell
|
||||
tags: aecgeeks/ifcopenshell:latest
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
@@ -0,0 +1,96 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9].[0-9].[0-9]*'
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
if: github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo ok go
|
||||
build:
|
||||
needs: activate
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
-
|
||||
name: Update ubuntu
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
|
||||
sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mkdir build && cd build
|
||||
cmake \
|
||||
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
-DBUILD_PACKAGE=On \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/oce \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
|
||||
-DLIBXML2_LIBRARIES=/usr/lib/x86_64-linux-gnu/libxml2.so \
|
||||
../cmake
|
||||
make -j $(nproc)
|
||||
make install
|
||||
- name: Package
|
||||
id: package
|
||||
run: |
|
||||
make package
|
||||
echo ::set-output name=tgz::$( ls assets/*.tar.gz | head -n 1 | xargs basename )
|
||||
echo ::set-output name=deb::$( ls assets/*.deb | head -n 1 | xargs basename )
|
||||
working-directory: build
|
||||
env:
|
||||
CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Release
|
||||
id: release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
body: |
|
||||
Changes to this release
|
||||
- add github actions support #1306
|
||||
- add boost depends to ifcopenshell deb packaging
|
||||
- <add more changes here>
|
||||
See more details in the documentation
|
||||
# body_path: build/CHANGELOG.md
|
||||
- name: Upload TGZ
|
||||
id: upload-tgz
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: build/assets/${{ steps.package.outputs.tgz }}
|
||||
asset_name: ${{ steps.package.outputs.tgz }}
|
||||
asset_content_type: application/x-gzip
|
||||
- name: Upload DEB
|
||||
id: upload-deb
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: build/assets/${{ steps.package.outputs.deb }}
|
||||
asset_name: ${{ steps.package.outputs.deb }}
|
||||
asset_content_type: application/vnd.debian.binary-package
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# -*- mode: Dockerfile -*-
|
||||
|
||||
FROM ubuntu:focal
|
||||
|
||||
ARG CHANNEL
|
||||
ENV CHANNEL=${CHANNEL:-latest}
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
vim \
|
||||
ssh \
|
||||
sudo \
|
||||
wget \
|
||||
software-properties-common ;\
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN useradd --user-group --create-home --shell /bin/bash IfcOpenShell ;\
|
||||
echo "IfcOpenShell ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu focal-proposed main restricted" | tee -a /etc/apt/sources.list; \
|
||||
echo "deb http://archive.ubuntu.com/ubuntu focal-proposed universe" | tee -a /etc/apt/sources.list; \
|
||||
echo "deb http://archive.ubuntu.com/ubuntu focal-proposed multiverse" | tee -a /etc/apt/sources.list; \
|
||||
apt-get -qq update; \
|
||||
apt-get -y install tzdata dos2unix rsync; \
|
||||
apt-get -y install python3 libxml2 liboce-foundation11 liboce-modeling11 liboce-ocaf11 liboce-visualization11 \
|
||||
liboce-ocaf-lite11 libpython3.8 libboost-system1.67.0 libboost-program-options1.67.0 \
|
||||
libboost-regex1.67.0 libboost-thread1.67.0 libboost-date-time1.67.0; \
|
||||
rm -rf /var/lib/apt/lists/* ;
|
||||
|
||||
COPY . /home/IfcOpenShell/
|
||||
RUN ls -lrtR /home/IfcOpenShell
|
||||
|
||||
RUN dpkg -i /home/IfcOpenShell/*.deb
|
||||
|
||||
USER IfcOpenShell
|
||||
|
||||
+90
-33
@@ -21,7 +21,10 @@ cmake_minimum_required(VERSION 3.1.3)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
|
||||
project (IfcOpenShell)
|
||||
project (IfcOpenShell VERSION 0.6.0)
|
||||
|
||||
# use extra version to make pre-release using eg semver
|
||||
set( EXTRA_VERSION "-alpha.3")
|
||||
|
||||
foreach(max_year RANGE 2014 2030)
|
||||
set(max_sdk "$ENV{ADSK_3DSMAX_SDK_${max_year}}")
|
||||
@@ -35,6 +38,7 @@ OPTION(COLLADA_SUPPORT "Build IfcConvert with COLLADA support (requires OpenCOLL
|
||||
OPTION(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
|
||||
OPTION(ENABLE_BUILD_OPTIMIZATIONS "Enable certain compiler and linker optimizations on RelWithDebInfo and Release builds." OFF)
|
||||
OPTION(IFCCONVERT_DOUBLE_PRECISION "IfcConvert: Use double precision floating-point numbers." ON)
|
||||
OPTION(BUILD_PACKAGE "" OFF)
|
||||
OPTION(BUILD_IFCGEOM "Build IfcGeom." ON)
|
||||
OPTION(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF)
|
||||
OPTION(BUILD_IFCPYTHON "Build IfcPython." ON)
|
||||
@@ -54,7 +58,7 @@ OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only ins
|
||||
|
||||
# TODO QtViewer is deprecated ATM as it uses the 0.4 API
|
||||
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND (NOT BUILD_IFCGEOM))
|
||||
message(STATUS "'IfcGeom' is required with current outputs")
|
||||
set(BUILD_IFCGEOM ON)
|
||||
@@ -65,7 +69,7 @@ IF(NOT BINDIR)
|
||||
set(BINDIR bin)
|
||||
ENDIF()
|
||||
IF(NOT IS_ABSOLUTE ${BINDIR})
|
||||
set(BINDIR ${CMAKE_INSTALL_PREFIX}/${BINDIR})
|
||||
set(BINDIR ${CMAKE_INSTALL_BINDIR})
|
||||
ENDIF()
|
||||
MESSAGE(STATUS "BINDIR: ${BINDIR}")
|
||||
|
||||
@@ -73,7 +77,7 @@ IF(NOT INCLUDEDIR)
|
||||
set(INCLUDEDIR include)
|
||||
ENDIF()
|
||||
IF(NOT IS_ABSOLUTE ${INCLUDEDIR})
|
||||
set(INCLUDEDIR ${CMAKE_INSTALL_PREFIX}/${INCLUDEDIR})
|
||||
set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
ENDIF()
|
||||
MESSAGE(STATUS "INCLUDEDIR: ${INCLUDEDIR}")
|
||||
|
||||
@@ -81,11 +85,11 @@ IF(NOT LIBDIR)
|
||||
set(LIBDIR lib)
|
||||
ENDIF()
|
||||
IF(NOT IS_ABSOLUTE ${LIBDIR})
|
||||
set(LIBDIR ${CMAKE_INSTALL_PREFIX}/${LIBDIR})
|
||||
set(LIBDIR ${CMAKE_INSTALL_LIBDIR})
|
||||
ENDIF()
|
||||
MESSAGE(STATUS "LIBDIR: ${LIBDIR}")
|
||||
|
||||
set(IFCOPENSHELL_LIBARY_DIR "") # for *nix rpaths
|
||||
set(IFCOPENSHELL_LIBRARY_DIR "") # for *nix rpaths
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
add_definitions(-DIFC_SHARED_BUILD)
|
||||
@@ -95,7 +99,7 @@ if (BUILD_SHARED_LIBS)
|
||||
# There will be couple hundreds of these so suppress them away, https://msdn.microsoft.com/en-us/library/esew7y1w.aspx
|
||||
add_definitions(-wd4251)
|
||||
endif()
|
||||
set(IFCOPENSHELL_LIBARY_DIR "${LIBDIR}")
|
||||
set(IFCOPENSHELL_LIBRARY_DIR "${LIBDIR}")
|
||||
endif()
|
||||
|
||||
# Create cache entries if absent for environment variables
|
||||
@@ -175,6 +179,7 @@ if(USE_MMAP)
|
||||
endif()
|
||||
|
||||
if (IFCXML_SUPPORT)
|
||||
find_package(LibXml2 REQUIRED)
|
||||
add_definitions(-DWITH_IFCXML)
|
||||
endif()
|
||||
|
||||
@@ -219,45 +224,57 @@ endfunction()
|
||||
if(BUILD_IFCGEOM)
|
||||
|
||||
IF(MSVC)
|
||||
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
|
||||
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
|
||||
ENDIF()
|
||||
|
||||
# Find Open CASCADE
|
||||
# Open CASCADE
|
||||
IF("${OCC_INCLUDE_DIR}" STREQUAL "")
|
||||
SET(OCC_INCLUDE_DIR "/usr/include/oce/" CACHE FILEPATH "Open CASCADE header files")
|
||||
MESSAGE(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
MESSAGE(STATUS "Use OCC_INCLUDE_DIR to specify another directory")
|
||||
FIND_PATH(OCC_INCLUDE_DIR Standard_Version.hxx
|
||||
[PATHS
|
||||
/usr/include/occt
|
||||
/usr/include/oce
|
||||
/usr/include/opencascade
|
||||
]
|
||||
REQUIRED
|
||||
)
|
||||
IF(OCC_INCLUDE_DIR)
|
||||
MESSAGE(STATUS "Found Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
ELSE()
|
||||
MESSAGE(FATAL_ERROR "Unable to find Open CASCADE include directory, specify OCC_INCLUDE_DIR manually.")
|
||||
ENDIF()
|
||||
ELSE()
|
||||
SET(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
|
||||
MESSAGE(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
FIND_FILE(gp_Pnt_hxx "gp_Pnt.hxx" ${OCC_INCLUDE_DIR})
|
||||
IF(gp_Pnt_hxx)
|
||||
MESSAGE(STATUS "Header files found")
|
||||
ELSE()
|
||||
MESSAGE(FATAL_ERROR "Unable to find header files, aborting")
|
||||
SET(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
|
||||
MESSAGE(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
SET(OPENCASCADE_LIBRARY_NAMES
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
|
||||
)
|
||||
|
||||
IF("${OCC_LIBRARY_DIR}" STREQUAL "")
|
||||
SET(OCC_LIBRARY_DIR "/usr/lib/" CACHE FILEPATH "Open CASCADE library files")
|
||||
MESSAGE(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
MESSAGE(STATUS "Use OCC_LIBRARY_DIR to specify another directory")
|
||||
find_library(OCC_LIBRARY TKernel
|
||||
[PATHS
|
||||
/usr/lib
|
||||
]
|
||||
REQUIRED
|
||||
)
|
||||
IF(OCC_LIBRARY)
|
||||
GET_FILENAME_COMPONENT(OCC_LIBRARY_DIR ${OCC_LIBRARY} PATH)
|
||||
MESSAGE(STATUS "Found Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
ELSE()
|
||||
MESSAGE(FATAL_ERROR "Unable find Open CASCADE library directory, specify OCC_LIBRARY_DIR manually.")
|
||||
ENDIF()
|
||||
ELSE()
|
||||
SET(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
|
||||
MESSAGE(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
SET(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
|
||||
MESSAGE(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
ENDIF()
|
||||
|
||||
FIND_LIBRARY(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
IF(libTKernel)
|
||||
MESSAGE(STATUS "Library files found")
|
||||
MESSAGE(STATUS "Required Open Cascade Library files found")
|
||||
ELSE()
|
||||
MESSAGE(FATAL_ERROR "Unable to find library files, aborting")
|
||||
MESSAGE(FATAL_ERROR "Unable to find Open Cascade library files, aborting")
|
||||
ENDIF()
|
||||
|
||||
# Use the found libTKernel as a template for all other OCC libraries
|
||||
@@ -473,7 +490,7 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
|
||||
)
|
||||
endfunction()
|
||||
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1")
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2")
|
||||
|
||||
if(COMPILE_SCHEMA)
|
||||
# @todo, this appears to be untested at the moment
|
||||
@@ -624,7 +641,7 @@ if ((NOT WIN32) AND BUILD_SHARED_LIBS)
|
||||
# Only set RPATHs when building shared libraries (i.e. IfcParse and
|
||||
# IfcGeom are dynamically linked). Not necessarily a perfect solution
|
||||
# but probably a good indication of whether RPATHs are necessary.
|
||||
SET_INSTALL_RPATHS(IfcConvert "${IFCOPENSHELL_LIBARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}")
|
||||
SET_INSTALL_RPATHS(IfcConvert "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
INSTALL(TARGETS IfcConvert
|
||||
@@ -645,7 +662,7 @@ ADD_EXECUTABLE(IfcGeomServer ${SOURCE_FILES})
|
||||
TARGET_LINK_LIBRARIES(IfcGeomServer ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
|
||||
|
||||
if ((NOT WIN32) AND BUILD_SHARED_LIBS)
|
||||
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
|
||||
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
|
||||
endif()
|
||||
|
||||
INSTALL(TARGETS IfcGeomServer
|
||||
@@ -716,3 +733,43 @@ INSTALL(FILES ${SERIALIZERS_S_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/serializers/schema_dependent
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES
|
||||
.git
|
||||
.gitignore
|
||||
)
|
||||
|
||||
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}${EXTRA_VERSION}")
|
||||
SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}")
|
||||
set(CPACK_PACKAGE_DIRECTORY "${PROJECT_BINARY_DIR}/assets")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IfcOpenShell")
|
||||
set(CPACK_PACKAGE_DESCRIPTION "IfcOpenShell.")
|
||||
set(CPACK_PACKAGE_VENDOR "Cemosis")
|
||||
set(CPACK_PACKAGE_CONTACT "Christophe Prud'homme <christophe.prudhomme@cemosis.fr>")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
|
||||
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
|
||||
set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
|
||||
|
||||
|
||||
set(CPACK_GENERATOR "TGZ;DEB")
|
||||
set(CPACK_SOURCE_GENERATOR "TGZ")
|
||||
|
||||
FOREACH(COMPONENT IN ITEMS ${BOOST_COMPONENTS})
|
||||
string(REPLACE "_" "-" COMP ${COMPONENT})
|
||||
set(BOOST_DEPS "${BOOST_DEPS}, libboost-${COMP}1.67.0")
|
||||
endforeach(COMPONENT)
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, liboce-foundation11, liboce-modeling11, liboce-ocaf11, liboce-visualization11, liboce-ocaf-lite11, libpython3.8 ${BOOST_DEPS}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
|
||||
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "science")
|
||||
set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}${EXTRA_VERSION}")
|
||||
set(CPACK_DEBIAN_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
#set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_SOURCE_DIR}/cmake/debian/postinst")
|
||||
|
||||
include(CPack)
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8
|
||||
# title of most generated pages and in a few other places.
|
||||
# The default value is: My Project.
|
||||
|
||||
PROJECT_NAME = "My Project"
|
||||
PROJECT_NAME = "IfcOpenShell"
|
||||
|
||||
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
@@ -829,7 +829,7 @@ WARN_LOGFILE =
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = ../src/ifcgeom ../src/serializers
|
||||
INPUT = ../src/ifcgeom ../src/serializers ../src/ifcgeom_schema_agnostic ../src/ifcparse
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
|
||||
+2
-13
@@ -22,7 +22,7 @@ exhale_args = {
|
||||
# These arguments are required
|
||||
"containmentFolder": "./rst_files",
|
||||
"rootFileName": "library_root.rst",
|
||||
"rootFileTitle": "IfcOpenShell",
|
||||
"rootFileTitle": "IfcOpenShell C++",
|
||||
"doxygenStripFromPath": "..",
|
||||
# Suggested optional arguments
|
||||
"createTreeView": True,
|
||||
@@ -34,15 +34,4 @@ exhale_args = {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
# html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# # Tell sphinx what the primary language being documented is.
|
||||
# primary_domain = 'cpp'
|
||||
|
||||
# # Tell sphinx what the pygments highlight language should be.
|
||||
# highlight_language = 'cpp'
|
||||
|
||||
|
||||
|
||||
cpp_id_attributes = ['IFC_PARSE_API', 'IFC_GEOM_API']
|
||||
|
||||
@@ -7,4 +7,4 @@ IfcOpenShell
|
||||
rst_files/library_root
|
||||
|
||||
|
||||
`ifcopenshell-python documentation <_build/html/index.html>`_
|
||||
* `ifcopenshell-python documentation <python/html/index.html>`_
|
||||
+26
-39
@@ -1,52 +1,39 @@
|
||||
# This program requires doxygen, sphinx, breathe and exhale.
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import multiprocessing
|
||||
|
||||
from sys import platform
|
||||
|
||||
|
||||
def copy_content(content, container):
|
||||
with open(content) as f:
|
||||
with open(container, "w") as f1:
|
||||
for line in f:
|
||||
f1.write(line)
|
||||
|
||||
|
||||
conf_path = "./conf.py"
|
||||
index_path = "./index.rst"
|
||||
# some extra check to see if we can find sphinx in pypy bin dir
|
||||
sphinx_build = os.path.join(os.path.dirname(sys.executable), 'sphinx-build')
|
||||
if not os.path.exists(sphinx_build):
|
||||
sphinx_build = "sphinx-build"
|
||||
# else:
|
||||
# os.environ['PATH'] = os.pathsep + os.path.dirname(sys.executable)
|
||||
|
||||
# not used, -j caused issues in my sphinx install
|
||||
j = str(multiprocessing.cpu_count())
|
||||
|
||||
if not os.path.isdir("./output/doxygen/xml"):
|
||||
os.makedirs("output/doxygen/xml")
|
||||
|
||||
if os.path.isfile(conf_path) and not os.path.isfile(index_path):
|
||||
os.remove(conf_path)
|
||||
if os.path.isfile(index_path) and not os.path.isfile(conf_path):
|
||||
os.remove(index_path)
|
||||
subprocess.check_call([sphinx_build, "-b", "html", ".", "output"])
|
||||
|
||||
if not (os.path.isfile(conf_path) and os.path.isfile(index_path)):
|
||||
subprocess.run(["sphinx-quickstart", "-q", "-p", "IfcOpenShell"])
|
||||
|
||||
conf_copy = ["./conf_to_copy.py", "./conf.py"]
|
||||
index_copy = ["./index_to_copy.rst", "./index.rst"]
|
||||
|
||||
copy_content(conf_copy[0], conf_copy[1])
|
||||
copy_content(index_copy[0], index_copy[1])
|
||||
|
||||
if platform == "linux" or platform == "linux2":
|
||||
subprocess.run(["make", "html"])
|
||||
if not os.path.isdir("/output/_build"):
|
||||
subprocess.run(["cd","../src/ifcblenderexport/docs"])
|
||||
subprocess.run(["make", "html"])
|
||||
subprocess.run(["cd", "../../../docs"])
|
||||
|
||||
subprocess.run(["mv" "../src/ifcblenderexport/docs/_build", "./output"])
|
||||
if sys.platform == "linux" or sys.platform == "linux2":
|
||||
if os.path.isdir("output/python"):
|
||||
shutil.rmtree("output/python")
|
||||
|
||||
if not os.path.exists(os.path.join("../src/ifcblenderexport/docs", "contents.rst")):
|
||||
subprocess.check_call(["ln", "index.rst", "contents.rst"], cwd="../src/ifcblenderexport/docs")
|
||||
|
||||
subprocess.check_call(["sed", "-i", "s/html_theme/# html_theme/g", "conf.py"], cwd="../src/ifcblenderexport/docs")
|
||||
subprocess.check_call(["make", "html"], cwd="../src/ifcblenderexport/docs")
|
||||
subprocess.check_call(["git", "checkout", "conf.py"], cwd="../src/ifcblenderexport/docs")
|
||||
|
||||
shutil.move("../src/ifcblenderexport/docs/_build", "./output")
|
||||
os.rename("./output/_build", "./output/python")
|
||||
|
||||
elif platform == "win32" or platform == "win64":
|
||||
subprocess.run(["make.bat", "html"])
|
||||
|
||||
|
||||
|
||||
elif sys.platform == "win32" or sys.platform == "win64":
|
||||
subprocess.check_call(["make.bat", "html"])
|
||||
|
||||
+1
-1
@@ -7,4 +7,4 @@ IfcOpenShell
|
||||
rst_files/library_root
|
||||
|
||||
|
||||
`ifcopenshell-python documentation <_build/html/index.html>`_
|
||||
* `ifcopenshell-python documentation <python/html/index.html>`_
|
||||
+1
-1
@@ -336,7 +336,7 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
`None`."""
|
||||
if not os.path.exists(target_dir):
|
||||
logger.info("cloning '%s' into '%s'" % (clone_url, target_dir))
|
||||
run([git, "clone", clone_url, target_dir])
|
||||
run([git, "clone", "--recursive", clone_url, target_dir])
|
||||
else:
|
||||
logger.info("directory '%s' already cloned. Pulling latest changes." % (target_dir,))
|
||||
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
# ifc2ca
|
||||
Files and scripts for the use of [`Code_Aster`](https://code-aster.org) in IFC-driven FEM analyses
|
||||
|
||||
### File Organisation
|
||||
#### Scripts:
|
||||
## File Organisation
|
||||
|
||||
## Scripts:
|
||||
|
||||
- [`ifc2ca.py`](ifc2ca.py): a python script to extract and create a `json` file from an `ifc` file
|
||||
- [`scriptSalome.py`](scriptSalome.py): a python script to run in the [`Salome-Meca`](https://www.code-aster.org/spip.php?article303) environment. Creates the geometry and the mesh of the structure
|
||||
- [`scriptCodeAster.py`](scriptCodeAster.py): a python script to create the input file (`.comm`) for Code_Aster
|
||||
- [`scriptSalomeBonded.py`](scriptSalomeBonded.py): a python script to run in the [`Salome-Meca`](https://www.code-aster.org/spip.php?article303) environment. Creates the geometry and the mesh of the structure by bonding together all structural elements (no connections are considered)
|
||||
- [`scriptCodeAsterBonded.py`](scriptCodeAsterBonded.py): a python script to create the input file (`.comm`) for Code_Aster for the "bonded" case
|
||||
|
||||
#### Analysis Models
|
||||
## Analysis Models
|
||||
|
||||
A number of analysis models with all the relative input/output and script files are provided in [this repository](https://github.com/IfcOpenShell/analysis-models) within the IfcOpenShell Organization
|
||||
|
||||
---
|
||||
|
||||
### Current Status
|
||||
## Current Status
|
||||
|
||||
Read [Change Log](changelog.md)
|
||||
|
||||
+17
-6
@@ -1,23 +1,32 @@
|
||||
## A change log of the ifc2ca files
|
||||
# A change log of the ifc2ca files
|
||||
|
||||
## 01/03/21
|
||||
|
||||
- Add salome and code_aster script files for a "bonded" case, meaning all connections between elements are considered fixed. This allows to consider exported ifc files from commercial software that do not include connections between surface elements (see building_02 example)
|
||||
|
||||
## 14/10/20
|
||||
|
||||
##### 14/10/20
|
||||
- Add curve connections betweeen elements and as supports according to the defined orientation - stiffness is not yet considered
|
||||
|
||||
##### 17/08/20
|
||||
## 17/08/20
|
||||
|
||||
- Add rigid links for point connection to elements
|
||||
|
||||
##### 14/07/20
|
||||
## 14/07/20
|
||||
|
||||
- Add internal springs for point connection to elements according to the defined orientation
|
||||
- Add external springs for point connections according to the defined orientation
|
||||
|
||||
##### 27/05/20
|
||||
## 27/05/20
|
||||
|
||||
- Add orientation for point, curve and surface geometries
|
||||
- Calculate final geometry and orientation based on object placement transformation
|
||||
- Add warnings in the json file to show any corrections considered while parsing the ifc file from ETABS with a model with rigid links
|
||||
- Reduce file size by adding material and profile db in the json file and referencing them in the element objects
|
||||
- Implement internal releases for curve-to-point connections with any arbitrary orientation
|
||||
|
||||
##### 22/03/20
|
||||
## 22/03/20
|
||||
|
||||
- Calculation of section properties for I symmetric profile if not provided (assuming fillet radius is zero)
|
||||
- Added geometry of planar structural surface members
|
||||
- Refactored the ifc2ca.py script
|
||||
@@ -25,10 +34,12 @@
|
||||
- Added advanced meshing script to correctly simulate connections and independent meshing of structural elements (no common nodes)
|
||||
|
||||
##### 26/02/20
|
||||
|
||||
- Changed section to profile and added I profile
|
||||
- Modified material and profile schema with mechanical and common properties
|
||||
- Added geometry identification for point connection from representation
|
||||
- Added connections along with supports based on the number of elements a connection is applied to
|
||||
|
||||
##### 09/02/20
|
||||
|
||||
- First implementation for straight structural curve elements and point connections
|
||||
|
||||
+123
-41
@@ -17,7 +17,9 @@ class IFC2CA:
|
||||
self.file = ifcopenshell.open(self.filename)
|
||||
for model in self.file.by_type("IfcStructuralAnalysisModel"):
|
||||
elements = self.get_structural_items(model, item_type="IfcStructuralMember")
|
||||
connections = self.get_structural_items(model, item_type="IfcStructuralConnection")
|
||||
connections = self.get_structural_items(
|
||||
model, item_type="IfcStructuralConnection"
|
||||
)
|
||||
|
||||
materialdb = []
|
||||
materials = list(dict.fromkeys([e["material"] for e in elements]))
|
||||
@@ -25,16 +27,24 @@ class IFC2CA:
|
||||
id = int(mat.split("|")[1])
|
||||
material = self.get_material_properties(self.file.by_id(id))
|
||||
material["relatedElements"] = [
|
||||
e["ifcName"] for e in elements if "material" in e and e["material"] == mat
|
||||
e["ifcName"]
|
||||
for e in elements
|
||||
if "material" in e and e["material"] == mat
|
||||
]
|
||||
materialdb.append(material)
|
||||
|
||||
profiledb = []
|
||||
profiles = list(dict.fromkeys([e["profile"] for e in elements if "profile" in e]))
|
||||
profiles = list(
|
||||
dict.fromkeys([e["profile"] for e in elements if "profile" in e])
|
||||
)
|
||||
for prof in [prof for prof in profiles if prof]:
|
||||
id = int(prof.split("|")[1])
|
||||
profile = self.get_profile_properties(self.file.by_id(id))
|
||||
profile["relatedElements"] = [e["ifcName"] for e in elements if "profile" in e and e["profile"] == prof]
|
||||
profile["relatedElements"] = [
|
||||
e["ifcName"]
|
||||
for e in elements
|
||||
if "profile" in e and e["profile"] == prof
|
||||
]
|
||||
profiledb.append(profile)
|
||||
|
||||
self.result = {
|
||||
@@ -47,11 +57,11 @@ class IFC2CA:
|
||||
"warnings": self.warnings,
|
||||
}
|
||||
|
||||
print('Model "%s" converted' % model.Name)
|
||||
print("Number of elements: ", len(elements))
|
||||
print("Number of connections: ", len(connections))
|
||||
print("Number of materials: ", len(materialdb))
|
||||
print("Number of profiles: ", len(profiledb))
|
||||
print(f"Model {model.Name} converted")
|
||||
print(f"Number of elements: {len(elements)}")
|
||||
print(f"Number of connections: {len(connections)}")
|
||||
print(f"Number of materials: {len(materialdb)}")
|
||||
print(f"Number of profiles: {len(profiledb)}")
|
||||
print("")
|
||||
|
||||
break
|
||||
@@ -75,12 +85,19 @@ class IFC2CA:
|
||||
material_profile = self.get_material_profile(item)
|
||||
if not representation:
|
||||
self.warnings.append(
|
||||
"No representation defined for %s. Member excluded" % (item.is_a() + "|" + str(item.id()))
|
||||
"No representation defined for %s. Member excluded"
|
||||
% (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
return
|
||||
if not material_profile:
|
||||
self.warnings.append("No material defined for in %s" % (item.is_a() + "|" + str(item.id())))
|
||||
self.warnings.append("No profile defined for in %s" % (item.is_a() + "|" + str(item.id())))
|
||||
self.warnings.append(
|
||||
"No material defined for in %s"
|
||||
% (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
self.warnings.append(
|
||||
"No profile defined for in %s"
|
||||
% (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
materialId = None
|
||||
profileId = None
|
||||
else:
|
||||
@@ -99,21 +116,32 @@ class IFC2CA:
|
||||
length = np.linalg.norm(np.array(geometry[1]) - np.array(geometry[0]))
|
||||
for c in connections:
|
||||
if c["eccentricity"]:
|
||||
if np.linalg.norm(np.array(c["eccentricity"]["pointOnElement"])) > length + self.tol:
|
||||
print(np.linalg.norm(np.array(c["eccentricity"]["pointOnElement"])), ">", length)
|
||||
self.warnings.append("Eccentricity in %s corrected" % (item.is_a() + "|" + str(item.id())))
|
||||
if (
|
||||
np.linalg.norm(np.array(c["eccentricity"]["pointOnElement"]))
|
||||
> length + self.tol
|
||||
):
|
||||
print(
|
||||
f"{np.linalg.norm(np.array(c['eccentricity']['pointOnElement']))} > {length}"
|
||||
)
|
||||
self.warnings.append(
|
||||
f"Eccentricity in {item.is_a()}|{str(item.id())} corrected"
|
||||
)
|
||||
c["eccentricity"]["pointOnElement"][0] = length
|
||||
# End <--
|
||||
if transformation:
|
||||
geometry = self.transform_vectors(geometry, transformation)
|
||||
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
|
||||
orientation = self.transform_vectors(
|
||||
orientation, transformation, include_translation=False
|
||||
)
|
||||
for c in connections:
|
||||
c["orientation"] = self.transform_vectors(
|
||||
c["orientation"], transformation, include_translation=False
|
||||
)
|
||||
if c["eccentricity"]:
|
||||
c["eccentricity"]["vector"] = self.transform_vectors(
|
||||
c["eccentricity"]["vector"], transformation, include_translation=False
|
||||
c["eccentricity"]["vector"],
|
||||
transformation,
|
||||
include_translation=False,
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -134,11 +162,15 @@ class IFC2CA:
|
||||
material = self.get_material_profile(item)
|
||||
if not representation:
|
||||
self.warnings.append(
|
||||
"No representation defined for %s. Member excluded" % (item.is_a() + "|" + str(item.id()))
|
||||
"No representation defined for %s. Member excluded"
|
||||
% (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
return
|
||||
if not material:
|
||||
self.warnings.append("No material defined for in %s" % (item.is_a() + "|" + str(item.id())))
|
||||
self.warnings.append(
|
||||
"No material defined for in %s"
|
||||
% (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
materialId = None
|
||||
else:
|
||||
materialId = material.is_a() + "|" + str(material.id())
|
||||
@@ -151,7 +183,9 @@ class IFC2CA:
|
||||
conn["orientation"] = orientation
|
||||
if transformation:
|
||||
geometry = self.transform_vectors(geometry, transformation)
|
||||
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
|
||||
orientation = self.transform_vectors(
|
||||
orientation, transformation, include_translation=False
|
||||
)
|
||||
for c in connections:
|
||||
c["orientation"] = self.transform_vectors(
|
||||
c["orientation"], transformation, include_translation=False
|
||||
@@ -174,7 +208,8 @@ class IFC2CA:
|
||||
representation = self.get_representation(item, "Vertex")
|
||||
if not representation:
|
||||
self.warnings.append(
|
||||
"No representation defined for %s. Connection excluded" % (item.is_a() + "|" + str(item.id()))
|
||||
"No representation defined for %s. Connection excluded"
|
||||
% (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
return
|
||||
|
||||
@@ -184,7 +219,9 @@ class IFC2CA:
|
||||
orientation = np.eye(3).tolist()
|
||||
if transformation:
|
||||
geometry = self.transform_vectors(geometry, transformation)
|
||||
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
|
||||
orientation = self.transform_vectors(
|
||||
orientation, transformation, include_translation=False
|
||||
)
|
||||
|
||||
return {
|
||||
"ifcName": item.is_a() + "|" + str(item.id()),
|
||||
@@ -194,14 +231,18 @@ class IFC2CA:
|
||||
"geometry": geometry,
|
||||
"orientation": orientation,
|
||||
"appliedCondition": self.get_connection_input(item, "point"),
|
||||
"relatedElements": [con.is_a() + "|" + str(con.id()) for con in item.ConnectsStructuralMembers],
|
||||
"relatedElements": [
|
||||
con.is_a() + "|" + str(con.id())
|
||||
for con in item.ConnectsStructuralMembers
|
||||
],
|
||||
}
|
||||
|
||||
elif item.is_a("IfcStructuralCurveConnection"):
|
||||
representation = self.get_representation(item, "Edge")
|
||||
if not representation:
|
||||
self.warnings.append(
|
||||
"No representation defined for %s. Connection excluded" % (item.is_a() + "|" + str(item.id()))
|
||||
"No representation defined for %s. Connection excluded"
|
||||
% (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
return
|
||||
|
||||
@@ -211,7 +252,9 @@ class IFC2CA:
|
||||
orientation = np.eye(3).tolist()
|
||||
if transformation:
|
||||
geometry = self.transform_vectors(geometry, transformation)
|
||||
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
|
||||
orientation = self.transform_vectors(
|
||||
orientation, transformation, include_translation=False
|
||||
)
|
||||
|
||||
return {
|
||||
"ifcName": item.is_a() + "|" + str(item.id()),
|
||||
@@ -221,7 +264,10 @@ class IFC2CA:
|
||||
"geometry": geometry,
|
||||
"orientation": orientation,
|
||||
"appliedCondition": self.get_connection_input(item, "line"),
|
||||
"relatedElements": [con.is_a() + "|" + str(con.id()) for con in item.ConnectsStructuralMembers],
|
||||
"relatedElements": [
|
||||
con.is_a() + "|" + str(con.id())
|
||||
for con in item.ConnectsStructuralMembers
|
||||
],
|
||||
}
|
||||
|
||||
def get_transformation(self, placement):
|
||||
@@ -229,11 +275,15 @@ class IFC2CA:
|
||||
return None
|
||||
if placement.is_a("IfcLocalPlacement"):
|
||||
if placement.PlacementRelTo:
|
||||
print("Warning! Object Placement with PlacementRelTo attribute is not supported and will be neglected")
|
||||
print(
|
||||
"Warning! Object Placement with PlacementRelTo attribute is not supported and will be neglected"
|
||||
)
|
||||
axes = placement.RelativePlacement
|
||||
location = np.array(self.get_coordinate(axes.Location))
|
||||
if axes.Axis and axes.RefDirection:
|
||||
xAxis = np.array(axes.RefDirection.DirectionRatios) # this can be not accurate (in the xz plane)
|
||||
xAxis = np.array(
|
||||
axes.RefDirection.DirectionRatios
|
||||
) # this can be not accurate (in the xz plane)
|
||||
zAxis = np.array(axes.Axis.DirectionRatios)
|
||||
zAxis /= np.linalg.norm(zAxis)
|
||||
yAxis = np.cross(zAxis, xAxis)
|
||||
@@ -253,10 +303,13 @@ class IFC2CA:
|
||||
and np.allclose(zAxis, np.array([0.0, 0.0, 1.0]))
|
||||
):
|
||||
return None
|
||||
return {"location": location, "rotationMatrix": np.array([xAxis, yAxis, zAxis]).transpose()}
|
||||
return {
|
||||
"location": location,
|
||||
"rotationMatrix": np.array([xAxis, yAxis, zAxis]).transpose(),
|
||||
}
|
||||
else:
|
||||
print(
|
||||
"Warning! Object Placement is of type %s, which is not supported. Default considered" % placement.is_a()
|
||||
f"Warning! Object Placement is of type {placement.is_a()}, which is not supported. Default considered"
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -264,11 +317,13 @@ class IFC2CA:
|
||||
if not element.Representation:
|
||||
return None
|
||||
for representation in element.Representation.Representations:
|
||||
rep = self.get_specific_representation(representation, "Reference", rep_type)
|
||||
rep = self.get_specific_representation(
|
||||
representation, "Reference", rep_type
|
||||
)
|
||||
if rep:
|
||||
return rep
|
||||
else:
|
||||
# print('Trying without rep identifier')
|
||||
# print("Trying without rep identifier")
|
||||
for representation in element.Representation.Representations:
|
||||
rep = self.get_specific_representation(representation, None, rep_type)
|
||||
if rep:
|
||||
@@ -281,7 +336,9 @@ class IFC2CA:
|
||||
return representation
|
||||
if representation.RepresentationType == "MappedRepresentation":
|
||||
return self.get_specific_representation(
|
||||
representation.Items[0].MappingSource.MappedRepresentation, rep_id, rep_type
|
||||
representation.Items[0].MappingSource.MappedRepresentation,
|
||||
rep_id,
|
||||
rep_type,
|
||||
)
|
||||
|
||||
def get_geometry(self, representation):
|
||||
@@ -299,7 +356,9 @@ class IFC2CA:
|
||||
edges = item.Bounds[0].Bound.EdgeList
|
||||
coords = []
|
||||
for edge in edges:
|
||||
coords.append(self.get_coordinate(edge.EdgeElement.EdgeStart.VertexGeometry))
|
||||
coords.append(
|
||||
self.get_coordinate(edge.EdgeElement.EdgeStart.VertexGeometry)
|
||||
)
|
||||
return coords
|
||||
|
||||
elif item.is_a("IfcVertexPoint"):
|
||||
@@ -328,7 +387,9 @@ class IFC2CA:
|
||||
def get_1D_orientation(self, geometry, zAxis):
|
||||
xAxis = np.array(geometry[1]) - np.array(geometry[0])
|
||||
xAxis /= np.linalg.norm(xAxis)
|
||||
zAxis = np.array(zAxis.DirectionRatios) # this can be not strictly perpendicular (in the xz plane)
|
||||
zAxis = np.array(
|
||||
zAxis.DirectionRatios
|
||||
) # this can be not strictly perpendicular (in the xz plane)
|
||||
yAxis = np.cross(zAxis, xAxis)
|
||||
yAxis /= np.linalg.norm(yAxis)
|
||||
zAxis = np.cross(xAxis, yAxis)
|
||||
@@ -347,7 +408,9 @@ class IFC2CA:
|
||||
return orientation
|
||||
|
||||
def transform_vectors(self, geometry, trsf, include_translation=True):
|
||||
if not any(isinstance(el, list) for el in geometry): # single point which contains no list
|
||||
if not any(
|
||||
isinstance(el, list) for el in geometry
|
||||
): # single point which contains no list
|
||||
geometry = [geometry]
|
||||
globalGeometry = []
|
||||
|
||||
@@ -453,13 +516,18 @@ class IFC2CA:
|
||||
{
|
||||
"ifcName": rel.is_a() + "|" + str(rel.id()),
|
||||
"id": rel.GlobalId,
|
||||
"relatingElement": rel.RelatingStructuralMember.is_a() + "|" + str(rel.RelatingStructuralMember.id()),
|
||||
"relatingElement": rel.RelatingStructuralMember.is_a()
|
||||
+ "|"
|
||||
+ str(rel.RelatingStructuralMember.id()),
|
||||
"relatedConnection": rel.RelatedStructuralConnection.is_a()
|
||||
+ "|"
|
||||
+ str(rel.RelatedStructuralConnection.id()),
|
||||
"orientation": self.get_0D_orientation(rel.ConditionCoordinateSystem),
|
||||
"appliedCondition": self.get_connection_input(
|
||||
rel, self.get_geometry_type_from_connection(rel.RelatedStructuralConnection)
|
||||
rel,
|
||||
self.get_geometry_type_from_connection(
|
||||
rel.RelatedStructuralConnection
|
||||
),
|
||||
),
|
||||
"eccentricity": None
|
||||
if not rel.is_a("IfcRelConnectsWithEccentricity")
|
||||
@@ -475,7 +543,9 @@ class IFC2CA:
|
||||
if not rel.ConnectionConstraint.EccentricityInZ
|
||||
else rel.ConnectionConstraint.EccentricityInZ,
|
||||
],
|
||||
"pointOnElement": self.get_coordinate(rel.ConnectionConstraint.PointOnRelatingElement),
|
||||
"pointOnElement": self.get_coordinate(
|
||||
rel.ConnectionConstraint.PointOnRelatingElement
|
||||
),
|
||||
},
|
||||
}
|
||||
for rel in itemList
|
||||
@@ -532,11 +602,23 @@ class IFC2CA:
|
||||
Iz = (2 * tf) * (b ** 3) / 12 + (h - 2 * tf) * (tw ** 3) / 12
|
||||
Jx = 1 / 3 * ((h - tf) * (tw ** 3) + 2 * b * (tf ** 3))
|
||||
|
||||
return {"crossSectionArea": A, "momentOfInertiaY": Iy, "momentOfInertiaZ": Iz, "torsionalConstantX": Jx}
|
||||
return {
|
||||
"crossSectionArea": A,
|
||||
"momentOfInertiaY": Iy,
|
||||
"momentOfInertiaZ": Iz,
|
||||
"torsionalConstantX": Jx,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fileNames = ["cantilever_01", "portal_01", "grid_of_beams", "slab_01", "structure_01"]
|
||||
fileNames = [
|
||||
"cantilever_01",
|
||||
"portal_01",
|
||||
"grid_of_beams",
|
||||
"slab_01",
|
||||
"structure_01",
|
||||
"building_02",
|
||||
]
|
||||
files = fileNames
|
||||
|
||||
for fileName in files:
|
||||
|
||||
+221
-54
@@ -33,7 +33,9 @@ class COMMANDFILE:
|
||||
self.calculateRestraints(conn)
|
||||
for el in elements:
|
||||
for rel in el["connections"]:
|
||||
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
|
||||
conn = [
|
||||
c for c in connections if c["ifcName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
rel["conn_string"] = None
|
||||
if conn["geometryType"] == "point":
|
||||
rel["conn_string"] = "_0DC_"
|
||||
@@ -61,7 +63,10 @@ class COMMANDFILE:
|
||||
+ self.getGroupName(rel["relatingElement"])
|
||||
)
|
||||
rel["index"] = len(conn["relatedElements"]) + 1
|
||||
rel["unifiedGroupName"] = self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"]
|
||||
rel["unifiedGroupName"] = (
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_%g" % rel["index"]
|
||||
)
|
||||
else:
|
||||
rel["groupName2"] = self.getGroupName(rel["relatedConnection"])
|
||||
self.calculateConstraints(rel)
|
||||
@@ -71,25 +76,54 @@ class COMMANDFILE:
|
||||
materials = data["db"]["materials"]
|
||||
profiles = data["db"]["profiles"]
|
||||
|
||||
edgeGroupNames = tuple([self.getGroupName(el["ifcName"]) for el in elements if el["geometryType"] == "line"])
|
||||
faceGroupNames = tuple([self.getGroupName(el["ifcName"]) for el in elements if el["geometryType"] == "surface"])
|
||||
edgeGroupNames = tuple(
|
||||
[
|
||||
self.getGroupName(el["ifcName"])
|
||||
for el in elements
|
||||
if el["geometryType"] == "line"
|
||||
]
|
||||
)
|
||||
faceGroupNames = tuple(
|
||||
[
|
||||
self.getGroupName(el["ifcName"])
|
||||
for el in elements
|
||||
if el["geometryType"] == "surface"
|
||||
]
|
||||
)
|
||||
point0DGroupNames = tuple(
|
||||
[self.getGroupName(el["ifcName"]) + "_0D" for el in connections if el["geometryType"] == "point"]
|
||||
[
|
||||
self.getGroupName(el["ifcName"]) + "_0D"
|
||||
for el in connections
|
||||
if el["geometryType"] == "point"
|
||||
]
|
||||
)
|
||||
spring1DGroupNames = tuple(
|
||||
flatten(
|
||||
[[rel["springGroupName"] for rel in el["connections"] if rel["springGroupName"]] for el in elements]
|
||||
[
|
||||
[
|
||||
rel["springGroupName"]
|
||||
for rel in el["connections"]
|
||||
if rel["springGroupName"]
|
||||
]
|
||||
for el in elements
|
||||
]
|
||||
)
|
||||
)
|
||||
point1DGroupNames = tuple(
|
||||
[self.getGroupName(el["ifcName"]) + "_0D" for el in connections if el["geometryType"] == "line"]
|
||||
[
|
||||
self.getGroupName(el["ifcName"]) + "_0D"
|
||||
for el in connections
|
||||
if el["geometryType"] == "line"
|
||||
]
|
||||
)
|
||||
|
||||
unifiedConnection = False
|
||||
rigidLinkGroupNames = []
|
||||
for conn in connections:
|
||||
conn["unifiedGroupNames"] = [
|
||||
rel["unifiedGroupName"] for rel in conn["relatedElements"] if rel["eccentricity"]
|
||||
rel["unifiedGroupName"]
|
||||
for rel in conn["relatedElements"]
|
||||
if rel["eccentricity"]
|
||||
]
|
||||
# if not conn['appliedCondition'] and len(conn['unifiedGroupNames']) == 1:
|
||||
# conn['appliedCondition'] = {
|
||||
@@ -103,7 +137,9 @@ class COMMANDFILE:
|
||||
unifiedConnection = True
|
||||
rigidLinkGroupNames.extend(
|
||||
[
|
||||
self.getGroupName(rel["relatingElement"]) + "_1DR_" + self.getGroupName(conn["ifcName"])
|
||||
self.getGroupName(rel["relatingElement"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(conn["ifcName"])
|
||||
for rel in conn["relatedElements"]
|
||||
if rel["eccentricity"]
|
||||
]
|
||||
@@ -182,7 +218,9 @@ model = AFFE_MODELE(
|
||||
MODELISATION = 'DIS_TR'
|
||||
),"""
|
||||
|
||||
context = {"groupNames": tuple(flatten([point0DGroupNames, spring1DGroupNames]))}
|
||||
context = {
|
||||
"groupNames": tuple(flatten([point0DGroupNames, spring1DGroupNames]))
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
@@ -234,7 +272,9 @@ model = AFFE_MODELE(
|
||||
else:
|
||||
if "shearModulus" in material["mechProps"]:
|
||||
poissonRatio = (
|
||||
material["mechProps"]["youngModulus"] / 2.0 / material["mechProps"]["shearModulus"]
|
||||
material["mechProps"]["youngModulus"]
|
||||
/ 2.0
|
||||
/ material["mechProps"]["shearModulus"]
|
||||
) - 1
|
||||
else:
|
||||
poissonRatio = 0.0
|
||||
@@ -263,7 +303,9 @@ material = AFFE_MATERIAU(
|
||||
),"""
|
||||
|
||||
context = {
|
||||
"groupNames": tuple([self.getGroupName(rel) for rel in material["relatedElements"]]),
|
||||
"groupNames": tuple(
|
||||
[self.getGroupName(rel) for rel in material["relatedElements"]]
|
||||
),
|
||||
"matNameID": "mat" + "_%s" % i,
|
||||
}
|
||||
|
||||
@@ -296,7 +338,10 @@ element = AFFE_CARA_ELEM(
|
||||
)
|
||||
|
||||
for profile in profiles:
|
||||
if profile["profileShape"] == "rectangular" and profile["profileType"] == "AREA":
|
||||
if (
|
||||
profile["profileShape"] == "rectangular"
|
||||
and profile["profileType"] == "AREA"
|
||||
):
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
@@ -306,13 +351,18 @@ element = AFFE_CARA_ELEM(
|
||||
),"""
|
||||
|
||||
context = {
|
||||
"groupNames": tuple([self.getGroupName(rel) for rel in profile["relatedElements"]]),
|
||||
"groupNames": tuple(
|
||||
[self.getGroupName(rel) for rel in profile["relatedElements"]]
|
||||
),
|
||||
"profileDimensions": (profile["xDim"], profile["yDim"]),
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
elif profile["profileShape"] == "iSymmetrical" and profile["profileType"] == "AREA":
|
||||
elif (
|
||||
profile["profileShape"] == "iSymmetrical"
|
||||
and profile["profileType"] == "AREA"
|
||||
):
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
@@ -322,7 +372,9 @@ element = AFFE_CARA_ELEM(
|
||||
),"""
|
||||
|
||||
context = {
|
||||
"groupNames": tuple([self.getGroupName(rel) for rel in profile["relatedElements"]]),
|
||||
"groupNames": tuple(
|
||||
[self.getGroupName(rel) for rel in profile["relatedElements"]]
|
||||
),
|
||||
"profileProperties": (
|
||||
profile["mechProps"]["crossSectionArea"],
|
||||
profile["mechProps"]["momentOfInertiaY"],
|
||||
@@ -388,7 +440,10 @@ element = AFFE_CARA_ELEM(
|
||||
REPERE = 'LOCAL'
|
||||
),"""
|
||||
|
||||
context = {"groupName": self.getGroupName(conn["ifcName"]) + "_0D", "stiffnesses": conn["stiffnesses"]}
|
||||
context = {
|
||||
"groupName": self.getGroupName(conn["ifcName"]) + "_0D",
|
||||
"stiffnesses": conn["stiffnesses"],
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
@@ -402,7 +457,10 @@ element = AFFE_CARA_ELEM(
|
||||
REPERE = 'LOCAL'
|
||||
),"""
|
||||
|
||||
context = {"groupName": rel["springGroupName"], "stiffnesses": rel["stiffnesses"]}
|
||||
context = {
|
||||
"groupName": rel["springGroupName"],
|
||||
"stiffnesses": rel["stiffnesses"],
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
@@ -416,7 +474,10 @@ element = AFFE_CARA_ELEM(
|
||||
REPERE = 'LOCAL'
|
||||
),"""
|
||||
|
||||
context = {"groupName": self.getGroupName(conn["ifcName"]) + "_0D", "stiffnesses": conn["stiffnesses"]}
|
||||
context = {
|
||||
"groupName": self.getGroupName(conn["ifcName"]) + "_0D",
|
||||
"stiffnesses": conn["stiffnesses"],
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
@@ -439,7 +500,10 @@ element = AFFE_CARA_ELEM(
|
||||
VALE = {localAxisY}
|
||||
),"""
|
||||
|
||||
context = {"groupName": self.getGroupName(el["ifcName"]), "localAxisY": tuple(el["orientation"][1])}
|
||||
context = {
|
||||
"groupName": self.getGroupName(el["ifcName"]),
|
||||
"localAxisY": tuple(el["orientation"][1]),
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
@@ -614,7 +678,9 @@ liaisons = AFFE_CHAR_MECA(
|
||||
LIAISON_UNIF = ("""
|
||||
)
|
||||
|
||||
for conn in [conn for conn in connections if len(conn["unifiedGroupNames"]) > 1]:
|
||||
for conn in [
|
||||
conn for conn in connections if len(conn["unifiedGroupNames"]) > 1
|
||||
]:
|
||||
template = """
|
||||
_F(
|
||||
GROUP_NO = {groupNames},
|
||||
@@ -798,44 +864,103 @@ FIN()
|
||||
gr1 = rel["groupName1"]
|
||||
gr2 = rel["groupName2"]
|
||||
o = np.array(rel["orientation"]).transpose().tolist()
|
||||
liaisons = {"groupNames": (gr1, gr1, gr1, gr2, gr2, gr2), "coeffs": [], "dofs": []}
|
||||
liaisons = {
|
||||
"groupNames": (gr1, gr1, gr1, gr2, gr2, gr2),
|
||||
"coeffs": [],
|
||||
"dofs": [],
|
||||
}
|
||||
stiffnesses = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
if not rel["appliedCondition"]:
|
||||
rel["appliedCondition"] = {"dx": True, "dy": True, "dz": True, "drx": True, "dry": True, "drz": True}
|
||||
if isinstance(rel["appliedCondition"]["dx"], bool) and rel["appliedCondition"]["dx"]:
|
||||
liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0]))
|
||||
rel["appliedCondition"] = {
|
||||
"dx": True,
|
||||
"dy": True,
|
||||
"dz": True,
|
||||
"drx": True,
|
||||
"dry": True,
|
||||
"drz": True,
|
||||
}
|
||||
if (
|
||||
isinstance(rel["appliedCondition"]["dx"], bool)
|
||||
and rel["appliedCondition"]["dx"]
|
||||
):
|
||||
liaisons["coeffs"].append(
|
||||
(o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0])
|
||||
)
|
||||
liaisons["dofs"].append(("DX", "DY", "DZ", "DX", "DY", "DZ"))
|
||||
elif isinstance(rel["appliedCondition"]["dx"], float) and rel["appliedCondition"]["dx"] > 0:
|
||||
elif (
|
||||
isinstance(rel["appliedCondition"]["dx"], float)
|
||||
and rel["appliedCondition"]["dx"] > 0
|
||||
):
|
||||
stiffnesses[0] = rel["appliedCondition"]["dx"]
|
||||
|
||||
if isinstance(rel["appliedCondition"]["dy"], bool) and rel["appliedCondition"]["dy"]:
|
||||
liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1]))
|
||||
if (
|
||||
isinstance(rel["appliedCondition"]["dy"], bool)
|
||||
and rel["appliedCondition"]["dy"]
|
||||
):
|
||||
liaisons["coeffs"].append(
|
||||
(o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1])
|
||||
)
|
||||
liaisons["dofs"].append(("DX", "DY", "DZ", "DX", "DY", "DZ"))
|
||||
elif isinstance(rel["appliedCondition"]["dy"], float) and rel["appliedCondition"]["dy"] > 0:
|
||||
elif (
|
||||
isinstance(rel["appliedCondition"]["dy"], float)
|
||||
and rel["appliedCondition"]["dy"] > 0
|
||||
):
|
||||
stiffnesses[1] = rel["appliedCondition"]["dy"]
|
||||
|
||||
if isinstance(rel["appliedCondition"]["dz"], bool) and rel["appliedCondition"]["dz"]:
|
||||
liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2]))
|
||||
if (
|
||||
isinstance(rel["appliedCondition"]["dz"], bool)
|
||||
and rel["appliedCondition"]["dz"]
|
||||
):
|
||||
liaisons["coeffs"].append(
|
||||
(o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2])
|
||||
)
|
||||
liaisons["dofs"].append(("DX", "DY", "DZ", "DX", "DY", "DZ"))
|
||||
elif isinstance(rel["appliedCondition"]["dz"], float) and rel["appliedCondition"]["dz"] > 0:
|
||||
elif (
|
||||
isinstance(rel["appliedCondition"]["dz"], float)
|
||||
and rel["appliedCondition"]["dz"] > 0
|
||||
):
|
||||
stiffnesses[2] = rel["appliedCondition"]["dz"]
|
||||
|
||||
if isinstance(rel["appliedCondition"]["drx"], bool) and rel["appliedCondition"]["drx"]:
|
||||
liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0]))
|
||||
if (
|
||||
isinstance(rel["appliedCondition"]["drx"], bool)
|
||||
and rel["appliedCondition"]["drx"]
|
||||
):
|
||||
liaisons["coeffs"].append(
|
||||
(o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0])
|
||||
)
|
||||
liaisons["dofs"].append(("DRX", "DRY", "DRZ", "DRX", "DRY", "DRZ"))
|
||||
elif isinstance(rel["appliedCondition"]["drx"], float) and rel["appliedCondition"]["drx"] > 0:
|
||||
elif (
|
||||
isinstance(rel["appliedCondition"]["drx"], float)
|
||||
and rel["appliedCondition"]["drx"] > 0
|
||||
):
|
||||
stiffnesses[3] = rel["appliedCondition"]["drx"]
|
||||
|
||||
if isinstance(rel["appliedCondition"]["dry"], bool) and rel["appliedCondition"]["dry"]:
|
||||
liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1]))
|
||||
if (
|
||||
isinstance(rel["appliedCondition"]["dry"], bool)
|
||||
and rel["appliedCondition"]["dry"]
|
||||
):
|
||||
liaisons["coeffs"].append(
|
||||
(o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1])
|
||||
)
|
||||
liaisons["dofs"].append(("DRX", "DRY", "DRZ", "DRX", "DRY", "DRZ"))
|
||||
elif isinstance(rel["appliedCondition"]["dry"], float) and rel["appliedCondition"]["dry"] > 0:
|
||||
elif (
|
||||
isinstance(rel["appliedCondition"]["dry"], float)
|
||||
and rel["appliedCondition"]["dry"] > 0
|
||||
):
|
||||
stiffnesses[4] = rel["appliedCondition"]["dry"]
|
||||
|
||||
if isinstance(rel["appliedCondition"]["drz"], bool) and rel["appliedCondition"]["drz"]:
|
||||
liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2]))
|
||||
if (
|
||||
isinstance(rel["appliedCondition"]["drz"], bool)
|
||||
and rel["appliedCondition"]["drz"]
|
||||
):
|
||||
liaisons["coeffs"].append(
|
||||
(o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2])
|
||||
)
|
||||
liaisons["dofs"].append(("DRX", "DRY", "DRZ", "DRX", "DRY", "DRZ"))
|
||||
elif isinstance(rel["appliedCondition"]["drz"], float) and rel["appliedCondition"]["drz"] > 0:
|
||||
elif (
|
||||
isinstance(rel["appliedCondition"]["drz"], float)
|
||||
and rel["appliedCondition"]["drz"] > 0
|
||||
):
|
||||
stiffnesses[5] = rel["appliedCondition"]["drz"]
|
||||
|
||||
rel["liaisons"] = liaisons
|
||||
@@ -852,40 +977,76 @@ FIN()
|
||||
conn["stiffnesses"] = tuple(stiffnesses)
|
||||
return
|
||||
|
||||
if isinstance(conn["appliedCondition"]["dx"], bool) and conn["appliedCondition"]["dx"]:
|
||||
if (
|
||||
isinstance(conn["appliedCondition"]["dx"], bool)
|
||||
and conn["appliedCondition"]["dx"]
|
||||
):
|
||||
liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0]))
|
||||
liaisons["dofs"].append(("DX", "DY", "DZ"))
|
||||
elif isinstance(conn["appliedCondition"]["dx"], float) and conn["appliedCondition"]["dx"] > 0:
|
||||
elif (
|
||||
isinstance(conn["appliedCondition"]["dx"], float)
|
||||
and conn["appliedCondition"]["dx"] > 0
|
||||
):
|
||||
stiffnesses[0] = conn["appliedCondition"]["dx"]
|
||||
|
||||
if isinstance(conn["appliedCondition"]["dy"], bool) and conn["appliedCondition"]["dy"]:
|
||||
if (
|
||||
isinstance(conn["appliedCondition"]["dy"], bool)
|
||||
and conn["appliedCondition"]["dy"]
|
||||
):
|
||||
liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1]))
|
||||
liaisons["dofs"].append(("DX", "DY", "DZ"))
|
||||
elif isinstance(conn["appliedCondition"]["dy"], float) and conn["appliedCondition"]["dy"] > 0:
|
||||
elif (
|
||||
isinstance(conn["appliedCondition"]["dy"], float)
|
||||
and conn["appliedCondition"]["dy"] > 0
|
||||
):
|
||||
stiffnesses[1] = conn["appliedCondition"]["dy"]
|
||||
|
||||
if isinstance(conn["appliedCondition"]["dz"], bool) and conn["appliedCondition"]["dz"]:
|
||||
if (
|
||||
isinstance(conn["appliedCondition"]["dz"], bool)
|
||||
and conn["appliedCondition"]["dz"]
|
||||
):
|
||||
liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2]))
|
||||
liaisons["dofs"].append(("DX", "DY", "DZ"))
|
||||
elif isinstance(conn["appliedCondition"]["dz"], float) and conn["appliedCondition"]["dz"] > 0:
|
||||
elif (
|
||||
isinstance(conn["appliedCondition"]["dz"], float)
|
||||
and conn["appliedCondition"]["dz"] > 0
|
||||
):
|
||||
stiffnesses[2] = conn["appliedCondition"]["dz"]
|
||||
|
||||
if isinstance(conn["appliedCondition"]["drx"], bool) and conn["appliedCondition"]["drx"]:
|
||||
if (
|
||||
isinstance(conn["appliedCondition"]["drx"], bool)
|
||||
and conn["appliedCondition"]["drx"]
|
||||
):
|
||||
liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0]))
|
||||
liaisons["dofs"].append(("DRX", "DRY", "DRZ"))
|
||||
elif isinstance(conn["appliedCondition"]["drx"], float) and conn["appliedCondition"]["drx"] > 0:
|
||||
elif (
|
||||
isinstance(conn["appliedCondition"]["drx"], float)
|
||||
and conn["appliedCondition"]["drx"] > 0
|
||||
):
|
||||
stiffnesses[3] = conn["appliedCondition"]["drx"]
|
||||
|
||||
if isinstance(conn["appliedCondition"]["dry"], bool) and conn["appliedCondition"]["dry"]:
|
||||
if (
|
||||
isinstance(conn["appliedCondition"]["dry"], bool)
|
||||
and conn["appliedCondition"]["dry"]
|
||||
):
|
||||
liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1]))
|
||||
liaisons["dofs"].append(("DRX", "DRY", "DRZ"))
|
||||
elif isinstance(conn["appliedCondition"]["dry"], float) and conn["appliedCondition"]["dry"] > 0:
|
||||
elif (
|
||||
isinstance(conn["appliedCondition"]["dry"], float)
|
||||
and conn["appliedCondition"]["dry"] > 0
|
||||
):
|
||||
stiffnesses[4] = conn["appliedCondition"]["dry"]
|
||||
|
||||
if isinstance(conn["appliedCondition"]["drz"], bool) and conn["appliedCondition"]["drz"]:
|
||||
if (
|
||||
isinstance(conn["appliedCondition"]["drz"], bool)
|
||||
and conn["appliedCondition"]["drz"]
|
||||
):
|
||||
liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2]))
|
||||
liaisons["dofs"].append(("DRX", "DRY", "DRZ"))
|
||||
elif isinstance(conn["appliedCondition"]["drz"], float) and conn["appliedCondition"]["drz"] > 0:
|
||||
elif (
|
||||
isinstance(conn["appliedCondition"]["drz"], float)
|
||||
and conn["appliedCondition"]["drz"] > 0
|
||||
):
|
||||
stiffnesses[5] = conn["appliedCondition"]["drz"]
|
||||
|
||||
conn["liaisons"] = liaisons
|
||||
@@ -893,7 +1054,13 @@ FIN()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fileNames = ["cantilever_01", "portal_01", "grid_of_beams", "slab_01", "structure_01"]
|
||||
fileNames = [
|
||||
"cantilever_01",
|
||||
"portal_01",
|
||||
"grid_of_beams",
|
||||
"slab_01",
|
||||
"structure_01",
|
||||
]
|
||||
files = fileNames
|
||||
|
||||
for fileName in files:
|
||||
|
||||
Executable
+555
@@ -0,0 +1,555 @@
|
||||
import json
|
||||
import numpy as np
|
||||
import itertools
|
||||
|
||||
flatten = itertools.chain.from_iterable
|
||||
|
||||
ScaleFactor = 1.0
|
||||
|
||||
AccelOfGravity = 9.806 * 1000
|
||||
|
||||
class COMMANDFILE:
|
||||
def __init__(self, dataFilename, asterFilename):
|
||||
self.dataFilename = dataFilename
|
||||
self.asterFilename = asterFilename
|
||||
self.create()
|
||||
|
||||
def getGroupName(self, name):
|
||||
info = name.split("|")
|
||||
sortName = "".join(c for c in info[0] if c.isupper())
|
||||
return str(sortName + "_" + info[1])
|
||||
|
||||
def create(self):
|
||||
# Read data from input file
|
||||
with open(self.dataFilename) as dataFile:
|
||||
data = json.load(dataFile)
|
||||
|
||||
elements = data["elements"]
|
||||
connections = data["connections"]
|
||||
# --> Delete this reference data and repopulate it with the objects
|
||||
# while going through elements
|
||||
for conn in connections:
|
||||
conn["relatedElements"] = []
|
||||
for el in elements:
|
||||
for rel in el["connections"]:
|
||||
conn = [
|
||||
c for c in connections if c["ifcName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
conn["relatedElements"].append(rel)
|
||||
# End <--
|
||||
|
||||
materials = data["db"]["materials"]
|
||||
profiles = data["db"]["profiles"]
|
||||
|
||||
edgeGroupNames = tuple(
|
||||
[
|
||||
self.getGroupName(el["ifcName"])
|
||||
for el in elements
|
||||
if el["geometryType"] == "line"
|
||||
]
|
||||
)
|
||||
faceGroupNames = tuple(
|
||||
[
|
||||
self.getGroupName(el["ifcName"])
|
||||
for el in elements
|
||||
if el["geometryType"] == "surface"
|
||||
]
|
||||
)
|
||||
|
||||
rigidLinkGroupNames = []
|
||||
for conn in connections:
|
||||
rigidLinkGroupNames.extend(
|
||||
[
|
||||
self.getGroupName(rel["relatingElement"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(conn["ifcName"])
|
||||
for rel in conn["relatedElements"]
|
||||
if rel["eccentricity"]
|
||||
]
|
||||
)
|
||||
rigidLinkGroupNames = tuple(rigidLinkGroupNames)
|
||||
|
||||
# Define file to write command file for code_aster
|
||||
f = open(self.asterFilename, "w")
|
||||
|
||||
f.write("# Command file generated by IfcOpenShell/ifc2ca scripts\n")
|
||||
f.write("\n")
|
||||
|
||||
f.write("# Linear Static Analysis With Self-Weight\n")
|
||||
|
||||
f.write(
|
||||
"""
|
||||
# STEP: INITIALIZE STUDY
|
||||
DEBUT(
|
||||
PAR_LOT = 'NON'
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
f.write(
|
||||
"""
|
||||
# STEP: READ MED FILE
|
||||
mesh = LIRE_MAILLAGE(
|
||||
FORMAT = 'MED',
|
||||
UNITE = 20
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
f.write(
|
||||
"""
|
||||
# STEP: DEFINE MODEL
|
||||
model = AFFE_MODELE(
|
||||
MAILLAGE = mesh,
|
||||
AFFE = (
|
||||
_F(
|
||||
TOUT = 'OUI',
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = '3D'
|
||||
),"""
|
||||
)
|
||||
|
||||
if faceGroupNames:
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = 'DKT'
|
||||
),"""
|
||||
|
||||
context = {"groupNames": faceGroupNames}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
if edgeGroupNames:
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = 'POU_D_E'
|
||||
),"""
|
||||
|
||||
context = {"groupNames": edgeGroupNames}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
if rigidLinkGroupNames:
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
PHENOMENE = 'MECANIQUE',
|
||||
MODELISATION = 'POU_D_E'
|
||||
),"""
|
||||
|
||||
context = {"groupNames": rigidLinkGroupNames}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
f.write(
|
||||
"""
|
||||
)
|
||||
)\n
|
||||
"""
|
||||
)
|
||||
|
||||
f.write("# STEP: DEFINE MATERIALS")
|
||||
|
||||
for i, material in enumerate(materials):
|
||||
template = """
|
||||
{matNameID} = DEFI_MATERIAU(
|
||||
ELAS = _F(
|
||||
E = {youngModulus},
|
||||
NU = {poissonRatio},
|
||||
RHO = {massDensity}
|
||||
)
|
||||
)
|
||||
"""
|
||||
if "poissonRatio" in material["mechProps"]:
|
||||
poissonRatio = material["mechProps"]["poissonRatio"]
|
||||
else:
|
||||
if "shearModulus" in material["mechProps"]:
|
||||
poissonRatio = (
|
||||
material["mechProps"]["youngModulus"]
|
||||
/ 2.0
|
||||
/ material["mechProps"]["shearModulus"]
|
||||
) - 1
|
||||
else:
|
||||
poissonRatio = 0.0
|
||||
|
||||
context = {
|
||||
"matNameID": "mat" + "_%s" % i,
|
||||
"youngModulus": float(material["mechProps"]["youngModulus"])
|
||||
* ScaleFactor ** 2,
|
||||
"poissonRatio": float(poissonRatio),
|
||||
"massDensity": float(material["commonProps"]["massDensity"])
|
||||
* ScaleFactor ** 3,
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
f.write(
|
||||
"""
|
||||
material = AFFE_MATERIAU(
|
||||
MAILLAGE = mesh,
|
||||
AFFE = ("""
|
||||
)
|
||||
|
||||
for i, material in enumerate(materials):
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
MATER = {matNameID},
|
||||
),"""
|
||||
|
||||
context = {
|
||||
"groupNames": tuple(
|
||||
[self.getGroupName(rel) for rel in material["relatedElements"]]
|
||||
),
|
||||
"matNameID": "mat" + "_%s" % i,
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
if rigidLinkGroupNames:
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
MATER = {matNameID},
|
||||
),"""
|
||||
|
||||
context = {"groupNames": rigidLinkGroupNames, "matNameID": "mat_0"}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
f.write(
|
||||
"""
|
||||
)
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
f.write(
|
||||
"""
|
||||
# STEP: DEFINE ELEMENTS
|
||||
element = AFFE_CARA_ELEM(
|
||||
MODELE = model,
|
||||
POUTRE = ("""
|
||||
)
|
||||
|
||||
for profile in profiles:
|
||||
if (
|
||||
profile["profileShape"] == "rectangular"
|
||||
and profile["profileType"] == "AREA"
|
||||
):
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
SECTION = 'RECTANGLE',
|
||||
CARA = ('HY', 'HZ'),
|
||||
VALE = {profileDimensions}
|
||||
),"""
|
||||
|
||||
context = {
|
||||
"groupNames": tuple(
|
||||
[self.getGroupName(rel) for rel in profile["relatedElements"]]
|
||||
),
|
||||
"profileDimensions": (
|
||||
profile["xDim"] / ScaleFactor,
|
||||
profile["yDim"] / ScaleFactor,
|
||||
),
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
elif (
|
||||
profile["profileShape"] == "iSymmetrical"
|
||||
and profile["profileType"] == "AREA"
|
||||
):
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
SECTION = 'GENERALE',
|
||||
CARA = ('A', 'IY', 'IZ', 'JX'),
|
||||
VALE = {profileProperties}
|
||||
),"""
|
||||
|
||||
context = {
|
||||
"groupNames": tuple(
|
||||
[self.getGroupName(rel) for rel in profile["relatedElements"]]
|
||||
),
|
||||
"profileProperties": (
|
||||
profile["mechProps"]["crossSectionArea"] / ScaleFactor ** 2,
|
||||
profile["mechProps"]["momentOfInertiaY"] / ScaleFactor ** 4,
|
||||
profile["mechProps"]["momentOfInertiaZ"] / ScaleFactor ** 4,
|
||||
profile["mechProps"]["torsionalConstantX"] / ScaleFactor ** 4,
|
||||
),
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
if rigidLinkGroupNames:
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = {groupNames},
|
||||
SECTION = 'RECTANGLE',
|
||||
CARA = ('HY', 'HZ'),
|
||||
VALE = {profileDimensions}
|
||||
),"""
|
||||
|
||||
context = {"groupNames": rigidLinkGroupNames, "profileDimensions": (1, 1)}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
f.write(
|
||||
"""
|
||||
),
|
||||
COQUE = ("""
|
||||
)
|
||||
|
||||
for el in [el for el in elements if el["geometryType"] == "surface"]:
|
||||
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = '{groupName}',
|
||||
EPAIS = {thickness},
|
||||
VECTEUR = {localAxisX}
|
||||
),"""
|
||||
|
||||
context = {
|
||||
"groupName": self.getGroupName(el["ifcName"]),
|
||||
"thickness": el["thickness"] / ScaleFactor,
|
||||
"localAxisX": tuple(el["orientation"][0]),
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
f.write(
|
||||
"""
|
||||
),"""
|
||||
)
|
||||
|
||||
f.write(
|
||||
"""
|
||||
ORIENTATION = ("""
|
||||
)
|
||||
|
||||
for el in [el for el in elements if el["geometryType"] == "line"]:
|
||||
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = '{groupName}',
|
||||
CARA = 'VECT_Y',
|
||||
VALE = {localAxisY}
|
||||
),"""
|
||||
|
||||
context = {
|
||||
"groupName": self.getGroupName(el["ifcName"]),
|
||||
"localAxisY": tuple(el["orientation"][1]),
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
f.write(
|
||||
"""
|
||||
),"""
|
||||
)
|
||||
|
||||
f.write(
|
||||
"""
|
||||
)\n
|
||||
"""
|
||||
)
|
||||
|
||||
f.write("# STEP: DEFINE SUPPORTS AND CONSTRAINTS")
|
||||
|
||||
f.write(
|
||||
"""
|
||||
liaisons = AFFE_CHAR_MECA(
|
||||
MODELE = model,
|
||||
DDL_IMPO = (
|
||||
_F(
|
||||
GROUP_NO = 'grdSupps',
|
||||
DX = 0.0,
|
||||
DY = 0.0,
|
||||
DZ = 0.0,
|
||||
DRX = 0.0,
|
||||
DRY = 0.0,
|
||||
DRZ = 0.0
|
||||
)
|
||||
),"""
|
||||
)
|
||||
|
||||
if rigidLinkGroupNames:
|
||||
f.write(
|
||||
"""
|
||||
LIAISON_SOLIDE = ("""
|
||||
)
|
||||
|
||||
for groupName in rigidLinkGroupNames:
|
||||
template = """
|
||||
_F(
|
||||
GROUP_MA = '{groupName}'
|
||||
),"""
|
||||
|
||||
context = {"groupName": groupName}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
f.write(
|
||||
"""
|
||||
),"""
|
||||
)
|
||||
|
||||
f.write(
|
||||
"""
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
template = """
|
||||
# STEP: DEFINE LOAD
|
||||
gravLoad = AFFE_CHAR_MECA(
|
||||
MODELE = model,
|
||||
PESANTEUR = _F(
|
||||
GRAVITE = {AccelOfGravity},
|
||||
DIRECTION = (0.0, 0.0, -1.0)
|
||||
)
|
||||
)
|
||||
"""
|
||||
context = {
|
||||
"AccelOfGravity": AccelOfGravity,
|
||||
}
|
||||
|
||||
f.write(template.format(**context))
|
||||
|
||||
f.write(
|
||||
"""
|
||||
# STEP: RUN ANALYSIS
|
||||
res_Bld = MECA_STATIQUE(
|
||||
MODELE = model,
|
||||
CHAM_MATER = material,
|
||||
CARA_ELEM = element,
|
||||
EXCIT = (
|
||||
_F(
|
||||
CHARGE = liaisons
|
||||
),
|
||||
_F(
|
||||
CHARGE = gravLoad
|
||||
)
|
||||
)
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# f.write(
|
||||
# '''
|
||||
# # STEP: POST-PROCESSING
|
||||
# res_Bld = CALC_CHAMP(
|
||||
# reuse = res_Bld,
|
||||
# RESULTAT = res_Bld,
|
||||
# # CONTRAINTE = ('SIEF_ELNO', 'SIGM_ELNO', 'EFGE_ELNO',),
|
||||
# FORCE = ('REAC_NODA', 'FORC_NODA',)
|
||||
# )
|
||||
# '''
|
||||
# )
|
||||
#
|
||||
# template = \
|
||||
# '''
|
||||
# # STEP: MASS EXTRACTION FOR EACH ASSEMBLE
|
||||
# FaceMass = POST_ELEM(
|
||||
# TITRE = 'TotMass',
|
||||
# MODELE = model,
|
||||
# CARA_ELEM = element,
|
||||
# CHAM_MATER = material,
|
||||
# MASS_INER = _F(
|
||||
# GROUP_MA = {massList},
|
||||
# ),
|
||||
# )\n'''
|
||||
#
|
||||
# context = {
|
||||
# 'massList': massList,
|
||||
# }
|
||||
#
|
||||
# f.write(template.format(**context))
|
||||
#
|
||||
# f.write(
|
||||
# '''
|
||||
# IMPR_TABLE(
|
||||
# UNITE = 10,
|
||||
# TABLE = FaceMass,
|
||||
# SEPARATEUR = ',',
|
||||
# NOM_PARA = ('LIEU', 'MASSE', 'CDG_X', 'CDG_Y', 'CDG_Z'),
|
||||
# # FORMAT_R = '1PE15.6',
|
||||
# )
|
||||
# '''
|
||||
# )
|
||||
#
|
||||
# template = \
|
||||
# '''
|
||||
# # STEP: REACTION EXTRACTION AT THE BASE
|
||||
# Reacs = POST_RELEVE_T(
|
||||
# ACTION = _F(
|
||||
# INTITULE = 'sumReac',
|
||||
# GROUP_NO = {groupNames},
|
||||
# RESULTAT = res_Bld,
|
||||
# NOM_CHAM = 'REAC_NODA',
|
||||
# RESULTANTE = ('DX','DY','DZ',),
|
||||
# MOMENT = ('DRX','DRY','DRZ',),
|
||||
# POINT = (0,0,0,),
|
||||
# OPERATION = 'EXTRACTION'
|
||||
# )
|
||||
# )
|
||||
# '''
|
||||
#
|
||||
# context = {
|
||||
# 'groupNames': point0DGroupNames,
|
||||
# }
|
||||
#
|
||||
# f.write(template.format(**context))
|
||||
#
|
||||
# f.write(
|
||||
# '''
|
||||
# IMPR_TABLE(
|
||||
# UNITE = 10,
|
||||
# TABLE = Reacs,
|
||||
# SEPARATEUR = ',',
|
||||
# # NOM_PARA = ('INTITULE', 'RESU', 'NOM_CHAM', 'INST', 'DX','DY','DZ'),
|
||||
# FORMAT_R = '1PE12.3',
|
||||
# )
|
||||
# '''
|
||||
# )
|
||||
#
|
||||
f.write(
|
||||
"""
|
||||
# STEP: DEFORMED SHAPE EXTRACTION
|
||||
IMPR_RESU(
|
||||
FORMAT = 'MED',
|
||||
UNITE = 80,
|
||||
RESU = _F(
|
||||
RESULTAT = res_Bld,
|
||||
NOM_CHAM = ('DEPL',), # 'REAC_NODA', 'FORC_NODA',
|
||||
NOM_CHAM_MED = ('Bld_DISP',), # 'Bld_REAC', 'Bld_FORC'
|
||||
)
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
f.write(
|
||||
"""
|
||||
# STEP: CONCLUDE STUDY
|
||||
FIN()
|
||||
"""
|
||||
)
|
||||
|
||||
f.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fileNames = ["building_02"]
|
||||
files = fileNames
|
||||
|
||||
for fileName in files:
|
||||
BASE_PATH = "/home/jesusbill/Dev-Projects/github.com/IfcOpenShell/analysis-models/models/"
|
||||
DATAFILENAME = BASE_PATH + fileName + "/" + fileName + ".json"
|
||||
ASTERFILENAME = BASE_PATH + fileName + "/" + fileName + ".comm"
|
||||
COMMANDFILE(DATAFILENAME, ASTERFILENAME)
|
||||
+127
-40
@@ -75,7 +75,9 @@ class MODEL:
|
||||
shapeType = "EDGE"
|
||||
if geometryType == "surface":
|
||||
shapeType = "FACE"
|
||||
return self.geompy.MakePartition(objects, [], [], [], self.geompy.ShapeType[shapeType], 0, [], 1)
|
||||
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"])
|
||||
@@ -157,17 +159,25 @@ class MODEL:
|
||||
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["ifcName"] == rel["relatedConnection"]][0]
|
||||
conn = [
|
||||
c for c in connections if c["ifcName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
if rel["eccentricity"]:
|
||||
rel["index"] = len(conn["relatedElements"]) + 1
|
||||
conn["relatedElements"].append(rel)
|
||||
|
||||
if not rel["eccentricity"]:
|
||||
el["connObjs"][j] = self.makeObject(conn["geometry"], conn["geometryType"])
|
||||
el["connObjs"][j] = self.makeObject(
|
||||
conn["geometry"], conn["geometryType"]
|
||||
)
|
||||
else:
|
||||
if conn["geometryType"] == "point":
|
||||
geometry = self.getLinkGeometry(rel["eccentricity"], el["orientation"], conn["geometry"])
|
||||
el["connObjs"][j] = self.makeObject(geometry[0], conn["geometryType"])
|
||||
geometry = self.getLinkGeometry(
|
||||
rel["eccentricity"], el["orientation"], conn["geometry"]
|
||||
)
|
||||
el["connObjs"][j] = self.makeObject(
|
||||
geometry[0], conn["geometryType"]
|
||||
)
|
||||
|
||||
el["linkPointObjs"][j][0] = self.geompy.MakeVertex(
|
||||
geometry[0][0], geometry[0][1], geometry[0][2]
|
||||
@@ -179,9 +189,14 @@ class MODEL:
|
||||
el["linkPointObjs"][j][0], el["linkPointObjs"][j][1]
|
||||
)
|
||||
else:
|
||||
print("Eccentricity defined for a %s geometryType" % conn["geometryType"])
|
||||
print(
|
||||
"Eccentricity defined for a %s geometryType"
|
||||
% conn["geometryType"]
|
||||
)
|
||||
|
||||
el["partObj"] = self.makePartition([el["elemObj"]] + el["connObjs"], el["geometryType"])
|
||||
el["partObj"] = self.makePartition(
|
||||
[el["elemObj"]] + el["connObjs"], el["geometryType"]
|
||||
)
|
||||
|
||||
el["elemObj"] = geompy.GetInPlace(el["partObj"], el["elemObj"])
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
@@ -193,7 +208,9 @@ class MODEL:
|
||||
# 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)
|
||||
@@ -203,9 +220,13 @@ class MODEL:
|
||||
# Loop 2
|
||||
for el in elements:
|
||||
# geompy.addToStudy(el['partObj'], self.getGroupName(el['ifcName']))
|
||||
geompy.addToStudyInFather(el["partObj"], el["elemObj"], self.getGroupName(el["ifcName"]))
|
||||
geompy.addToStudyInFather(
|
||||
el["partObj"], el["elemObj"], self.getGroupName(el["ifcName"])
|
||||
)
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
|
||||
conn = [
|
||||
c for c in connections if c["ifcName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
rel["conn_string"] = None
|
||||
if conn["geometryType"] == "point":
|
||||
rel["conn_string"] = "_0DC_"
|
||||
@@ -216,7 +237,9 @@ class MODEL:
|
||||
geompy.addToStudyInFather(
|
||||
el["partObj"],
|
||||
el["connObjs"][j],
|
||||
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ rel["conn_string"]
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
if rel["eccentricity"]:
|
||||
pass
|
||||
@@ -226,7 +249,9 @@ class MODEL:
|
||||
|
||||
for conn in connections:
|
||||
# geompy.addToStudy(conn['connObj'], self.getGroupName(conn['ifcName']))
|
||||
geompy.addToStudyInFather(conn["connObj"], conn["connObj"], self.getGroupName(conn["ifcName"]))
|
||||
geompy.addToStudyInFather(
|
||||
conn["connObj"], conn["connObj"], self.getGroupName(conn["ifcName"])
|
||||
)
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
@@ -240,14 +265,18 @@ class MODEL:
|
||||
# Define and add groups for all curve and surface members
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 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["geometryType"] == "line"]
|
||||
)
|
||||
# Define group object and add to study
|
||||
curveCompound = geompy.GetInPlace(bldComp, compoundTemp)
|
||||
geompy.addToStudyInFather(bldComp, curveCompound, "CurveMembers")
|
||||
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
# Make compound of requested group
|
||||
compoundTemp = geompy.MakeCompound([e["elemObj"] for e in elements if e["geometryType"] == "surface"])
|
||||
compoundTemp = geompy.MakeCompound(
|
||||
[e["elemObj"] for e in elements if e["geometryType"] == "surface"]
|
||||
)
|
||||
# Define group object and add to study
|
||||
surfaceCompound = geompy.GetInPlace(bldComp, compoundTemp)
|
||||
geompy.addToStudyInFather(bldComp, surfaceCompound, "SurfaceMembers")
|
||||
@@ -255,34 +284,45 @@ 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["ifcName"]))
|
||||
geompy.addToStudyInFather(
|
||||
bldComp, el["elemObj"], self.getGroupName(el["ifcName"])
|
||||
)
|
||||
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["connObjs"][j],
|
||||
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ rel["conn_string"]
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
if rel["eccentricity"]: # point geometry
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkObjs"][j],
|
||||
self.getGroupName(el["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkPointObjs"][j][0],
|
||||
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_"
|
||||
+ self.getGroupName(el["ifcName"]),
|
||||
)
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkPointObjs"][j][1],
|
||||
self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"],
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_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["ifcName"]))
|
||||
geompy.addToStudyInFather(
|
||||
bldComp, conn["connObj"], self.getGroupName(conn["ifcName"])
|
||||
)
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
@@ -339,7 +379,9 @@ class MODEL:
|
||||
smesh.SetName(tempgroup, "CurveMembers")
|
||||
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(surfaceCompound, "SurfaceMembers", SMESH.FACE)
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
surfaceCompound, "SurfaceMembers", SMESH.FACE
|
||||
)
|
||||
smesh.SetName(tempgroup, "SurfaceMembers")
|
||||
|
||||
# Define groups in Mesh
|
||||
@@ -348,41 +390,59 @@ class MODEL:
|
||||
shapeType = SMESH.EDGE
|
||||
if el["geometryType"] == "surface":
|
||||
shapeType = SMESH.FACE
|
||||
tempgroup = bldMesh.GroupOnGeom(el["elemObj"], self.getGroupName(el["ifcName"]), shapeType)
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["elemObj"], self.getGroupName(el["ifcName"]), shapeType
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(el["ifcName"]))
|
||||
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["connObjs"][j],
|
||||
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ rel["conn_string"]
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
SMESH.NODE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ rel["conn_string"]
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
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["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
SMESH.EDGE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(el["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["linkPointObjs"][j][0],
|
||||
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_"
|
||||
+ self.getGroupName(el["ifcName"]),
|
||||
SMESH.NODE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_"
|
||||
+ self.getGroupName(el["ifcName"]),
|
||||
)
|
||||
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],
|
||||
@@ -391,38 +451,60 @@ class MODEL:
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
SMESH.NODE,
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"])
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_%g" % rel["index"],
|
||||
)
|
||||
|
||||
for conn in connections:
|
||||
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.NODE)
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.NODE
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"]))
|
||||
nodesId = bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
|
||||
tempgroup = bldMesh.Add0DElementsToAllNodes(nodesId, self.getGroupName(conn["ifcName"]))
|
||||
tempgroup = bldMesh.Add0DElementsToAllNodes(
|
||||
nodesId, self.getGroupName(conn["ifcName"])
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"] + "_0D"))
|
||||
if conn["geometryType"] == "point":
|
||||
conn["node"] = nodesId.GetIDs()[0]
|
||||
if conn["geometryType"] == "line":
|
||||
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.EDGE)
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.EDGE
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"]))
|
||||
if conn["geometryType"] == "surface":
|
||||
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.FACE)
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.FACE
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"]))
|
||||
|
||||
# create 1D SEG2 spring elements
|
||||
for el in elements:
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
|
||||
conn = [
|
||||
c for c in connections if c["ifcName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
if conn["geometryType"] == "point":
|
||||
grpName = bldMesh.CreateEmptyGroup(
|
||||
SMESH.EDGE,
|
||||
self.getGroupName(el["ifcName"]) + "_1DS_" + self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ "_1DS_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
smesh.SetName(
|
||||
grpName,
|
||||
self.getGroupName(el["ifcName"]) + "_1DS_" + self.getGroupName(rel["relatedConnection"]),
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ "_1DS_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
if not rel["eccentricity"]:
|
||||
conn = [conn for conn in connections if conn["ifcName"] == rel["relatedConnection"]][0]
|
||||
conn = [
|
||||
conn
|
||||
for conn in connections
|
||||
if conn["ifcName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
grpName.Add([bldMesh.AddEdge([conn["node"], rel["node"]])])
|
||||
else:
|
||||
grpName.Add([bldMesh.AddEdge([rel["eccNode"], rel["node"]])])
|
||||
@@ -437,7 +519,12 @@ class MODEL:
|
||||
try:
|
||||
if NEW_SALOME:
|
||||
bldMesh.ExportMED(
|
||||
self.medFilename, auto_groups=0, minor=40, overwrite=1, meshPart=None, autoDimension=0
|
||||
self.medFilename,
|
||||
auto_groups=0,
|
||||
minor=40,
|
||||
overwrite=1,
|
||||
meshPart=None,
|
||||
autoDimension=0,
|
||||
)
|
||||
else:
|
||||
bldMesh.ExportMED(self.medFilename, 0, SMESH.MED_V2_2, 1, None, 0)
|
||||
|
||||
Executable
+415
@@ -0,0 +1,415 @@
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import salome
|
||||
import salome_notebook
|
||||
import salome_version
|
||||
import numpy as np
|
||||
import itertools
|
||||
|
||||
flatten = itertools.chain.from_iterable
|
||||
|
||||
ScaleFactor = 1.0
|
||||
decimals = 2
|
||||
|
||||
|
||||
class MODEL:
|
||||
def __init__(self, dataFilename, medFilename, meshSize, zGround):
|
||||
self.dataFilename = dataFilename
|
||||
self.medFilename = medFilename
|
||||
self.meshSize = meshSize
|
||||
self.zGround = zGround
|
||||
self.tolLoc = 0
|
||||
self.mesh = None
|
||||
self.meshNodes = None
|
||||
self.create()
|
||||
|
||||
def getGroupName(self, name):
|
||||
info = name.split("|")
|
||||
sortName = "".join(c for c in info[0] if c.isupper())
|
||||
return str(sortName + "_" + info[1])
|
||||
|
||||
def makePoint(self, pl):
|
||||
"""Function to define a Point from
|
||||
a polyline (list of 1 point)"""
|
||||
|
||||
(x, y, z) = pl
|
||||
return self.geompy.MakeVertex(x, y, z)
|
||||
|
||||
def makeLine(self, pl):
|
||||
"""Function to define a Line from
|
||||
a polyline (list of 2 points)"""
|
||||
|
||||
(x, y, z) = pl[0]
|
||||
P1 = self.geompy.MakeVertex(x, y, z)
|
||||
(x, y, z) = pl[1]
|
||||
P2 = self.geompy.MakeVertex(x, y, z)
|
||||
|
||||
return self.geompy.MakeLineTwoPnt(P1, P2)
|
||||
|
||||
def makeFace(self, pl):
|
||||
"""Function to define a Face from
|
||||
a polyline (list of points)"""
|
||||
|
||||
pointList = [None for _ in range(len(pl))]
|
||||
for ip, (x, y, z) in enumerate(pl):
|
||||
pointList[ip] = self.geompy.MakeVertex(x, y, z)
|
||||
|
||||
LineList = [None for _ in range(len(pl))]
|
||||
for ip, P2 in enumerate(pointList):
|
||||
P1 = pointList[ip - 1]
|
||||
LineList[ip] = self.geompy.MakeLineTwoPnt(P1, P2)
|
||||
|
||||
return self.geompy.MakeFaceWires(LineList, 1)
|
||||
|
||||
def makeObject(self, geometry, geometryType):
|
||||
geometry = np.round(np.array(geometry), decimals=decimals) / ScaleFactor
|
||||
geometry = geometry.tolist()
|
||||
if geometryType == "point":
|
||||
return self.makePoint(geometry)
|
||||
if geometryType == "line":
|
||||
return self.makeLine(geometry)
|
||||
if geometryType == "surface":
|
||||
return self.makeFace(geometry)
|
||||
|
||||
def makePartition(self, objects, geometryType):
|
||||
if geometryType == "point":
|
||||
shapeType = "VERTEX"
|
||||
if geometryType == "line":
|
||||
shapeType = "EDGE"
|
||||
if geometryType == "surface":
|
||||
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]
|
||||
|
||||
def length(self, geometry):
|
||||
return (
|
||||
(geometry[1][0] - geometry[0][0]) ** 2
|
||||
+ (geometry[1][1] - geometry[0][1]) ** 2
|
||||
+ (geometry[1][2] - geometry[0][2]) ** 2
|
||||
) ** 0.5
|
||||
|
||||
def select(self, elements):
|
||||
zmin = -100
|
||||
zmax = 126300
|
||||
for el in elements:
|
||||
include = True
|
||||
for p in el["geometry"]:
|
||||
if p[2] < zmin or p[2] > zmax:
|
||||
include = False
|
||||
break
|
||||
el["include"] = include
|
||||
return [el for el in elements if el["include"]]
|
||||
|
||||
def create(self):
|
||||
# Read data from input file
|
||||
with open(self.dataFilename) as dataFile:
|
||||
data = json.load(dataFile)
|
||||
|
||||
# print(len(data['elements']))
|
||||
# elements = self.select(data['elements'])
|
||||
# print(len(elements))
|
||||
elements = data["elements"]
|
||||
connections = data["connections"]
|
||||
# --> Delete this reference data and repopulate it with the objects
|
||||
# while going through elements
|
||||
for conn in connections:
|
||||
conn["relatedElements"] = []
|
||||
# End <--
|
||||
|
||||
meshSize = self.meshSize / ScaleFactor
|
||||
zGround = self.zGround / ScaleFactor
|
||||
|
||||
dec = 5 # 4 decimals for length in mm
|
||||
tol = 10 ** (-dec - 3 + 1)
|
||||
|
||||
self.tolLoc = tol * 10 * 2
|
||||
tolLoc = self.tolLoc
|
||||
|
||||
NEW_SALOME = int(salome_version.getVersion()[0]) >= 9
|
||||
salome.salome_init()
|
||||
theStudy = salome.myStudy
|
||||
notebook = salome_notebook.NoteBook(theStudy)
|
||||
|
||||
###
|
||||
### GEOM component
|
||||
###
|
||||
import GEOM
|
||||
from salome.geom import geomBuilder
|
||||
import math
|
||||
import SALOMEDS
|
||||
|
||||
gg = salome.ImportComponentGUI("GEOM")
|
||||
if NEW_SALOME:
|
||||
geompy = geomBuilder.New()
|
||||
else:
|
||||
geompy = geomBuilder.New(theStudy)
|
||||
self.geompy = geompy
|
||||
|
||||
O = geompy.MakeVertex(0, 0, 0)
|
||||
OX = geompy.MakeVectorDXDYDZ(1, 0, 0)
|
||||
OY = geompy.MakeVectorDXDYDZ(0, 1, 0)
|
||||
OZ = geompy.MakeVectorDXDYDZ(0, 0, 1)
|
||||
geompy.addToStudy(O, "O")
|
||||
geompy.addToStudy(OX, "OX")
|
||||
geompy.addToStudy(OY, "OY")
|
||||
geompy.addToStudy(OZ, "OZ")
|
||||
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
buildingShapeType = "EDGE"
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
buildingShapeType = "FACE"
|
||||
|
||||
### Define entities ###
|
||||
start_time = time.time()
|
||||
print("Defining Object Geometry")
|
||||
init_time = start_time
|
||||
|
||||
# Loop 1
|
||||
for el in elements:
|
||||
el["elemObj"] = self.makeObject(el["geometry"], el["geometryType"])
|
||||
|
||||
el["linkObjs"] = [None for _ in el["connections"]]
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
conn = [
|
||||
c for c in connections if c["ifcName"] == rel["relatedConnection"]
|
||||
][0]
|
||||
if rel["eccentricity"]:
|
||||
rel["index"] = len(conn["relatedElements"]) + 1
|
||||
|
||||
geometry = self.getLinkGeometry(
|
||||
rel["eccentricity"], el["orientation"], conn["geometry"]
|
||||
)
|
||||
el["linkObjs"][j] = self.makeObject(geometry, "line")
|
||||
conn["relatedElements"].append(rel)
|
||||
|
||||
# Make assemble of Building Object
|
||||
bldObjs = []
|
||||
bldObjs.extend([el["elemObj"] for el in elements])
|
||||
bldObjs.extend(
|
||||
flatten([[link for link in el["linkObjs"] if link] for el in elements])
|
||||
)
|
||||
|
||||
# bldComp = geompy.MakeCompound(bldObjs)
|
||||
bldComp = geompy.MakePartition(
|
||||
bldObjs, [], [], [], self.geompy.ShapeType[buildingShapeType], 0, [], 1
|
||||
)
|
||||
geompy.addToStudy(bldComp, "bldComp")
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
print("Building Geometry Defined in %g sec" % (elapsed_time))
|
||||
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
buildingShapeType = "EDGE"
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
buildingShapeType = "FACE"
|
||||
|
||||
# Define and add groups for all curve, surface and rigid members
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
# Make compound of requested group
|
||||
compoundTemp = geompy.MakeCompound(
|
||||
[e["elemObj"] for e in elements if e["geometryType"] == "line"]
|
||||
)
|
||||
# Define group object and add to study
|
||||
curveCompound = geompy.GetInPlace(bldComp, compoundTemp)
|
||||
geompy.addToStudyInFather(bldComp, curveCompound, "CurveMembers")
|
||||
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
# Make compound of requested group
|
||||
compoundTemp = geompy.MakeCompound(
|
||||
[e["elemObj"] for e in elements if e["geometryType"] == "surface"]
|
||||
)
|
||||
# Define group object and add to study
|
||||
surfaceCompound = geompy.GetInPlace(bldComp, compoundTemp)
|
||||
geompy.addToStudyInFather(bldComp, surfaceCompound, "SurfaceMembers")
|
||||
|
||||
linkObjs = list(
|
||||
flatten([[obj for obj in el["linkObjs"] if obj] for el in elements])
|
||||
)
|
||||
if len(linkObjs) > 0:
|
||||
# Make compound of requested group
|
||||
compoundTemp = geompy.MakeCompound(linkObjs)
|
||||
# Define group object and add to study
|
||||
rigidCompound = geompy.GetInPlace(bldComp, compoundTemp)
|
||||
geompy.addToStudyInFather(bldComp, rigidCompound, "RigidMembers")
|
||||
|
||||
for el in elements:
|
||||
# el['partObj'] = geompy.RestoreGivenSubShapes(bldComp, [el['partObj']], GEOM.FSM_GetInPlace, False, False)[0]
|
||||
el["elemObj"] = geompy.GetInPlace(bldComp, el["elemObj"])
|
||||
geompy.addToStudyInFather(
|
||||
bldComp, el["elemObj"], self.getGroupName(el["ifcName"])
|
||||
)
|
||||
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
if rel["eccentricity"]: # point geometry
|
||||
el["linkObjs"][j] = geompy.GetInPlace(bldComp, el["linkObjs"][j])
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkObjs"][j],
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
print("Building Geometry Groups Defined in %g sec" % (elapsed_time))
|
||||
|
||||
###
|
||||
### SMESH component
|
||||
###
|
||||
|
||||
import SMESH
|
||||
from salome.smesh import smeshBuilder
|
||||
|
||||
print("Defining Mesh Components")
|
||||
|
||||
if NEW_SALOME:
|
||||
smesh = smeshBuilder.New()
|
||||
else:
|
||||
smesh = smeshBuilder.New(theStudy)
|
||||
bldMesh = smesh.Mesh(bldComp)
|
||||
Regular_1D = bldMesh.Segment()
|
||||
Local_Length_1 = Regular_1D.LocalLength(meshSize, None, tolLoc)
|
||||
|
||||
if buildingShapeType == "FACE":
|
||||
NETGEN2D_ONLY = bldMesh.Triangle(algo=smeshBuilder.NETGEN_2D)
|
||||
NETGEN2D_Pars = NETGEN2D_ONLY.Parameters()
|
||||
NETGEN2D_Pars.SetMaxSize(meshSize)
|
||||
NETGEN2D_Pars.SetOptimize(1)
|
||||
NETGEN2D_Pars.SetFineness(2)
|
||||
NETGEN2D_Pars.SetMinSize(meshSize / 5.0)
|
||||
NETGEN2D_Pars.SetUseSurfaceCurvature(1)
|
||||
NETGEN2D_Pars.SetQuadAllowed(1)
|
||||
NETGEN2D_Pars.SetSecondOrder(0)
|
||||
NETGEN2D_Pars.SetFuseEdges(254)
|
||||
|
||||
isDone = bldMesh.Compute()
|
||||
coincident_nodes_on_part = bldMesh.FindCoincidentNodesOnPart(
|
||||
[bldMesh], tolLoc, [], 0
|
||||
)
|
||||
if coincident_nodes_on_part:
|
||||
# bldMesh.MergeNodes(coincident_nodes_on_part, [], 0)
|
||||
# print(f'{len(coincident_nodes_on_part)} Sets of Coincident Nodes Found and Merged')
|
||||
print(f"{len(coincident_nodes_on_part)} Sets of Coincident Nodes Found")
|
||||
print(f"{coincident_nodes_on_part}")
|
||||
|
||||
## Set names of Mesh objects
|
||||
smesh.SetName(Regular_1D.GetAlgorithm(), "Regular_1D")
|
||||
smesh.SetName(Local_Length_1, "Local_Length_1")
|
||||
|
||||
if buildingShapeType == "FACE":
|
||||
smesh.SetName(NETGEN2D_ONLY.GetAlgorithm(), "NETGEN2D_ONLY")
|
||||
smesh.SetName(NETGEN2D_Pars, "NETGEN2D_Pars")
|
||||
|
||||
smesh.SetName(bldMesh.GetMesh(), "bldMesh")
|
||||
|
||||
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, surface and rigid members
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(curveCompound, "CurveMembers", SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, "CurveMembers")
|
||||
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
surfaceCompound, "SurfaceMembers", SMESH.FACE
|
||||
)
|
||||
smesh.SetName(tempgroup, "SurfaceMembers")
|
||||
|
||||
if len(linkObjs) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(rigidCompound, "RigidMembers", SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, "RigidMembers")
|
||||
|
||||
# Define groups in Mesh
|
||||
for el in elements:
|
||||
if el["geometryType"] == "line":
|
||||
shapeType = SMESH.EDGE
|
||||
if el["geometryType"] == "surface":
|
||||
shapeType = SMESH.FACE
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["elemObj"], self.getGroupName(el["ifcName"]), shapeType
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(el["ifcName"]))
|
||||
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
if rel["eccentricity"]:
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["linkObjs"][j],
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
SMESH.EDGE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(el["ifcName"])
|
||||
+ "_1DR_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
|
||||
self.mesh = bldMesh
|
||||
self.meshNodes = bldMesh.GetNodesId()
|
||||
|
||||
# Find ground supports and extract node coordinates
|
||||
grdSupps = bldMesh.CreateEmptyGroup(SMESH.NODE, "grdSupps")
|
||||
|
||||
for node in self.meshNodes:
|
||||
coords = bldMesh.GetNodeXYZ(node)
|
||||
if abs(coords[2] - self.zGround) < tolLoc:
|
||||
grdSupps.Add([node])
|
||||
|
||||
smesh.SetName(grdSupps, "grdSupps")
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
print("Mesh Groups Defined in %g sec" % (elapsed_time))
|
||||
|
||||
try:
|
||||
if NEW_SALOME:
|
||||
bldMesh.ExportMED(
|
||||
self.medFilename,
|
||||
auto_groups=0,
|
||||
minor=40,
|
||||
overwrite=1,
|
||||
meshPart=None,
|
||||
autoDimension=0,
|
||||
)
|
||||
else:
|
||||
bldMesh.ExportMED(self.medFilename, 0, SMESH.MED_V2_2, 1, None, 0)
|
||||
except:
|
||||
print("ExportMED() failed. Invalid file name?")
|
||||
|
||||
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 = ["building_02"]
|
||||
files = fileNames
|
||||
|
||||
meshSize = 500
|
||||
zGround = 0
|
||||
|
||||
for fileName in files:
|
||||
BASE_PATH = "/home/jesusbill/Dev-Projects/github.com/IfcOpenShell/analysis-models/models/"
|
||||
DATAFILENAME = BASE_PATH + fileName + "/" + fileName + ".json"
|
||||
MEDFILENAME = BASE_PATH + fileName + "/" + fileName + ".med"
|
||||
model = MODEL(DATAFILENAME, MEDFILENAME, meshSize, zGround)
|
||||
@@ -0,0 +1,191 @@
|
||||
from behave import step, given, when, then, use_step_matcher
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.table import TableModel
|
||||
from bimtester import util
|
||||
|
||||
use_step_matcher("parse")
|
||||
|
||||
|
||||
@step('There must be exactly {number} "{ifc_class}" element')
|
||||
@step('There must be exactly {number} "{ifc_class}" elements')
|
||||
def step_impl(context, number, ifc_class):
|
||||
num = len(IfcStore.file.by_type(ifc_class))
|
||||
assert num == int(
|
||||
number
|
||||
), "Could not find {} elements of {}. \
|
||||
Found {} element(s).".format(
|
||||
number, ifc_class, num
|
||||
)
|
||||
|
||||
|
||||
@given('a set of (key,value) called ("{key_name}","{value_name}")')
|
||||
def step_impl(context, key_name, value_name):
|
||||
model = getattr(context, "model", None)
|
||||
if not model:
|
||||
context.model = TableModel(key_name, value_name)
|
||||
for row in context.table:
|
||||
context.model.add_row(row[key_name], row[value_name])
|
||||
|
||||
|
||||
@given('a set of (key,value) called ("{key_name}","{value_name}") taken from the file "{path_file}"')
|
||||
def step_impl(context, key_name, value_name, path_file):
|
||||
import csv
|
||||
import os
|
||||
|
||||
model = getattr(context, "model", None)
|
||||
if not model:
|
||||
context.model = TableModel(key_name, value_name)
|
||||
if context.config.userdata.get("path"):
|
||||
path_file = os.path.join(context.config.userdata.get("path"), path_file)
|
||||
if not os.path.exists(path_file):
|
||||
assert False, "File {} not found".format(path_file)
|
||||
with open(path_file, "r", encoding="utf-8-sig") as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
for row in reader:
|
||||
context.model.add_row(row[key_name], row[value_name])
|
||||
|
||||
|
||||
@then('there is an element of type key with an attribute "{attribute_name}" equals to value')
|
||||
def step_impl(context, attribute_name):
|
||||
if not context.model.key_name and not context.model.value_name:
|
||||
assert False, "Missing (key,value)"
|
||||
errors = []
|
||||
rows = context.model.rows
|
||||
for key, values in rows.items():
|
||||
for value in values:
|
||||
found = check_equals(attribute_name, value, IfcStore.file.by_type(key))
|
||||
if not found:
|
||||
errors.append(f"The row ({key}, {value}) was not found.")
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
|
||||
@then('there is an element of type key with an attribute "{attribute_name}" starting with value')
|
||||
def step_impl(context, attribute_name):
|
||||
# import re
|
||||
if not context.model.key_name and not context.model.value_name:
|
||||
assert False, "Missing (key,value)"
|
||||
errors = []
|
||||
rows = context.model.rows
|
||||
for key, values in rows.items():
|
||||
for value in values:
|
||||
found = check_starts_with(attribute_name, value, IfcStore.file.by_type(key))
|
||||
if not found:
|
||||
errors.append(f"The row ({key}, {value}) was not found.")
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
|
||||
@then('there must be exactly a number of "{ifc_class}" equals to the number of distinct value')
|
||||
def step_impl(context, ifc_class):
|
||||
try:
|
||||
context.execute_steps(
|
||||
"""
|
||||
then There must be exactly {number} "{ifc_class}" elements
|
||||
""".format(
|
||||
ifc_class=ifc_class, number=context.model.get_count_distinct_values()
|
||||
)
|
||||
)
|
||||
except AssertionError as error:
|
||||
str_error = str(error)
|
||||
assert False, str_error[: str_error.find("Traceback")]
|
||||
assert True
|
||||
|
||||
|
||||
@then('there is a relationship "{ifc_class}" between the two elements of each row')
|
||||
def step_impl(context, ifc_class):
|
||||
if not context.model.key_name and not context.model.value_name:
|
||||
assert False, "Missing (key,value)"
|
||||
errors = []
|
||||
elements = IfcStore.file.by_type(ifc_class)
|
||||
|
||||
# first, loop on the dataset to check if there are all in the relationships
|
||||
rows = context.model.rows
|
||||
for key, values in rows.items():
|
||||
found = False
|
||||
# value is actually a list of values
|
||||
for value in values:
|
||||
for element in elements:
|
||||
if (
|
||||
any(x.Name == key for x in getattr(element, context.model.key_name))
|
||||
and getattr(element, context.model.value_name).Name == value
|
||||
):
|
||||
found = True
|
||||
if not found:
|
||||
errors.append(f"The row ({key}, {value}) does not have the relationship.")
|
||||
|
||||
# second, loop on the elements to check if there are not too many relationships
|
||||
for element in elements:
|
||||
if hasattr(element, context.model.key_name) and hasattr(element, context.model.value_name):
|
||||
# list here
|
||||
keys = getattr(element, context.model.key_name)
|
||||
value = getattr(element, context.model.value_name).Name
|
||||
for key in keys:
|
||||
if not key.Name in rows or not value in rows[key.Name]:
|
||||
errors.append(f"The element ({element}) has a relation that cannot be found in the dataset: {key}")
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
|
||||
use_step_matcher("re")
|
||||
|
||||
|
||||
@step('all IfcGroup must be linked to a type "(?P<linked_ifc_classes>.*)"')
|
||||
def step_impl(context, linked_ifc_classes):
|
||||
groups = IfcStore.file.by_type("IfcGroup")
|
||||
errors = []
|
||||
for group in groups:
|
||||
if not hasattr(group, "IsGroupedBy"):
|
||||
errors.append(f'The element "{group.Name}" has no "IsGroupedBy" attribute.')
|
||||
else:
|
||||
for grouped_by in getattr(group, "IsGroupedBy"):
|
||||
if not hasattr(grouped_by, "RelatedObjects"):
|
||||
errors.append(f'The element "{grouped_by.Name}" has no "RelatedObjects" attribute.')
|
||||
else:
|
||||
for related_object in getattr(grouped_by, "RelatedObjects"):
|
||||
found = False
|
||||
for linked_ifc_class in linked_ifc_classes.split(","):
|
||||
if related_object.is_a(linked_ifc_class):
|
||||
found = True
|
||||
if not found:
|
||||
errors.append(
|
||||
f'The element "{related_object.Name}" does not have the right associated type.'
|
||||
)
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
|
||||
@then('there is an element of type "(?P<ifc_types>.*)" with an attribute "(?P<attribute_name>.*)" for each key')
|
||||
def step_impl(context, ifc_types, attribute_name):
|
||||
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, context.model.rows.keys())
|
||||
|
||||
|
||||
@then('there is an element of type "(?P<ifc_types>.*)" with an attribute "(?P<attribute_name>.*)" for each value')
|
||||
def step_impl(context, ifc_types, attribute_name):
|
||||
values = set(item for sublist in context.model.rows.values() for item in sublist)
|
||||
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, values)
|
||||
|
||||
|
||||
def check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, attribute_values):
|
||||
errors = []
|
||||
# retrieve all elements of that type
|
||||
elements = util.by_types(IfcStore.file, ifc_types)
|
||||
# loop
|
||||
for attribute_value in attribute_values:
|
||||
found = False
|
||||
for element in elements:
|
||||
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
|
||||
found = True
|
||||
if not found:
|
||||
errors.append(f'An element with attribute "{attribute_name}" equals to "{attribute_value}" was not found.')
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
|
||||
def check_starts_with(attribute_name, value, elements):
|
||||
"""Make sure at least an element of the list has a attribute starting with the value"""
|
||||
if any(hasattr(x, attribute_name) and getattr(x, attribute_name).startswith(value) for x in elements):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def check_equals(attribute_name, value, elements):
|
||||
"""Make sure at least an element of the list has a attribute equals to the value"""
|
||||
if any(hasattr(x, attribute_name) and getattr(x, attribute_name) == value for x in elements):
|
||||
return True
|
||||
return False
|
||||
@@ -1,15 +1,23 @@
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.classification import en
|
||||
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.element_classes import en
|
||||
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.geocoding import en
|
||||
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.geolocation import en
|
||||
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.geometric_detail import en
|
||||
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.model_federation import en
|
||||
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.project_setup import de, en, fr, it, nl
|
||||
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.aggregation import en
|
||||
@@ -10,6 +10,12 @@ def step_impl(context, schema):
|
||||
assert real_schema == schema, _("We expected a schema of {} but instead got {}").format(schema, real_schema)
|
||||
|
||||
|
||||
@step("The IFC file must be valid")
|
||||
def step_impl(context):
|
||||
errors = util.validate(IfcStore.file)
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
|
||||
@step('The IFC file "{file}" is exempt from being provided')
|
||||
def step_impl(context, file):
|
||||
pass
|
||||
|
||||
@@ -21,12 +21,12 @@ class ReportGenerator:
|
||||
self.generate_feature_report(feature, output_file)
|
||||
|
||||
def generate_feature_report(self, feature, output_file):
|
||||
file_name = os.path.basename(feature["location"]).split(":")[0]
|
||||
# file_name = os.path.basename(feature["location"]).split(":")[0]
|
||||
data = {
|
||||
"file_name": file_name,
|
||||
# "file_name": file_name,
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"name": feature["name"],
|
||||
"description": feature["description"],
|
||||
"description": feature.get("description", ""),
|
||||
"is_success": feature["status"] == "passed",
|
||||
"scenarios": [],
|
||||
}
|
||||
@@ -93,9 +93,7 @@ class ReportGenerator:
|
||||
step["result"] = {}
|
||||
step["result"]["status"] = "skipped"
|
||||
step["result"]["duration"] = 0
|
||||
step["result"][
|
||||
"error_message"
|
||||
] = "This requirement has been skipped due to a previous failing step."
|
||||
step["result"]["error_message"] = "This requirement has been skipped due to a previous failing step."
|
||||
elif step["result"]["status"] == "undefined":
|
||||
step["result"] = {}
|
||||
step["result"]["status"] = "undefined"
|
||||
@@ -108,14 +106,12 @@ class ReportGenerator:
|
||||
"is_success": step["result"]["status"] == "passed",
|
||||
"is_unspecified": step["result"]["status"] == "undefined",
|
||||
"is_skipped": step["result"]["status"] == "skipped",
|
||||
"error_message": None
|
||||
if step["result"]["status"] == "passed"
|
||||
else step["result"]["error_message"],
|
||||
"error_message": None if step["result"]["status"] == "passed" else step["result"]["error_message"],
|
||||
}
|
||||
|
||||
# TODO: there is probably a better way of doing this
|
||||
if isinstance(data["error_message"], list):
|
||||
data["error_message"] = data["error_message"][1]
|
||||
# Remove the first "Assertion Failed" message
|
||||
if isinstance(data["error_message"], list) and data["error_message"]:
|
||||
data["error_message"].pop(0)
|
||||
return data
|
||||
|
||||
def get_template_strings(self):
|
||||
|
||||
@@ -35,11 +35,11 @@
|
||||
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}</span>
|
||||
{{_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
|
||||
<br />
|
||||
{{#description}}
|
||||
<p class="description">
|
||||
{{#description}}
|
||||
{{.}}<br />
|
||||
{{/description}}
|
||||
</p>
|
||||
{{/description}}
|
||||
<hr>
|
||||
</header>
|
||||
{{#scenarios}}
|
||||
|
||||
@@ -1,21 +1,70 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import logging
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
|
||||
try:
|
||||
import ifcopenshell.express
|
||||
except:
|
||||
pass # They are using an old version of IfcOpenShell. Gracefully degrade for now.
|
||||
pass # They are using an old version of IfcOpenShell. Gracefully degrade for now.
|
||||
import behave.formatter.pretty # Needed for pyinstaller to package it
|
||||
from bimtester.ifc import IfcStore
|
||||
from distutils.dir_util import copy_tree
|
||||
from behave.__main__ import main as behave_main
|
||||
|
||||
|
||||
# TODO: refactor when this isn't super experimental
|
||||
from logging import StreamHandler
|
||||
|
||||
class IDSHandler(StreamHandler):
|
||||
def __init__(self):
|
||||
StreamHandler.__init__(self)
|
||||
self.results = {
|
||||
"name": "Specification name",
|
||||
"status": "passed",
|
||||
"location": "filename.xml",
|
||||
"elements": [
|
||||
{
|
||||
"keyword": "Scenario",
|
||||
"name": "Checking IDS specifications",
|
||||
"status": "passed",
|
||||
"steps": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def emit(self, record):
|
||||
msg = self.format(record)
|
||||
# Obviously, not a final product
|
||||
is_fail = "is compliant" not in msg
|
||||
if is_fail:
|
||||
self.results["status"] = "failed"
|
||||
self.results["elements"][0]["status"] = "failed"
|
||||
self.results["elements"][0]["steps"].append({
|
||||
"keyword": "*",
|
||||
"match": {},
|
||||
"name": msg,
|
||||
"result": {
|
||||
"duration": 0.0,
|
||||
"error_message": "Assertion Failed",
|
||||
"status": "failed" if is_fail else "passed"
|
||||
},
|
||||
"step_type": "given"
|
||||
})
|
||||
|
||||
|
||||
class TestRunner:
|
||||
def __init__(self, ifc_path, ifc=None):
|
||||
def __init__(self, ifc_path, schema_path=None, ifc=None):
|
||||
IfcStore.path = ifc_path
|
||||
|
||||
# can't load the IFC file if the schema is not loaded before
|
||||
if schema_path:
|
||||
schema = ifcopenshell.express.parse(schema_path)
|
||||
ifcopenshell.register_schema(schema)
|
||||
|
||||
IfcStore.file = ifc if ifc else ifcopenshell.open(ifc_path)
|
||||
|
||||
try:
|
||||
@@ -27,10 +76,27 @@ class TestRunner:
|
||||
self.locale_path = os.path.join(self.base_path, "locale")
|
||||
|
||||
def run(self, args):
|
||||
if args["schema_file"]:
|
||||
schema = ifcopenshell.express.parse(args["schema_file"])
|
||||
ifcopenshell.register_schema(args["schema_name"])
|
||||
if args["feature"][-4:].lower() == ".xml":
|
||||
return self.test_ids(args)
|
||||
return self.test_feature(args)
|
||||
|
||||
def test_ids(self, args):
|
||||
# Local import whilst this is experimental
|
||||
import ifcopenshell.ids
|
||||
|
||||
logger = logging.getLogger("IDS")
|
||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||
ids_handler = IDSHandler()
|
||||
logger.addHandler(ids_handler)
|
||||
ids_file = ifcopenshell.ids.ids(args["feature"])
|
||||
ids_file.validate(IfcStore.file, logger)
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
report_json = os.path.join(tmpdir, "report.json")
|
||||
json.dump([ids_handler.results], open(report_json, "w"))
|
||||
return report_json
|
||||
|
||||
def test_feature(self, args):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
features_path = os.path.join(tmpdir, "features")
|
||||
steps_path = os.path.join(features_path, "steps")
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class TableModel:
|
||||
"""This class represents a table of data"""
|
||||
|
||||
def __init__(self, key_name=None, value_name=None):
|
||||
self.rows = defaultdict(list)
|
||||
self.key_name = key_name
|
||||
self.value_name = value_name
|
||||
|
||||
def add_row(self, related, relating):
|
||||
self.rows[related].append(relating)
|
||||
|
||||
def get_count(self):
|
||||
return len(self.rows)
|
||||
|
||||
def get_count_distinct_values(self):
|
||||
return len(set(item for sublist in self.rows.values() for item in sublist))
|
||||
@@ -1,8 +1,29 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.validate
|
||||
import json
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
def by_types(ifc, ifc_types):
|
||||
elements = []
|
||||
for ifc_type in ifc_types.split(","):
|
||||
elements += ifc.by_type(ifc_type.strip())
|
||||
return elements
|
||||
|
||||
|
||||
def validate(ifc):
|
||||
errors = []
|
||||
logger = ifcopenshell.validate.json_logger()
|
||||
try:
|
||||
ifcopenshell.validate.validate(ifc, logger)
|
||||
if logger.statements:
|
||||
for statement in logger.statements:
|
||||
errors.append(f"{json.dumps(statement, default=str)}")
|
||||
except RuntimeError as error:
|
||||
assert False, str(error)
|
||||
|
||||
|
||||
def assert_guid(ifc, guid):
|
||||
try:
|
||||
return ifc.by_guid(guid)
|
||||
|
||||
@@ -10,7 +10,7 @@ parser = argparse.ArgumentParser(description="Runs unit tests for BIM data")
|
||||
parser.add_argument("-a", "--action", type=str, help="Action to perform, from run/purge", default="run")
|
||||
parser.add_argument("--advanced-arguments", type=str, help="Specify arguments to Behave", default="")
|
||||
parser.add_argument("-c", "--console", action="store_true", help="Show results in the console")
|
||||
parser.add_argument("-f", "--feature", type=str, help="Specify a feature file to test", required=True)
|
||||
parser.add_argument("-f", "--feature", type=str, help="Specify a feature file or IDS to test", required=True)
|
||||
parser.add_argument("-i", "--ifc", type=str, help="Specify an IFC file to test", required=True)
|
||||
parser.add_argument("-p", "--path", type=str, help="Define a path for use in test steps that use relative paths")
|
||||
parser.add_argument("-r", "--report", type=str, help="Specify an output file for a HTML report")
|
||||
@@ -22,7 +22,7 @@ parser.add_argument("--lang", type=str, help="Specify a language e.g. en/de/fr/i
|
||||
args = vars(parser.parse_args())
|
||||
|
||||
if args["action"] == "run":
|
||||
report_json = bimtester.run.TestRunner(args["ifc"]).run(args)
|
||||
report_json = bimtester.run.TestRunner(args["ifc"], args["schema_file"]).run(args)
|
||||
if args["report"]:
|
||||
bimtester.reports.ReportGenerator().generate(report_json, args["report"])
|
||||
elif args["action"] == "purge":
|
||||
|
||||
@@ -1,19 +1,44 @@
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
PYVERSION:=py37
|
||||
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/win-64/pythonocc-core-0.17.3-py37he980bc4_10.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/win-64/oce-0.17.2-he980bc4_14.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/DLR-SC/tbb/2019.5/download/win-64/tbb-2019.5-he980bc4_0.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/win-64/pythonocc-core-7.4.1-py39h3d1c7c5_0.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/win-64/occt-7.4.0-h823b557_3.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/win-64/tbb-2020.2-h2d74725_4.tar.bz2
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/osx-64/pythonocc-core-0.17.3-py37h04f5b5a_10.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/osx-64/oce-0.17.2-h04f5b5a_12.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/DLR-SC/tbb/4.3.6/download/osx-64/tbb-4.3.6-0.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/osx-64/pythonocc-core-7.4.1-py39h4d29fe3_0.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/osx-64/occt-7.4.0-hb9b6dc7_3.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/osx-64/tbb-2020.2-h940c156_4.tar.bz2
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/linux-64/pythonocc-core-0.17.3-py37h6bb024c_10.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/linux-64/oce-0.17.2-h6bb024c_14.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/DLR-SC/tbb/4.3.6/download/linux-64/tbb-4.3.6-0.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/linux-64/pythonocc-core-7.4.1-py39h465cb30_0.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/linux-64/occt-7.4.0-h9121d39_3.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/linux-64/tbb-2020.2-h4bd325d_4.tar.bz2
|
||||
endif
|
||||
endif
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
@@ -26,14 +51,31 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-3bbdc3f-$(PLATFORM)64.zip
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-517b819-$(PLATFORM)64.zip
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-517b819-$(PLATFORM)64.zip
|
||||
endif
|
||||
cd dist/working && unzip ifcblender*
|
||||
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
# See bug #812
|
||||
cd dist/working && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcblenderexport/occ_utils.py
|
||||
cd dist/working && mv occ_utils.py ../blenderbim/libs/site/packages/ifcopenshell/geom/occ_utils.py
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides IfcConvert for construction documentation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.6.0-517b819-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip IfcConvert*
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/IfcConvert.exe dist/blenderbim/libs/
|
||||
else
|
||||
cp -r dist/working/IfcConvert dist/blenderbim/libs/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Python OCC functionality for cutting IFC geometry for construction documentation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(PYTHONOCC_URL)
|
||||
@@ -41,16 +83,26 @@ endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cd dist/working && cp -r Lib/site-packages/OCC ../blenderbim/libs/site/packages/
|
||||
else
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && cp -r lib/python3.7/site-packages/OCC ../blenderbim/libs/site/packages/
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && cp -r lib/python3.9/site-packages/OCC ../blenderbim/libs/site/packages/
|
||||
endif
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by Python OCC
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(OCE_URL)
|
||||
cd dist/working && tar -xjvf oce*
|
||||
cd dist/working && tar -xjvf oc*
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/Core/
|
||||
endif
|
||||
else
|
||||
# Unix Conda builds of PythonOCC expect OCE libs to have a RPATH of ../../../
|
||||
cd dist/working && cp -r lib/* ../blenderbim/libs/
|
||||
@@ -65,7 +117,12 @@ endif
|
||||
cd dist/working && wget $(TBB_URL)
|
||||
cd dist/working && tar -xjvf tbb*
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/Core/
|
||||
endif
|
||||
else
|
||||
cd dist/working && cp -r lib/* ../blenderbim/libs/
|
||||
endif
|
||||
@@ -98,7 +155,7 @@ endif
|
||||
# Provides IFCCSV functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifccsv/* dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCPatch functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcpatch dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcpatch/ifcpatch dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Mustache templating in construction documentation
|
||||
@@ -179,8 +236,6 @@ ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/fcl dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
endif
|
||||
|
||||
# Required by IFCClash
|
||||
ifeq ($(PLATFORM), win)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/19/e6/6e9f33fb59e8f27c0e1592bd26e644bc92b85c942b072b2d3854105d5887/python_fcl_win32-0.0.12.post3-py3-none-win_amd64.whl
|
||||
@@ -231,6 +286,34 @@ endif
|
||||
cd dist/working/defusedxml-0.6.0/ && cp -r defusedxml ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by augin
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
|
||||
cd dist/working && tar -xzvf boto3*
|
||||
cd dist/working/boto3-1.17.17/ && cp -r boto3 ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/7f/2e/e02fdfd0f0377c4e44e61de27b05d5cfe93575770661ef9ded80ed90fa88/botocore-1.20.17.tar.gz
|
||||
cd dist/working && tar -xzvf botocore*
|
||||
cd dist/working/botocore-1.20.17/ && cp -r botocore ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz
|
||||
cd dist/working && tar -xzvf jmespath*
|
||||
cd dist/working/jmespath-0.10.0/ && cp -r jmespath ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/08/e1/3ee2096ebaeeb8c186d20ed16c8faf4a503913e5c9a0e14cd6b8ffc405a3/s3transfer-0.3.4.tar.gz
|
||||
cd dist/working && tar -xzvf s3transfer*
|
||||
cd dist/working/s3transfer-0.3.4/ && cp -r s3transfer ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides IFCJSON functionality
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
|
||||
@@ -239,7 +322,7 @@ endif
|
||||
rm -rf dist/working
|
||||
|
||||
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist && zip -r blender28-bim-$(VERSION)-$(PLATFORM).zip ./*
|
||||
cd dist && zip -r blender28-bim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
@@ -6,7 +6,7 @@ bpy = sys.modules.get("bpy")
|
||||
if bpy is not None:
|
||||
import bpy
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator
|
||||
from . import handler, ui, prop, operator, gizmos
|
||||
|
||||
modules = {
|
||||
"project": None,
|
||||
@@ -16,6 +16,7 @@ if bpy is not None:
|
||||
"unit": None,
|
||||
"georeference": None,
|
||||
"context": None,
|
||||
"drawing": None,
|
||||
"attribute": None,
|
||||
"type": None,
|
||||
"spatial": None,
|
||||
@@ -23,6 +24,9 @@ if bpy is not None:
|
||||
"aggregate": None,
|
||||
"geometry": None,
|
||||
"cobie": None,
|
||||
"sequence": None,
|
||||
"group": None,
|
||||
"structural": None,
|
||||
"material": None,
|
||||
"style": None,
|
||||
"layer": None,
|
||||
@@ -40,6 +44,7 @@ if bpy is not None:
|
||||
"diff": None,
|
||||
"patch": None,
|
||||
"covetool": None,
|
||||
"augin": None,
|
||||
"debug": None,
|
||||
}
|
||||
|
||||
@@ -86,7 +91,6 @@ if bpy is not None:
|
||||
operator.RemoveVariable,
|
||||
operator.PropagateTextData,
|
||||
operator.SetOverrideColour,
|
||||
operator.AddDrawing,
|
||||
operator.RemoveDrawing,
|
||||
operator.AddDrawingStyle,
|
||||
operator.RemoveDrawingStyle,
|
||||
@@ -135,7 +139,6 @@ if bpy is not None:
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_camera,
|
||||
ui.BIM_PT_text,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_PT_misc_utilities,
|
||||
@@ -143,6 +146,11 @@ if bpy is not None:
|
||||
ui.BIM_UL_drawinglist,
|
||||
ui.BIM_UL_topics,
|
||||
ui.BIM_ADDON_preferences,
|
||||
gizmos.UglyDotGizmo,
|
||||
gizmos.DotGizmo,
|
||||
gizmos.DimensionLabelGizmo,
|
||||
gizmos.ExtrusionGuidesGizmo,
|
||||
gizmos.ExtrusionWidget
|
||||
]
|
||||
|
||||
for module in modules.values():
|
||||
@@ -164,6 +172,7 @@ if bpy is not None:
|
||||
bpy.app.handlers.depsgraph_update_post.append(on_register)
|
||||
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
|
||||
bpy.app.handlers.save_pre.append(handler.storeIdMap)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
@@ -174,6 +183,8 @@ if bpy is not None:
|
||||
bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) # Check if we need this
|
||||
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
|
||||
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
|
||||
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
|
||||
@@ -190,6 +201,7 @@ if bpy is not None:
|
||||
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.remove(handler.storeIdMap)
|
||||
bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
del bpy.types.Scene.BIMProperties
|
||||
@@ -199,6 +211,8 @@ if bpy is not None:
|
||||
del bpy.types.Collection.BIMObjectProperties # Check if we need this
|
||||
del bpy.types.Material.BIMMaterialProperties
|
||||
del bpy.types.Mesh.BIMMeshProperties
|
||||
del bpy.types.Curve.BIMMeshProperties
|
||||
del bpy.types.Camera.BIMMeshProperties
|
||||
del bpy.types.Camera.BIMCameraProperties
|
||||
del bpy.types.TextCurve.BIMTextProperties
|
||||
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import numpy as np
|
||||
import datetime
|
||||
import zipfile
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import addon_utils
|
||||
|
||||
@@ -16,6 +18,9 @@ class IfcExporter:
|
||||
def export(self):
|
||||
self.file = IfcStore.get_file()
|
||||
self.set_header()
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
self.sync_object_placements()
|
||||
self.sync_edited_objects()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1]
|
||||
if extension == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
@@ -40,6 +45,9 @@ class IfcExporter:
|
||||
jsonData = ifcjson.IFC2JSON5a(self.file, self.ifc_export_settings.json_compact).spf2Json()
|
||||
with open(self.ifc_export_settings.output_file, "w") as outfile:
|
||||
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4)
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
if bpy.data.filepath:
|
||||
bpy.ops.wm.save_mainfile()
|
||||
|
||||
def set_header(self):
|
||||
# TODO: add all metadata, pending bug #747
|
||||
@@ -66,6 +74,46 @@ class IfcExporter:
|
||||
# else:
|
||||
# self.file.wrapped_data.header.file_name.authorization = "Nobody"
|
||||
|
||||
def sync_object_placements(self):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
for obj in bpy.data.objects:
|
||||
try:
|
||||
self.sync_object_placement(obj)
|
||||
except:
|
||||
pass
|
||||
|
||||
def sync_edited_objects(self):
|
||||
for obj_name in IfcStore.edited_objs.copy():
|
||||
obj = bpy.data.objects.get(obj_name)
|
||||
if not obj:
|
||||
continue
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
|
||||
bpy.ops.bim.update_mesh_representation(obj=obj.name)
|
||||
IfcStore.edited_objs.clear()
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
blender_matrix = np.matrix(obj.matrix_world)
|
||||
ifc_matrix = ifcopenshell.util.placement.get_local_placement(
|
||||
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).ObjectPlacement
|
||||
)
|
||||
ifc_matrix[0][3] *= self.unit_scale
|
||||
ifc_matrix[1][3] *= self.unit_scale
|
||||
ifc_matrix[2][3] *= self.unit_scale
|
||||
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
ifc_matrix = ifcopenshell.util.geolocation.global2local(
|
||||
ifc_matrix,
|
||||
float(props.blender_eastings) * self.unit_scale,
|
||||
float(props.blender_northings) * self.unit_scale,
|
||||
float(props.blender_orthogonal_height) * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001):
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
def get_application_name(self):
|
||||
return "BlenderBIM"
|
||||
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
import bpy
|
||||
import blf
|
||||
from bpy import types
|
||||
import math
|
||||
from mathutils import Vector, Matrix
|
||||
from mathutils import geometry
|
||||
import gpu, bgl
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
|
||||
from .shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
|
||||
|
||||
|
||||
"""Gizmos under the hood
|
||||
|
||||
## Transforms:
|
||||
|
||||
source/blender/windowmanager/gizmo/WM_gizmo_types.h
|
||||
matrix_basis -- "Transformation of this gizmo." = placement in scene
|
||||
matrix_offset -- "Custom offset from origin." = local transforms according to state/value
|
||||
matrix_space -- "The space this gizmo is being modified in." used by some gizmos for undefined purposes
|
||||
matrix_world -- final matrix, scaled according to viewport zoom and custom scale_basis
|
||||
|
||||
source/blender/windowmanager/gizmo/intern/wm_gizmo.c:WM_gizmo_calc_matrix_final_params
|
||||
final = space @ (autoscale * (basis @ offset))
|
||||
final = space @ (basis @ offset) -- if gizmo.use_draw_scale == False
|
||||
final = space @ ((autoscale * basis) @ offset) -- if gizmo.use_draw_offset_scale
|
||||
|
||||
source/blender/windowmanager/gizmo/intern/wm_gizmo.c:wm_gizmo_calculate_scale
|
||||
autoscale = gizmo.scale_basis * magic(preferences, matrix_space, matrix_basis, context.region_data)
|
||||
magic -- making 1.0 to match preferences.view.gizmo_size pixels (75 by default)
|
||||
|
||||
|
||||
## Selection
|
||||
|
||||
select_id -- apparently, id of a selectable part
|
||||
test_select -- expected to return id of selection, doesn't seem to work
|
||||
draw_select -- fake-draw of selection geometry for gpu-side cursor tracking
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# some geometries for Gizmo.custom_shape shaders
|
||||
|
||||
CUBE = (
|
||||
(+1, +1, +1), (-1, +1, +1), (+1, -1, +1), # top
|
||||
(+1, -1, +1), (-1, +1, +1), (-1, -1, +1),
|
||||
(+1, +1, +1), (+1, -1, +1), (+1, +1, -1), # right
|
||||
(+1, +1, -1), (+1, -1, +1), (+1, -1, -1),
|
||||
(+1, +1, +1), (+1, +1, -1), (-1, +1, +1), # back
|
||||
(-1, +1, +1), (+1, +1, -1), (-1, +1, -1),
|
||||
(-1, -1, -1), (-1, +1, -1), (+1, -1, -1), # bot
|
||||
(+1, -1, -1), (-1, +1, -1), (+1, +1, -1),
|
||||
(-1, -1, -1), (-1, -1, +1), (-1, +1, -1), # left
|
||||
(-1, +1, -1), (-1, -1, +1), (-1, +1, +1),
|
||||
(-1, -1, -1), (+1, -1, -1), (-1, -1, +1), # front
|
||||
(-1, -1, +1), (+1, -1, -1), (+1, -1, +1)
|
||||
)
|
||||
|
||||
DISC = (
|
||||
(0.0, 0.0, 0.0), (1.0, 0.0, 0), (0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844387, 0.49999999999999994, 0), (0.5000000000000001, 0.8660254037844386, 0),
|
||||
(0.0, 0.0, 0.0), (0.5000000000000001, 0.8660254037844386, 0), (6.123233995736766e-17, 1.0, 0),
|
||||
(0.0, 0.0, 0.0), (6.123233995736766e-17, 1.0, 0), (-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(0.0, 0.0, 0.0), (-0.4999999999999998, 0.8660254037844387, 0), (-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844385, 0.5000000000000003, 0), (-1.0, 1.2246467991473532e-16, 0),
|
||||
(0.0, 0.0, 0.0), (-1.0, 1.2246467991473532e-16, 0), (-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844388, -0.4999999999999997, 0), (-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(0.0, 0.0, 0.0), (-0.5000000000000004, -0.8660254037844384, 0), (-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.0, 0.0, 0.0), (-1.8369701987210297e-16, -1.0, 0), (0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.0, 0.0, 0.0), (0.49999999999999933, -0.866025403784439, 0), (0.8660254037844384, -0.5000000000000004, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844384, -0.5000000000000004, 0), (1.0, 0.0, 0),
|
||||
)
|
||||
|
||||
X3DISC = (
|
||||
(0.0, 0.0, 0.0), (1.0, 0.0, 0), (0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844387, 0.49999999999999994, 0), (0.5000000000000001, 0.8660254037844386, 0),
|
||||
(0.0, 0.0, 0.0), (0.5000000000000001, 0.8660254037844386, 0), (6.123233995736766e-17, 1.0, 0),
|
||||
(0.0, 0.0, 0.0), (6.123233995736766e-17, 1.0, 0), (-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(0.0, 0.0, 0.0), (-0.4999999999999998, 0.8660254037844387, 0), (-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844385, 0.5000000000000003, 0), (-1.0, 1.2246467991473532e-16, 0),
|
||||
(0.0, 0.0, 0.0), (-1.0, 1.2246467991473532e-16, 0), (-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844388, -0.4999999999999997, 0), (-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(0.0, 0.0, 0.0), (-0.5000000000000004, -0.8660254037844384, 0), (-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.0, 0.0, 0.0), (-1.8369701987210297e-16, -1.0, 0), (0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.0, 0.0, 0.0), (0.49999999999999933, -0.866025403784439, 0), (0.8660254037844384, -0.5000000000000004, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844384, -0.5000000000000004, 0), (1.0, 0.0, 0),
|
||||
(0.0, 0.0, 0.0), (0, 1.0, 0.0), (0, 0.8660254037844387, 0.49999999999999994),
|
||||
(0.0, 0.0, 0.0), (0, 0.8660254037844387, 0.49999999999999994), (0, 0.5000000000000001, 0.8660254037844386),
|
||||
(0.0, 0.0, 0.0), (0, 0.5000000000000001, 0.8660254037844386), (0, 6.123233995736766e-17, 1.0),
|
||||
(0.0, 0.0, 0.0), (0, 6.123233995736766e-17, 1.0), (0, -0.4999999999999998, 0.8660254037844387),
|
||||
(0.0, 0.0, 0.0), (0, -0.4999999999999998, 0.8660254037844387), (0, -0.8660254037844385, 0.5000000000000003),
|
||||
(0.0, 0.0, 0.0), (0, -0.8660254037844385, 0.5000000000000003), (0, -1.0, 1.2246467991473532e-16),
|
||||
(0.0, 0.0, 0.0), (0, -1.0, 1.2246467991473532e-16), (0, -0.8660254037844388, -0.4999999999999997),
|
||||
(0.0, 0.0, 0.0), (0, -0.8660254037844388, -0.4999999999999997), (0, -0.5000000000000004, -0.8660254037844384),
|
||||
(0.0, 0.0, 0.0), (0, -0.5000000000000004, -0.8660254037844384), (0, -1.8369701987210297e-16, -1.0),
|
||||
(0.0, 0.0, 0.0), (0, -1.8369701987210297e-16, -1.0), (0, 0.49999999999999933, -0.866025403784439),
|
||||
(0.0, 0.0, 0.0), (0, 0.49999999999999933, -0.866025403784439), (0, 0.8660254037844384, -0.5000000000000004),
|
||||
(0.0, 0.0, 0.0), (0, 0.8660254037844384, -0.5000000000000004), (0, 1.0, 0.0),
|
||||
(0.0, 0.0, 0.0), (0.0, 0, 1.0), (0.49999999999999994, 0, 0.8660254037844387),
|
||||
(0.0, 0.0, 0.0), (0.49999999999999994, 0, 0.8660254037844387), (0.8660254037844386, 0, 0.5000000000000001),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844386, 0, 0.5000000000000001), (1.0, 0, 6.123233995736766e-17),
|
||||
(0.0, 0.0, 0.0), (1.0, 0, 6.123233995736766e-17), (0.8660254037844387, 0, -0.4999999999999998),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844387, 0, -0.4999999999999998), (0.5000000000000003, 0, -0.8660254037844385),
|
||||
(0.0, 0.0, 0.0), (0.5000000000000003, 0, -0.8660254037844385), (1.2246467991473532e-16, 0, -1.0),
|
||||
(0.0, 0.0, 0.0), (1.2246467991473532e-16, 0, -1.0), (-0.4999999999999997, 0, -0.8660254037844388),
|
||||
(0.0, 0.0, 0.0), (-0.4999999999999997, 0, -0.8660254037844388), (-0.8660254037844384, 0, -0.5000000000000004),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844384, 0, -0.5000000000000004), (-1.0, 0, -1.8369701987210297e-16),
|
||||
(0.0, 0.0, 0.0), (-1.0, 0, -1.8369701987210297e-16), (-0.866025403784439, 0, 0.49999999999999933),
|
||||
(0.0, 0.0, 0.0), (-0.866025403784439, 0, 0.49999999999999933), (-0.5000000000000004, 0, 0.8660254037844384),
|
||||
(0.0, 0.0, 0.0), (-0.5000000000000004, 0, 0.8660254037844384), (0.0, 0, 1.0),
|
||||
)
|
||||
|
||||
|
||||
class CustomGizmo():
|
||||
# FIXME: highliting/selection doesnt work
|
||||
def draw_very_custom_shape(self, ctx, custom_shape, select_id=None):
|
||||
# similar to draw_custom_shape
|
||||
shape, batch, shader = custom_shape
|
||||
|
||||
shader.bind()
|
||||
|
||||
if select_id is not None:
|
||||
gpu.select.load_id(select_id)
|
||||
else:
|
||||
if self.is_highlight:
|
||||
color = (*self.color_highlight, self.alpha_highlight)
|
||||
else:
|
||||
color = (*self.color, self.alpha)
|
||||
shader.uniform_float('color', color)
|
||||
shape.glenable()
|
||||
|
||||
shape.uniform_region(ctx)
|
||||
# shader.uniform_float('modelMatrix', self.matrix_world)
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.multiply_matrix(self.matrix_world)
|
||||
batch.draw()
|
||||
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
|
||||
|
||||
class OffsetHandle():
|
||||
"""Handling mouse to offset gizmo from base along Z axis"""
|
||||
# FIXME: works a bit weird for rotated objects
|
||||
|
||||
def invoke(self, ctx, event):
|
||||
self.init_value = self.target_get_value('offset') / self.scale_value
|
||||
coordz = self.project_mouse(ctx, event)
|
||||
if coordz is None:
|
||||
return {'CANCELLED'}
|
||||
self.init_coordz = coordz
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def modal(self, ctx, event, tweak):
|
||||
coordz = self.project_mouse(ctx, event)
|
||||
if coordz is None:
|
||||
return {'CANCELLED'}
|
||||
delta = coordz - self.init_coordz
|
||||
if 'PRECISE' in tweak:
|
||||
delta /= 10.0
|
||||
value = max(0, self.init_value + delta)
|
||||
value *= self.scale_value
|
||||
# ctx.area.header_text_set(f"coords: {self.init_coordz} - {coordz}, delta: {delta}, value: {value}")
|
||||
ctx.area.header_text_set(f"Depth: {value}")
|
||||
self.target_set_value('offset', value)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def project_mouse(self, ctx, event):
|
||||
"""Projecting mouse coords to local axis Z"""
|
||||
# logic from source/blender/editors/gizmo_library/gizmo_types/arrow3d_gizmo.c:gizmo_arrow_modal
|
||||
|
||||
mouse = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||
region = ctx.region
|
||||
region3d = ctx.region_data
|
||||
ray_orig = view3d_utils.region_2d_to_origin_3d(region, region3d, mouse)
|
||||
ray_norm = view3d_utils.region_2d_to_vector_3d(region, region3d, mouse)
|
||||
|
||||
# 'arrow' origin and direction
|
||||
base = Vector((0, 0, 0))
|
||||
axis = Vector((0, 0, 1))
|
||||
|
||||
# projecttion of the arrow to a plane, perpendicular to view ray
|
||||
axis_proj = axis - ray_norm * axis.dot(ray_norm)
|
||||
|
||||
# intersection of the axis with the plane through view origin perpendicular to the arrow projection
|
||||
coords = geometry.intersect_line_plane(base, axis, ray_orig, axis_proj)
|
||||
|
||||
return coords.z
|
||||
|
||||
def exit(self, ctx, cancel):
|
||||
if cancel:
|
||||
self.target_set_value('offset', self.init_value)
|
||||
else:
|
||||
self.group.update(ctx)
|
||||
|
||||
|
||||
class UglyDotGizmo(OffsetHandle, types.Gizmo):
|
||||
"""three orthogonal circles"""
|
||||
bl_idname = "BIM_GT_uglydot_3d"
|
||||
bl_target_properties = (
|
||||
{'id': 'offset', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
'scale_value',
|
||||
'custom_shape',
|
||||
'init_value',
|
||||
'init_coordz',
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
self.custom_shape = self.new_custom_shape(type='TRIS', verts=X3DISC)
|
||||
|
||||
def refresh(self):
|
||||
offset = self.target_get_value('offset') / self.scale_value
|
||||
self.matrix_offset.col[3][2] = offset # z-shift
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
self.draw_custom_shape(self.custom_shape)
|
||||
|
||||
def draw_select(self, ctx, select_id):
|
||||
self.refresh()
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
|
||||
|
||||
class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
|
||||
"""Single dot viewport-aligned"""
|
||||
# FIXME: make it selectable
|
||||
bl_idname = "BIM_GT_dot_2d"
|
||||
bl_target_properties = (
|
||||
{'id': 'offset', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
'scale_value',
|
||||
'custom_shape',
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
shader = DotsGizmoShader()
|
||||
self.custom_shape = shader, shader.batch(pos=((0, 0, 0),)), shader.prog
|
||||
self.use_draw_scale = False
|
||||
|
||||
def refresh(self):
|
||||
offset = self.target_get_value('offset') / self.scale_value
|
||||
self.matrix_offset.col[3][2] = offset # z-shifted
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape)
|
||||
|
||||
def draw_select(self, ctx, select_id):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape, select_id=select_id)
|
||||
|
||||
# doesn't get called
|
||||
# def test_select(self, ctx, location):
|
||||
# pass
|
||||
|
||||
|
||||
class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
|
||||
"""Extrusion guides
|
||||
|
||||
Noninteractive gizmo to indicate extrusion depth and planes.
|
||||
Draws main segment and orthogonal cross at endpoints.
|
||||
"""
|
||||
bl_idname = "BIM_GT_extrusion_guides"
|
||||
bl_target_properties = (
|
||||
{'id': 'depth', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
'scale_value',
|
||||
'custom_shape'
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
shader = ExtrusionGuidesShader()
|
||||
self.custom_shape = shader, shader.batch(pos=((0, 0, 0), (0, 0, 1))), shader.prog
|
||||
self.use_draw_scale = False
|
||||
|
||||
def refresh(self):
|
||||
depth = self.target_get_value('depth') / self.scale_value
|
||||
self.matrix_offset.col[2][2] = depth # z-scaled
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape)
|
||||
|
||||
|
||||
class DimensionLabelGizmo(types.Gizmo):
|
||||
"""Text label for a dimension"""
|
||||
# does not work properly, fonts are totally screwed up
|
||||
|
||||
bl_idname = "BIM_GT_dimension_label"
|
||||
bl_target_properties = (
|
||||
{'id': 'value', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
'text_label'
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
def refresh(self, ctx):
|
||||
value = self.target_get_value('value')
|
||||
self.matrix_offset.col[3][2] = value * .5
|
||||
unit_system = ctx.scene.unit_settings.system
|
||||
self.text_label = bpy.utils.units.to_string(unit_system, 'LENGTH', value, 3, split_unit=False)
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh(ctx)
|
||||
self.draw_text(ctx)
|
||||
|
||||
def draw_text(self, ctx):
|
||||
font_id = 0
|
||||
font_size = 16
|
||||
dpi = ctx.preferences.system.dpi
|
||||
|
||||
# pos = self.matrix_world @ Vector((0, 0, 0, 1))
|
||||
# pos = Vector((0, 0, 0.5))
|
||||
# region = ctx.region
|
||||
# region3d = ctx.region_data
|
||||
# pos = view3d_utils.location_3d_to_region_2d(region, region3d, pos)
|
||||
|
||||
# text = self.text_label
|
||||
|
||||
blf.size(font_id, font_size, dpi)
|
||||
blf.position(font_id, 0, 0, 0)
|
||||
blf.color(font_id, *self.color, self.alpha)
|
||||
blf.draw(font_id, "ABC")
|
||||
|
||||
|
||||
class ExtrusionWidget(types.GizmoGroup):
|
||||
bl_idname = "bim.extrusion_widget"
|
||||
bl_label = "Extrusion Gizmos"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_options = {'3D', 'PERSISTENT', 'SHOW_MODAL_ALL'}
|
||||
|
||||
# FIXME: use proper scale from ifc value to blender units
|
||||
|
||||
@classmethod
|
||||
def poll(cls, ctx):
|
||||
obj = ctx.object
|
||||
return (obj and obj.type == 'MESH'
|
||||
and obj.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth') is not None)
|
||||
|
||||
def setup(self, ctx):
|
||||
target = ctx.object
|
||||
prop = target.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth')
|
||||
|
||||
basis = target.matrix_world.normalized()
|
||||
theme = ctx.preferences.themes[0].user_interface
|
||||
|
||||
gz = self.handle = self.gizmos.new('BIM_GT_uglydot_3d')
|
||||
gz.matrix_basis = basis
|
||||
gz.scale_basis = 0.1
|
||||
gz.color = gz.color_highlight = tuple(theme.gizmo_primary)
|
||||
gz.alpha = 0.5
|
||||
gz.alpha_highlight = 1.0
|
||||
gz.use_draw_modal = True
|
||||
gz.target_set_prop('offset', prop, 'value')
|
||||
gz.scale_value = 1000
|
||||
|
||||
gz = self.guides = self.gizmos.new('BIM_GT_extrusion_guides')
|
||||
gz.matrix_basis = basis
|
||||
gz.color = gz.color_highlight = tuple(theme.gizmo_secondary)
|
||||
gz.alpha = gz.alpha_highlight = 0.5
|
||||
gz.use_draw_modal = True
|
||||
gz.target_set_prop('depth', prop, 'value')
|
||||
gz.scale_value = 1000
|
||||
|
||||
# gz = self.label = self.gizmos.new('GIZMO_GT_dimension_label')
|
||||
# gz.matrix_basis = basis
|
||||
# gz.color = tuple(theme.gizmo_secondary)
|
||||
# gz.alpha = 0.5
|
||||
# gz.use_draw_modal = True
|
||||
# gz.target_set_prop('value', target.demo, 'depth')
|
||||
|
||||
def refresh(self, ctx):
|
||||
"""updating gizmos"""
|
||||
target = ctx.object
|
||||
basis = target.matrix_world.normalized()
|
||||
self.handle.matrix_basis = basis
|
||||
self.guides.matrix_basis = basis
|
||||
|
||||
def update(self, ctx):
|
||||
"""updating object"""
|
||||
bpy.ops.bim.update_parametric_representation()
|
||||
# parameters disappear after update
|
||||
# need to retrieve and rebind them again
|
||||
bpy.ops.bim.get_representation_ifc_parameters()
|
||||
target = ctx.object
|
||||
prop = target.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth')
|
||||
self.handle.target_set_prop('offset', prop, 'value')
|
||||
self.guides.target_set_prop('depth', prop, 'value')
|
||||
@@ -3,6 +3,48 @@ import json
|
||||
import blenderbim.bim.decoration as decoration
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.attribute.data import Data as AttributeData
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
if (
|
||||
obj.mode != "OBJECT"
|
||||
or not obj.data
|
||||
or not isinstance(obj.data, bpy.types.Mesh)
|
||||
or not obj.data.BIMMeshProperties.ifc_definition_id
|
||||
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
||||
):
|
||||
return
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
|
||||
IfcStore.edited_objs.add(obj.name)
|
||||
|
||||
|
||||
def name_callback(obj, data):
|
||||
# Blender material names are up to 63 UTF-8 bytes
|
||||
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name or len(bytes(obj.name, "utf-8")) >= 63:
|
||||
return
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
AttributeData.load(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
|
||||
def subscribe_to(object, data_path, callback):
|
||||
subscribe_to = object.path_resolve(data_path, False)
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_to,
|
||||
owner=object,
|
||||
args=(
|
||||
object,
|
||||
data_path,
|
||||
),
|
||||
notify=callback,
|
||||
options={
|
||||
"PERSISTENT",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@persistent
|
||||
@@ -10,26 +52,50 @@ def loadIfcStore(scene):
|
||||
IfcStore.file = None
|
||||
IfcStore.schema = None
|
||||
props = bpy.context.scene.BIMProperties
|
||||
IfcStore.id_map = {int(k): bpy.data.objects.get(v) for k, v in json.loads(props.id_map).items()} if props.id_map else {}
|
||||
IfcStore.id_map = (
|
||||
{int(k): bpy.data.objects.get(v) for k, v in json.loads(props.id_map).items()} if props.id_map else {}
|
||||
)
|
||||
IfcStore.guid_map = (
|
||||
{k: bpy.data.objects.get(v) for k, v in json.loads(props.guid_map).items()} if props.id_map else {}
|
||||
)
|
||||
|
||||
# Purge data cache
|
||||
from blenderbim.bim import modules
|
||||
|
||||
for module in modules.values():
|
||||
if not module:
|
||||
continue
|
||||
try:
|
||||
getattr(getattr(module, 'data'), 'Data').purge()
|
||||
getattr(getattr(module, "data"), "Data").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
@persistent
|
||||
def ensureIfcExported(scene):
|
||||
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
|
||||
# The invocation pops up a file select window.
|
||||
# This is non-blocking, therefore the Blend file is saved before we export.
|
||||
bpy.ops.export_ifc.bim("INVOKE_DEFAULT", should_force_resave=True)
|
||||
|
||||
|
||||
@persistent
|
||||
def storeIdMap(scene):
|
||||
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
|
||||
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
|
||||
try:
|
||||
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
|
||||
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
|
||||
except:
|
||||
# Regenerate maps. Is there a better solution for this? It seems fragile.
|
||||
file = IfcStore.get_file()
|
||||
IfcStore.id_map = {
|
||||
o.ifc_definition_id: o.name for o in bpy.data.objects if o.BIMObjectProperties.ifc_definition_id
|
||||
}
|
||||
IfcStore.guid_map = {
|
||||
file.by_id(i).GlobalId: n for i, n in IfcStore.id_map.items() if file.by_id(i).is_a("IfcRoot")
|
||||
}
|
||||
# Then attempt to store it again
|
||||
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
|
||||
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
|
||||
|
||||
|
||||
@persistent
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.handler
|
||||
|
||||
|
||||
class IfcStore:
|
||||
@@ -8,6 +9,7 @@ class IfcStore:
|
||||
schema = None
|
||||
id_map = {}
|
||||
guid_map = {}
|
||||
edited_objs = set()
|
||||
pset_template_path = ""
|
||||
pset_template_file = None
|
||||
|
||||
@@ -33,12 +35,16 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
IfcStore.id_map[element.id()] = obj
|
||||
IfcStore.guid_map[element.GlobalId] = obj
|
||||
if hasattr(element, "GlobalId"):
|
||||
IfcStore.guid_map[element.GlobalId] = obj
|
||||
obj.BIMObjectProperties.ifc_definition_id = element.id()
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
|
||||
|
||||
@staticmethod
|
||||
def unlink_element(element, obj=None):
|
||||
del IfcStore.id_map[element.id()]
|
||||
del IfcStore.guid_map[element.GlobalId]
|
||||
if hasattr(element, "GlobalId"):
|
||||
del IfcStore.guid_map[element.GlobalId]
|
||||
if obj:
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,12 @@ def draw_ui(context, layout, obj_type):
|
||||
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
if "GlobalId" not in [a["name"] for a in Data.products[oprops.ifc_definition_id]]:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="STEP ID")
|
||||
row.label(text=str(oprops.ifc_definition_id))
|
||||
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
if attribute["value"] is None or attribute["type"] == "entity":
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
prop.AuginProperties,
|
||||
ui.BIM_PT_augin,
|
||||
operator.AuginLogin,
|
||||
operator.AuginCreateNewModel,
|
||||
operator.AuginReset,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.AuginProperties = bpy.props.PointerProperty(type=prop.AuginProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.AuginProperties
|
||||
@@ -0,0 +1,133 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import string
|
||||
import random
|
||||
import shutil
|
||||
import requests
|
||||
import tempfile
|
||||
import datetime
|
||||
import addon_utils
|
||||
|
||||
|
||||
class AuginLogin(bpy.types.Operator):
|
||||
bl_idname = "bim.augin_login"
|
||||
bl_label = "Login to Augin"
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.AuginProperties
|
||||
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/user_login"
|
||||
payload = {"email": props.username, "password": props.password}
|
||||
response = requests.request("POST", url, data=payload)
|
||||
result = response.json()
|
||||
|
||||
if result["success_feedback"]["success"]:
|
||||
props.token = result["token"]
|
||||
else:
|
||||
self.report({"ERROR"}, "Login failed :(")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AuginReset(bpy.types.Operator):
|
||||
bl_idname = "bim.augin_reset"
|
||||
bl_label = "Upload Another Project"
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.AuginProperties
|
||||
props.is_success = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AuginCreateNewModel(bpy.types.Operator):
|
||||
bl_idname = "bim.augin_create_new_model"
|
||||
bl_label = "Create New Augin Model"
|
||||
|
||||
def execute(self, context):
|
||||
import boto3
|
||||
from botocore.config import Config
|
||||
props = bpy.context.scene.AuginProperties
|
||||
|
||||
# Create project
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/new_model"
|
||||
|
||||
chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
|
||||
props.project_filename = "BBM{}{}".format(
|
||||
datetime.datetime.now().strftime("%Y%m%d%H%M%S"), "".join(random.choice(chars) for _ in range(6))
|
||||
)
|
||||
addon_version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
|
||||
payload = {
|
||||
"user_token": props.token,
|
||||
"partner_token": "Mmf2zpxdgrkHaUNRm6Cv7GLJdBAeb7Rn",
|
||||
"project_name": props.project_name,
|
||||
"model_filename": props.project_filename + ".ifc",
|
||||
"thumb_filename": props.project_filename + ".png",
|
||||
"app_name": "BlenderBIM",
|
||||
"sw_version": bpy.app.version_string,
|
||||
"plugin_version": addon_version,
|
||||
}
|
||||
response = requests.request("POST", url, data=payload)
|
||||
result = response.json()
|
||||
|
||||
# Upload to S3
|
||||
my_config = Config(
|
||||
region_name=result["s3_region"],
|
||||
)
|
||||
|
||||
client = boto3.client(
|
||||
"s3",
|
||||
aws_access_key_id=result["s3_key"],
|
||||
aws_secret_access_key=result["s3_secret"],
|
||||
aws_session_token=result["s3_token"],
|
||||
config=my_config,
|
||||
)
|
||||
|
||||
# Generate thumb
|
||||
old_file_format = context.scene.render.image_settings.file_format
|
||||
old_filepath = context.scene.render.filepath
|
||||
|
||||
cam = bpy.data.cameras.new("Camera")
|
||||
cam_obj = bpy.data.objects.new("Camera", cam)
|
||||
context.scene.collection.objects.link(cam_obj)
|
||||
context.scene.camera = cam_obj
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
thumb_path = os.path.join(tmpdir, "thumb.png")
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
context.scene.render.filepath = thumb_path
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
|
||||
context.scene.render.image_settings.file_format = old_file_format
|
||||
context.scene.render.filepath = old_filepath
|
||||
|
||||
client.upload_file(bpy.context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
|
||||
client.upload_file(thumb_path, result["s3_bucket"], result["thumb_path"])
|
||||
|
||||
|
||||
# Notify done
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/files_uploaded"
|
||||
payload = {
|
||||
"user_token": props.token,
|
||||
"ifc_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
|
||||
"model_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
|
||||
"thumb_filesize": os.path.getsize(thumb_path),
|
||||
"model_upload_path": result["model_path"],
|
||||
"thumb_upload_path": result["thumb_path"],
|
||||
"model_id": result["model_id"],
|
||||
}
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
response = requests.request("POST", url, headers=headers, data=payload)
|
||||
|
||||
shutil.rmtree(tmpdir)
|
||||
props.is_success = True
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,11 @@
|
||||
import bpy.props
|
||||
import bpy.types
|
||||
|
||||
|
||||
class AuginProperties(bpy.types.PropertyGroup):
|
||||
username: bpy.props.StringProperty(name="Username")
|
||||
password: bpy.props.StringProperty(name="Password")
|
||||
token: bpy.props.StringProperty(name="Token")
|
||||
project_name: bpy.props.StringProperty(name="Project Name")
|
||||
project_filename: bpy.props.StringProperty(name="IFC Filename")
|
||||
is_success: bpy.props.BoolProperty(name="Is Successful Upload", default=False)
|
||||
@@ -0,0 +1,45 @@
|
||||
import bpy.types
|
||||
|
||||
|
||||
class BIM_PT_augin(bpy.types.Panel):
|
||||
bl_label = "Augin"
|
||||
bl_idname = "BIM_PT_augin"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
|
||||
scene = context.scene
|
||||
props = scene.AuginProperties
|
||||
|
||||
if not props.token:
|
||||
row = layout.row()
|
||||
row.prop(props, "username")
|
||||
row = layout.row()
|
||||
row.prop(props, "password")
|
||||
row = layout.row()
|
||||
row.operator("bim.augin_login")
|
||||
return
|
||||
|
||||
row = layout.row()
|
||||
row.label(text="Logged in as " + props.username)
|
||||
|
||||
if not bpy.context.scene.BIMProperties.ifc_file:
|
||||
row = layout.row()
|
||||
row.label(text="No IFC Found")
|
||||
return
|
||||
|
||||
if props.is_success:
|
||||
row = layout.row()
|
||||
row.label(text="Upload Successful")
|
||||
row = layout.row()
|
||||
row.operator("bim.augin_reset")
|
||||
else:
|
||||
row = layout.row()
|
||||
row.prop(props, "project_name")
|
||||
row = layout.row()
|
||||
row.operator("bim.augin_create_new_model")
|
||||
@@ -3,9 +3,14 @@ import bpy
|
||||
import tempfile
|
||||
import webbrowser
|
||||
import ifcopenshell
|
||||
import bimtester
|
||||
import bimtester.run
|
||||
import bimtester.reports
|
||||
|
||||
try:
|
||||
import bimtester
|
||||
import bimtester.run
|
||||
import bimtester.reports
|
||||
except:
|
||||
print("Failed to load BIMTester. Try disabling other add-ons, in particular Blender-OSM. See bug #1318.")
|
||||
|
||||
from pathlib import Path
|
||||
from itertools import cycle
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -31,7 +36,7 @@ class ExecuteBIMTester(bpy.types.Operator):
|
||||
"schema_file": "",
|
||||
"schema_name": "",
|
||||
"lang": "en",
|
||||
"steps": props.steps
|
||||
"steps": props.steps,
|
||||
}
|
||||
use_stored_ifc = props.should_load_from_memory and IfcStore.get_file()
|
||||
runner = bimtester.run.TestRunner(args["ifc"], ifc=IfcStore.get_file() if use_stored_ifc else None)
|
||||
@@ -59,9 +64,9 @@ class BIMTesterPurge(bpy.types.Operator):
|
||||
|
||||
class SelectFeature(bpy.types.Operator):
|
||||
bl_idname = "bim.select_feature"
|
||||
bl_label = "Select Feature"
|
||||
bl_label = "Select Feature / IDS"
|
||||
filename_ext = ".feature"
|
||||
filter_glob: bpy.props.StringProperty(default="*.feature", options={"HIDDEN"})
|
||||
filter_glob: bpy.props.StringProperty(default="*.feature;*.xml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from blenderbim.bim.module.root.prop import getIfcClasses
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -41,7 +40,7 @@ def refreshScenarios(self, context):
|
||||
|
||||
|
||||
class BimTesterProperties(PropertyGroup):
|
||||
feature: StringProperty(default="", name="Feature File", update=refreshScenarios)
|
||||
feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios)
|
||||
steps: StringProperty(default="", name="Custom Steps")
|
||||
ifc_file: StringProperty(default="", name="IFC File")
|
||||
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
|
||||
|
||||
@@ -42,7 +42,6 @@ class BIM_PT_constraints(Panel):
|
||||
|
||||
if self.props.active_constraint_id:
|
||||
self.draw_editable_ui(context)
|
||||
return
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
for attribute in self.props.constraint_attributes:
|
||||
|
||||
@@ -18,10 +18,7 @@ class Data:
|
||||
cls.contexts = {}
|
||||
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
subcontexts = {}
|
||||
# See bug #1224 for why we don't use HasSubContexts
|
||||
for subcontext in file.by_type("IfcGeometricRepresentationSubContext"):
|
||||
if subcontext.ParentContext != context:
|
||||
continue
|
||||
for subcontext in context.HasSubContexts:
|
||||
subcontexts[int(subcontext.id())] = {
|
||||
"ContextType": subcontext.ContextType,
|
||||
"ContextIdentifier": subcontext.ContextIdentifier,
|
||||
|
||||
@@ -13,8 +13,11 @@ class ProfileImportIFC(bpy.types.Operator):
|
||||
import cProfile
|
||||
import pstats
|
||||
|
||||
# For Windows
|
||||
filepath = bpy.context.scene.BIMProperties.ifc_file.replace('\\', '\\\\')
|
||||
|
||||
cProfile.run(
|
||||
f"import bpy; bpy.ops.import_ifc.bim(filepath='{bpy.context.scene.BIMProperties.ifc_file}')", "blender.prof"
|
||||
f"import bpy; bpy.ops.import_ifc.bim(filepath='{filepath}')", "blender.prof"
|
||||
)
|
||||
p = pstats.Stats("blender.prof")
|
||||
p.sort_stats("cumulative").print_stats(50)
|
||||
@@ -84,9 +87,6 @@ class SelectHighPolygonMeshes(bpy.types.Operator):
|
||||
except:
|
||||
# If it is not in the view layer
|
||||
pass
|
||||
relating_type = obj.BIMObjectProperties.relating_type
|
||||
if relating_type:
|
||||
relating_type.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -121,6 +121,8 @@ class InspectFromStepId(bpy.types.Operator):
|
||||
bpy.context.scene.BIMDebugProperties.attributes.remove(0)
|
||||
while len(bpy.context.scene.BIMDebugProperties.inverse_attributes) > 0:
|
||||
bpy.context.scene.BIMDebugProperties.inverse_attributes.remove(0)
|
||||
while len(bpy.context.scene.BIMDebugProperties.inverse_references) > 0:
|
||||
bpy.context.scene.BIMDebugProperties.inverse_references.remove(0)
|
||||
for key, value in element.get_info().items():
|
||||
self.add_attribute(bpy.context.scene.BIMDebugProperties.attributes, key, value)
|
||||
for key in dir(element):
|
||||
@@ -132,6 +134,10 @@ class InspectFromStepId(bpy.types.Operator):
|
||||
):
|
||||
continue
|
||||
self.add_attribute(bpy.context.scene.BIMDebugProperties.inverse_attributes, key, getattr(element, key))
|
||||
for inverse in self.file.get_inverse(element):
|
||||
new = bpy.context.scene.BIMDebugProperties.inverse_references.add()
|
||||
new.string_value = str(inverse)
|
||||
new.int_value = inverse.id()
|
||||
return {"FINISHED"}
|
||||
|
||||
def add_attribute(self, prop, key, value):
|
||||
|
||||
@@ -19,3 +19,4 @@ class BIMDebugProperties(PropertyGroup):
|
||||
step_id_breadcrumb: CollectionProperty(name="STEP ID Breadcrumb", type=StrProperty)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
inverse_attributes: CollectionProperty(name="Inverse Attributes", type=Attribute)
|
||||
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
|
||||
|
||||
@@ -69,3 +69,14 @@ class BIM_PT_debug(Panel):
|
||||
row.operator(
|
||||
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
|
||||
).step_id = attribute.int_value
|
||||
|
||||
if props.inverse_references:
|
||||
layout.label(text="Inverse references:")
|
||||
|
||||
for index, attribute in enumerate(props.inverse_references):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "string_value", text="")
|
||||
if attribute.int_value:
|
||||
row.operator(
|
||||
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
|
||||
).step_id = attribute.int_value
|
||||
|
||||
@@ -41,12 +41,7 @@ class BIM_PT_documents(Panel):
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_documents",
|
||||
"",
|
||||
self.props,
|
||||
"documents",
|
||||
self.props,
|
||||
"active_document_index",
|
||||
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index"
|
||||
)
|
||||
|
||||
if self.props.active_document_id:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import bpy
|
||||
from . import ui, operator
|
||||
|
||||
classes = (
|
||||
operator.AddDrawing,
|
||||
operator.CreateDrawing,
|
||||
ui.BIM_PT_camera,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
@@ -0,0 +1,89 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import subprocess
|
||||
import webbrowser
|
||||
from blenderbim.bim.operator import open_with_user_command
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.group.data import Data as GroupData
|
||||
from blenderbim.bim.module.pset.data import Data as PsetData
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
class AddDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.add_drawing"
|
||||
bl_label = "Add Drawing"
|
||||
|
||||
def execute(self, context):
|
||||
new = context.scene.DocProperties.drawings.add()
|
||||
new.name = "DRAWING {}".format(len(context.scene.DocProperties.drawings))
|
||||
if not bpy.data.collections.get("Views"):
|
||||
context.scene.collection.children.link(bpy.data.collections.new("Views"))
|
||||
views_collection = bpy.data.collections.get("Views")
|
||||
view_collection = bpy.data.collections.new("IfcGroup/" + new.name)
|
||||
views_collection.children.link(view_collection)
|
||||
camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name))
|
||||
camera.location = (0, 0, 1.7) # The view shall be 1.7m above the origin
|
||||
camera.data.type = "ORTHO"
|
||||
camera.data.ortho_scale = 50 # The default of 6m is too small
|
||||
camera.data.clip_end = 10 # A slightly more reasonable default
|
||||
if context.scene.unit_settings.system == "IMPERIAL":
|
||||
camera.data.BIMCameraProperties.diagram_scale = '1/8"=1\'-0"|1/96'
|
||||
else:
|
||||
camera.data.BIMCameraProperties.diagram_scale = "1:100|1/100"
|
||||
context.scene.camera = camera
|
||||
view_collection.objects.link(camera)
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
new.camera = camera
|
||||
bpy.ops.bim.assign_class(obj=camera.name, ifc_class="IfcAnnotation", predefined_type="DRAWING")
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
|
||||
bpy.ops.bim.add_group()
|
||||
bpy.ops.bim.assign_group(product=camera.name, group=sorted(GroupData.groups.keys())[-1])
|
||||
bpy.ops.bim.add_pset(obj=camera.name, obj_type="Object", pset_name="EPset_Drawing")
|
||||
pset_id = sorted(PsetData.products[camera.BIMObjectProperties.ifc_definition_id]["psets"])[-1]
|
||||
bpy.ops.bim.edit_pset(
|
||||
obj=camera.name,
|
||||
obj_type="Object",
|
||||
pset_id=pset_id,
|
||||
properties=json.dumps({"TargetView": "PLAN_VIEW", "Scale": "1/100"}),
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CreateDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.create_drawing"
|
||||
bl_label = "Create Drawing"
|
||||
|
||||
def execute(self, context):
|
||||
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
|
||||
subprocess.run(
|
||||
[
|
||||
ifcconvert_path,
|
||||
context.scene.BIMProperties.ifc_file,
|
||||
"-yv",
|
||||
context.scene.BIMProperties.ifc_file[0:-4] + ".svg",
|
||||
"--plan",
|
||||
"--model",
|
||||
"--section-height-from-storeys",
|
||||
"--section-height=1.2",
|
||||
"--door-arcs",
|
||||
"--print-space-names",
|
||||
"--print-space-areas",
|
||||
"--bounds=1024x1024",
|
||||
"--elevation-ref=DRAWING",
|
||||
"--svg-xmlns",
|
||||
"--svg-project",
|
||||
"--svg-poly",
|
||||
"--exclude",
|
||||
"entities",
|
||||
"IfcSpace",
|
||||
"IfcOpeningElement",
|
||||
]
|
||||
)
|
||||
open_with_user_command(
|
||||
bpy.context.preferences.addons["blenderbim"].preferences.svg_command,
|
||||
context.scene.BIMProperties.ifc_file[0:-4] + ".svg",
|
||||
)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,108 @@
|
||||
import os
|
||||
import bpy
|
||||
from bpy.types import Panel
|
||||
from bpy.props import StringProperty, BoolProperty
|
||||
|
||||
|
||||
class BIM_PT_camera(Panel):
|
||||
bl_label = "Drawing Generation"
|
||||
bl_idname = "BIM_PT_camera"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "data"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
engine = context.engine
|
||||
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
if "/" not in context.active_object.name:
|
||||
layout.label(text="This is not a BIM camera.")
|
||||
return
|
||||
|
||||
layout.use_property_split = True
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
props = context.active_object.data.BIMCameraProperties
|
||||
|
||||
layout.label(text="Generation Options:")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_recut")
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_recut_selected")
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_extract")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "is_nts")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.generate_references")
|
||||
row = layout.row()
|
||||
row.operator("bim.resize_text")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "target_view")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "cut_objects")
|
||||
if props.cut_objects == "CUSTOM":
|
||||
row = layout.row()
|
||||
row.prop(props, "cut_objects_custom")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_x")
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_y")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "diagram_scale")
|
||||
if props.diagram_scale == "CUSTOM":
|
||||
row = layout.row()
|
||||
row.prop(props, "custom_diagram_scale")
|
||||
|
||||
layout.label(text="Drawing Styles:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing_style")
|
||||
|
||||
if dprops.drawing_styles:
|
||||
layout.template_list("BIM_UL_generic", "", dprops, "drawing_styles", props, "active_drawing_style_index")
|
||||
|
||||
if props.active_drawing_style_index < len(dprops.drawing_styles):
|
||||
drawing_style = dprops.drawing_styles[props.active_drawing_style_index]
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "name")
|
||||
row.operator("bim.remove_drawing_style", icon="X", text="").index = props.active_drawing_style_index
|
||||
|
||||
row = layout.row()
|
||||
row.prop(drawing_style, "render_type")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "vector_style")
|
||||
row.operator("bim.edit_vector_style", text="", icon="GREASEPENCIL")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "include_query")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "exclude_query")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_drawing_style_attribute")
|
||||
|
||||
for index, attribute in enumerate(drawing_style.attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_drawing_style_attribute", icon="X", text="").index = index
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.save_drawing_style")
|
||||
row.operator("bim.activate_drawing_style")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.cut_section", text="Create Drawing")
|
||||
row.operator("bim.create_drawing", text="Create Drawing 2.0")
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op.view = context.active_object.name.split("/")[1]
|
||||
@@ -4,6 +4,7 @@ from . import ui, prop, operator
|
||||
classes = (
|
||||
operator.EditObjectPlacement,
|
||||
operator.AddRepresentation,
|
||||
operator.MapRepresentations,
|
||||
operator.MapRepresentation,
|
||||
operator.SwitchRepresentation,
|
||||
operator.RemoveRepresentation,
|
||||
|
||||
@@ -2,6 +2,7 @@ import bpy
|
||||
import bmesh
|
||||
import ifcopenshell.util.unit
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
|
||||
|
||||
class Usecase:
|
||||
@@ -12,6 +13,7 @@ class Usecase:
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"blender_object": None, # This is (currently) a Blender object, hence this depends on Blender now
|
||||
"geometry": None, # This is (currently) a Blender data object, hence this depends on Blender now
|
||||
"coordinate_offset": None, # Optionally apply a vector offset to all coordinates
|
||||
"total_items": 1, # How many representation items to create
|
||||
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
|
||||
"should_force_faceted_brep": False, # If we should force faceted breps for meshes
|
||||
@@ -19,13 +21,20 @@ class Usecase:
|
||||
"is_wireframe": False, # If the geometry is a wireframe
|
||||
"is_curve": False, # If the geometry is a Blender curve
|
||||
"is_point_cloud": False, # If the geometry is a point cloud
|
||||
# Possible IFC representation classes:
|
||||
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcCircleProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcArbitraryProfileDef
|
||||
"ifc_representation_class": None, # Whether to cast a mesh into a particular class
|
||||
}
|
||||
self.ifc_vertices = []
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.evaluate_geometry()
|
||||
if isinstance(self.settings["geometry"], bpy.types.Mesh):
|
||||
self.evaluate_geometry()
|
||||
if self.settings["unit_scale"] is None:
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
if self.settings["context"].ContextType == "Model":
|
||||
@@ -123,8 +132,54 @@ class Usecase:
|
||||
return self.create_curve_representation()
|
||||
elif self.settings["is_point_cloud"]:
|
||||
return self.create_point_cloud_representation()
|
||||
elif isinstance(self.settings["geometry"], bpy.types.Camera):
|
||||
return self.create_camera_block_representation()
|
||||
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcRectangleProfileDef":
|
||||
return self.create_rectangle_extrusion_representation()
|
||||
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcCircleProfileDef":
|
||||
return self.create_circle_extrusion_representation()
|
||||
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef":
|
||||
return self.create_arbitrary_extrusion_representation()
|
||||
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids":
|
||||
return self.create_arbitrary_void_extrusion_representation()
|
||||
return self.create_mesh_representation()
|
||||
|
||||
def create_camera_block_representation(self):
|
||||
raster_x = self.settings["geometry"].BIMCameraProperties.raster_x
|
||||
raster_y = self.settings["geometry"].BIMCameraProperties.raster_y
|
||||
|
||||
if self.is_camera_landscape():
|
||||
width = self.settings["geometry"].ortho_scale
|
||||
height = width / raster_x * raster_y
|
||||
else:
|
||||
height = self.settings["geometry"].ortho_scale
|
||||
width = height / raster_y * raster_x
|
||||
|
||||
block = self.file.create_entity(
|
||||
"IfcBlock",
|
||||
**{
|
||||
"Position": self.file.createIfcAxis2Placement3D(
|
||||
self.create_cartesian_point(-width / 2, -height / 2, -self.settings["geometry"].clip_end)
|
||||
),
|
||||
"XLength": self.convert_si_to_unit(width),
|
||||
"YLength": self.convert_si_to_unit(height),
|
||||
"ZLength": self.convert_si_to_unit(self.settings["geometry"].clip_end),
|
||||
}
|
||||
)
|
||||
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"CSG",
|
||||
[self.file.createIfcCsgSolid(block)],
|
||||
)
|
||||
|
||||
def is_camera_landscape(self):
|
||||
return (
|
||||
self.settings["geometry"].BIMCameraProperties.raster_x
|
||||
> self.settings["geometry"].BIMCameraProperties.raster_y
|
||||
)
|
||||
|
||||
def create_curve3d_representation(self):
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
@@ -214,6 +269,56 @@ class Usecase:
|
||||
results.append(self.file.createIfcPolyline(points))
|
||||
return results
|
||||
|
||||
def create_rectangle_extrusion_representation(self):
|
||||
helper = Helper(self.file)
|
||||
indices = helper.auto_detect_rectangle_profile_extruded_area_solid(self.settings["geometry"])
|
||||
profile_def = helper.create_rectangle_profile_def(self.settings["geometry"], indices["profile"])
|
||||
item = helper.create_extruded_area_solid(self.settings["geometry"], indices["extrusion"], profile_def)
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"SweptSolid",
|
||||
[item],
|
||||
)
|
||||
|
||||
def create_circle_extrusion_representation(self):
|
||||
helper = Helper(self.file)
|
||||
indices = helper.auto_detect_circle_profile_extruded_area_solid(self.settings["geometry"])
|
||||
profile_def = helper.create_circle_profile_def(self.settings["geometry"], indices["profile"])
|
||||
item = helper.create_extruded_area_solid(self.settings["geometry"], indices["extrusion"], profile_def)
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"SweptSolid",
|
||||
[item],
|
||||
)
|
||||
|
||||
def create_arbitrary_extrusion_representation(self):
|
||||
helper = Helper(self.file)
|
||||
indices = helper.auto_detect_arbitrary_closed_profile_extruded_area_solid(self.settings["geometry"])
|
||||
profile_def = helper.create_arbitrary_closed_profile_def(self.settings["geometry"], indices["profile"])
|
||||
item = helper.create_extruded_area_solid(self.settings["geometry"], indices["extrusion"], profile_def)
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"SweptSolid",
|
||||
[item],
|
||||
)
|
||||
|
||||
def create_arbitrary_void_extrusion_representation(self):
|
||||
helper = Helper(self.file)
|
||||
indices = helper.auto_detect_arbitrary_profile_with_voids_extruded_area_solid(self.settings["geometry"])
|
||||
profile_def = helper.create_arbitrary_profile_def_with_voids(
|
||||
self.settings["geometry"], indices["profile"], indices["inner_curves"]
|
||||
)
|
||||
item = helper.create_extruded_area_solid(self.settings["geometry"], indices["extrusion"], profile_def)
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"SweptSolid",
|
||||
[item],
|
||||
)
|
||||
|
||||
def create_mesh_representation(self):
|
||||
if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]:
|
||||
return self.create_faceted_brep()
|
||||
@@ -290,6 +395,8 @@ class Usecase:
|
||||
return self.file.createIfcCartesianPointList3D([self.convert_si_to_unit(v.co) for v in vertices])
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
if self.settings["coordinate_offset"]:
|
||||
return (co / self.settings["unit_scale"]) + self.settings["coordinate_offset"]
|
||||
return co / self.settings["unit_scale"]
|
||||
|
||||
def create_geometric_curve_set_representation(self, is_2d=False):
|
||||
@@ -315,3 +422,30 @@ class Usecase:
|
||||
"BoundingBox",
|
||||
[bounding_box],
|
||||
)
|
||||
|
||||
def create_structural_reference_representation(self):
|
||||
if self.settings["blender_object"].type == "EMPTY":
|
||||
return self.file.createIfcTopologyRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"Vertex",
|
||||
[self.create_vertex_point(Vector((0, 0, 0)))],
|
||||
)
|
||||
return self.file.createIfcTopologyRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"Edge",
|
||||
[self.create_edge()],
|
||||
)
|
||||
|
||||
def create_vertex_point(self, point):
|
||||
return self.file.createIfcVertexPoint(self.create_cartesian_point(point.x, point.y, point.z))
|
||||
|
||||
def create_edge(self):
|
||||
if hasattr(self.settings["geometry"], "splines"):
|
||||
points = self.get_spline_points(self.settings["geometry"].splines[0])
|
||||
else:
|
||||
points = self.settings["geometry"].vertices
|
||||
if not points:
|
||||
return
|
||||
return self.file.createIfcEdge(self.create_vertex_point(points[0].co), self.create_vertex_point(points[1].co))
|
||||
|
||||
@@ -17,9 +17,15 @@ class Data:
|
||||
return
|
||||
cls.products[product_id] = []
|
||||
product = file.by_id(product_id)
|
||||
if not hasattr(product, "Representation") or not product.Representation:
|
||||
return
|
||||
for representation in product.Representation.Representations:
|
||||
representations = []
|
||||
if product.is_a("IfcProduct"):
|
||||
if product.Representation:
|
||||
representations = product.Representation.Representations
|
||||
else:
|
||||
representations = []
|
||||
elif product.is_a("IfcTypeProduct"):
|
||||
representations = [rm.MappedRepresentation for rm in product.RepresentationMaps or []]
|
||||
for representation in representations:
|
||||
c = representation.ContextOfItems
|
||||
rep_id = int(representation.id())
|
||||
cls.representations[rep_id] = {
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
from math import pi
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
|
||||
class Helper:
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
# We can detect a rectangular extrusion by picking any face, then find an
|
||||
# edge that shares a single vertex only with that face to find the extrusion
|
||||
# edge. A face with the normal facing down is prioritised. A limited
|
||||
# dissolve ensure that faces are quads and not tris.
|
||||
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
face = None
|
||||
for face in bm.faces:
|
||||
if face.normal.z < -0.1:
|
||||
break
|
||||
profile = [l.vert.index for l in face.loops]
|
||||
extrusion = self.detect_extrusion_edge(bm, face)
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
|
||||
return {"profile": profile, "extrusion": extrusion}
|
||||
|
||||
# After a limited dissolve, we detect the circle profile as it should be the
|
||||
# only ngon (this assumes the circle has at least a facetation of > 4 edges.
|
||||
# The extrusion direction is any edge that only shares a single vertex with
|
||||
# the profile. We prioritise the profile that has a downwards normal.
|
||||
def auto_detect_circle_profile_extruded_area_solid(self, mesh):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
for face in bm.faces:
|
||||
if len(face.verts) > 4:
|
||||
potential_faces.append(face)
|
||||
for face in potential_faces:
|
||||
if face.normal.z < -0.1:
|
||||
break
|
||||
|
||||
profile = [l.vert.index for l in face.loops]
|
||||
extrusion = self.detect_extrusion_edge(bm, face)
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
|
||||
return {"profile": profile, "extrusion": extrusion}
|
||||
|
||||
# After a limited dissolve, the arbitrary profile is any ngon or tri.
|
||||
# Failing that, it is equivalent to a rectangular profile. The extrusion
|
||||
# direction is any edge that only shares a single vertex with the profile.
|
||||
# We prioritise the profile that has a downwards normal.
|
||||
def auto_detect_arbitrary_closed_profile_extruded_area_solid(self, mesh):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
for face in bm.faces:
|
||||
total_verts = len(face.verts)
|
||||
if total_verts > 4 or total_verts == 3:
|
||||
potential_faces.append(face)
|
||||
|
||||
if not potential_faces:
|
||||
potential_faces = bm.faces
|
||||
|
||||
for face in potential_faces:
|
||||
if face.normal.z < -0.1:
|
||||
break
|
||||
|
||||
profile = [l.vert.index for l in face.loops]
|
||||
extrusion = self.detect_extrusion_edge(bm, face)
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
|
||||
return {"profile": profile, "extrusion": extrusion}
|
||||
|
||||
# An arbitrary closed profile with voids is similar to one without voids.
|
||||
# We start the same way with any ngon (no tri), but instead of being the entire
|
||||
# profile, it is only one of the possible faces that make up the end of our
|
||||
# extrusion. Then we find all faces with the same normal. This creates a set
|
||||
# of faces that define the end of our extrusion. From this set of faces, we
|
||||
# need to then extract the outer loop and inner loops. First, all loops are
|
||||
# detected from the faces. Any edge that is not shared between other faces
|
||||
# in the set must be part of either an inner or outer loop. This gives us a
|
||||
# set of edges. Then, connected edges (i.e. sharing a vertex) are joined to
|
||||
# form distinct loops. Finally, the outer loop is distinguished by being
|
||||
# the loop with the greatest area.
|
||||
def auto_detect_arbitrary_profile_with_voids_extruded_area_solid(self, mesh):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
for face in bm.faces:
|
||||
total_verts = len(face.verts)
|
||||
if total_verts > 4:
|
||||
potential_faces.append(face)
|
||||
|
||||
for face in potential_faces:
|
||||
if face.normal.z < -0.1:
|
||||
break
|
||||
|
||||
end_faces = []
|
||||
end_face_normal = face.normal
|
||||
for face in bm.faces:
|
||||
if (face.normal - end_face_normal).length < 0.001:
|
||||
end_faces.append(face)
|
||||
|
||||
loop_edges = set()
|
||||
for face in end_faces:
|
||||
potential_edges = set(face.edges)
|
||||
for face2 in end_faces:
|
||||
if face == face2:
|
||||
continue
|
||||
potential_edges -= set(face2.edges)
|
||||
loop_edges |= potential_edges
|
||||
|
||||
# Create loops from edges
|
||||
loops = []
|
||||
while loop_edges:
|
||||
edge = loop_edges.pop()
|
||||
loop = [edge]
|
||||
has_found_connected_edge = True
|
||||
while has_found_connected_edge:
|
||||
has_found_connected_edge = False
|
||||
for edge in loop_edges.copy():
|
||||
edge_verts = set(edge.verts)
|
||||
if edge_verts & set(loop[0].verts):
|
||||
loop.insert(0, edge)
|
||||
loop_edges.remove(edge)
|
||||
has_found_connected_edge = True
|
||||
elif edge_verts & set(loop[-1].verts):
|
||||
loop.append(edge)
|
||||
loop_edges.remove(edge)
|
||||
has_found_connected_edge = True
|
||||
loops.append(loop)
|
||||
|
||||
# Determine outer loop
|
||||
max_area = 0
|
||||
outer_loop = None
|
||||
inner_loops = []
|
||||
|
||||
for loop in loops:
|
||||
loop_vertices = []
|
||||
total_edges = len(loop)
|
||||
for i, edge in enumerate(loop):
|
||||
if i + 1 == total_edges and edge.verts[0] in loop[i - 1].verts:
|
||||
loop_vertices.append(edge.verts[0])
|
||||
elif i + 1 == total_edges and edge.verts[1] in loop[i - 1].verts:
|
||||
loop_vertices.append(edge.verts[1])
|
||||
elif edge.verts[0] in loop[i + 1].verts:
|
||||
loop_vertices.append(edge.verts[1])
|
||||
elif edge.verts[1] in loop[i + 1].verts:
|
||||
loop_vertices.append(edge.verts[0])
|
||||
|
||||
loop_bm = bmesh.new()
|
||||
for vert in loop_vertices:
|
||||
loop_bm.verts.new(vert.co)
|
||||
face = loop_bm.faces.new(loop_bm.verts)
|
||||
|
||||
loop_vertex_indices = [v.index for v in loop_vertices]
|
||||
face_area = face.calc_area()
|
||||
if face_area > max_area:
|
||||
max_area = face_area
|
||||
outer_loop = loop_vertex_indices
|
||||
inner_loops.append(loop_vertex_indices)
|
||||
loop_bm.free()
|
||||
|
||||
inner_loops.remove(outer_loop)
|
||||
|
||||
extrusion = self.detect_extrusion_edge(bm, end_faces[0])
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
|
||||
return {"profile": outer_loop, "inner_curves": inner_loops, "extrusion": extrusion}
|
||||
|
||||
# An extrusion edge is an edge that shares a single vertex with a profile
|
||||
# face and is not parallel to the face.
|
||||
def detect_extrusion_edge(self, bm, profile_face):
|
||||
bm.edges.ensure_lookup_table()
|
||||
extrusion = None
|
||||
face_verts_set = set(profile_face.verts)
|
||||
for edge in bm.edges:
|
||||
edge_vector = edge.verts[1].co - edge.verts[0].co
|
||||
unshared_verts = set(edge.verts) - face_verts_set
|
||||
if len(unshared_verts) == 1 and not (edge_vector.angle(profile_face.normal) - pi / 2 < 0.001):
|
||||
if unshared_verts.pop() == edge.verts[1]:
|
||||
return [edge.verts[0].index, edge.verts[1].index]
|
||||
return [edge.verts[1].index, edge.verts[0].index]
|
||||
|
||||
def create_extruded_area_solid(self, mesh, extrusion_indices, profile_def):
|
||||
position = self.create_ifc_axis_2_placement_3d(
|
||||
profile_def["curve_ucs"]["center"], profile_def["curve_ucs"]["z_axis"], profile_def["curve_ucs"]["x_axis"]
|
||||
)
|
||||
direction = self.get_extrusion_direction(mesh, extrusion_indices, profile_def["curve_ucs"])
|
||||
unit_direction = direction.normalized()
|
||||
return self.file.createIfcExtrudedAreaSolid(
|
||||
profile_def["curve"],
|
||||
position,
|
||||
self.file.createIfcDirection((unit_direction.x, unit_direction.y, unit_direction.z)),
|
||||
self.convert_si_to_unit(direction.length),
|
||||
)
|
||||
|
||||
def create_arbitrary_closed_profile_def(self, mesh, profile_indices):
|
||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||
outer_curve = self.create_polyline_from_loop(mesh, profile_indices, curve_ucs)
|
||||
curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve)
|
||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||
|
||||
def create_arbitrary_profile_def_with_voids(self, mesh, profile_indices, inner_curve_indices):
|
||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||
outer_curve = self.create_polyline_from_loop(mesh, profile_indices, curve_ucs)
|
||||
inner_curves = [
|
||||
self.create_polyline_from_loop(mesh, curve_indices, curve_ucs) for curve_indices in inner_curve_indices
|
||||
]
|
||||
curve = self.file.createIfcArbitraryProfileDefWithVoids("AREA", None, outer_curve, inner_curves)
|
||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||
|
||||
def create_rectangle_profile_def(self, mesh, profile_indices):
|
||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||
xdim = self.convert_si_to_unit(
|
||||
(mesh.vertices[profile_indices[0]].co - mesh.vertices[profile_indices[1]].co).length
|
||||
)
|
||||
ydim = self.convert_si_to_unit(
|
||||
(mesh.vertices[profile_indices[1]].co - mesh.vertices[profile_indices[2]].co).length
|
||||
)
|
||||
position = None
|
||||
if self.file.schema == "IFC2X3":
|
||||
position = self.file.createIfcAxis2Placement2D(self.file.createIfcCartesianPoint([0.0, 0.0, 0.0]))
|
||||
curve = self.file.createIfcRectangleProfileDef("AREA", None, position, xdim, ydim)
|
||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||
|
||||
def create_circle_profile_def(self, mesh, profile_indices):
|
||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||
radius = self.convert_si_to_unit(
|
||||
abs(
|
||||
(
|
||||
mesh.vertices[profile_indices[0]].co
|
||||
- mesh.vertices[profile_indices[int(len(profile_indices) / 2)]].co
|
||||
).length
|
||||
)
|
||||
/ 2
|
||||
)
|
||||
center = Vector((0, 0))
|
||||
position = self.create_ifc_axis_2_placement_2d(center, Vector((1, 0)))
|
||||
curve = self.file.createIfcCircleProfileDef("AREA", None, position, radius)
|
||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||
|
||||
# Not used anywhere, but probably useful in the future
|
||||
def get_loop_from_v_indices(self, obj, indices):
|
||||
edges = self.get_edges_in_v_indices(obj, indices)
|
||||
loop = self.get_loop_from_edges(edges)
|
||||
loop.pop(-1)
|
||||
return loop
|
||||
|
||||
def get_loop_from_edges(self, edges):
|
||||
while edges:
|
||||
currentEdge = edges.pop()
|
||||
startVert = currentEdge.vertices[0]
|
||||
endVert = currentEdge.vertices[1]
|
||||
polyLine = [startVert, endVert]
|
||||
ok = 1
|
||||
while ok:
|
||||
ok = 0
|
||||
i = len(edges)
|
||||
while i:
|
||||
i -= 1
|
||||
ed = edges[i]
|
||||
if ed.vertices[0] == endVert:
|
||||
polyLine.append(ed.vertices[1])
|
||||
endVert = polyLine[-1]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
elif ed.vertices[1] == endVert:
|
||||
polyLine.append(ed.vertices[0])
|
||||
endVert = polyLine[-1]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
elif ed.vertices[0] == startVert:
|
||||
polyLine.insert(0, ed.vertices[1])
|
||||
startVert = polyLine[0]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
elif ed.vertices[1] == startVert:
|
||||
polyLine.insert(0, ed.vertices[0])
|
||||
startVert = polyLine[0]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
return polyLine
|
||||
|
||||
def get_edges_in_v_indices(self, obj, indices):
|
||||
return [e for e in obj.data.edges if (e.vertices[0] in indices and e.vertices[1] in indices)]
|
||||
|
||||
def get_curve_profile_coordinate_system(self, mesh, loop):
|
||||
profile_face = bpy.data.meshes.new("profile_face")
|
||||
profile_verts = [(mesh.vertices[p].co.x, mesh.vertices[p].co.y, mesh.vertices[p].co.z) for p in loop]
|
||||
profile_faces = [tuple(range(0, len(profile_verts)))]
|
||||
profile_face.from_pydata(profile_verts, [], profile_faces)
|
||||
center = profile_face.polygons[0].center
|
||||
if (mesh.vertices[loop[1]].co - mesh.vertices[loop[0]].co).length < 0.01:
|
||||
x_axis = (mesh.vertices[loop[0]].co - center).normalized()
|
||||
else:
|
||||
x_axis = (mesh.vertices[loop[1]].co - mesh.vertices[loop[0]].co).normalized()
|
||||
z_axis = profile_face.polygons[0].normal.normalized()
|
||||
y_axis = z_axis.cross(x_axis).normalized()
|
||||
matrix = Matrix((x_axis, y_axis, z_axis))
|
||||
matrix.normalize()
|
||||
return {
|
||||
"center": center,
|
||||
"x_axis": x_axis,
|
||||
"y_axis": y_axis,
|
||||
"z_axis": z_axis,
|
||||
"matrix": matrix.to_4x4() @ Matrix.Translation(-center),
|
||||
}
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
return co / self.unit_scale
|
||||
|
||||
def create_polyline_from_loop(self, mesh, loop, curve_ucs):
|
||||
points = []
|
||||
for point in loop:
|
||||
transformed_point = curve_ucs["matrix"] @ mesh.vertices[point].co
|
||||
points.append(self.create_cartesian_point(transformed_point.x, transformed_point.y))
|
||||
points.append(points[0])
|
||||
return self.file.createIfcPolyline(points)
|
||||
|
||||
def create_cartesian_point(self, x, y, z=None):
|
||||
x = self.convert_si_to_unit(x)
|
||||
y = self.convert_si_to_unit(y)
|
||||
if z is None:
|
||||
return self.file.createIfcCartesianPoint((x, y))
|
||||
z = self.convert_si_to_unit(z)
|
||||
return self.file.createIfcCartesianPoint((x, y, z))
|
||||
|
||||
def get_extrusion_direction(self, mesh, extrusion_indices, curve_ucs):
|
||||
return curve_ucs["matrix"] @ (
|
||||
curve_ucs["center"] + (mesh.vertices[extrusion_indices[1]].co - mesh.vertices[extrusion_indices[0]].co)
|
||||
)
|
||||
|
||||
def get_start_and_end_of_extrusion(self, profile_points, extrusion_edge):
|
||||
if extrusion_edge.vertices[0] in profile_points:
|
||||
return (extrusion_edge.vertices[0], extrusion_edge.vertices[1])
|
||||
return (extrusion_edge.vertices[1], extrusion_edge.vertices[0])
|
||||
|
||||
def create_ifc_axis_2_placement_2d(self, point, forward):
|
||||
return self.file.createIfcAxis2Placement2D(
|
||||
self.create_cartesian_point(point.x, point.y), self.file.createIfcDirection((forward.x, forward.y))
|
||||
)
|
||||
|
||||
def create_ifc_axis_2_placement_3d(self, point, up, forward):
|
||||
return self.file.createIfcAxis2Placement3D(
|
||||
self.create_cartesian_point(point.x, point.y, point.z),
|
||||
self.file.createIfcDirection((up.x, up.y, up.z)),
|
||||
self.file.createIfcDirection((forward.x, forward.y, forward.z)),
|
||||
)
|
||||
@@ -1,26 +1,24 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"product": None,
|
||||
"representation": None,
|
||||
}
|
||||
self.settings = {"representation": None}
|
||||
self.ifc_vertices = []
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||
self.x_axis = self.file.createIfcDirection((1.0, 0.0, 0.0))
|
||||
self.y_axis = self.file.createIfcDirection((0.0, 1.0, 0.0))
|
||||
self.z_axis = self.file.createIfcDirection((0.0, 0.0, 1.0))
|
||||
mapping_source = self.get_mapping_source()
|
||||
|
||||
self.get_mapping_source()
|
||||
if not mapping_source:
|
||||
return
|
||||
|
||||
mapping_target = self.file.createIfcCartesianTransformationOperator3D(
|
||||
self.x_axis, self.y_axis, self.zero, 1, self.z_axis
|
||||
)
|
||||
mapped_item = self.file.createIfcMappedItem(self.get_mapping_source(), mapping_target)
|
||||
zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||
x_axis = self.file.createIfcDirection((1.0, 0.0, 0.0))
|
||||
y_axis = self.file.createIfcDirection((0.0, 1.0, 0.0))
|
||||
z_axis = self.file.createIfcDirection((0.0, 0.0, 1.0))
|
||||
|
||||
mapping_target = self.file.createIfcCartesianTransformationOperator3D(x_axis, y_axis, zero, 1, z_axis)
|
||||
mapped_item = self.file.createIfcMappedItem(mapping_source, mapping_target)
|
||||
return self.file.create_entity(
|
||||
"IfcShapeRepresentation",
|
||||
**{
|
||||
@@ -32,12 +30,6 @@ class Usecase:
|
||||
)
|
||||
|
||||
def get_mapping_source(self):
|
||||
if self.settings["representation"].RepresentationMap:
|
||||
return self.settings["representation"].RepresentationMap[0]
|
||||
return self.file.create_entity(
|
||||
"IfcRepresentationMap",
|
||||
**{
|
||||
"MappingOrigin": self.file.createIfcAxis2Placement3D(self.zero, self.z_axis, self.x_axis),
|
||||
"MappedRepresentation": self.settings["representation"],
|
||||
}
|
||||
)
|
||||
for inverse in self.file.get_inverse(self.settings["representation"]):
|
||||
if inverse.is_a("IfcRepresentationMap"):
|
||||
return inverse
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import logging
|
||||
import blenderbim.bim.module.geometry.edit_object_placement as edit_object_placement
|
||||
@@ -8,19 +9,22 @@ import blenderbim.bim.module.geometry.add_representation as add_representation
|
||||
import blenderbim.bim.module.geometry.map_representation as map_representation
|
||||
import blenderbim.bim.module.geometry.assign_styles as assign_styles
|
||||
import blenderbim.bim.module.geometry.assign_representation as assign_representation
|
||||
import blenderbim.bim.module.geometry.unassign_representation as unassign_representation
|
||||
import blenderbim.bim.module.geometry.remove_representation as remove_representation
|
||||
import blenderbim.bim.module.grid.create_axis_curve as create_axis_curve
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim.module.geometry.data import Data
|
||||
from blenderbim.bim.module.context.data import Data as ContextData
|
||||
from blenderbim.bim.module.void.data import Data as VoidData
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
def get_box_context_id():
|
||||
def get_context_id(context_type, context_identifier, target_view):
|
||||
for context in ContextData.contexts.values():
|
||||
if context["ContextType"] == "Model":
|
||||
if context["ContextType"] == context_type:
|
||||
for i, subcontext in context["HasSubContexts"].items():
|
||||
if subcontext["ContextIdentifier"] == "Box" and subcontext["TargetView"] == "MODEL_VIEW":
|
||||
if subcontext["ContextIdentifier"] == context_identifier and subcontext["TargetView"] == target_view:
|
||||
return i
|
||||
|
||||
|
||||
@@ -39,14 +43,14 @@ class EditObjectPlacement(bpy.types.Operator):
|
||||
continue
|
||||
matrix = np.array(obj.matrix_world)
|
||||
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
self.calculate_unit_scale()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
# TODO: np.array? Why not matrix?
|
||||
matrix = np.array(
|
||||
ifcopenshell.util.geolocation.local2global(
|
||||
np.matrix(obj.matrix_world),
|
||||
float(props.blender_eastings) * self.unit_scale,
|
||||
float(props.blender_northings) * self.unit_scale,
|
||||
float(props.blender_orthogonal_height) * self.unit_scale,
|
||||
float(props.blender_eastings) * unit_scale,
|
||||
float(props.blender_northings) * unit_scale,
|
||||
float(props.blender_orthogonal_height) * unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
@@ -60,18 +64,6 @@ class EditObjectPlacement(bpy.types.Operator):
|
||||
).execute()
|
||||
return {"FINISHED"}
|
||||
|
||||
def calculate_unit_scale(self):
|
||||
self.unit_scale = 1
|
||||
units = self.file.by_type("IfcUnitAssignment")[0]
|
||||
for unit in units.Units:
|
||||
if not hasattr(unit, "UnitType") or unit.UnitType != "LENGTHUNIT":
|
||||
continue
|
||||
while unit.is_a("IfcConversionBasedUnit"):
|
||||
self.unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue
|
||||
unit = unit.ConversionFactor.UnitComponent
|
||||
if unit.is_a("IfcSIUnit"):
|
||||
self.unit_scale *= ifcopenshell.util.unit.get_prefix_multiplier(unit.Prefix)
|
||||
|
||||
|
||||
class AddRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.add_representation"
|
||||
@@ -82,18 +74,30 @@ class AddRepresentation(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
context_id = self.context_id or int(bpy.context.scene.BIMProperties.contexts)
|
||||
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
if obj.data:
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
context_id = self.context_id or int(bpy.context.scene.BIMProperties.contexts)
|
||||
context_of_items = self.file.by_id(context_id)
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
|
||||
coordinate_offset = Vector(
|
||||
(
|
||||
float(gprop.blender_eastings),
|
||||
float(gprop.blender_northings),
|
||||
float(gprop.blender_orthogonal_height),
|
||||
)
|
||||
)
|
||||
|
||||
representation_data = {
|
||||
"context": context_of_items,
|
||||
"blender_object": obj,
|
||||
"geometry": obj.data,
|
||||
"coordinate_offset": coordinate_offset,
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
@@ -105,7 +109,7 @@ class AddRepresentation(bpy.types.Operator):
|
||||
print("Failed to write shape representation")
|
||||
return {"FINISHED"}
|
||||
|
||||
box_context_id = get_box_context_id()
|
||||
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and context_of_items.ContextType == "Model"
|
||||
@@ -202,13 +206,22 @@ class SwitchRepresentation(bpy.types.Operator):
|
||||
class RemoveRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_representation"
|
||||
bl_label = "Remove Representation"
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
obj: bpy.props.StringProperty()
|
||||
representation_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
representation = self.file.by_id(self.ifc_definition_id)
|
||||
obj = bpy.context.active_object
|
||||
mesh = bpy.data.meshes.get("{}/{}".format(representation.ContextOfItems.id(), representation.id()))
|
||||
representation = self.file.by_id(self.representation_id)
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
is_mapped_representation = representation.RepresentationType == "MappedRepresentation"
|
||||
if is_mapped_representation:
|
||||
mesh_name = "{}/{}".format(
|
||||
representation.ContextOfItems.id(), representation.Items[0].MappingSource.MappedRepresentation.id()
|
||||
)
|
||||
else:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = bpy.data.meshes.get(mesh_name)
|
||||
if mesh:
|
||||
if obj.data == mesh:
|
||||
# TODO we can do better than this
|
||||
@@ -216,9 +229,39 @@ class RemoveRepresentation(bpy.types.Operator):
|
||||
if not void_mesh:
|
||||
void_mesh = bpy.data.meshes.new("Void")
|
||||
obj.data = void_mesh
|
||||
bpy.data.meshes.remove(mesh)
|
||||
if not is_mapped_representation:
|
||||
bpy.data.meshes.remove(mesh)
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
unassign_representation.Usecase(self.file, {"product": product, "representation": representation}).execute()
|
||||
remove_representation.Usecase(self.file, {"representation": representation}).execute()
|
||||
Data.load(obj.BIMObjectProperties.ifc_definition_id)
|
||||
Data.load(product.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MapRepresentations(bpy.types.Operator):
|
||||
bl_idname = "bim.map_representations"
|
||||
bl_label = "Map Representations"
|
||||
product_id: bpy.props.IntProperty()
|
||||
type_product_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
related_object = IfcStore.id_map[self.product_id]
|
||||
|
||||
if self.product_id not in Data.products:
|
||||
Data.load(self.product_id)
|
||||
|
||||
for representation_id in Data.products[self.product_id]:
|
||||
bpy.ops.bim.remove_representation(obj=related_object.name, representation_id=representation_id)
|
||||
|
||||
if self.type_product_id not in Data.products:
|
||||
Data.load(self.type_product_id)
|
||||
|
||||
for representation_id in Data.products[self.type_product_id]:
|
||||
bpy.ops.bim.map_representation(
|
||||
obj=related_object.name,
|
||||
representation_id=representation_id,
|
||||
obj_data=IfcStore.id_map[self.type_product_id].data.name,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -226,33 +269,24 @@ class MapRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.map_representation"
|
||||
bl_label = "Map Representation"
|
||||
obj: bpy.props.StringProperty()
|
||||
representation_id: bpy.props.IntProperty()
|
||||
obj_data: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
|
||||
obj_data = bpy.data.meshes.get(self.obj_data) if self.obj_data else bpy.context.active_object.data
|
||||
objs = [o for o in objs if o.data != obj_data]
|
||||
obj_data = bpy.data.meshes.get(self.obj_data) if self.obj_data else None
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
for obj in objs:
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
else:
|
||||
old_representation = None
|
||||
target_representation = self.file.by_id(obj_data.BIMMeshProperties.ifc_definition_id)
|
||||
obj.data = obj_data
|
||||
if obj_data:
|
||||
obj.data = obj_data
|
||||
result = map_representation.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"product": product,
|
||||
"representation": target_representation,
|
||||
},
|
||||
self.file, {"representation": self.file.by_id(self.representation_id)}
|
||||
).execute()
|
||||
assign_representation.Usecase(self.file, {"product": product, "representation": result}).execute()
|
||||
if old_representation:
|
||||
bpy.ops.bim.remove_representation(ifc_definition_id=old_representation.id())
|
||||
Data.load(obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -261,6 +295,7 @@ class UpdateMeshRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.update_mesh_representation"
|
||||
bl_label = "Update Mesh Representation"
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
if not ContextData.is_loaded:
|
||||
@@ -270,64 +305,99 @@ class UpdateMeshRepresentation(bpy.types.Operator):
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
for obj in objs:
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
context_of_items = old_representation.ContextOfItems
|
||||
|
||||
representation_data = {
|
||||
"context": context_of_items,
|
||||
"blender_object": obj,
|
||||
"geometry": obj.data,
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
}
|
||||
|
||||
new_representation = add_representation.Usecase(self.file, representation_data).execute()
|
||||
|
||||
if not new_representation:
|
||||
print("Failed to write shape representation")
|
||||
return {"FINISHED"}
|
||||
|
||||
box_context_id = get_box_context_id()
|
||||
old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and old_box
|
||||
and context_of_items.ContextType == "Model"
|
||||
and context_of_items.ContextIdentifier
|
||||
and context_of_items.ContextIdentifier == "Body"
|
||||
):
|
||||
representation_data["context"] = self.file.by_id(box_context_id)
|
||||
new_box = add_representation.Usecase(self.file, representation_data).execute()
|
||||
for inverse in self.file.get_inverse(old_box):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_box, new_box)
|
||||
|
||||
assign_styles.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"shape_representation": new_representation,
|
||||
"styles": [
|
||||
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
|
||||
for s in obj.material_slots
|
||||
if s.material
|
||||
],
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
).execute()
|
||||
|
||||
# TODO: move this into a replace_representation usecase or something
|
||||
for inverse in self.file.get_inverse(old_representation):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
|
||||
|
||||
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
|
||||
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
|
||||
bpy.ops.bim.remove_representation(ifc_definition_id=old_representation.id())
|
||||
Data.load(obj.BIMObjectProperties.ifc_definition_id)
|
||||
self.update_obj_mesh_representation(context, obj)
|
||||
IfcStore.edited_objs.discard(obj.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
def update_obj_mesh_representation(self, context, obj):
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
if product.is_a("IfcGridAxis"):
|
||||
create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": product}).execute()
|
||||
return
|
||||
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
context_of_items = old_representation.ContextOfItems
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
|
||||
coordinate_offset = Vector(
|
||||
(
|
||||
float(gprop.blender_eastings),
|
||||
float(gprop.blender_northings),
|
||||
float(gprop.blender_orthogonal_height),
|
||||
)
|
||||
)
|
||||
|
||||
representation_data = {
|
||||
"context": context_of_items,
|
||||
"blender_object": obj,
|
||||
"geometry": obj.data,
|
||||
"coordinate_offset": coordinate_offset,
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
"ifc_representation_class": self.ifc_representation_class
|
||||
}
|
||||
|
||||
new_representation = add_representation.Usecase(self.file, representation_data).execute()
|
||||
|
||||
#if product.is_a("IfcWall"):
|
||||
# # Generate axis representation
|
||||
# axis_context_id = get_context_id("Model", "Axis", "MODEL_VIEW")
|
||||
# old_axis = ifcopenshell.util.element.get_representation(product, "Model", "Axis", "MODEL_VIEW")
|
||||
# if (
|
||||
# axis_context_id
|
||||
# and old_axis
|
||||
# and context_of_items.ContextType == "Model"
|
||||
# and context_of_items.ContextIdentifier
|
||||
# and context_of_items.ContextIdentifier == "Body"
|
||||
# ):
|
||||
# has_axis_generator = False
|
||||
# if has_axis_generator:
|
||||
# # TODO, just pseudocode for now
|
||||
# representation_data["geometry"] = axis_generator_function_call
|
||||
# pass
|
||||
|
||||
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
|
||||
old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and old_box
|
||||
and context_of_items.ContextType == "Model"
|
||||
and context_of_items.ContextIdentifier
|
||||
and context_of_items.ContextIdentifier == "Body"
|
||||
):
|
||||
representation_data["context"] = self.file.by_id(box_context_id)
|
||||
new_box = add_representation.Usecase(self.file, representation_data).execute()
|
||||
for inverse in self.file.get_inverse(old_box):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_box, new_box)
|
||||
|
||||
assign_styles.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"shape_representation": new_representation,
|
||||
"styles": [
|
||||
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
|
||||
for s in obj.material_slots
|
||||
if s.material
|
||||
],
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
).execute()
|
||||
|
||||
# TODO: move this into a replace_representation usecase or something
|
||||
for inverse in self.file.get_inverse(old_representation):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
|
||||
|
||||
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
|
||||
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
|
||||
bpy.ops.bim.remove_representation(representation_id=old_representation.id())
|
||||
Data.load(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
|
||||
class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.update_parametric_representation"
|
||||
@@ -354,15 +424,14 @@ class GetRepresentationIfcParameters(bpy.types.Operator):
|
||||
props = obj.data.BIMMeshProperties
|
||||
elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id))
|
||||
for element in elements:
|
||||
if not element.is_a("IfcRepresentationItem"):
|
||||
continue
|
||||
for i in range(0, len(element)):
|
||||
if element.attribute_type(i) == "DOUBLE":
|
||||
new = props.ifc_parameters.add()
|
||||
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
|
||||
new.step_id = element.id()
|
||||
new.type = element.attribute_type(i)
|
||||
new.index = i
|
||||
if element[i]:
|
||||
new.value = element[i]
|
||||
if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"):
|
||||
for i in range(0, len(element)):
|
||||
if element.attribute_type(i) == "DOUBLE":
|
||||
new = props.ifc_parameters.add()
|
||||
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
|
||||
new.step_id = element.id()
|
||||
new.type = element.attribute_type(i)
|
||||
new.index = i
|
||||
if element[i]:
|
||||
new.value = element[i]
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
@@ -8,6 +9,19 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["representation"].RepresentationType == "MappedRepresentation":
|
||||
return self.remove_mapped_representation_portion_only()
|
||||
return self.remove_entire_representation_tree()
|
||||
|
||||
def remove_mapped_representation_portion_only(self):
|
||||
dummy_context = self.file.create_entity("IfcRepresentationContext")
|
||||
dummy_representation_map = self.file.createIfcRepresentationMap()
|
||||
self.settings["representation"].ContextOfItems = dummy_context
|
||||
for item in self.settings["representation"].Items:
|
||||
item.MappingSource = dummy_representation_map
|
||||
ifcopenshell.util.element.remove_deep(self.file, self.settings["representation"])
|
||||
|
||||
def remove_entire_representation_tree(self):
|
||||
dummy_context = self.file.create_entity("IfcRepresentationContext")
|
||||
for subelement in self.file.traverse(self.settings["representation"]):
|
||||
if subelement.is_a("IfcRepresentationItem") and subelement.StyledByItem:
|
||||
@@ -26,11 +40,4 @@ class Usecase:
|
||||
self.file.remove(inverse)
|
||||
else:
|
||||
assigned_items.remove(element)
|
||||
inverse.AssignedItems == list(assigned_items)
|
||||
elif inverse.is_a("IfcProductRepresentation"):
|
||||
representations = set(inverse.Representations)
|
||||
if len(representations) == 1:
|
||||
self.file.remove(inverse)
|
||||
else:
|
||||
representations.remove(element)
|
||||
inverse.Representations = list(representations)
|
||||
inverse.AssignedItems = list(assigned_items)
|
||||
|
||||
@@ -41,7 +41,7 @@ class BIM_PT_representations(Panel):
|
||||
op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="")
|
||||
op.ifc_definition_id = ifc_definition_id
|
||||
op.disable_opening_subtractions = False
|
||||
row.operator("bim.remove_representation", icon="X", text="").ifc_definition_id = ifc_definition_id
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = ifc_definition_id
|
||||
|
||||
|
||||
class BIM_PT_mesh(Panel):
|
||||
@@ -66,9 +66,6 @@ class BIM_PT_mesh(Panel):
|
||||
layout = self.layout
|
||||
props = context.active_object.data.BIMMeshProperties
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.map_representation")
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT")
|
||||
op.ifc_definition_id = props.ifc_definition_id
|
||||
@@ -79,6 +76,23 @@ class BIM_PT_mesh(Panel):
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.update_mesh_representation")
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Rectangle Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcRectangleProfileDef"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Circle Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcCircleProfileDef"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Arbitrary Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Arbitrary Extrusion With Voids")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.get_representation_ifc_parameters")
|
||||
for index, ifc_parameter in enumerate(props.ifc_parameters):
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import blenderbim.bim.module.geometry.remove_representation as remove_representation
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {"product": None, "representation": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["product"].is_a("IfcProduct"):
|
||||
self.unassign_product_representation(self.settings["product"], self.settings["representation"])
|
||||
elif self.settings["product"].is_a("IfcTypeProduct"):
|
||||
self.unassign_type_representation()
|
||||
|
||||
def unassign_product_representation(self, product, representation):
|
||||
representations = list(product.Representation.Representations or [])
|
||||
if representation not in representations:
|
||||
return
|
||||
representations.remove(representation)
|
||||
if not representations:
|
||||
self.file.remove(product.Representation)
|
||||
else:
|
||||
product.Representation.Representations = representations
|
||||
|
||||
def unassign_type_representation(self):
|
||||
for representation_map in self.settings["product"].RepresentationMaps or []:
|
||||
if representation_map.MappedRepresentation == self.settings["representation"]:
|
||||
self.unassign_products_using_mapped_representation(representation_map)
|
||||
self.remove_representation_map_only(representation_map)
|
||||
break
|
||||
self.settings["product"].RepresentationMaps = self.settings["product"].RepresentationMaps or None
|
||||
|
||||
def remove_representation_map_only(self, representation_map):
|
||||
dummy_representation = self.file.createIfcShapeRepresentation()
|
||||
representation_map.MappedRepresentation = dummy_representation
|
||||
ifcopenshell.util.element.remove_deep(self.file, representation_map)
|
||||
self.file.remove(representation_map)
|
||||
|
||||
def unassign_products_using_mapped_representation(self, representation_map):
|
||||
mapped_representations = []
|
||||
just_representations = []
|
||||
for map_usage in representation_map.MapUsage or []:
|
||||
for inverse in self.file.get_inverse(map_usage):
|
||||
if not inverse.is_a("IfcShapeRepresentation"):
|
||||
continue
|
||||
for definition in inverse.OfProductRepresentation or []:
|
||||
for product in definition.ShapeOfProduct or []:
|
||||
mapped_representations.append({"product": product, "representation": inverse})
|
||||
just_representations.append(inverse)
|
||||
for item in mapped_representations:
|
||||
self.unassign_product_representation(item["product"], item["representation"])
|
||||
for representation in just_representations:
|
||||
remove_representation.Usecase(self.file, {"representation": representation}).execute()
|
||||
@@ -11,6 +11,8 @@ classes = (
|
||||
operator.AddGeoreferencing,
|
||||
operator.ConvertLocalToGlobal,
|
||||
operator.ConvertGlobalToLocal,
|
||||
operator.GetCursorLocation,
|
||||
operator.SetCursorLocation,
|
||||
prop.BIMGeoreferenceProperties,
|
||||
ui.BIM_PT_gis,
|
||||
ui.BIM_PT_gis_utilities,
|
||||
|
||||
@@ -136,11 +136,9 @@ class EditGeoreferencing(bpy.types.Operator):
|
||||
if not props.is_map_unit_null:
|
||||
map_unit = props.map_unit_si if props.map_unit_type == "IfcSIUnit" else props.map_unit_imperial
|
||||
|
||||
edit_georeferencing.Usecase(self.file, {
|
||||
"map_conversion": map_conversion,
|
||||
"projected_crs": projected_crs,
|
||||
"map_unit": map_unit
|
||||
}).execute()
|
||||
edit_georeferencing.Usecase(
|
||||
self.file, {"map_conversion": map_conversion, "projected_crs": projected_crs, "map_unit": map_unit}
|
||||
).execute()
|
||||
Data.load()
|
||||
bpy.ops.bim.disable_editing_georeferencing()
|
||||
return {"FINISHED"}
|
||||
@@ -154,7 +152,7 @@ class SetNorthOffset(bpy.types.Operator):
|
||||
context.scene.sun_pos_properties.north_offset = -radians(
|
||||
ifcopenshell.util.geolocation.xy2angle(
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").float_value,
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value,
|
||||
)
|
||||
)
|
||||
return {"FINISHED"}
|
||||
@@ -200,17 +198,42 @@ class ConvertLocalToGlobal(bpy.types.Operator):
|
||||
Data.load()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
|
||||
results = ifcopenshell.util.geolocation.xyz2enh(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
Data.map_conversion["Eastings"],
|
||||
Data.map_conversion["Northings"],
|
||||
Data.map_conversion["OrthogonalHeight"],
|
||||
Data.map_conversion.get("XAxisAbscissa", 1.0),
|
||||
Data.map_conversion.get("XAxisOrdinate", 0.0),
|
||||
Data.map_conversion.get("Scale", 1.0),
|
||||
)
|
||||
|
||||
if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
|
||||
x -= float(props.blender_eastings)
|
||||
y -= float(props.blender_northings)
|
||||
z -= float(props.blender_orthogonal_height)
|
||||
elif props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
results = ifcopenshell.util.geolocation.xyz2enh(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
float(props.blender_orthogonal_height),
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
1.0,
|
||||
)
|
||||
x, y, z = results
|
||||
|
||||
# TODO: what if the project CRS units and the project units are different?
|
||||
|
||||
if Data.map_conversion:
|
||||
results = ifcopenshell.util.geolocation.xyz2enh(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
Data.map_conversion["Eastings"],
|
||||
Data.map_conversion["Northings"],
|
||||
Data.map_conversion["OrthogonalHeight"],
|
||||
Data.map_conversion.get("XAxisAbscissa", 1.0),
|
||||
Data.map_conversion.get("XAxisOrdinate", 0.0),
|
||||
Data.map_conversion.get("Scale", 1.0),
|
||||
)
|
||||
else:
|
||||
results = (x, y, z)
|
||||
|
||||
props.coordinate_output = ",".join([str(r) for r in results])
|
||||
bpy.context.scene.cursor.location = results
|
||||
return {"FINISHED"}
|
||||
@@ -225,18 +248,64 @@ class ConvertGlobalToLocal(bpy.types.Operator):
|
||||
Data.load()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
|
||||
results = ifcopenshell.util.geolocation.enh2xyz(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
Data.map_conversion["Eastings"],
|
||||
Data.map_conversion["Northings"],
|
||||
Data.map_conversion["OrthogonalHeight"],
|
||||
Data.map_conversion.get("XAxisAbscissa", 1.0),
|
||||
Data.map_conversion.get("XAxisOrdinate", 0.0),
|
||||
Data.map_conversion.get("Scale", 1.0),
|
||||
)
|
||||
|
||||
if Data.map_conversion:
|
||||
results = ifcopenshell.util.geolocation.enh2xyz(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
Data.map_conversion["Eastings"],
|
||||
Data.map_conversion["Northings"],
|
||||
Data.map_conversion["OrthogonalHeight"],
|
||||
Data.map_conversion.get("XAxisAbscissa", 1.0),
|
||||
Data.map_conversion.get("XAxisOrdinate", 0.0),
|
||||
Data.map_conversion.get("Scale", 1.0),
|
||||
)
|
||||
else:
|
||||
results = (x, y, z)
|
||||
|
||||
if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
|
||||
results[0] += float(props.blender_eastings)
|
||||
results[1] += float(props.blender_northings)
|
||||
results[2] += float(props.blender_orthogonal_height)
|
||||
elif props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
results = ifcopenshell.util.geolocation.enh2xyz(
|
||||
results[0],
|
||||
results[1],
|
||||
results[2],
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
float(props.blender_orthogonal_height),
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
1.0,
|
||||
)
|
||||
|
||||
props.coordinate_output = ",".join([str(r) for r in results])
|
||||
bpy.context.scene.cursor.location = results
|
||||
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
bpy.context.scene.cursor.location = [o * scale for o in results]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GetCursorLocation(bpy.types.Operator):
|
||||
bl_idname = "bim.get_cursor_location"
|
||||
bl_label = "Get Cursor Location"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
project_coordinates = [o / scale for o in bpy.context.scene.cursor.location]
|
||||
props.coordinate_input = ",".join([str(o) for o in project_coordinates])
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetCursorLocation(bpy.types.Operator):
|
||||
bl_idname = "bim.set_cursor_location"
|
||||
bl_label = "Set Cursor Location"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
bpy.context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -143,10 +143,12 @@ class BIM_PT_gis_utilities(Panel):
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
row = self.layout.row()
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "coordinate_input", text="Input")
|
||||
row = self.layout.row()
|
||||
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "coordinate_output", text="Output")
|
||||
row.operator("bim.set_cursor_location", text="", icon="TRACKER")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.convert_local_to_global", text="Local to Global")
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
from mathutils import Matrix # For now, we depend on Blender
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"AxisCurve": None, # A Blender object
|
||||
"grid_axis": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["grid_axis"].AxisCurve:
|
||||
ifcopenshell.util.element.remove_deep(self.file, self.settings["grid_axis"].AxisCurve)
|
||||
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
grid = [i for i in self.file.get_inverse(self.settings["grid_axis"]) if i.is_a("IfcGrid")][0]
|
||||
points = [
|
||||
Matrix(ifcopenshell.util.placement.get_local_placement(grid.ObjectPlacement)).inverted()
|
||||
@ (self.settings["AxisCurve"].matrix_world @ v.co)
|
||||
for v in self.settings["AxisCurve"].data.vertices[0:2]
|
||||
]
|
||||
self.settings["grid_axis"].AxisCurve = self.file.createIfcPolyline(
|
||||
[
|
||||
self.create_cartesian_point(points[0][0], points[0][1], points[0][2]),
|
||||
self.create_cartesian_point(points[1][0], points[1][1], points[1][2]),
|
||||
]
|
||||
)
|
||||
|
||||
def create_cartesian_point(self, x, y, z=None):
|
||||
x = self.convert_si_to_unit(x)
|
||||
y = self.convert_si_to_unit(y)
|
||||
if z is None:
|
||||
return self.file.createIfcCartesianPoint((x, y))
|
||||
z = self.convert_si_to_unit(z)
|
||||
return self.file.createIfcCartesianPoint((x, y, z))
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
return co / self.settings["unit_scale"]
|
||||
@@ -0,0 +1,21 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"AxisTag": "A",
|
||||
"SameSense": True,
|
||||
"UVWAxes": "UAxes", # Choose which axes
|
||||
"Grid": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
element = self.file.create_entity("IfcGridAxis", **{
|
||||
"AxisTag": self.settings["AxisTag"],
|
||||
"SameSense": self.settings["SameSense"]
|
||||
})
|
||||
axes = list(getattr(self.settings["Grid"], self.settings["UVWAxes"]) or [])
|
||||
axes.append(element)
|
||||
setattr(self.settings["Grid"], self.settings["UVWAxes"], axes)
|
||||
return element
|
||||
@@ -0,0 +1,26 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.LoadGroups,
|
||||
operator.DisableGroupEditingUI,
|
||||
operator.AddGroup,
|
||||
operator.EditGroup,
|
||||
operator.RemoveGroup,
|
||||
operator.AssignGroup,
|
||||
operator.UnassignGroup,
|
||||
operator.EnableEditingGroup,
|
||||
operator.DisableEditingGroup,
|
||||
prop.Group,
|
||||
prop.BIMGroupProperties,
|
||||
ui.BIM_PT_groups,
|
||||
ui.BIM_UL_groups,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMGroupProperties = bpy.props.PointerProperty(type=prop.BIMGroupProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMGroupProperties
|
||||
@@ -0,0 +1,17 @@
|
||||
import ifcopenshell
|
||||
from blenderbim.bim.module.owner.api import create_owner_history
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings={}):
|
||||
self.file = file
|
||||
self.settings = {}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
return self.file.create_entity("IfcGroup", **{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": create_owner_history(),
|
||||
"Name": "Unnamed"
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
import ifcopenshell
|
||||
from blenderbim.bim.module.owner.api import create_owner_history
|
||||
from blenderbim.bim.module.owner.api import update_owner_history
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"product": None,
|
||||
"group": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if not self.settings["group"].IsGroupedBy:
|
||||
return self.file.create_entity("IfcRelAssignsToGroup", **{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": create_owner_history(),
|
||||
"RelatedObjects": [self.settings["product"]],
|
||||
"RelatingGroup": self.settings["group"]
|
||||
})
|
||||
rel = self.settings["group"].IsGroupedBy[0]
|
||||
related_objects = set(rel.RelatedObjects) or set()
|
||||
related_objects.add(self.settings["product"])
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
update_owner_history(rel)
|
||||
@@ -0,0 +1,27 @@
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class Data:
|
||||
is_loaded = False
|
||||
products = {}
|
||||
groups = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.products = {}
|
||||
cls.groups = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.products = {}
|
||||
cls.groups = {}
|
||||
for group in IfcStore.get_file().by_type("IfcGroup", include_subtypes=False):
|
||||
if group.IsGroupedBy:
|
||||
for rel in group.IsGroupedBy:
|
||||
for product in rel.RelatedObjects:
|
||||
cls.products.setdefault(product.id(), []).append(group.id())
|
||||
data = group.get_info()
|
||||
del data["OwnerHistory"]
|
||||
cls.groups[group.id()] = data
|
||||
cls.is_loaded=True
|
||||
@@ -0,0 +1,13 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"group": None,
|
||||
"attributes": {}
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["group"], name, value)
|
||||
@@ -0,0 +1,149 @@
|
||||
import bpy
|
||||
import blenderbim.bim.module.group.add_group as add_group
|
||||
import blenderbim.bim.module.group.edit_group as edit_group
|
||||
import blenderbim.bim.module.group.remove_group as remove_group
|
||||
import blenderbim.bim.module.group.assign_group as assign_group
|
||||
import blenderbim.bim.module.group.unassign_group as unassign_group
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.group.data import Data
|
||||
|
||||
|
||||
class LoadGroups(bpy.types.Operator):
|
||||
bl_idname = "bim.load_groups"
|
||||
bl_label = "Load Groups"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
while len(props.groups) > 0:
|
||||
props.groups.remove(0)
|
||||
for ifc_definition_id, group in Data.groups.items():
|
||||
new = props.groups.add()
|
||||
new.ifc_definition_id = ifc_definition_id
|
||||
new.name = group["Name"]
|
||||
props.is_editing = True
|
||||
bpy.ops.bim.disable_editing_group()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableGroupEditingUI(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_group_editing_ui"
|
||||
bl_label = "Disable Group Editing UI"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMGroupProperties.is_editing = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.add_group"
|
||||
bl_label = "Add Group"
|
||||
|
||||
def execute(self, context):
|
||||
result = add_group.Usecase(IfcStore.get_file()).execute()
|
||||
Data.load()
|
||||
bpy.ops.bim.load_groups()
|
||||
bpy.ops.bim.enable_editing_group(group=result.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_group"
|
||||
bl_label = "Edit Group"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
attributes = {}
|
||||
for attribute in props.group_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_group.Usecase(
|
||||
self.file, {"group": self.file.by_id(props.active_group_id), "attributes": attributes}
|
||||
).execute()
|
||||
Data.load()
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_group"
|
||||
bl_label = "Remove Group"
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
self.file = IfcStore.get_file()
|
||||
remove_group.Usecase(self.file, {"group": self.file.by_id(self.group)}).execute()
|
||||
Data.load()
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_group"
|
||||
bl_label = "Enable Editing Group"
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
while len(props.group_attributes) > 0:
|
||||
props.group_attributes.remove(0)
|
||||
|
||||
data = Data.groups[self.group]
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcGroup").all_attributes():
|
||||
data_type = str(attribute.type_of_attribute)
|
||||
if "<entity" in data_type:
|
||||
continue
|
||||
new = props.group_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
props.active_group_id = self.group
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_group"
|
||||
bl_label = "Disable Editing Group"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMGroupProperties.active_group_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_group"
|
||||
bl_label = "Assign Group"
|
||||
product: bpy.props.StringProperty()
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
product = bpy.data.objects.get(self.product) if self.product else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
assign_group.Usecase(self.file, {
|
||||
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
|
||||
"group": self.file.by_id(self.group)
|
||||
}).execute()
|
||||
Data.load()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_group"
|
||||
bl_label = "Unassign Group"
|
||||
product: bpy.props.StringProperty()
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
product = bpy.data.objects.get(self.product) if self.product else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
unassign_group.Usecase(self.file, {
|
||||
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
|
||||
"group": self.file.by_id(self.group)
|
||||
}).execute()
|
||||
Data.load()
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,26 @@
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class Group(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class BIMGroupProperties(PropertyGroup):
|
||||
group_attributes: CollectionProperty(name="Group Attributes", type=Attribute)
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
groups: CollectionProperty(name="Groups", type=Group)
|
||||
active_group_index: IntProperty(name="Active Group Index")
|
||||
active_group_id: IntProperty(name="Active Group Id")
|
||||
@@ -0,0 +1,11 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {"group": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["group"].IsGroupedBy or []:
|
||||
self.file.remove(rel)
|
||||
self.file.remove(self.settings["group"])
|
||||
@@ -0,0 +1,79 @@
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.group.data import Data
|
||||
|
||||
|
||||
class BIM_PT_groups(Panel):
|
||||
bl_label = "IFC Groups"
|
||||
bl_idname = "BIM_PT_groups"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load()
|
||||
self.props = context.scene.BIMGroupProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Groups Found".format(len(Data.groups)), icon="OUTLINER")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.add_group", text="", icon="ADD")
|
||||
row.operator("bim.disable_group_editing_ui", text="", icon="CHECKMARK")
|
||||
else:
|
||||
row.operator("bim.load_groups", text="", icon="GREASEPENCIL")
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_groups",
|
||||
"",
|
||||
self.props,
|
||||
"groups",
|
||||
self.props,
|
||||
"active_group_index",
|
||||
)
|
||||
|
||||
if self.props.active_group_id:
|
||||
self.draw_editable_ui(context)
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
for attribute in self.props.group_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
|
||||
|
||||
class BIM_UL_groups(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
|
||||
if context.active_object:
|
||||
oprops = context.active_object.BIMObjectProperties
|
||||
if (
|
||||
oprops.ifc_definition_id in Data.products
|
||||
and item.ifc_definition_id in Data.products[oprops.ifc_definition_id]
|
||||
):
|
||||
op = row.operator("bim.unassign_group", text="", icon="KEYFRAME_HLT", emboss=False)
|
||||
op.group = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.assign_group", text="", icon="KEYFRAME", emboss=False)
|
||||
op.group = item.ifc_definition_id
|
||||
|
||||
if context.scene.BIMGroupProperties.active_group_id == item.ifc_definition_id:
|
||||
row.operator("bim.edit_group", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_group", text="", icon="X")
|
||||
elif context.scene.BIMGroupProperties.active_group_id:
|
||||
row.operator("bim.remove_group", text="", icon="X").group = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_group", text="", icon="GREASEPENCIL")
|
||||
op.group = item.ifc_definition_id
|
||||
row.operator("bim.remove_group", text="", icon="X").group = item.ifc_definition_id
|
||||
@@ -0,0 +1,25 @@
|
||||
import ifcopenshell
|
||||
from blenderbim.bim.module.owner.api import update_owner_history
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"product": None,
|
||||
"group": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if not self.settings["group"].IsGroupedBy:
|
||||
return
|
||||
rel = self.settings["group"].IsGroupedBy[0]
|
||||
related_objects = set(rel.RelatedObjects) or set()
|
||||
related_objects.remove(self.settings["product"])
|
||||
if len(related_objects):
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
update_owner_history(rel)
|
||||
else:
|
||||
self.file.remove(rel)
|
||||
@@ -1,5 +1,3 @@
|
||||
import ifcopenshell
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings={}):
|
||||
self.file = file
|
||||
|
||||
@@ -16,7 +16,7 @@ class Data:
|
||||
cls.layers = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, item_id=None):
|
||||
def load(cls):
|
||||
cls._file = IfcStore.get_file()
|
||||
if not cls._file:
|
||||
return
|
||||
|
||||
@@ -21,17 +21,13 @@ class BIM_PT_layers(Panel):
|
||||
|
||||
self.props = context.scene.BIMLayerProperties
|
||||
|
||||
if Data.layers:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Layers Found".format(len(Data.layers.keys())))
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.add_presentation_layer", text="", icon="ADD")
|
||||
row.operator("bim.disable_layer_editing_ui", text="", icon="X")
|
||||
else:
|
||||
row.operator("bim.load_layers", text="", icon="IMPORT")
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Layers Found".format(len(Data.layers.keys())))
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.add_presentation_layer", text="", icon="ADD")
|
||||
row.operator("bim.disable_layer_editing_ui", text="", icon="X")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Layers")
|
||||
row.operator("bim.load_layers", text="", icon="GREASEPENCIL")
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
|
||||
@@ -10,6 +10,7 @@ classes = (
|
||||
operator.RemoveConstituent,
|
||||
operator.AddLayer,
|
||||
operator.RemoveLayer,
|
||||
operator.ReorderMaterialSetItem,
|
||||
operator.AddListItem,
|
||||
operator.RemoveListItem,
|
||||
operator.EnableEditingAssignedMaterial,
|
||||
|
||||
@@ -9,7 +9,7 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
constituents = list(self.settings["constituent_set"].MaterialConstituents)
|
||||
constituents = list(self.settings["constituent_set"].MaterialConstituents or [])
|
||||
constituent = self.file.create_entity("IfcMaterialConstituent", **{"Material": self.settings["material"]})
|
||||
constituents.append(constituent)
|
||||
self.settings["constituent_set"].MaterialConstituents = constituents
|
||||
|
||||
@@ -9,7 +9,7 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
layers = list(self.settings["layer_set"].MaterialLayers)
|
||||
layers = list(self.settings["layer_set"].MaterialLayers or [])
|
||||
layer = self.file.create_entity("IfcMaterialLayer", **{
|
||||
"Material": self.settings["material"],
|
||||
"LayerThickness": 0.
|
||||
|
||||
@@ -9,6 +9,6 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
materials = list(self.settings["material_list"].Materials)
|
||||
materials = list(self.settings["material_list"].Materials or [])
|
||||
materials.append(self.settings["material"])
|
||||
self.settings["material_list"].Materials = materials
|
||||
|
||||
@@ -13,21 +13,30 @@ class Usecase:
|
||||
self.assign_ifc_material()
|
||||
elif self.settings["type"] == "IfcMaterialConstituentSet":
|
||||
material_set = self.file.create_entity(self.settings["type"])
|
||||
material_set.MaterialConstituents = [self.settings["material"]]
|
||||
self.create_material_association(material_set)
|
||||
elif self.settings["type"] == "IfcMaterialLayerSet":
|
||||
material_set = self.file.create_entity(self.settings["type"])
|
||||
material_set.MaterialLayers = [self.settings["material"]]
|
||||
self.create_material_association(material_set)
|
||||
elif self.settings["type"] == "IfcMaterialLayerSetUsage":
|
||||
material_set = self.file.create_entity("IfcMaterialLayerSet")
|
||||
material_set_usage = self.create_layer_set_usage(material_set)
|
||||
self.create_material_association(material_set_usage)
|
||||
elif self.settings["type"] == "IfcMaterialProfileSet":
|
||||
material_set = self.file.create_entity(self.settings["type"])
|
||||
material_set.MaterialProfiles = [self.settings["material"]]
|
||||
self.create_material_association(material_set)
|
||||
elif self.settings["type"] == "IfcMaterialList":
|
||||
material_set = self.file.create_entity(self.settings["type"])
|
||||
material_set.Materials = [self.settings["material"]]
|
||||
self.create_material_association(material_set)
|
||||
|
||||
def create_layer_set_usage(self, material_set):
|
||||
return self.file.create_entity("IfcMaterialLayerSetUsage", **{
|
||||
"ForLayerSet": material_set,
|
||||
"LayerSetDirection": "AXIS2" if self.settings["product"].is_a("IfcWall") else "AXIS3",
|
||||
"DirectionSense": "POSITIVE",
|
||||
"OffsetFromReferenceLine": 0
|
||||
})
|
||||
|
||||
def assign_ifc_material(self):
|
||||
rel = self.get_rel_associates_material(self.settings["material"])
|
||||
if not rel:
|
||||
|
||||
@@ -21,6 +21,7 @@ class Data:
|
||||
cls.materials = {}
|
||||
cls.constituent_sets = {}
|
||||
cls.constituents = {}
|
||||
cls.layer_set_usages = {}
|
||||
cls.layer_sets = {}
|
||||
cls.layers = {}
|
||||
cls.profile_sets = {}
|
||||
@@ -39,6 +40,7 @@ class Data:
|
||||
cls.load_materials()
|
||||
cls.load_constituents()
|
||||
cls.load_layers()
|
||||
cls.load_layer_usages()
|
||||
cls.load_profiles()
|
||||
cls.load_lists()
|
||||
cls.is_loaded = True
|
||||
@@ -62,6 +64,11 @@ class Data:
|
||||
cls.load_element("IfcMaterialLayer", cls.layers)
|
||||
cls.load_element("IfcMaterialLayerSet", cls.layer_sets)
|
||||
|
||||
@classmethod
|
||||
def load_layer_usages(cls):
|
||||
cls.layer_set_usages = {}
|
||||
cls.load_element("IfcMaterialLayerSetUsage", cls.layer_set_usages)
|
||||
|
||||
@classmethod
|
||||
def load_profiles(cls):
|
||||
cls.profile_sets = {}
|
||||
@@ -103,8 +110,6 @@ class Data:
|
||||
@classmethod
|
||||
def load_association(cls, association, product_id):
|
||||
material_select = association.RelatingMaterial
|
||||
if material_select.is_a("IfcMaterialLayerSetUsage"): # TODO: implement usages
|
||||
material_select = material_select.ForLayerSet
|
||||
elif material_select.is_a("IfcMaterialProfileSetUsage"): # TODO: implement usages
|
||||
if material_select.is_a("IfcMaterialProfileSetUsage"): # TODO: implement usages
|
||||
material_select = material_select.ForProfileSet
|
||||
cls.products[product_id] = {"type": material_select.is_a(), "id": material_select.id()}
|
||||
|
||||
@@ -8,6 +8,7 @@ import blenderbim.bim.module.material.remove_constituent as remove_constituent
|
||||
import blenderbim.bim.module.material.add_layer as add_layer
|
||||
import blenderbim.bim.module.material.edit_layer as edit_layer
|
||||
import blenderbim.bim.module.material.remove_layer as remove_layer
|
||||
import blenderbim.bim.module.material.reorder_set_item as reorder_set_item
|
||||
import blenderbim.bim.module.material.add_list_item as add_list_item
|
||||
import blenderbim.bim.module.material.remove_list_item as remove_list_item
|
||||
import blenderbim.bim.module.material.edit_assigned_material as edit_assigned_material
|
||||
@@ -63,6 +64,7 @@ class AssignMaterial(bpy.types.Operator):
|
||||
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
|
||||
},
|
||||
).execute()
|
||||
Data.load()
|
||||
Data.load(obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -136,6 +138,37 @@ class AddLayer(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ReorderMaterialSetItem(bpy.types.Operator):
|
||||
bl_idname = "bim.reorder_material_set_item"
|
||||
bl_label = "Reorder Material Set Item"
|
||||
obj: bpy.props.StringProperty()
|
||||
old_index: bpy.props.IntProperty()
|
||||
new_index: bpy.props.IntProperty()
|
||||
material_set: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
material_set = self.file.by_id(self.material_set)
|
||||
reorder_set_item.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"material_set": material_set,
|
||||
"old_index": self.old_index,
|
||||
"new_index": self.new_index,
|
||||
},
|
||||
).execute()
|
||||
if material_set.is_a("IfcMaterialConstituentSet"):
|
||||
Data.load_constituents()
|
||||
elif material_set.is_a("IfcMaterialLayerSet"):
|
||||
Data.load_layers()
|
||||
elif material_set.is_a("IfcMaterialProfileSet"):
|
||||
Data.load_profiles()
|
||||
elif material_set.is_a("IfcMaterialList"):
|
||||
Data.load_lists()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveLayer(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_layer"
|
||||
bl_label = "Remove Layer"
|
||||
@@ -206,10 +239,15 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
|
||||
props.material = str(product_data["id"])
|
||||
return {"FINISHED"}
|
||||
|
||||
material_set_class = product_data["type"]
|
||||
if product_data["type"] == "IfcMaterialConstituentSet":
|
||||
material_set_data = Data.constituent_sets[product_data["id"]]
|
||||
elif product_data["type"] == "IfcMaterialLayerSet":
|
||||
material_set_data = Data.layer_sets[product_data["id"]]
|
||||
elif product_data["type"] == "IfcMaterialLayerSetUsage":
|
||||
layer_set_usage = Data.layer_set_usages[product_data["id"]]
|
||||
material_set_data = Data.layer_sets[layer_set_usage["ForLayerSet"]]
|
||||
material_set_class = "IfcMaterialLayerSet"
|
||||
elif product_data["type"] == "IfcMaterialProfileSet":
|
||||
material_set_data = Data.profile_sets[product_data["id"]]
|
||||
elif product_data["type"] == "IfcMaterialList":
|
||||
@@ -220,7 +258,7 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
|
||||
while len(props.material_set_attributes) > 0:
|
||||
props.material_set_attributes.remove(0)
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(product_data["type"]).all_attributes():
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(material_set_class).all_attributes():
|
||||
if "<string>" not in str(attribute.type_of_attribute):
|
||||
continue
|
||||
if attribute.name() in material_set_data:
|
||||
@@ -254,8 +292,7 @@ class EditAssignedMaterial(bpy.types.Operator):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
props = obj.BIMObjectMaterialProperties
|
||||
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
|
||||
material_set_id = self.material_set or product_data["id"]
|
||||
material_set = self.file.by_id(material_set_id)
|
||||
material_set = self.file.by_id(self.material_set)
|
||||
|
||||
if product_data["type"] == "IfcMaterial":
|
||||
bpy.ops.bim.unassign_material(obj=obj.name)
|
||||
@@ -301,7 +338,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
|
||||
|
||||
if product_data["type"] == "IfcMaterialConstituentSet":
|
||||
material_set_item_data = Data.constituents[self.material_set_item]
|
||||
elif product_data["type"] == "IfcMaterialLayerSet":
|
||||
elif product_data["type"] == "IfcMaterialLayerSet" or product_data["type"] == "IfcMaterialLayerSetUsage":
|
||||
material_set_item_data = Data.layers[self.material_set_item]
|
||||
elif product_data["type"] == "IfcMaterialProfileSet":
|
||||
material_set_item_data = Data.profiles[self.material_set_item]
|
||||
@@ -382,7 +419,7 @@ class EditMaterialSetItem(bpy.types.Operator):
|
||||
},
|
||||
).execute()
|
||||
Data.load_constituents()
|
||||
elif product_data["type"] == "IfcMaterialLayerSet":
|
||||
elif product_data["type"] == "IfcMaterialLayerSet" or product_data["type"] == "IfcMaterialLayerSetUsage":
|
||||
edit_layer.Usecase(
|
||||
self.file,
|
||||
{
|
||||
|
||||
@@ -34,11 +34,12 @@ def getMaterialTypes(self, context):
|
||||
"IfcMaterial",
|
||||
"IfcMaterialConstituentSet",
|
||||
"IfcMaterialLayerSet",
|
||||
"IfcMaterialLayerSetUsage",
|
||||
"IfcMaterialProfileSet",
|
||||
"IfcMaterialList",
|
||||
]
|
||||
if IfcStore.get_file().schema == "IFC2X3":
|
||||
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
|
||||
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialList"]
|
||||
materialtypes_enum.clear()
|
||||
materialtypes_enum = [(m, m, "") for m in material_types]
|
||||
return materialtypes_enum
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {"material_set": None, "old_index": 0, "new_index": 0}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["material_set"].is_a("IfcMaterialConstituentSet"):
|
||||
set_name = "MaterialConstituents"
|
||||
elif self.settings["material_set"].is_a("IfcMaterialLayerSet"):
|
||||
set_name = "MaterialLayers"
|
||||
elif self.settings["material_set"].is_a("IfcMaterialProfileSet"):
|
||||
set_name = "MaterialProfiles"
|
||||
elif self.settings["material_set"].is_a("IfcMaterialList"):
|
||||
set_name = "Materials"
|
||||
items = list(getattr(self.settings["material_set"], set_name) or [])
|
||||
items.insert(self.settings["new_index"], items.pop(self.settings["old_index"]))
|
||||
setattr(self.settings["material_set"], set_name, items)
|
||||
@@ -10,6 +10,10 @@ class BIM_PT_material(Panel):
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file() and context.active_object and context.active_object.active_material
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id):
|
||||
@@ -51,23 +55,34 @@ class BIM_PT_object_material(Panel):
|
||||
|
||||
if self.product_data:
|
||||
if self.product_data["type"] == "IfcMaterialConstituentSet":
|
||||
self.material_set_data = Data.constituent_sets[self.product_data["id"]]
|
||||
self.set_items = self.material_set_data["MaterialConstituents"]
|
||||
self.material_set_id = self.product_data["id"]
|
||||
self.material_set_data = Data.constituent_sets[self.material_set_id]
|
||||
self.set_items = self.material_set_data["MaterialConstituents"] or []
|
||||
self.set_data = Data.constituents
|
||||
self.set_item_name = "constituent"
|
||||
elif self.product_data["type"] == "IfcMaterialLayerSet":
|
||||
self.material_set_data = Data.layer_sets[self.product_data["id"]]
|
||||
self.set_items = self.material_set_data["MaterialLayers"]
|
||||
self.material_set_id = self.product_data["id"]
|
||||
self.material_set_data = Data.layer_sets[self.material_set_id]
|
||||
self.set_items = self.material_set_data["MaterialLayers"] or []
|
||||
self.set_data = Data.layers
|
||||
self.set_item_name = "layer"
|
||||
elif self.product_data["type"] == "IfcMaterialLayerSetUsage":
|
||||
self.material_set_usage = Data.layer_set_usages[self.product_data["id"]]
|
||||
self.material_set_id = self.material_set_usage["ForLayerSet"]
|
||||
self.material_set_data = Data.layer_sets[self.material_set_id]
|
||||
self.set_items = self.material_set_data["MaterialLayers"] or []
|
||||
self.set_data = Data.layers
|
||||
self.set_item_name = "layer"
|
||||
elif self.product_data["type"] == "IfcMaterialProfileSet":
|
||||
self.material_set_data = Data.profile_sets[self.product_data["id"]]
|
||||
self.set_items = self.material_set_data["MaterialProfiles"]
|
||||
self.material_set_id = self.product_data["id"]
|
||||
self.material_set_data = Data.profile_sets[self.material_set_id]
|
||||
self.set_items = self.material_set_data["MaterialProfiles"] or []
|
||||
self.set_data = Data.profiles
|
||||
self.set_item_name = "profile"
|
||||
elif self.product_data["type"] == "IfcMaterialList":
|
||||
self.material_set_data = Data.lists[self.product_data["id"]]
|
||||
self.set_items = self.material_set_data["Materials"]
|
||||
self.material_set_id = self.product_data["id"]
|
||||
self.material_set_data = Data.lists[self.material_set_id]
|
||||
self.set_items = self.material_set_data["Materials"] or []
|
||||
self.set_item_name = "list_item"
|
||||
return self.draw_material_ui()
|
||||
|
||||
@@ -82,7 +97,8 @@ class BIM_PT_object_material(Panel):
|
||||
row.label(text=self.product_data["type"])
|
||||
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.edit_assigned_material", icon="CHECKMARK", text="")
|
||||
op = row.operator("bim.edit_assigned_material", icon="CHECKMARK", text="")
|
||||
op.material_set = self.material_set_id
|
||||
row.operator("bim.disable_editing_assigned_material", icon="X", text="")
|
||||
else:
|
||||
row.operator("bim.enable_editing_assigned_material", icon="GREASEPENCIL", text="")
|
||||
@@ -122,14 +138,16 @@ class BIM_PT_object_material(Panel):
|
||||
row.prop(self.props, "material", text="")
|
||||
|
||||
op = row.operator(f"bim.add_{self.set_item_name}", icon="ADD", text="")
|
||||
setattr(op, f"{self.set_item_name}_set", self.product_data["id"])
|
||||
setattr(op, f"{self.set_item_name}_set", self.material_set_id)
|
||||
|
||||
total_items = len(self.set_items)
|
||||
for index, set_item_id in enumerate(self.set_items):
|
||||
if self.props.active_material_set_item_id == set_item_id:
|
||||
self.draw_editable_set_item_ui(set_item_id)
|
||||
else:
|
||||
self.draw_read_only_set_item_ui(set_item_id, is_first=index == 0, is_last=index == total_items - 1)
|
||||
self.draw_read_only_set_item_ui(
|
||||
set_item_id, index, is_first=index == 0, is_last=index == total_items - 1
|
||||
)
|
||||
|
||||
def draw_editable_set_item_ui(self, set_item_id):
|
||||
item = self.set_data[set_item_id]
|
||||
@@ -154,7 +172,7 @@ class BIM_PT_object_material(Panel):
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_read_only_set_item_ui(self, set_item_id, is_first=False, is_last=False):
|
||||
def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False):
|
||||
if self.product_data["type"] == "IfcMaterialList":
|
||||
item = Data.materials[set_item_id]
|
||||
row = self.layout.row(align=True)
|
||||
@@ -163,26 +181,38 @@ class BIM_PT_object_material(Panel):
|
||||
else:
|
||||
item = self.set_data[set_item_id]
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=item.get("Name", "Unnamed"), icon="ALIGN_CENTER")
|
||||
row.label(text=item.get("Name", "Unnamed") or "Unnamed", icon="ALIGN_CENTER")
|
||||
row.label(text=Data.materials[item["Material"]]["Name"], icon="MATERIAL")
|
||||
|
||||
if not is_first:
|
||||
row.operator("bim.edit_attributes", icon="TRIA_UP", text="")
|
||||
op = row.operator(f"bim.reorder_material_set_item", icon="TRIA_UP", text="")
|
||||
op.old_index = index
|
||||
op.new_index = index - 1
|
||||
setattr(op, "material_set", self.material_set_id)
|
||||
if not is_last:
|
||||
row.operator("bim.edit_attributes", icon="TRIA_DOWN", text="")
|
||||
op = row.operator(f"bim.reorder_material_set_item", icon="TRIA_DOWN", text="")
|
||||
op.old_index = index
|
||||
op.new_index = index + 1
|
||||
setattr(op, "material_set", self.material_set_id)
|
||||
if not self.props.active_material_set_item_id and self.product_data["type"] != "IfcMaterialList":
|
||||
op = row.operator("bim.enable_editing_material_set_item", icon="GREASEPENCIL", text="")
|
||||
op.material_set_item = set_item_id
|
||||
op = row.operator(f"bim.remove_{self.set_item_name}", icon="X", text="")
|
||||
if self.product_data["type"] == "IfcMaterialList":
|
||||
setattr(op, "list_item_set", self.product_data["id"])
|
||||
setattr(op, "list_item_set", self.material_set_id)
|
||||
setattr(op, self.set_item_name, item["id"])
|
||||
|
||||
def draw_read_only_set_ui(self):
|
||||
name_attr = "LayerSetName" if self.product_data["type"] == "IfcMaterialLayerSet" else "Name"
|
||||
if (
|
||||
self.product_data["type"] == "IfcMaterialLayerSetUsage"
|
||||
or self.product_data["type"] == "IfcMaterialLayerSet"
|
||||
):
|
||||
name_attr = "LayerSetName"
|
||||
else:
|
||||
name_attr = "Name"
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=name_attr)
|
||||
row.label(text=self.material_set_data.get(name_attr, "Unnamed"))
|
||||
row.label(text=self.material_set_data.get(name_attr, "Unnamed") or "Unnamed")
|
||||
if hasattr(self.material_set_data, "Description") and self.material_set_data["Description"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Description")
|
||||
@@ -196,5 +226,5 @@ class BIM_PT_object_material(Panel):
|
||||
else:
|
||||
item = self.set_data[item_id]
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=item.get("Name", "Unnamed"), icon="ALIGN_CENTER")
|
||||
row.label(text=item.get("Name", "Unnamed") or "Unnamed", icon="ALIGN_CENTER")
|
||||
row.label(text=Data.materials[item["Material"]]["Name"], icon="MATERIAL")
|
||||
|
||||
@@ -3,8 +3,8 @@ import math
|
||||
import ifcopenshell
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def add_object(self, context):
|
||||
@@ -79,7 +79,11 @@ def add_object(self, context):
|
||||
faces = [[0, 1, 2, 3, 4, 5]]
|
||||
mesh = bpy.data.meshes.new(name="Dumb Door Profile")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = object_data_add(context, mesh, operator=self)
|
||||
obj = bpy.data.objects.new("Door Profile", mesh)
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
bpy.ops.object.convert(target="CURVE")
|
||||
|
||||
# Door lining sweep
|
||||
@@ -93,7 +97,11 @@ def add_object(self, context):
|
||||
faces = []
|
||||
mesh = bpy.data.meshes.new(name="Dumb Door")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj2 = object_data_add(context, mesh, operator=self)
|
||||
obj2 = bpy.data.objects.new("Door", mesh)
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj2)
|
||||
bpy.context.view_layer.objects.active = obj2
|
||||
obj2.select_set(True)
|
||||
bpy.ops.object.convert(target="CURVE")
|
||||
|
||||
obj2.data.dimensions = "2D"
|
||||
@@ -116,10 +124,14 @@ def add_object(self, context):
|
||||
faces = [[0, 1, 2, 3]]
|
||||
mesh = bpy.data.meshes.new(name="Dumb Door Panel")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj3 = object_data_add(context, mesh, operator=self)
|
||||
obj3 = bpy.data.objects.new("Door Panel", mesh)
|
||||
modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY")
|
||||
modifier.offset = 1
|
||||
modifier.thickness = self.overall_height - 0.045
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj3)
|
||||
bpy.context.view_layer.objects.active = obj3
|
||||
obj3.select_set(True)
|
||||
bpy.ops.object.convert(target="MESH")
|
||||
|
||||
ctx = bpy.context.copy()
|
||||
@@ -138,11 +150,16 @@ def add_object(self, context):
|
||||
faces = [[0, 1, 2, 3]]
|
||||
mesh = bpy.data.meshes.new(name="Dumb Door Opening")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj4 = object_data_add(context, mesh, operator=self)
|
||||
obj4 = bpy.data.objects.new("Door Opening", mesh)
|
||||
modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY")
|
||||
modifier.offset = -1
|
||||
modifier.thickness = self.overall_height + 0.1
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj4)
|
||||
bpy.context.view_layer.objects.active = obj4
|
||||
obj4.select_set(True)
|
||||
bpy.ops.object.convert(target="MESH")
|
||||
|
||||
obj4.display_type = "WIRE"
|
||||
obj4.parent = obj2
|
||||
obj4.matrix_parent_inverse = obj2.matrix_world.inverted()
|
||||
@@ -150,6 +167,11 @@ def add_object(self, context):
|
||||
obj4.name = "Door Opening"
|
||||
|
||||
obj2.name = "Door"
|
||||
|
||||
if IfcStore.get_file():
|
||||
bpy.ops.bim.assign_class(obj=obj2.name, ifc_class="IfcDoor")
|
||||
|
||||
obj2.location = context.scene.cursor.location
|
||||
#obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
|
||||
#obj2.data.use_fake_user = True
|
||||
|
||||
@@ -165,10 +187,9 @@ def add_object(self, context):
|
||||
#rep.target_view = "PLAN_VIEW"
|
||||
|
||||
|
||||
class BIM_OT_add_object(Operator, AddObjectHelper):
|
||||
class BIM_OT_add_object(Operator):
|
||||
bl_idname = "mesh.add_door"
|
||||
bl_label = "Dumb Door"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
overall_width: FloatProperty(name="Overall Width", default=0.85)
|
||||
overall_height: FloatProperty(name="Overall Height", default=2.1)
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import bpy
|
||||
import blenderbim.bim.module.grid.create_grid_axis as create_grid_axis
|
||||
import blenderbim.bim.module.grid.create_axis_curve as create_axis_curve
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty, IntProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def add_object(self, context):
|
||||
obj = object_data_add(context, None, operator=self)
|
||||
obj.name = "IfcGrid/Grid"
|
||||
name = obj.name.split("/")[1]
|
||||
obj = bpy.data.objects.new("Grid", None)
|
||||
obj.name = "Grid"
|
||||
|
||||
default_collection = obj.users_collection[0]
|
||||
default_collection.objects.unlink(obj)
|
||||
|
||||
collection = bpy.data.collections.new("IfcGrid/" + name)
|
||||
collection = bpy.data.collections.new(obj.name)
|
||||
has_site_collection = False
|
||||
for child in bpy.context.view_layer.layer_collection.children:
|
||||
if "IfcProject/" not in child.name:
|
||||
@@ -28,6 +26,17 @@ def add_object(self, context):
|
||||
bpy.context.view_layer.active_layer_collection.collection.children.link(collection)
|
||||
collection.objects.link(obj)
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
if self.file:
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcGrid")
|
||||
grid = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if has_site_collection:
|
||||
site_obj = bpy.data.objects.get(grandchild.name)
|
||||
if site_obj and site_obj.BIMObjectProperties.ifc_definition_id:
|
||||
bpy.ops.bim.assign_container(
|
||||
relating_structure=site_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
|
||||
)
|
||||
|
||||
axes_collection = bpy.data.collections.new("UAxes")
|
||||
collection.children.link(axes_collection)
|
||||
for i in range(0, self.total_u):
|
||||
@@ -39,14 +48,17 @@ def add_object(self, context):
|
||||
faces = []
|
||||
mesh = bpy.data.meshes.new(name="Grid Axis")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = object_data_add(context, mesh, operator=self)
|
||||
tag = chr(ord("A") + i)
|
||||
obj.name = "IfcGridAxis/" + tag
|
||||
default_collection.objects.unlink(obj)
|
||||
obj = bpy.data.objects.new(f"IfcGridAxis/{tag}", mesh)
|
||||
|
||||
axes_collection.objects.link(obj)
|
||||
attribute = obj.BIMObjectProperties.attributes.add()
|
||||
attribute.name = "AxisTag"
|
||||
attribute.string_value = tag
|
||||
|
||||
if self.file:
|
||||
result = create_grid_axis.Usecase(
|
||||
self.file, {"AxisTag": tag, "AxisCurve": obj, "UVWAxes": "UAxes", "Grid": grid}
|
||||
).execute()
|
||||
create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": result}).execute()
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
|
||||
axes_collection = bpy.data.collections.new("VAxes")
|
||||
collection.children.link(axes_collection)
|
||||
@@ -59,17 +71,20 @@ def add_object(self, context):
|
||||
faces = []
|
||||
mesh = bpy.data.meshes.new(name="Grid Axis")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = object_data_add(context, mesh, operator=self)
|
||||
tag = str(i + 1).zfill(2)
|
||||
obj.name = "IfcGridAxis/" + tag
|
||||
default_collection.objects.unlink(obj)
|
||||
obj = bpy.data.objects.new(f"IfcGridAxis/{tag}", mesh)
|
||||
|
||||
axes_collection.objects.link(obj)
|
||||
attribute = obj.BIMObjectProperties.attributes.add()
|
||||
attribute.name = "AxisTag"
|
||||
attribute.string_value = tag
|
||||
|
||||
if IfcStore.get_file():
|
||||
result = create_grid_axis.Usecase(
|
||||
self.file, {"AxisTag": tag, "AxisCurve": obj, "UVWAxes": "VAxes", "Grid": grid}
|
||||
).execute()
|
||||
create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": result}).execute()
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
|
||||
|
||||
class BIM_OT_add_object(Operator, AddObjectHelper):
|
||||
class BIM_OT_add_object(Operator):
|
||||
bl_idname = "mesh.add_grid"
|
||||
bl_label = "Grid"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@@ -98,13 +98,13 @@ class PieUpdateContainer(bpy.types.Operator):
|
||||
for obj in context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
relating_structure = None
|
||||
for collection in obj.users_collection:
|
||||
relating_structure_obj = bpy.data.objects.get(collection.name)
|
||||
if not relating_structure_obj or not relating_structure_obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
relating_structure = relating_structure_obj
|
||||
bpy.ops.bim.assign_container(relating_structure=relating_structure.name, related_element=obj.name)
|
||||
spatial_obj = bpy.data.objects.get(collection.name)
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
bpy.ops.bim.assign_container(
|
||||
relating_structure=spatial_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
|
||||
)
|
||||
break
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -115,7 +115,6 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
pie = self.layout.menu_pie()
|
||||
pie.operator("bim.edit_object_placement")
|
||||
pie.operator("bim.update_mesh_representation")
|
||||
pie.operator("bim.map_representation")
|
||||
pie.operator("bim.pie_add_opening")
|
||||
pie.operator("bim.pie_update_container")
|
||||
pie.operator("bim.open_pie_class", text="Assign IFC Class")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def add_object(self, context):
|
||||
@@ -17,18 +17,21 @@ def add_object(self, context):
|
||||
|
||||
mesh = bpy.data.meshes.new(name="Dumb Slab")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = object_data_add(context, mesh, operator=self)
|
||||
obj = bpy.data.objects.new("Slab", mesh)
|
||||
modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
|
||||
modifier.use_even_offset = True
|
||||
modifier.offset = 1
|
||||
modifier.thickness = self.depth
|
||||
obj.name = "Slab"
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj)
|
||||
if IfcStore.get_file():
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSlab", predefined_type="FLOOR")
|
||||
obj.location = context.scene.cursor.location
|
||||
|
||||
|
||||
class BIM_OT_add_object(Operator, AddObjectHelper):
|
||||
class BIM_OT_add_object(Operator):
|
||||
bl_idname = "mesh.add_slab"
|
||||
bl_label = "Dumb Slab"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
length: FloatProperty(name="Length", default=2)
|
||||
width: FloatProperty(name="Width", default=2)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty, BoolProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def add_object(self, context):
|
||||
@@ -25,7 +25,8 @@ def add_object(self, context):
|
||||
|
||||
mesh = bpy.data.meshes.new(name="Dumb Wall")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = object_data_add(context, mesh, operator=self)
|
||||
obj = bpy.data.objects.new("Wall", mesh)
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj)
|
||||
if not self.use_plane:
|
||||
modifier = obj.modifiers.new("Wall Height", "SCREW")
|
||||
modifier.angle = 0
|
||||
@@ -39,13 +40,15 @@ def add_object(self, context):
|
||||
modifier.use_even_offset = True
|
||||
modifier.thickness = self.width
|
||||
obj.name = "Wall"
|
||||
if IfcStore.get_file():
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcWall")
|
||||
obj.location = context.scene.cursor.location
|
||||
return obj
|
||||
|
||||
|
||||
class BIM_OT_add_object(Operator, AddObjectHelper):
|
||||
class BIM_OT_add_object(Operator):
|
||||
bl_idname = "mesh.add_wall"
|
||||
bl_label = "Dumb Wall"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
height: FloatProperty(name="Height", default=3)
|
||||
length: FloatProperty(name="Length", default=1)
|
||||
|
||||
@@ -3,8 +3,8 @@ import math
|
||||
import ifcopenshell
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def add_object(self, context):
|
||||
@@ -64,7 +64,11 @@ def add_object(self, context):
|
||||
faces = [[0, 1, 2, 3]]
|
||||
mesh = bpy.data.meshes.new(name="Dumb Window Profile")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = object_data_add(context, mesh, operator=self)
|
||||
obj = bpy.data.objects.new("Window Profile", mesh)
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
bpy.ops.object.convert(target="CURVE")
|
||||
|
||||
# Window lining sweep
|
||||
@@ -78,7 +82,11 @@ def add_object(self, context):
|
||||
faces = []
|
||||
mesh = bpy.data.meshes.new(name="Dumb Window")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj2 = object_data_add(context, mesh, operator=self)
|
||||
obj2 = bpy.data.objects.new("Window", mesh)
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj2)
|
||||
bpy.context.view_layer.objects.active = obj2
|
||||
obj2.select_set(True)
|
||||
bpy.ops.object.convert(target="CURVE")
|
||||
obj2.data.splines[0].use_cyclic_u = True
|
||||
|
||||
@@ -102,10 +110,14 @@ def add_object(self, context):
|
||||
faces = [[0, 1, 2, 3]]
|
||||
mesh = bpy.data.meshes.new(name="Dumb Window Panel")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj3 = object_data_add(context, mesh, operator=self)
|
||||
obj3 = bpy.data.objects.new("Window Panel", mesh)
|
||||
modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY")
|
||||
modifier.offset = 1
|
||||
modifier.thickness = self.overall_height - 0.08
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj3)
|
||||
bpy.context.view_layer.objects.active = obj3
|
||||
obj3.select_set(True)
|
||||
bpy.ops.object.convert(target="MESH")
|
||||
|
||||
ctx = bpy.context.copy()
|
||||
@@ -124,10 +136,14 @@ def add_object(self, context):
|
||||
faces = [[0, 1, 2, 3]]
|
||||
mesh = bpy.data.meshes.new(name="Dumb Window Opening")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj4 = object_data_add(context, mesh, operator=self)
|
||||
obj4 = bpy.data.objects.new("Window Opening", mesh)
|
||||
modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY")
|
||||
modifier.offset = -1
|
||||
modifier.thickness = self.overall_height
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj4)
|
||||
bpy.context.view_layer.objects.active = obj4
|
||||
obj4.select_set(True)
|
||||
bpy.ops.object.convert(target="MESH")
|
||||
obj4.display_type = "WIRE"
|
||||
obj4.parent = obj2
|
||||
@@ -135,7 +151,10 @@ def add_object(self, context):
|
||||
obj4.hide_render = True
|
||||
obj4.name = "Window Opening"
|
||||
|
||||
if IfcStore.get_file():
|
||||
bpy.ops.bim.assign_class(obj=obj2.name, ifc_class="IfcWindow")
|
||||
obj2.name = "Window"
|
||||
obj2.location = context.scene.cursor.location
|
||||
#obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
|
||||
#obj2.data.use_fake_user = True
|
||||
|
||||
@@ -150,10 +169,9 @@ def add_object(self, context):
|
||||
#rep.target_view = "PLAN_VIEW"
|
||||
|
||||
|
||||
class BIM_OT_add_object(Operator, AddObjectHelper):
|
||||
class BIM_OT_add_object(Operator):
|
||||
bl_idname = "mesh.add_window"
|
||||
bl_label = "Dumb Window"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
overall_width: FloatProperty(name="Overall Width", default=0.7)
|
||||
overall_height: FloatProperty(name="Overall Height", default=1)
|
||||
|
||||
@@ -27,6 +27,15 @@ def update_owner_history(element, change_action=None):
|
||||
|
||||
def create_owner_history(change_action=None):
|
||||
file = IfcStore.get_file()
|
||||
|
||||
if (
|
||||
not bpy.context.scene.BIMOwnerProperties.user_person
|
||||
or not bpy.context.scene.BIMOwnerProperties.user_organisation
|
||||
):
|
||||
if file.schema == "IFC2X3":
|
||||
assert False, "A person and organisation is required in IFC2X3."
|
||||
return None
|
||||
|
||||
return create_owner_history_usecase.Usecase(
|
||||
file,
|
||||
{
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import bpy
|
||||
from . import ui, operator
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.CreateProject,
|
||||
operator.ValidateIfcFile,
|
||||
prop.BIMProjectProperties,
|
||||
ui.BIM_PT_project,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
del bpy.types.Scene.BIMProjectProperties
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class BIMProjectProperties(PropertyGroup):
|
||||
is_authoring: BoolProperty(name="Enable Authoring Mode", default=True)
|
||||
@@ -22,11 +22,13 @@ class BIM_PT_project(Panel):
|
||||
|
||||
def draw_project_ui(self, context):
|
||||
props = context.scene.BIMProperties
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="IFC Filename", icon="FILE")
|
||||
row.label(text=os.path.basename(props.ifc_file) or "No File Found")
|
||||
|
||||
if IfcStore.get_file():
|
||||
row.prop(pprops, "is_authoring", icon="GREASEPENCIL", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="IFC Schema", icon="FILE_CACHE")
|
||||
row.label(text=IfcStore.get_file().schema)
|
||||
|
||||
@@ -175,6 +175,8 @@ class Data:
|
||||
data_type = "string"
|
||||
elif "<real>" in data_type:
|
||||
data_type = "float"
|
||||
elif "<number>" in data_type:
|
||||
data_type = "integer"
|
||||
elif "<integer>" in data_type:
|
||||
data_type = "integer"
|
||||
elif "<boolean>" in data_type:
|
||||
|
||||
@@ -51,7 +51,7 @@ class EnablePsetEditing(bpy.types.Operator):
|
||||
elif prop["type"] == "integer":
|
||||
new.int_value = prop["value"] or 0
|
||||
elif prop["type"] == "float":
|
||||
new.float_value = prop["value"] or 0.
|
||||
new.float_value = prop["value"] or 0.0
|
||||
elif prop["type"] == "boolean":
|
||||
new.bool_value = prop["value"] or False
|
||||
elif prop["type"] == "enum":
|
||||
@@ -84,6 +84,8 @@ class EditPset(bpy.types.Operator):
|
||||
bl_label = "Edit Pset"
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
pset_id: bpy.props.IntProperty()
|
||||
properties: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
@@ -94,35 +96,47 @@ class EditPset(bpy.types.Operator):
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.PsetProperties
|
||||
properties = {}
|
||||
data = Data.psets if props.active_pset_id in Data.psets else Data.qtos
|
||||
for prop in data[props.active_pset_id]["Properties"]:
|
||||
blender_prop = props.properties.get(prop["Name"])
|
||||
if not blender_prop:
|
||||
continue
|
||||
if blender_prop.is_null:
|
||||
properties[prop["Name"]] = None
|
||||
elif prop["type"] == "string":
|
||||
properties[prop["Name"]] = blender_prop.string_value
|
||||
elif prop["type"] == "boolean":
|
||||
properties[prop["Name"]] = blender_prop.bool_value
|
||||
elif prop["type"] == "integer":
|
||||
properties[prop["Name"]] = blender_prop.int_value
|
||||
elif prop["type"] == "float":
|
||||
properties[prop["Name"]] = blender_prop.float_value
|
||||
elif prop["type"] == "enum":
|
||||
properties[prop["Name"]] = blender_prop.enum_value
|
||||
if props.active_pset_id in Data.psets:
|
||||
edit_pset.Usecase(self.file, {
|
||||
"pset": self.file.by_id(props.active_pset_id),
|
||||
"Name": props.active_pset_name,
|
||||
"Properties": properties
|
||||
}).execute()
|
||||
|
||||
pset_id = self.pset_id or props.active_pset_id
|
||||
if self.properties:
|
||||
properties = json.loads(self.properties)
|
||||
else:
|
||||
edit_qto.Usecase(self.file, {
|
||||
"qto": self.file.by_id(props.active_pset_id),
|
||||
"Name": props.active_pset_name,
|
||||
"Properties": properties
|
||||
}).execute()
|
||||
data = Data.psets if pset_id in Data.psets else Data.qtos
|
||||
for prop in data[pset_id]["Properties"]:
|
||||
blender_prop = props.properties.get(prop["Name"])
|
||||
if not blender_prop:
|
||||
continue
|
||||
if blender_prop.is_null:
|
||||
properties[prop["Name"]] = None
|
||||
elif prop["type"] == "string":
|
||||
properties[prop["Name"]] = blender_prop.string_value
|
||||
elif prop["type"] == "boolean":
|
||||
properties[prop["Name"]] = blender_prop.bool_value
|
||||
elif prop["type"] == "integer":
|
||||
properties[prop["Name"]] = blender_prop.int_value
|
||||
elif prop["type"] == "float":
|
||||
properties[prop["Name"]] = blender_prop.float_value
|
||||
elif prop["type"] == "enum":
|
||||
properties[prop["Name"]] = blender_prop.enum_value
|
||||
|
||||
if pset_id in Data.psets:
|
||||
edit_pset.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"pset": self.file.by_id(pset_id),
|
||||
"Name": props.active_pset_name,
|
||||
"Properties": properties,
|
||||
},
|
||||
).execute()
|
||||
else:
|
||||
edit_qto.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"qto": self.file.by_id(pset_id),
|
||||
"Name": props.active_pset_name,
|
||||
"Properties": properties,
|
||||
},
|
||||
).execute()
|
||||
Data.load(oprops.ifc_definition_id)
|
||||
bpy.ops.bim.disable_pset_editing(obj=self.obj, obj_type=self.obj_type)
|
||||
return {"FINISHED"}
|
||||
@@ -142,10 +156,13 @@ class RemovePset(bpy.types.Operator):
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
props = obj.BIMObjectProperties
|
||||
remove_pset.Usecase(self.file, {
|
||||
"product": self.file.by_id(props.ifc_definition_id),
|
||||
"pset": self.file.by_id(self.pset_id),
|
||||
}).execute()
|
||||
remove_pset.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"product": self.file.by_id(props.ifc_definition_id),
|
||||
"pset": self.file.by_id(self.pset_id),
|
||||
},
|
||||
).execute()
|
||||
Data.load(props.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -155,6 +172,7 @@ class AddPset(bpy.types.Operator):
|
||||
bl_label = "Add Pset"
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
pset_name: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
@@ -164,10 +182,21 @@ class AddPset(bpy.types.Operator):
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.PsetProperties
|
||||
add_pset.Usecase(self.file, {
|
||||
"product": self.file.by_id(oprops.ifc_definition_id),
|
||||
"Name": props.pset_name if self.obj_type == "Object" else props.material_pset_name,
|
||||
}).execute()
|
||||
|
||||
if self.pset_name:
|
||||
pset_name = self.pset_name
|
||||
elif self.obj_type == "Object":
|
||||
pset_name = props.pset_name
|
||||
elif self.obj_type == "Material":
|
||||
pset_name = props.material_pset_name
|
||||
|
||||
add_pset.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"product": self.file.by_id(oprops.ifc_definition_id),
|
||||
"Name": pset_name,
|
||||
},
|
||||
).execute()
|
||||
Data.load(oprops.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -181,10 +210,13 @@ class AddQto(bpy.types.Operator):
|
||||
obj = bpy.context.active_object
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.PsetProperties
|
||||
add_qto.Usecase(self.file, {
|
||||
"product": self.file.by_id(oprops.ifc_definition_id),
|
||||
"Name": props.qto_name,
|
||||
}).execute()
|
||||
add_qto.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"product": self.file.by_id(oprops.ifc_definition_id),
|
||||
"Name": props.qto_name,
|
||||
},
|
||||
).execute()
|
||||
Data.load(oprops.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user