Compare commits
301 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8083a635a8 | |||
| fab76bbcde | |||
| 7f78df595f | |||
| 03ff34f89f | |||
| 8820a1dcc3 | |||
| 514975d123 | |||
| c87911f00e | |||
| 1e77562f0f | |||
| b1843ce781 | |||
| 02fb09ff7e | |||
| bdb193f500 | |||
| e9a3630595 | |||
| fb3e4016ec | |||
| 15a1aa5355 | |||
| f1d6e95e51 | |||
| b9c99b35a5 | |||
| df0a503b89 | |||
| 346071185f | |||
| f3f90e0ad5 | |||
| 8aa2c1eea7 | |||
| 57c32e55c2 | |||
| de080a668f | |||
| 00fc71dcbe | |||
| 808cb6ec14 | |||
| 8899776106 | |||
| ae674b00c2 | |||
| 73b6d23d4f | |||
| 6d1cabe534 | |||
| ab34fda42c | |||
| fab4133df6 | |||
| 301e368a2c | |||
| 0a22714fb8 | |||
| ef2cec7435 | |||
| b4f486e9aa | |||
| 297303063e | |||
| df95bdb48e | |||
| 95ac021623 | |||
| 416efa4f14 | |||
| 2003bfe4fe | |||
| fc55a48c11 | |||
| 37a6be1e2b | |||
| e551bf531c | |||
| e173ea7f80 | |||
| f8dc421f03 | |||
| e2afb34e0d | |||
| f7181aae8a | |||
| 2a0fdd214c | |||
| 45209be0ff | |||
| fcb3640bee | |||
| d503a4b50b | |||
| 8bae2984e1 | |||
| 78499cc55d | |||
| b6aa5c57c0 | |||
| d71e34f073 | |||
| a38d06d7e7 | |||
| 72b5cc8a44 | |||
| c97e1079e8 | |||
| 65caa7906d | |||
| d89a8520a9 | |||
| d68356c3a7 | |||
| cb0e8b6bf2 | |||
| b20b99669d | |||
| d41a277b71 | |||
| 33e6a0fdae | |||
| d911d8579d | |||
| 05bdc962a3 | |||
| 0e03a01f61 | |||
| ff806dc6e4 | |||
| 5fcfbac4a3 | |||
| 00b18f67cf | |||
| 088354a96b | |||
| cfd857233c | |||
| 2816d7f522 | |||
| 3da718a6d1 | |||
| 558aff72a0 | |||
| bcc399b842 | |||
| 32e47d03d4 | |||
| a669c194cf | |||
| 91f4687eac | |||
| 31f6baaf67 | |||
| a83c4b2896 | |||
| c685425667 | |||
| 553334d50e | |||
| 0b7b0a29a9 | |||
| ae7f834a0f | |||
| fe0a98a44f | |||
| 35e748089b | |||
| ff2cda7d18 | |||
| 0d473fa9ac | |||
| d8eb765906 | |||
| fab7b590f7 | |||
| c9f52d2f8e | |||
| 821993d289 | |||
| 2d32bc609a | |||
| 296cec02e8 | |||
| b55ac88c9d | |||
| c6d3598c52 | |||
| c4d2b30017 | |||
| ee1e401719 | |||
| 3c2ef518e8 | |||
| f3e327e088 | |||
| be1d4e06de | |||
| 32f5a77d07 | |||
| 9121a6b864 | |||
| 9946117de1 | |||
| ee85055d45 | |||
| cce0867f63 | |||
| 021321eb65 | |||
| 6bd1b84b2b | |||
| f5281a0895 | |||
| 08fdf6014d | |||
| 94f06d9e18 | |||
| 8405ee4926 | |||
| 154c4295df | |||
| 93519c1c63 | |||
| 1250a20e35 | |||
| 9ec54228f8 | |||
| 3f94184ace | |||
| 88c06f5450 | |||
| e50045bbed | |||
| 41417a77b9 | |||
| a5774bd02a | |||
| c38f48ee62 | |||
| 30c850e37d | |||
| b6fdbd979e | |||
| 7a8bcbee33 | |||
| 42d7c7d32c | |||
| c71a5a4d65 | |||
| e69452883f | |||
| 8624888bfc | |||
| fad5c7c921 | |||
| 0dad93bf00 | |||
| c51aa910cb | |||
| 6d486aeb4d | |||
| 7041a4a4a9 | |||
| 381570cd9e | |||
| c9f93b2316 | |||
| 3706ecc5f2 | |||
| 51ad51036e | |||
| 3edbcb9ff8 | |||
| 3e3c1699da | |||
| 7666463751 | |||
| c532b90c92 | |||
| 7677733850 | |||
| 972d701801 | |||
| cdee5532b6 | |||
| 38019288de | |||
| 3e2ca328d2 | |||
| 93b44b43a5 | |||
| 4ebcf8133c | |||
| 13f8b183cb | |||
| 8e5154e65e | |||
| 6d524b632c | |||
| 4ad3d65628 | |||
| 4282014bfa | |||
| daee666735 | |||
| 5a6ae57833 | |||
| da8e5dced1 | |||
| fb38c68c7d | |||
| 2dc3c9b027 | |||
| b057312ebf | |||
| d1217e61c4 | |||
| b521b68fd8 | |||
| c9302cde7c | |||
| 823e3c76eb | |||
| 30d6289942 | |||
| a43a3d1ecd | |||
| 9e0c5e9244 | |||
| a8f1df8a50 | |||
| fba84ecd3c | |||
| 8cc4edf60f | |||
| e6eb455d71 | |||
| b84782e07f | |||
| 2e3e0bef12 | |||
| 51ff2f05bf | |||
| 42110e7f63 | |||
| f0733cb148 | |||
| 7383f44f8f | |||
| 311a80955d | |||
| 08561b11c3 | |||
| 47192cc834 | |||
| 860a0bfc8e | |||
| 5677768e99 | |||
| 4c58ca28e5 | |||
| 588d6ccaaa | |||
| 21f583800a | |||
| 4ea08a087c | |||
| e5885db187 | |||
| 1fca38625f | |||
| cf2ad43ed7 | |||
| fb3023bb79 | |||
| 52f18fea85 | |||
| d5ecb9b8ea | |||
| 67d33001d5 | |||
| 4f571fa2fc | |||
| c5bd902dc6 | |||
| 68282c7a1e | |||
| d40dcae212 | |||
| 12151c40ff | |||
| 9c8b15fa1a | |||
| 3d49cc9d13 | |||
| 955c0d19a6 | |||
| 3cf7a9e925 | |||
| 8cbf719772 | |||
| 2f614330c9 | |||
| 5a40519114 | |||
| 96271e8bd8 | |||
| 9b75a415d4 | |||
| 5f71abb5fa | |||
| affb066b7b | |||
| 17add48254 | |||
| f5b6fdcb5b | |||
| 68bdbe4c4f | |||
| 363ddc2a9b | |||
| 20d44378d9 | |||
| 4fe321275a | |||
| 60b1192703 | |||
| b25233a748 | |||
| d9b54c923c | |||
| 131ee8270b | |||
| e71bace6a8 | |||
| 2c4a0669ed | |||
| ee451b6b29 | |||
| 7b858fffb2 | |||
| 6ce0deeb63 | |||
| 96b2eaeae4 | |||
| 8fe8175a87 | |||
| ee2bedfb64 | |||
| aa8da74ed8 | |||
| f2c03a508f | |||
| aa172488b0 | |||
| ae09adf854 | |||
| 5b5dd73684 | |||
| 3849a87adc | |||
| 708dc6aca2 | |||
| 2fe2c349fd | |||
| d396d32a8f | |||
| 1fd02755f8 | |||
| 79d92b511b | |||
| ff7d93e66c | |||
| 68a8c7a235 | |||
| 5e744ff7d1 | |||
| 921a185ffa | |||
| 855751f026 | |||
| 255c2efe98 | |||
| 892b282054 | |||
| 26efc6b4e7 | |||
| a0618c5827 | |||
| d4bbb6e2c7 | |||
| 66b7692227 | |||
| 2429f507ad | |||
| 03a66d2806 | |||
| 9d8551cb48 | |||
| 2ca0b52c6a | |||
| 9cc754488b | |||
| f81ceb53cf | |||
| d1d0b5724d | |||
| 40560b71c2 | |||
| 378a25bbaa | |||
| 1dfabe2591 | |||
| 0e1689c2d9 | |||
| bd071cc472 | |||
| dde847cb5a | |||
| 3c17718f70 | |||
| 0445119e77 | |||
| e82cb5c149 | |||
| 984548f315 | |||
| a0be4f58e6 | |||
| 147602e107 | |||
| ac81169290 | |||
| 1d78e05370 | |||
| 4b15b02ac8 | |||
| 57d18a0644 | |||
| 2f73af7e6a | |||
| e712ef344d | |||
| f91e9a3119 | |||
| 9cf36f1d91 | |||
| ab9a4856ab | |||
| d52618277e | |||
| 743bd2c48f | |||
| 11894de455 | |||
| 0c8122afc4 | |||
| f893503d0c | |||
| 504782f156 | |||
| 5d1f32bfc0 | |||
| 5bc315be92 | |||
| e8f21c79b1 | |||
| ba24f9b491 | |||
| 937fa21cce | |||
| 16cbab2de7 | |||
| a85f81d8ca | |||
| 1617ede010 | |||
| d9f1d501aa | |||
| 8ee60db0df | |||
| d2e02c8ab3 | |||
| 2d4e4d2445 | |||
| 9dc69f3aa7 | |||
| 476ab506d0 | |||
| a6fa6201f7 | |||
| 26f7db0504 | |||
| 77a0da2a17 |
@@ -0,0 +1,104 @@
|
||||
import pathlib
|
||||
import shutil
|
||||
|
||||
import requests
|
||||
import zipfile
|
||||
import os
|
||||
|
||||
# To test this locally, set these environment variables
|
||||
REPO_OWNER = os.environ.get("REPO_OWNER", "IfcOpenShell/IfcOpenShell")
|
||||
MY_WORKFLOW = os.environ.get("MY_WORKFLOW", "ci-ifcopenshell-conda-daily")
|
||||
WORKFLOW_RUN_ID = os.environ.get("WORKFLOW_RUN_ID", None)
|
||||
TOKEN = os.environ.get("GITHUB_TOKEN", None)
|
||||
# To test this locally create a fine-grained personal access token for this repo with permissions "actions:read"
|
||||
# See https://github.com/settings/tokens?type=beta
|
||||
|
||||
# This is a list of strings that indicate that a job has stopped abruptly.
|
||||
SIGNS_OF_STOPPAGE = [
|
||||
"Error: The operation was canceled.",
|
||||
"fatal error C1060: compiler is out of heap space",
|
||||
]
|
||||
|
||||
|
||||
def start_request_session():
|
||||
s = requests.Session()
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
}
|
||||
if TOKEN:
|
||||
headers["Authorization"] = f"Bearer {TOKEN}"
|
||||
|
||||
s.headers = headers
|
||||
return s
|
||||
|
||||
|
||||
def get_ci_run(repo_name, run_id):
|
||||
url = f"https://api.github.com/repos/{repo_name}/actions/runs/{run_id}"
|
||||
s = start_request_session()
|
||||
response = s.get(url)
|
||||
return response.json()
|
||||
|
||||
|
||||
def evaluate_log_file_for_abrupt_stop(log_file):
|
||||
with open(log_file) as f:
|
||||
for i, line in enumerate(f):
|
||||
for sign in SIGNS_OF_STOPPAGE:
|
||||
if sign in line:
|
||||
print(f"Found sign of abrupt stoppage in [line {i}]: '{sign}'")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_ci_specific_run_specific_failure_details(repo_name, run_id):
|
||||
url = f"https://api.github.com/repos/{repo_name}/actions/runs/{run_id}/attempts/1/logs"
|
||||
s = start_request_session()
|
||||
response = s.get(url)
|
||||
|
||||
# SAVE zip file
|
||||
with open("logs.zip", "wb") as f:
|
||||
f.write(response.content)
|
||||
|
||||
# unzip file
|
||||
with zipfile.ZipFile("logs.zip", "r") as zip_ref:
|
||||
zip_ref.extractall("logs")
|
||||
|
||||
failed_logs = []
|
||||
for file in pathlib.Path("logs").iterdir():
|
||||
if file.is_dir():
|
||||
continue
|
||||
|
||||
if evaluate_log_file_for_abrupt_stop(file):
|
||||
failed_logs.append(file)
|
||||
|
||||
return failed_logs
|
||||
|
||||
|
||||
def restart_job(repo_name, job_id):
|
||||
url = f"https://api.github.com/repos/{repo_name}/actions/runs/{job_id}/rerun-failed-jobs"
|
||||
s = start_request_session()
|
||||
response = s.post(url)
|
||||
return response.json()
|
||||
|
||||
|
||||
def eval_jobs():
|
||||
run = get_ci_run(REPO_OWNER, WORKFLOW_RUN_ID)
|
||||
if run["run_attempt"] > 1:
|
||||
print("This is not the first attempt. Exiting")
|
||||
return
|
||||
failed_logs = get_ci_specific_run_specific_failure_details(REPO_OWNER, WORKFLOW_RUN_ID)
|
||||
|
||||
if len(failed_logs) == 0:
|
||||
print("No runs exhibit signs of abrupt stoppage")
|
||||
return
|
||||
|
||||
print("restarting job", WORKFLOW_RUN_ID)
|
||||
r = restart_job(REPO_OWNER, WORKFLOW_RUN_ID)
|
||||
print(r)
|
||||
|
||||
shutil.rmtree("logs")
|
||||
os.remove("logs.zip")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
eval_jobs()
|
||||
@@ -62,6 +62,7 @@ jobs:
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
python-version: '3.10'
|
||||
- run: echo ${{ env.DATE }}
|
||||
- name: Get current date
|
||||
id: date
|
||||
|
||||
@@ -8,7 +8,11 @@ on:
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "49 23 13 * *" # 11min before utc midnight
|
||||
- cron: "0 0 18 * *"
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-ifcopenshell-pypi.yml'
|
||||
|
||||
|
||||
env:
|
||||
major: 0
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
- 'test/**'
|
||||
- 'conda/**'
|
||||
- 'cmake/**'
|
||||
- '.github/workflows/ci_py_only.yml'
|
||||
- '.github/workflows/ci-py-only.yml'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
@@ -46,13 +46,14 @@ jobs:
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: ${GITHUB_WORKFLOW}
|
||||
|
||||
- name: Build ifcopenshell
|
||||
run: |
|
||||
sudo /usr/sbin/update-ccache-symlinks
|
||||
mkdir build && cd build
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
PATH="/usr/lib/ccache:$PATH" cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
@@ -62,7 +63,7 @@ jobs:
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
"-DSCHEMA_VERSIONS=2x3;4" \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add1" \
|
||||
-DBUILD_CONVERT=Off \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
@@ -79,8 +80,8 @@ jobs:
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
sudo /usr/bin/python -m pip install -U pip
|
||||
sudo /usr/bin/python -m pip install xmlschema numpy lxml
|
||||
sudo /usr/bin/python -m pip install src/bcf
|
||||
sudo /usr/bin/python -m pip install xmlschema xsdata numpy lxml
|
||||
sudo /usr/bin/python -m pip install src/bcf --no-deps
|
||||
sudo /usr/bin/python -m pip install pytest
|
||||
sudo /usr/bin/python -m pip install isodate
|
||||
sudo /usr/bin/python -m pip install lark
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Restart failed daily conda builds
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ ci-ifcopenshell-conda-daily ]
|
||||
types:
|
||||
- completed
|
||||
|
||||
env:
|
||||
REPO_OWNER: IfcOpenShell/IfcOpenShell
|
||||
MY_WORKFLOW: ci-ifcopenshell-conda-daily
|
||||
|
||||
jobs:
|
||||
restart-failed-runs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Use python 3.11
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.11
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
- name: Check for failed runs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
run: |
|
||||
python check_repo_ci_jobs.py
|
||||
working-directory: .github/workflows
|
||||
@@ -57,6 +57,8 @@ jobs:
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: ${GITHUB_WORKFLOW}
|
||||
|
||||
- name: Build ifcopenshell
|
||||
run: |
|
||||
@@ -73,7 +75,7 @@ jobs:
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3" \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add1" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
@@ -103,8 +105,8 @@ jobs:
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
sudo /usr/bin/python -m pip install -U pip
|
||||
sudo /usr/bin/python -m pip install xmlschema numpy lxml
|
||||
sudo /usr/bin/python -m pip install src/bcf
|
||||
sudo /usr/bin/python -m pip install xmlschema xsdata numpy lxml
|
||||
sudo /usr/bin/python -m pip install src/bcf --no-deps
|
||||
sudo /usr/bin/python -m pip install pytest
|
||||
sudo /usr/bin/python -m pip install isodate
|
||||
sudo /usr/bin/python -m pip install lark
|
||||
|
||||
@@ -84,6 +84,9 @@ src/ifcopenshell-python/test/build
|
||||
# blenderbim libs
|
||||
src/blenderbim/blenderbim/libs
|
||||
|
||||
# blenderbim test temp files
|
||||
src/blenderbim/test/files/temp
|
||||
|
||||
# ifcopenshell swig and compiled files
|
||||
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
|
||||
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
|
||||
@@ -1017,7 +1017,7 @@ endforeach(COMPONENT)
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3.8 ${BOOST_DEPS}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3.8, python3-pytest ${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")
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{% set name = "ifcopenshell" %}
|
||||
{% set version = "0.7.0" %}
|
||||
{% set occt_version = "7.6.2" %}
|
||||
{% set cgal_cpp_version = "5.3" %}
|
||||
{% set hdf5_version = "1.12.1" %}
|
||||
|
||||
|
||||
package:
|
||||
name: {{ name }}
|
||||
|
||||
@@ -1,31 +1,11 @@
|
||||
.PHONY: test
|
||||
test:
|
||||
.PHONY: ci
|
||||
ci:
|
||||
tox
|
||||
|
||||
.PHONY: qa
|
||||
qa:
|
||||
black .
|
||||
isort .
|
||||
pylint ./src --output-format=colorized
|
||||
|
||||
.PHONY: typecheck
|
||||
typecheck:
|
||||
mypy src/bcf
|
||||
|
||||
.PHONY: license
|
||||
license:
|
||||
copyright-header --license LGPL3 --copyright-holder "Andrea Ghensi <andrea.ghensi@gmail.com>" --copyright-year "2022" --copyright-software "IfcOpenShell" --copyright-software-description "BCF XML file handling" -a ./ -o ./
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
coverage run --source bcf -m pytest tests
|
||||
coverage html
|
||||
xdg-open htmlcov/index.html
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf htmlcov
|
||||
|
||||
# TODO: make this based on xsd file presence
|
||||
.PHONY: models
|
||||
models:
|
||||
|
||||
@@ -27,7 +27,7 @@ with load("/path/to/file.bcf") as bcfxml:
|
||||
# Get a dictionary of topics
|
||||
topics = bcfxml.topics
|
||||
|
||||
for topic_handler in bcfxml.topics:
|
||||
for topic_guid, topic_handler in bcfxml.topics.items():
|
||||
topic = topic_handler.topic
|
||||
print("Topic guid is", topic.guid)
|
||||
print("Topic title is", topic.title)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
name: bcf-client
|
||||
channels:
|
||||
- conda-forge
|
||||
dependencies:
|
||||
- ifcopenshell
|
||||
- xsdata
|
||||
- numpy
|
||||
@@ -15,6 +15,7 @@ keywords = ["IFC", "BCF", "BIM", "eingineering"]
|
||||
dependencies = [
|
||||
"xsdata",
|
||||
"numpy",
|
||||
"ifcopenshell",
|
||||
]
|
||||
version = "0.0.1"
|
||||
classifiers = [
|
||||
@@ -58,20 +59,34 @@ exclude_lines = [
|
||||
[tool.tox]
|
||||
legacy_tox_ini = """
|
||||
[tox]
|
||||
envlist = py310
|
||||
isolated_build = true
|
||||
env_list = lint, type, py3{10,11}
|
||||
skip_missing_interpreters = true
|
||||
requires = tox-conda
|
||||
|
||||
[testenv]
|
||||
conda_deps =
|
||||
deps =
|
||||
pytest
|
||||
pytest-cov
|
||||
coverage
|
||||
conda_channels =
|
||||
conda-forge
|
||||
conda_env = environment.yml
|
||||
commands = pytest --cov --cov-report=term tests
|
||||
|
||||
[testenv:lint]
|
||||
description = run linters
|
||||
skip_install = true
|
||||
deps =
|
||||
black
|
||||
isort
|
||||
pylint
|
||||
commands =
|
||||
black {posargs:.}
|
||||
isort {posargs:.}
|
||||
pylint {posargs:.} --output-format=colorized
|
||||
|
||||
[testenv:type]
|
||||
description = run type checks
|
||||
deps =
|
||||
mypy>=0.991
|
||||
commands =
|
||||
- mypy {posargs:src}
|
||||
"""
|
||||
|
||||
[tool.mypy]
|
||||
|
||||
@@ -4,4 +4,3 @@ pylint
|
||||
isort
|
||||
xsdata
|
||||
tox==3.27.1
|
||||
tox-conda
|
||||
|
||||
@@ -23,8 +23,9 @@ from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
from bcf.v2.bcfxml import BcfXml as BcfXml2
|
||||
from bcf.v2.model import Version as Version2
|
||||
from bcf.v3.bcfxml import BcfXml as BcfXml3
|
||||
from bcf.v3.model import Version
|
||||
from bcf.v3.model import Version as Version3
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
@@ -65,5 +66,8 @@ def _get_version(filepath: Union[str, Path], xml_handler: Optional[AbstractXmlPa
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
with zipfile.ZipFile(filepath) as bcf_zip:
|
||||
version = xml_handler.parse(bcf_zip.read("bcf.version"), Version)
|
||||
try:
|
||||
version = xml_handler.parse(bcf_zip.read("bcf.version"), Version3)
|
||||
except:
|
||||
version = xml_handler.parse(bcf_zip.read("bcf.version"), Version2)
|
||||
return version.version_id
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""BCF XML V2 Topic handler."""
|
||||
import datetime
|
||||
import tempfile
|
||||
import uuid
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
@@ -218,9 +219,56 @@ class TopicHandler:
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
destination_zip.writestr(real_path.at, self.document_references[doc.referenced_document])
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> None:
|
||||
def extract_file(self, entity, outfile: Optional[Path] = None) -> Path:
|
||||
"""Extracts an element with a file into a temporary directory
|
||||
|
||||
These include header files, bim snippets, document references, and
|
||||
viewpoint bitmaps. External reference are not downloaded. Instead, the
|
||||
URI reference is returned.
|
||||
|
||||
:param entity: The entity with a file reference to extract
|
||||
:type entity: bcf.v2.model.HeaderFile,bcf.v2.model.BimSnippet,bcf.v2.model.TopicDocumentReference
|
||||
:param outfile: If provided, save the header file to that location.
|
||||
Otherwise, a temporary directory is created and the filename is
|
||||
derived from the header's original filename.
|
||||
:type outfile: pathlib.Path,optional
|
||||
:return: The filepath of the extracted file. It may be a URL if the
|
||||
header file is external.
|
||||
:rtype: Path
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
if hasattr(entity, "reference"):
|
||||
reference = entity.reference
|
||||
else:
|
||||
reference = entity.referenced_document
|
||||
|
||||
if not reference:
|
||||
return
|
||||
|
||||
if getattr(entity, "is_external", False):
|
||||
return entity.reference
|
||||
|
||||
resolved_reference = self._topic_dir
|
||||
|
||||
for part in Path(reference).parts:
|
||||
if part == "..":
|
||||
resolved_reference = resolved_reference.parent
|
||||
else:
|
||||
resolved_reference = resolved_reference.joinpath(part)
|
||||
|
||||
if not outfile:
|
||||
if getattr(entity, "filename", None):
|
||||
filename = entity.filename
|
||||
else:
|
||||
filename = resolved_reference.name
|
||||
outfile = Path(tempfile.mkdtemp()) / filename
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(resolved_reference.read_bytes())
|
||||
|
||||
return outfile
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> None:
|
||||
"""Add a viewpoint pointed at the placement of an IFC element to the topic.
|
||||
|
||||
Args:
|
||||
element: The IFC element.
|
||||
@@ -229,11 +277,11 @@ class TopicHandler:
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
"""Add a viewpoint pointing at an XYZ point in space
|
||||
|
||||
Args:
|
||||
element: The IFC element.
|
||||
position: the XYZ point in space
|
||||
guids: one or more element GlobalIds.
|
||||
"""
|
||||
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
@@ -241,8 +289,8 @@ class TopicHandler:
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
self.viewpoints[new_viewpoint.guid] = new_viewpoint
|
||||
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid + ".bcfv", guid=new_viewpoint.guid))
|
||||
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,8 @@ from typing import Any, Iterable, Optional
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.util import placement
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
from numpy.typing import NDArray
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
@@ -209,7 +210,12 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
elem_placement = placement.get_local_placement(element.ObjectPlacement)
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[0][3] *= unit_scale
|
||||
elem_placement[1][3] *= unit_scale
|
||||
elem_placement[2][3] *= unit_scale
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
|
||||
@@ -187,10 +187,12 @@ class TopicHandler:
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
self.viewpoints[new_viewpoint.guid] = new_viewpoint
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
if self.topic.viewpoints is None:
|
||||
self.topic.viewpoints = mdl.TopicViewpoints()
|
||||
self.topic.viewpoints.view_point.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
|
||||
self.topic.viewpoints.view_point.append(
|
||||
mdl.ViewPoint(viewpoint=new_viewpoint.guid + ".bcfv", guid=new_viewpoint.guid)
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
|
||||
@@ -15,7 +15,7 @@ def test_create_clash_set_bcf() -> None:
|
||||
assert len(topic.viewpoints) == 1
|
||||
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
|
||||
v_info = vi_handler.visualization_info
|
||||
assert v_info.guid == guid
|
||||
assert f"{v_info.guid}.bcfv" == guid
|
||||
components = v_info.components.selection.component
|
||||
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
|
||||
camera = v_info.perspective_camera
|
||||
|
||||
@@ -15,7 +15,7 @@ def test_create_clash_set_bcf() -> None:
|
||||
assert len(topic.viewpoints) == 1
|
||||
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
|
||||
v_info = vi_handler.visualization_info
|
||||
assert v_info.guid == guid
|
||||
assert f"{v_info.guid}.bcfv" == guid
|
||||
components = v_info.components.selection.component
|
||||
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
|
||||
camera = v_info.perspective_camera
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2020-2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -16,7 +16,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
PYTHON:=python
|
||||
PYTHON:=python3.10
|
||||
PIP:=pip3.10
|
||||
PATCH:=patch
|
||||
SED:=sed -i
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@@ -56,7 +57,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py37h95e2c48_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py37h0379df6_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a2/73/07f507b539527579c026ed5f411c35e287685ce794c4dfb7edf6675cdc24/Shapely-1.8.4-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1d/a4/931d0780f31f3ea8c4f9ef6464a2825137c5241e6707a5fb03bef760a7eb/shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/ed/d5/c2e84e1e36ab8ebea033921d5886a056c77e18bab5ab1051fcc22de2e8a2/Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
@@ -64,7 +65,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py39h5aed9d1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/08/12/5af915b2bc0470f4a652ef9978557a0fa8b705b122ccaec6626029e1674c/Shapely-1.8.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
@@ -72,7 +73,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/60/20/c6321e3da92a4ad9495727c9aa8c423e47295572ccd35ece82d1ac013e28/Shapely-1.8.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
@@ -86,7 +87,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/1e/3e/f0abc15d5dac50939bccc589aae336d5ead4c72e7ad1039a2e0f3630ea92/lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/47/a9/7c644e71638bff2bbaa87f05ee62fdf6b4552a55e74bbf731d6210b6d436/Shapely-1.8.4-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/49/c26fc3b5b0e82bdc9d8751d6b939da29327b0d98f7c3b95a575cbfed2743/Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
@@ -94,7 +95,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py39h5405915_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/f5/f9/bf6cc34b02b3c7d3777c5e47fa57d2f69387613bbd8decdf3577a9d83a83/Shapely-1.8.4-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
@@ -102,7 +103,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/f5/a8/02347c450b5507b9a7a2c15ad6a029613149468fc55ac2100ab215666159/Shapely-1.8.4-cp310-cp310-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
@@ -117,7 +118,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-64/lxml-4.9.1-py37h994c40b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/47/a9/7c644e71638bff2bbaa87f05ee62fdf6b4552a55e74bbf731d6210b6d436/Shapely-1.8.4-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
@@ -125,7 +126,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-arm64/hp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.7/download/osx-arm64/eigenpy-2.6.7-py39ha69f3c1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h2550fe3_0.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/8e/60/8186995a96c5a27c9b79c015bbdd89c6d9a6df9988f86f3f1027ecd4a56a/Shapely-1.8.4-cp39-cp39-macosx_11_0_arm64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
@@ -133,7 +134,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-arm64/hp
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.9/download/osx-arm64/eigenpy-2.6.9-py310habfc766_1.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h4566fd1_0.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ba/d0/75cd36c81a1aa26dd6d37cf7ae1c79b0bc2d379e0f32c359bcb00b14e5c3/Shapely-1.8.4-cp310-cp310-macosx_11_0_arm64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
|
||||
@@ -147,7 +148,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py37h2c32e34_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py37h3b38789_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/9e/5e/171ee9d40a600f565fe691ec5bf7596247ec62cfb2edc00c91afe8ea837b/lxml-4.6.3-cp37-cp37m-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ca/4d/85b912d7947261e6b3218b01946bcd7c2efb2bbafaf517c6fe1dc3bb8399/Shapely-1.8.4-cp37-cp37m-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e2/87/b8b8d8d57b429b01aa56b7d723075c09f33c988b8091bb6f790c83436909/shapely-2.0.1-cp37-cp37m-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/69/f5/9e802159d78b2eaf26bf1f8b94648605993f5ca7247ac8870f065063fc40/Pillow-9.2.0-cp37-cp37m-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
@@ -155,7 +156,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py39h3ce40e6_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/72/c3/0d52812db25ca71203f6203891277e6ee4f0244b21a36c062123ece15f36/Shapely-1.8.4-cp39-cp39-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a7/ae/eab645c4075093584b7a65ab71cb8ff4563a015bd935c9b55dba14b2c1eb/shapely-2.0.1-cp39-cp39-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/19/3f/b4d4bcf05dbcbe07f2e9613a8f4180c297395e73a91d8ad22c32c6624f8c/Pillow-9.2.0-cp39-cp39-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
@@ -163,7 +164,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-f
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/24/b5/46d13799804b5fe9ede2eca8b19023410e29b78baca0bff727671a1df8c1/Shapely-1.8.4-cp310-cp310-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
|
||||
@@ -189,7 +190,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-07ee62e-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-476ab50-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -207,14 +208,16 @@ endif
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/validate.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
|
||||
# Provides bcf functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCClash functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcclash/ifcclash dist/blenderbim/libs/site/packages/
|
||||
# Provides BIMTester functionality
|
||||
cd dist/working && $(PYTHON) -m venv env
|
||||
cd dist/working && . env/bin/activate && pip install pybabel
|
||||
cd dist/working && . env/bin/activate && pip install babel
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pybabel
|
||||
cd dist/working && . env/bin/activate && $(PIP) install babel
|
||||
cd dist/working && . env/bin/activate && ./env/bin/pybabel compile -d ./IfcOpenShell-0.7.0/src/ifcbimtester/bimtester/locale/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcbimtester/bimtester dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCTester functionality
|
||||
@@ -236,6 +239,7 @@ endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Mustache templating in construction documentation
|
||||
# TODO: remove this dependency, it seems overkill and we seem to get by with str replaces
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/3f/e7/8750ba6c6101d6aa5ceeb20c013adf2c6f3554a12c71d75654b468404bfa/pystache-0.6.0.tar.gz
|
||||
cd dist/working && tar -xzvf pystache*
|
||||
@@ -367,10 +371,10 @@ endif
|
||||
cd dist/working && cp *.whl shapely.zip && unzip shapely.zip
|
||||
cp -r dist/working/shapely dist/blenderbim/libs/site/packages/
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Shapely.libs dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/shapely.libs dist/blenderbim/libs/site/packages/
|
||||
endif
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/Shapely.libs dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/shapely.libs dist/blenderbim/libs/site/packages/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
@@ -392,11 +396,13 @@ endif
|
||||
cp -r dist/working/tqdm-4.50.0/tqdm dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCityJSON
|
||||
mkdir dist/working
|
||||
cd dist/working && $(PYTHON) -m venv env
|
||||
cd dist/working && mkdir site-packages
|
||||
cd dist/working && . env/bin/activate && pip install cjio --target=./site-packages
|
||||
# Required by IFCCityJSON
|
||||
cd dist/working && . env/bin/activate && $(PIP) install cjio --target=./site-packages
|
||||
# Provides express rule validation for ifcopenshell.validate
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pytest --target=./site-packages
|
||||
cd dist/working/site-packages/ && rm -r *dist-info*
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
@@ -542,6 +548,7 @@ endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCOBie for XLSX support
|
||||
# TODO: see if we can replace this with openpyxl which does both read/write
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/0c/bc/82d6783f83f65f56d8b77d052773c4a2f952fa86385f0cd54e1e006658d7/XlsxWriter-1.2.9.tar.gz
|
||||
cd dist/working && tar -xzvf XlsxWriter*
|
||||
@@ -613,7 +620,7 @@ endif
|
||||
cd dist/working/brickschema-0.5.1/ && cp -r brickschema ../../blenderbim/libs/site/packages/
|
||||
# For now lets bundle the latest nightly schema
|
||||
cd dist/working && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||
cd dist/working && cp Brick.ttl ../../blenderbim/bim/schema/Brick.ttl
|
||||
cd dist/working && cp Brick.ttl ../blenderbim/bim/schema/Brick.ttl
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
@@ -645,6 +652,13 @@ endif
|
||||
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required for hipped roof generation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/prochitecture/bpypolyskel/archive/refs/heads/master.zip
|
||||
cd dist/working && unzip master.zip
|
||||
cp -r dist/working/bpypolyskel-master/bpypolyskel dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
|
||||
@@ -152,7 +152,7 @@ def register():
|
||||
bpy.app.handlers.redo_post.append(handler.redo_post)
|
||||
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_post.append(handler.ensureIfcExported)
|
||||
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
|
||||
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
@@ -174,7 +174,7 @@ def unregister():
|
||||
bpy.utils.unregister_class(cls)
|
||||
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
|
||||
bpy.app.handlers.save_post.remove(handler.ensureIfcExported)
|
||||
del bpy.types.Scene.BIMProperties
|
||||
del bpy.types.Object.BIMObjectProperties
|
||||
del bpy.types.Material.BIMObjectProperties
|
||||
|
||||
@@ -74,17 +74,17 @@ DATA;
|
||||
#67=IFCMATERIALLAYERSET((#69),$,$);
|
||||
#68=IFCRELASSOCIATESMATERIAL('03XoY4bYD4ZwZgvg3JqIIK',$,$,$,(#66),#67);
|
||||
#69=IFCMATERIALLAYER(#19,0.3,$,$,$,$,$);
|
||||
#70=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,0.5,0.6);
|
||||
#70=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
|
||||
#71=IFCCOLUMNTYPE('187MdL9O13_OzlwsOaT0zY',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#72=IFCMATERIALPROFILESET($,$,(#74),$);
|
||||
#73=IFCRELASSOCIATESMATERIAL('1CMVTn08P2Tu8afsZHQiUk',$,$,$,(#71),#72);
|
||||
#74=IFCMATERIALPROFILE($,$,#19,#70,$,$);
|
||||
#75=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,$,$,0.25,0.005);
|
||||
#75=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
|
||||
#76=IFCCOLUMNTYPE('3e6tIcskjDOuj1_UAWcT87',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#77=IFCMATERIALPROFILESET($,$,(#79),$);
|
||||
#78=IFCRELASSOCIATESMATERIAL('0ahT30pYb7zQ968gWY9q1A',$,$,$,(#76),#77);
|
||||
#79=IFCMATERIALPROFILE($,$,#19,#75,$,$);
|
||||
#80=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,$,$,0.075,0.15,0.005,0.005,0.005);
|
||||
#80=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
|
||||
#81=IFCCOLUMNTYPE('0ZxXYg0BTFg8sbqXr50qPW',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#82=IFCMATERIALPROFILESET($,$,(#84),$);
|
||||
#83=IFCRELASSOCIATESMATERIAL('0ul_xSpIH2MxsZcv$ZFXG7',$,$,$,(#81),#82);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Annotation.ifc','2020-01-01T00:00:00',(),(),'EPset_Annotation','EPset_Annotation',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,22 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',(),(),'Psets_BBIM_Annotation','Psets_BBIM_Annotation',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#6,#7));
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#9,#10,#11,#12,#13));
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2a_9s8spHDc9dZHtgg71XL',$,'ShowStartArrow','Display start arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('1naFqntIL7igCY7hCaE7kq',$,'HasConnectedSectionLine','Connect or disconnect section markers with line (by default = True).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -19,11 +19,12 @@
|
||||
*/
|
||||
|
||||
* { stroke-linecap: round; stroke-linejoin: round; }
|
||||
text { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT'; font-size: 4.13px; }
|
||||
text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', 'DejaVu Sans Condensed', 'Liberation Sans', 'Arial Narrow', 'Arial'; font-size: 4.13px; }
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
|
||||
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.IfcGeographicElement { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 1; }
|
||||
.PredefinedType-LINEWORK { stroke: black; stroke-width: 0.25; }
|
||||
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
|
||||
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
|
||||
@@ -32,23 +33,27 @@ text { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT';
|
||||
.PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); }
|
||||
.PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); }
|
||||
.PredefinedType-ANGLE { marker-start: url(#angle-marker-start); marker-end: url(#angle-marker-end); }
|
||||
.PredefinedType-RADIUS { marker-end: url(#radius-marker-end); }
|
||||
.PredefinedType-RADIUS { marker-end: url(#fall-marker-end); }
|
||||
.PredefinedType-FALL { marker-end: url(#radius-marker-end); }
|
||||
.PredefinedType-SLOPEANGLE { marker-end: url(#radius-marker-end); }
|
||||
.PredefinedType-SLOPEPERCENT { marker-end: url(#radius-marker-end); }
|
||||
.PredefinedType-SLOPEFRACTION { marker-end: url(#radius-marker-end); }
|
||||
.PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); }
|
||||
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
|
||||
.PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
|
||||
.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; }
|
||||
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
|
||||
.PredefinedType-FILLAREA { fill: white; stroke: black; }
|
||||
.PredefinedType-BREAKLINE { fill: none; stroke: black; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
|
||||
.PredefinedType-TEXT { fill: black; stroke: none; }
|
||||
path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); }
|
||||
text.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
|
||||
text.title { /* 7mm */ font-size: 11.55px; }
|
||||
text.header { /* 5mm */ font-size: 8.25px; }
|
||||
text.large { /* 3.5mm */ font-size: 5.78px; }
|
||||
text.regular { /* 2.5mm */ font-size: 4.13px; }
|
||||
text.small { /* 1.8mm */ font-size: 2.97px; }
|
||||
text.PredefinedType-TEXTLEADER, tspan.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
|
||||
text.title, tspan.title { /* 7mm */ font-size: 11.55px; }
|
||||
text.header, tspan.header { /* 5mm */ font-size: 8.25px; }
|
||||
text.large, tspan.large { /* 3.5mm */ font-size: 5.78px; }
|
||||
text.regular, tspan.regular { /* 2.5mm */ font-size: 4.13px; }
|
||||
text.small, tspan.small { /* 1.8mm */ font-size: 2.97px; }
|
||||
text.GRID, tspan.GRID { /* 5mm */ font-size: 8.25px; }
|
||||
.material-blank { fill: white; }
|
||||
.material-diagonal1 { fill: url(#diagonal1); }
|
||||
.material-diagonal2 { fill: url(#diagonal2); }
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<marker id="batting" markerHeight="10" markerWidth="4" orient="auto" refX="0" refY="5" markerUnits="userSpaceOnUse">
|
||||
<!-- Tip: https://svg-path-visualizer.netlify.app/ -->
|
||||
<path style="fill: none; stroke:black; stroke-width:0.18" d="M 0 2 A 2 2 0 0 1 4 2 L 2 8 M 0 2 L 2 8 A 2 2 0 0 0 4 10 M 2 8 A 2 2 0 0 1 0 10" />
|
||||
</marker>
|
||||
<!-- Tip: https://www.svgviewer.dev/ -->
|
||||
<marker id="eng-dimension-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
@@ -21,6 +19,12 @@
|
||||
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
|
||||
</g>
|
||||
</marker>
|
||||
<!-- same as for the radius -->
|
||||
<marker id="fall-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
|
||||
<g>
|
||||
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="angle-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
|
||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -5,7 +5,9 @@
|
||||
</g>
|
||||
<g id="door-tag">
|
||||
<circle cy="1.75" r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<text text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
<line x1="-5" y1="1.75" x2="5" y2="1.75" style="stroke: black; stroke-width: 0.25;" />
|
||||
<text y="5" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
</g>
|
||||
<g id="elevation-arrow">
|
||||
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,15 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 841 594"
|
||||
id="root"
|
||||
version="1.1"
|
||||
height="594mm"
|
||||
width="841mm">
|
||||
width="841mm"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs901" />
|
||||
<metadata
|
||||
@@ -20,7 +20,6 @@
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
@@ -44,146 +43,10 @@
|
||||
width="3027.1016"
|
||||
id="rect116616"
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.88976377;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
id="text116526"
|
||||
y="909.15491"
|
||||
x="113.70713"
|
||||
style="font-style:normal;font-weight:normal;font-size:15.1536px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15.1536px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.94488188;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="909.15491"
|
||||
x="113.70713"
|
||||
id="tspan116524">REV. NO.</tspan></text>
|
||||
<path
|
||||
id="path116530"
|
||||
d="M 75.590564,895.24802 H 3102.9921"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path116532"
|
||||
d="M 151.6811,895.24802 V 1046.9291"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 454.04331,895.24802 V 1046.9291"
|
||||
id="path116534" />
|
||||
<path
|
||||
id="path116536"
|
||||
d="M 529.63386,895.24802 V 1046.4302"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 605.22441,895.24802 V 1046.9291"
|
||||
id="path116538" />
|
||||
<path
|
||||
id="path116540"
|
||||
d="M 75.590564,914.14565 H 605.22441"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 75.590564,933.04329 H 605.22441"
|
||||
id="path116542" />
|
||||
<path
|
||||
id="path116544"
|
||||
d="M 75.590564,951.94093 H 605.22441"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 75.590564,970.83857 H 605.22441"
|
||||
id="path116546" />
|
||||
<path
|
||||
id="path116548"
|
||||
d="M 75.590564,989.73621 H 605.22441"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 75.590564,1008.6338 H 605.22441"
|
||||
id="path116550" />
|
||||
<path
|
||||
id="path116552"
|
||||
d="M 75.590564,1027.5315 H 605.22441"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path116576"
|
||||
d="M 2800.5,895.24802 V 1046.9291"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path116578"
|
||||
d="m 2800.5,1008.6338 h 302.4921"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path116598"
|
||||
d="m 3027.4027,1008.6338 v 38.2953"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:15.1536px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
x="302.86591"
|
||||
y="909.15491"
|
||||
id="text1395"><tspan
|
||||
id="tspan1393"
|
||||
x="302.86591"
|
||||
y="909.15491"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15.1536px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.94488188;stroke-miterlimit:4;stroke-dasharray:none">DESCRIPTION</tspan></text>
|
||||
<text
|
||||
id="text1399"
|
||||
y="909.15491"
|
||||
x="491.97513"
|
||||
style="font-style:normal;font-weight:normal;font-size:15.1536px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15.1536px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.94488188;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="909.15491"
|
||||
x="491.97513"
|
||||
id="tspan1397">AUTHOR</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:16.0591px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
x="568.05585"
|
||||
y="909.15485"
|
||||
id="text1403"><tspan
|
||||
id="tspan1401"
|
||||
x="568.05585"
|
||||
y="909.15485"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.0591px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.94488188;stroke-miterlimit:4;stroke-dasharray:none">DATE</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:11.2271px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
x="2839.5081"
|
||||
y="1020.4988"
|
||||
id="text1421"><tspan
|
||||
id="tspan1419"
|
||||
x="2839.5081"
|
||||
y="1020.4988"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.2271px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.94488188;stroke-miterlimit:4;stroke-dasharray:none">DRAWING NUMBER</tspan></text>
|
||||
<text
|
||||
id="text1429"
|
||||
y="1020.4988"
|
||||
x="3038.5586"
|
||||
style="font-style:normal;font-weight:normal;font-size:11.2271px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.94488188;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.2271px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.94488188;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="1020.4988"
|
||||
x="3038.5586"
|
||||
id="tspan1427">REV</tspan></text>
|
||||
<text
|
||||
id="text2529"
|
||||
y="1036.1166"
|
||||
x="3030.9653"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3918px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="stroke-width:0.999999px"
|
||||
y="1036.1166"
|
||||
x="3030.9653"
|
||||
id="tspan2527">{{Revision}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3918px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="2804.7021"
|
||||
y="1036.1166"
|
||||
id="text2533"><tspan
|
||||
id="tspan2531"
|
||||
x="2804.7021"
|
||||
y="1036.1166"
|
||||
style="stroke-width:0.999999px">{{Identification}} - {{Name}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
@@ -205,4 +68,260 @@
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
id="g90677"
|
||||
style="stroke-width:3.77953" />
|
||||
<g
|
||||
id="g38171"
|
||||
transform="translate(411.014,286.96036)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 329.96563,246.86771 V 286.75"
|
||||
id="path116576" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 329.96563,276.8677 H 410"
|
||||
id="path116578" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 390.00031,276.8677 V 287"
|
||||
id="path116598" />
|
||||
<text
|
||||
id="text1421"
|
||||
y="280.00699"
|
||||
x="340.28653"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.9705px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.9705px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="280.00699"
|
||||
x="340.28653"
|
||||
id="tspan1419">DRAWING NUMBER</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.05925px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25747;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="393.00568"
|
||||
y="280.0592"
|
||||
id="text1429"><tspan
|
||||
id="tspan1427"
|
||||
x="393.00568"
|
||||
y="280.0592"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.05925px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25747;stroke-miterlimit:4;stroke-dasharray:none">REV</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="390.94287"
|
||||
y="284.13919"
|
||||
id="text2529"><tspan
|
||||
id="tspan2527"
|
||||
x="390.94287"
|
||||
y="284.13919"
|
||||
style="stroke-width:0.264583px">{{Revision}}</tspan></text>
|
||||
<text
|
||||
id="text2533"
|
||||
y="284.13919"
|
||||
x="331.07742"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="stroke-width:0.265;stroke-dasharray:none"
|
||||
y="284.13919"
|
||||
x="331.07742"
|
||||
id="tspan2531">{{Identification}}</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 329.96563,266.868 H 410"
|
||||
id="path36736" />
|
||||
<text
|
||||
id="text36740"
|
||||
y="270.05899"
|
||||
x="339.00919"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.05925px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25747;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.05925px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25747;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="270.05899"
|
||||
x="339.00919"
|
||||
id="tspan36738">DRAWING TITLE</tspan></text>
|
||||
<text
|
||||
id="text36744"
|
||||
y="275.05634"
|
||||
x="330.99438"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.96055px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.371;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="stroke-width:0.371;stroke-dasharray:none"
|
||||
y="275.05634"
|
||||
x="330.99438"
|
||||
id="tspan36742">{{Name}}</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 390.00031,246.86771 V 266.993"
|
||||
id="path36746" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.88677px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.169851;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="397.04108"
|
||||
y="249.72177"
|
||||
id="text36677"><tspan
|
||||
id="tspan36675"
|
||||
x="397.04108"
|
||||
y="249.72177"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.88677px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.169851;stroke-miterlimit:4;stroke-dasharray:none">GRID NORTH</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.88677px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.169851;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="338.77008"
|
||||
y="249.72177"
|
||||
id="text36752"><tspan
|
||||
id="tspan36750"
|
||||
x="335.76562"
|
||||
y="249.72177"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.88677px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.169851;stroke-miterlimit:4;stroke-dasharray:none">COMPANY</tspan></text>
|
||||
<g
|
||||
id="g37148"
|
||||
transform="translate(399.87519,258.25776)">
|
||||
<g
|
||||
id="g37140"
|
||||
data-type="grid-north">
|
||||
<path
|
||||
id="path11652"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="M 0,0 V 7.12566 Z" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="M 7.3079,3e-5 H -7.30798"
|
||||
id="path36547" />
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
id="path36601"
|
||||
cx="-3.8305665e-05"
|
||||
cy="4.15625e-05"
|
||||
r="5" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="m -5.00004,4e-5 5,-5 5,5 H 3.94162 L -4e-5,-3.94162 -3.9417,4e-5 Z"
|
||||
id="path36671" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="M -4e-5,-7.12545 V 3e-5"
|
||||
id="path36673" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g38492"
|
||||
transform="translate(9.7896584,287.21036)">
|
||||
<text
|
||||
id="text116526"
|
||||
y="250.54721"
|
||||
x="20.085007"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="250.54721"
|
||||
x="20.085007"
|
||||
id="tspan116524">REV. NO.</tspan></text>
|
||||
<path
|
||||
id="path116532"
|
||||
d="M 30.132288,246.86771 V 287"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 110.13229,246.86771 V 287"
|
||||
id="path116534" />
|
||||
<path
|
||||
id="path116536"
|
||||
d="M 130.13229,246.86771 V 286.868"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 150.13229,246.86771 V 287"
|
||||
id="path116538" />
|
||||
<path
|
||||
id="path116540"
|
||||
d="M 10,251.86771 H 170.132"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10,256.86771 H 170.132"
|
||||
id="path116542" />
|
||||
<path
|
||||
id="path116544"
|
||||
d="M 10,261.86771 H 170.132"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10,266.86771 H 170.132"
|
||||
id="path116546" />
|
||||
<path
|
||||
id="path116548"
|
||||
d="M 10,271.86771 H 170.132"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10,276.8677 H 170.132"
|
||||
id="path116550" />
|
||||
<path
|
||||
id="path116552"
|
||||
d="M 10,281.86772 H 170.132"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="70.13327"
|
||||
y="250.54721"
|
||||
id="text1395"><tspan
|
||||
id="tspan1393"
|
||||
x="70.13327"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DESCRIPTION</tspan></text>
|
||||
<text
|
||||
id="text1399"
|
||||
y="250.54721"
|
||||
x="120.16841"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="250.54721"
|
||||
x="120.16841"
|
||||
id="tspan1397">AUTHOR</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="140.29811"
|
||||
y="250.54721"
|
||||
id="text1403"><tspan
|
||||
id="tspan1401"
|
||||
x="140.29811"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">ISSUED</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="180.14232"
|
||||
y="250.54721"
|
||||
id="text36130"><tspan
|
||||
id="tspan36128"
|
||||
x="175.72011"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">NOTES</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="194.37886"
|
||||
y="255.5477"
|
||||
id="text36768"><tspan
|
||||
id="tspan36766"
|
||||
x="189.95665"
|
||||
y="255.5477"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DO NOT SCALE DRAWINGS</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 170.132,246.86771 V 287"
|
||||
id="path36836" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="160.29811"
|
||||
y="250.54721"
|
||||
id="text36842"><tspan
|
||||
id="tspan36840"
|
||||
x="160.29811"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DATE</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 20 KiB |
@@ -1,15 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="594mm"
|
||||
height="420mm"
|
||||
version="1.1"
|
||||
id="root"
|
||||
viewBox="0 0 594 420">
|
||||
viewBox="0 0 594 420"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs901" />
|
||||
<metadata
|
||||
@@ -20,7 +20,6 @@
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
@@ -40,146 +39,10 @@
|
||||
width="553.5"
|
||||
id="rect116616"
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.499999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
id="text116526"
|
||||
y="363.54721"
|
||||
x="30.085009"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="363.54721"
|
||||
x="30.085009"
|
||||
id="tspan116524">REV. NO.</tspan></text>
|
||||
<path
|
||||
id="path116530"
|
||||
d="m 20.000001,359.86771 553.749999,0"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path116532"
|
||||
d="M 40.132289,359.86771 V 400"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 120.13229,359.86771 V 400"
|
||||
id="path116534" />
|
||||
<path
|
||||
id="path116536"
|
||||
d="M 140.13229,359.86771 V 399.868"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 160.13229,359.86771 V 400"
|
||||
id="path116538" />
|
||||
<path
|
||||
id="path116540"
|
||||
d="M 20.000001,364.86771 H 160.13229"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 20.000001,369.86771 H 160.13229"
|
||||
id="path116542" />
|
||||
<path
|
||||
id="path116544"
|
||||
d="M 20.000001,374.86771 H 160.13229"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 20.000001,379.86771 H 160.13229"
|
||||
id="path116546" />
|
||||
<path
|
||||
id="path116548"
|
||||
d="M 20.000001,384.86771 H 160.13229"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 20.000001,389.8677 H 160.13229"
|
||||
id="path116550" />
|
||||
<path
|
||||
id="path116552"
|
||||
d="M 20.000001,394.86772 H 160.13229"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="80.13327"
|
||||
y="363.54721"
|
||||
id="text1395"><tspan
|
||||
id="tspan1393"
|
||||
x="80.13327"
|
||||
y="363.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DESCRIPTION</tspan></text>
|
||||
<text
|
||||
id="text1399"
|
||||
y="363.54721"
|
||||
x="130.16841"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="363.54721"
|
||||
x="130.16841"
|
||||
id="tspan1397">AUTHOR</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="150.29811"
|
||||
y="363.54721"
|
||||
id="text1403"><tspan
|
||||
id="tspan1401"
|
||||
x="150.29811"
|
||||
y="363.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DATE</tspan></text>
|
||||
<path
|
||||
id="path116576"
|
||||
d="M 493.96563,359.86771 V 400"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path116578"
|
||||
d="M 493.96563,389.8677 H 574"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path116598"
|
||||
d="M 554.00031,389.8677 V 400"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.9705px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="504.28653"
|
||||
y="393.00699"
|
||||
id="text1421"><tspan
|
||||
id="tspan1419"
|
||||
x="504.28653"
|
||||
y="393.00699"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.9705px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DRAWING NUMBER</tspan></text>
|
||||
<text
|
||||
id="text1429"
|
||||
y="393.00699"
|
||||
x="556.9519"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.9705px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.9705px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="393.00699"
|
||||
x="556.9519"
|
||||
id="tspan1427">REV</tspan></text>
|
||||
<text
|
||||
id="text2529"
|
||||
y="397.13919"
|
||||
x="554.94287"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="stroke-width:0.264583px"
|
||||
y="397.13919"
|
||||
x="554.94287"
|
||||
id="tspan2527">{{Revision}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="495.07742"
|
||||
y="397.13919"
|
||||
id="text2533"><tspan
|
||||
id="tspan2531"
|
||||
x="495.07742"
|
||||
y="397.13919"
|
||||
style="stroke-width:0.264583px">{{Identification}} - {{Name}}</tspan></text>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90669"
|
||||
@@ -200,4 +63,260 @@
|
||||
style="stroke-width:3.77953"
|
||||
id="g90677"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<g
|
||||
id="g38171"
|
||||
transform="translate(163.75769,113)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 329.96563,246.86771 V 286.75"
|
||||
id="path116576" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 329.96563,276.8677 H 410"
|
||||
id="path116578" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 390.00031,276.8677 V 287"
|
||||
id="path116598" />
|
||||
<text
|
||||
id="text1421"
|
||||
y="280.00699"
|
||||
x="340.28653"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.9705px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.9705px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="280.00699"
|
||||
x="340.28653"
|
||||
id="tspan1419">DRAWING NUMBER</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.05925px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25747;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="393.00568"
|
||||
y="280.0592"
|
||||
id="text1429"><tspan
|
||||
id="tspan1427"
|
||||
x="393.00568"
|
||||
y="280.0592"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.05925px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25747;stroke-miterlimit:4;stroke-dasharray:none">REV</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="390.94287"
|
||||
y="284.13919"
|
||||
id="text2529"><tspan
|
||||
id="tspan2527"
|
||||
x="390.94287"
|
||||
y="284.13919"
|
||||
style="stroke-width:0.264583px">{{Revision}}</tspan></text>
|
||||
<text
|
||||
id="text2533"
|
||||
y="284.13919"
|
||||
x="331.07742"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="stroke-width:0.265;stroke-dasharray:none"
|
||||
y="284.13919"
|
||||
x="331.07742"
|
||||
id="tspan2531">{{Identification}}</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 329.96563,266.868 H 410"
|
||||
id="path36736" />
|
||||
<text
|
||||
id="text36740"
|
||||
y="270.05899"
|
||||
x="339.00919"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.05925px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25747;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.05925px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25747;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="270.05899"
|
||||
x="339.00919"
|
||||
id="tspan36738">DRAWING TITLE</tspan></text>
|
||||
<text
|
||||
id="text36744"
|
||||
y="275.05634"
|
||||
x="330.99438"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.96055px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.371;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="stroke-width:0.371;stroke-dasharray:none"
|
||||
y="275.05634"
|
||||
x="330.99438"
|
||||
id="tspan36742">{{Name}}</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 390.00031,246.86771 V 266.993"
|
||||
id="path36746" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.88677px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.169851;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="397.04108"
|
||||
y="249.72177"
|
||||
id="text36677"><tspan
|
||||
id="tspan36675"
|
||||
x="397.04108"
|
||||
y="249.72177"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.88677px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.169851;stroke-miterlimit:4;stroke-dasharray:none">GRID NORTH</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.88677px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.169851;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="338.77008"
|
||||
y="249.72177"
|
||||
id="text36752"><tspan
|
||||
id="tspan36750"
|
||||
x="335.76562"
|
||||
y="249.72177"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.88677px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.169851;stroke-miterlimit:4;stroke-dasharray:none">COMPANY</tspan></text>
|
||||
<g
|
||||
id="g37148"
|
||||
transform="translate(399.87519,258.25776)">
|
||||
<g
|
||||
id="g37140"
|
||||
data-type="grid-north">
|
||||
<path
|
||||
id="path11652"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="M 0,0 V 7.12566 Z" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="M 7.3079,3e-5 H -7.30798"
|
||||
id="path36547" />
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
id="path36601"
|
||||
cx="-3.8305665e-05"
|
||||
cy="4.15625e-05"
|
||||
r="5" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="m -5.00004,4e-5 5,-5 5,5 H 3.94162 L -4e-5,-3.94162 -3.9417,4e-5 Z"
|
||||
id="path36671" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="M -4e-5,-7.12545 V 3e-5"
|
||||
id="path36673" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g38492"
|
||||
transform="translate(10.000001,113)">
|
||||
<text
|
||||
id="text116526"
|
||||
y="250.54721"
|
||||
x="20.085007"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="250.54721"
|
||||
x="20.085007"
|
||||
id="tspan116524">REV. NO.</tspan></text>
|
||||
<path
|
||||
id="path116532"
|
||||
d="M 30.132288,246.86771 V 287"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 110.13229,246.86771 V 287"
|
||||
id="path116534" />
|
||||
<path
|
||||
id="path116536"
|
||||
d="M 130.13229,246.86771 V 286.868"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 150.13229,246.86771 V 287"
|
||||
id="path116538" />
|
||||
<path
|
||||
id="path116540"
|
||||
d="M 10,251.86771 H 170.132"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10,256.86771 H 170.132"
|
||||
id="path116542" />
|
||||
<path
|
||||
id="path116544"
|
||||
d="M 10,261.86771 H 170.132"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10,266.86771 H 170.132"
|
||||
id="path116546" />
|
||||
<path
|
||||
id="path116548"
|
||||
d="M 10,271.86771 H 170.132"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10,276.8677 H 170.132"
|
||||
id="path116550" />
|
||||
<path
|
||||
id="path116552"
|
||||
d="M 10,281.86772 H 170.132"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="70.13327"
|
||||
y="250.54721"
|
||||
id="text1395"><tspan
|
||||
id="tspan1393"
|
||||
x="70.13327"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DESCRIPTION</tspan></text>
|
||||
<text
|
||||
id="text1399"
|
||||
y="250.54721"
|
||||
x="120.16841"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="250.54721"
|
||||
x="120.16841"
|
||||
id="tspan1397">AUTHOR</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="140.29811"
|
||||
y="250.54721"
|
||||
id="text1403"><tspan
|
||||
id="tspan1401"
|
||||
x="140.29811"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">ISSUED</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="180.14232"
|
||||
y="250.54721"
|
||||
id="text36130"><tspan
|
||||
id="tspan36128"
|
||||
x="175.72011"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">NOTES</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="194.37886"
|
||||
y="255.5477"
|
||||
id="text36768"><tspan
|
||||
id="tspan36766"
|
||||
x="189.95665"
|
||||
y="255.5477"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DO NOT SCALE DRAWINGS</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 170.132,246.86771 V 287"
|
||||
id="path36836" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="160.29811"
|
||||
y="250.54721"
|
||||
id="text36842"><tspan
|
||||
id="tspan36840"
|
||||
x="160.29811"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DATE</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 20 KiB |
@@ -1,15 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="420mm"
|
||||
height="297mm"
|
||||
version="1.1"
|
||||
id="root"
|
||||
viewBox="0 0 420 297">
|
||||
viewBox="0 0 420 297"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs901" />
|
||||
<metadata
|
||||
@@ -20,7 +20,6 @@
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
@@ -40,150 +39,10 @@
|
||||
width="399.5"
|
||||
id="rect116616"
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.49999897;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
id="text116526"
|
||||
y="250.54721"
|
||||
x="20.085007"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="250.54721"
|
||||
x="20.085007"
|
||||
id="tspan116524">REV. NO.</tspan></text>
|
||||
<path
|
||||
id="path116530"
|
||||
d="m 10,246.86771 399.75,0"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path116532"
|
||||
d="M 30.132288,246.86771 V 287"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 110.13229,246.86771 V 287"
|
||||
id="path116534" />
|
||||
<path
|
||||
id="path116536"
|
||||
d="M 130.13229,246.86771 V 286.868"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 150.13229,246.86771 V 287"
|
||||
id="path116538" />
|
||||
<path
|
||||
id="path116540"
|
||||
d="M 10,251.86771 H 150.13229"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10,256.86771 H 150.13229"
|
||||
id="path116542" />
|
||||
<path
|
||||
id="path116544"
|
||||
d="M 10,261.86771 H 150.13229"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10,266.86771 H 150.13229"
|
||||
id="path116546" />
|
||||
<path
|
||||
id="path116548"
|
||||
d="M 10,271.86771 H 150.13229"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10,276.8677 H 150.13229"
|
||||
id="path116550" />
|
||||
<path
|
||||
id="path116552"
|
||||
d="M 10,281.86772 H 150.13229"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="70.13327"
|
||||
y="250.54721"
|
||||
id="text1395"><tspan
|
||||
id="tspan1393"
|
||||
x="70.13327"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DESCRIPTION</tspan></text>
|
||||
<text
|
||||
id="text1399"
|
||||
y="250.54721"
|
||||
x="120.16841"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="250.54721"
|
||||
x="120.16841"
|
||||
id="tspan1397">AUTHOR</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="140.29811"
|
||||
y="250.54721"
|
||||
id="text1403"><tspan
|
||||
id="tspan1401"
|
||||
x="140.29811"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DATE</tspan></text>
|
||||
<g
|
||||
transform="translate(-154)"
|
||||
id="g3735">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 483.96563,246.86771 V 287"
|
||||
id="path116576" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 483.96563,276.8677 H 564"
|
||||
id="path116578" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 544.00031,276.8677 V 287"
|
||||
id="path116598" />
|
||||
<text
|
||||
id="text1421"
|
||||
y="280.00699"
|
||||
x="494.28653"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.9705px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.9705px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="280.00699"
|
||||
x="494.28653"
|
||||
id="tspan1419">DRAWING NUMBER</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.9705px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="546.9519"
|
||||
y="280.00699"
|
||||
id="text1429"><tspan
|
||||
id="tspan1427"
|
||||
x="546.9519"
|
||||
y="280.00699"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.9705px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">REV</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="544.94287"
|
||||
y="284.13919"
|
||||
id="text2529"><tspan
|
||||
id="tspan2527"
|
||||
x="544.94287"
|
||||
y="284.13919"
|
||||
style="stroke-width:0.264583px">{{Revision}}</tspan></text>
|
||||
<text
|
||||
id="text2533"
|
||||
y="284.13919"
|
||||
x="485.07742"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="stroke-width:0.264583px"
|
||||
y="284.13919"
|
||||
x="485.07742"
|
||||
id="tspan2531">{{Identification}} - {{Name}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90669"
|
||||
@@ -204,4 +63,271 @@
|
||||
style="stroke-width:3.77953"
|
||||
id="g90677"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:100%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="162.3047"
|
||||
y="256.66803"
|
||||
id="text36756" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:20.4013px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT, Normal';text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264999;stroke-linecap:round;stroke-dasharray:none"
|
||||
x="161.2028"
|
||||
y="266.93387"
|
||||
id="text36760" />
|
||||
<g
|
||||
id="g38492">
|
||||
<text
|
||||
id="text116526"
|
||||
y="250.54721"
|
||||
x="20.085007"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="250.54721"
|
||||
x="20.085007"
|
||||
id="tspan116524">REV. NO.</tspan></text>
|
||||
<path
|
||||
id="path116532"
|
||||
d="M 30.132288,246.86771 V 287"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 110.13229,246.86771 V 287"
|
||||
id="path116534" />
|
||||
<path
|
||||
id="path116536"
|
||||
d="M 130.13229,246.86771 V 286.868"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 150.13229,246.86771 V 287"
|
||||
id="path116538" />
|
||||
<path
|
||||
id="path116540"
|
||||
d="m 10,251.86771 160.132,0"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 10,256.86771 160.132,0"
|
||||
id="path116542" />
|
||||
<path
|
||||
id="path116544"
|
||||
d="m 10,261.86771 160.132,0"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 10,266.86771 160.132,0"
|
||||
id="path116546" />
|
||||
<path
|
||||
id="path116548"
|
||||
d="m 10,271.86771 160.132,0"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 10,276.8677 160.132,0"
|
||||
id="path116550" />
|
||||
<path
|
||||
id="path116552"
|
||||
d="m 10,281.86772 160.132,0"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="70.13327"
|
||||
y="250.54721"
|
||||
id="text1395"><tspan
|
||||
id="tspan1393"
|
||||
x="70.13327"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DESCRIPTION</tspan></text>
|
||||
<text
|
||||
id="text1399"
|
||||
y="250.54721"
|
||||
x="120.16841"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.00939px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.00939px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="250.54721"
|
||||
x="120.16841"
|
||||
id="tspan1397">AUTHOR</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="140.29811"
|
||||
y="250.54721"
|
||||
id="text1403"><tspan
|
||||
id="tspan1401"
|
||||
x="140.29811"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">ISSUED</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="180.14232"
|
||||
y="250.54721"
|
||||
id="text36130"><tspan
|
||||
id="tspan36128"
|
||||
x="175.72011"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">NOTES</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="194.37886"
|
||||
y="255.5477"
|
||||
id="text36768"><tspan
|
||||
id="tspan36766"
|
||||
x="189.95665"
|
||||
y="255.5477"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DO NOT SCALE DRAWINGS</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 170.132,246.86771 V 287"
|
||||
id="path36836" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.24897px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="160.29811"
|
||||
y="250.54721"
|
||||
id="text36842"><tspan
|
||||
id="tspan36840"
|
||||
x="160.29811"
|
||||
y="250.54721"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.24897px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none">DATE</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="g38171"
|
||||
transform="translate(-0.25000095)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 329.96563,246.86771 V 286.75"
|
||||
id="path116576" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 329.96563,276.8677 H 410"
|
||||
id="path116578" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 390.00031,276.8677 V 287"
|
||||
id="path116598" />
|
||||
<text
|
||||
id="text1421"
|
||||
y="280.00699"
|
||||
x="340.28653"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.9705px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.9705px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="280.00699"
|
||||
x="340.28653"
|
||||
id="tspan1419">DRAWING NUMBER</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.05925px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25747;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="393.00568"
|
||||
y="280.0592"
|
||||
id="text1429"><tspan
|
||||
id="tspan1427"
|
||||
x="393.00568"
|
||||
y="280.0592"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.05925px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25747;stroke-miterlimit:4;stroke-dasharray:none">REV</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="390.94287"
|
||||
y="284.13919"
|
||||
id="text2529"><tspan
|
||||
id="tspan2527"
|
||||
x="390.94287"
|
||||
y="284.13919"
|
||||
style="stroke-width:0.264583px">{{Revision}}</tspan></text>
|
||||
<text
|
||||
id="text2533"
|
||||
y="284.13919"
|
||||
x="331.07742"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="stroke-width:0.265;stroke-dasharray:none"
|
||||
y="284.13919"
|
||||
x="331.07742"
|
||||
id="tspan2531">{{Identification}}</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 329.96563,266.868 H 410"
|
||||
id="path36736" />
|
||||
<text
|
||||
id="text36740"
|
||||
y="270.05899"
|
||||
x="339.00919"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.05925px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25747;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.05925px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.25747;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
y="270.05899"
|
||||
x="339.00919"
|
||||
id="tspan36738">DRAWING TITLE</tspan></text>
|
||||
<text
|
||||
id="text36744"
|
||||
y="275.05634"
|
||||
x="330.99438"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.96055px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.371;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="stroke-width:0.371;stroke-dasharray:none"
|
||||
y="275.05634"
|
||||
x="330.99438"
|
||||
id="tspan36742">{{Name}}</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 390.00031,246.86771 V 266.993"
|
||||
id="path36746" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.88677px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.169851;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="397.04108"
|
||||
y="249.72177"
|
||||
id="text36677"><tspan
|
||||
id="tspan36675"
|
||||
x="397.04108"
|
||||
y="249.72177"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.88677px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.169851;stroke-miterlimit:4;stroke-dasharray:none">GRID NORTH</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.88677px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.169851;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="338.77008"
|
||||
y="249.72177"
|
||||
id="text36752"><tspan
|
||||
id="tspan36750"
|
||||
x="335.76562"
|
||||
y="249.72177"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.88677px;line-height:100%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.169851;stroke-miterlimit:4;stroke-dasharray:none">COMPANY</tspan></text>
|
||||
<g
|
||||
id="g37148"
|
||||
transform="translate(399.87519,258.25776)">
|
||||
<g
|
||||
id="g37140"
|
||||
data-type="grid-north">
|
||||
<path
|
||||
id="path11652"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="M 0,0 V 7.12566 Z" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="M 7.3079,3e-5 H -7.30798"
|
||||
id="path36547" />
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
id="path36601"
|
||||
cx="-3.8305665e-05"
|
||||
cy="4.15625e-05"
|
||||
r="5" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="m -5.00004,4e-5 5,-5 5,5 H 3.94162 L -4e-5,-3.94162 -3.9417,4e-5 Z"
|
||||
id="path36671" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-dasharray:none"
|
||||
d="M -4e-5,-7.12545 V 3e-5"
|
||||
id="path36673" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 20 KiB |
@@ -126,7 +126,8 @@ class IfcExporter:
|
||||
continue
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=obj)
|
||||
if self.has_changed_shading(obj):
|
||||
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=obj)
|
||||
else:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
@@ -148,11 +149,15 @@ class IfcExporter:
|
||||
checksum = obj.data.BIMMeshProperties.material_checksum
|
||||
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
|
||||
|
||||
def has_changed_shading(self, obj):
|
||||
checksum = obj.BIMMaterialProperties.shading_checksum
|
||||
return checksum != repr(np.array(obj.diffuse_color).tobytes())
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not tool.Ifc.is_moved(obj):
|
||||
return
|
||||
blender_matrix = np.array(obj.matrix_world)
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
return element
|
||||
|
||||
@@ -47,9 +47,9 @@ def mode_callback(obj, data):
|
||||
):
|
||||
continue
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
IfcStore.edited_objs.add(obj)
|
||||
tool.Ifc.edit(obj)
|
||||
elif IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcGridAxis"):
|
||||
IfcStore.edited_objs.add(obj)
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
def name_callback(obj, data):
|
||||
@@ -94,13 +94,17 @@ def name_callback(obj, data):
|
||||
|
||||
def color_callback(obj, data):
|
||||
if obj.BIMMaterialProperties.ifc_style_id:
|
||||
IfcStore.edited_objs.add(obj)
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
def active_object_callback():
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
def active_material_index_callback(obj, data):
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
def subscribe_to(object, data_path, callback):
|
||||
try:
|
||||
subscribe_to = object.path_resolve(data_path, False)
|
||||
|
||||
@@ -31,9 +31,15 @@ import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def draw_attributes(props, layout, copy_operator=None):
|
||||
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
|
||||
"""you can set attribute active in popup with `active_attribute`
|
||||
meaning you will be able to type into attribute's field without having to click
|
||||
on it first
|
||||
"""
|
||||
for attribute in props:
|
||||
row = layout.row(align=True)
|
||||
if attribute == popup_active_attribute:
|
||||
row.activate_init = True
|
||||
draw_attribute(attribute, row, copy_operator)
|
||||
|
||||
|
||||
@@ -64,14 +70,14 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
||||
text=attribute.name,
|
||||
)
|
||||
|
||||
if attribute.is_uri:
|
||||
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
||||
op.data_path = attribute.path_from_id("string_value")
|
||||
if attribute.is_optional:
|
||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
if copy_operator:
|
||||
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||
op.name = attribute.name
|
||||
if attribute.is_uri:
|
||||
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
||||
op.data_path = attribute.path_from_id("string_value")
|
||||
|
||||
|
||||
def import_attributes(ifc_class, props, data, callback=None):
|
||||
@@ -267,35 +273,6 @@ def convert_property_group_from_si(property_group, skip_props=()):
|
||||
setattr(property_group, prop_name, prop_value)
|
||||
|
||||
|
||||
def draw_image_for_ifc_profile(draw, profile, size):
|
||||
"""generates image based on `profile` using `PIL.ImageDraw`"""
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, profile)
|
||||
verts = shape.verts
|
||||
edges = shape.edges
|
||||
|
||||
grouped_verts = [[verts[i], verts[i + 1]] for i in range(0, len(verts), 3)]
|
||||
grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)]
|
||||
|
||||
max_x = max([v[0] for v in grouped_verts])
|
||||
min_x = min([v[0] for v in grouped_verts])
|
||||
max_y = max([v[1] for v in grouped_verts])
|
||||
min_y = min([v[1] for v in grouped_verts])
|
||||
|
||||
dim_x = max_x - min_x
|
||||
dim_y = max_y - min_y
|
||||
max_dim = max([dim_x, dim_y])
|
||||
scale = 100 / max_dim
|
||||
|
||||
for vert in grouped_verts:
|
||||
vert[0] = round(scale * (vert[0] - min_x)) + ((size / 2) - scale * (dim_x / 2))
|
||||
vert[1] = round(scale * (vert[1] - min_y)) + ((size / 2) - scale * (dim_y / 2))
|
||||
|
||||
for e in grouped_edges:
|
||||
draw.line((tuple(grouped_verts[e[0]]), tuple(grouped_verts[e[1]])), fill="white", width=2)
|
||||
|
||||
|
||||
class IfcHeaderExtractor:
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
|
||||
@@ -51,6 +51,7 @@ class IfcStore:
|
||||
history = []
|
||||
future = []
|
||||
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
|
||||
session_files = {}
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
@@ -71,6 +72,7 @@ class IfcStore:
|
||||
IfcStore.history = []
|
||||
IfcStore.future = []
|
||||
IfcStore.schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
|
||||
IfcStore.session_files = {}
|
||||
|
||||
@staticmethod
|
||||
def get_file():
|
||||
@@ -318,6 +320,7 @@ class IfcStore:
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
elif isinstance(obj, bpy.types.Object):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
|
||||
|
||||
for listener in IfcStore.element_listeners:
|
||||
listener(element, obj)
|
||||
|
||||
@@ -302,12 +302,12 @@ class IfcImporter:
|
||||
)
|
||||
if self.body_contexts:
|
||||
self.settings.set_context_ids(self.body_contexts)
|
||||
# Annotation is to accommodate broken Revit files
|
||||
# Annotation ContextType is to accommodate broken Revit files
|
||||
# See https://github.com/Autodesk/revit-ifc/issues/187
|
||||
self.plan_contexts = [
|
||||
c.id()
|
||||
for c in self.file.by_type("IfcGeometricRepresentationContext")
|
||||
if c.ContextType in ["Plan", "Annotation"]
|
||||
if c.ContextType in ["Plan", "Annotation"] or c.ContextIdentifier == "Annotation"
|
||||
]
|
||||
if self.plan_contexts:
|
||||
self.settings_2d.set_context_ids(self.plan_contexts)
|
||||
@@ -335,14 +335,23 @@ class IfcImporter:
|
||||
if self.ifc_import_settings.has_filter or offset or offset_limit < len(self.elements):
|
||||
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
|
||||
else:
|
||||
if self.file.schema in ("IFC2X3", "IFC4"):
|
||||
if self.file.schema == "IFC2X3":
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType")
|
||||
+ self.file.by_type("IfcDoorStyle")
|
||||
+ self.file.by_type("IfcWindowStyle")
|
||||
)
|
||||
elif self.file.schema == "IFC4":
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType")
|
||||
+ self.file.by_type("IfcDoorStyle")
|
||||
+ self.file.by_type("IfcWindowStyle")
|
||||
+ self.file.by_type("IfcSpatialElementType")
|
||||
)
|
||||
else:
|
||||
self.element_types = set(self.file.by_type("IfcElementType"))
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType") + self.file.by_type("IfcSpatialElementType")
|
||||
)
|
||||
|
||||
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||
@@ -616,37 +625,33 @@ class IfcImporter:
|
||||
|
||||
def create_element_type(self, element):
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
representation_map = self.get_type_product_body_representation_map(element)
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
|
||||
mesh = None
|
||||
if representation_map:
|
||||
representation = representation_map.MappedRepresentation
|
||||
if representation:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation)
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation)
|
||||
except:
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
if shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
self.type_products[element.GlobalId] = obj
|
||||
|
||||
def get_type_product_body_representation_map(self, element):
|
||||
if not element.RepresentationMaps:
|
||||
return
|
||||
for representation_map in element.RepresentationMaps:
|
||||
context = representation_map.MappedRepresentation.ContextOfItems
|
||||
if (
|
||||
context.ContextType == "Model"
|
||||
and context.ContextIdentifier == "Body"
|
||||
and context.TargetView == "MODEL_VIEW"
|
||||
):
|
||||
return representation_map
|
||||
|
||||
def create_native_elements(self):
|
||||
progress = 0
|
||||
checkpoint = time.time()
|
||||
@@ -985,9 +990,37 @@ class IfcImporter:
|
||||
self.convert_representation(representation)
|
||||
|
||||
mesh = bpy.data.meshes.new("Native")
|
||||
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
mat = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
if props.has_blender_offset and self.is_point_far_away(self.mesh_data["co"][0:3], is_meters=False):
|
||||
offset_point = np.linalg.inv(mat) @ np.array(
|
||||
(
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
float(props.blender_orthogonal_height),
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
verts = [None] * len(self.mesh_data["co"])
|
||||
for i in range(0, len(self.mesh_data["co"]), 3):
|
||||
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
|
||||
self.mesh_data["co"][i] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 1] * self.unit_scale,
|
||||
self.mesh_data["co"][i + 2] * self.unit_scale,
|
||||
offset_point[0] * self.unit_scale,
|
||||
offset_point[1] * self.unit_scale,
|
||||
offset_point[2] * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
mesh["has_cartesian_point_offset"] = True
|
||||
else:
|
||||
verts = [c * self.unit_scale for c in self.mesh_data["co"]]
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
mesh.vertices.add(self.mesh_data["total_verts"])
|
||||
mesh.vertices.foreach_set("co", [c * self.unit_scale for c in self.mesh_data["co"]])
|
||||
# mesh.vertices.foreach_set("co", self.mesh_data["co"])
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(len(self.mesh_data["vertex_index"]))
|
||||
mesh.loops.foreach_set("vertex_index", self.mesh_data["vertex_index"])
|
||||
mesh.polygons.add(self.mesh_data["total_polygons"])
|
||||
@@ -1497,6 +1530,8 @@ class IfcImporter:
|
||||
if rendering_style and texture_style:
|
||||
self.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
||||
|
||||
tool.Style.record_shading(blender_material)
|
||||
|
||||
def create_surface_style_shading(self, blender_material, surface_style):
|
||||
alpha = 1.0
|
||||
# Transparency was added in IFC4
|
||||
|
||||
@@ -34,7 +34,6 @@ class AggregateData:
|
||||
cls.data = {
|
||||
"has_relating_object": cls.has_relating_object(),
|
||||
"relating_object_label": cls.get_relating_object_label(),
|
||||
"relating_object_id": cls.get_relating_object_id(),
|
||||
"has_related_objects": cls.has_related_objects(),
|
||||
"related_objects_amount": cls.get_related_objects_amount(),
|
||||
"ifc_class": cls.ifc_class(),
|
||||
@@ -55,12 +54,6 @@ class AggregateData:
|
||||
if aggregate:
|
||||
return f"{aggregate.is_a()}/{aggregate.Name or ''}"
|
||||
|
||||
@classmethod
|
||||
def get_relating_object_id(cls) -> int:
|
||||
aggregate = cls.get_relating_object()
|
||||
if aggregate:
|
||||
return aggregate.id()
|
||||
|
||||
@classmethod
|
||||
def get_related_objects(cls):
|
||||
return ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
|
||||
@@ -58,16 +58,22 @@ class BIM_OT_unassign_object(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.unassign_object"
|
||||
bl_label = "Unassign Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
relating_object: bpy.props.IntProperty()
|
||||
related_object: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)),
|
||||
related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)),
|
||||
)
|
||||
for obj in bpy.context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
continue
|
||||
core.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(aggregate),
|
||||
related_obj=tool.Ifc.get_object(element),
|
||||
)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
@@ -92,52 +98,61 @@ class BIM_OT_disable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
core.disable_editing_aggregate(tool.Aggregate, obj=context.active_object)
|
||||
|
||||
|
||||
class BIM_OT_add_aggregate(bpy.types.Operator):
|
||||
class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"""Add aggregate to IFC element"""
|
||||
|
||||
bl_idname = "bim.add_aggregate"
|
||||
bl_label = "Add Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_class: bpy.props.StringProperty(name="IFC Class", default="IfcElementAssembly")
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout
|
||||
row.prop(self, "ifc_class")
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return {"FINISHED"}
|
||||
try:
|
||||
ifc_class = tool.Ifc.schema().declaration_by_name(self.ifc_class).name()
|
||||
except:
|
||||
return
|
||||
aggregate = self.create_aggregate(context, ifc_class)
|
||||
|
||||
aggregate = self.create_aggregate(context)
|
||||
tool.Collector.sync(obj)
|
||||
current_aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
current_container = ifcopenshell.util.element.get_container(element)
|
||||
if current_aggregate:
|
||||
core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(current_aggregate),
|
||||
related_obj=aggregate,
|
||||
)
|
||||
elif current_container:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
structure_obj=tool.Ifc.get_object(current_container),
|
||||
element_obj=aggregate,
|
||||
)
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
return {"FINISHED"}
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
def create_aggregate(self, context):
|
||||
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
|
||||
tool.Collector.sync(obj)
|
||||
current_aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
current_container = ifcopenshell.util.element.get_container(element)
|
||||
if current_aggregate:
|
||||
core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(current_aggregate),
|
||||
related_obj=aggregate,
|
||||
)
|
||||
elif current_container:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
structure_obj=tool.Ifc.get_object(current_container),
|
||||
element_obj=aggregate,
|
||||
)
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
|
||||
def create_aggregate(self, context, ifc_class):
|
||||
aggregate_collection = bpy.data.collections.new(f"{ifc_class}/Assembly")
|
||||
context.scene.collection.children.link(aggregate_collection)
|
||||
aggregate = bpy.data.objects.new("Assembly", None)
|
||||
aggregate_collection.objects.link(aggregate)
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class=ifc_class)
|
||||
return aggregate
|
||||
|
||||
|
||||
|
||||
@@ -67,8 +67,6 @@ class BIM_PT_aggregate(Panel):
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
op = row.operator("bim.unassign_object", icon="X", text="")
|
||||
op.relating_object = AggregateData.data["relating_object_id"]
|
||||
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
else:
|
||||
row.label(text="No Aggregate", icon="TRIA_UP")
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
|
||||
@@ -20,43 +20,44 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.NewBcfProject,
|
||||
operator.LoadBcfProject,
|
||||
operator.LoadBcfTopics,
|
||||
operator.LoadBcfTopic,
|
||||
operator.LoadBcfComments,
|
||||
operator.EditBcfProjectName,
|
||||
operator.EditBcfAuthor,
|
||||
operator.EditBcfTopicName,
|
||||
operator.EditBcfTopic,
|
||||
operator.SaveBcfProject,
|
||||
operator.AddBcfTopic,
|
||||
operator.AddBcfHeaderFile,
|
||||
operator.AddBcfBimSnippet,
|
||||
operator.AddBcfReferenceLink,
|
||||
operator.AddBcfDocumentReference,
|
||||
operator.AddBcfLabel,
|
||||
operator.AddBcfRelatedTopic,
|
||||
operator.ViewBcfTopic,
|
||||
operator.RemoveBcfTopic,
|
||||
operator.RemoveBcfComment,
|
||||
operator.RemoveBcfBimSnippet,
|
||||
operator.RemoveBcfReferenceLink,
|
||||
operator.RemoveBcfDocumentReference,
|
||||
operator.RemoveBcfRelatedTopic,
|
||||
operator.EditBcfReferenceLinks,
|
||||
operator.EditBcfLabels,
|
||||
operator.RemoveBcfLabel,
|
||||
operator.EditBcfComment,
|
||||
operator.AddBcfComment,
|
||||
operator.ActivateBcfViewpoint,
|
||||
operator.AddBcfBimSnippet,
|
||||
operator.AddBcfComment,
|
||||
operator.AddBcfDocumentReference,
|
||||
operator.AddBcfHeaderFile,
|
||||
operator.AddBcfLabel,
|
||||
operator.AddBcfReferenceLink,
|
||||
operator.AddBcfRelatedTopic,
|
||||
operator.AddBcfTopic,
|
||||
operator.AddBcfViewpoint,
|
||||
operator.RemoveBcfViewpoint,
|
||||
operator.RemoveBcfFile,
|
||||
operator.EditBcfAuthor,
|
||||
operator.EditBcfComment,
|
||||
operator.EditBcfLabels,
|
||||
operator.EditBcfProjectName,
|
||||
operator.EditBcfReferenceLinks,
|
||||
operator.EditBcfTopic,
|
||||
operator.EditBcfTopicName,
|
||||
operator.ExtractBcfFile,
|
||||
operator.LoadBcfComments,
|
||||
operator.LoadBcfProject,
|
||||
operator.LoadBcfTopic,
|
||||
operator.LoadBcfTopics,
|
||||
operator.NewBcfProject,
|
||||
operator.OpenBcfReferenceLink,
|
||||
operator.SelectBcfHeaderFile,
|
||||
operator.RemoveBcfBimSnippet,
|
||||
operator.RemoveBcfComment,
|
||||
operator.RemoveBcfDocumentReference,
|
||||
operator.RemoveBcfFile,
|
||||
operator.RemoveBcfLabel,
|
||||
operator.RemoveBcfReferenceLink,
|
||||
operator.RemoveBcfRelatedTopic,
|
||||
operator.RemoveBcfTopic,
|
||||
operator.RemoveBcfViewpoint,
|
||||
operator.SaveBcfProject,
|
||||
operator.SelectBcfBimSnippetReference,
|
||||
operator.SelectBcfDocumentReference,
|
||||
operator.SelectBcfHeaderFile,
|
||||
operator.ViewBcfTopic,
|
||||
prop.BcfReferenceLink,
|
||||
prop.BcfLabel,
|
||||
prop.BcfBimSnippet,
|
||||
|
||||
@@ -20,14 +20,20 @@ import os
|
||||
import bpy
|
||||
import bcf
|
||||
import bcf.bcfxml
|
||||
import bcf.v2.model
|
||||
import uuid
|
||||
import numpy as np
|
||||
import tempfile
|
||||
import webbrowser
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
from . import bcfstore
|
||||
import blenderbim.bim.module.bcf.prop as bcf_prop
|
||||
from . import bcfstore
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import radians, degrees, atan, tan, cos, sin
|
||||
from mathutils import Vector, Matrix, Euler, geometry
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
|
||||
class NewBcfProject(bpy.types.Operator):
|
||||
@@ -56,7 +62,6 @@ class LoadBcfProject(bpy.types.Operator):
|
||||
if self.filepath:
|
||||
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_project()
|
||||
context.scene.BCFProperties.name = bcfxml.project.name
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
context.scene.BCFProperties.is_loaded = True
|
||||
@@ -74,7 +79,6 @@ class LoadBcfTopics(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_topics()
|
||||
context.scene.BCFProperties.topics.clear()
|
||||
for index, topic_guid in enumerate(bcfxml.topics.keys()):
|
||||
new = context.scene.BCFProperties.topics.add()
|
||||
@@ -91,7 +95,7 @@ class LoadBcfTopic(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.get_topic(self.topic_guid)
|
||||
topic = bcfxml.topics[self.topic_guid]
|
||||
bcfxml.get_header(self.topic_guid)
|
||||
new = context.scene.BCFProperties.topics[self.topic_index]
|
||||
data_map = {
|
||||
@@ -125,10 +129,10 @@ class LoadBcfTopic(bpy.types.Operator):
|
||||
|
||||
if topic.topic.bim_snippet:
|
||||
data_map = {
|
||||
"type": topic.bim_snippet.snippet_type,
|
||||
"is_external": topic.bim_snippet.is_external,
|
||||
"reference": topic.bim_snippet.reference,
|
||||
"schema": topic.bim_snippet.reference_schema,
|
||||
"type": topic.topic.bim_snippet.snippet_type,
|
||||
"is_external": topic.topic.bim_snippet.is_external,
|
||||
"reference": topic.topic.bim_snippet.reference,
|
||||
"schema": topic.topic.bim_snippet.reference_schema,
|
||||
}
|
||||
for key, value in data_map.items():
|
||||
if value is not None:
|
||||
@@ -164,10 +168,9 @@ class LoadBcfComments(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_comments(self.topic_guid)
|
||||
blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid)
|
||||
blender_topic.comments.clear()
|
||||
for comment in bcfxml.topics[self.topic_guid].comments.values():
|
||||
for comment in bcfxml.topics[self.topic_guid].comments:
|
||||
new = blender_topic.comments.add()
|
||||
data_map = {
|
||||
"name": comment.guid,
|
||||
@@ -192,7 +195,6 @@ class EditBcfProjectName(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.project.name = context.scene.BCFProperties.name
|
||||
bcfxml.edit_project()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -218,7 +220,6 @@ class EditBcfTopicName(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic.title = blender_topic.title
|
||||
bcfxml.edit_topic(topic)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -242,7 +243,6 @@ class EditBcfTopic(bpy.types.Operator):
|
||||
topic.topic_status = blender_topic.status or None
|
||||
topic.topic_type = blender_topic.type or None
|
||||
|
||||
bcfxml.edit_topic(topic)
|
||||
props.refresh_topic(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -256,7 +256,7 @@ class SaveBcfProject(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.save_project(self.filepath)
|
||||
bcfxml.save(self.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -299,15 +299,17 @@ class AddBcfBimSnippet(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bim_snippet = bcf.v2.data.BimSnippet()
|
||||
bim_snippet.reference = props.bim_snippet_reference
|
||||
bim_snippet.reference_schema = props.bim_snippet_schema
|
||||
bim_snippet.snippet_type = props.bim_snippet_type
|
||||
bcfxml.add_bim_snippet(topic, bim_snippet)
|
||||
is_external = "://" in props.bim_snippet_reference
|
||||
bim_snippet = bcf.v2.model.BimSnippet(
|
||||
reference=props.bim_snippet_reference if is_external else Path(props.bim_snippet_reference).name,
|
||||
reference_schema=props.bim_snippet_schema,
|
||||
snippet_type=props.bim_snippet_type,
|
||||
is_external=is_external,
|
||||
)
|
||||
topic.topic.bim_snippet = bim_snippet
|
||||
with open(props.bim_snippet_reference, "r") as f:
|
||||
topic.bim_snippet = f.read()
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
bim_snippet.reference = ""
|
||||
bim_snippet.reference_schema = ""
|
||||
bim_snippet.snippet_type = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -326,15 +328,14 @@ class AddBcfRelatedTopic(bpy.types.Operator):
|
||||
if props.related_topic == blender_topic.title:
|
||||
# Prevent adding self as related topic
|
||||
return False
|
||||
related_topic = None
|
||||
related_topic_guid = None
|
||||
for topic in bcfxml.topics.values():
|
||||
if topic.title == props.related_topic:
|
||||
related_topic = bcf.v2.data.RelatedTopic()
|
||||
related_topic.guid = topic.guid
|
||||
if topic.topic.title == props.related_topic:
|
||||
related_topic_guid = topic.guid
|
||||
break
|
||||
if not related_topic:
|
||||
if not related_topic_guid:
|
||||
return False
|
||||
if str(related_topic.guid) in [t.name for t in blender_topic.related_topics]:
|
||||
if str(related_topic_guid) in [t.name for t in blender_topic.related_topics]:
|
||||
# Prevent adding the same related topic more than once
|
||||
return False
|
||||
return True
|
||||
@@ -343,11 +344,12 @@ class AddBcfRelatedTopic(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
related_topic = bcf.v2.data.RelatedTopic()
|
||||
related_topic.guid = next((t for t in bcfxml.topics.values() if t.title == props.related_topic)).guid
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic.related_topics.append(related_topic)
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.related_topic.append(
|
||||
bcf.v2.model.TopicRelatedTopic(
|
||||
guid=next((t for t in bcfxml.topics.values() if t.topic.title == props.related_topic)).guid
|
||||
)
|
||||
)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
props.related_topic = ""
|
||||
return {"FINISHED"}
|
||||
@@ -367,15 +369,24 @@ class AddBcfHeaderFile(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
header_file = bcf.v2.data.HeaderFile()
|
||||
header_file.reference = props.file_reference
|
||||
if not os.path.exists(header_file.reference):
|
||||
header_file.filename = header_file.reference
|
||||
if len(props.file_ifc_project) == 22:
|
||||
header_file.ifc_project = props.file_ifc_project
|
||||
if len(props.file_ifc_spatial_structure_element) == 22:
|
||||
header_file.ifc_spatial_structure_element = props.file_ifc_spatial_structure_element
|
||||
bcfxml.add_file(topic, header_file)
|
||||
|
||||
is_external = "://" in props.file_reference
|
||||
filename = Path(props.file_reference).name if os.path.exists(props.file_reference) else None
|
||||
|
||||
if filename:
|
||||
with open(props.file_reference, "r") as f:
|
||||
topic.reference_files[filename] = f.read()
|
||||
|
||||
header_file = bcf.v2.model.HeaderFile(
|
||||
filename=filename,
|
||||
date=XmlDateTime.now(),
|
||||
reference=props.file_reference if is_external else Path(props.file_reference).name,
|
||||
ifc_project=props.file_ifc_project,
|
||||
ifc_spatial_structure_element=props.file_ifc_spatial_structure_element,
|
||||
is_external=is_external,
|
||||
)
|
||||
topic.header.file.append(header_file)
|
||||
|
||||
props.refresh_topic(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -409,27 +420,34 @@ class AddBcfViewpoint(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
viewpoint = bcf.v2.data.Viewpoint()
|
||||
|
||||
if blender_camera.data.type == "ORTHO":
|
||||
camera = bcf.v2.data.OrthogonalCamera()
|
||||
camera.view_to_world_scale = blender_camera.data.ortho_scale
|
||||
viewpoint.orthogonal_camera = camera
|
||||
elif blender_camera.data.type == "PERSP":
|
||||
camera = bcf.v2.data.PerspectiveCamera()
|
||||
camera.field_of_view = degrees(blender_camera.data.angle)
|
||||
viewpoint.perspective_camera = camera
|
||||
camera.camera_view_point.x = blender_camera.location.x
|
||||
camera.camera_view_point.y = blender_camera.location.y
|
||||
camera.camera_view_point.z = blender_camera.location.z
|
||||
direction = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
|
||||
camera.camera_direction.x = direction.x
|
||||
camera.camera_direction.y = direction.y
|
||||
camera.camera_direction.z = direction.z
|
||||
up = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
|
||||
camera.camera_up_vector.x = up.x
|
||||
camera.camera_up_vector.y = up.y
|
||||
camera.camera_up_vector.z = up.z
|
||||
|
||||
camera_view_point = bcf.v2.model.Point(
|
||||
x=blender_camera.location.x, y=blender_camera.location.y, z=blender_camera.location.z
|
||||
)
|
||||
camera_direction = bcf.v2.model.Direction(x=direction.x, y=direction.y, z=direction.z)
|
||||
camera_up_vector = bcf.v2.model.Direction(x=up.x, y=up.y, z=up.z)
|
||||
if blender_camera.data.type == "ORTHO":
|
||||
camera = bcf.v2.model.OrthogonalCamera(
|
||||
view_to_world_scale=blender_camera.data.ortho_scale,
|
||||
camera_view_point=camera_view_point,
|
||||
camera_direction=camera_direction,
|
||||
camera_up_vector=camera_up_vector,
|
||||
)
|
||||
visualization_info = bcf.v2.model.VisualizationInfo(orthogonal_camera=camera)
|
||||
elif blender_camera.data.type == "PERSP":
|
||||
camera = bcf.v2.model.PerspectiveCamera(
|
||||
field_of_view=degrees(blender_camera.data.angle),
|
||||
camera_view_point=camera_view_point,
|
||||
camera_direction=camera_direction,
|
||||
camera_up_vector=camera_up_vector,
|
||||
)
|
||||
visualization_info = bcf.v2.model.VisualizationInfo(guid=str(uuid.uuid4()), perspective_camera=camera)
|
||||
|
||||
# TODO allow the user to enable or disable snapshotting
|
||||
snapshot = None
|
||||
|
||||
blender_render = context.scene.render
|
||||
old_file_format = blender_render.image_settings.file_format
|
||||
@@ -437,9 +455,17 @@ class AddBcfViewpoint(bpy.types.Operator):
|
||||
old_filepath = blender_render.filepath
|
||||
blender_render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
viewpoint.snapshot = blender_render.filepath
|
||||
with open(blender_render.filepath, "rb") as f:
|
||||
snapshot = f.read()
|
||||
# viewpoint.snapshot = blender_render.filepath
|
||||
|
||||
# TODO we should handle selection and visibility state
|
||||
vizinfo = bcf.v2.visinfo.VisualizationInfoHandler(visualization_info=visualization_info, snapshot=snapshot)
|
||||
topic.viewpoints[vizinfo.guid + ".bcfv"] = vizinfo
|
||||
topic.markup.viewpoints.append(
|
||||
bcf.v2.model.ViewPoint(viewpoint=vizinfo.guid + ".bcfv", guid=vizinfo.guid, snapshot=vizinfo.guid + ".png")
|
||||
)
|
||||
|
||||
bcfxml.add_viewpoint(topic, viewpoint)
|
||||
blender_render.filepath = old_filepath
|
||||
blender_render.image_settings.file_format = old_file_format
|
||||
props.refresh_topic(context)
|
||||
@@ -459,9 +485,15 @@ class RemoveBcfViewpoint(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
viewpoint_guid = blender_topic.viewpoints
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_viewpoint(viewpoint_guid, topic)
|
||||
for key, viewpoint in topic.viewpoints.items():
|
||||
if viewpoint.guid == blender_topic.viewpoints:
|
||||
break
|
||||
del topic.viewpoints[key]
|
||||
for i, viewpoint in enumerate(topic.markup.viewpoints):
|
||||
if viewpoint.guid == blender_topic.viewpoints:
|
||||
break
|
||||
del topic.markup.viewpoints[i]
|
||||
props.refresh_topic(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -477,7 +509,7 @@ class RemoveBcfFile(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_file(topic, self.index)
|
||||
del topic.header.file[self.index]
|
||||
props.refresh_topic(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -495,8 +527,7 @@ class RemoveBcfTopic(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = context.scene.BCFProperties
|
||||
topic_to_delete = props.active_topic
|
||||
bcfxml.delete_topic(topic_to_delete.name)
|
||||
del bcfxml.topics[props.active_topic.name]
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -515,8 +546,7 @@ class AddBcfReferenceLink(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic.reference_links.append(props.reference_link)
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.reference_link.append(props.reference_link)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
props.reference_link = ""
|
||||
return {"FINISHED"}
|
||||
@@ -536,10 +566,23 @@ class AddBcfDocumentReference(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
document_reference = bcf.v2.data.DocumentReference()
|
||||
document_reference.referenced_document = props.document_reference
|
||||
document_reference.description = props.document_reference_description or None
|
||||
bcfxml.add_document_reference(topic, document_reference)
|
||||
|
||||
is_external = "://" in props.document_reference
|
||||
filename = Path(props.document_reference).name if os.path.exists(props.document_reference) else None
|
||||
|
||||
if filename:
|
||||
with open(props.document_reference, "rb") as f:
|
||||
topic.document_references[filename] = f.read()
|
||||
|
||||
document_reference = bcf.v2.model.TopicDocumentReference(
|
||||
referenced_document=props.document_reference if is_external else filename,
|
||||
description=props.document_reference_description or None,
|
||||
guid=str(uuid.uuid4()),
|
||||
is_external=is_external,
|
||||
)
|
||||
|
||||
topic.topic.document_reference.append(document_reference)
|
||||
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
props.document_reference = ""
|
||||
props.document_reference_description = ""
|
||||
@@ -562,8 +605,7 @@ class AddBcfLabel(bpy.types.Operator):
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
new = blender_topic.labels.add()
|
||||
new.name = props.label
|
||||
topic.labels.append(props.label)
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.labels.append(props.label)
|
||||
props.label = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -578,11 +620,10 @@ class EditBcfReferenceLinks(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
for index, reference_link in enumerate(topic.reference_links):
|
||||
for index, reference_link in enumerate(topic.topic.reference_link):
|
||||
if reference_link == blender_topic.reference_links[index].name:
|
||||
continue
|
||||
topic.reference_links[index] = blender_topic.reference_links[index].name
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.reference_link[index] = blender_topic.reference_links[index].name
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -597,12 +638,11 @@ class EditBcfLabels(bpy.types.Operator):
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
for index, label in enumerate(blender_topic.labels):
|
||||
if index >= len(topic.labels):
|
||||
if index >= len(topic.topic.labels):
|
||||
break
|
||||
if label.name == topic.labels[index]:
|
||||
if label.name == topic.topic.labels[index]:
|
||||
continue
|
||||
topic.labels[index] = blender_topic.labels[index].name
|
||||
bcfxml.edit_topic(topic)
|
||||
topic.topic.labels[index] = blender_topic.labels[index].name
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -617,8 +657,7 @@ class RemoveBcfReferenceLink(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.reference_links[self.index]
|
||||
bcfxml.edit_topic(topic)
|
||||
del topic.topic.reference_link[self.index]
|
||||
blender_topic.reference_links.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -634,8 +673,7 @@ class RemoveBcfLabel(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.labels[self.index]
|
||||
bcfxml.edit_topic(topic)
|
||||
del topic.topic.labels[self.index]
|
||||
blender_topic.labels.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -650,7 +688,7 @@ class RemoveBcfBimSnippet(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_bim_snippet(topic)
|
||||
topic.topic.bim_snippet = None
|
||||
blender_topic.bim_snippet.schema = ""
|
||||
blender_topic.bim_snippet.reference = ""
|
||||
blender_topic.bim_snippet.type = ""
|
||||
@@ -668,7 +706,7 @@ class RemoveBcfDocumentReference(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_document_reference(topic, self.index)
|
||||
del topic.topic.document_reference[self.index]
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -684,8 +722,7 @@ class RemoveBcfRelatedTopic(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.related_topics[self.index]
|
||||
bcfxml.edit_topic(topic)
|
||||
del topic.topic.related_topic[self.index]
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -701,7 +738,10 @@ class RemoveBcfComment(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_comment(self.comment_guid, topic)
|
||||
for i, comment in enumerate(topic.comments):
|
||||
if comment.guid == self.comment_guid:
|
||||
break
|
||||
del topic.comments[i]
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -718,9 +758,11 @@ class EditBcfComment(bpy.types.Operator):
|
||||
blender_topic = props.active_topic
|
||||
blender_comment = blender_topic.comments.get(self.comment_guid)
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
comment = topic.comments[self.comment_guid]
|
||||
comment.comment = blender_comment.comment
|
||||
bcfxml.edit_comment(comment, topic)
|
||||
for i, comment in enumerate(topic.comments):
|
||||
if comment.guid == self.comment_guid:
|
||||
comment.comment = blender_comment.comment
|
||||
comment.modified_date = XmlDateTime.now()
|
||||
comment.modified_author = context.scene.BCFProperties.author
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -745,12 +787,15 @@ class AddBcfComment(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
comment = bcf.v2.data.Comment()
|
||||
comment.comment = props.comment
|
||||
comment = bcf.v2.model.Comment(
|
||||
date=XmlDateTime.now(),
|
||||
author=context.scene.BCFProperties.author,
|
||||
comment=props.comment,
|
||||
guid=str(uuid.uuid4()),
|
||||
)
|
||||
if props.has_related_viewpoint:
|
||||
comment.viewpoint = bcf.v2.data.Viewpoint()
|
||||
comment.viewpoint.guid = blender_topic.viewpoints
|
||||
bcfxml.add_comment(topic, comment)
|
||||
comment.viewpoint = bcf.v2.model.CommentViewpoint(guid=blender_topic.viewpoints)
|
||||
topic.comments.append(comment)
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
|
||||
props.comment = ""
|
||||
props.has_related_viewpoint = False
|
||||
@@ -791,11 +836,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
cam_height = context.scene.render.resolution_y
|
||||
cam_aspect = cam_width / cam_height
|
||||
|
||||
obj.data.background_images.clear()
|
||||
if viewpoint.snapshot:
|
||||
obj.data.show_background_images = True
|
||||
obj.data.background_images.clear()
|
||||
background = obj.data.background_images.new()
|
||||
background.image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot))
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
f.write(viewpoint.snapshot)
|
||||
background.image = bpy.data.images.load(f.name)
|
||||
src_width = background.image.size[0]
|
||||
src_height = background.image.size[1]
|
||||
src_aspect = src_width / src_height
|
||||
@@ -805,6 +852,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
else:
|
||||
background.frame_method = "CROP"
|
||||
background.display_depth = "FRONT"
|
||||
else:
|
||||
obj.data.show_background_images = False
|
||||
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
|
||||
@@ -814,27 +864,27 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
gp = bpy.data.grease_pencils.get("BCF")
|
||||
if gp:
|
||||
bpy.data.grease_pencils.remove(gp)
|
||||
if viewpoint.lines:
|
||||
if viewpoint.visualization_info.lines:
|
||||
self.draw_lines(viewpoint, context)
|
||||
|
||||
self.delete_clipping_planes(context)
|
||||
if viewpoint.clipping_planes:
|
||||
if viewpoint.visualization_info.clipping_planes:
|
||||
self.create_clipping_planes(viewpoint)
|
||||
|
||||
self.delete_bitmaps(context)
|
||||
if viewpoint.bitmaps:
|
||||
if viewpoint.visualization_info.bitmap:
|
||||
self.create_bitmaps(bcfxml, viewpoint, topic)
|
||||
|
||||
self.setup_camera(viewpoint, obj, cam_aspect, context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def setup_camera(self, viewpoint, obj, cam_aspect, context):
|
||||
if viewpoint.orthogonal_camera:
|
||||
camera = viewpoint.orthogonal_camera
|
||||
if viewpoint.visualization_info.orthogonal_camera:
|
||||
camera = viewpoint.visualization_info.orthogonal_camera
|
||||
obj.data.type = "ORTHO"
|
||||
obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale
|
||||
elif viewpoint.perspective_camera:
|
||||
camera = viewpoint.perspective_camera
|
||||
obj.data.ortho_scale = viewpoint.visualization_info.orthogonal_camera.view_to_world_scale
|
||||
elif viewpoint.visualization_info.perspective_camera:
|
||||
camera = viewpoint.visualization_info.perspective_camera
|
||||
obj.data.type = "PERSP"
|
||||
if cam_aspect >= 1:
|
||||
obj.data.angle = radians(camera.field_of_view)
|
||||
@@ -875,15 +925,15 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
obj.matrix_world = Matrix(matrix.tolist())
|
||||
|
||||
def set_viewpoint_components(self, viewpoint, context):
|
||||
if not viewpoint.components:
|
||||
if not viewpoint.visualization_info.components:
|
||||
return
|
||||
|
||||
# Operators with context overrides are used because they are
|
||||
# significantly faster than looping through all objects
|
||||
|
||||
exception_global_ids = {v.ifc_guid for v in viewpoint.components.visibility.exceptions}
|
||||
exception_global_ids = {v.ifc_guid for v in viewpoint.visualization_info.components.visibility.exceptions or []}
|
||||
|
||||
if viewpoint.components.visibility.default_visibility:
|
||||
if viewpoint.visualization_info.components.visibility.default_visibility:
|
||||
old = context.area.type
|
||||
context.area.type = "VIEW_3D"
|
||||
bpy.ops.object.hide_view_clear()
|
||||
@@ -907,11 +957,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
bpy.ops.object.hide_view_set(context_override, unselected=True)
|
||||
context.area.type = old
|
||||
|
||||
if viewpoint.components.view_setup_hints:
|
||||
if not viewpoint.components.view_setup_hints.spaces_visible:
|
||||
if viewpoint.visualization_info.components.view_setup_hints:
|
||||
if not viewpoint.visualization_info.components.view_setup_hints.spaces_visible:
|
||||
self.hide_spaces(context)
|
||||
if viewpoint.components.view_setup_hints.openings_visible is not None:
|
||||
self.set_openings_visibility(viewpoint.components.view_setup_hints.openings_visible, context)
|
||||
if viewpoint.visualization_info.components.view_setup_hints.openings_visible is not None:
|
||||
self.set_openings_visibility(
|
||||
viewpoint.visualization_info.components.view_setup_hints.openings_visible, context
|
||||
)
|
||||
else:
|
||||
self.hide_spaces(context)
|
||||
self.set_openings_visibility(False, context)
|
||||
@@ -927,11 +979,12 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
context.area.type = old
|
||||
|
||||
def set_openings_visibility(self, is_visible, context):
|
||||
for collection in self.get_opening_collections(context):
|
||||
collection.hide_viewport = not is_visible
|
||||
pass # We no longer have an openings collection
|
||||
|
||||
def set_selection(self, viewpoint):
|
||||
selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection]
|
||||
if not viewpoint.visualization_info.components or not viewpoint.visualization_info.components.selection:
|
||||
return
|
||||
selected_global_ids = [s.ifc_guid for s in viewpoint.visualization_info.components.selection.component or []]
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for global_id in selected_global_ids:
|
||||
obj = IfcStore.get_element(global_id)
|
||||
@@ -940,7 +993,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
|
||||
def set_colours(self, viewpoint):
|
||||
global_id_colours = {}
|
||||
for coloring in viewpoint.components.coloring:
|
||||
for coloring in viewpoint.visualization_info.components.coloring or []:
|
||||
for component in coloring.components:
|
||||
global_id_colours.setdefault(component.ifc_guid, coloring.color)
|
||||
for global_id, color in global_id_colours.items():
|
||||
@@ -948,14 +1001,6 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
if obj:
|
||||
obj.color = self.hex_to_rgb(color)
|
||||
|
||||
def get_opening_collections(self, context):
|
||||
collections = []
|
||||
for collection in context.view_layer.layer_collection.children:
|
||||
opening_collection = collection.children.get("IfcOpeningElements")
|
||||
if opening_collection:
|
||||
collections.append(opening_collection)
|
||||
return collections
|
||||
|
||||
def draw_lines(self, viewpoint, context):
|
||||
gp = bpy.data.grease_pencils.new("BCF")
|
||||
scene = context.scene
|
||||
@@ -967,9 +1012,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
frame = layer.frames.new(1)
|
||||
stroke = frame.strokes.new()
|
||||
stroke.display_mode = "3DSPACE"
|
||||
stroke.points.add(len(viewpoint.lines) * 2)
|
||||
stroke.points.add(len(viewpoint.visualization_info.lines.line) * 2)
|
||||
coords = []
|
||||
for l in viewpoint.lines:
|
||||
for l in viewpoint.visualization_info.lines.line:
|
||||
coords.extend(
|
||||
[l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z]
|
||||
)
|
||||
@@ -977,7 +1022,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
|
||||
def create_clipping_planes(self, viewpoint):
|
||||
n = 0
|
||||
for plane in viewpoint.clipping_planes:
|
||||
for plane in viewpoint.visualization_info.clipping_planes.clipping_plane:
|
||||
bpy.ops.bim.add_section_plane()
|
||||
if n == 0:
|
||||
obj = bpy.data.objects["Section"]
|
||||
@@ -1010,10 +1055,14 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
collection = bpy.data.collections.get("Bitmaps")
|
||||
if not collection:
|
||||
collection = bpy.data.collections.new("Bitmaps")
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
for bitmap in viewpoint.visualization_info.bitmap:
|
||||
obj = bpy.data.objects.new("Bitmap", None)
|
||||
obj.empty_display_type = "IMAGE"
|
||||
image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
|
||||
# image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
topic.extract_file(bitmap, f.name)
|
||||
# f.write(bitmap.what)
|
||||
image = bpy.data.images.load(f.name)
|
||||
src_width = image.size[0]
|
||||
src_height = image.size[1]
|
||||
if src_height > src_width:
|
||||
@@ -1052,6 +1101,7 @@ class SelectBcfHeaderFile(bpy.types.Operator):
|
||||
bl_label = "Select BCF Header File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcjson", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
@@ -1093,3 +1143,25 @@ class SelectBcfDocumentReference(bpy.types.Operator):
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExtractBcfFile(bpy.types.Operator):
|
||||
bl_idname = "bim.extract_bcf_file"
|
||||
bl_label = "Extract BCF Header File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
entity_type: bpy.props.StringProperty()
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.topics[context.scene.BCFProperties.active_topic.name]
|
||||
|
||||
if self.entity_type == "HEADER_FILE":
|
||||
entity = topic.header.file[self.index]
|
||||
elif self.entity_type == "BIM_SNIPPET":
|
||||
entity = topic.markup.topic.bim_snippet
|
||||
elif self.entity_type == "DOCUMENT_REFERENCE":
|
||||
entity = topic.markup.topic.document_reference[self.index]
|
||||
|
||||
webbrowser.open(str(topic.extract_file(entity).parent))
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -95,8 +95,8 @@ def getBcfViewpoints(self, context, force_update=False):
|
||||
props = context.scene.BCFProperties
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = props.active_topic
|
||||
viewpoints = bcfxml.get_viewpoints(topic.name) if topic else {}
|
||||
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
|
||||
viewpoints = bcfxml.topics[topic.name].viewpoints.keys() if topic else []
|
||||
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints)])
|
||||
return bcfviewpoints_enum
|
||||
|
||||
|
||||
|
||||
@@ -127,20 +127,21 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
layout.label(text="Header Files:")
|
||||
|
||||
if bcf_topic.header:
|
||||
for index, f in enumerate(bcf_topic.header.files):
|
||||
for index, f in enumerate(bcf_topic.header.file):
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text=f.filename, icon="FILE_BLANK")
|
||||
if f.is_external:
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = f.reference
|
||||
else:
|
||||
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
|
||||
op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference)
|
||||
op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.entity_type = "HEADER_FILE"
|
||||
row.operator("bim.remove_bcf_file", icon="X", text="").index = index
|
||||
|
||||
row = box.row()
|
||||
row.label(text="Date")
|
||||
row.label(text=f.date)
|
||||
row.label(text=str(f.date))
|
||||
|
||||
if f.ifc_project:
|
||||
row = box.row()
|
||||
@@ -196,8 +197,8 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
if topic.bim_snippet.is_external:
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference
|
||||
else:
|
||||
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
|
||||
op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference)
|
||||
op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
|
||||
op.entity_type = "BIM_SNIPPET"
|
||||
row.operator("bim.remove_bcf_bim_snippet", icon="X", text="")
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
@@ -218,8 +219,10 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
if doc.is_external:
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference
|
||||
else:
|
||||
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
|
||||
op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference)
|
||||
op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
|
||||
op.entity_type = "DOCUMENT_REFERENCE"
|
||||
op.index = index
|
||||
|
||||
row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index
|
||||
row = box.row(align=True)
|
||||
row.prop(doc, "description")
|
||||
@@ -236,7 +239,7 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
try:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator(
|
||||
"bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].title}"
|
||||
"bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].topic.title}"
|
||||
)
|
||||
op.topic_guid = related_topic.name
|
||||
row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index
|
||||
|
||||
@@ -20,18 +20,21 @@ import bpy
|
||||
from . import ui, operator, prop
|
||||
|
||||
classes = (
|
||||
operator.ColourByRelatedBuildingElement,
|
||||
operator.DisableEditingBoundary,
|
||||
operator.EditBoundaryAttributes,
|
||||
operator.EnableEditingBoundary,
|
||||
operator.LoadProjectSpaceBoundaries,
|
||||
operator.LoadSpaceBoundaries,
|
||||
operator.LoadBoundary,
|
||||
operator.SelectRelatedElementBoundaries,
|
||||
operator.SelectProjectBoundaries,
|
||||
operator.ColourByRelatedBuildingElement,
|
||||
operator.EnableEditingBoundary,
|
||||
operator.DisableEditingBoundary,
|
||||
operator.EditBoundaryAttributes,
|
||||
operator.SelectRelatedElementTypeBoundaries,
|
||||
operator.SelectSpaceBoundaries,
|
||||
operator.UpdateBoundaryGeometry,
|
||||
ui.BIM_PT_Boundary,
|
||||
ui.BIM_PT_SpaceBoundaries,
|
||||
ui.BIM_PT_SceneBoundaries,
|
||||
ui.BIM_PT_SpaceBoundaries,
|
||||
prop.BIMBoundaryProperties,
|
||||
)
|
||||
|
||||
|
||||
@@ -158,6 +158,70 @@ class LoadSpaceBoundaries(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def get_element_boundaries(element):
|
||||
if not element:
|
||||
return ()
|
||||
if element.is_a("IfcSpace"):
|
||||
return (b for b in element.BoundedBy or ())
|
||||
return (b for b in element.ProvidesBoundaries)
|
||||
|
||||
|
||||
class SelectRelatedElementBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_related_element_boundaries"
|
||||
bl_label = "Select related element space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
related_element: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
element = tool.Ifc.get().by_id(self.related_element)
|
||||
for rel in get_element_boundaries(element):
|
||||
obj = tool.Ifc.get_object(rel)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectRelatedElementTypeBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_related_element_type_boundaries"
|
||||
bl_label = "Select related element type space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
related_element: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
element = tool.Ifc.get().by_id(self.related_element)
|
||||
if not element:
|
||||
return {"FINISHED"}
|
||||
element_type = tool.Root.get_element_type(element)
|
||||
if not element_type:
|
||||
return {"FINISHED"}
|
||||
for child in tool.Type.get_type_occurrences(element_type):
|
||||
for rel in get_element_boundaries(child):
|
||||
obj = tool.Ifc.get_object(rel)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectSpaceBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_space_boundaries"
|
||||
bl_label = "Select all space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
for rel in element.BoundedBy or []:
|
||||
obj = tool.Ifc.get_object(rel)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectProjectBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_project_space_boundaries"
|
||||
bl_label = "Select all project space boundaries"
|
||||
@@ -208,11 +272,10 @@ class ColourByRelatedBuildingElement(bpy.types.Operator):
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
for obj in context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element.is_a("IfcRelSpaceBoundary"):
|
||||
continue
|
||||
obj.color = get_colour(element)
|
||||
|
||||
@@ -102,6 +102,11 @@ class BIM_PT_Boundary(Panel):
|
||||
row.label(text=f"{entity.is_a()}/{entity.Name}")
|
||||
op = row.operator("bim.select_global_id", text="", icon="OBJECT_DATA")
|
||||
op.global_id = entity.GlobalId
|
||||
if ifc_attribute in ("RelatingSpace", "RelatedBuildingElement"):
|
||||
op = row.operator("bim.select_related_element_boundaries", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.related_element = entity.id()
|
||||
op = row.operator("bim.select_related_element_type_boundaries", text="", icon="ASSET_MANAGER")
|
||||
op.related_element = entity.id()
|
||||
else:
|
||||
row.label(text="")
|
||||
|
||||
@@ -118,6 +123,7 @@ class BIM_PT_SpaceBoundaries(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
|
||||
@classmethod
|
||||
@@ -143,6 +149,7 @@ class BIM_PT_SpaceBoundaries(Panel):
|
||||
self.ifc_file = tool.Ifc.get()
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_space_boundaries")
|
||||
row.operator("bim.select_space_boundaries", text="", icon="RESTRICT_SELECT_OFF")
|
||||
for boundary in SpaceBoundariesData.data["boundaries"]:
|
||||
row = self.layout.row()
|
||||
row.label(text=boundary["description"], icon="GHOST_ENABLED")
|
||||
|
||||
@@ -21,7 +21,7 @@ import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
|
||||
|
||||
|
||||
class CadTool(WorkSpaceTool):
|
||||
@@ -48,7 +48,9 @@ class CadTool(WorkSpaceTool):
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
obj = context.active_object
|
||||
if obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile:
|
||||
if not obj or not obj.data:
|
||||
return
|
||||
if hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
@@ -100,7 +102,57 @@ class CadTool(WorkSpaceTool):
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_X")
|
||||
row.operator("bim.reset_vertex", text="Reset Vertex")
|
||||
|
||||
elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
row.operator("bim.edit_extrusion_axis", text="Save Axis")
|
||||
row.operator("bim.disable_editing_extrusion_axis", text="", icon="CANCEL")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.cad_hotkey", text="Extend").hotkey = "S_E"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_T")
|
||||
row.operator("bim.cad_hotkey", text="Mitre").hotkey = "S_T"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_F")
|
||||
row.operator("bim.cad_hotkey", text="Fillet").hotkey = "S_F"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_O")
|
||||
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
|
||||
|
||||
else:
|
||||
if (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
row.operator("bim.cad_hotkey", text="Apply Railing Path").hotkey = "S_Q"
|
||||
row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="")
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
row.operator("bim.cad_hotkey", text="Apply Roof Path").hotkey = "S_Q"
|
||||
row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
@@ -187,7 +239,25 @@ class CadHotkey(bpy.types.Operator):
|
||||
|
||||
def hotkey_S_Q(self):
|
||||
if tool.Ifc.get_entity(bpy.context.active_object):
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
|
||||
elif (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and bpy.context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.finish_editing_railing_path()
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.finish_editing_roof_path()
|
||||
|
||||
else:
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
|
||||
@@ -210,4 +280,9 @@ class CadHotkey(bpy.types.Operator):
|
||||
|
||||
def is_profile(self):
|
||||
obj = bpy.context.active_object
|
||||
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile
|
||||
return (
|
||||
obj
|
||||
and obj.data
|
||||
and hasattr(obj.data, "BIMMeshProperties")
|
||||
and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ import json
|
||||
import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
from mathutils import Matrix
|
||||
from math import radians
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -30,8 +31,10 @@ from blenderbim.bim.ifc import IfcStore
|
||||
class ExportClashSets(bpy.types.Operator):
|
||||
bl_idname = "bim.export_clash_sets"
|
||||
bl_label = "Export Clash Sets"
|
||||
bl_description = "Export clash sets to a selected file"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
@@ -62,8 +65,10 @@ class ImportClashSets(bpy.types.Operator):
|
||||
bl_idname = "bim.import_clash_sets"
|
||||
bl_label = "Import Clash Sets"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Import clash sets from a selected file"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
@@ -98,6 +103,7 @@ class AddClashSet(bpy.types.Operator):
|
||||
bl_idname = "bim.add_clash_set"
|
||||
bl_label = "Add Clash Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add a clash set"
|
||||
|
||||
def execute(self, context):
|
||||
new = context.scene.BIMClashProperties.clash_sets.add()
|
||||
@@ -110,6 +116,7 @@ class RemoveClashSet(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_clash_set"
|
||||
bl_label = "Remove Clash Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Remove the selected clash set"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -121,6 +128,7 @@ class AddClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.add_clash_source"
|
||||
bl_label = "Add Clash Source"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add a clash source to this group"
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -133,6 +141,7 @@ class RemoveClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_clash_source"
|
||||
bl_label = "Remove Clash Source"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Remove this clash source"
|
||||
index: bpy.props.IntProperty()
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
@@ -146,6 +155,7 @@ class SelectClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.select_clash_source"
|
||||
bl_label = "Select Clash Source"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select an IFC file to add as a clash source"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
|
||||
index: bpy.props.IntProperty()
|
||||
@@ -194,7 +204,8 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator):
|
||||
class ExecuteIfcClash(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifc_clash"
|
||||
bl_label = "Execute IFC Clash"
|
||||
filter_glob: bpy.props.StringProperty( default='*.bcf;*.json', options={'HIDDEN'} )
|
||||
bl_description = "Execute clash detection and save the information to a .bcf or .json file"
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -263,6 +274,7 @@ class SelectIfcClashResults(bpy.types.Operator):
|
||||
bl_idname = "bim.select_ifc_clash_results"
|
||||
bl_label = "Select IFC Clash Results"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select the clashing IFC geometry stored in a file"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
@@ -287,10 +299,26 @@ class SelectIfcClashResults(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
for clash in clash_set["clashes"].values():
|
||||
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
|
||||
|
||||
for obj in context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
ifc_file = ""
|
||||
for scene in obj.users_scene:
|
||||
if scene.BIMProperties.ifc_file:
|
||||
ifc_file = scene.BIMProperties.ifc_file
|
||||
if scene.library:
|
||||
break
|
||||
|
||||
if ifc_file:
|
||||
if ifc_file not in IfcStore.session_files:
|
||||
IfcStore.session_files[ifc_file] = ifcopenshell.open(ifc_file)
|
||||
element_file = IfcStore.session_files[ifc_file]
|
||||
else:
|
||||
element_file = self.file
|
||||
|
||||
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if element.GlobalId in global_ids:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
@@ -308,7 +336,7 @@ class SmartClashGroup(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
from ifcclash import ifcclash
|
||||
|
||||
|
||||
settings = ifcclash.ClashSettings()
|
||||
self.filepath = bpy.path.ensure_ext(context.scene.BIMClashProperties.clash_results_path, ".json")
|
||||
settings.output = self.filepath
|
||||
|
||||
@@ -65,12 +65,13 @@ class ClassificationsData:
|
||||
|
||||
class ReferencesData:
|
||||
@classmethod
|
||||
def is_available_classification_added(cls):
|
||||
def active_classification_library(cls):
|
||||
if not IfcStore.classification_file or not IfcStore.classification_file.by_type("IfcClassification"):
|
||||
return False
|
||||
props = bpy.context.scene.BIMClassificationProperties
|
||||
name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
|
||||
return name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]
|
||||
if name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]:
|
||||
return name
|
||||
|
||||
|
||||
class ClassificationReferencesData(ReferencesData):
|
||||
@@ -81,7 +82,7 @@ class ClassificationReferencesData(ReferencesData):
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
@@ -103,7 +104,7 @@ class MaterialClassificationsData(ReferencesData):
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
@@ -125,7 +126,7 @@ class CostClassificationsData(ReferencesData):
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
|
||||
@@ -100,12 +100,16 @@ class ReferenceUI:
|
||||
self.draw_reference_ui(reference)
|
||||
|
||||
def draw_add_ui(self, context):
|
||||
if not self.data.data["is_available_classification_added"]:
|
||||
if not self.data.data["active_classification_library"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Active Classification Library")
|
||||
row.operator("bim.load_classification_library", text="", icon="IMPORT")
|
||||
return
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.sprops, "available_classifications", text="")
|
||||
row.label(text=f"Active Classification Library: {self.data.data['active_classification_library']}")
|
||||
#row.prop(self.sprops, "available_classifications", text="")
|
||||
if not self.sprops.available_library_references:
|
||||
op = row.operator("bim.change_classification_level", text="", icon="IMPORT")
|
||||
op = row.operator("bim.change_classification_level", text="", icon="GREASEPENCIL")
|
||||
op.parent_id = int(self.sprops.available_classifications)
|
||||
return
|
||||
if self.sprops.active_library_referenced_source:
|
||||
|
||||
@@ -74,6 +74,7 @@ class BIM_PT_object_constraints(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -30,6 +30,7 @@ classes = (
|
||||
operator.EnableEditingCostSchedule,
|
||||
operator.EnableEditingCostItems,
|
||||
operator.EnableEditingCostItem,
|
||||
operator.ExportCostSchedules,
|
||||
operator.ExpandCostItems,
|
||||
operator.EnableEditingCostItemQuantities,
|
||||
operator.EnableEditingCostItemQuantity,
|
||||
|
||||
@@ -21,6 +21,8 @@ import ifcopenshell
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.schema
|
||||
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -36,6 +38,8 @@ class CostSchedulesData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"predefined_types": cls.get_cost_schedule_types(),
|
||||
"total_cost_schedules": cls.total_cost_schedules(),
|
||||
"schedules": cls.schedules(),
|
||||
"is_editing_rates": cls.is_editing_rates(),
|
||||
"cost_items": cls.cost_items(),
|
||||
@@ -45,6 +49,10 @@ class CostSchedulesData:
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_cost_schedules(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcCostSchedule"))
|
||||
|
||||
@classmethod
|
||||
def schedules(cls):
|
||||
results = []
|
||||
@@ -212,6 +220,21 @@ class CostSchedulesData:
|
||||
for t in tool.Ifc.schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_cost_schedule_types(cls):
|
||||
results = []
|
||||
declaration = tool.Ifc().schema().declaration_by_name("IfcCostSchedule")
|
||||
version = tool.Ifc.get_schema()
|
||||
for attribute in declaration.attributes():
|
||||
if attribute.name() == "PredefinedType":
|
||||
results.extend(
|
||||
[
|
||||
(e, e, get_predefined_type_doc(version, "IfcCostSchedule", e))
|
||||
for e in attribute.type_of_attribute().declared_type().enumeration_items()
|
||||
]
|
||||
)
|
||||
break
|
||||
return results
|
||||
|
||||
class CostItemRatesData:
|
||||
data = {}
|
||||
|
||||
@@ -36,7 +36,7 @@ class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Add a new cost schedule"
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_cost_schedule(tool.Ifc)
|
||||
core.add_cost_schedule(tool.Ifc, predefined_type = context.scene.BIMCostProperties.cost_schedule_predefined_types)
|
||||
|
||||
|
||||
class EditCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -328,7 +328,7 @@ class DisableEditingCostItemQuantity(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Disable editing cost item quantity"
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
core.disable_editing_cost_item_quantity(tool.Cost)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -586,3 +586,22 @@ class CalculateCostItemResourceValue(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.calculate_cost_item_resource_value(tool.Ifc, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExportCostSchedules(bpy.types.Operator):
|
||||
bl_idname = "bim.export_cost_schedules"
|
||||
bl_label = "Export Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file"
|
||||
format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
|
||||
|
||||
def execute(self, context):
|
||||
core.export_cost_schedules(tool.Cost, format=self.format)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Choose a format")
|
||||
self.layout.prop(self, "format")
|
||||
|
||||
@@ -109,6 +109,12 @@ def update_cost_item_name(self, context):
|
||||
attribute.string_value = self.name
|
||||
|
||||
|
||||
def get_schedule_predefined_types(self, context):
|
||||
if not CostSchedulesData.is_loaded:
|
||||
CostSchedulesData.load()
|
||||
return CostSchedulesData.data["predefined_types"]
|
||||
|
||||
|
||||
class CostItem(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=update_cost_item_name)
|
||||
identification: StringProperty(name="Identification", update=update_cost_item_identification)
|
||||
@@ -130,6 +136,9 @@ class CostItemType(PropertyGroup):
|
||||
|
||||
|
||||
class BIMCostProperties(PropertyGroup):
|
||||
cost_schedule_predefined_types: EnumProperty(
|
||||
items=get_schedule_predefined_types, name="Predefined Type", default=None
|
||||
)
|
||||
is_cost_update_enabled: BoolProperty(name="Is Cost Update Enabled", default=True)
|
||||
cost_schedule_attributes: CollectionProperty(name="Cost Schedule Attributes", type=Attribute)
|
||||
is_editing: StringProperty(name="Is Editing")
|
||||
|
||||
@@ -42,9 +42,16 @@ class BIM_PT_cost_schedules(Panel):
|
||||
CostSchedulesData.load()
|
||||
|
||||
self.props = context.scene.BIMCostProperties
|
||||
row = self.layout.row()
|
||||
if CostSchedulesData.data["total_cost_schedules"]:
|
||||
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
||||
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="No Cost Schedules Found found.", icon="COMMUNITY")
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_cost_schedule", icon="ADD")
|
||||
row.prop(self.props, "cost_schedule_predefined_types")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="Add new")
|
||||
|
||||
for schedule in CostSchedulesData.data["schedules"]:
|
||||
self.draw_cost_schedule_ui(schedule)
|
||||
@@ -57,7 +64,6 @@ class BIM_PT_cost_schedules(Panel):
|
||||
op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.cost_schedule = cost_schedule["id"]
|
||||
row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY")
|
||||
row.operator("bim.export_cost_schedule", text="", icon="EXPORT").cost_schedule = cost_schedule["id"]
|
||||
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
|
||||
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
|
||||
elif self.props.is_editing == "COST_ITEMS":
|
||||
@@ -169,7 +175,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row.prop(self.props, "cost_types", text="")
|
||||
if self.props.cost_types == "CATEGORY":
|
||||
row.prop(self.props, "cost_category", text="")
|
||||
op = row.operator("bim.add_cost_value", text="", icon="ADD")
|
||||
op = row.operator("bim.add_cost_value", text="Add Value", icon="ADD")
|
||||
op.parent = self.props.active_cost_item_id
|
||||
op.cost_type = self.props.cost_types
|
||||
if self.props.cost_types == "CATEGORY":
|
||||
@@ -505,6 +511,8 @@ class BIM_UL_cost_items_trait:
|
||||
|
||||
self.draw_quantity_column(split2, cost_item)
|
||||
self.draw_value_column(split2, cost_item)
|
||||
if self.props.active_cost_item_id == item.ifc_definition_id:
|
||||
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
|
||||
|
||||
for column in self.props.columns:
|
||||
split2.label(text=str(cost_item["CategoryValues"].get(column.name, "-")))
|
||||
|
||||
@@ -82,6 +82,7 @@ class BIM_PT_object_documents(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -29,12 +29,10 @@ classes = (
|
||||
operator.AddDrawingToSheet,
|
||||
operator.AddSchedule,
|
||||
operator.AddScheduleToSheet,
|
||||
operator.AddSectionsAnnotations,
|
||||
operator.AddSheet,
|
||||
operator.BuildSchedule,
|
||||
operator.CleanWireframes,
|
||||
operator.ContractSheet,
|
||||
operator.CopyGrid,
|
||||
operator.CreateDrawing,
|
||||
operator.CreateSheets,
|
||||
operator.DisableEditingAssignedProduct,
|
||||
@@ -42,9 +40,12 @@ classes = (
|
||||
operator.DisableEditingSchedules,
|
||||
operator.DisableEditingSheets,
|
||||
operator.DisableEditingText,
|
||||
operator.AddTextLiteral,
|
||||
operator.RemoveTextLiteral,
|
||||
operator.DuplicateDrawing,
|
||||
operator.EditAssignedProduct,
|
||||
operator.EditText,
|
||||
operator.EditTextPopup,
|
||||
operator.EditVectorStyle,
|
||||
operator.EnableEditingAssignedProduct,
|
||||
operator.EnableEditingText,
|
||||
@@ -72,6 +73,7 @@ classes = (
|
||||
prop.Sheet,
|
||||
prop.DocProperties,
|
||||
prop.BIMCameraProperties,
|
||||
prop.Literal,
|
||||
prop.BIMTextProperties,
|
||||
prop.BIMAssignedProductProperties,
|
||||
ui.BIM_PT_camera,
|
||||
|
||||
@@ -32,7 +32,7 @@ class Annotator:
|
||||
if related_element is None:
|
||||
location, _, _, _ = Annotator.get_placeholder_coords(bpy.context)
|
||||
else:
|
||||
obj.data.BIMTextProperties.related_element = related_element
|
||||
obj.data.BIMAssignedProductProperties.related_element = related_element
|
||||
location = related_element.location
|
||||
obj.location = location
|
||||
obj.hide_render = True
|
||||
|
||||
@@ -17,16 +17,17 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
ProductAssignmentsData.is_loaded = False
|
||||
TextData.is_loaded = False
|
||||
SheetsData.is_loaded = False
|
||||
SchedulesData.is_loaded = False
|
||||
DrawingsData.is_loaded = False
|
||||
DecoratorData.data = {}
|
||||
|
||||
|
||||
class ProductAssignmentsData:
|
||||
@@ -49,28 +50,6 @@ class ProductAssignmentsData:
|
||||
return f"{rel.RelatingProduct.is_a()}/{name}"
|
||||
|
||||
|
||||
class TextData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
|
||||
return []
|
||||
rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
text_literal = [i for i in rep.Items if i.is_a("IfcTextLiteral")][0]
|
||||
return [
|
||||
{"name": "Literal", "value": text_literal.Literal},
|
||||
{"name": "BoxAlignment", "value": text_literal.BoxAlignment},
|
||||
]
|
||||
|
||||
|
||||
class SheetsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
@@ -121,3 +100,117 @@ class SchedulesData:
|
||||
@classmethod
|
||||
def total_schedules(cls):
|
||||
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SCHEDULE"])
|
||||
|
||||
|
||||
FONT_SIZES = {
|
||||
"small": 1.8,
|
||||
"regular": 2.5,
|
||||
"large": 3.5,
|
||||
"header": 5.0,
|
||||
"title": 7.0,
|
||||
}
|
||||
|
||||
|
||||
class DecoratorData:
|
||||
# stores 1 type of data per object
|
||||
data = {}
|
||||
|
||||
@classmethod
|
||||
def get_batting_thickness(cls, obj):
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
thickness = ifcopenshell.util.element.get_pset(element, "BBIM_Batting", "Thickness")
|
||||
thickness = thickness * unit_scale if thickness else 1.5
|
||||
cls.data[obj.name] = thickness
|
||||
return thickness
|
||||
|
||||
@classmethod
|
||||
def get_section_markers_display_data(cls, obj):
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
|
||||
# default values
|
||||
pset_data = {
|
||||
"HasConnectedSectionLine": True,
|
||||
"ShowStartArrow": True,
|
||||
"StartArrowSymbol": "",
|
||||
"ShowEndArrow": True,
|
||||
"EndArrowSymbol": "",
|
||||
}
|
||||
obj_pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Section") or {}
|
||||
pset_data.update(obj_pset_data)
|
||||
|
||||
# create more usable display data
|
||||
start_symbol = pset_data["StartArrowSymbol"]
|
||||
end_symbol = pset_data["EndArrowSymbol"]
|
||||
display_data = {
|
||||
"start": {
|
||||
"add_circle": pset_data["ShowStartArrow"] and not start_symbol,
|
||||
"add_symbol": pset_data["ShowStartArrow"] or bool(start_symbol),
|
||||
"symbol": start_symbol or "section-arrow",
|
||||
},
|
||||
"end": {
|
||||
"add_circle": pset_data["ShowEndArrow"] and not end_symbol,
|
||||
"add_symbol": pset_data["ShowEndArrow"] or bool(end_symbol),
|
||||
"symbol": end_symbol or "section-arrow",
|
||||
},
|
||||
"connect_markers": pset_data["HasConnectedSectionLine"],
|
||||
}
|
||||
|
||||
cls.data[obj.name] = display_data
|
||||
return display_data
|
||||
|
||||
@classmethod
|
||||
def get_ifc_text_data(cls, obj):
|
||||
"""returns font size in mm for current ifc text object"""
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
|
||||
return None
|
||||
|
||||
props = obj.BIMTextProperties
|
||||
# getting font size
|
||||
classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
# use `regular` as default
|
||||
if classes:
|
||||
classes_split = classes.split()
|
||||
# prioritize smaller font sizes just like in svg
|
||||
font_size_type = next(
|
||||
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
|
||||
)
|
||||
else:
|
||||
font_size_type = "regular"
|
||||
font_size = FONT_SIZES[font_size_type]
|
||||
|
||||
# other attributes
|
||||
props_literals = props.literals
|
||||
props_literals_n = len(props.literals)
|
||||
literals = tool.Drawing.get_text_literal(obj, return_list=True)
|
||||
literals_data = []
|
||||
for i, literal in enumerate(literals):
|
||||
literal_data = {
|
||||
"Literal": literal.Literal,
|
||||
"BoxAlignment": literal.BoxAlignment,
|
||||
}
|
||||
if i < props_literals_n:
|
||||
literal_data["CurrentValue"] = props_literals[i].value
|
||||
else:
|
||||
literal_data["CurrentValue"] = literal.Literal
|
||||
|
||||
literals_data.append(literal_data)
|
||||
|
||||
text_data = {"Literals": literals_data, "FontSize": font_size}
|
||||
cls.data[obj.name] = text_data
|
||||
return text_data
|
||||
|
||||
@@ -26,14 +26,15 @@ import bmesh
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
from math import acos, pi, atan, degrees
|
||||
from functools import reduce
|
||||
from itertools import chain
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector, Matrix
|
||||
from mathutils import Vector, Matrix, geometry
|
||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from math import acos, pi
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
|
||||
|
||||
def ccw(A, B, C):
|
||||
@@ -180,6 +181,11 @@ class BaseDecorator:
|
||||
defines=self.DEF_GLSL,
|
||||
)
|
||||
|
||||
self.font_id = blf.load(
|
||||
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
)
|
||||
|
||||
def get_camera_width_mm(self):
|
||||
# Horrific prototype code to ensure bgl draws at drawing scales
|
||||
# https://blender.stackexchange.com/questions/16493/is-there-a-way-to-fit-the-viewport-to-the-current-field-of-view
|
||||
def is_landscape(render):
|
||||
@@ -199,11 +205,9 @@ class BaseDecorator:
|
||||
camera_width_model = camera.data.ortho_scale
|
||||
else:
|
||||
camera_width_model = camera.data.ortho_scale / render.resolution_y * render.resolution_x
|
||||
self.camera_width_mm = get_scale(camera) * camera_width_model
|
||||
|
||||
self.font_id = blf.load(
|
||||
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
)
|
||||
camera_width_mm = get_scale(camera) * camera_width_model
|
||||
return camera_width_mm
|
||||
|
||||
def camera_zoom_to_factor(self, zoom):
|
||||
return math.pow(((zoom / 50) + math.sqrt(2)) / 2, 2)
|
||||
@@ -235,12 +239,20 @@ class BaseDecorator:
|
||||
if element.is_a("IfcAnnotation"):
|
||||
if element.ObjectType == self.objecttype:
|
||||
results.append(obj)
|
||||
|
||||
elif (
|
||||
self.objecttype == "MISC"
|
||||
and element.ObjectType not in decoration_presets
|
||||
and isinstance(obj.data, bpy.types.Mesh)
|
||||
):
|
||||
results.append(obj)
|
||||
|
||||
elif self.objecttype == "FALL" and element.ObjectType in (
|
||||
"SLOPE_ANGLE",
|
||||
"SLOPE_FRACTION",
|
||||
"SLOPE_PERCENT",
|
||||
):
|
||||
results.append(obj)
|
||||
return results
|
||||
|
||||
def get_path_geom(self, obj, topo=True):
|
||||
@@ -295,17 +307,10 @@ class BaseDecorator:
|
||||
|
||||
def get_editmesh_geom(self, obj):
|
||||
"""Parses editmode mesh geometry into line segments"""
|
||||
mesh = bmesh.from_edit_mesh(obj.data)
|
||||
vertices = []
|
||||
indices = []
|
||||
idx = 0
|
||||
|
||||
for edge in mesh.edges:
|
||||
vertices.extend(edge.verts)
|
||||
indices.append((idx, idx + 1))
|
||||
idx += 2
|
||||
vertices = [obj.matrix_world @ v.co for v in vertices]
|
||||
|
||||
# TODO: avoid bmesh here?
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
indices = [[v.index for v in edge.verts] for edge in bm.edges]
|
||||
vertices = [obj.matrix_world @ v.co for v in bm.verts]
|
||||
return vertices, indices
|
||||
|
||||
def decorate(self, context, object):
|
||||
@@ -313,7 +318,15 @@ class BaseDecorator:
|
||||
raise NotImplementedError()
|
||||
|
||||
def draw_lines(
|
||||
self, context, obj, vertices, indices, topology=None, is_scale_dependant=True, fill_next_vertices=False
|
||||
self,
|
||||
context,
|
||||
obj,
|
||||
vertices,
|
||||
indices,
|
||||
topology=None,
|
||||
is_scale_dependant=True,
|
||||
fill_next_vertices=False,
|
||||
extra_float_kwargs={},
|
||||
):
|
||||
# use is_scale_dependant = False if shader is not using uniform viewportDrawingScale
|
||||
# otherwise uniform will be discarded during the optimization process
|
||||
@@ -358,21 +371,39 @@ class BaseDecorator:
|
||||
# Horrific prototype code
|
||||
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
|
||||
camera_width_px = factor * context.region.width
|
||||
mm_to_px = camera_width_px / self.camera_width_mm
|
||||
mm_to_px = camera_width_px / self.get_camera_width_mm()
|
||||
# 0.00025 is a magic constant number I visually discovered to get the right number.
|
||||
# It probably should be dynamically calculated using system.dpi or something.
|
||||
viewport_drawing_scale = 0.00025 * mm_to_px
|
||||
self.shader.uniform_float("viewportDrawingScale", viewport_drawing_scale)
|
||||
|
||||
for kwarg, value in extra_float_kwargs.items():
|
||||
self.shader.uniform_float(kwarg, value)
|
||||
|
||||
batch.draw(self.shader)
|
||||
|
||||
def draw_label(self, context, text, pos, dir, gap=4, center=True, vcenter=False):
|
||||
def draw_label(
|
||||
self,
|
||||
context,
|
||||
text,
|
||||
pos,
|
||||
text_dir,
|
||||
gap=4,
|
||||
center=True,
|
||||
vcenter=False,
|
||||
font_size_mm=2.5,
|
||||
line_no=0,
|
||||
box_alignment=None,
|
||||
):
|
||||
"""Draw text label
|
||||
|
||||
Args:
|
||||
pos: bottom-center
|
||||
|
||||
aligned and centered at segment middle
|
||||
|
||||
NOTE: `blf.draw` seems to ignore the \n character, so we have to split the text ourselves
|
||||
and use the `line_no` argument of `draw_label`
|
||||
"""
|
||||
# 0 is the default font, but we're fancier than that
|
||||
font_id = self.font_id
|
||||
@@ -381,37 +412,59 @@ class BaseDecorator:
|
||||
|
||||
color = context.scene.DocProperties.decorations_colour
|
||||
|
||||
ang = -Vector((1, 0)).angle_signed(dir)
|
||||
ang = -Vector((1, 0)).angle_signed(text_dir)
|
||||
cos = math.cos(ang)
|
||||
sin = math.sin(ang)
|
||||
rotation_matrix = Matrix.Rotation(-ang, 2)
|
||||
|
||||
# Horrific prototype code
|
||||
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
|
||||
camera_width_px = factor * context.region.width
|
||||
mm_to_px = camera_width_px / self.camera_width_mm
|
||||
mm_to_px = camera_width_px / self.get_camera_width_mm()
|
||||
# 0.004118616 is a magic constant number I visually discovered to get the right number.
|
||||
# In particular it works only for the OpenGOST font and produces a 2.5mm font size.
|
||||
# It probably should be dynamically calculated using system.dpi or something.
|
||||
# font_size = 16 <-- this is a good default
|
||||
font_size = int(0.004118616 * mm_to_px)
|
||||
# TODO: need to synchronize it better with svg
|
||||
font_size_px = int(0.004118616 * mm_to_px) * font_size_mm / 2.5
|
||||
pos = pos - line_no * font_size_px * rotation_matrix[1]
|
||||
|
||||
blf.size(font_id, font_size, dpi)
|
||||
blf.size(font_id, font_size_px, dpi)
|
||||
|
||||
w, h = 0, 0
|
||||
if center or vcenter:
|
||||
if box_alignment or center or vcenter:
|
||||
w, h = blf.dimensions(font_id, text)
|
||||
|
||||
if center:
|
||||
# horizontal centering
|
||||
pos -= Vector((cos, sin)) * w * 0.5
|
||||
if box_alignment:
|
||||
box_alignment_offset = Vector((0, 0))
|
||||
if "bottom" in box_alignment:
|
||||
pass
|
||||
elif "top" in box_alignment:
|
||||
box_alignment_offset += Vector((0, h))
|
||||
else:
|
||||
# middle / center
|
||||
box_alignment_offset += Vector((0, h / 2))
|
||||
|
||||
if vcenter:
|
||||
# vertical centering
|
||||
pos -= Vector((-sin, cos)) * h * 0.5
|
||||
if "left" in box_alignment:
|
||||
pass
|
||||
elif "right" in box_alignment:
|
||||
box_alignment_offset += Vector((w, 0))
|
||||
else:
|
||||
# middle / center
|
||||
box_alignment_offset += Vector((w / 2, 0))
|
||||
|
||||
pos -= rotation_matrix.transposed() @ box_alignment_offset
|
||||
else:
|
||||
if center:
|
||||
# horizontal centering
|
||||
pos -= Vector((cos, sin)) * w * 0.5
|
||||
|
||||
if gap:
|
||||
# side-shifting
|
||||
pos += Vector((-sin, cos)) * gap
|
||||
if vcenter:
|
||||
# vertical centering
|
||||
pos -= Vector((-sin, cos)) * h * 0.5
|
||||
|
||||
if gap:
|
||||
# side-shifting
|
||||
pos += Vector((-sin, cos)) * gap
|
||||
|
||||
blf.enable(font_id, blf.ROTATION)
|
||||
blf.position(font_id, pos.x, pos.y, 0) if hasattr(pos, "x") else blf.position(font_id, 0, 0, 0)
|
||||
@@ -750,7 +803,12 @@ class LeaderDecorator(BaseDecorator):
|
||||
region3d = context.region_data
|
||||
dir = Vector((1, 0))
|
||||
pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj))
|
||||
self.draw_label(context, obj.BIMTextProperties.value, pos, dir, gap=0, center=False, vcenter=False)
|
||||
|
||||
props = obj.BIMTextProperties
|
||||
text_data = props.get_text_edited_data() if props.is_editing else DecoratorData.get_ifc_text_data(obj)
|
||||
text = text_data["Literals"][0]['CurrentValue']
|
||||
|
||||
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
|
||||
|
||||
|
||||
class RadiusDecorator(BaseDecorator):
|
||||
@@ -845,6 +903,125 @@ class RadiusDecorator(BaseDecorator):
|
||||
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
|
||||
|
||||
|
||||
class FallDecorator(BaseDecorator):
|
||||
"""Decorating text with arrows
|
||||
- head point with arrow
|
||||
"""
|
||||
|
||||
objecttype = "FALL"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
+ """
|
||||
#define ARROW_ANGLE PI / 12.0
|
||||
#define ARROW_SIZE 16.0
|
||||
"""
|
||||
)
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
in uint type[];
|
||||
|
||||
void main() {
|
||||
vec4 clip2win = matCLIP2WIN();
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
uint t0 = type[0], t1 = type[1];
|
||||
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
|
||||
vec4 edge = p1w - p0w, dir = normalize(edge);
|
||||
vec4 gap1 = vec4(0);
|
||||
|
||||
vec4 p;
|
||||
|
||||
// end edge arrow for last segment
|
||||
if (t1 == 2u) {
|
||||
vec4 head[3];
|
||||
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
|
||||
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
p = p1w - head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
gap1 = dir * viewportDrawingScale * ARROW_SIZE;
|
||||
}
|
||||
|
||||
// stem, adjusted for and arrow
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
p = p1w - gap1;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
"""
|
||||
|
||||
def decorate(self, context, obj):
|
||||
verts, idxs, topo = self.get_path_geom(obj)
|
||||
self.draw_lines(context, obj, verts, idxs, topo)
|
||||
self.draw_labels(context, obj)
|
||||
|
||||
def draw_labels(self, context, obj):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
dir = Vector((1, 0))
|
||||
pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj))
|
||||
|
||||
spline = obj.data.splines[0]
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
|
||||
# generate label text
|
||||
# same function as in svgwriter.py
|
||||
def get_label_text():
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
B, A = [v.co.xyz for v in spline_points[:2]]
|
||||
rise = abs(A.z - B.z)
|
||||
O = A.copy()
|
||||
O.z = B.z
|
||||
run = (B - O).length
|
||||
if run != 0:
|
||||
angle_tg = rise / run
|
||||
angle = round(degrees(atan(angle_tg)))
|
||||
else:
|
||||
angle = 90
|
||||
|
||||
# ues SLOPE_ANGLE as default
|
||||
if element.ObjectType in ("FALL", "SLOPE_ANGLE"):
|
||||
return f"{angle}°"
|
||||
elif element.ObjectType == "SLOPE_FRACTION":
|
||||
if angle == 90:
|
||||
return "-"
|
||||
return f"{self.format_value(context, rise)} / {self.format_value(context, run)}"
|
||||
elif element.ObjectType == "SLOPE_PERCENT":
|
||||
if angle == 90:
|
||||
return "-"
|
||||
return f"{round(angle_tg * 100)} %"
|
||||
|
||||
if spline_points:
|
||||
text = get_label_text()
|
||||
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
|
||||
|
||||
def get_spline_end(self, obj):
|
||||
spline = obj.data.splines[0]
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
if not spline_points:
|
||||
return Vector((0, 0, 0))
|
||||
return obj.matrix_world @ spline_points[0].co
|
||||
|
||||
|
||||
class StairDecorator(BaseDecorator):
|
||||
"""Decorating stairs
|
||||
- head point with arrow
|
||||
@@ -1332,6 +1509,128 @@ class BreakDecorator(BaseDecorator):
|
||||
self.draw_lines(context, obj, verts, idxs)
|
||||
|
||||
|
||||
class BattingDecorator(BaseDecorator):
|
||||
"""Decorator for batting objects
|
||||
|
||||
Uses first two vertices in verts list.
|
||||
"""
|
||||
|
||||
objecttype = "BATTING"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
+ """
|
||||
#define PATTERN_SEGMENT_LENGTH 2
|
||||
"""
|
||||
)
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
uniform float batting_thickness_winspace;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=256) out;
|
||||
|
||||
void place_vert(vec4 ref_point, vec2 win_space_vert) {
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
vec4 p = ref_point + vec4(win_space_vert, 0, 0);
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 clip2win = matCLIP2WIN();
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1), pmw = (p0w + p1w) * .5;
|
||||
vec4 edge = p1w - p0w, dir = normalize(edge);
|
||||
|
||||
vec4 gap = dir * 16.0; // TODO: never used?
|
||||
|
||||
float segment_width = batting_thickness_winspace / 2.5;
|
||||
vec2 batting_dimensions = vec2(segment_width, batting_thickness_winspace);
|
||||
// need to multiply by viewportDrawingScale
|
||||
// so the drawing will stay consistent on zoom in / zoom out
|
||||
// but since we use winspace batting dimensions we skip it
|
||||
mat2 m_edge_space = mat2( dir.xy, dir.yx*vec2(1,-1) );
|
||||
|
||||
// simplied rectangle + cross version to indicate batting
|
||||
place_vert(p0w, m_edge_space * ( vec2(0, 0.5) * batting_dimensions ) );
|
||||
place_vert(p0w, m_edge_space * ( -vec2(0, 0.5) * batting_dimensions ) );
|
||||
place_vert(p1w, m_edge_space * ( -vec2(0, 0.5) * batting_dimensions ) );
|
||||
place_vert(p1w, m_edge_space * ( vec2(0, 0.5) * batting_dimensions ) );
|
||||
place_vert(p0w, m_edge_space * ( vec2(0, 0.5) * batting_dimensions ) );
|
||||
EndPrimitive();
|
||||
place_vert(p0w, m_edge_space * ( -vec2(0, 0.5) * batting_dimensions ) );
|
||||
place_vert(p1w, m_edge_space * ( vec2(0, 0.5) * batting_dimensions ) );
|
||||
EndPrimitive();
|
||||
place_vert(p0w, m_edge_space * ( vec2(0, 0.5) * batting_dimensions ) );
|
||||
place_vert(p1w, m_edge_space * ( -vec2(0, 0.5) * batting_dimensions ) );
|
||||
EndPrimitive();
|
||||
|
||||
|
||||
// TODO: more fancy pattern? possibly use of frag shader?
|
||||
// we're not using complicated patterns because of the
|
||||
// hardware shader limit:
|
||||
// Error: C6033: Hardware limitation reached, can only emit 256 vertices of this size
|
||||
// vec2 pattern_segment_data[PATTERN_SEGMENT_LENGTH];
|
||||
// simplified insulation pattern
|
||||
// pattern_segment_data[0] = vec2(0, 1);
|
||||
// pattern_segment_data[1] = vec2(0.5, 0.8);
|
||||
// pattern_segment_data[2] = vec2(0, 0.2);
|
||||
// pattern_segment_data[3] = vec2(0.5, 0);
|
||||
// pattern_segment_data[4] = vec2(1.0, 0.2);
|
||||
// pattern_segment_data[5] = vec2(0.5, 0.8);
|
||||
// pattern_segment_data[6] = vec2(1.0, 1.0);
|
||||
// zigzag pattern
|
||||
// pattern_segment_data[0] = vec2(0, 1);
|
||||
// pattern_segment_data[1] = vec2(0, 0);
|
||||
// int segs = int( ceil( length(edge) / (batting_dimensions.x * viewportDrawingScale) ) ); // amount of segments
|
||||
// vec4 p;
|
||||
// vec2 p_base, p_cur_ver;
|
||||
// for (int i = 0; i < segs; i++) {
|
||||
// p_base = m_edge_space * (vec2(i, -0.5) * batting_dimensions);
|
||||
// for(int j=0; j<PATTERN_SEGMENT_LENGTH; j++) {
|
||||
// p_cur_ver = p_base + m_edge_space * (pattern_segment_data[j] * batting_dimensions);
|
||||
// place_vert(p0w, p_cur_ver);
|
||||
// }
|
||||
// }
|
||||
// gl_Position = p1;
|
||||
// EmitVertex();
|
||||
// EndPrimitive();
|
||||
}
|
||||
"""
|
||||
|
||||
def decorate(self, context, obj):
|
||||
if obj.data.is_editmode:
|
||||
verts, idxs = self.get_editmesh_geom(obj)
|
||||
else:
|
||||
verts, idxs = self.get_mesh_geom(obj)
|
||||
|
||||
# TODO: find the less ugly way to figure thickness
|
||||
thickness = DecoratorData.get_batting_thickness(obj)
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
original_edge_length = (verts[1] - verts[0]).length
|
||||
clipspace_verts = [region3d.perspective_matrix @ v for v in verts[:2]]
|
||||
winspace_verts = [v * Vector([region.width / 2, region.height / 2, 1]) for v in clipspace_verts]
|
||||
win_space_edge_length = (winspace_verts[1] - winspace_verts[0]).xy.length
|
||||
k = win_space_edge_length / original_edge_length
|
||||
winspace_thickness = k * thickness
|
||||
|
||||
self.draw_lines(
|
||||
context,
|
||||
obj,
|
||||
verts[:2],
|
||||
idxs,
|
||||
extra_float_kwargs={"batting_thickness_winspace": winspace_thickness},
|
||||
is_scale_dependant=False,
|
||||
)
|
||||
|
||||
|
||||
class GridDecorator(BaseDecorator):
|
||||
objecttype = "GRID"
|
||||
|
||||
@@ -1557,6 +1856,11 @@ class SectionDecorator(LevelDecorator):
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
uniform float connect_markers;
|
||||
uniform float display_start_symbol;
|
||||
uniform float display_end_symbol;
|
||||
uniform float display_start_circle;
|
||||
uniform float display_end_circle;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -1580,7 +1884,7 @@ class SectionDecorator(LevelDecorator):
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
|
||||
vec4 edge = p1w - p0w, side = normalize(edge);
|
||||
vec4 gap = side * viewportDrawingScale * CIRCLE_SIZE;
|
||||
vec4 dir = vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
|
||||
vec4 edge_ortho_dir = -vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
|
||||
vec4 p;
|
||||
|
||||
vec4 head[CIRCLE_SEGS];
|
||||
@@ -1589,91 +1893,117 @@ class SectionDecorator(LevelDecorator):
|
||||
vec4 head5[5];
|
||||
|
||||
// start edge circle
|
||||
for(int i=0; i<CIRCLE_SEGS; i++) {
|
||||
p = p0w + head[i];
|
||||
if (display_start_circle > 0) {
|
||||
for(int i=0; i<CIRCLE_SEGS; i++) {
|
||||
p = p0w + head[i];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
}
|
||||
p = p0w + head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
p = p0w + head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// start edge triangle
|
||||
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
|
||||
p = p0w + head5[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head5[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head5[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head5[3];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head5[4];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// start reference divider
|
||||
p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
if (display_start_symbol > 0) {
|
||||
// start edge triangle
|
||||
triangle_head(side, edge_ortho_dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
|
||||
p = p0w + head5[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head5[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head5[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head5[3];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head5[4];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
// end edge circle
|
||||
for(int i=0; i<CIRCLE_SEGS; i++) {
|
||||
p = p1w - head[i];
|
||||
if (display_end_circle > 0) {
|
||||
for(int i=0; i<CIRCLE_SEGS; i++) {
|
||||
p = p1w - head[i];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
}
|
||||
p = p1w - head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
p = p1w - head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// end edge triangle
|
||||
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
|
||||
p = p1w + head5[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w + head5[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w + head5[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w + head5[3];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w + head5[4];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
if (display_end_symbol > 0) {
|
||||
triangle_head(side, edge_ortho_dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
|
||||
p = p1w + head5[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w + head5[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w + head5[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w + head5[3];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w + head5[4];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
float divider_line_size;
|
||||
if (connect_markers > 0) {
|
||||
divider_line_size = CIRCLE_SIZE;
|
||||
} else {
|
||||
divider_line_size = CIRCLE_SIZE * 3;
|
||||
}
|
||||
|
||||
// NOTE: basically we consider first vertex to be
|
||||
// start and the second to be the end marker
|
||||
// which is a bit inconsistent with svg
|
||||
// but not that anyone will use multiple edge section annotation
|
||||
|
||||
// start reference divider
|
||||
if (display_start_symbol > 0) {
|
||||
p = p0w + side * viewportDrawingScale * divider_line_size;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w - side * viewportDrawingScale * CIRCLE_SIZE;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
// end reference divider
|
||||
p = p1w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
if (display_end_symbol > 0) {
|
||||
p = p1w + side * viewportDrawingScale * CIRCLE_SIZE;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - side * viewportDrawingScale * divider_line_size;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
// stem
|
||||
p = p0w + gap;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - gap;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
if (connect_markers > 0) {
|
||||
p = p0w + (display_start_symbol > 0 ? gap : vec4(0));
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - (display_end_symbol > 0 ? gap : vec4(0));
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -1682,9 +2012,23 @@ class SectionDecorator(LevelDecorator):
|
||||
verts, idxs = self.get_editmesh_geom(obj)
|
||||
else:
|
||||
verts, idxs = self.get_mesh_geom(obj)
|
||||
vert_map = {v.freeze(): (obj.matrix_world.inverted() @ v).x for v in verts}
|
||||
verts = sorted(verts, key=lambda v: vert_map[v], reverse=True)
|
||||
self.draw_lines(context, obj, verts, idxs)
|
||||
|
||||
# TODO: add dashed line to shader
|
||||
# mind the vertices limit
|
||||
display_data = DecoratorData.get_section_markers_display_data(obj)
|
||||
self.draw_lines(
|
||||
context,
|
||||
obj,
|
||||
verts,
|
||||
idxs,
|
||||
extra_float_kwargs={
|
||||
"connect_markers": float(display_data["connect_markers"]),
|
||||
"display_start_symbol": float(display_data["start"]["add_symbol"]),
|
||||
"display_end_symbol": float(display_data["end"]["add_symbol"]),
|
||||
"display_start_circle": float(display_data["start"]["add_circle"]),
|
||||
"display_end_circle": float(display_data["end"]["add_circle"]),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TextDecorator(BaseDecorator):
|
||||
@@ -1697,8 +2041,8 @@ class TextDecorator(BaseDecorator):
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
+ """
|
||||
#define ARROW_ANGLE PI / 12.0
|
||||
#define ARROW_SIZE 16.0
|
||||
#define CIRCLE_SIZE 4.0
|
||||
#define CIRCLE_SEGS_ASTERISK 6
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -1709,54 +2053,34 @@ class TextDecorator(BaseDecorator):
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
|
||||
void circle_head_asterisk(in float size, out vec4 head[CIRCLE_SEGS_ASTERISK]) {
|
||||
float angle_d = PI * 2 / CIRCLE_SEGS_ASTERISK;
|
||||
for(int i = 0; i<CIRCLE_SEGS_ASTERISK; i++) {
|
||||
float angle = angle_d * i;
|
||||
head[i] = vec4(cos(angle), sin(angle), 0, 0) * size;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 clip2win = matCLIP2WIN();
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
|
||||
vec4 edge = p1w - p0w, dir = normalize(edge);
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position;
|
||||
vec4 p0w = CLIP2WIN(p0);
|
||||
vec4 p;
|
||||
|
||||
vec4 head[CIRCLE_SEGS_ASTERISK];
|
||||
circle_head_asterisk(viewportDrawingScale * CIRCLE_SIZE, head);
|
||||
|
||||
vec4 head[3];
|
||||
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
|
||||
|
||||
// start edge arrow
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
p = p0w + head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// end edge arrow
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
p = p1w - head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// stem, with gaps for arrows
|
||||
p = p0w + head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
for (int i=0; i < CIRCLE_SEGS_ASTERISK/2; i++) {
|
||||
p = p0w + head[i];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head[i+CIRCLE_SEGS_ASTERISK/2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -1766,9 +2090,48 @@ class TextDecorator(BaseDecorator):
|
||||
def draw_labels(self, context, obj):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
dir = Vector((1, 0))
|
||||
|
||||
def matrix_only_rotation(m):
|
||||
return m.to_3x3().normalized().to_4x4()
|
||||
|
||||
text_dir = Vector((1,0))
|
||||
text_dir_world = matrix_only_rotation(region3d.perspective_matrix.inverted()) @ text_dir.to_3d()
|
||||
text_dir_world_rotated = matrix_only_rotation(obj.matrix_world) @ text_dir_world
|
||||
text_dir = (matrix_only_rotation(region3d.perspective_matrix) @ text_dir_world_rotated).to_2d().normalized()
|
||||
|
||||
pos = location_3d_to_region_2d(region, region3d, obj.matrix_world.translation)
|
||||
self.draw_label(context, obj.BIMTextProperties.value, pos, dir, gap=0, center=False, vcenter=False)
|
||||
props = obj.BIMTextProperties
|
||||
text_data = props.get_text_edited_data() if props.is_editing else DecoratorData.get_ifc_text_data(obj)
|
||||
literals_data = text_data["Literals"]
|
||||
|
||||
# draw asterisk symbol to visually indicate that there are multiple literals
|
||||
if len(literals_data) > 1:
|
||||
verts = [obj.location]
|
||||
idxs = [(0, 0)]
|
||||
self.draw_lines(context, obj, verts, idxs)
|
||||
|
||||
box_alignment_used = []
|
||||
for literal_data in literals_data:
|
||||
box_alignment = literal_data["BoxAlignment"]
|
||||
# Skip literals with the same box alignment to prevent visual clutter in viewport
|
||||
# User is still indicated by the asterisk symbol
|
||||
if box_alignment in box_alignment_used:
|
||||
continue
|
||||
box_alignment_used.append(box_alignment)
|
||||
|
||||
for line_i, line in enumerate(literal_data["CurrentValue"].split("\\n")):
|
||||
self.draw_label(
|
||||
context,
|
||||
line,
|
||||
pos,
|
||||
text_dir,
|
||||
gap=0,
|
||||
center=False,
|
||||
vcenter=False,
|
||||
font_size_mm=text_data["FontSize"],
|
||||
line_no=line_i,
|
||||
box_alignment=box_alignment,
|
||||
)
|
||||
|
||||
|
||||
class DecorationsHandler:
|
||||
@@ -1788,6 +2151,8 @@ class DecorationsHandler:
|
||||
SectionDecorator,
|
||||
ElevationDecorator,
|
||||
TextDecorator,
|
||||
BattingDecorator,
|
||||
FallDecorator,
|
||||
]
|
||||
|
||||
installed = None
|
||||
|
||||
@@ -23,6 +23,7 @@ import json
|
||||
import time
|
||||
import bmesh
|
||||
import shutil
|
||||
import shapely
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import multiprocessing
|
||||
@@ -38,11 +39,12 @@ import blenderbim.bim.module.drawing.annotation as annotation
|
||||
import blenderbim.bim.module.drawing.sheeter as sheeter
|
||||
import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
import blenderbim.bim.export_ifc
|
||||
from lxml import etree
|
||||
from mathutils import Vector
|
||||
from timeit import default_timer as timer
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
@@ -137,53 +139,89 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
Only available if :
|
||||
- IFC file is created
|
||||
- Camera is in Orthographic mode
|
||||
"""
|
||||
- Camera is in Orthographic mode"""
|
||||
|
||||
bl_idname = "bim.create_drawing"
|
||||
bl_label = "Create Drawing"
|
||||
bl_description = (
|
||||
"Creates/refreshes a .svg drawing based on currently active camera.\n\n"
|
||||
+ "SHIFT+CLICK to print all annotations"
|
||||
)
|
||||
print_all: bpy.props.BoolProperty(name="Print all", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(tool.Ifc.get() and tool.Drawing.is_camera_orthographic() and tool.Drawing.is_drawing_active())
|
||||
|
||||
def invoke(self, context, event):
|
||||
# printing all annotations on shift+click
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.print_all = True
|
||||
else:
|
||||
# can't rely on default value since `self.print_all = True`
|
||||
# will set the value to `True` for all future calls
|
||||
self.print_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
self.camera = context.scene.camera
|
||||
self.camera_element = tool.Ifc.get_entity(self.camera)
|
||||
self.file = IfcStore.get_file()
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
with profile("Drawing generation process"):
|
||||
if self.print_all:
|
||||
original_drawing_id = self.props.active_drawing_id
|
||||
drawings_to_print = [drawing.ifc_definition_id for drawing in self.props.drawings]
|
||||
else:
|
||||
drawings_to_print = [self.props.active_drawing_id]
|
||||
|
||||
with profile("Initialize drawing generation process"):
|
||||
self.props = context.scene.DocProperties
|
||||
self.cprops = self.camera.data.BIMCameraProperties
|
||||
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
|
||||
self.get_scale()
|
||||
if self.cprops.update_representation(self.camera):
|
||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||
for drawing_id in drawings_to_print:
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_view(drawing=drawing_id)
|
||||
|
||||
self.svg_writer = svgwriter.SvgWriter()
|
||||
self.svg_writer.human_scale = self.human_scale
|
||||
self.svg_writer.scale = self.scale
|
||||
self.svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
||||
self.svg_writer.camera = self.camera
|
||||
self.svg_writer.camera_width, self.svg_writer.camera_height = self.get_camera_dimensions()
|
||||
self.svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
||||
self.camera = context.scene.camera
|
||||
self.camera_element = tool.Ifc.get_entity(self.camera)
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
with profile("Generate underlay"):
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
with profile("Drawing generation process"):
|
||||
with profile("Initialize drawing generation process"):
|
||||
self.cprops = self.camera.data.BIMCameraProperties
|
||||
self.drawing_name = self.file.by_id(drawing_id).Name
|
||||
self.get_scale()
|
||||
if self.cprops.update_representation(self.camera):
|
||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||
|
||||
with profile("Generate linework"):
|
||||
linework_svg = self.generate_linework(context)
|
||||
self.svg_writer = svgwriter.SvgWriter()
|
||||
self.svg_writer.human_scale = self.human_scale
|
||||
self.svg_writer.scale = self.scale
|
||||
self.svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
||||
self.svg_writer.camera = self.camera
|
||||
self.svg_writer.camera_width, self.svg_writer.camera_height = self.get_camera_dimensions()
|
||||
self.svg_writer.camera_projection = tuple(
|
||||
self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
)
|
||||
pset = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]
|
||||
related_paths = {
|
||||
"Stylesheet": pset.get("Stylesheet"),
|
||||
"Markers": pset.get("Markers"),
|
||||
"Symbols": pset.get("Symbols"),
|
||||
"Patterns": pset.get("Patterns"),
|
||||
}
|
||||
self.svg_writer.define_related_paths(**related_paths)
|
||||
|
||||
with profile("Generate annotation"):
|
||||
annotation_svg = self.generate_annotation(context)
|
||||
with profile("Generate underlay"):
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
|
||||
with profile("Combine SVG layers"):
|
||||
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
||||
with profile("Generate linework"):
|
||||
linework_svg = self.generate_linework(context)
|
||||
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
with profile("Generate annotation"):
|
||||
annotation_svg = self.generate_annotation(context)
|
||||
|
||||
with profile("Combine SVG layers"):
|
||||
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
||||
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_view(drawing=original_drawing_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_camera_dimensions(self):
|
||||
@@ -200,10 +238,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Hacky :)
|
||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg")
|
||||
with open(svg_path, "w") as outfile:
|
||||
pset = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]
|
||||
self.svg_writer.create_blank_svg(svg_path).define_boilerplate(
|
||||
pset.get("Stylesheet"), pset.get("Markers"), pset.get("Symbols"), pset.get("Patterns")
|
||||
)
|
||||
self.svg_writer.create_blank_svg(svg_path).define_boilerplate()
|
||||
boilerplate = self.svg_writer.svg.tostring()
|
||||
outfile.write(boilerplate.replace("</svg>", ""))
|
||||
if underlay:
|
||||
@@ -456,8 +491,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
with profile("Post processing"):
|
||||
root = etree.fromstring(results)
|
||||
self.enrich_linework_with_metadata(root)
|
||||
self.move_projection_to_bottom(root)
|
||||
self.merge_linework_and_add_metadata(root)
|
||||
with open(svg_path, "wb") as svg:
|
||||
svg.write(etree.tostring(root))
|
||||
|
||||
@@ -498,25 +533,64 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if not it.next():
|
||||
break
|
||||
|
||||
def enrich_linework_with_metadata(self, root):
|
||||
def merge_linework_and_add_metadata(self, root):
|
||||
joined_paths = {}
|
||||
|
||||
ifc = tool.Ifc.get()
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
classes = ["cut"]
|
||||
try:
|
||||
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
except:
|
||||
# See #1861 - freshly created guids cannot be extracted unfortunately
|
||||
continue
|
||||
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
material_name = ""
|
||||
if material:
|
||||
if material.is_a("IfcMaterialLayerSet"):
|
||||
name = material.LayerSetName or "null"
|
||||
material_name = material.LayerSetName or "null"
|
||||
else:
|
||||
name = getattr(material, "Name", "null") or "null"
|
||||
name = self.canonicalise_class_name(name)
|
||||
classes.append(f"material-{name}")
|
||||
material_name = getattr(material, "Name", "null") or "null"
|
||||
material_name = self.canonicalise_class_name(material_name)
|
||||
classes.append(f"material-{material_name}")
|
||||
el.set("class", (el.get("class", "") + " " + " ".join(classes)).strip())
|
||||
|
||||
# Drawing convention states that objects with the same material are merged when cut.
|
||||
if material_name != "null" and el.findall("{http://www.w3.org/2000/svg}path"):
|
||||
joined_paths.setdefault(material_name, []).append(el)
|
||||
|
||||
for key, els in joined_paths.items():
|
||||
polygons = []
|
||||
classes = set()
|
||||
|
||||
for el in els:
|
||||
classes.update(el.attrib["class"].split())
|
||||
classes.add(el.attrib["{http://www.ifcopenshell.org/ns}guid"])
|
||||
is_closed_polygon = False
|
||||
for path in el.findall("{http://www.w3.org/2000/svg}path"):
|
||||
for subpath in path.attrib["d"].split("M")[1:]:
|
||||
subpath = "M" + subpath.strip()
|
||||
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
|
||||
if len(coords) > 2 and coords[0] == coords[-1]:
|
||||
is_closed_polygon = True
|
||||
polygons.append(shapely.Polygon(coords))
|
||||
if is_closed_polygon:
|
||||
el.getparent().remove(el)
|
||||
|
||||
merged_polygons = shapely.ops.unary_union(polygons)
|
||||
|
||||
if type(merged_polygons) == shapely.MultiPolygon:
|
||||
merged_polygons = merged_polygons.geoms
|
||||
elif type(merged_polygons) == shapely.Polygon:
|
||||
merged_polygons = [merged_polygons]
|
||||
|
||||
root_g = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
||||
for polygon in merged_polygons:
|
||||
g = etree.SubElement(root, "g")
|
||||
path = etree.SubElement(g, "path")
|
||||
d = "M" + " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]) + " Z"
|
||||
for interior in polygon.interiors:
|
||||
d += " M" + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z"
|
||||
path.attrib["d"] = d
|
||||
g.set("class", " ".join(list(classes)))
|
||||
root_g.append(g)
|
||||
|
||||
def move_projection_to_bottom(self, root):
|
||||
# https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
@@ -573,7 +647,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", self.get_material_name(material))))
|
||||
classes.append("globalid-{}".format(element.GlobalId))
|
||||
for attribute in svg_writer.annotations["attributes"]:
|
||||
result = self.selector.get_element_value(element, attribute)
|
||||
result = ifcopenshell.util.selector.get_element_value(element, attribute)
|
||||
if result:
|
||||
classes.append(
|
||||
"{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result))
|
||||
@@ -921,7 +995,6 @@ class ActivateDrawingStyle(bpy.types.Operator):
|
||||
exec(f"{path} = {value}")
|
||||
|
||||
def set_query(self, context):
|
||||
self.selector = ifcopenshell.util.selector.Selector()
|
||||
self.include_global_ids = []
|
||||
self.exclude_global_ids = []
|
||||
for ifc_file in context.scene.DocProperties.ifc_files:
|
||||
@@ -930,10 +1003,10 @@ class ActivateDrawingStyle(bpy.types.Operator):
|
||||
except:
|
||||
continue
|
||||
if self.drawing_style.include_query:
|
||||
results = self.selector.parse(ifc, self.drawing_style.include_query)
|
||||
results = ifcopenshell.util.selector.Selector.parse(ifc, self.drawing_style.include_query)
|
||||
self.include_global_ids.extend([e.GlobalId for e in results])
|
||||
if self.drawing_style.exclude_query:
|
||||
results = self.selector.parse(ifc, self.drawing_style.exclude_query)
|
||||
results = ifcopenshell.util.selector.Selector.parse(ifc, self.drawing_style.exclude_query)
|
||||
self.exclude_global_ids.extend([e.GlobalId for e in results])
|
||||
if self.drawing_style.include_query:
|
||||
self.parse_filter_query("INCLUDE", context)
|
||||
@@ -1134,169 +1207,65 @@ class CleanWireframes(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CopyGrid(bpy.types.Operator):
|
||||
bl_idname = "bim.add_grid"
|
||||
bl_label = "Add Grid"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
class EditTextPopup(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_text_popup"
|
||||
bl_label = "Edit Text"
|
||||
first_run: bpy.props.BoolProperty(default=True)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return helper.get_active_drawing(context.scene)[0] is not None
|
||||
def draw(self, context):
|
||||
# shares most of the code with BIM_PT_text.draw()
|
||||
# need to keep them in sync or move to some common function
|
||||
|
||||
props = context.active_object.BIMTextProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.add_text_literal", icon="ADD", text="Add literal")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "font_size")
|
||||
|
||||
for i, literal_props in enumerate(props.literals):
|
||||
box = self.layout.box()
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"Literal[{i}]:")
|
||||
row.operator("bim.remove_text_literal", icon="X", text="").literal_prop_id = i
|
||||
|
||||
# skip BoxAlignment since we're going to format it ourselves
|
||||
attributes = [a for a in literal_props.attributes if a.name != "BoxAlignment"]
|
||||
blenderbim.bim.helper.draw_attributes(attributes, box)
|
||||
|
||||
# a bit hacky way to align box alignment widget
|
||||
rows = [box.row(align=True) for i in range(3)]
|
||||
for i in range(9):
|
||||
if i % 3 == 0:
|
||||
split = rows[i // 3].split(factor=0.1, align=True)
|
||||
split.column()
|
||||
split.prop(literal_props, "box_alignment", text="", index=i)
|
||||
|
||||
text_lines = ["Text box alignment:", literal_props.attributes["BoxAlignment"].string_value, ""]
|
||||
for i in range(3):
|
||||
split = rows[i].split(factor=0.1, align=False)
|
||||
split.column()
|
||||
split.label(text=text_lines[i])
|
||||
|
||||
def cancel(self, context):
|
||||
# disable editing when dialog is closed
|
||||
bpy.ops.bim.disable_editing_text()
|
||||
|
||||
def execute(self, context):
|
||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||
target_view = tool.Drawing.get_drawing_target_view(drawing)
|
||||
subcontext = ifcopenshell.util.representation.get_context(
|
||||
IfcStore.get_file(), "Plan", "Annotation", target_view
|
||||
)
|
||||
if not subcontext:
|
||||
# can't use invoke() because this operator
|
||||
# will be run indirectly by hotkey
|
||||
# so we use execute() and track whether it's the first run of the operator
|
||||
if self.first_run:
|
||||
bpy.ops.bim.enable_editing_text()
|
||||
self.first_run = False
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
else:
|
||||
bpy.ops.bim.edit_text()
|
||||
return {"FINISHED"}
|
||||
|
||||
proj_coll = helper.get_project_collection(context.scene)
|
||||
view_coll, camera = helper.get_active_drawing(context.scene)
|
||||
is_ortho = camera.data.type == "ORTHO"
|
||||
bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
|
||||
clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
|
||||
elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
|
||||
|
||||
def grep(coll):
|
||||
results = []
|
||||
for obj in coll.all_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.is_a("IfcAnnotation") and element.ObjectType == "GRID":
|
||||
results.append(obj)
|
||||
return results
|
||||
|
||||
def clone(src):
|
||||
dst = src.copy()
|
||||
dst.data = dst.data.copy()
|
||||
dst.name = dst.name.replace("IfcGridAxis/", "")
|
||||
dst.BIMObjectProperties.ifc_definition_id = 0
|
||||
dst.data.BIMMeshProperties.ifc_definition_id = 0
|
||||
return dst
|
||||
|
||||
def disassemble(obj):
|
||||
mesh = bmesh.new()
|
||||
mesh.verts.ensure_lookup_table()
|
||||
mesh.from_mesh(obj.data)
|
||||
return obj, mesh
|
||||
|
||||
def assemble(obj, mesh):
|
||||
mesh.to_mesh(obj.data)
|
||||
return obj
|
||||
|
||||
def localize(obj, mesh):
|
||||
# convert to camera coords
|
||||
mesh.transform(camera.matrix_world.inverted() @ obj.matrix_world)
|
||||
obj.matrix_world = camera.matrix_world
|
||||
return obj, mesh
|
||||
|
||||
def clip(mesh):
|
||||
# clip segment against camera view bounds
|
||||
mesh.verts.ensure_lookup_table()
|
||||
points = [v.co for v in mesh.verts[0:2]]
|
||||
points = helper.clip_segment(bounds, points)
|
||||
if points is None:
|
||||
return None
|
||||
mesh.verts[0].co = points[0]
|
||||
mesh.verts[1].co = points[1]
|
||||
return mesh
|
||||
|
||||
def elev(mesh):
|
||||
# put camera-perpendicular segments vertically
|
||||
mesh.verts.ensure_lookup_table()
|
||||
points = [v.co for v in mesh.verts[0:2]]
|
||||
points = helper.elevate_segment(bounds, points)
|
||||
if points is None:
|
||||
return None
|
||||
points = helper.clip_segment(bounds, points)
|
||||
if points is None:
|
||||
return None
|
||||
mesh.verts[0].co = points[0]
|
||||
mesh.verts[1].co = points[1]
|
||||
return mesh
|
||||
|
||||
for obj in grep(view_coll):
|
||||
view_coll.objects.unlink(obj)
|
||||
|
||||
grids = []
|
||||
for element in tool.Ifc.get().by_type("IfcGridAxis"):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
continue
|
||||
grids.append((*localize(*disassemble(clone(obj))), element))
|
||||
|
||||
if clipping:
|
||||
grids = [(obj, clip(mesh), element) for obj, mesh, element in grids]
|
||||
elif elevating:
|
||||
grids = [(obj, elev(mesh), element) for obj, mesh, element in grids]
|
||||
|
||||
for obj, mesh, element in grids:
|
||||
if mesh is not None:
|
||||
view_coll.objects.link(assemble(obj, mesh))
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id())
|
||||
annotation = tool.Ifc.get_entity(obj)
|
||||
annotation.Name = element.AxisTag
|
||||
annotation.ObjectType = "GRID"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddSectionsAnnotations(bpy.types.Operator):
|
||||
bl_idname = "bim.add_sections_annotations"
|
||||
bl_label = "Add Sections"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
camera = helper.get_active_drawing(context.scene)[1]
|
||||
return camera and camera.data.type == "ORTHO"
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
view_coll, camera = helper.get_active_drawing(scene)
|
||||
bounds = helper.ortho_view_frame(camera.data)
|
||||
|
||||
drawings = [
|
||||
d
|
||||
for d in scene.DocProperties.drawings
|
||||
if (
|
||||
d.camera is not camera
|
||||
and d.camera.data.type == "ORTHO"
|
||||
and d.camera.data.BIMCameraProperties.target_view == "SECTION_VIEW"
|
||||
)
|
||||
]
|
||||
|
||||
def sideview(cam):
|
||||
# leftmost and rightmost points of camera view area, local coords
|
||||
xmin, xmax, _, _, _, _ = helper.ortho_view_frame(cam.data, margin=0)
|
||||
proj = camera.matrix_world.inverted() @ cam.matrix_world
|
||||
p_l = proj @ Vector((xmin, 0, 0))
|
||||
p_r = proj @ Vector((xmax, 0, 0))
|
||||
return helper.clip_segment(bounds, (p_l, p_r))
|
||||
|
||||
def annotation(drawing, points):
|
||||
# object with path geometry
|
||||
name = drawing.name
|
||||
curve = bpy.data.curves.new(f"Section/{name}", "CURVE")
|
||||
spline = curve.splines.new("POLY") # has 1 initial point
|
||||
spline.points.add(1)
|
||||
p1, p2 = points
|
||||
z = bounds[4] - 1e-5 # zmin
|
||||
spline.points[0].co = (p1.x, p1.y, z, 1)
|
||||
spline.points[1].co = (p2.x, p2.y, z, 1)
|
||||
obj = bpy.data.objects.new(f"IfcAnnotation/Section/{name}", curve)
|
||||
obj.matrix_world = camera.matrix_world
|
||||
return obj
|
||||
|
||||
for d in drawings:
|
||||
points = sideview(d.camera)
|
||||
if points is None:
|
||||
continue
|
||||
obj = annotation(d, points)
|
||||
view_coll.objects.link(obj)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditText(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_text"
|
||||
@@ -1304,7 +1273,8 @@ class EditText(bpy.types.Operator, Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_text(tool.Ifc, tool.Drawing, obj=context.active_object)
|
||||
core.edit_text(tool.Drawing, obj=context.active_object)
|
||||
tool.Blender.update_viewport()
|
||||
|
||||
|
||||
class EnableEditingText(bpy.types.Operator, Operator):
|
||||
@@ -1324,6 +1294,58 @@ class DisableEditingText(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
core.disable_editing_text(tool.Drawing, obj=context.active_object)
|
||||
|
||||
# force update this object's font size for viewport display
|
||||
DecoratorData.data.pop(context.object.name, None)
|
||||
tool.Blender.update_viewport()
|
||||
|
||||
|
||||
class AddTextLiteral(bpy.types.Operator):
|
||||
bl_idname = "bim.add_text_literal"
|
||||
bl_label = "Add text literal"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
|
||||
# similar to `tool.Drawing.import_text_attributes`
|
||||
literal_props = obj.BIMTextProperties.literals.add()
|
||||
literal_attributes = literal_props.attributes
|
||||
literal_attr_values = {
|
||||
"Literal": "Literal",
|
||||
"Path": "RIGHT",
|
||||
"BoxAlignment": "bottom_left",
|
||||
}
|
||||
# emulates `blenderbim.bim.helper.import_attributes2(ifc_literal, literal_props.attributes)`
|
||||
for attr_name in literal_attr_values:
|
||||
attr = literal_attributes.add()
|
||||
attr.name = attr_name
|
||||
if attr_name == "Path":
|
||||
attr.data_type = "enum"
|
||||
attr.enum_items = '["DOWN", "LEFT", "RIGHT", "UP"]'
|
||||
attr.enum_value = literal_attr_values[attr_name]
|
||||
|
||||
else:
|
||||
attr.data_type = "string"
|
||||
attr.string_value = literal_attr_values[attr_name]
|
||||
|
||||
box_alignment_mask = [False] * 9
|
||||
box_alignment_mask[6] = True # bottom_left box_alignment
|
||||
literal_props.box_alignment = box_alignment_mask
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveTextLiteral(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_text_literal"
|
||||
bl_label = "Remove text literal"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
literal_prop_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
obj.BIMTextProperties.literals.remove(self.literal_prop_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditAssignedProduct(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_assigned_product"
|
||||
@@ -1335,6 +1357,7 @@ class EditAssignedProduct(bpy.types.Operator, Operator):
|
||||
if context.active_object.BIMAssignedProductProperties.relating_product:
|
||||
product = tool.Ifc.get_entity(context.active_object.BIMAssignedProductProperties.relating_product)
|
||||
core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=context.active_object, product=product)
|
||||
tool.Blender.update_viewport()
|
||||
|
||||
|
||||
class EnableEditingAssignedProduct(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -25,7 +25,8 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.drawing as core
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
import blenderbim.bim.module.drawing.decoration as decoration
|
||||
from blenderbim.bim.module.drawing.data import DrawingsData
|
||||
from blenderbim.bim.module.drawing.data import DrawingsData, DecoratorData
|
||||
from blenderbim.bim.module.drawing.data import refresh as refresh_drawing_data
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.prop import Attribute, StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -38,6 +39,7 @@ from bpy.props import (
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
BoolVectorProperty,
|
||||
)
|
||||
|
||||
|
||||
@@ -212,6 +214,7 @@ def toggleDecorations(self, context):
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
for obj in collection.objects:
|
||||
tool.Drawing.update_text_value(obj)
|
||||
refresh_drawing_data()
|
||||
decoration.DecorationsHandler.install(context)
|
||||
else:
|
||||
decoration.DecorationsHandler.uninstall()
|
||||
@@ -227,10 +230,6 @@ def getVectorStyles(self, context):
|
||||
return vector_styles_enum
|
||||
|
||||
|
||||
def refreshFontSize(self, context):
|
||||
annotation.Annotator.resize_text(context.active_object)
|
||||
|
||||
|
||||
class Variable(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
prop_key: StringProperty(name="Property Key")
|
||||
@@ -381,10 +380,66 @@ class BIMCameraProperties(PropertyGroup):
|
||||
return False
|
||||
|
||||
|
||||
DEFAULT_BOX_ALIGNMENT = [False] * 6 + [True] + [False] * 2
|
||||
BOX_ALIGNMENT_POSITIONS = [
|
||||
"top-left",
|
||||
"top-middle",
|
||||
"top-right",
|
||||
"middle-left",
|
||||
"center",
|
||||
"middle-right",
|
||||
"bottom-left",
|
||||
"bottom-middle",
|
||||
"bottom-right",
|
||||
]
|
||||
|
||||
|
||||
class Literal(PropertyGroup):
|
||||
def set_box_alignment(self, new_value):
|
||||
markers = new_value.count(True)
|
||||
if not markers:
|
||||
return
|
||||
|
||||
if markers > 1:
|
||||
prev_value = self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
|
||||
# looking for the first value changed to positive
|
||||
first_changed_value = next((i for i in range(9) if new_value[i] and new_value[i] != prev_value[i]), None)
|
||||
|
||||
# if nothing have changed we just keep the previous value
|
||||
if first_changed_value is None:
|
||||
return
|
||||
new_value = [False] * 9
|
||||
new_value[first_changed_value] = True
|
||||
|
||||
self["box_alignment"] = new_value
|
||||
position_string = BOX_ALIGNMENT_POSITIONS[next(i for i in range(9) if new_value[i])]
|
||||
self.attributes["BoxAlignment"].set_value(position_string)
|
||||
|
||||
def get_box_alignment(self):
|
||||
return self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
|
||||
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
# Current text value with evaluated experessions stored in `value`.
|
||||
# The original (Literal) value stored in `attributes['Literal']`
|
||||
# and can be accessed with `get_text()`
|
||||
value: StringProperty(name="Value", default="TEXT")
|
||||
box_alignment: BoolVectorProperty(
|
||||
name="Box alignment", size=9, set=set_box_alignment, get=get_box_alignment, default=DEFAULT_BOX_ALIGNMENT
|
||||
)
|
||||
ifc_definition_id: IntProperty(name="IFC definition ID", default=0)
|
||||
|
||||
def get_literal_edited_data(self):
|
||||
text_data = {
|
||||
"CurrentValue": self.attributes["Literal"].string_value,
|
||||
"Literal": self.attributes["Literal"].string_value,
|
||||
"BoxAlignment": self.attributes["BoxAlignment"].string_value,
|
||||
}
|
||||
return text_data
|
||||
|
||||
|
||||
class BIMTextProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
value: StringProperty(name="Value", default="TEXT")
|
||||
literals: CollectionProperty(name="Literals", type=Literal)
|
||||
font_size: EnumProperty(
|
||||
items=[
|
||||
("1.8", "1.8 - Small", ""),
|
||||
@@ -394,10 +449,24 @@ class BIMTextProperties(PropertyGroup):
|
||||
("7.0", "7.0 - Title", ""),
|
||||
],
|
||||
default="2.5",
|
||||
update=refreshFontSize,
|
||||
name="Font Size",
|
||||
)
|
||||
|
||||
def get_text_edited_data(self):
|
||||
"""should be called only if `is_editing`
|
||||
otherwise should use `DecoratorData.get_ifc_text_data(obj)` instead
|
||||
because this data could be out of date
|
||||
"""
|
||||
literals_data = []
|
||||
for literal in self.literals:
|
||||
literal_data = literal.get_literal_edited_data()
|
||||
literals_data.append(literal_data)
|
||||
text_data = {
|
||||
"Literals": literals_data,
|
||||
"FontSize": float(self.font_size),
|
||||
}
|
||||
return text_data
|
||||
|
||||
|
||||
class BIMAssignedProductProperties(PropertyGroup):
|
||||
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import ntpath
|
||||
import pystache
|
||||
import urllib.parse
|
||||
import xml.etree.ElementTree as ET
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.geolocation
|
||||
from shutil import copy
|
||||
from xml.dom import minidom
|
||||
|
||||
@@ -182,6 +184,9 @@ class SheetBuilder:
|
||||
tree = ET.parse(sheet_path)
|
||||
root = tree.getroot()
|
||||
|
||||
self.defs = ET.Element("defs")
|
||||
root.append(self.defs)
|
||||
|
||||
self.build_titleblock(root, sheet)
|
||||
self.build_drawings(root, sheet)
|
||||
self.build_schedules(root)
|
||||
@@ -192,7 +197,14 @@ class SheetBuilder:
|
||||
def build_titleblock(self, root, sheet):
|
||||
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
|
||||
titleblock.append(self.parse_embedded_svg(image, sheet.get_info()))
|
||||
g = self.parse_embedded_svg(image, sheet.get_info())
|
||||
grid_north = ifcopenshell.util.geolocation.get_grid_north(tool.Ifc.get()) * -1
|
||||
true_north = ifcopenshell.util.geolocation.get_true_north(tool.Ifc.get()) * -1
|
||||
for north in g.iterfind('.//{http://www.w3.org/2000/svg}g[@data-type="grid-north"]'):
|
||||
north.attrib["transform"] = f"rotate({grid_north})"
|
||||
for north in g.iterfind('.//{http://www.w3.org/2000/svg}g[@data-type="true-north"]'):
|
||||
north.attrib["transform"] = f"rotate({true_north})"
|
||||
titleblock.append(g)
|
||||
titleblock.remove(image)
|
||||
|
||||
def build_drawings(self, root, sheet):
|
||||
@@ -271,6 +283,20 @@ class SheetBuilder:
|
||||
group.attrib["transform"] = "translate({},{})".format(
|
||||
self.convert_to_mm(image.attrib.get("x")), self.convert_to_mm(image.attrib.get("y"))
|
||||
)
|
||||
|
||||
# Convert viewBox into a clip path
|
||||
clip_id = str(uuid.uuid4())
|
||||
group.attrib["clip-path"] = f"url(#{clip_id})"
|
||||
clip_path = ET.Element("clipPath")
|
||||
clip_path.attrib["id"] = clip_id
|
||||
rect = ET.Element("rect")
|
||||
rect.attrib["x"] = "0"
|
||||
rect.attrib["y"] = "0"
|
||||
rect.attrib["width"] = str(self.convert_to_mm(image.attrib.get("width")))
|
||||
rect.attrib["height"] = str(self.convert_to_mm(image.attrib.get("height")))
|
||||
clip_path.append(rect)
|
||||
self.defs.append(clip_path)
|
||||
|
||||
svg_path = self.get_href(image)
|
||||
with open(os.path.join(self.data_dir, "sheets", svg_path), "r") as template:
|
||||
embedded = ET.fromstring(pystache.render(template.read(), data))
|
||||
@@ -282,7 +308,10 @@ class SheetBuilder:
|
||||
for image in images:
|
||||
new_href = ntpath.basename(image.attrib.get("{http://www.w3.org/1999/xlink}href"))
|
||||
image.attrib["{http://www.w3.org/1999/xlink}href"] = new_href
|
||||
group.append(embedded)
|
||||
for child in embedded:
|
||||
if "namedview" in child.tag:
|
||||
continue
|
||||
group.append(child)
|
||||
return group
|
||||
|
||||
def convert_to_mm(self, value):
|
||||
|
||||
@@ -31,10 +31,12 @@ import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
from math import pi
|
||||
from mathutils import Vector
|
||||
from mathutils import geometry
|
||||
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
from math import pi, ceil, atan, degrees
|
||||
from mathutils import geometry, Vector
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
|
||||
@@ -63,8 +65,10 @@ class SvgWriter:
|
||||
self.scale = 1 / 100 # 1:100
|
||||
self.camera_width = None
|
||||
self.camera_height = None
|
||||
self.related_paths = None
|
||||
|
||||
def create_blank_svg(self, output_path):
|
||||
self.define_related_paths() # making sure all paths are defined
|
||||
self.calculate_scale()
|
||||
self.svg = svgwrite.Drawing(
|
||||
output_path,
|
||||
@@ -90,39 +94,66 @@ class SvgWriter:
|
||||
)
|
||||
return self
|
||||
|
||||
def define_boilerplate(self, stylesheet=None, markers=None, symbols=None, patterns=None):
|
||||
self.add_stylesheet(stylesheet)
|
||||
self.add_markers(markers)
|
||||
self.add_symbols(symbols)
|
||||
self.add_patterns(patterns)
|
||||
def define_related_paths(self, **related_paths):
|
||||
if not self.related_paths:
|
||||
self.related_paths = {}
|
||||
|
||||
if not related_paths:
|
||||
related_paths = {
|
||||
"Stylesheet": os.path.join(self.data_dir, "styles", f"default.css"),
|
||||
"Markers": os.path.join(self.data_dir, "templates", "markers.svg"),
|
||||
"Symbols": os.path.join(self.data_dir, "templates", "symbols.svg"),
|
||||
"Patterns": os.path.join(self.data_dir, "templates", "patterns.svg"),
|
||||
}
|
||||
for path_name in list(related_paths.keys()):
|
||||
if path_name in self.related_paths:
|
||||
del related_paths[path_name]
|
||||
|
||||
for path_name in related_paths:
|
||||
uri = related_paths[path_name]
|
||||
custom_path = tool.Ifc.resolve_uri(uri)
|
||||
if custom_path:
|
||||
self.related_paths[path_name] = custom_path
|
||||
|
||||
def define_boilerplate(self):
|
||||
self.add_stylesheet()
|
||||
self.add_markers()
|
||||
self.add_symbols()
|
||||
self.add_patterns()
|
||||
return self
|
||||
|
||||
def calculate_scale(self):
|
||||
# svg_scale is for conversion from m to paper units
|
||||
# paper units = mm x drawing scale
|
||||
self.svg_scale = self.scale * 1000 # IFC is in meters, SVG is in mm
|
||||
self.raw_width = self.camera_width
|
||||
self.raw_height = self.camera_height
|
||||
self.width = self.raw_width * self.svg_scale
|
||||
self.height = self.raw_height * self.svg_scale
|
||||
|
||||
def add_stylesheet(self, uri):
|
||||
default_stylesheet = os.path.join(self.data_dir, "styles", f"default.css")
|
||||
with open(tool.Ifc.resolve_uri(uri) or default_stylesheet, "r") as stylesheet:
|
||||
def add_stylesheet(self):
|
||||
with open(self.related_paths["Stylesheet"], "r") as stylesheet:
|
||||
self.svg.defs.add(self.svg.style(stylesheet.read()))
|
||||
|
||||
def add_markers(self, uri):
|
||||
tree = ET.parse(tool.Ifc.resolve_uri(uri) or os.path.join(self.data_dir, "templates", "markers.svg"))
|
||||
def add_markers(self):
|
||||
tree = ET.parse(self.related_paths["Markers"])
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def add_symbols(self, uri):
|
||||
tree = ET.parse(tool.Ifc.resolve_uri(uri) or os.path.join(self.data_dir, "templates", "symbols.svg"))
|
||||
def add_symbols(self):
|
||||
tree = ET.parse(self.related_paths["Symbols"])
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def add_patterns(self, uri):
|
||||
tree = ET.parse(tool.Ifc.resolve_uri(uri) or os.path.join(self.data_dir, "templates", "patterns.svg"))
|
||||
def find_xml_symbol_by_id(self, id):
|
||||
tree = ET.parse(self.related_paths["Symbols"])
|
||||
xml_symbol = tree.find(f'.//*[@id="{id}"]')
|
||||
return External(xml_symbol) if xml_symbol else None
|
||||
|
||||
def add_patterns(self):
|
||||
tree = ET.parse(self.related_paths["Patterns"])
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
@@ -160,6 +191,8 @@ class SvgWriter:
|
||||
self.draw_section_level_annotation(obj)
|
||||
elif element.ObjectType == "TEXT":
|
||||
self.draw_text_annotation(obj, obj.location)
|
||||
elif element.ObjectType in ("FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
|
||||
self.draw_fall_annotations(obj)
|
||||
else:
|
||||
self.draw_misc_annotation(obj)
|
||||
|
||||
@@ -207,7 +240,7 @@ class SvgWriter:
|
||||
"alignment-baseline": "baseline",
|
||||
"dominant-baseline": "baseline",
|
||||
}
|
||||
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), **text_style))
|
||||
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style))
|
||||
if tag:
|
||||
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style))
|
||||
|
||||
@@ -234,6 +267,7 @@ class SvgWriter:
|
||||
self.svg.text(
|
||||
"UP",
|
||||
insert=tuple(text_position),
|
||||
class_="STAIR",
|
||||
**{
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
@@ -266,6 +300,7 @@ class SvgWriter:
|
||||
self.svg.text(
|
||||
axis_tag,
|
||||
insert=tuple(start * self.svg_scale),
|
||||
class_="GRID",
|
||||
**{
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
@@ -277,6 +312,7 @@ class SvgWriter:
|
||||
self.svg.text(
|
||||
axis_tag,
|
||||
insert=tuple(end * self.svg_scale),
|
||||
class_="GRID",
|
||||
**{
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
@@ -328,7 +364,7 @@ class SvgWriter:
|
||||
if custom_classes:
|
||||
classes.extend(custom_classes.split())
|
||||
for key in self.metadata:
|
||||
value = ifcopenshell.util.selector.Selector.get_element_value(element, key)
|
||||
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||
if value:
|
||||
classes.append(self.canonicalise_class_name(key) + "-" + self.canonicalise_class_name(str(value)))
|
||||
return classes
|
||||
@@ -360,23 +396,135 @@ class SvgWriter:
|
||||
self.draw_edge_annotation(obj, classes)
|
||||
|
||||
def draw_edge_annotation(self, obj, classes):
|
||||
predefined_type = classes[2].split("-", 1)[1]
|
||||
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
matrix_world = obj.matrix_world
|
||||
for edge in obj.data.edges:
|
||||
v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz
|
||||
v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz
|
||||
|
||||
def draw_simple_edge_annotation(v0, v1):
|
||||
v0_global = matrix_world @ obj.data.vertices[v0].co.xyz
|
||||
v1_global = matrix_world @ obj.data.vertices[v1].co.xyz
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
v1 = self.project_point_onto_camera(v1_global)
|
||||
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
|
||||
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
|
||||
vector = end - start
|
||||
line = self.svg.add(
|
||||
self.svg.line(
|
||||
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
|
||||
)
|
||||
self.svg.line(start=start * self.svg_scale, end=end * self.svg_scale, class_=" ".join(classes))
|
||||
)
|
||||
|
||||
# BATTING ANNOTATIONS
|
||||
if predefined_type == "BATTING":
|
||||
v0_global = matrix_world @ obj.data.vertices[0].co.xyz
|
||||
v1_global = matrix_world @ obj.data.vertices[1].co.xyz
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
v1 = self.project_point_onto_camera(v1_global)
|
||||
start_svg = Vector(((x_offset + v0.x), (y_offset - v0.y))) * self.svg_scale
|
||||
end_svg = Vector(((x_offset + v1.x), (y_offset - v1.y))) * self.svg_scale
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Batting") or {}
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
# default thickness set to 15 mm in paper space to keep the insulation visible
|
||||
thickness = pset_data["Thickness"] * unit_scale * self.svg_scale if "Thickness" in pset_data else 15.0
|
||||
reverse_x = pset_data.get("Reverse pattern direction", False)
|
||||
|
||||
if reverse_x:
|
||||
start_svg, end_svg = end_svg, start_svg
|
||||
|
||||
pattern_dir = (end_svg - start_svg).normalized()
|
||||
pattern_length = (end_svg - start_svg).length
|
||||
segment_width = thickness / 2.5
|
||||
|
||||
segments = ceil(pattern_length / segment_width)
|
||||
end_marker_width = pattern_length - segment_width * (segments - 1)
|
||||
|
||||
points = [start_svg + pattern_dir * segment_width * i for i in range(segments)]
|
||||
marker_id = f"batting-{element.GlobalId}"
|
||||
marker_end_id = f"batting-end-{element.GlobalId}"
|
||||
path_data = f"""M 0 {0.2*thickness}
|
||||
A {0.5*segment_width} {0.2*thickness} 0 0 1 {segment_width} {0.2*thickness}
|
||||
L {0.5*segment_width} {0.8*thickness}
|
||||
M 0 {0.2*thickness}
|
||||
L {0.5*segment_width} {0.8*thickness}
|
||||
A {0.5*segment_width} {0.2*thickness} 0 0 0 {segment_width} {1.0*thickness}
|
||||
M {0.5*segment_width} {0.8*thickness}
|
||||
A {0.5*segment_width} {0.2*thickness} 0 0 1 0 {1.0*thickness}
|
||||
"""
|
||||
path_data = " ".join(path_data.split())
|
||||
|
||||
svg_path = self.svg.path(style="fill: none; stroke:black; stroke-width:0.18", d=path_data)
|
||||
if reverse_x:
|
||||
svg_path.update({"transform": f"scale(-1,-1) translate(-{segment_width}, -{thickness})"})
|
||||
|
||||
marker = self.svg.marker(
|
||||
markerUnits="userSpaceOnUse",
|
||||
insert=(0, thickness / 2),
|
||||
size=(segment_width, thickness),
|
||||
orient="auto",
|
||||
id=marker_id,
|
||||
)
|
||||
marker.add(svg_path)
|
||||
self.svg.add(marker)
|
||||
|
||||
# separate marker for the end of the pattern to truncate it more gracefully
|
||||
marker_end = marker.copy()
|
||||
marker_end.update({"markerWidth": end_marker_width, "id": marker_end_id})
|
||||
self.svg.add(marker_end)
|
||||
|
||||
polyline_style = (
|
||||
f"marker-start: url(#{marker_id}); "
|
||||
f"marker-mid: url(#{marker_id}); stroke: none; "
|
||||
f"marker-end: url(#{marker_end_id}); stroke: none; "
|
||||
)
|
||||
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style))
|
||||
|
||||
elif predefined_type == "SECTION":
|
||||
display_data = DecoratorData.get_section_markers_display_data(obj)
|
||||
connect_markers = display_data["connect_markers"]
|
||||
|
||||
if connect_markers:
|
||||
for edge in obj.data.edges:
|
||||
draw_simple_edge_annotation(*edge.vertices[:])
|
||||
else:
|
||||
for edge in obj.data.edges:
|
||||
v0_marker_position = "start" if edge.vertices[0] == 0 else "end"
|
||||
v1_marker_position = "start" if edge.vertices[1] == 0 else "end"
|
||||
|
||||
v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz
|
||||
v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
v1 = self.project_point_onto_camera(v1_global)
|
||||
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
|
||||
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
|
||||
|
||||
edge_dir = (end - start).normalized()
|
||||
circle_radius = 5
|
||||
segment_size = circle_radius * 3
|
||||
|
||||
if display_data[v0_marker_position]["add_symbol"]:
|
||||
self.svg.add(
|
||||
self.svg.line(
|
||||
start=start * self.svg_scale,
|
||||
end=start * self.svg_scale + edge_dir * segment_size,
|
||||
class_=" ".join(classes),
|
||||
)
|
||||
)
|
||||
|
||||
if display_data[v1_marker_position]["add_symbol"]:
|
||||
self.svg.add(
|
||||
self.svg.line(
|
||||
start=end * self.svg_scale,
|
||||
end=end * self.svg_scale - edge_dir * segment_size,
|
||||
class_=" ".join(classes),
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
for edge in obj.data.edges:
|
||||
draw_simple_edge_annotation(*edge.vertices[:])
|
||||
|
||||
def draw_leader_annotation(self, obj):
|
||||
self.draw_line_annotation(obj)
|
||||
spline = obj.data.splines[0]
|
||||
@@ -393,65 +541,89 @@ class SvgWriter:
|
||||
|
||||
self.draw_line_annotation(obj)
|
||||
|
||||
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
|
||||
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
|
||||
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
|
||||
display_data = DecoratorData.get_section_markers_display_data(obj)
|
||||
|
||||
for vert in obj.data.vertices:
|
||||
point = obj.matrix_world @ vert.co
|
||||
symbol_position = self.project_point_onto_camera(point)
|
||||
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
|
||||
transform = "rotate({}, {}, {})".format(
|
||||
angle,
|
||||
(symbol_position * self.svg_scale)[0],
|
||||
(symbol_position * self.svg_scale)[1],
|
||||
)
|
||||
for edge in obj.data.edges:
|
||||
edge_verts = [obj.data.vertices[v_i] for v_i in edge.vertices]
|
||||
|
||||
self.svg.add(
|
||||
self.svg.use("#section-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform)
|
||||
)
|
||||
self.svg.add(self.svg.use("#section-tag", insert=tuple(symbol_position * self.svg_scale)))
|
||||
# convert edge vertiices coordinates to svg space
|
||||
# to calculate the edge angle and for later use
|
||||
edge_verts_svg = []
|
||||
for vert in edge_verts:
|
||||
point = obj.matrix_world @ vert.co
|
||||
symbol_position = self.project_point_onto_camera(point)
|
||||
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
|
||||
symbol_position_svg = symbol_position * self.svg_scale
|
||||
edge_verts_svg.append(symbol_position_svg)
|
||||
|
||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||
text_position = list(symbol_position * self.svg_scale)
|
||||
text_style = {
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), **text_style))
|
||||
self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), **text_style))
|
||||
edge_dir = (edge_verts_svg[1] - edge_verts_svg[0]).normalized()
|
||||
angle = degrees(edge_dir.xy.angle_signed(Vector([1, 0])))
|
||||
|
||||
for v_i, symbol_position_svg in zip(edge.vertices, edge_verts_svg):
|
||||
current_marker_position = "start" if v_i == 0 else "end"
|
||||
|
||||
# marker arrow symbol
|
||||
if display_data[current_marker_position]["add_symbol"]:
|
||||
transform = "rotate({}, {}, {})".format(angle, *symbol_position_svg.xy)
|
||||
symbol_id = display_data[current_marker_position]["symbol"]
|
||||
self.svg.add(self.svg.use(f"#{symbol_id}", insert=symbol_position_svg, transform=transform))
|
||||
|
||||
# marker circle and it's text
|
||||
if display_data[current_marker_position]["add_circle"]:
|
||||
self.svg.add(self.svg.use("#section-tag", insert=symbol_position_svg))
|
||||
|
||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||
text_position = symbol_position_svg
|
||||
text_style = {
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
reference_id,
|
||||
insert=(text_position[0], text_position[1] - 2.5),
|
||||
class_="SECTION",
|
||||
**text_style,
|
||||
)
|
||||
)
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
sheet_id, insert=(text_position[0], text_position[1] + 2.5), class_="SECTION", **text_style
|
||||
)
|
||||
)
|
||||
|
||||
def draw_elevation_annotation(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
symbol_position = self.project_point_onto_camera(obj.location)
|
||||
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
|
||||
symbol_position_svg = symbol_position * self.svg_scale
|
||||
|
||||
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
|
||||
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
|
||||
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
|
||||
|
||||
transform = "rotate({}, {}, {})".format(
|
||||
angle,
|
||||
(symbol_position * self.svg_scale)[0],
|
||||
(symbol_position * self.svg_scale)[1],
|
||||
)
|
||||
transform = "rotate({}, {}, {})".format(angle, *symbol_position_svg.xy)
|
||||
|
||||
self.svg.add(
|
||||
self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform)
|
||||
)
|
||||
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.svg_scale)))
|
||||
self.svg.add(self.svg.use("#elevation-arrow", insert=symbol_position_svg, transform=transform))
|
||||
self.svg.add(self.svg.use("#elevation-tag", insert=symbol_position_svg))
|
||||
|
||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||
text_position = list(symbol_position * self.svg_scale)
|
||||
text_position = symbol_position_svg
|
||||
text_style = {
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), **text_style))
|
||||
self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), **text_style))
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
reference_id, insert=(text_position[0], text_position[1] - 2.5), class_="ELEVATION", **text_style
|
||||
)
|
||||
)
|
||||
self.svg.add(
|
||||
self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), class_="ELEVATION", **text_style)
|
||||
)
|
||||
|
||||
def get_reference_and_sheet_id_from_annotation(self, element):
|
||||
reference_id = "-"
|
||||
@@ -474,11 +646,12 @@ class SvgWriter:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
element = tool.Ifc.get_entity(text_obj)
|
||||
rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
text_literal = [i for i in rep.Items if i.is_a("IfcTextLiteral")][0]
|
||||
text_literals = tool.Drawing.get_text_literal(text_obj, return_list=True)
|
||||
product = tool.Drawing.get_assigned_product(element)
|
||||
|
||||
text_position = self.project_point_onto_camera(position)
|
||||
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
|
||||
text_position_svg = text_position * self.svg_scale
|
||||
|
||||
local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0))
|
||||
projected_x_axis = self.project_point_onto_camera(position + local_x_axis)
|
||||
@@ -488,67 +661,82 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
|
||||
transform = "rotate({}, {}, {})".format(
|
||||
angle,
|
||||
(text_position * self.svg_scale)[0],
|
||||
(text_position * self.svg_scale)[1],
|
||||
)
|
||||
transform = "rotate({}, {}, {})".format(angle, *text_position_svg)
|
||||
classes_str = " ".join(self.get_attribute_classes(text_obj))
|
||||
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
template_text_fields = []
|
||||
if symbol:
|
||||
self.svg.add(self.svg.use(f"#{symbol}", insert=tuple(text_position * self.svg_scale)))
|
||||
symbol_svg = self.find_xml_symbol_by_id(symbol)
|
||||
if symbol_svg:
|
||||
symbol_xml = symbol_svg.get_xml()
|
||||
template_text_fields = symbol_xml.findall('.//text[@data-type="text-template"]')
|
||||
# if there is a symbol with template text fields
|
||||
# then we just populate it's fields with the data from text literals
|
||||
if template_text_fields:
|
||||
symbol_xml.attrib["transform"] = f"translate({', '.join(map(str, text_position_svg))})"
|
||||
symbol_xml.attrib.pop("id")
|
||||
# note: zip makes sure that we iterate over the shortest list
|
||||
for field, text_literal in zip(template_text_fields, text_literals):
|
||||
field.text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
field.attrib["class"] = classes_str
|
||||
self.svg.add(symbol_svg)
|
||||
return None
|
||||
|
||||
if text_literal.BoxAlignment == "top-left":
|
||||
alignment_baseline = "hanging"
|
||||
text_anchor = "start"
|
||||
elif text_literal.BoxAlignment == "top-middle":
|
||||
alignment_baseline = "hanging"
|
||||
text_anchor = "middle"
|
||||
elif text_literal.BoxAlignment == "top-right":
|
||||
alignment_baseline = "hanging"
|
||||
text_anchor = "end"
|
||||
elif text_literal.BoxAlignment == "middle-left":
|
||||
alignment_baseline = "middle"
|
||||
text_anchor = "start"
|
||||
elif text_literal.BoxAlignment == "center":
|
||||
alignment_baseline = "middle"
|
||||
text_anchor = "middle"
|
||||
elif text_literal.BoxAlignment == "middle-right":
|
||||
alignment_baseline = "middle"
|
||||
text_anchor = "end"
|
||||
elif text_literal.BoxAlignment == "bottom-left":
|
||||
alignment_baseline = "baseline"
|
||||
text_anchor = "start"
|
||||
elif text_literal.BoxAlignment == "bottom-middle":
|
||||
alignment_baseline = "baseline"
|
||||
text_anchor = "middle"
|
||||
elif text_literal.BoxAlignment == "bottom-right":
|
||||
alignment_baseline = "baseline"
|
||||
text_anchor = "end"
|
||||
if not symbol_svg or not template_text_fields:
|
||||
self.svg.add(self.svg.use(f"#{symbol}", insert=text_position_svg))
|
||||
|
||||
literal = text_literal.Literal
|
||||
def get_box_alignment_parameters(box_alignment):
|
||||
# reference for alignment values:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline
|
||||
vertical_alignment = {
|
||||
"top": "hanging",
|
||||
"bottom": "baseline",
|
||||
"center": "middle",
|
||||
"middle": "middle",
|
||||
}
|
||||
alignment_baseline = vertical_alignment[
|
||||
next(align for align in vertical_alignment if align in box_alignment)
|
||||
]
|
||||
horizontal_alignment = {
|
||||
"left": "start",
|
||||
"right": "end",
|
||||
"center": "middle",
|
||||
"middle": "middle",
|
||||
}
|
||||
text_anchor = horizontal_alignment[next(align for align in horizontal_alignment if align in box_alignment)]
|
||||
|
||||
product = tool.Drawing.get_assigned_product(element)
|
||||
selector = ifcopenshell.util.selector.Selector
|
||||
variables = {}
|
||||
if product != None:
|
||||
for variable in re.findall("{{.*?}}", literal):
|
||||
literal = literal.replace(variable, str(selector.get_element_value(product, variable[2:-2]) or ""))
|
||||
# using dominant-baseline because we plan to use <tspan> subtags
|
||||
# otherwise alignment-baseline would be sufficient
|
||||
return {
|
||||
"dominant-baseline": alignment_baseline,
|
||||
"text-anchor": text_anchor,
|
||||
}
|
||||
|
||||
for line_number, text_line in enumerate(literal.replace("\\n", "\n").split("\n")):
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
text_line,
|
||||
insert=tuple((text_position * self.svg_scale) + Vector((0, 3.5 * line_number))),
|
||||
class_=" ".join(self.get_attribute_classes(text_obj)),
|
||||
**{
|
||||
"text-anchor": text_anchor,
|
||||
"alignment-baseline": alignment_baseline,
|
||||
"dominant-baseline": alignment_baseline,
|
||||
"transform": transform,
|
||||
},
|
||||
)
|
||||
for text_literal in text_literals:
|
||||
# after pretty indentation some redundant spaces can occur in svg tags
|
||||
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
|
||||
# and add clases to the tspan tags
|
||||
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
|
||||
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
text_tag = self.svg.text(
|
||||
"",
|
||||
**{
|
||||
"transform": transform,
|
||||
"style": "font-size: 0;",
|
||||
},
|
||||
**get_box_alignment_parameters(text_literal.BoxAlignment),
|
||||
)
|
||||
self.svg.add(text_tag)
|
||||
|
||||
for line_number, text_line in enumerate(text.replace("\\n", "\n").split("\n")):
|
||||
# position has to be inserted at tspan to avoid x offset between tspans
|
||||
t_span = self.svg.tspan(text_line, class_=classes_str, insert=text_position_svg)
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
t_span.update({"dy": f"{line_number}em"})
|
||||
text_tag.add(t_span)
|
||||
|
||||
def draw_break_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -613,6 +801,7 @@ class SvgWriter:
|
||||
self.svg.text(
|
||||
"RL +{}".format(rl),
|
||||
insert=tuple(text_position),
|
||||
class_="PLANLEVEL",
|
||||
**{
|
||||
"text-anchor": text_anchor,
|
||||
"alignment-baseline": "baseline",
|
||||
@@ -625,7 +814,7 @@ class SvgWriter:
|
||||
points = obj.data.splines[0].points
|
||||
region = bpy.context.region
|
||||
region_3d = bpy.context.area.spaces.active.region_3d
|
||||
points_chunked = [points[i:i+3] for i in range(len(points)-2)]
|
||||
points_chunked = [points[i : i + 3] for i in range(len(points) - 2)]
|
||||
|
||||
for points_chunk in points_chunked:
|
||||
points_2d = [view3d_utils.location_3d_to_region_2d(region, region_3d, p.co.xyz) for p in points_chunk]
|
||||
@@ -651,7 +840,7 @@ class SvgWriter:
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
new_verts = [bm.verts.new(p) for p in points_chunk]
|
||||
new_edges = [bm.edges.new( (new_verts[e[0]], new_verts[e[1]]) ) for e in ((0, 1), (1, 2))]
|
||||
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in ((0, 1), (1, 2))]
|
||||
self.draw_svg_3point_arc(obj, bm)
|
||||
|
||||
def draw_svg_3point_arc(self, obj, bm):
|
||||
@@ -742,7 +931,7 @@ class SvgWriter:
|
||||
angle_text = abs(round(math.degrees(angle), 3))
|
||||
if is_reflex:
|
||||
angle_text = 360 - angle_text
|
||||
self.svg.add(self.svg.text(f"{angle_text}deg", insert=tuple(text_position), **text_style))
|
||||
self.svg.add(self.svg.text(f"{angle_text}deg", insert=tuple(text_position), class_="ANGLE", **text_style))
|
||||
|
||||
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
|
||||
arc_proj_end_pts = [self.project_point_onto_camera(v) for v in arc_end_pts]
|
||||
@@ -805,11 +994,85 @@ class SvgWriter:
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
|
||||
radius = ((matrix_world @ points[-1].co) - (matrix_world @ points[-2].co)).length
|
||||
radius = (points[-1].co - points[-2].co).length
|
||||
radius = helper.format_distance(radius)
|
||||
tag = element.Description or f"R{radius}"
|
||||
|
||||
self.svg.add(self.svg.text(tag, insert=tuple(text_position), **text_style))
|
||||
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
|
||||
|
||||
def draw_fall_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
classes = self.get_attribute_classes(obj)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
matrix_world = obj.matrix_world
|
||||
for spline in obj.data.splines:
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
d = " ".join(
|
||||
[
|
||||
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
|
||||
for p in projected_points
|
||||
]
|
||||
)
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
|
||||
p0 = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.svg_scale,
|
||||
(y_offset - projected_points[0].y) * self.svg_scale,
|
||||
)
|
||||
)
|
||||
p1 = Vector(
|
||||
(
|
||||
(x_offset + projected_points[1].x) * self.svg_scale,
|
||||
(y_offset - projected_points[1].y) * self.svg_scale,
|
||||
)
|
||||
)
|
||||
|
||||
# generate label text
|
||||
# same function as in decoration.py
|
||||
def get_label_text():
|
||||
B, A = [v.co.xyz for v in points[:2]]
|
||||
rise = abs(A.z - B.z)
|
||||
O = A.copy()
|
||||
O.z = B.z
|
||||
run = (B - O).length
|
||||
if run != 0:
|
||||
angle_tg = rise / run
|
||||
angle = round(degrees(atan(angle_tg)))
|
||||
else:
|
||||
angle = 90
|
||||
|
||||
# ues SLOPE_ANGLE as default
|
||||
if element.ObjectType in ("FALL", "SLOPE_ANGLE"):
|
||||
return f"{angle}°"
|
||||
elif element.ObjectType == "SLOPE_FRACTION":
|
||||
if angle == 90:
|
||||
return "-"
|
||||
return f"{helper.format_distance(rise)} / {helper.format_distance(run)}"
|
||||
elif element.ObjectType == "SLOPE_PERCENT":
|
||||
if angle == 90:
|
||||
return "-"
|
||||
return f"{round(angle_tg * 100)} %"
|
||||
|
||||
tag = element.Description or get_label_text()
|
||||
|
||||
text_offset = (p0 - p1).xy.normalized() * len(tag)
|
||||
text_position = projected_points[0]
|
||||
text_position = Vector(
|
||||
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
|
||||
)
|
||||
text_position += text_offset
|
||||
|
||||
text_style = {
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
|
||||
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
|
||||
|
||||
def draw_diameter_annotations(self, obj):
|
||||
classes = self.get_attribute_classes(obj)
|
||||
@@ -886,6 +1149,7 @@ class SvgWriter:
|
||||
self.svg.text(
|
||||
text,
|
||||
insert=tuple(text_position),
|
||||
class_="DIMENSION",
|
||||
**{
|
||||
"transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y),
|
||||
"text-anchor": "middle",
|
||||
|
||||
@@ -20,7 +20,13 @@ import bpy
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.module.drawing.data import ProductAssignmentsData, TextData, SheetsData, SchedulesData, DrawingsData
|
||||
from blenderbim.bim.module.drawing.data import (
|
||||
ProductAssignmentsData,
|
||||
SheetsData,
|
||||
SchedulesData,
|
||||
DrawingsData,
|
||||
DecoratorData,
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_camera(Panel):
|
||||
@@ -174,7 +180,9 @@ class BIM_PT_drawings(Panel):
|
||||
col.alignment = "LEFT"
|
||||
row2 = col.row(align=True)
|
||||
row2.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
|
||||
row2.operator("bim.duplicate_drawing", icon="COPYDOWN", text="").drawing = active_drawing.ifc_definition_id
|
||||
row2.operator(
|
||||
"bim.duplicate_drawing", icon="COPYDOWN", text=""
|
||||
).drawing = active_drawing.ifc_definition_id
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
@@ -291,6 +299,7 @@ class BIM_PT_product_assignments(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -325,6 +334,7 @@ class BIM_PT_text(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 0
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -336,24 +346,62 @@ class BIM_PT_text(Panel):
|
||||
return element.is_a("IfcAnnotation") and element.ObjectType in ["TEXT", "TEXT_LEADER"]
|
||||
|
||||
def draw(self, context):
|
||||
if not TextData.is_loaded:
|
||||
TextData.load()
|
||||
|
||||
props = context.active_object.BIMTextProperties
|
||||
obj = context.active_object
|
||||
props = obj.BIMTextProperties
|
||||
|
||||
if props.is_editing:
|
||||
# shares most of the code with EditTextPopup.draw()
|
||||
# need to keep them in sync or move to some common function
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_text", icon="CHECKMARK")
|
||||
row.operator("bim.add_text_literal", icon="ADD", text="")
|
||||
row.operator("bim.disable_editing_text", icon="CANCEL", text="")
|
||||
blenderbim.bim.helper.draw_attributes(props.attributes, self.layout)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "font_size")
|
||||
|
||||
for i, literal_props in enumerate(props.literals):
|
||||
box = self.layout.box()
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"Literal[{i}]:")
|
||||
row.operator("bim.remove_text_literal", icon="X", text="").literal_prop_id = i
|
||||
|
||||
# skip BoxAlignment since we're going to format it ourselves
|
||||
attributes = [a for a in literal_props.attributes if a.name != "BoxAlignment"]
|
||||
blenderbim.bim.helper.draw_attributes(attributes, box)
|
||||
|
||||
# a bit hacky way to align box alignment widget
|
||||
rows = [box.row(align=True) for i in range(3)]
|
||||
for i in range(9):
|
||||
if i % 3 == 0:
|
||||
split = rows[i // 3].split(factor=0.1, align=True)
|
||||
split.column()
|
||||
split.prop(literal_props, "box_alignment", text="", index=i)
|
||||
|
||||
text_lines = ["Text box alignment:", literal_props.attributes["BoxAlignment"].string_value, ""]
|
||||
for i in range(3):
|
||||
split = rows[i].split(factor=0.1, align=False)
|
||||
split.column()
|
||||
split.label(text=text_lines[i])
|
||||
else:
|
||||
text_data = DecoratorData.get_ifc_text_data(obj)
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_editing_text", icon="GREASEPENCIL")
|
||||
|
||||
for attribute in TextData.data["attributes"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="FontSize")
|
||||
row.label(text=str(text_data["FontSize"]))
|
||||
|
||||
for literal_data in text_data["Literals"]:
|
||||
box = self.layout.box()
|
||||
for attribute in literal_data:
|
||||
row = box.row(align=True)
|
||||
row.label(text=attribute)
|
||||
row.label(text=literal_data[attribute])
|
||||
|
||||
|
||||
class BIM_PT_annotation_utilities(Panel):
|
||||
@@ -417,9 +465,17 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
op.data_type = "mesh"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE")
|
||||
op.object_type = "BATTING"
|
||||
op.data_type = "mesh"
|
||||
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
|
||||
op.object_type = "FILL_AREA"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC")
|
||||
op.object_type = "FALL"
|
||||
op.data_type = "curve"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
|
||||
row.enabled = context.scene.camera is not None
|
||||
|
||||
@@ -27,6 +27,9 @@ classes = (
|
||||
operator.OverrideDelete,
|
||||
operator.OverrideDuplicateMove,
|
||||
operator.OverrideDuplicateMoveLinked,
|
||||
operator.OverrideJoin,
|
||||
operator.OverrideModeSetEdit,
|
||||
operator.OverrideModeSetObject,
|
||||
operator.OverrideOutlinerDelete,
|
||||
operator.OverridePasteBuffer,
|
||||
operator.RemoveConnection,
|
||||
@@ -35,6 +38,7 @@ classes = (
|
||||
operator.SwitchRepresentation,
|
||||
operator.UpdateParametricRepresentation,
|
||||
operator.UpdateRepresentation,
|
||||
prop.BIMObjectGeometryProperties,
|
||||
prop.BIMGeometryProperties,
|
||||
ui.BIM_PT_derived_placements,
|
||||
ui.BIM_PT_representations,
|
||||
@@ -48,23 +52,37 @@ addon_keymaps = []
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties)
|
||||
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
|
||||
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
|
||||
bpy.types.VIEW3D_MT_object.append(ui.object_menu)
|
||||
wm = bpy.context.window_manager
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="3D View", space_type="VIEW_3D")
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("bim.override_object_join", "J", "PRESS", ctrl=True)
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move", "D", "PRESS", shift=True)
|
||||
kmi.properties.is_interactive = True
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked", "D", "PRESS", alt=True)
|
||||
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
|
||||
kmi.properties["name"] = "bim.override_paste_buffer"
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_edit", "TAB", "PRESS")
|
||||
kmi = km.keymap_items.new("bim.override_object_delete", "X", "PRESS")
|
||||
kmi = km.keymap_items.new("bim.override_object_delete", "DEL", "PRESS")
|
||||
kmi.properties.confirm = False
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Mesh", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER")
|
||||
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
|
||||
kmi.properties["name"] = "bim.override_paste_buffer"
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_outliner_delete", "X", "PRESS")
|
||||
kmi = km.keymap_items.new("bim.override_outliner_delete", "DEL", "PRESS")
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.VIEW3D_MT_object.remove(ui.object_menu)
|
||||
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
|
||||
del bpy.types.Scene.BIMGeometryProperties
|
||||
del bpy.types.Object.BIMGeometryProperties
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
if kc:
|
||||
|
||||
@@ -35,12 +35,18 @@ class RepresentationsData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"representations": cls.representations()}
|
||||
cls.data["contexts"] = cls.contexts()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def representations(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
|
||||
active_representation_id = None
|
||||
if bpy.context.active_object.data and hasattr(bpy.context.active_object.data, "BIMMeshProperties"):
|
||||
active_representation_id = bpy.context.active_object.data.BIMMeshProperties.ifc_definition_id
|
||||
|
||||
representations = []
|
||||
if element.is_a("IfcProduct") and element.Representation:
|
||||
representations = element.Representation.Representations
|
||||
@@ -57,6 +63,7 @@ class RepresentationsData:
|
||||
"ContextIdentifier": "",
|
||||
"TargetView": "",
|
||||
"RepresentationType": representation_type or "",
|
||||
"is_active": representation.id() == active_representation_id,
|
||||
}
|
||||
if representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"):
|
||||
data["ContextIdentifier"] = representation.ContextOfItems.ContextIdentifier or ""
|
||||
@@ -64,6 +71,25 @@ class RepresentationsData:
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def contexts(cls):
|
||||
results = []
|
||||
for element in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
results.append((str(element.id()), element.ContextType or "Unnamed", ""))
|
||||
for element in tool.Ifc.get().by_type("IfcGeometricRepresentationSubContext", include_subtypes=False):
|
||||
results.append(
|
||||
(
|
||||
str(element.id()),
|
||||
"{}/{}/{}".format(
|
||||
element.ContextType or "Unnamed",
|
||||
element.ContextIdentifier or "Unnamed",
|
||||
element.TargetView or "Unnamed",
|
||||
),
|
||||
"",
|
||||
)
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
class ConnectionsData:
|
||||
data = {}
|
||||
|
||||
@@ -38,6 +38,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
face = None
|
||||
@@ -61,6 +62,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
@@ -88,6 +90,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
@@ -289,6 +292,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
@@ -385,6 +389,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
faces = bm.faces
|
||||
|
||||
@@ -28,12 +28,12 @@ import ifcopenshell.api
|
||||
import blenderbim.core.geometry as core
|
||||
import blenderbim.core.style
|
||||
import blenderbim.core.root
|
||||
import blenderbim.core.drawing
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.handler
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.root.prop import get_contexts
|
||||
|
||||
|
||||
class Operator:
|
||||
@@ -59,27 +59,20 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_representation"
|
||||
bl_label = "Add Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
context_id: bpy.props.IntProperty()
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
profile_set_usage: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_context = self.context_id
|
||||
if not ifc_context and get_contexts(self, context):
|
||||
ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None
|
||||
ifc_context = int(context.active_object.BIMGeometryProperties.contexts or "0") or None
|
||||
if ifc_context:
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=obj,
|
||||
obj=context.active_object,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=self.ifc_representation_class,
|
||||
profile_set_usage=tool.Ifc.get().by_id(self.profile_set_usage) if self.profile_set_usage else None,
|
||||
ifc_representation_class=None,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -357,6 +350,8 @@ class OverrideDeleteTrait:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
return blenderbim.core.drawing.remove_drawing(tool.Ifc, tool.Drawing, drawing=element)
|
||||
IfcStore.delete_element(element)
|
||||
if obj.users_collection and obj.users_collection[0].name == obj.name:
|
||||
parent = ifcopenshell.util.element.get_aggregate(element)
|
||||
@@ -396,8 +391,8 @@ class OverrideDeleteTrait:
|
||||
|
||||
|
||||
class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
bl_idname = "object.delete"
|
||||
bl_label = "Delete"
|
||||
bl_idname = "bim.override_object_delete"
|
||||
bl_label = "IFC Delete"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
use_global: bpy.props.BoolProperty(default=False)
|
||||
confirm: bpy.props.BoolProperty(default=True)
|
||||
@@ -425,15 +420,19 @@ class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
def _execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
self.delete_ifc_object(obj)
|
||||
bpy.data.objects.remove(obj)
|
||||
try:
|
||||
obj.name
|
||||
bpy.data.objects.remove(obj)
|
||||
except:
|
||||
pass
|
||||
# Required otherwise gizmos are still visible
|
||||
context.view_layer.objects.active = None
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
bl_idname = "outliner.delete"
|
||||
bl_label = "Delete"
|
||||
bl_idname = "bim.override_outliner_delete"
|
||||
bl_label = "IFC Delete"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hierarchy: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@@ -500,9 +499,10 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
|
||||
|
||||
class OverrideDuplicateMove(bpy.types.Operator):
|
||||
bl_idname = "object.duplicate_move"
|
||||
bl_label = "Duplicate Objects"
|
||||
bl_idname = "bim.override_object_duplicate_move"
|
||||
bl_label = "IFC Duplicate Objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -529,7 +529,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
new_obj.select_set(True)
|
||||
if new_active_obj:
|
||||
context.view_layer.objects.active = new_active_obj
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
if self.is_interactive:
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -553,13 +554,14 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = [new]
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
if self.is_interactive:
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
bl_idname = "object.duplicate_move_linked"
|
||||
bl_label = "Duplicate Linked"
|
||||
bl_idname = "bim.override_object_duplicate_move_linked"
|
||||
bl_label = "IFC Duplicate Linked"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -611,9 +613,96 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideJoin(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.override_object_join"
|
||||
bl_label = "IFC Join"
|
||||
|
||||
def _execute(self, context):
|
||||
if not tool.Ifc.get():
|
||||
return bpy.ops.object.join()
|
||||
|
||||
if not context.active_object:
|
||||
return
|
||||
|
||||
self.target = context.active_object
|
||||
self.target_element = tool.Ifc.get_entity(self.target)
|
||||
if self.target_element:
|
||||
return self.join_ifc_obj()
|
||||
return self.join_blender_obj()
|
||||
|
||||
def join_ifc_obj(self):
|
||||
representation = tool.Ifc.get().by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType in ("Tessellation", "Brep"):
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj == self.target:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
tool.Ifc.delete(element)
|
||||
bpy.ops.object.join()
|
||||
bpy.ops.bim.update_representation(obj=self.target.name, ifc_representation_class="")
|
||||
elif representation.RepresentationType == "SweptSolid":
|
||||
target_placement = np.array(self.target.matrix_world)
|
||||
items = list(representation.Items)
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj == self.target:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
# Non IFC elements cannot be join since we cannot guarantee SweptSolid compliance
|
||||
if not element:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
# Only objects of the same representation type can be joined
|
||||
obj_rep = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if obj_rep.RepresentationType != "SweptSolid":
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
placement = np.array(obj.matrix_world)
|
||||
|
||||
for item in obj_rep.Items:
|
||||
copied_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item)
|
||||
if copied_item.Position:
|
||||
position = ifcopenshell.util.placement.get_axis2placement(copied_item.Position)
|
||||
else:
|
||||
position = np.eye(4)
|
||||
position = placement @ position
|
||||
position = np.linalg.inv(target_placement) @ position
|
||||
copied_item.Position = tool.Ifc.get().createIfcAxis2Placement3D(
|
||||
tool.Ifc.get().createIfcCartesianPoint([float(n) for n in position[:, 3][:3]]),
|
||||
tool.Ifc.get().createIfcDirection([float(n) for n in position[:, 2][:3]]),
|
||||
tool.Ifc.get().createIfcDirection([float(n) for n in position[:, 0][:3]]),
|
||||
)
|
||||
items.append(copied_item)
|
||||
tool.Ifc.delete(element)
|
||||
representation.Items = items
|
||||
bpy.ops.object.join()
|
||||
core.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=self.target,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
def join_blender_obj(self):
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj == self.target:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
tool.Ifc.delete(element)
|
||||
bpy.ops.object.join()
|
||||
|
||||
|
||||
class OverridePasteBuffer(bpy.types.Operator):
|
||||
bl_idname = "bim.override_paste_buffer"
|
||||
bl_label = "Paste BIM Objects"
|
||||
bl_label = "IFC Paste BIM Objects"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.pastebuffer()
|
||||
@@ -621,3 +710,134 @@ class OverridePasteBuffer(bpy.types.Operator):
|
||||
for obj in context.selected_objects:
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideModeSetEdit(bpy.types.Operator):
|
||||
bl_idname = "bim.override_mode_set_edit"
|
||||
bl_label = "IFC Mode Set Edit"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
objs = context.selected_objects or [context.active_object]
|
||||
if context.active_object:
|
||||
context.active_object.select_set(True)
|
||||
edited_objs = []
|
||||
for obj in objs:
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
# We are switching from OBJECT to EDIT mode.
|
||||
usage_type = tool.Model.get_usage_type(element)
|
||||
if usage_type:
|
||||
# Parametric objects shall not be edited as meshes as they
|
||||
# can be modified to be incompatible with the parametric
|
||||
# constraints.
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType in ("Tessellation", "Brep"):
|
||||
if element.HasOpenings:
|
||||
# Mesh elements with openings must disable openings
|
||||
# so that you can edit the original topology.
|
||||
core.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=False,
|
||||
)
|
||||
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
|
||||
if not context.selected_objects:
|
||||
return {"FINISHED"}
|
||||
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if not tool.Ifc.get():
|
||||
return bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
class OverrideModeSetObject(bpy.types.Operator):
|
||||
bl_idname = "bim.override_mode_set_object"
|
||||
bl_label = "IFC Mode Set Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
should_save: bpy.props.BoolProperty(name="Should Save", default=True)
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in self.edited_objs:
|
||||
if self.should_save:
|
||||
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="")
|
||||
if tool.Ifc.get_entity(obj).HasOpenings:
|
||||
self.reload_representation(obj)
|
||||
else:
|
||||
self.reload_representation(obj)
|
||||
|
||||
for obj in self.unchanged_objs_with_openings:
|
||||
self.reload_representation(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def reload_representation(self, obj):
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
core.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self, "should_save")
|
||||
|
||||
def invoke(self, context, event):
|
||||
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
|
||||
if not tool.Ifc.get():
|
||||
return {"FINISHED"}
|
||||
|
||||
objs = context.selected_objects or [context.active_object]
|
||||
|
||||
self.edited_objs = []
|
||||
self.unchanged_objs_with_openings = []
|
||||
|
||||
for obj in objs:
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType in (
|
||||
"Tessellation",
|
||||
"Brep",
|
||||
) and obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
|
||||
self.edited_objs.append(obj)
|
||||
elif element.HasOpenings:
|
||||
self.unchanged_objs_with_openings.append(obj)
|
||||
|
||||
if self.edited_objs:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
return self.execute(context)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -31,6 +32,16 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
def get_contexts(self, context):
|
||||
if not RepresentationsData.is_loaded:
|
||||
RepresentationsData.load()
|
||||
return RepresentationsData.data["contexts"]
|
||||
|
||||
|
||||
class BIMObjectGeometryProperties(PropertyGroup):
|
||||
contexts: EnumProperty(items=get_contexts, name="Contexts")
|
||||
|
||||
|
||||
class BIMGeometryProperties(PropertyGroup):
|
||||
# Revit workaround
|
||||
should_use_presentation_style_assignment: BoolProperty(name="Force Presentation Style Assignment", default=False)
|
||||
|
||||
@@ -23,6 +23,12 @@ from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData, ConnectionsData, DerivedPlacementsData
|
||||
|
||||
|
||||
def object_menu(self, context):
|
||||
self.layout.operator("bim.override_object_duplicate_move", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
|
||||
|
||||
|
||||
class BIM_PT_representations(Panel):
|
||||
bl_label = "IFC Representations"
|
||||
bl_idname = "BIM_PT_representations"
|
||||
@@ -30,6 +36,7 @@ class BIM_PT_representations(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
|
||||
@classmethod
|
||||
@@ -51,7 +58,7 @@ class BIM_PT_representations(Panel):
|
||||
layout.label(text="No representations found")
|
||||
|
||||
row = layout.row(align=True)
|
||||
prop_with_search(row, context.scene.BIMRootProperties, "contexts", text="")
|
||||
prop_with_search(row, context.active_object.BIMGeometryProperties, "contexts", text="")
|
||||
row.operator("bim.add_representation", icon="ADD", text="")
|
||||
|
||||
for representation in RepresentationsData.data["representations"]:
|
||||
@@ -60,7 +67,7 @@ class BIM_PT_representations(Panel):
|
||||
row.label(text=representation["ContextIdentifier"])
|
||||
row.label(text=representation["TargetView"])
|
||||
row.label(text=representation["RepresentationType"])
|
||||
op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="")
|
||||
op = row.operator("bim.switch_representation", icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH", text="")
|
||||
op.should_switch_all_meshes = True
|
||||
op.should_reload = True
|
||||
op.ifc_definition_id = representation["id"]
|
||||
@@ -75,6 +82,7 @@ class BIM_PT_connections(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
|
||||
@classmethod
|
||||
@@ -179,6 +187,7 @@ class BIM_PT_derived_placements(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "OBJECT_PT_transform"
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -75,6 +75,7 @@ class BIM_PT_object_groups(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_utilities_object"
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -100,6 +100,7 @@ class BIM_PT_library_references(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -242,7 +242,7 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator):
|
||||
constituent: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
for inverse in tool.Ifc.get().get_inverse(layer):
|
||||
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.constituent)):
|
||||
if inverse.is_a("IfcMaterialConstituentSet") and len(inverse.MaterialConstituents) == 1:
|
||||
return
|
||||
ifcopenshell.api.run(
|
||||
@@ -277,7 +277,7 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
profile: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
for inverse in tool.Ifc.get().get_inverse(layer):
|
||||
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.profile)):
|
||||
if inverse.is_a("IfcMaterialProfileSet") and len(inverse.MaterialProfiles) == 1:
|
||||
return
|
||||
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))
|
||||
@@ -335,7 +335,7 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
layer: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
for inverse in tool.Ifc.get().get_inverse(layer):
|
||||
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.layer)):
|
||||
if inverse.is_a("IfcMaterialLayerSet") and len(inverse.MaterialLayers) == 1:
|
||||
return
|
||||
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=tool.Ifc.get().by_id(self.layer))
|
||||
@@ -583,7 +583,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
|
||||
blenderbim.bim.helper.import_attributes2(material_set_item, self.props.material_set_item_attributes)
|
||||
|
||||
if material_set_item.is_a("IfcMaterialProfile"):
|
||||
if material_set_item.Profile:
|
||||
if material_set_item.Profile and material_set_item.Profile.ProfileName:
|
||||
self.mprops.profiles = str(material_set_item.Profile.id())
|
||||
|
||||
return {"FINISHED"}
|
||||
@@ -664,35 +664,23 @@ class CopyMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
material = ifcopenshell.util.element.get_material(
|
||||
self.file.by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
)
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
if obj == context.active_object:
|
||||
continue
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"material.copy_material",
|
||||
self.file,
|
||||
**{
|
||||
"material": material,
|
||||
"element": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
},
|
||||
)
|
||||
self.set_default_material(obj, material)
|
||||
blender_material = context.active_object.active_material
|
||||
material = tool.Ifc.get_entity(blender_material)
|
||||
copied_material = ifcopenshell.api.run("material.copy_material", tool.Ifc.get(), material=material)
|
||||
copied_blender_material = bpy.data.materials.new(blender_material.name)
|
||||
copied_style = self.get_style(copied_material)
|
||||
tool.Ifc.link(copied_material, copied_blender_material)
|
||||
if copied_style:
|
||||
tool.Ifc.link(copied_style, copied_blender_material)
|
||||
context.active_object.active_material = copied_blender_material
|
||||
|
||||
def set_default_material(self, obj, material):
|
||||
object_material_ids = [
|
||||
om.BIMObjectProperties.ifc_definition_id
|
||||
for om in obj.data.materials
|
||||
if om is not None and om.BIMObjectProperties.ifc_definition_id
|
||||
]
|
||||
|
||||
if material.id() in object_material_ids:
|
||||
return
|
||||
obj.data.materials.append(IfcStore.get_element(material.id()))
|
||||
def get_style(self, material):
|
||||
for material_representation in material.HasRepresentation:
|
||||
for representation in material_representation.Representations:
|
||||
for item in representation.Items:
|
||||
for style in item.Styles:
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
return style
|
||||
|
||||
|
||||
class ExpandMaterialCategory(bpy.types.Operator):
|
||||
|
||||
@@ -92,6 +92,7 @@ class BIM_PT_material(Panel):
|
||||
material_id = context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
if bool(material_id):
|
||||
row.operator("bim.remove_material", icon="X", text="Remove IFC Material").material = material_id
|
||||
row.operator("bim.copy_material", icon="DUPLICATE", text="")
|
||||
row.operator("bim.unlink_material", icon="UNLINKED", text="")
|
||||
else:
|
||||
op = row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
|
||||
@@ -158,8 +159,6 @@ class BIM_PT_object_material(Panel):
|
||||
op.material_set_usage = ObjectMaterialData.data["material_id"]
|
||||
row.operator("bim.disable_editing_assigned_material", icon="CANCEL", text="")
|
||||
else:
|
||||
if ObjectMaterialData.data["material_class"] == "IfcMaterial":
|
||||
row.operator("bim.copy_material", icon="COPYDOWN", text="")
|
||||
row.operator("bim.enable_editing_assigned_material", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.unassign_material", icon="X", text="")
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ from . import (
|
||||
array,
|
||||
product,
|
||||
wall,
|
||||
roof,
|
||||
slab,
|
||||
space,
|
||||
stair,
|
||||
window,
|
||||
opening,
|
||||
@@ -34,6 +36,8 @@ from . import (
|
||||
profile,
|
||||
sverchok_modifier,
|
||||
door,
|
||||
railing,
|
||||
roof,
|
||||
)
|
||||
|
||||
classes = (
|
||||
@@ -47,9 +51,8 @@ classes = (
|
||||
product.AddEmptyType,
|
||||
product.AlignProduct,
|
||||
product.ChangeTypePage,
|
||||
product.DisplayConstrTypes,
|
||||
product.LoadTypeThumbnails,
|
||||
product.ReinvokeOperator,
|
||||
product.MirrorElements,
|
||||
workspace.Hotkey,
|
||||
wall.AlignWall,
|
||||
wall.ChangeExtrusionDepth,
|
||||
@@ -74,9 +77,13 @@ classes = (
|
||||
opening.ShowOpenings,
|
||||
profile.ChangeCardinalPoint,
|
||||
profile.ChangeProfileDepth,
|
||||
profile.DisableEditingExtrusionAxis,
|
||||
profile.EditExtrusionAxis,
|
||||
profile.EnableEditingExtrusionAxis,
|
||||
profile.ExtendProfile,
|
||||
profile.RecalculateProfile,
|
||||
profile.Rotate90,
|
||||
roof.GenerateHippedRoof,
|
||||
slab.DisableEditingExtrusionProfile,
|
||||
slab.DisableEditingSketchExtrusionProfile,
|
||||
slab.EditExtrusionProfile,
|
||||
@@ -85,6 +92,7 @@ classes = (
|
||||
slab.EnableEditingSketchExtrusionProfile,
|
||||
slab.ResetVertex,
|
||||
slab.SetArcIndex,
|
||||
space.GenerateSpace,
|
||||
prop.ConstrTypeInfo,
|
||||
prop.ConstrClassInfo,
|
||||
prop.ConstrBrowserState,
|
||||
@@ -94,13 +102,16 @@ classes = (
|
||||
prop.BIMSverchokProperties,
|
||||
prop.BIMWindowProperties,
|
||||
prop.BIMDoorProperties,
|
||||
prop.BIMRailingProperties,
|
||||
prop.BIMRoofProperties,
|
||||
ui.BIM_PT_authoring,
|
||||
ui.BIM_PT_array,
|
||||
ui.BIM_PT_stair,
|
||||
ui.BIM_PT_sverchok,
|
||||
ui.BIM_PT_window,
|
||||
ui.BIM_PT_door,
|
||||
ui.DisplayConstrTypesUI,
|
||||
ui.BIM_PT_railing,
|
||||
ui.BIM_PT_roof,
|
||||
ui.LaunchTypeManager,
|
||||
ui.HelpConstrTypes,
|
||||
ui.BIM_MT_model,
|
||||
@@ -134,6 +145,24 @@ classes = (
|
||||
door.FinishEditingDoor,
|
||||
door.EnableEditingDoor,
|
||||
door.RemoveDoor,
|
||||
railing.BIM_OT_add_railing,
|
||||
railing.AddRailing,
|
||||
railing.CancelEditingRailing,
|
||||
railing.FinishEditingRailing,
|
||||
railing.EnableEditingRailing,
|
||||
railing.CancelEditingRailingPath,
|
||||
railing.FinishEditingRailingPath,
|
||||
railing.EnableEditingRailingPath,
|
||||
railing.RemoveRailing,
|
||||
roof.BIM_OT_add_roof,
|
||||
roof.AddRoof,
|
||||
roof.CancelEditingRoof,
|
||||
roof.FinishEditingRoof,
|
||||
roof.EnableEditingRoof,
|
||||
roof.CancelEditingRoofPath,
|
||||
roof.FinishEditingRoofPath,
|
||||
roof.EnableEditingRoofPath,
|
||||
roof.RemoveRoof,
|
||||
)
|
||||
|
||||
addon_keymaps = []
|
||||
@@ -148,10 +177,14 @@ def register():
|
||||
bpy.types.Object.BIMSverchokProperties = bpy.props.PointerProperty(type=prop.BIMSverchokProperties)
|
||||
bpy.types.Object.BIMWindowProperties = bpy.props.PointerProperty(type=prop.BIMWindowProperties)
|
||||
bpy.types.Object.BIMDoorProperties = bpy.props.PointerProperty(type=prop.BIMDoorProperties)
|
||||
bpy.types.Object.BIMRailingProperties = bpy.props.PointerProperty(type=prop.BIMRailingProperties)
|
||||
bpy.types.Object.BIMRoofProperties = bpy.props.PointerProperty(type=prop.BIMRoofProperties)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(window.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(door.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(railing.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(roof.add_object_button)
|
||||
bpy.types.VIEW3D_MT_add.append(ui.add_menu)
|
||||
bpy.app.handlers.load_post.append(handler.load_post)
|
||||
wm = bpy.context.window_manager
|
||||
@@ -171,11 +204,15 @@ def unregister():
|
||||
del bpy.types.Object.BIMSverchokProperties
|
||||
del bpy.types.Object.BIMWindowProperties
|
||||
del bpy.types.Object.BIMDoorProperties
|
||||
del bpy.types.Object.BIMRailingProperties
|
||||
del bpy.types.Object.BIMRoofProperties
|
||||
bpy.app.handlers.load_post.remove(handler.load_post)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(door.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(railing.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(roof.add_object_button)
|
||||
bpy.types.VIEW3D_MT_add.remove(ui.add_menu)
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
|
||||
@@ -36,8 +36,7 @@ class AddArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
array = {"children": [], "count": 1, "x": 0.0, "y": 0.0, "z": 0.0}
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets.get("BBIM_Array", None)
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
|
||||
if pset:
|
||||
data = json.loads(pset["Data"])
|
||||
@@ -77,8 +76,7 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMArrayProperties
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets["BBIM_Array"]
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
data = json.loads(pset["Data"])
|
||||
data[self.item] = {
|
||||
"children": data[self.item]["children"],
|
||||
@@ -112,8 +110,7 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
data = json.loads(psets["BBIM_Array"]["Data"])[self.item]
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
|
||||
props = obj.BIMArrayProperties
|
||||
props.count = data["count"]
|
||||
props.x = data["x"]
|
||||
@@ -134,8 +131,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMArrayProperties
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets["BBIM_Array"]
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
data = json.loads(pset["Data"])
|
||||
data[self.item]["count"] = 1
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ def refresh():
|
||||
SverchokData.is_loaded = False
|
||||
WindowData.is_loaded = False
|
||||
DoorData.is_loaded = False
|
||||
RailingData.is_loaded = False
|
||||
RoofData.is_loaded = False
|
||||
|
||||
|
||||
class AuthoringData:
|
||||
@@ -68,7 +70,14 @@ class AuthoringData:
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcElementType")
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
names = [d.name() for d in declarations]
|
||||
names.extend(("IfcDoorStyle", "IfcWindowStyle"))
|
||||
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialElementType")
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
names.extend([d.name() for d in declarations])
|
||||
|
||||
if tool.Ifc.get_schema() in ("IFC2X3", "IFC4"):
|
||||
names.extend(("IfcDoorStyle", "IfcWindowStyle"))
|
||||
|
||||
version = tool.Ifc.get_schema()
|
||||
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)]
|
||||
|
||||
@@ -90,7 +99,7 @@ class AuthoringData:
|
||||
|
||||
@classmethod
|
||||
def type_thumbnail(cls):
|
||||
if not cls.props.relating_type_id:
|
||||
if not cls.data["relating_types_ids"]:
|
||||
return 0
|
||||
element = tool.Ifc.get().by_id(int(cls.props.relating_type_id))
|
||||
return cls.type_thumbnails.get(element.id(), None) or 0
|
||||
@@ -176,12 +185,7 @@ class AuthoringData:
|
||||
def active_material_usage(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
if material:
|
||||
if material.is_a("IfcMaterialLayerSetUsage"):
|
||||
return f"LAYER{material.LayerSetDirection[-1]}"
|
||||
elif material.is_a("IfcMaterialProfileSetUsage"):
|
||||
return "PROFILE"
|
||||
return tool.Model.get_usage_type(element)
|
||||
|
||||
@classmethod
|
||||
def ifc_classes(cls):
|
||||
@@ -221,7 +225,8 @@ class AuthoringData:
|
||||
|
||||
@classmethod
|
||||
def relating_types_browser(cls):
|
||||
return cls.relating_types(ifc_class=cls.props.ifc_class_browser)
|
||||
if cls.data["ifc_classes"]:
|
||||
return cls.relating_types(ifc_class=cls.props.ifc_class_browser)
|
||||
|
||||
@classmethod
|
||||
def new_constr_class_info(cls, ifc_class):
|
||||
@@ -321,14 +326,14 @@ class AuthoringData:
|
||||
def new_relating_type(cls, ifc_class=None, relating_type_id=None):
|
||||
if ifc_class is None:
|
||||
bpy.ops.bim.add_constr_type_instance(
|
||||
ifc_class=cls.props.ifc_class, relating_type_id=int(cls.props.relating_type_id), link_to_scene=True
|
||||
ifc_class=cls.props.ifc_class, relating_type_id=int(cls.props.relating_type_id)
|
||||
)
|
||||
else:
|
||||
cls.props.updating = True
|
||||
cls.props.ifc_class = ifc_class
|
||||
cls.props.relating_type_id = str(relating_type_id)
|
||||
cls.props.updating = False
|
||||
bpy.ops.bim.add_constr_type_instance(link_to_scene=True)
|
||||
bpy.ops.bim.add_constr_type_instance()
|
||||
return bpy.context.selected_objects[-1]
|
||||
|
||||
@staticmethod
|
||||
@@ -459,3 +464,43 @@ class DoorData:
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
|
||||
|
||||
class RailingData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"parameters": cls.parameters()}
|
||||
|
||||
@classmethod
|
||||
def parameters(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
parameters = psets.get("BBIM_Railing", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
|
||||
|
||||
class RoofData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"parameters": cls.parameters()}
|
||||
|
||||
@classmethod
|
||||
def parameters(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
parameters = psets.get("BBIM_Roof", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import gpu
|
||||
import bgl
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from math import pi, degrees, sin, cos, radians
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector, Matrix
|
||||
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
class ProfileDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def __call__(self, context):
|
||||
obj = context.active_object
|
||||
if obj.mode != "EDIT":
|
||||
return
|
||||
|
||||
bgl.glLineWidth(2)
|
||||
bgl.glPointSize(6)
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
|
||||
all_vertices = []
|
||||
error_vertices = []
|
||||
selected_vertices = []
|
||||
unselected_vertices = []
|
||||
special_vertices = []
|
||||
special_vertex_indices = {}
|
||||
selected_edges = []
|
||||
unselected_edges = []
|
||||
special_edges = [] # edges that have a circle or an arc associated with them
|
||||
|
||||
arc_groups = []
|
||||
circle_groups = []
|
||||
for i, group in enumerate(obj.vertex_groups):
|
||||
if "IFCARCINDEX" in group.name:
|
||||
arc_groups.append(i)
|
||||
elif "IFCCIRCLE" in group.name:
|
||||
circle_groups.append(i)
|
||||
|
||||
arcs = {}
|
||||
circles = {}
|
||||
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
# https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess
|
||||
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive
|
||||
deform_layer = bm.verts.layers.deform.active
|
||||
|
||||
for vertex in bm.verts:
|
||||
co = tuple(obj.matrix_world @ vertex.co)
|
||||
all_vertices.append(co)
|
||||
if vertex.hide:
|
||||
continue
|
||||
|
||||
is_arc = False
|
||||
for group_index in arc_groups:
|
||||
if group_index in vertex[deform_layer]:
|
||||
is_arc = True
|
||||
break
|
||||
if is_arc:
|
||||
arcs.setdefault(group_index, []).append(vertex)
|
||||
special_vertex_indices[vertex.index] = group_index
|
||||
|
||||
is_circle = False
|
||||
for group_index in circle_groups:
|
||||
if group_index in vertex[deform_layer]:
|
||||
is_circle = True
|
||||
break
|
||||
if is_circle:
|
||||
circles.setdefault(group_index, []).append(vertex)
|
||||
special_vertex_indices[vertex.index] = group_index
|
||||
|
||||
if vertex.select:
|
||||
selected_vertices.append(co)
|
||||
else:
|
||||
if len(vertex.link_edges) > 1 and is_circle:
|
||||
error_vertices.append(co)
|
||||
elif is_circle:
|
||||
special_vertices.append(co)
|
||||
elif len(vertex.link_edges) != 2:
|
||||
error_vertices.append(co)
|
||||
elif is_arc:
|
||||
special_vertices.append(co)
|
||||
else:
|
||||
unselected_vertices.append(co)
|
||||
|
||||
for edge in bm.edges:
|
||||
edge_indices = [v.index for v in edge.verts]
|
||||
if edge.hide:
|
||||
continue
|
||||
if edge.select:
|
||||
selected_edges.append(edge_indices)
|
||||
else:
|
||||
i1, i2 = edge.verts[0].index, edge.verts[1].index
|
||||
if i1 in special_vertex_indices and special_vertex_indices[i1] == special_vertex_indices.get(i2, None):
|
||||
special_edges.append(edge_indices)
|
||||
else:
|
||||
unselected_edges.append(edge_indices)
|
||||
indices = [[v.index for v in e.verts] for e in bm.edges]
|
||||
|
||||
white = (1, 1, 1, 1)
|
||||
green = (0.545, 0.863, 0, 1)
|
||||
red = (1, 0.2, 0.322, 1)
|
||||
blue = (0.157, 0.565, 1, 1)
|
||||
grey = (0.2, 0.2, 0.2, 1)
|
||||
|
||||
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
|
||||
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=unselected_edges)
|
||||
self.shader.bind()
|
||||
self.shader.uniform_float("color", white)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=selected_edges)
|
||||
self.shader.uniform_float("color", green)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=special_edges)
|
||||
self.shader.uniform_float("color", grey)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": unselected_vertices})
|
||||
self.shader.uniform_float("color", white)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": error_vertices})
|
||||
self.shader.uniform_float("color", red)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": special_vertices})
|
||||
self.shader.uniform_float("color", blue)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": selected_vertices})
|
||||
self.shader.uniform_float("color", green)
|
||||
batch.draw(self.shader)
|
||||
|
||||
# Draw arcs
|
||||
|
||||
arc_centroids = []
|
||||
arc_segments = []
|
||||
for arc in arcs.values():
|
||||
if len(arc) != 3:
|
||||
continue
|
||||
sorted_arc = [None, None, None]
|
||||
for v1 in arc:
|
||||
connections = 0
|
||||
for link_edge in v1.link_edges:
|
||||
v2 = link_edge.other_vert(v1)
|
||||
if v2 in arc:
|
||||
connections += 1
|
||||
if connections == 2: # Midpoint
|
||||
sorted_arc[1] = v1
|
||||
else:
|
||||
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
|
||||
points = [tuple(obj.matrix_world @ v.co) for v in sorted_arc]
|
||||
centroid = tool.Cad.get_center_of_arc(points)
|
||||
if centroid:
|
||||
arc_centroids.append(tuple(centroid))
|
||||
arc_segments.append(tool.Cad.create_arc_segments(pts=points, num_verts=17, make_edges=True))
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": arc_centroids})
|
||||
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
|
||||
batch.draw(self.shader)
|
||||
|
||||
for verts, edges in arc_segments:
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
||||
self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
|
||||
batch.draw(self.shader)
|
||||
|
||||
# Draw circles
|
||||
|
||||
circle_centroids = []
|
||||
circle_segments = []
|
||||
for circle in circles.values():
|
||||
if len(circle) != 2:
|
||||
continue
|
||||
p1 = obj.matrix_world @ circle[0].co
|
||||
p2 = obj.matrix_world @ circle[1].co
|
||||
radius = (p2 - p1).length / 2
|
||||
centroid = p1.lerp(p2, 0.5)
|
||||
circle_centroids.append(tuple(centroid))
|
||||
segments = self.create_circle_segments(360, 20, radius)
|
||||
matrix = obj.matrix_world.copy()
|
||||
matrix.col[3] = centroid.to_4d()
|
||||
segments = [[list(matrix @ Vector(v)) for v in segments[0]], segments[1]]
|
||||
circle_segments.append(segments)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": circle_centroids})
|
||||
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
|
||||
batch.draw(self.shader)
|
||||
|
||||
for verts, edges in circle_segments:
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
||||
self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
|
||||
batch.draw(self.shader)
|
||||
|
||||
def create_matrix(self, p, x, y, z):
|
||||
return Matrix([x, y, z, p]).to_4x4().transposed()
|
||||
|
||||
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
|
||||
# This function is taken from Sverchok, licensed under GPL v2-or-later.
|
||||
# This is a combination of the make_verts and make_edges function.
|
||||
def create_circle_segments(self, Angle, Vertices, Radius):
|
||||
if Angle < 360:
|
||||
theta = Angle / (Vertices - 1)
|
||||
else:
|
||||
theta = Angle / Vertices
|
||||
listVertX = []
|
||||
listVertY = []
|
||||
for i in range(Vertices):
|
||||
listVertX.append(Radius * cos(radians(theta * i)))
|
||||
listVertY.append(Radius * sin(radians(theta * i)))
|
||||
|
||||
if Angle < 360 and self.mode_ == 0:
|
||||
sigma = radians(Angle)
|
||||
listVertX[-1] = Radius * cos(sigma)
|
||||
listVertY[-1] = Radius * sin(sigma)
|
||||
elif Angle < 360 and self.mode_ == 1:
|
||||
listVertX.append(0.0)
|
||||
listVertY.append(0.0)
|
||||
|
||||
points = list((x, y, 0) for x, y in zip(listVertX, listVertY))
|
||||
|
||||
listEdg = [(i, i + 1) for i in range(Vertices - 1)]
|
||||
|
||||
if Angle < 360 and self.mode_ == 1:
|
||||
listEdg.append((0, Vertices))
|
||||
listEdg.append((Vertices - 1, Vertices))
|
||||
else:
|
||||
listEdg.append((Vertices - 1, 0))
|
||||
|
||||
return points, listEdg
|
||||
@@ -30,7 +30,6 @@ import blenderbim.core.geometry as core
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.window import create_bm_window, create_bm_box, update_simple_openings
|
||||
from blenderbim.bim.module.model.helper import replace_ifc_representation_for_object
|
||||
|
||||
from mathutils import Vector, Matrix
|
||||
from pprint import pprint
|
||||
@@ -80,20 +79,20 @@ def update_door_modifier_representation(context):
|
||||
elevation_representation = ifcopenshell.api.run(
|
||||
"geometry.add_door_representation", ifc_file, **representation_data
|
||||
)
|
||||
replace_ifc_representation_for_object(ifc_file, profile, obj, elevation_representation)
|
||||
tool.Model.replace_object_ifc_representation(profile, obj, elevation_representation)
|
||||
|
||||
# MODEL_VIEW representation
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
representation_data["context"] = body
|
||||
model_representation = ifcopenshell.api.run("geometry.add_door_representation", ifc_file, **representation_data)
|
||||
replace_ifc_representation_for_object(ifc_file, body, obj, model_representation)
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
|
||||
# PLAN_VIEW representation
|
||||
plan = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "Body", "PLAN_VIEW")
|
||||
if plan:
|
||||
representation_data["context"] = plan
|
||||
plan_representation = ifcopenshell.api.run("geometry.add_door_representation", ifc_file, **representation_data)
|
||||
replace_ifc_representation_for_object(ifc_file, plan, obj, plan_representation)
|
||||
tool.Model.replace_object_ifc_representation(plan, obj, plan_representation)
|
||||
|
||||
# adding switch representation at the end instead of changing order of representations
|
||||
# to prevent #2744
|
||||
@@ -107,11 +106,19 @@ def update_door_modifier_representation(context):
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
# type attributes
|
||||
element.OperationType = props.door_type
|
||||
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.OverallWidth = props.overall_width
|
||||
occurence.OverallHeight = props.overall_height
|
||||
|
||||
update_simple_openings(element, props.overall_width, props.overall_height)
|
||||
|
||||
|
||||
# TODO: move it out to tools
|
||||
def bm_sort_out_geom(geom_data):
|
||||
geom_dict = {"verts": [], "edges": [], "faces": []}
|
||||
|
||||
@@ -433,12 +440,12 @@ def update_door_modifier_bmesh(context):
|
||||
obj.data.update()
|
||||
|
||||
|
||||
class BIM_OT_add_door(Operator):
|
||||
class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_door"
|
||||
bl_label = "Door"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file:
|
||||
self.report({"ERROR"}, "You need to start IFC project first to create a door.")
|
||||
@@ -453,7 +460,6 @@ class BIM_OT_add_door(Operator):
|
||||
mesh = bpy.data.meshes.new("IfcDoor")
|
||||
obj = bpy.data.objects.new("IfcDoor", mesh)
|
||||
obj.location = spawn_location
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcDoor", should_add_representation=False
|
||||
)
|
||||
@@ -493,12 +499,9 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
door_data["lining_properties"] = lining_props
|
||||
door_data["panel_properties"] = panel_props
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets.get("BBIM_Door", None)
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
|
||||
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Door")
|
||||
|
||||
ifcopenshell.api.run(
|
||||
@@ -519,8 +522,7 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
data = json.loads(psets["BBIM_Door"]["Data"])
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
|
||||
props = obj.BIMDoorProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
@@ -551,8 +553,6 @@ class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMDoorProperties
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets["BBIM_Door"]
|
||||
door_data = props.get_general_kwargs()
|
||||
lining_props = props.get_lining_kwargs()
|
||||
panel_props = props.get_panel_kwargs()
|
||||
@@ -564,7 +564,7 @@ class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
update_door_modifier_representation(context)
|
||||
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
|
||||
door_data = json.dumps(door_data, default=list)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": door_data})
|
||||
return {"FINISHED"}
|
||||
@@ -579,8 +579,7 @@ class EnableEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
props = obj.BIMDoorProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
pset = ifcopenshell.util.element.get_psets(element)
|
||||
data = json.loads(pset["BBIM_Door"]["Data"])
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
|
||||
data.update(data.pop("lining_properties"))
|
||||
data.update(data.pop("panel_properties"))
|
||||
|
||||
@@ -609,8 +608,7 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
obj.BIMDoorProperties.is_editing = -1
|
||||
|
||||
pset = ifcopenshell.util.element.get_psets(element)
|
||||
pset = tool.Ifc.get().by_id(pset["BBIM_Door"]["id"])
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.door_added_previously = True
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 @Andrej730
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.core.geometry as core
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def get_ifc_context_or_create(ifc_file, context_type, context_identifier, target_view):
|
||||
ifc_context = ifcopenshell.util.representation.get_context(ifc_file, context_type, context_identifier, target_view)
|
||||
if not ifc_context:
|
||||
parent_context = ifcopenshell.util.representation.get_context(ifc_file, context_type)
|
||||
ifc_context = ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
ifc_file,
|
||||
context_type=context_type,
|
||||
context_identifier=context_identifier,
|
||||
target_view=target_view,
|
||||
parent=parent_context,
|
||||
)
|
||||
return ifc_context
|
||||
|
||||
|
||||
def replace_ifc_representation_for_object(ifc_file, ifc_context, obj, new_representation):
|
||||
ifc_element = tool.Ifc.get_entity(obj)
|
||||
old_representation = ifcopenshell.util.representation.get_representation(
|
||||
ifc_element, ifc_context.ContextType, ifc_context.ContextIdentifier, ifc_context.TargetView
|
||||
)
|
||||
|
||||
if old_representation:
|
||||
old_representation = tool.Geometry.resolve_mapped_representation(old_representation)
|
||||
for inverse in ifc_file.get_inverse(old_representation):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
|
||||
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=old_representation)
|
||||
else:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", ifc_file, product=ifc_element, representation=new_representation
|
||||
)
|
||||
core.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class MepGenerator:
|
||||
def __init__(self, relating_type):
|
||||
self.relating_type = relating_type
|
||||
|
||||
def generate(self, link_to_scene=True):
|
||||
def generate(self):
|
||||
self.file = tool.Ifc.get()
|
||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
|
||||
@@ -55,15 +55,15 @@ class MepGenerator:
|
||||
self.height = dimensions.get("NominalHeight")
|
||||
self.length = 1
|
||||
|
||||
return self.derive_from_cursor(link_to_scene=link_to_scene)
|
||||
return self.derive_from_cursor()
|
||||
elif self.relating_type.is_a("IfcPipeSegmentType"):
|
||||
pass
|
||||
|
||||
def derive_from_cursor(self, link_to_scene):
|
||||
def derive_from_cursor(self):
|
||||
self.location = bpy.context.scene.cursor.location
|
||||
return self.create_rectangle_segment(link_to_scene)
|
||||
return self.create_rectangle_segment()
|
||||
|
||||
def create_rectangle_segment(self, link_to_scene):
|
||||
def create_rectangle_segment(self):
|
||||
verts = [
|
||||
Vector((-self.width / 2, self.height / 2, 0)),
|
||||
Vector((-self.width / 2, -self.height / 2, 0)),
|
||||
@@ -89,11 +89,11 @@ class MepGenerator:
|
||||
ifc_class = ifc_classes[0]
|
||||
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
||||
if link_to_scene:
|
||||
obj.location = self.location
|
||||
obj.rotation_euler[0] = math.pi / 2
|
||||
obj.rotation_euler[2] = math.pi / 2
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
obj.location = self.location
|
||||
obj.rotation_euler[0] = math.pi / 2
|
||||
obj.rotation_euler[2] = math.pi / 2
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
bpy.ops.bim.assign_class(
|
||||
obj=obj.name,
|
||||
|
||||
@@ -53,38 +53,47 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
|
||||
class FilledOpeningGenerator:
|
||||
def generate(self, filling_obj, voided_obj):
|
||||
def generate(self, filling_obj, voided_obj, target=None):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
filling = tool.Ifc.get_entity(filling_obj)
|
||||
element = tool.Ifc.get_entity(voided_obj)
|
||||
|
||||
if not voided_obj or not filling_obj:
|
||||
return
|
||||
|
||||
element = tool.Ifc.get_entity(voided_obj)
|
||||
target = bpy.context.scene.cursor.location
|
||||
raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.01)
|
||||
if not raycast[0]:
|
||||
target = filling_obj.matrix_world.col[3].to_3d().copy()
|
||||
raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.5)
|
||||
if filling.FillsVoids:
|
||||
ifcopenshell.api.run(
|
||||
"void.remove_opening", tool.Ifc.get(), opening=filling.FillsVoids[0].RelatingOpeningElement
|
||||
)
|
||||
|
||||
if target is None:
|
||||
target = bpy.context.scene.cursor.location
|
||||
|
||||
# Sometimes, the voided_obj may be an aggregate, which won't have any representation.
|
||||
if voided_obj.data:
|
||||
raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.01)
|
||||
if not raycast[0]:
|
||||
return
|
||||
target = filling_obj.matrix_world.col[3].to_3d().copy()
|
||||
raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.5)
|
||||
if not raycast[0]:
|
||||
return
|
||||
|
||||
# In this prototype, we assume openings are only added to axis-based elements
|
||||
layers = tool.Model.get_material_layer_parameters(element)
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
# In this prototype, we assume openings are only added to axis-based elements
|
||||
layers = tool.Model.get_material_layer_parameters(element)
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
new_matrix.col[3] = tool.Cad.point_on_edge(target, axis).to_4d()
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
new_matrix.col[3] = tool.Cad.point_on_edge(target, axis).to_4d()
|
||||
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix[2][3] = voided_obj.matrix_world[2][3]
|
||||
else:
|
||||
new_matrix[2][3] = voided_obj.matrix_world[2][3] + (props.rl2 * unit_scale)
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix[2][3] = voided_obj.matrix_world[2][3]
|
||||
else:
|
||||
new_matrix[2][3] = voided_obj.matrix_world[2][3] + (props.rl2 * unit_scale)
|
||||
|
||||
filling_obj.matrix_world = new_matrix
|
||||
bpy.context.view_layer.update()
|
||||
filling_obj.matrix_world = new_matrix
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
if tool.Ifc.is_moved(voided_obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=voided_obj)
|
||||
@@ -133,22 +142,29 @@ class FilledOpeningGenerator:
|
||||
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element)
|
||||
ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling)
|
||||
|
||||
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
voided_objs = [voided_obj]
|
||||
# Openings affect all subelements of an aggregate
|
||||
for subelement in ifcopenshell.util.element.get_decomposition(element):
|
||||
subobj = tool.Ifc.get_object(subelement)
|
||||
if subobj:
|
||||
voided_objs.append(subobj)
|
||||
|
||||
return {"FINISHED"}
|
||||
for voided_obj in voided_objs:
|
||||
if voided_obj.data:
|
||||
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
filling = settings["related_object"]
|
||||
if not filling.FillsVoids:
|
||||
if not getattr(filling, "FillsVoids", None):
|
||||
return
|
||||
|
||||
opening = filling.FillsVoids[0].RelatingOpeningElement
|
||||
@@ -223,10 +239,34 @@ class FilledOpeningGenerator:
|
||||
return shape_builder.get_representation(context, [extrusion])
|
||||
|
||||
x, y, z = filling_obj.dimensions
|
||||
opening_position = Vector([0.0, -0.1 / unit_scale, 0.0])
|
||||
opening_size = Vector([x, 0, z]) / unit_scale
|
||||
|
||||
# Windows and doors can have a casing that overlaps the wall
|
||||
# but shouldn't affect the size of the opening.
|
||||
# So we shouldn't use object dimensions in that case. More: #2784
|
||||
# Just keeping it for windows and doors for now to be safe
|
||||
x_redefined, z_redefined = False, False
|
||||
if filling.is_a() in ["IfcWindow", "IfcDoor"]:
|
||||
if filling.OverallWidth:
|
||||
opening_size.x = filling.OverallWidth
|
||||
x_redefined = True
|
||||
if filling.OverallHeight:
|
||||
opening_size.z = filling.OverallHeight
|
||||
z_redefined = True
|
||||
|
||||
# making sure if min_x or min_z != 0 to shift the opening accordingly
|
||||
# to prevent something like #2784
|
||||
if not x_redefined:
|
||||
opening_position.x = min(v[0] for v in filling_obj.bound_box)
|
||||
|
||||
if not z_redefined:
|
||||
opening_position.z = min(v[2] for v in filling_obj.bound_box)
|
||||
|
||||
extrusion = shape_builder.extrude(
|
||||
shape_builder.rectangle(size=Vector([x / unit_scale, 0.0, z / unit_scale])),
|
||||
shape_builder.rectangle(size=opening_size),
|
||||
magnitude=thickness / unit_scale,
|
||||
position=Vector([0.0, -0.1 / unit_scale, 0.0]),
|
||||
position=opening_position,
|
||||
extrusion_vector=Vector([0.0, 1.0, 0.0]),
|
||||
)
|
||||
|
||||
@@ -276,21 +316,28 @@ class RecalculateFill(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=obj.matrix_world
|
||||
)
|
||||
|
||||
decomposed_building_elements = set()
|
||||
for building_element in building_elements:
|
||||
decomposed_building_elements.add(building_element)
|
||||
decomposed_building_elements.update(ifcopenshell.util.element.get_decomposition(building_element))
|
||||
|
||||
for building_element in decomposed_building_elements:
|
||||
building_obj = tool.Ifc.get_object(building_element)
|
||||
body = ifcopenshell.util.representation.get_representation(
|
||||
building_element, "Model", "Body", "MODEL_VIEW"
|
||||
)
|
||||
if body:
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=building_obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
if building_obj and building_obj.data:
|
||||
body = ifcopenshell.util.representation.get_representation(
|
||||
building_element, "Model", "Body", "MODEL_VIEW"
|
||||
)
|
||||
if body:
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=building_obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -605,7 +652,14 @@ class ShowOpenings(Operator, tool.Ifc.Operator):
|
||||
continue
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
openings = tool.Model.load_openings(element, [r.RelatedOpeningElement for r in element.HasOpenings])
|
||||
openings = tool.Model.load_openings(
|
||||
element,
|
||||
[
|
||||
r.RelatedOpeningElement
|
||||
for r in element.HasOpenings
|
||||
if not tool.Ifc.get_object(r.RelatedOpeningElement)
|
||||
],
|
||||
)
|
||||
for opening in openings:
|
||||
new = props.openings.add()
|
||||
new.obj = opening
|
||||
@@ -665,18 +719,27 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
tool.Ifc.unlink(element=opening, obj=opening_obj)
|
||||
bpy.data.objects.remove(opening_obj)
|
||||
|
||||
decomposed_building_objs = set()
|
||||
for obj in building_objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
decomposed_building_objs.add(obj)
|
||||
for subelement in ifcopenshell.util.element.get_decomposition(tool.Ifc.get_entity(obj)):
|
||||
subobj = tool.Ifc.get_object(subelement)
|
||||
if subobj:
|
||||
decomposed_building_objs.add(subobj)
|
||||
|
||||
for obj in decomposed_building_objs:
|
||||
if obj.data:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_all_building_objects_of_similar_openings(self, opening):
|
||||
|
||||
@@ -31,11 +31,10 @@ import blenderbim.core.geometry
|
||||
from . import wall, slab, profile, mep
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.model.prop import store_cursor_position
|
||||
from blenderbim.bim.helper import draw_image_for_ifc_profile
|
||||
from mathutils import Vector, Matrix
|
||||
from bpy_extras.object_utils import AddObjectHelper
|
||||
from . import prop
|
||||
import json
|
||||
|
||||
|
||||
def select_and_activate_single_object(context, obj):
|
||||
@@ -69,7 +68,6 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
relating_type_id: bpy.props.IntProperty()
|
||||
from_invoke: bpy.props.BoolProperty(default=False)
|
||||
link_to_scene: bpy.props.BoolProperty(default=True)
|
||||
|
||||
def invoke(self, context, event):
|
||||
return self.execute(context)
|
||||
@@ -95,14 +93,14 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
material = ifcopenshell.util.element.get_material(relating_type)
|
||||
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
if profile.DumbProfileGenerator(relating_type).generate(link_to_scene=self.link_to_scene):
|
||||
if profile.DumbProfileGenerator(relating_type).generate():
|
||||
return {"FINISHED"}
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
if self.generate_layered_element(ifc_class, relating_type, link_to_scene=self.link_to_scene):
|
||||
if self.generate_layered_element(ifc_class, relating_type):
|
||||
select_and_activate_single_object(context, context.selected_objects[-1])
|
||||
return {"FINISHED"}
|
||||
if relating_type.is_a("IfcFlowSegmentType") and not relating_type.RepresentationMaps:
|
||||
if mep.MepGenerator(relating_type).generate(link_to_scene=self.link_to_scene):
|
||||
if mep.MepGenerator(relating_type).generate():
|
||||
return {"FINISHED"}
|
||||
|
||||
building_obj = None
|
||||
@@ -133,17 +131,27 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
mesh = bpy.data.meshes.new(name="Instance")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(relating_type, instance_class), mesh)
|
||||
if self.link_to_scene:
|
||||
obj.location = context.scene.cursor.location
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
|
||||
obj.location = context.scene.cursor.location
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
||||
if self.link_to_scene:
|
||||
# Update required as core.type.assign_type may change obj.data
|
||||
context.view_layer.update()
|
||||
|
||||
# Update required as core.type.assign_type may change obj.data
|
||||
context.view_layer.update()
|
||||
|
||||
# set occurences properties for the types defined with modifiers
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
pset_name = f"BBIM_{instance_class[3:]}"
|
||||
bbim_pset = ifcopenshell.util.element.get_psets(element).get(pset_name, None)
|
||||
if bbim_pset:
|
||||
bbim_prop_data = json.loads(bbim_pset["Data"])
|
||||
element.OverallWidth = bbim_prop_data["overall_width"]
|
||||
element.OverallHeight = bbim_prop_data["overall_height"]
|
||||
|
||||
if (
|
||||
building_obj
|
||||
@@ -153,7 +161,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
# TODO For now we are hardcoding windows and doors as a prototype
|
||||
bpy.ops.bim.add_filled_opening(voided_obj=building_obj.name, filling_obj=obj.name)
|
||||
elif self.link_to_scene:
|
||||
else:
|
||||
if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box])
|
||||
|
||||
@@ -175,7 +183,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
@staticmethod
|
||||
def generate_layered_element(ifc_class, relating_type, link_to_scene=True):
|
||||
def generate_layered_element(ifc_class, relating_type):
|
||||
layer_set_direction = None
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
||||
@@ -189,9 +197,9 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
|
||||
obj = None
|
||||
if layer_set_direction == "AXIS3":
|
||||
obj = slab.DumbSlabGenerator(relating_type).generate(link_to_scene=link_to_scene)
|
||||
obj = slab.DumbSlabGenerator(relating_type).generate()
|
||||
elif layer_set_direction == "AXIS2":
|
||||
obj = wall.DumbWallGenerator(relating_type).generate(link_to_scene=link_to_scene)
|
||||
obj = wall.DumbWallGenerator(relating_type).generate()
|
||||
else:
|
||||
pass # Dumb block generator? Eh? :)
|
||||
|
||||
@@ -214,66 +222,6 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisplayConstrTypes(bpy.types.Operator):
|
||||
bl_idname = "bim.display_constr_types"
|
||||
bl_label = "Browse Construction Types"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Display all available Construction Types to add new instances"
|
||||
|
||||
def invoke(self, context, event):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
props = context.scene.BIMModelProperties
|
||||
ifc_class = props.ifc_class
|
||||
constr_class_info = AuthoringData.constr_class_info(ifc_class)
|
||||
if constr_class_info is None or not constr_class_info.fully_loaded:
|
||||
AuthoringData.assetize_constr_class(ifc_class)
|
||||
bpy.ops.bim.display_constr_types_ui("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ReinvokeOperator(bpy.types.Operator):
|
||||
bl_idname = "bim.reinvoke_operator"
|
||||
bl_label = "Reinvoke Popup Operator"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Reinvoke a popup operator"
|
||||
operator: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
browser_state = context.scene.BIMModelProperties.constr_browser_state
|
||||
store_cursor_position(context, event, window=False)
|
||||
cursor_x, cursor_y = browser_state.cursor_x, browser_state.cursor_y
|
||||
window_x, window_y = browser_state.window_x, browser_state.window_y
|
||||
window = context.window
|
||||
operator = self.operator
|
||||
self.move_cursor_away(context, window)
|
||||
|
||||
def move_cursor_to_window():
|
||||
window.cursor_warp(window_x, window_y)
|
||||
|
||||
def run_operator(operator, *args, **kwargs):
|
||||
reduce(lambda x, arg: getattr(x, arg), operator.split("."), bpy.ops)(*args, **kwargs)
|
||||
|
||||
def reinvoke():
|
||||
run_operator(operator, "INVOKE_DEFAULT", reinvoked=True)
|
||||
window.cursor_warp(cursor_x, cursor_y)
|
||||
|
||||
bpy.app.timers.register(move_cursor_to_window, first_interval=browser_state.update_delay)
|
||||
bpy.app.timers.register(reinvoke, first_interval=3 * browser_state.update_delay)
|
||||
return {"FINISHED"}
|
||||
|
||||
def move_cursor_away(self, context, window): # closes current popup
|
||||
browser_state = context.scene.BIMModelProperties.constr_browser_state
|
||||
window.cursor_warp(browser_state.far_away_x, browser_state.far_away_y)
|
||||
|
||||
@staticmethod
|
||||
def run_operator(operator, *args, **kwargs):
|
||||
reduce(lambda x, arg: getattr(x, arg), operator.split("."), bpy.ops)(*args, **kwargs)
|
||||
|
||||
|
||||
class AlignProduct(bpy.types.Operator):
|
||||
bl_idname = "bim.align_product"
|
||||
bl_label = "Align Product"
|
||||
@@ -374,7 +322,7 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
profile = material.MaterialProfiles[0].Profile
|
||||
draw_image_for_ifc_profile(draw, profile, size)
|
||||
tool.Profile.draw_image_for_ifc_profile(draw, profile, size)
|
||||
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
|
||||
@@ -450,6 +398,64 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.mirror_elements"
|
||||
bl_label = "Mirror Elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Faux-mirrors the selected object by an active empty along a mirror plane."
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
# This is not a true mirror operation. In BIM, objects that are
|
||||
# mirrored are not the same type. (i.e. a mirrored asymmetric desk is a
|
||||
# completely different product). To preserve types, we calculate
|
||||
# bounding box centroids, and mirror the position of the object. The
|
||||
# mirrored object performs the necessary rotation to preserve the
|
||||
# mirrored intention, but never actually truly mirror anything (i.e.
|
||||
# scale = -1).
|
||||
|
||||
# Objects are mirrored along the YZ plane of the mirror object.
|
||||
# Mirrored objects have their relative local Y and Z axes preserved,
|
||||
# and the new X axis is calculated.
|
||||
|
||||
# In theory, untyped objects may be truly mirrored, but this is not yet
|
||||
# implemented.
|
||||
mirror = context.active_object
|
||||
reflection = Matrix()
|
||||
reflection[0][0] = -1
|
||||
|
||||
mirror.select_set(False)
|
||||
|
||||
bpy.ops.bim.override_object_duplicate_move(is_interactive=False)
|
||||
|
||||
for obj in context.selected_objects:
|
||||
if obj == mirror:
|
||||
continue
|
||||
|
||||
objmat = mirror.matrix_world.inverted() @ obj.matrix_world.copy()
|
||||
x, y, z = objmat.to_3x3().col
|
||||
centroid = Vector(obj.bound_box[0]).lerp(Vector(obj.bound_box[6]), 0.5)
|
||||
c = mirror.matrix_world.inverted() @ obj.matrix_world @ centroid
|
||||
newy = mirror.matrix_world.to_quaternion() @ (y @ reflection)
|
||||
newz = mirror.matrix_world.to_quaternion() @ (z @ reflection)
|
||||
newx = newy.cross(newz)
|
||||
newc = mirror.matrix_world @ (c @ reflection)
|
||||
|
||||
newmat = Matrix((newx.to_4d(), newy.to_4d(), newz.to_4d(), newc.to_4d())).transposed()
|
||||
newmat.col[3][0:3] = Vector(newmat.col[3][0:3]) - (newmat.to_quaternion() @ centroid)
|
||||
|
||||
obj.matrix_world = newmat
|
||||
|
||||
def copy_obj(self, obj):
|
||||
new = obj.copy()
|
||||
new.data = wall2.data.copy()
|
||||
wall1.users_collection[0].objects.link(wall2)
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2)
|
||||
|
||||
|
||||
def generate_box(usecase_path, ifc_file, settings):
|
||||
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
|
||||
if not box_context:
|
||||
@@ -472,7 +478,7 @@ def generate_box(usecase_path, ifc_file, settings):
|
||||
"geometry.assign_representation",
|
||||
ifc_file,
|
||||
should_run_listeners=False,
|
||||
**{"product": product, "representation": new_box}
|
||||
**{"product": product, "representation": new_box},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
import bpy
|
||||
import copy
|
||||
import math
|
||||
import bmesh
|
||||
import mathutils.geometry
|
||||
import ifcopenshell
|
||||
@@ -29,10 +28,10 @@ import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi, degrees, inf
|
||||
from mathutils import Vector, Matrix
|
||||
from mathutils import Vector, Matrix, Quaternion
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
|
||||
def element_listener(element, obj):
|
||||
@@ -48,16 +47,15 @@ def mode_callback(obj, data):
|
||||
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
||||
):
|
||||
return
|
||||
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
product = tool.Ifc.get().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbProfile":
|
||||
return
|
||||
if obj.mode == "EDIT":
|
||||
IfcStore.edited_objs.add(obj)
|
||||
tool.Ifc.edit(obj)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
bm.free()
|
||||
else:
|
||||
material_usage = ifcopenshell.util.element.get_material(product)
|
||||
x, y = obj.dimensions[0:2]
|
||||
@@ -96,9 +94,9 @@ class DumbProfileGenerator:
|
||||
self.relating_type = relating_type
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
def generate(self, link_to_scene=True):
|
||||
self.file = IfcStore.get_file()
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
def generate(self):
|
||||
self.file = tool.Ifc.get()
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
material = ifcopenshell.util.element.get_material(self.relating_type)
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
self.profile_set = material
|
||||
@@ -114,13 +112,13 @@ class DumbProfileGenerator:
|
||||
self.rotation = 0
|
||||
self.location = Vector((0, 0, 0))
|
||||
self.cardinal_point = int(bpy.context.scene.BIMModelProperties.cardinal_point)
|
||||
return self.derive_from_cursor(link_to_scene=link_to_scene)
|
||||
return self.derive_from_cursor()
|
||||
|
||||
def derive_from_cursor(self, link_to_scene):
|
||||
def derive_from_cursor(self):
|
||||
self.location = bpy.context.scene.cursor.location
|
||||
return self.create_profile(link_to_scene)
|
||||
return self.create_profile()
|
||||
|
||||
def create_profile(self, link_to_scene):
|
||||
def create_profile(self):
|
||||
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
|
||||
# Standard cases are deprecated, so let's cull them
|
||||
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
|
||||
@@ -132,10 +130,9 @@ class DumbProfileGenerator:
|
||||
if self.relating_type.is_a() in ["IfcBeamType", "IfcMemberType"]:
|
||||
matrix_world = Matrix.Rotation(pi / 2, 4, "Z") @ Matrix.Rotation(pi / 2, 4, "X") @ matrix_world
|
||||
matrix_world.col[3] = self.location.to_4d()
|
||||
if link_to_scene and self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
matrix_world[2][3] = self.collection_obj.location[2]
|
||||
if link_to_scene:
|
||||
self.collection.objects.link(obj)
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
@@ -190,8 +187,7 @@ class DumbProfileGenerator:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"})
|
||||
|
||||
if link_to_scene:
|
||||
obj.select_set(True)
|
||||
obj.select_set(True)
|
||||
|
||||
return obj
|
||||
|
||||
@@ -330,7 +326,7 @@ class DumbProfileJoiner:
|
||||
body = copy.deepcopy(axis1)
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
si_length = unit_scale * length
|
||||
end = profile1.matrix_world @ Vector((si_length, 0, 0))
|
||||
end = profile1.matrix_world @ Vector((0, 0, si_length))
|
||||
axis[1] = end
|
||||
body[1] = end
|
||||
self.recreate_profile(element1, profile1, axis, body)
|
||||
@@ -502,9 +498,7 @@ class DumbProfileJoiner:
|
||||
# Openings should move with the host overall ...
|
||||
# ... except their position should stay the same along the local Z axis of the wall
|
||||
for opening in [r.RelatedOpeningElement for r in element.HasOpenings]:
|
||||
percent = tool.Cad.edge_percent(
|
||||
self.body[0], (previous_origin, (previous_matrix @ Vector((0, 0, 1))))
|
||||
)
|
||||
percent = tool.Cad.edge_percent(self.body[0], (previous_origin, (previous_matrix @ Vector((0, 0, 1)))))
|
||||
is_z_offset_increased = True if percent < 0 else False
|
||||
|
||||
change_in_z = (self.body[0] - previous_origin).length / self.unit_scale
|
||||
@@ -915,3 +909,121 @@ class Rotate90(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.context.view_layer.update()
|
||||
DumbProfileRecalculator().recalculate(objs)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_extrusion_axis"
|
||||
bl_label = "Enable Editing Extrusion Axis"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
axis = ifcopenshell.util.representation.get_representation(element, "Model", "Axis", "GRAPH_VIEW")
|
||||
if axis:
|
||||
position = obj.matrix_world.copy()
|
||||
tool.Model.import_axis(axis.Items[0], obj=obj)
|
||||
else:
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
extrusion = tool.Model.get_extrusion(body)
|
||||
|
||||
if extrusion.Position:
|
||||
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
||||
position[0][3] *= self.unit_scale
|
||||
position[1][3] *= self.unit_scale
|
||||
position[2][3] *= self.unit_scale
|
||||
else:
|
||||
position = Matrix()
|
||||
|
||||
direction = Vector(extrusion.ExtrudedDirection.DirectionRatios).normalized()
|
||||
tool.Model.import_axis([Vector((0, 0, 0)), direction * extrusion.Depth], obj=obj, position=position)
|
||||
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
ProfileDecorator.install(context)
|
||||
if not bpy.app.background:
|
||||
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_extrusion_axis"
|
||||
bl_label = "Disable Editing Extrusion Axis"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_extrusion_axis"
|
||||
bl_label = "Edit Extrusion Axis"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
start = obj.matrix_world @ obj.data.vertices[0].co.copy()
|
||||
end = obj.matrix_world @ obj.data.vertices[1].co.copy()
|
||||
depth = (end - start).length
|
||||
z_axis = (end - start).normalized()
|
||||
y_axis = Vector((0, 0, 1))
|
||||
x_axis = y_axis.cross(z_axis).normalized()
|
||||
y_axis = z_axis.cross(x_axis).normalized()
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
matrix = Matrix(
|
||||
(
|
||||
[x_axis[0], y_axis[0], z_axis[0], start.x],
|
||||
[x_axis[1], y_axis[1], z_axis[1], start.y],
|
||||
[x_axis[2], y_axis[2], z_axis[2], start.z],
|
||||
[0, 0, 0, 1],
|
||||
)
|
||||
)
|
||||
|
||||
obj.matrix_world = matrix
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
joiner = DumbProfileJoiner()
|
||||
joiner.set_depth(obj, depth / self.unit_scale)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -55,14 +55,6 @@ def get_type_predefined_type(self, context):
|
||||
return AuthoringData.data["type_predefined_type"]
|
||||
|
||||
|
||||
def store_cursor_position(context, event, cursor=True, window=True):
|
||||
browser_state = context.scene.BIMModelProperties.constr_browser_state
|
||||
if cursor:
|
||||
browser_state.cursor_x, browser_state.cursor_y = event.mouse_x, event.mouse_y
|
||||
if window:
|
||||
browser_state.window_x, browser_state.window_y = event.mouse_x, event.mouse_y
|
||||
|
||||
|
||||
def update_icon_id(self, context, browser=False):
|
||||
if context == "lost_context" or (context.region is not None and context.region.type != "TOOL_HEADER"):
|
||||
ifc_class = self.ifc_class_browser if browser else self.ifc_class
|
||||
@@ -244,12 +236,15 @@ class BIMModelProperties(PropertyGroup):
|
||||
("WINDOW", "Window", ""),
|
||||
("DOOR", "Door", ""),
|
||||
("STAIR", "Stair", ""),
|
||||
("RAILING", "Railing", ""),
|
||||
("ROOF", "Roof", ""),
|
||||
),
|
||||
name="Type Template",
|
||||
default="MESH",
|
||||
)
|
||||
type_class: bpy.props.EnumProperty(items=get_type_class, name="IFC Class", update=update_type_class)
|
||||
type_predefined_type: bpy.props.EnumProperty(items=get_type_predefined_type, name="Predefined Type", default=None)
|
||||
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
|
||||
|
||||
|
||||
class BIMArrayProperties(PropertyGroup):
|
||||
@@ -264,6 +259,7 @@ class BIMStairProperties(PropertyGroup):
|
||||
stair_types = (
|
||||
("CONCRETE", "Concrete", ""),
|
||||
("WOOD/STEEL", "Wood / Steel", ""),
|
||||
("GENERIC", "Generic", ""),
|
||||
)
|
||||
|
||||
stair_added_previously: bpy.props.BoolProperty(default=False)
|
||||
@@ -284,7 +280,6 @@ class BIMStairProperties(PropertyGroup):
|
||||
"width": self.width,
|
||||
"height": self.height,
|
||||
"number_of_treads": self.number_of_treads,
|
||||
"tread_depth": self.tread_depth,
|
||||
"tread_run": self.tread_run,
|
||||
}
|
||||
|
||||
@@ -294,11 +289,20 @@ class BIMStairProperties(PropertyGroup):
|
||||
"base_slab_depth": self.base_slab_depth,
|
||||
"top_slab_depth": self.top_slab_depth,
|
||||
"has_top_nib": self.has_top_nib,
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
)
|
||||
return stair_kwargs
|
||||
|
||||
elif self.stair_type == "WOOD/STEEL":
|
||||
stair_kwargs.update(
|
||||
{
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
)
|
||||
return stair_kwargs
|
||||
|
||||
elif self.stair_type == "GENERIC":
|
||||
return stair_kwargs
|
||||
|
||||
|
||||
@@ -541,3 +545,54 @@ class BIMDoorProperties(PropertyGroup):
|
||||
kwargs["frame_depth"] = self.frame_depth
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
class BIMRailingProperties(PropertyGroup):
|
||||
railing_types = (("FRAMELESS_PANEL", "FRAMELESS_PANEL", ""),)
|
||||
|
||||
railing_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
is_editing_path: bpy.props.BoolProperty(default=False)
|
||||
|
||||
railing_type: bpy.props.EnumProperty(name="Railing Type", items=railing_types, default="FRAMELESS_PANEL")
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0)
|
||||
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050)
|
||||
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050)
|
||||
|
||||
def get_general_kwargs(self):
|
||||
return {
|
||||
"railing_type": self.railing_type,
|
||||
"height": self.height,
|
||||
"thickness": self.thickness,
|
||||
"spacing": self.spacing,
|
||||
}
|
||||
|
||||
|
||||
class BIMRoofProperties(PropertyGroup):
|
||||
roof_types = (("HIP_ROOF", "HIP_ROOF", ""),)
|
||||
roof_generation_methods = (
|
||||
("HEIGHT", "HEIGHT", ""),
|
||||
("ANGLE", "ANGLE", ""),
|
||||
)
|
||||
|
||||
roof_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
is_editing_path: bpy.props.BoolProperty(default=False)
|
||||
|
||||
roof_type: bpy.props.EnumProperty(name="Roof Type", items=roof_types, default="HIP_ROOF")
|
||||
generation_method: bpy.props.EnumProperty(
|
||||
name="Roof Generation Method", items=roof_generation_methods, default="HEIGHT"
|
||||
)
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0)
|
||||
angle: bpy.props.FloatProperty(name="Slope Angle", default=10, description="In degrees")
|
||||
|
||||
def get_general_kwargs(self):
|
||||
kwargs = {
|
||||
"roof_type": self.roof_type,
|
||||
"generation_method": self.generation_method,
|
||||
}
|
||||
if self.generation_method == "HEIGHT":
|
||||
kwargs["height"] = self.height
|
||||
else:
|
||||
kwargs["angle"] = self.angle
|
||||
return kwargs
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 @Andrej730
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
import bmesh
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell.util.shape_builder import V
|
||||
import blenderbim
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry as core
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.door import bm_sort_out_geom
|
||||
from blenderbim.bim.module.model.data import RailingData, refresh
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
from mathutils import Vector, Matrix
|
||||
from pprint import pprint
|
||||
import json
|
||||
|
||||
# reference:
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailing.htm
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm
|
||||
|
||||
|
||||
def bm_split_edge_at_offset(edge, offset):
|
||||
v0, v1 = edge.verts
|
||||
|
||||
offset = offset / 2
|
||||
edge_len = (v0.co - v1.co).xy.length
|
||||
|
||||
split_output_0 = bmesh.utils.edge_split(edge, v0, offset / edge_len)
|
||||
split_output_1 = bmesh.utils.edge_split(edge, v1, offset / (edge_len - offset))
|
||||
new_geometry = bm_sort_out_geom(split_output_0 + split_output_1)
|
||||
return new_geometry
|
||||
|
||||
|
||||
def update_railing_modifier_ifc_data(context):
|
||||
"""should be called after new geometry settled
|
||||
since it's going to update ifc representation
|
||||
"""
|
||||
obj = context.active_object
|
||||
props = obj.BIMRailingProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# type attributes
|
||||
element.PredefinedType = "USERDEFINED"
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.ObjectType = props.railing_type
|
||||
|
||||
# update pset
|
||||
pset_common = tool.Pset.get_element_pset(element, "Pset_RailingCommon")
|
||||
if not pset_common:
|
||||
pset_common = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name="Pset_RailingCommon")
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
ifc_file,
|
||||
pset=pset_common,
|
||||
properties={
|
||||
"Height": props.height,
|
||||
},
|
||||
)
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
def update_bbim_railing_pset(element, railing_data):
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Railing")
|
||||
railing_data = json.dumps(railing_data, default=list)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": railing_data})
|
||||
|
||||
|
||||
def update_railing_modifier_bmesh(context):
|
||||
obj = context.object
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
if not RailingData.is_loaded:
|
||||
RailingData.load()
|
||||
path_data = RailingData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
# need to make sure we support edit mode
|
||||
# since users will probably be in edit mode when they'll be changing railing path
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
|
||||
# generating railing path
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
new_verts = [bm.verts.new(Vector(v) * si_conversion) for v in path_data["verts"]]
|
||||
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in path_data["edges"]]
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
|
||||
if props.is_editing_path:
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
return
|
||||
|
||||
# generating the entire railing
|
||||
height = props.height * si_conversion
|
||||
thickness = props.thickness * si_conversion
|
||||
spacing = props.spacing * si_conversion
|
||||
|
||||
# spacing
|
||||
# split each edge in 3 segments by 0.5 * spacing by x-y plane
|
||||
main_edges = bm.edges[:]
|
||||
for main_edge in main_edges:
|
||||
bm_split_edge_at_offset(main_edge, spacing)
|
||||
|
||||
# thickness
|
||||
# keep track of translated verts so we won't translate the same
|
||||
# vert twice
|
||||
edge_dissolving_verts = []
|
||||
for main_edge in main_edges:
|
||||
v0, v1 = main_edge.verts
|
||||
edge_dissolving_verts.extend([v0, v1])
|
||||
|
||||
edge_dir = ((v1.co - v0.co) * V(1, 1, 0)).normalized()
|
||||
ortho_vector = edge_dir.cross(V(0, 0, 1))
|
||||
|
||||
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=ortho_vector * (-thickness / 2), verts=extruded_verts)
|
||||
|
||||
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=ortho_vector * (thickness / 2), verts=extruded_verts)
|
||||
|
||||
# dissolve middle edge
|
||||
bmesh.ops.dissolve_edges(bm, edges=[main_edge])
|
||||
|
||||
# height
|
||||
extruded_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)["verts"]
|
||||
extrusion_vector = Vector((0, 0, 1)) * height
|
||||
bmesh.ops.translate(bm, vec=extrusion_vector, verts=extruded_verts)
|
||||
|
||||
# dissolve middle edges
|
||||
edges_to_dissolve = []
|
||||
verts_to_dissolve = []
|
||||
for v in edge_dissolving_verts:
|
||||
for e in v.link_edges:
|
||||
other_vert = e.other_vert(v)
|
||||
if other_vert in extruded_verts:
|
||||
edges_to_dissolve.append(e)
|
||||
verts_to_dissolve.append(other_vert)
|
||||
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
|
||||
bmesh.ops.dissolve_verts(bm, verts=verts_to_dissolve)
|
||||
|
||||
# to remove unnecessary verts in 0 spacing case
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
|
||||
def get_path_data(obj):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if obj.mode == "EDIT":
|
||||
# otherwise mesh may not contain all changes
|
||||
# added in edit mode
|
||||
obj.update_from_editmode()
|
||||
|
||||
mesh = obj.data
|
||||
path_data = dict()
|
||||
path_data["edges"] = [e.vertices[:] for e in mesh.edges]
|
||||
path_data["verts"] = [v.co / si_conversion for v in mesh.vertices]
|
||||
|
||||
if not path_data["edges"] or not path_data["verts"]:
|
||||
return None
|
||||
return path_data
|
||||
|
||||
|
||||
class BIM_OT_add_railing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_railing"
|
||||
bl_label = "Railing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file:
|
||||
self.report({"ERROR"}, "You need to start IFC project first to create a railing.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if context.object is not None:
|
||||
spawn_location = context.object.location.copy()
|
||||
context.object.select_set(False)
|
||||
else:
|
||||
spawn_location = bpy.context.scene.cursor.location.copy()
|
||||
|
||||
mesh = bpy.data.meshes.new("IfcRailing")
|
||||
obj = bpy.data.objects.new("IfcRailing", mesh)
|
||||
obj.location = spawn_location
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class="IfcRailing",
|
||||
should_add_representation=True,
|
||||
context=body_context,
|
||||
)
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = None
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
bpy.ops.bim.add_railing()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# UI operators
|
||||
class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_railing"
|
||||
bl_label = "Add Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
if element.is_a() not in ("IfcRailing", "IfcRailingType"):
|
||||
self.report({"ERROR"}, "Object has to be IfcRailing/IfcRailingType type to add a railing.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.railing_added_previously:
|
||||
skip_props = ("is_editing", "railing_type", "railing_added_previously")
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
if not path_data:
|
||||
path_data = {
|
||||
"edges": [[0, 1], [1, 2]],
|
||||
"verts": [
|
||||
Vector([-1.0, 0.0, 0.0]) / si_conversion,
|
||||
Vector([0.0, 0.0, 0.0]) / si_conversion,
|
||||
Vector([1.0, 0.0, 0.0]) / si_conversion,
|
||||
],
|
||||
}
|
||||
railing_data["path_data"] = path_data
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
refresh()
|
||||
update_railing_modifier_bmesh(context)
|
||||
update_railing_modifier_ifc_data(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_railing"
|
||||
bl_label = "Enable Editing Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRailingProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
|
||||
data["path_data"] = json.dumps(data["path_data"])
|
||||
|
||||
# required since we could load pset from .ifc and BIMRailingProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = ("is_editing", "railing_type", "railing_added_previously")
|
||||
skip_props += tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_railing"
|
||||
bl_label = "Cancel editing Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
|
||||
props = obj.BIMRailingProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = -1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FinishEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_railing"
|
||||
bl_label = "Finish editing railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
if not RailingData.is_loaded:
|
||||
RailingData.load()
|
||||
path_data = RailingData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data["path_data"] = path_data
|
||||
props.is_editing = -1
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
update_railing_modifier_ifc_data(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_railing_path"
|
||||
bl_label = "Enable Editing Railing Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
props.is_editing_path = True
|
||||
update_railing_modifier_bmesh(context)
|
||||
|
||||
if bpy.context.object.mode != "EDIT":
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
|
||||
ProfileDecorator.install(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CancelEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_railing_path"
|
||||
bl_label = "Cancel Editing Railing Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
ProfileDecorator.uninstall()
|
||||
props.is_editing_path = False
|
||||
|
||||
update_railing_modifier_bmesh(context)
|
||||
if bpy.context.object.mode == "EDIT":
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_railing_path"
|
||||
bl_label = "Finish Editing Railing Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
railing_data["path_data"] = path_data
|
||||
ProfileDecorator.uninstall()
|
||||
props.is_editing_path = False
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
refresh() # RailingData has to be updated before run update_railing_modifier_bmesh
|
||||
update_railing_modifier_bmesh(context)
|
||||
if bpy.context.object.mode == "EDIT":
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
update_railing_modifier_ifc_data(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_railing"
|
||||
bl_label = "Remove Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRailingProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
obj.BIMRailingProperties.is_editing = -1
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.railing_added_previously = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def add_object_button(self, context):
|
||||
self.layout.operator(BIM_OT_add_railing.bl_idname, icon="PLUGIN")
|
||||
@@ -0,0 +1,467 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>, @Andrej730
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
import bmesh
|
||||
|
||||
import ifcopenshell
|
||||
import blenderbim
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
from blenderbim.bim.module.model.door import bm_sort_out_geom
|
||||
from blenderbim.bim.module.model.data import RoofData, refresh
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
import json
|
||||
from math import tan, radians
|
||||
from mathutils import Vector, Matrix
|
||||
from bpypolyskel import bpypolyskel
|
||||
import shapely
|
||||
|
||||
# reference:
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoof.htm
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
|
||||
|
||||
|
||||
class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.generate_hipped_roof"
|
||||
bl_label = "Generate Hipped Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
mode: bpy.props.StringProperty(default="ANGLE")
|
||||
height: bpy.props.FloatProperty(default=1)
|
||||
angle: bpy.props.FloatProperty(default=10)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.context.active_object
|
||||
if not obj:
|
||||
self.report({"ERROR"}, "Need to select some object first.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
# argument values are the defaults for `bpy.ops.mesh.dissolve_limited`
|
||||
bmesh.ops.dissolve_limit(
|
||||
bm, angle_limit=0.0872665, use_dissolve_boundaries=False, delimit={"NORMAL"}, edges=bm.edges[:], verts=bm.verts[:]
|
||||
)
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
generate_hipped_roof(obj, self.mode, self.height, self.angle)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def generate_hipped_roof(obj, mode="ANGLE", height=1.0, angle=10):
|
||||
boundary_lines = []
|
||||
|
||||
for edge in obj.data.edges:
|
||||
boundary_lines.append(
|
||||
shapely.LineString([obj.data.vertices[edge.vertices[0]].co, obj.data.vertices[edge.vertices[1]].co])
|
||||
)
|
||||
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
|
||||
|
||||
roof_polygon = None
|
||||
biggest_area = 0
|
||||
for polygon in closed_polygons.geoms:
|
||||
area = polygon.area
|
||||
if area > biggest_area:
|
||||
roof_polygon = polygon
|
||||
biggest_area = area
|
||||
|
||||
roof_polygon = shapely.force_3d(roof_polygon)
|
||||
|
||||
if not shapely.is_ccw(roof_polygon):
|
||||
roof_polygon = roof_polygon.reverse()
|
||||
|
||||
# Define vertices for the base footprint of the building at height 0.0
|
||||
# counterclockwise order
|
||||
verts = [Vector(v) for v in roof_polygon.exterior.coords[0:-1]]
|
||||
total_exterior_verts = len(verts)
|
||||
next_index = total_exterior_verts
|
||||
|
||||
inner_loops = None
|
||||
for interior in roof_polygon.interiors:
|
||||
if inner_loops is None:
|
||||
inner_loops = []
|
||||
loop = interior.coords[0:-1]
|
||||
total_verts = len(loop)
|
||||
verts.extend([Vector(v) for v in loop])
|
||||
inner_loops.append((next_index, total_verts))
|
||||
next_index += total_verts
|
||||
|
||||
unit_vectors = None # we have no unit vectors, let them computed by polygonize()
|
||||
start_exterior_index = 0
|
||||
|
||||
faces = []
|
||||
|
||||
if mode == "HEIGHT":
|
||||
height = height
|
||||
angle = 0.0
|
||||
else:
|
||||
angle = tan(radians(round(angle, 4)))
|
||||
height = 0.0
|
||||
|
||||
faces = bpypolyskel.polygonize(
|
||||
verts, start_exterior_index, total_exterior_verts, inner_loops, height, angle, faces, unit_vectors
|
||||
)
|
||||
|
||||
edges = []
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
new_verts = [bm.verts.new(v) for v in verts]
|
||||
new_edges = [bm.edges.new([new_verts[vi] for vi in edge]) for edge in edges]
|
||||
new_faces = [bm.faces.new([new_verts[vi] for vi in face]) for face in faces]
|
||||
|
||||
extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
|
||||
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
|
||||
def bm_get_indices(sequence):
|
||||
return [i.index for i in sequence]
|
||||
|
||||
|
||||
def update_roof_modifier_ifc_data(context):
|
||||
"""should be called after new geometry settled
|
||||
since it's going to update ifc representation
|
||||
"""
|
||||
obj = context.active_object
|
||||
props = obj.BIMRoofProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
# type attributes
|
||||
element.PredefinedType = props.roof_type
|
||||
# occurences attributes
|
||||
# occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
|
||||
# TODO: add Qto_RoofBaseQuantities, need to calculate GrossArea, NetArea, ProjectedArea
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/Qto_RoofBaseQuantities.htm
|
||||
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
def update_bbim_roof_pset(element, roof_data):
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Roof")
|
||||
roof_data = json.dumps(roof_data, default=list)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": roof_data})
|
||||
|
||||
|
||||
def update_roof_modifier_bmesh(context):
|
||||
obj = context.object
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
if not RoofData.is_loaded:
|
||||
RoofData.load()
|
||||
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
# need to make sure we support edit mode
|
||||
# since users will probably be in edit mode when they'll be changing roof path
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
|
||||
# generating roof path
|
||||
new_verts = [bm.verts.new(Vector(v) * si_conversion) for v in path_data["verts"]]
|
||||
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in path_data["edges"]]
|
||||
|
||||
if props.is_editing_path:
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
return
|
||||
|
||||
# apply dissolve limit seems to get more correct results with `generate_hipped_roof`
|
||||
# argument values are the defaults for `bpy.ops.mesh.dissolve_limited`
|
||||
bmesh.ops.dissolve_limit(
|
||||
bm, angle_limit=0.0872665, use_dissolve_boundaries=False, delimit={"NORMAL"}, edges=bm.edges[:], verts=bm.verts[:]
|
||||
)
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
height = props.height * si_conversion
|
||||
angle = props.angle * si_conversion
|
||||
generation_method = props.generation_method
|
||||
generate_hipped_roof(obj, generation_method, height, angle)
|
||||
|
||||
|
||||
def get_path_data(obj):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
if obj.mode == "EDIT":
|
||||
# otherwise mesh may not contain all changes
|
||||
# added in edit mode
|
||||
obj.update_from_editmode()
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
|
||||
# remove internal edges and faces
|
||||
# adding missing faces so we could rely on `e.is_boundary` later
|
||||
bmesh.ops.contextual_create(bm, geom=bm.edges[:])
|
||||
|
||||
edges_to_dissolve = [e for e in bm.edges if not e.is_boundary]
|
||||
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
|
||||
bmesh.ops.delete(bm, geom=bm.faces[:], context="FACES_ONLY")
|
||||
|
||||
path_data = dict()
|
||||
path_data["edges"] = [bm_get_indices(e.verts) for e in bm.edges]
|
||||
path_data["verts"] = [v.co / si_conversion for v in bm.verts]
|
||||
|
||||
if not path_data["edges"] or not path_data["verts"]:
|
||||
return None
|
||||
return path_data
|
||||
|
||||
|
||||
class BIM_OT_add_roof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_roof"
|
||||
bl_label = "Roof"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file:
|
||||
self.report({"ERROR"}, "You need to start IFC project first to create a roof.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if context.object is not None:
|
||||
spawn_location = context.object.location.copy()
|
||||
context.object.select_set(False)
|
||||
else:
|
||||
spawn_location = bpy.context.scene.cursor.location.copy()
|
||||
|
||||
mesh = bpy.data.meshes.new("IfcRoof")
|
||||
obj = bpy.data.objects.new("IfcRoof", mesh)
|
||||
obj.location = spawn_location
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class="IfcRoof",
|
||||
should_add_representation=True,
|
||||
context=body_context,
|
||||
)
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = None
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
bpy.ops.bim.add_roof()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# UI operators
|
||||
class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_roof"
|
||||
bl_label = "Add Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRoofProperties
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
if element.is_a() not in ("IfcRoof", "IfcRoofType"):
|
||||
self.report({"ERROR"}, "Object has to be IfcRoof/IfcRoofType type to add a roof.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.roof_added_previously:
|
||||
skip_props = ("is_editing", "roof_type", "roof_added_previously", "generation_method")
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
if not path_data:
|
||||
path_data = {
|
||||
"edges": [[0, 1], [1, 2], [2, 3], [3, 0]],
|
||||
"verts": [
|
||||
Vector([-5.0, -5.0, 0.0]) / si_conversion,
|
||||
Vector([-5.0, 5.0, 0.0]) / si_conversion,
|
||||
Vector([5.0, 5.0, 0.0]) / si_conversion,
|
||||
Vector([5.0, -5.0, 0.0]) / si_conversion,
|
||||
],
|
||||
}
|
||||
roof_data["path_data"] = path_data
|
||||
|
||||
update_bbim_roof_pset(element, roof_data)
|
||||
refresh()
|
||||
update_roof_modifier_bmesh(context)
|
||||
update_roof_modifier_ifc_data(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_roof"
|
||||
bl_label = "Enable Editing Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRoofProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
|
||||
data["path_data"] = json.dumps(data["path_data"])
|
||||
|
||||
# required since we could load pset from .ifc and BIMRoofProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = ("is_editing", "roof_type", "roof_added_previously", "generation_method")
|
||||
skip_props += tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_roof"
|
||||
bl_label = "Cancel editing Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
|
||||
props = obj.BIMRoofProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = -1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FinishEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_roof"
|
||||
bl_label = "Finish editing roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
if not RoofData.is_loaded:
|
||||
RoofData.load()
|
||||
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
roof_data["path_data"] = path_data
|
||||
props.is_editing = -1
|
||||
|
||||
update_bbim_roof_pset(element, roof_data)
|
||||
update_roof_modifier_ifc_data(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_roof_path"
|
||||
bl_label = "Enable Editing Roof Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
props.is_editing_path = True
|
||||
update_roof_modifier_bmesh(context)
|
||||
|
||||
if bpy.context.object.mode != "EDIT":
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
|
||||
ProfileDecorator.install(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CancelEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_roof_path"
|
||||
bl_label = "Cancel Editing Roof Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
ProfileDecorator.uninstall()
|
||||
props.is_editing_path = False
|
||||
|
||||
update_roof_modifier_bmesh(context)
|
||||
if bpy.context.object.mode == "EDIT":
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_roof_path"
|
||||
bl_label = "Finish Editing Roof Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
roof_data["path_data"] = path_data
|
||||
ProfileDecorator.uninstall()
|
||||
props.is_editing_path = False
|
||||
|
||||
update_bbim_roof_pset(element, roof_data)
|
||||
refresh() # RoofData has to be updated before run update_roof_modifier_bmesh
|
||||
update_roof_modifier_bmesh(context)
|
||||
if bpy.context.object.mode == "EDIT":
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
update_roof_modifier_ifc_data(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_roof"
|
||||
bl_label = "Remove Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRoofProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
obj.BIMRoofProperties.is_editing = -1
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.roof_added_previously = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def add_object_button(self, context):
|
||||
self.layout.operator(BIM_OT_add_roof.bl_idname, icon="PLUGIN")
|
||||
@@ -16,30 +16,21 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import gpu
|
||||
import blf
|
||||
import bgl
|
||||
import bpy
|
||||
import math
|
||||
import json
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import mathutils.geometry
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi, degrees, sin, cos, radians
|
||||
from math import radians
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
|
||||
def calculate_quantities(usecase_path, ifc_file, settings):
|
||||
@@ -122,9 +113,9 @@ class DumbSlabGenerator:
|
||||
def __init__(self, relating_type):
|
||||
self.relating_type = relating_type
|
||||
|
||||
def generate(self, link_to_scene=True):
|
||||
self.file = IfcStore.get_file()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
def generate(self):
|
||||
self.file = tool.Ifc.get()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
thicknesses = []
|
||||
for rel in self.relating_type.HasAssociations:
|
||||
if rel.is_a("IfcRelAssociatesMaterial"):
|
||||
@@ -149,13 +140,13 @@ class DumbSlabGenerator:
|
||||
self.rotation = 0
|
||||
self.location = Vector((0, 0, 0))
|
||||
self.x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else radians(props.x_angle)
|
||||
return self.derive_from_cursor(link_to_scene=link_to_scene)
|
||||
return self.derive_from_cursor()
|
||||
|
||||
def derive_from_cursor(self, link_to_scene):
|
||||
def derive_from_cursor(self):
|
||||
self.location = bpy.context.scene.cursor.location
|
||||
return self.create_slab(link_to_scene)
|
||||
return self.create_slab()
|
||||
|
||||
def create_slab(self, link_to_scene):
|
||||
def create_slab(self):
|
||||
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
|
||||
# Standard cases are deprecated, so let's cull them
|
||||
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
|
||||
@@ -163,16 +154,15 @@ class DumbSlabGenerator:
|
||||
mesh = bpy.data.meshes.new("Dummy")
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
||||
|
||||
if link_to_scene:
|
||||
matrix_world = Matrix()
|
||||
matrix_world.col[3] = self.location.to_4d()
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
matrix_world[2][3] = self.collection_obj.location[2] - self.depth
|
||||
else:
|
||||
matrix_world[2][3] -= self.depth
|
||||
obj.matrix_world = Matrix.Rotation(self.x_angle, 4, "X") @ matrix_world
|
||||
bpy.context.view_layer.update()
|
||||
self.collection.objects.link(obj)
|
||||
matrix_world = Matrix()
|
||||
matrix_world.col[3] = self.location.to_4d()
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
matrix_world[2][3] = self.collection_obj.location[2] - self.depth
|
||||
else:
|
||||
matrix_world[2][3] -= self.depth
|
||||
obj.matrix_world = Matrix.Rotation(self.x_angle, 4, "X") @ matrix_world
|
||||
bpy.context.view_layer.update()
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
@@ -269,7 +259,7 @@ class DumbSlabPlaner:
|
||||
|
||||
def change_thickness(self, element, thickness):
|
||||
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
obj = IfcStore.get_element(element.id())
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
return
|
||||
|
||||
@@ -574,7 +564,7 @@ class DisableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
DecorationsHandler.uninstall()
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
obj = context.active_object
|
||||
@@ -621,8 +611,9 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position)
|
||||
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
DecorationsHandler.install(context)
|
||||
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
|
||||
ProfileDecorator.install(context)
|
||||
if not bpy.app.background:
|
||||
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -633,7 +624,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
DecorationsHandler.uninstall()
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
obj = context.active_object
|
||||
@@ -657,7 +648,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.layout.label(text="INVALID PROFILE")
|
||||
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
DecorationsHandler.install(context)
|
||||
ProfileDecorator.install(context)
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
return
|
||||
|
||||
@@ -749,245 +740,3 @@ class SetArcIndex(bpy.types.Operator):
|
||||
group.add(selected_vertices, 1, "REPLACE")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DecorationsHandler:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def __call__(self, context):
|
||||
obj = context.active_object
|
||||
if obj.mode != "EDIT":
|
||||
return
|
||||
|
||||
bgl.glLineWidth(2)
|
||||
bgl.glPointSize(6)
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
|
||||
all_vertices = []
|
||||
error_vertices = []
|
||||
selected_vertices = []
|
||||
unselected_vertices = []
|
||||
special_vertices = []
|
||||
special_vertex_indices = {}
|
||||
selected_edges = []
|
||||
unselected_edges = []
|
||||
special_edges = [] # edges that have a circle or an arc associated with them
|
||||
|
||||
arc_groups = []
|
||||
circle_groups = []
|
||||
for i, group in enumerate(obj.vertex_groups):
|
||||
if "IFCARCINDEX" in group.name:
|
||||
arc_groups.append(i)
|
||||
elif "IFCCIRCLE" in group.name:
|
||||
circle_groups.append(i)
|
||||
|
||||
arcs = {}
|
||||
circles = {}
|
||||
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
# https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess
|
||||
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive
|
||||
deform_layer = bm.verts.layers.deform.active
|
||||
|
||||
for vertex in bm.verts:
|
||||
co = tuple(obj.matrix_world @ vertex.co)
|
||||
all_vertices.append(co)
|
||||
if vertex.hide:
|
||||
continue
|
||||
|
||||
is_arc = False
|
||||
for group_index in arc_groups:
|
||||
if group_index in vertex[deform_layer]:
|
||||
is_arc = True
|
||||
break
|
||||
if is_arc:
|
||||
arcs.setdefault(group_index, []).append(vertex)
|
||||
special_vertex_indices[vertex.index] = group_index
|
||||
|
||||
is_circle = False
|
||||
for group_index in circle_groups:
|
||||
if group_index in vertex[deform_layer]:
|
||||
is_circle = True
|
||||
break
|
||||
if is_circle:
|
||||
circles.setdefault(group_index, []).append(vertex)
|
||||
special_vertex_indices[vertex.index] = group_index
|
||||
|
||||
if vertex.select:
|
||||
selected_vertices.append(co)
|
||||
else:
|
||||
if len(vertex.link_edges) > 1 and is_circle:
|
||||
error_vertices.append(co)
|
||||
elif is_circle:
|
||||
special_vertices.append(co)
|
||||
elif len(vertex.link_edges) != 2:
|
||||
error_vertices.append(co)
|
||||
elif is_arc:
|
||||
special_vertices.append(co)
|
||||
else:
|
||||
unselected_vertices.append(co)
|
||||
|
||||
for edge in bm.edges:
|
||||
edge_indices = [v.index for v in edge.verts]
|
||||
if edge.hide:
|
||||
continue
|
||||
if edge.select:
|
||||
selected_edges.append(edge_indices)
|
||||
else:
|
||||
i1, i2 = edge.verts[0].index, edge.verts[1].index
|
||||
if i1 in special_vertex_indices and special_vertex_indices[i1] == special_vertex_indices.get(i2, None):
|
||||
special_edges.append(edge_indices)
|
||||
else:
|
||||
unselected_edges.append(edge_indices)
|
||||
indices = [[v.index for v in e.verts] for e in bm.edges]
|
||||
|
||||
white = (1, 1, 1, 1)
|
||||
green = (0.545, 0.863, 0, 1)
|
||||
red = (1, 0.2, 0.322, 1)
|
||||
blue = (0.157, 0.565, 1, 1)
|
||||
grey = (0.2, 0.2, 0.2, 1)
|
||||
|
||||
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
|
||||
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=unselected_edges)
|
||||
self.shader.bind()
|
||||
self.shader.uniform_float("color", white)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=selected_edges)
|
||||
self.shader.uniform_float("color", green)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=special_edges)
|
||||
self.shader.uniform_float("color", grey)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": unselected_vertices})
|
||||
self.shader.uniform_float("color", white)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": error_vertices})
|
||||
self.shader.uniform_float("color", red)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": special_vertices})
|
||||
self.shader.uniform_float("color", blue)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": selected_vertices})
|
||||
self.shader.uniform_float("color", green)
|
||||
batch.draw(self.shader)
|
||||
|
||||
# Draw arcs
|
||||
|
||||
arc_centroids = []
|
||||
arc_segments = []
|
||||
for arc in arcs.values():
|
||||
if len(arc) != 3:
|
||||
continue
|
||||
sorted_arc = [None, None, None]
|
||||
for v1 in arc:
|
||||
connections = 0
|
||||
for link_edge in v1.link_edges:
|
||||
v2 = link_edge.other_vert(v1)
|
||||
if v2 in arc:
|
||||
connections += 1
|
||||
if connections == 2: # Midpoint
|
||||
sorted_arc[1] = v1
|
||||
else:
|
||||
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
|
||||
points = [tuple(obj.matrix_world @ v.co) for v in sorted_arc]
|
||||
centroid = tool.Cad.get_center_of_arc(points)
|
||||
if centroid:
|
||||
arc_centroids.append(tuple(centroid))
|
||||
arc_segments.append(tool.Cad.create_arc_segments(pts=points, num_verts=17, make_edges=True))
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": arc_centroids})
|
||||
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
|
||||
batch.draw(self.shader)
|
||||
|
||||
for verts, edges in arc_segments:
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
||||
self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
|
||||
batch.draw(self.shader)
|
||||
|
||||
# Draw circles
|
||||
|
||||
circle_centroids = []
|
||||
circle_segments = []
|
||||
for circle in circles.values():
|
||||
if len(circle) != 2:
|
||||
continue
|
||||
p1 = obj.matrix_world @ circle[0].co
|
||||
p2 = obj.matrix_world @ circle[1].co
|
||||
radius = (p2 - p1).length / 2
|
||||
centroid = p1.lerp(p2, 0.5)
|
||||
circle_centroids.append(tuple(centroid))
|
||||
segments = self.create_circle_segments(360, 20, radius)
|
||||
matrix = obj.matrix_world.copy()
|
||||
matrix.col[3] = centroid.to_4d()
|
||||
segments = [[list(matrix @ Vector(v)) for v in segments[0]], segments[1]]
|
||||
circle_segments.append(segments)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": circle_centroids})
|
||||
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
|
||||
batch.draw(self.shader)
|
||||
|
||||
for verts, edges in circle_segments:
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
||||
self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
|
||||
batch.draw(self.shader)
|
||||
|
||||
def create_matrix(self, p, x, y, z):
|
||||
return Matrix([x, y, z, p]).to_4x4().transposed()
|
||||
|
||||
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
|
||||
# This function is taken from Sverchok, licensed under GPL v2-or-later.
|
||||
# This is a combination of the make_verts and make_edges function.
|
||||
def create_circle_segments(self, Angle, Vertices, Radius):
|
||||
if Angle < 360:
|
||||
theta = Angle / (Vertices - 1)
|
||||
else:
|
||||
theta = Angle / Vertices
|
||||
listVertX = []
|
||||
listVertY = []
|
||||
for i in range(Vertices):
|
||||
listVertX.append(Radius * cos(radians(theta * i)))
|
||||
listVertY.append(Radius * sin(radians(theta * i)))
|
||||
|
||||
if Angle < 360 and self.mode_ == 0:
|
||||
sigma = radians(Angle)
|
||||
listVertX[-1] = Radius * cos(sigma)
|
||||
listVertY[-1] = Radius * sin(sigma)
|
||||
elif Angle < 360 and self.mode_ == 1:
|
||||
listVertX.append(0.0)
|
||||
listVertY.append(0.0)
|
||||
|
||||
points = list((x, y, 0) for x, y in zip(listVertX, listVertY))
|
||||
|
||||
listEdg = [(i, i + 1) for i in range(Vertices - 1)]
|
||||
|
||||
if Angle < 360 and self.mode_ == 1:
|
||||
listEdg.append((0, Vertices))
|
||||
listEdg.append((Vertices - 1, Vertices))
|
||||
else:
|
||||
listEdg.append((Vertices - 1, 0))
|
||||
|
||||
return points, listEdg
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import shapely
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
from math import pi
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
|
||||
class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.generate_space"
|
||||
bl_label = "Generate Space"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
# layered walls (i.e. prismatic) in the currently active storey (though
|
||||
# we can easily extend it to include other "bounding" elements). For
|
||||
# now we generate rooms that exclude walls (i.e. not to wall midpoint
|
||||
# or exterior / interior edge).
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="NO ACTIVE STOREY")
|
||||
|
||||
props = context.scene.BIMModelProperties
|
||||
relating_type_id = props.relating_type_id
|
||||
relating_type = None
|
||||
if relating_type_id:
|
||||
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
||||
if not relating_type.is_a("IfcSpaceType"):
|
||||
relating_type = None
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
if not collection_obj:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
spatial_element = tool.Ifc.get_entity(collection_obj)
|
||||
if not spatial_element:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
active_obj = bpy.context.active_object
|
||||
element = None
|
||||
if bpy.context.selected_objects and active_obj:
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
x, y, z = active_obj.matrix_world.translation.xyz
|
||||
mat = active_obj.matrix_world
|
||||
h = active_obj.dimensions.z
|
||||
else:
|
||||
x, y = context.scene.cursor.location.xy
|
||||
z = collection_obj.matrix_world.translation.z
|
||||
mat = Matrix()
|
||||
mat[0][3], mat[1][3], mat[2][3] = x, y, z
|
||||
h = 3
|
||||
|
||||
boundary_elements = []
|
||||
for subelement in ifcopenshell.util.element.get_decomposition(spatial_element):
|
||||
if subelement.is_a("IfcWall") and tool.Model.get_usage_type(subelement) == "LAYER2":
|
||||
boundary_elements.append(subelement)
|
||||
|
||||
boundary_lines = []
|
||||
for boundary_element in boundary_elements:
|
||||
obj = tool.Ifc.get_object(boundary_element)
|
||||
if not obj:
|
||||
continue
|
||||
axis = self.get_wall_axis(obj)
|
||||
for ypos in ["miny", "maxy"]:
|
||||
start, end = axis[ypos]
|
||||
offset = (end - start).normalized() * 0.3 # Offset wall lines by 300mm
|
||||
boundary_lines.append(shapely.LineString([start - offset, end + offset]))
|
||||
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
|
||||
|
||||
space_polygon = None
|
||||
for polygon in closed_polygons.geoms:
|
||||
if shapely.contains_xy(polygon, x, y):
|
||||
space_polygon = shapely.force_3d(polygon)
|
||||
|
||||
if not space_polygon:
|
||||
return
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
|
||||
mat_invert = mat.inverted()
|
||||
new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], z])) for v in space_polygon.exterior.coords[0:-1]]
|
||||
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
||||
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
|
||||
|
||||
for interior in space_polygon.interiors:
|
||||
new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], z])) for v in interior.coords[0:-1]]
|
||||
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
||||
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
|
||||
bmesh.ops.triangle_fill(bm, edges=bm.edges)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces)
|
||||
extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)]
|
||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts)
|
||||
|
||||
mesh = bpy.data.meshes.new(name="Space")
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
|
||||
if element and element.is_a("IfcSpace"):
|
||||
mesh.name = active_obj.data.name
|
||||
mesh.BIMMeshProperties.ifc_definition_id = active_obj.data.BIMMeshProperties.ifc_definition_id
|
||||
tool.Geometry.change_object_data(active_obj, mesh, is_global=True)
|
||||
tool.Ifc.edit(active_obj)
|
||||
else:
|
||||
if relating_type:
|
||||
name = tool.Model.generate_occurrence_name(relating_type, "IfcSpace")
|
||||
else:
|
||||
name = "Space"
|
||||
obj = bpy.data.objects.new("Space", mesh)
|
||||
obj.matrix_world = mat
|
||||
collection.objects.link(obj)
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace")
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if relating_type:
|
||||
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
||||
|
||||
def get_wall_axis(self, obj):
|
||||
x_values = [v[0] for v in obj.bound_box]
|
||||
y_values = [v[1] for v in obj.bound_box]
|
||||
min_x = min(x_values)
|
||||
max_x = max(x_values)
|
||||
min_y = min(y_values)
|
||||
max_y = max(y_values)
|
||||
return {
|
||||
"miny": [
|
||||
(obj.matrix_world @ Vector((min_x, min_y, 0.0))).to_2d(),
|
||||
(obj.matrix_world @ Vector((max_x, min_y, 0.0))).to_2d(),
|
||||
],
|
||||
"maxy": [
|
||||
(obj.matrix_world @ Vector((min_x, max_y, 0.0))).to_2d(),
|
||||
(obj.matrix_world @ Vector((max_x, max_y, 0.0))).to_2d(),
|
||||
],
|
||||
}
|
||||
@@ -25,9 +25,9 @@ import bmesh
|
||||
from bmesh.types import BMVert
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell.util.shape_builder import V, ShapeBuilder
|
||||
import blenderbim
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.module.model.prop import BIMStairProperties
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
|
||||
from mathutils import Vector
|
||||
@@ -88,8 +88,9 @@ def generate_stair_2d_profile(
|
||||
height,
|
||||
width,
|
||||
tread_run,
|
||||
tread_depth,
|
||||
stair_type,
|
||||
# WOOD/STEEL CONCRETE STAIR ARGUMENTS
|
||||
tread_depth=None,
|
||||
# CONCRETE STAIR ARGUMENTS
|
||||
has_top_nib=None,
|
||||
top_slab_depth=None,
|
||||
@@ -104,12 +105,16 @@ def generate_stair_2d_profile(
|
||||
length = tread_run * number_of_risers
|
||||
|
||||
if stair_type == "WOOD/STEEL":
|
||||
builder = ShapeBuilder(None)
|
||||
tread_shape = builder.get_rectangle_coords(
|
||||
size=V(tread_run, 0, tread_depth),
|
||||
position=V(0, 0, -(tread_depth-tread_rise))
|
||||
)
|
||||
tread_offset = V(tread_run, 0, tread_rise)
|
||||
|
||||
for i in range(number_of_risers):
|
||||
v1 = Vector(((i + 1) * tread_run, 0, (i + 1) * tread_rise))
|
||||
v0 = v1 - Vector((tread_run, 0, 0))
|
||||
v2 = v1 - Vector((0, 0, tread_depth))
|
||||
v3 = v0 - Vector((0, 0, tread_depth))
|
||||
vertices.extend([v0, v1, v2, v3])
|
||||
cur_trade_shape = [v + tread_offset * i for v in tread_shape]
|
||||
vertices.extend(cur_trade_shape)
|
||||
|
||||
cur_vertex = i * 4
|
||||
edges.extend([
|
||||
@@ -118,9 +123,37 @@ def generate_stair_2d_profile(
|
||||
(cur_vertex+2, cur_vertex+3),
|
||||
(cur_vertex+3, cur_vertex),
|
||||
])
|
||||
faces.append(list(range(4 * i, 4 * i + 1)))
|
||||
faces.append(list(range(cur_vertex, cur_vertex + 1)))
|
||||
|
||||
return (vertices, edges, faces)
|
||||
|
||||
elif stair_type == "GENERIC":
|
||||
vertices.append(Vector([0, 0, 0]))
|
||||
|
||||
tread_verts = [
|
||||
Vector([0, 0, tread_rise]),
|
||||
Vector([tread_run, 0, tread_rise])
|
||||
]
|
||||
tread_offset = Vector([tread_run, 0, tread_rise])
|
||||
|
||||
for i in range(number_of_risers):
|
||||
current_tread_verts = [v + tread_offset * i for v in tread_verts]
|
||||
last_vert_i = len(vertices) - 1
|
||||
edges.extend([
|
||||
(last_vert_i, last_vert_i + 1),
|
||||
(last_vert_i + 1, last_vert_i + 2)
|
||||
])
|
||||
vertices.extend(current_tread_verts)
|
||||
|
||||
last_vert_i = len(vertices)
|
||||
vertices.append(vertices[-1] * V(1,0,0))
|
||||
edges.extend([
|
||||
(last_vert_i - 1, last_vert_i),
|
||||
(last_vert_i, 0)
|
||||
])
|
||||
|
||||
return (vertices, edges, faces)
|
||||
|
||||
elif stair_type == "CONCRETE":
|
||||
for i in range(number_of_risers):
|
||||
vertices.extend([
|
||||
@@ -218,11 +251,15 @@ def update_stair_modifier(context):
|
||||
obj.data.update()
|
||||
|
||||
|
||||
def update_ifc_stair_props(obj, psets):
|
||||
def update_ifc_stair_props(obj):
|
||||
"""should be called after new geometry settled
|
||||
since it's going to update ifc representation
|
||||
"""
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMStairProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
element.PredefinedType = "STRAIGHT"
|
||||
number_of_risers = props.number_of_treads + 1
|
||||
# update IfcStairFlight properties (seems already deprecated but keep it for now)
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcStairFlight.htm
|
||||
@@ -233,10 +270,8 @@ def update_ifc_stair_props(obj, psets):
|
||||
element.TreadLength = props.tread_depth
|
||||
|
||||
# update pset with ifc properties
|
||||
pset_common = psets.get("Pset_StairFlightCommon", None)
|
||||
if pset_common:
|
||||
pset_common = ifc_file.by_id(pset_common["id"])
|
||||
else:
|
||||
pset_common = tool.Pset.get_element_pset(element, "Pset_StairFlightCommon")
|
||||
if not pset_common:
|
||||
pset_common = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name="Pset_StairFlightCommon")
|
||||
|
||||
ifcopenshell.api.run(
|
||||
@@ -250,14 +285,15 @@ def update_ifc_stair_props(obj, psets):
|
||||
"TreadLength": props.tread_depth,
|
||||
},
|
||||
)
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
class BIM_OT_add_clever_stair(Operator):
|
||||
class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_clever_stair"
|
||||
bl_label = "Clever Stair"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file:
|
||||
self.report({"ERROR"}, "You need to start IFC project first to create a stair.")
|
||||
@@ -316,12 +352,8 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
|
||||
stair_data = props.get_props_kwargs()
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets.get("BBIM_Stair", None)
|
||||
|
||||
if pset:
|
||||
pset = ifc_file.by_id(pset["id"])
|
||||
else:
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name="BBIM_Stair")
|
||||
|
||||
ifcopenshell.api.run(
|
||||
@@ -331,7 +363,7 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
properties={"Data": json.dumps(stair_data)},
|
||||
)
|
||||
update_stair_modifier(context)
|
||||
update_ifc_stair_props(obj, psets)
|
||||
update_ifc_stair_props(obj)
|
||||
|
||||
|
||||
class CancelEditingStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -342,8 +374,7 @@ class CancelEditingStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
data = json.loads(psets["BBIM_Stair"]["Data"])
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Stair", "Data"))
|
||||
props = obj.BIMStairProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
@@ -365,21 +396,16 @@ class FinishEditingStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMStairProperties
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets["BBIM_Stair"]
|
||||
data = props.get_props_kwargs()
|
||||
|
||||
props.is_editing = -1
|
||||
|
||||
update_stair_modifier(context)
|
||||
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
|
||||
data = json.dumps(data)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
|
||||
|
||||
# update IfcStairFlight properties
|
||||
element.PredefinedType = "STRAIGHT"
|
||||
update_ifc_stair_props(obj, psets)
|
||||
update_ifc_stair_props(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -392,8 +418,7 @@ class EnableEditingStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
props = obj.BIMStairProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
pset = ifcopenshell.util.element.get_psets(element)
|
||||
data = json.loads(pset["BBIM_Stair"]["Data"])
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Stair", "Data"))
|
||||
# required since we could load pset from .ifc and BIMStairProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
@@ -419,8 +444,7 @@ class RemoveStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
obj.BIMStairProperties.is_editing = -1
|
||||
|
||||
pset = ifcopenshell.util.element.get_psets(element)
|
||||
pset = tool.Ifc.get().by_id(pset["BBIM_Stair"]["id"])
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.stair_added_previously = True
|
||||
|
||||
|
||||
@@ -17,19 +17,11 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty, IntProperty, BoolProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
import bmesh
|
||||
|
||||
import ifcopenshell
|
||||
import blenderbim
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.module.model.prop import BIMStairProperties
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
from mathutils import Vector
|
||||
from pprint import pprint
|
||||
import json
|
||||
import zipfile
|
||||
import os.path
|
||||
@@ -65,7 +57,7 @@ def update_sverchok_modifier(context):
|
||||
bm.to_mesh(obj.data)
|
||||
bm.free()
|
||||
obj.data.update()
|
||||
IfcStore.edited_objs.add(obj)
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
# UI operators
|
||||
|
||||
@@ -19,11 +19,22 @@
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, Operator, Menu
|
||||
from blenderbim.bim.module.model.data import AuthoringData, ArrayData, StairData, SverchokData, WindowData, DoorData
|
||||
from blenderbim.bim.module.model.prop import store_cursor_position
|
||||
from blenderbim.bim.module.model.data import (
|
||||
AuthoringData,
|
||||
ArrayData,
|
||||
StairData,
|
||||
SverchokData,
|
||||
WindowData,
|
||||
DoorData,
|
||||
RailingData,
|
||||
RoofData,
|
||||
)
|
||||
from blenderbim.bim.module.model.prop import get_ifc_class
|
||||
from blenderbim.bim.module.model.stair import update_stair_modifier
|
||||
from blenderbim.bim.module.model.window import update_window_modifier_bmesh
|
||||
from blenderbim.bim.module.model.door import update_door_modifier_bmesh
|
||||
from blenderbim.bim.module.model.railing import update_railing_modifier_bmesh
|
||||
from blenderbim.bim.module.model.roof import update_roof_modifier_bmesh
|
||||
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
|
||||
@@ -40,11 +51,9 @@ class LaunchTypeManager(bpy.types.Operator):
|
||||
def invoke(self, context, event):
|
||||
props = context.scene.BIMModelProperties
|
||||
props.type_page = 1
|
||||
if props.ifc_class:
|
||||
if get_ifc_class(None, context):
|
||||
props.type_class = props.ifc_class
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=props.ifc_class, offset=0, limit=9)
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
return context.window_manager.invoke_popup(self, width=550)
|
||||
|
||||
def draw(self, context):
|
||||
@@ -63,6 +72,7 @@ class LaunchTypeManager(bpy.types.Operator):
|
||||
row.alignment = "CENTER"
|
||||
row.prop(props, "type_predefined_type", text="")
|
||||
row.prop(props, "type_template", text="")
|
||||
row.prop(props, "type_name", text="")
|
||||
row.operator("bim.add_type", icon="ADD", text="")
|
||||
|
||||
row = columns.row(align=True)
|
||||
@@ -127,86 +137,6 @@ class BIM_PT_authoring(Panel):
|
||||
row.operator("bim.align_wall", icon="ANCHOR_BOTTOM", text="Int.").align_type = "INTERIOR"
|
||||
|
||||
|
||||
class DisplayConstrTypesUI(Operator):
|
||||
bl_idname = "bim.display_constr_types_ui"
|
||||
bl_label = "Browse Construction Types"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Display all available Construction Types to add new instances"
|
||||
reinvoked: bpy.props.BoolProperty(default=False)
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
store_cursor_position(context, event, window=not self.reinvoked)
|
||||
return context.window_manager.invoke_popup(self, width=550)
|
||||
|
||||
def reinvoke(self, context):
|
||||
browser_state = context.scene.BIMModelProperties.constr_browser_state
|
||||
|
||||
def set_updating_transaction():
|
||||
browser_state.updating = True
|
||||
|
||||
def run_operator():
|
||||
bpy.ops.bim.reinvoke_operator("INVOKE_DEFAULT", operator="bim.display_constr_types_ui")
|
||||
browser_state.updating = False
|
||||
|
||||
if not browser_state.updating:
|
||||
bpy.app.timers.register(set_updating_transaction)
|
||||
bpy.app.timers.register(run_operator, first_interval=browser_state.update_delay)
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMModelProperties
|
||||
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="", icon="FILE_VOLUME")
|
||||
prop_with_search(row, props, "ifc_class_browser", text="")
|
||||
ifc_class = props.ifc_class_browser
|
||||
num_cols = 3
|
||||
self.layout.row().separator(factor=0.25)
|
||||
flow = self.layout.grid_flow(row_major=True, columns=num_cols, even_columns=True, even_rows=True, align=True)
|
||||
relating_types = AuthoringData.relating_types_browser()
|
||||
num_types = len(relating_types)
|
||||
|
||||
for idx, (relating_type_id, name, desc) in enumerate(relating_types):
|
||||
outer_col = flow.column()
|
||||
box = outer_col.box()
|
||||
row = box.row()
|
||||
row.label(text=name, icon="FILE_3D")
|
||||
row.alignment = "CENTER"
|
||||
row = box.row()
|
||||
|
||||
if ifc_class in props.constr_classes:
|
||||
constr_class_info = props.constr_classes[ifc_class]
|
||||
constr_types_info = constr_class_info.constr_types
|
||||
|
||||
if relating_type_id in constr_types_info:
|
||||
icon_id = constr_types_info[relating_type_id].icon_id
|
||||
row.template_icon(icon_value=icon_id, scale=6.0)
|
||||
else:
|
||||
self.reinvoke(context)
|
||||
return
|
||||
|
||||
row = box.row()
|
||||
op = row.operator("bim.add_constr_type_instance", icon="ADD")
|
||||
op.from_invoke = True
|
||||
op.ifc_class = ifc_class
|
||||
|
||||
if relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(relating_type_id)
|
||||
|
||||
last_row_cols = num_types % num_cols
|
||||
|
||||
if last_row_cols != 0:
|
||||
for _ in range(num_cols - last_row_cols):
|
||||
flow.column()
|
||||
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.help_relating_types", text="", icon="QUESTION")
|
||||
|
||||
|
||||
class HelpConstrTypes(Operator):
|
||||
bl_idname = "bim.help_relating_types"
|
||||
bl_label = "Construction Types Help"
|
||||
@@ -611,6 +541,124 @@ class BIM_PT_door(bpy.types.Panel):
|
||||
row.operator("bim.add_door", icon="ADD", text="")
|
||||
|
||||
|
||||
class BIM_PT_railing(bpy.types.Panel):
|
||||
bl_label = "IFC Railing"
|
||||
bl_idname = "BIM_PT_railing"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "modifier"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# always display modifier if it's IFC object
|
||||
return tool.Ifc.get() and tool.Ifc.get_entity(context.active_object)
|
||||
|
||||
def draw(self, context):
|
||||
if not RailingData.is_loaded:
|
||||
RailingData.load()
|
||||
|
||||
props = context.active_object.BIMRailingProperties
|
||||
|
||||
if RailingData.data["parameters"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Railing parameters", icon="OUTLINER_OB_LATTICE")
|
||||
|
||||
railing_data = RailingData.data["parameters"]["data_dict"]
|
||||
|
||||
if props.is_editing != -1:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_railing", icon="CHECKMARK", text="Finish editing")
|
||||
row.operator("bim.cancel_editing_railing", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
update_railing_modifier_bmesh(context)
|
||||
|
||||
elif props.is_editing_path:
|
||||
row.operator("bim.finish_editing_railing_path", icon="CHECKMARK", text="")
|
||||
row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="")
|
||||
|
||||
else:
|
||||
row.operator("bim.enable_editing_railing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.enable_editing_railing_path", icon="ANIM", text="")
|
||||
row.operator("bim.remove_railing", icon="X", text="")
|
||||
|
||||
box = self.layout.box()
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
prop_value = railing_data[prop]
|
||||
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"{props.bl_rna.properties[prop].name}")
|
||||
row.label(text=str(prop_value))
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Railing Found")
|
||||
row.operator("bim.add_railing", icon="ADD", text="")
|
||||
|
||||
|
||||
class BIM_PT_roof(bpy.types.Panel):
|
||||
bl_label = "IFC Roof"
|
||||
bl_idname = "BIM_PT_roof"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "modifier"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# always display modifier if it's IFC object
|
||||
return tool.Ifc.get() and tool.Ifc.get_entity(context.active_object)
|
||||
|
||||
def draw(self, context):
|
||||
if not RoofData.is_loaded:
|
||||
RoofData.load()
|
||||
|
||||
props = context.active_object.BIMRoofProperties
|
||||
|
||||
if RoofData.data["parameters"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Roof parameters", icon="OUTLINER_OB_LATTICE")
|
||||
|
||||
roof_data = RoofData.data["parameters"]["data_dict"]
|
||||
|
||||
if props.is_editing != -1:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_roof", icon="CHECKMARK", text="Finish editing")
|
||||
row.operator("bim.cancel_editing_roof", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
update_roof_modifier_bmesh(context)
|
||||
|
||||
elif props.is_editing_path:
|
||||
row.operator("bim.finish_editing_roof_path", icon="CHECKMARK", text="")
|
||||
row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="")
|
||||
|
||||
else:
|
||||
row.operator("bim.enable_editing_roof", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.enable_editing_roof_path", icon="ANIM", text="")
|
||||
row.operator("bim.remove_roof", icon="X", text="")
|
||||
|
||||
box = self.layout.box()
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
prop_value = roof_data[prop]
|
||||
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"{props.bl_rna.properties[prop].name}")
|
||||
row.label(text=str(prop_value))
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Roof Found")
|
||||
row.operator("bim.add_roof", icon="ADD", text="")
|
||||
|
||||
|
||||
class BIM_MT_model(Menu):
|
||||
bl_idname = "BIM_MT_model"
|
||||
bl_label = "IFC Objects"
|
||||
|
||||
@@ -34,6 +34,7 @@ import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi, sin, cos, degrees, radians
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
|
||||
|
||||
|
||||
def element_listener(element, obj):
|
||||
@@ -54,11 +55,10 @@ def mode_callback(obj, data):
|
||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
|
||||
return
|
||||
if obj.mode == "EDIT":
|
||||
IfcStore.edited_objs.add(obj)
|
||||
tool.Ifc.edit(obj)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
bm.free()
|
||||
else:
|
||||
new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
|
||||
obj.data.transform(
|
||||
@@ -108,12 +108,18 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
if self.join_type == "T":
|
||||
elements = [tool.Ifc.get_entity(o) for o in context.selected_objects]
|
||||
targets = [e for e in elements if e and e.is_a() in ("IfcSlab", "IfcSlabStandardCase", "IfcRoof")]
|
||||
if targets:
|
||||
target = tool.Ifc.get_object(targets[0])
|
||||
for element in elements:
|
||||
if element.is_a("IfcWall"):
|
||||
joiner.join_Z(tool.Ifc.get_object(element), target)
|
||||
layer2_elements = []
|
||||
layer3_elements = []
|
||||
for element in elements:
|
||||
usage = tool.Model.get_usage_type(element)
|
||||
if usage == "LAYER2":
|
||||
layer2_elements.append(element)
|
||||
elif usage == "LAYER3":
|
||||
layer3_elements.append(element)
|
||||
if layer3_elements:
|
||||
target = tool.Ifc.get_object(layer3_elements[0])
|
||||
for element in layer2_elements:
|
||||
joiner.join_Z(tool.Ifc.get_object(element), target)
|
||||
else:
|
||||
for obj in selected_objs:
|
||||
if obj == context.active_object:
|
||||
@@ -149,7 +155,7 @@ class AlignWall(bpy.types.Operator):
|
||||
aligner.align_first_layer()
|
||||
elif self.align_type == "INTERIOR":
|
||||
aligner.align_last_layer()
|
||||
IfcStore.edited_objs.add(obj)
|
||||
tool.Ifc.edit(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -193,6 +199,7 @@ class SplitWall(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class MergeWall(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.merge_wall"
|
||||
bl_label = "Merge Wall"
|
||||
bl_description = "Merge selected walls into one object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
@@ -231,7 +238,7 @@ class ChangeExtrusionDepth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
wall_objs = []
|
||||
layer2_objs = []
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
@@ -245,7 +252,7 @@ class ChangeExtrusionDepth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
|
||||
x_angle = Vector((0, 1)).angle_signed(Vector((y, z)))
|
||||
extrusion.Depth = self.depth * (1 / cos(x_angle))
|
||||
if element.is_a("IfcWall"):
|
||||
if tool.Model.get_usage_type(element) == "LAYER2":
|
||||
for rel in element.ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements":
|
||||
ifcopenshell.api.run(
|
||||
@@ -254,9 +261,9 @@ class ChangeExtrusionDepth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
relating_element=rel.RelatingElement,
|
||||
related_element=element,
|
||||
)
|
||||
wall_objs.append(obj)
|
||||
if wall_objs:
|
||||
DumbWallRecalculator().recalculate(wall_objs)
|
||||
layer2_objs.append(obj)
|
||||
if layer2_objs:
|
||||
DumbWallRecalculator().recalculate(layer2_objs)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -271,7 +278,7 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
wall_objs = []
|
||||
layer2_objs = []
|
||||
other_objs = []
|
||||
x_angle = radians(self.x_angle)
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
@@ -290,8 +297,8 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
perpendicular_depth = extrusion.Depth / (1 / cos(existing_x_angle))
|
||||
extrusion.Depth = perpendicular_depth * (1 / cos(x_angle))
|
||||
extrusion.ExtrudedDirection.DirectionRatios = (0.0, sin(x_angle), cos(x_angle))
|
||||
if element.is_a("IfcWall"):
|
||||
wall_objs.append(obj)
|
||||
if tool.Model.get_usage_type(element) == "LAYER2":
|
||||
layer2_objs.append(obj)
|
||||
else:
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
@@ -308,8 +315,8 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
new_matrix = euler.to_matrix().to_4x4()
|
||||
new_matrix.col[3] = obj.matrix_world.col[3]
|
||||
obj.matrix_world = new_matrix
|
||||
if wall_objs:
|
||||
DumbWallRecalculator().recalculate(wall_objs)
|
||||
if layer2_objs:
|
||||
DumbWallRecalculator().recalculate(layer2_objs)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -345,28 +352,6 @@ def recalculate_dumb_wall_origin(wall, new_origin=None):
|
||||
child.matrix_parent_inverse = wall.matrix_world.inverted()
|
||||
|
||||
|
||||
class DumbWallFlipper:
|
||||
def __init__(self, wall):
|
||||
self.wall = wall
|
||||
|
||||
def flip(self):
|
||||
if (
|
||||
self.wall.matrix_world.translation - self.wall.matrix_world @ Vector(self.wall.bound_box[0])
|
||||
).length < 0.001:
|
||||
recalculate_dumb_wall_origin(self.wall, self.wall.matrix_world @ Vector(self.wall.bound_box[7]))
|
||||
self.rotate_wall_180()
|
||||
bpy.context.view_layer.update()
|
||||
for child in self.wall.children:
|
||||
child.matrix_parent_inverse = self.wall.matrix_world.inverted()
|
||||
else:
|
||||
recalculate_dumb_wall_origin(self.wall)
|
||||
|
||||
def rotate_wall_180(self):
|
||||
flip_matrix = Matrix.Rotation(pi, 4, "Z")
|
||||
self.wall.data.transform(flip_matrix)
|
||||
self.wall.rotation_euler.rotate(flip_matrix)
|
||||
|
||||
|
||||
class DumbWallAligner:
|
||||
# An alignment shifts the origin of all walls to the closest point on the
|
||||
# local X axis of the reference wall. In addition, the Z rotation is copied.
|
||||
@@ -376,61 +361,58 @@ class DumbWallAligner:
|
||||
self.reference_wall = reference_wall
|
||||
|
||||
def align_centerline(self):
|
||||
recalculate_dumb_wall_origin(self.wall)
|
||||
recalculate_dumb_wall_origin(self.reference_wall)
|
||||
self.align_rotation()
|
||||
|
||||
width = (Vector(self.wall.bound_box[3]) - Vector(self.wall.bound_box[0])).y
|
||||
reference_width = (Vector(self.reference_wall.bound_box[3]) - Vector(self.reference_wall.bound_box[0])).y
|
||||
l_start = Vector(self.reference_wall.bound_box[0]).lerp(Vector(self.reference_wall.bound_box[3]), 0.5)
|
||||
l_end = Vector(self.reference_wall.bound_box[4]).lerp(Vector(self.reference_wall.bound_box[7]), 0.5)
|
||||
|
||||
if self.is_rotation_flipped():
|
||||
offset = self.wall.matrix_world.to_quaternion() @ Vector((0, -(reference_width / 2) - (width / 2), 0))
|
||||
else:
|
||||
offset = self.wall.matrix_world.to_quaternion() @ Vector((0, (reference_width / 2) - (width / 2), 0))
|
||||
start = self.reference_wall.matrix_world @ l_start
|
||||
end = self.reference_wall.matrix_world @ l_end
|
||||
|
||||
self.align(
|
||||
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[0]),
|
||||
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[4]),
|
||||
offset,
|
||||
)
|
||||
l_snap_point = Vector(self.wall.bound_box[0]).lerp(Vector(self.wall.bound_box[3]), 0.5)
|
||||
snap_point = self.wall.matrix_world @ l_snap_point
|
||||
offset = snap_point - self.wall.matrix_world.translation
|
||||
|
||||
point, _ = mathutils.geometry.intersect_point_line(snap_point, start, end)
|
||||
|
||||
new_origin = point - offset
|
||||
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
|
||||
|
||||
def align_last_layer(self):
|
||||
recalculate_dumb_wall_origin(self.wall)
|
||||
recalculate_dumb_wall_origin(self.reference_wall)
|
||||
self.align_rotation()
|
||||
|
||||
if self.is_rotation_flipped():
|
||||
DumbWallFlipper(self.wall).flip()
|
||||
DumbWallJoiner().flip(self.wall)
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
start = self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[3])
|
||||
end = self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[7])
|
||||
|
||||
wall_width = (Vector(self.wall.bound_box[3]) - Vector(self.wall.bound_box[0])).y
|
||||
snap_point = self.wall.matrix_world @ Vector(self.wall.bound_box[3])
|
||||
offset = snap_point - self.wall.matrix_world.translation
|
||||
|
||||
offset = self.wall.matrix_world.to_quaternion() @ Vector((0, -wall_width, 0))
|
||||
self.align(start, end, offset)
|
||||
point, _ = mathutils.geometry.intersect_point_line(snap_point, start, end)
|
||||
|
||||
new_origin = point - offset
|
||||
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
|
||||
|
||||
def align_first_layer(self):
|
||||
recalculate_dumb_wall_origin(self.wall)
|
||||
recalculate_dumb_wall_origin(self.reference_wall)
|
||||
self.align_rotation()
|
||||
|
||||
if self.is_rotation_flipped():
|
||||
DumbWallFlipper(self.wall).flip()
|
||||
DumbWallJoiner().flip(self.wall)
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
start = self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[0])
|
||||
end = self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[4])
|
||||
|
||||
self.align(start, end)
|
||||
snap_point = self.wall.matrix_world @ Vector(self.wall.bound_box[0])
|
||||
offset = snap_point - self.wall.matrix_world.translation
|
||||
|
||||
def align(self, start, end, offset=None):
|
||||
if offset is None:
|
||||
offset = Vector((0, 0, 0))
|
||||
point, distance = mathutils.geometry.intersect_point_line(self.wall.matrix_world.translation, start, end)
|
||||
new_origin = point + offset
|
||||
self.wall.matrix_world.translation[0] = new_origin[0]
|
||||
self.wall.matrix_world.translation[1] = new_origin[1]
|
||||
point, _ = mathutils.geometry.intersect_point_line(snap_point, start, end)
|
||||
|
||||
new_origin = point - offset
|
||||
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
|
||||
|
||||
def align_rotation(self):
|
||||
reference = (self.reference_wall.matrix_world.to_quaternion() @ Vector((1, 0, 0))).to_2d()
|
||||
@@ -463,7 +445,7 @@ class DumbWallRecalculator:
|
||||
queue.add((rel.RelatingElement, tool.Ifc.get_object(rel.RelatingElement)))
|
||||
joiner = DumbWallJoiner()
|
||||
for element, wall in queue:
|
||||
if element.is_a("IfcWall") and wall:
|
||||
if tool.Model.get_usage_type(element) == "LAYER2" and wall:
|
||||
joiner.recreate_wall(element, wall)
|
||||
|
||||
|
||||
@@ -472,7 +454,7 @@ class DumbWallGenerator:
|
||||
self.relating_type = relating_type
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
def generate(self, link_to_scene=True):
|
||||
def generate(self):
|
||||
self.file = IfcStore.get_file()
|
||||
self.layers = tool.Model.get_material_layer_parameters(self.relating_type)
|
||||
if not self.layers["thickness"]:
|
||||
@@ -491,10 +473,7 @@ class DumbWallGenerator:
|
||||
self.location = Vector((0, 0, 0))
|
||||
self.x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else radians(props.x_angle)
|
||||
|
||||
if self.has_sketch():
|
||||
return # For now
|
||||
return self.derive_from_sketch()
|
||||
return self.derive_from_cursor(link_to_scene)
|
||||
return self.derive_from_cursor()
|
||||
|
||||
def has_sketch(self):
|
||||
return (
|
||||
@@ -573,7 +552,7 @@ class DumbWallGenerator:
|
||||
def is_near(self, point1, point2):
|
||||
return (point1 - point2).length < 0.1
|
||||
|
||||
def derive_from_cursor(self, link_to_scene):
|
||||
def derive_from_cursor(self):
|
||||
self.location = bpy.context.scene.cursor.location
|
||||
if self.collection:
|
||||
for sibling_obj in self.collection.objects:
|
||||
@@ -599,21 +578,21 @@ class DumbWallGenerator:
|
||||
normal = (sibling_obj.matrix_world.to_quaternion() @ face.normal).normalized()
|
||||
self.rotation = math.atan2(normal[1], normal[0])
|
||||
break
|
||||
return self.create_wall(link_to_scene)
|
||||
return self.create_wall()
|
||||
|
||||
def create_wall(self, link_to_scene):
|
||||
def create_wall(self):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
ifc_class = self.get_relating_type_class(self.relating_type)
|
||||
mesh = bpy.data.meshes.new("Dummy")
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
||||
if link_to_scene:
|
||||
matrix_world = Matrix.Rotation(self.rotation, 4, "Z")
|
||||
matrix_world.col[3] = self.location.to_4d()
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
matrix_world[2][3] = self.collection_obj.location[2] + (props.rl1 * self.unit_scale)
|
||||
obj.matrix_world = matrix_world
|
||||
bpy.context.view_layer.update()
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
matrix_world = Matrix.Rotation(self.rotation, 4, "Z")
|
||||
matrix_world.col[3] = self.location.to_4d()
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
matrix_world[2][3] = self.collection_obj.location[2] + (props.rl1 * self.unit_scale)
|
||||
obj.matrix_world = matrix_world
|
||||
bpy.context.view_layer.update()
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
@@ -781,10 +760,10 @@ class DumbWallJoiner:
|
||||
return
|
||||
axis1 = tool.Model.get_wall_axis(wall1)
|
||||
axis2 = copy.deepcopy(axis1)
|
||||
intersect, connection = mathutils.geometry.intersect_point_line(target.to_2d(), *axis1["reference"])
|
||||
if connection < 0 or connection > 1 or tool.Cad.is_x(connection, (0, 1)):
|
||||
intersect, cut_percentage = mathutils.geometry.intersect_point_line(target.to_2d(), *axis1["reference"])
|
||||
if cut_percentage < 0 or cut_percentage > 1 or tool.Cad.is_x(cut_percentage, (0, 1)):
|
||||
return
|
||||
connection = "ATEND" if connection > 0.5 else "ATSTART"
|
||||
connection = "ATEND" if cut_percentage > 0.5 else "ATSTART"
|
||||
|
||||
wall2 = self.duplicate_wall(wall1)
|
||||
element2 = tool.Ifc.get_entity(wall2)
|
||||
@@ -792,6 +771,40 @@ class DumbWallJoiner:
|
||||
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element1, connection_type="ATEND")
|
||||
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element2, connection_type="ATSTART")
|
||||
|
||||
# During the duplication process, unfilled voids are copied, so we need
|
||||
# to check openings on both element1 and element2. Let's check element1
|
||||
# first.
|
||||
for opening in [
|
||||
r.RelatedOpeningElement for r in element1.HasOpenings if not r.RelatedOpeningElement.HasFillings
|
||||
]:
|
||||
opening_matrix = Matrix(ifcopenshell.util.placement.get_local_placement(opening.ObjectPlacement).tolist())
|
||||
opening_location = opening_matrix.col[3].to_3d()
|
||||
_, opening_position = mathutils.geometry.intersect_point_line(opening_location.to_2d(), *axis1["reference"])
|
||||
if opening_position > cut_percentage:
|
||||
# The opening should be removed from element1.
|
||||
ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening)
|
||||
|
||||
# Now let's check element2.
|
||||
for opening in [
|
||||
r.RelatedOpeningElement for r in element2.HasOpenings if not r.RelatedOpeningElement.HasFillings
|
||||
]:
|
||||
opening_matrix = Matrix(ifcopenshell.util.placement.get_local_placement(opening.ObjectPlacement).tolist())
|
||||
opening_location = opening_matrix.col[3].to_3d()
|
||||
_, opening_position = mathutils.geometry.intersect_point_line(opening_location.to_2d(), *axis1["reference"])
|
||||
if opening_position < cut_percentage:
|
||||
# The opening should be removed from element2.
|
||||
ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening)
|
||||
|
||||
# During the duplication process, filled voids are not copied. So we
|
||||
# only need to check fillings on the original element1.
|
||||
for opening in [r.RelatedOpeningElement for r in element1.HasOpenings if r.RelatedOpeningElement.HasFillings]:
|
||||
filling_obj = tool.Ifc.get_object(opening.HasFillings[0].RelatedBuildingElement)
|
||||
filling_location = filling_obj.matrix_world.translation
|
||||
_, filling_position = mathutils.geometry.intersect_point_line(filling_location.to_2d(), *axis1["reference"])
|
||||
if filling_position > cut_percentage:
|
||||
# The filling should be moved from element1 to element2.
|
||||
FilledOpeningGenerator().generate(filling_obj, wall2, target=filling_obj.matrix_world.translation)
|
||||
|
||||
axis1["reference"][1] = intersect
|
||||
axis2["reference"][0] = intersect
|
||||
self.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
|
||||
@@ -952,6 +965,15 @@ class DumbWallJoiner:
|
||||
element1 = tool.Ifc.get_entity(wall1)
|
||||
element2 = tool.Ifc.get_entity(slab2)
|
||||
|
||||
for rel in element1.ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.Description == "TOP":
|
||||
ifcopenshell.api.run(
|
||||
"geometry.disconnect_element",
|
||||
tool.Ifc.get(),
|
||||
relating_element=rel.RelatingElement,
|
||||
related_element=element1,
|
||||
)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"geometry.connect_element",
|
||||
tool.Ifc.get(),
|
||||
@@ -1166,10 +1188,13 @@ class DumbWallJoiner:
|
||||
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_body
|
||||
)
|
||||
|
||||
if tool.Ifc.is_moved(obj):
|
||||
wall_moved = tool.Ifc.is_moved(obj)
|
||||
if wall_moved:
|
||||
# Openings should move with the host overall ...
|
||||
# ... except their position should stay the same along the local X axis of the wall
|
||||
for opening in [r.RelatedOpeningElement for r in element.HasOpenings]:
|
||||
for opening in [
|
||||
r.RelatedOpeningElement for r in element.HasOpenings if not r.RelatedOpeningElement.HasFillings
|
||||
]:
|
||||
percent = tool.Cad.edge_percent(
|
||||
self.body[0], (previous_origin, (previous_matrix @ Vector((1, 0, 0))).to_2d())
|
||||
)
|
||||
@@ -1182,8 +1207,21 @@ class DumbWallJoiner:
|
||||
else:
|
||||
coordinates[0] -= change_in_x
|
||||
opening.ObjectPlacement.RelativePlacement.Location.Coordinates = coordinates
|
||||
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
# If opening has filling then stick to the filling's position
|
||||
# We're applying new openings position only after wall position is applied
|
||||
for opening in [r.RelatedOpeningElement for r in element.HasOpenings if r.RelatedOpeningElement.HasFillings]:
|
||||
filling_obj = tool.Ifc.get_object(opening.HasFillings[0].RelatedBuildingElement)
|
||||
filling_moved = tool.Ifc.is_moved(filling_obj)
|
||||
if filling_moved:
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=filling_obj)
|
||||
if filling_moved or wall_moved:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=filling_obj.matrix_world
|
||||
)
|
||||
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
|
||||
@@ -32,23 +32,13 @@ from bpy.props import FloatProperty, IntProperty, BoolProperty
|
||||
from bmesh.types import BMVert
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.helper import replace_ifc_representation_for_object
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
# TODO: move to some utils helpers/tool module
|
||||
def update_simple_openings(element, opening_width, opening_height):
|
||||
element_type = None
|
||||
ifc_file = tool.Ifc.get()
|
||||
if element.is_a("IfcElementType"):
|
||||
element_type = element
|
||||
fillings = ifcopenshell.util.element.get_types(element_type)
|
||||
else:
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
fillings = ifcopenshell.util.element.get_types(element_type)
|
||||
else:
|
||||
fillings = [element]
|
||||
fillings = tool.Ifc.get_all_element_occurences(element)
|
||||
|
||||
voided_objs = set()
|
||||
has_replaced_opening_representation = False
|
||||
@@ -148,13 +138,13 @@ def update_window_modifier_representation(context):
|
||||
elevation_representation = ifcopenshell.api.run(
|
||||
"geometry.add_window_representation", ifc_file, **representation_data
|
||||
)
|
||||
replace_ifc_representation_for_object(ifc_file, profile, obj, elevation_representation)
|
||||
tool.Model.replace_object_ifc_representation(profile, obj, elevation_representation)
|
||||
|
||||
# MODEL_VIEW representation
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
representation_data["context"] = body
|
||||
model_representation = ifcopenshell.api.run("geometry.add_window_representation", ifc_file, **representation_data)
|
||||
replace_ifc_representation_for_object(ifc_file, body, obj, model_representation)
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
|
||||
# PLAN_VIEW representation
|
||||
plan = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "Body", "PLAN_VIEW")
|
||||
@@ -163,7 +153,7 @@ def update_window_modifier_representation(context):
|
||||
plan_representation = ifcopenshell.api.run(
|
||||
"geometry.add_window_representation", ifc_file, **representation_data
|
||||
)
|
||||
replace_ifc_representation_for_object(ifc_file, plan, obj, plan_representation)
|
||||
tool.Model.replace_object_ifc_representation(plan, obj, plan_representation)
|
||||
|
||||
# adding switch representation at the end instead of changing order of representations
|
||||
# to prevent #2744
|
||||
@@ -177,8 +167,15 @@ def update_window_modifier_representation(context):
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
# type attributes
|
||||
element.PartitioningType = props.window_type
|
||||
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.OverallWidth = props.overall_width
|
||||
occurence.OverallHeight = props.overall_height
|
||||
|
||||
update_simple_openings(element, props.overall_width, props.overall_height)
|
||||
|
||||
|
||||
@@ -303,7 +300,7 @@ def update_window_modifier_bmesh(context):
|
||||
lining_to_panel_offset_x = props.lining_to_panel_offset_x * si_conversion
|
||||
lining_to_panel_offset_y = props.lining_to_panel_offset_y * si_conversion
|
||||
lining_thickness = props.lining_thickness * si_conversion
|
||||
lining_offset = props.lining_offset
|
||||
lining_offset = props.lining_offset * si_conversion
|
||||
|
||||
mullion_thickness = props.mullion_thickness * si_conversion / 2
|
||||
first_mullion_offset = props.first_mullion_offset * si_conversion
|
||||
@@ -409,12 +406,12 @@ def update_window_modifier_bmesh(context):
|
||||
obj.data.update()
|
||||
|
||||
|
||||
class BIM_OT_add_window(Operator):
|
||||
class BIM_OT_add_window(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_window"
|
||||
bl_label = "Window"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file:
|
||||
self.report({"ERROR"}, "You need to start IFC project first to create a window.")
|
||||
@@ -429,7 +426,6 @@ class BIM_OT_add_window(Operator):
|
||||
mesh = bpy.data.meshes.new("IfcWindow")
|
||||
obj = bpy.data.objects.new("IfcWindow", mesh)
|
||||
obj.location = spawn_location
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcWindow", should_add_representation=False
|
||||
)
|
||||
@@ -468,12 +464,8 @@ class AddWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
window_data["lining_properties"] = lining_props
|
||||
window_data["panel_properties"] = panel_props
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets.get("BBIM_Window", None)
|
||||
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Window")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Window")
|
||||
|
||||
ifcopenshell.api.run(
|
||||
@@ -494,8 +486,7 @@ class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
data = json.loads(psets["BBIM_Window"]["Data"])
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Window", "Data"))
|
||||
props = obj.BIMWindowProperties
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
@@ -525,8 +516,6 @@ class FinishEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMWindowProperties
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets["BBIM_Window"]
|
||||
window_data = props.get_general_kwargs()
|
||||
lining_props = props.get_lining_kwargs()
|
||||
panel_props = props.get_panel_kwargs()
|
||||
@@ -538,7 +527,7 @@ class FinishEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
update_window_modifier_representation(context)
|
||||
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Window")
|
||||
window_data = json.dumps(window_data, default=list)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": window_data})
|
||||
return {"FINISHED"}
|
||||
@@ -553,8 +542,7 @@ class EnableEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
props = obj.BIMWindowProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
pset = ifcopenshell.util.element.get_psets(element)
|
||||
data = json.loads(pset["BBIM_Window"]["Data"])
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Window", "Data"))
|
||||
data.update(data.pop("lining_properties"))
|
||||
data.update(data.pop("panel_properties"))
|
||||
|
||||
@@ -583,8 +571,7 @@ class RemoveWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
obj.BIMWindowProperties.is_editing = -1
|
||||
|
||||
pset = ifcopenshell.util.element.get_psets(element)
|
||||
pset = tool.Ifc.get().by_id(pset["BBIM_Window"]["id"])
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Window")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.window_added_previously = True
|
||||
|
||||
|
||||
@@ -24,8 +24,9 @@ import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
from blenderbim.bim.helper import prop_with_search, close_operator_panel
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.model import prop
|
||||
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
from blenderbim.bim.module.model.prop import get_ifc_class
|
||||
|
||||
|
||||
class BimTool(WorkSpaceTool):
|
||||
@@ -42,13 +43,23 @@ class BimTool(WorkSpaceTool):
|
||||
# ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
|
||||
# ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}),
|
||||
# ("bim.sync_modeling", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
|
||||
# replicate default blender selection behaviour with click and box selection
|
||||
# Replicate default blender selection behaviour with click and box selection
|
||||
# code below comes from blender_default.py which is part of default blender scripts licensed under GPL v2
|
||||
# https://github.com/blender/blender/blob/master/release/scripts/presets/keyconfig/keymap_data/blender_default.py
|
||||
# the code is the data from evaluating km_3d_view_tool_select() and km_3d_view_tool_select_box()
|
||||
# TODO: call blender_default.py functions instead of copying?
|
||||
# from evaluating km_3d_view_tool_select()
|
||||
# https://github.com/blender/blender/blob/4815d0706fb57d6e4f897dbb4e9aba9d2323cdce/release/scripts/presets/keyconfig/keymap_data/blender_default.py#L6710
|
||||
#
|
||||
# You can run the snippet below in Blender console
|
||||
# to regenerate those keybindings in case of errors in the future
|
||||
# ```
|
||||
# import os
|
||||
# version = ".".join(bpy.app.version_string.split(".")[:2])
|
||||
# fl = os.path.join(os.getcwd(), version, "scripts/presets/keyconfig/keymap_data/blender_default.py")
|
||||
# def_keymap = bpy.utils.execfile(fl)
|
||||
# params = def_keymap.Params
|
||||
# box_keymap = def_keymap.km_3d_view_tool_select_box(def_keymap.Params(), fallback=None)[2]["items"]
|
||||
# click_keymap = def_keymap.km_3d_view_tool_select(def_keymap.Params(select_mouse="LEFTMOUSE"), fallback=None)[2]["items"]
|
||||
# ```
|
||||
# box selection keymap
|
||||
("view3d.select_box", {"type": "LEFTMOUSE", "value": "CLICK_DRAG"}, None),
|
||||
(
|
||||
"view3d.select_box",
|
||||
@@ -65,32 +76,26 @@ class BimTool(WorkSpaceTool):
|
||||
{"type": "LEFTMOUSE", "value": "CLICK_DRAG", "shift": True, "ctrl": True},
|
||||
{"properties": [("mode", "AND")]},
|
||||
),
|
||||
# from evaluating km_3d_view_tool_select()
|
||||
# https://github.com/blender/blender/blob/4815d0706fb57d6e4f897dbb4e9aba9d2323cdce/release/scripts/presets/keyconfig/keymap_data/blender_default.py#L6734
|
||||
("view3d.select", {"type": "LEFTMOUSE", "value": "CLICK"}, {"properties": [("deselect_all", True)]}),
|
||||
("view3d.select", {"type": "LEFTMOUSE", "value": "CLICK", "shift": True}, {"properties": [("toggle", True)]}),
|
||||
("view3d.select", {"type": "LEFTMOUSE", "value": "CLICK", "alt": True}, {"properties": [("enumerate", True)]}),
|
||||
(
|
||||
"view3d.select",
|
||||
{"type": "LEFTMOUSE", "value": "CLICK", "shift": True, "alt": True},
|
||||
{"properties": [("toggle", True), ("enumerate", True)]},
|
||||
),
|
||||
# left-click selection keymap
|
||||
("view3d.select", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": [("deselect_all", True)]}),
|
||||
("view3d.select", {"type": "LEFTMOUSE", "value": "PRESS", "shift": True}, {"properties": [("toggle", True)]}),
|
||||
("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
|
||||
("bim.hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
|
||||
("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||
("bim.hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
|
||||
("bim.hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
|
||||
("bim.hotkey", {"type": "K", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_K")]}),
|
||||
("bim.hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
|
||||
("bim.hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
|
||||
("bim.hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
||||
("bim.hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
|
||||
("bim.hotkey", {"type": "K", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_K")]}),
|
||||
("bim.hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||
("bim.hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
|
||||
("bim.hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
|
||||
("bim.hotkey", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Y")]}),
|
||||
("bim.hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}),
|
||||
("bim.hotkey", {"type": "E", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_E")]}),
|
||||
("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
|
||||
("bim.hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
@@ -98,6 +103,13 @@ class BimTool(WorkSpaceTool):
|
||||
BimToolUI.draw(context, layout)
|
||||
|
||||
|
||||
def add_layout_hotkey_operator(layout, text, hotkey, description):
|
||||
op = layout.operator("bim.hotkey", text=text)
|
||||
op.hotkey = hotkey
|
||||
op.description = description
|
||||
return op
|
||||
|
||||
|
||||
class BimToolUI:
|
||||
@classmethod
|
||||
def draw(cls, context, layout):
|
||||
@@ -112,11 +124,6 @@ class BimToolUI:
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
|
||||
# if ifc_classes and relating_types_ids and not cls.props.icon_id and not is_tool_header:
|
||||
# # hack Dion won't like to show a preview also on the first time the sidebar is shown
|
||||
# bpy.app.timers.register(lambda: prop.update_ifc_class(cls.props, "lost_context"))
|
||||
# bpy.app.timers.register(lambda: prop.update_relating_type(cls.props, "lost_context"))
|
||||
|
||||
if context.region.type == "TOOL_HEADER":
|
||||
cls.draw_header_interface()
|
||||
elif context.region.type in ("UI", "WINDOW"):
|
||||
@@ -160,6 +167,11 @@ class BimToolUI:
|
||||
elif cls.props.ifc_class in ("IfcWindowType", "IfcWindowStyle", "IfcDoorType", "IfcDoorStyle"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl2", text="RL")
|
||||
elif cls.props.ifc_class in ("IfcSpaceType"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
add_layout_hotkey_operator(row, "Generate", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
|
||||
@classmethod
|
||||
def draw_edit_object_interface(cls, context):
|
||||
@@ -194,19 +206,15 @@ class BimToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_M")
|
||||
row.operator("bim.hotkey", text="Merge").hotkey = "S_M"
|
||||
add_layout_hotkey_operator(row, "Merge", "S_M", bpy.ops.bim.merge_wall.__doc__)
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_F")
|
||||
op = row.operator("bim.hotkey", text="Flip")
|
||||
op.hotkey = "S_F"
|
||||
op.bim_operator = "flip_wall"
|
||||
add_layout_hotkey_operator(row, "Flip", "S_F", bpy.ops.bim.flip_wall.__doc__)
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_K")
|
||||
op = row.operator("bim.hotkey", text="Split")
|
||||
op.hotkey = "S_K"
|
||||
op.bim_operator = "split_wall"
|
||||
add_layout_hotkey_operator(row, "Split", "S_K", bpy.ops.bim.split_wall.__doc__)
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_R")
|
||||
@@ -214,9 +222,11 @@ class BimToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
row.operator("bim.hotkey", text="Regen").hotkey = "S_G"
|
||||
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.recalculate_wall.__doc__)
|
||||
row.operator("bim.join_wall", icon="X", text="").join_type = ""
|
||||
|
||||
elif AuthoringData.data["active_material_usage"] == "LAYER3":
|
||||
# unnecessary check because BIM Tool is not available in EDIT mode?
|
||||
if context.active_object.mode == "OBJECT":
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
@@ -246,6 +256,10 @@ class BimToolUI:
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Extend").hotkey = "S_E"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_ALT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Edit Axis").hotkey = "A_E"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_T")
|
||||
row.operator("bim.hotkey", text="Butt").hotkey = "S_T"
|
||||
@@ -260,8 +274,9 @@ class BimToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
row.operator("bim.hotkey", text="Regen").hotkey = "S_G"
|
||||
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__)
|
||||
row.operator("bim.extend_profile", icon="X", text="").join_type = ""
|
||||
|
||||
elif AuthoringData.data["active_class"] in (
|
||||
"IfcWindow",
|
||||
"IfcWindowStandardCase",
|
||||
@@ -278,12 +293,18 @@ class BimToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
row.operator("bim.hotkey", text="Regen").hotkey = "S_G"
|
||||
|
||||
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.recalculate_fill.__doc__)
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_F")
|
||||
row.operator("bim.hotkey", text="Flip").hotkey = "S_F"
|
||||
|
||||
elif AuthoringData.data["active_class"] in ("IfcSpace",):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
|
||||
elif AuthoringData.data["active_class"] in (
|
||||
"IfcCableCarrierSegmentType",
|
||||
"IfcCableSegmentType",
|
||||
@@ -294,6 +315,32 @@ class BimToolUI:
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Extend", icon="EVENT_E")
|
||||
|
||||
elif (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and not context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Edit Railing Path").hotkey = "S_E"
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and not context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Edit Roof Path").hotkey = "S_E"
|
||||
|
||||
elif DecoratorData.get_ifc_text_data(bpy.context.object):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Edit text").hotkey = "S_E"
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_O")
|
||||
@@ -301,6 +348,7 @@ class BimToolUI:
|
||||
row.operator("bim.add_opening", text="Apply Void")
|
||||
else:
|
||||
row.operator("bim.add_potential_opening", text="Add Void")
|
||||
|
||||
if AuthoringData.data["is_voidable_element"]:
|
||||
if AuthoringData.data["has_visible_openings"]:
|
||||
row.operator("bim.edit_openings", icon="CHECKMARK", text="")
|
||||
@@ -320,13 +368,15 @@ class BimToolUI:
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Align Interior", icon="EVENT_V")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Mirror", icon="EVENT_M")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="Mode")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_ALT")
|
||||
row.label(text="", icon="EVENT_O")
|
||||
row.operator("bim.hotkey", text="Void").hotkey = "A_O"
|
||||
add_layout_hotkey_operator(row, "Void", "A_O", "Show / edit openings")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_ALT")
|
||||
row.label(text="", icon="EVENT_D")
|
||||
@@ -368,12 +418,13 @@ class BimToolUI:
|
||||
def draw_basic_bim_tool_interface(cls):
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
box = cls.layout.box()
|
||||
if AuthoringData.data["type_thumbnail"]:
|
||||
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
|
||||
else:
|
||||
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
|
||||
op.ifc_class = cls.props.ifc_class
|
||||
if cls.props.ifc_class:
|
||||
box = cls.layout.box()
|
||||
if AuthoringData.data["type_thumbnail"]:
|
||||
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
|
||||
else:
|
||||
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
|
||||
op.ifc_class = cls.props.ifc_class
|
||||
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
row = cls.layout.row(align=True)
|
||||
@@ -391,7 +442,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Hotkey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hotkey: bpy.props.StringProperty()
|
||||
bim_operator: bpy.props.StringProperty()
|
||||
description: bpy.props.StringProperty()
|
||||
x: bpy.props.FloatProperty(name="X", default=0.5)
|
||||
y: bpy.props.FloatProperty(name="Y", default=0.5)
|
||||
z: bpy.props.FloatProperty(name="Z", default=0.5)
|
||||
@@ -402,7 +453,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, operator):
|
||||
return "" if not operator.bim_operator else getattr(bpy.ops.bim, operator.bim_operator).__doc__
|
||||
return operator.description or ""
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = context.scene.BIMModelProperties
|
||||
@@ -413,20 +464,14 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if element:
|
||||
self.active_class = element.is_a()
|
||||
self.active_material_usage = tool.Model.get_usage_type(element)
|
||||
|
||||
material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
if material:
|
||||
if material.is_a("IfcMaterialLayerSetUsage"):
|
||||
self.active_material_usage = f"LAYER{material.LayerSetDirection[-1]}"
|
||||
elif material.is_a("IfcMaterialProfileSetUsage"):
|
||||
self.active_material_usage = "PROFILE"
|
||||
|
||||
try:
|
||||
self.has_ifc_class = bool(self.props.ifc_class)
|
||||
except:
|
||||
pass
|
||||
if get_ifc_class(None, None):
|
||||
try:
|
||||
self.has_ifc_class = bool(self.props.ifc_class)
|
||||
except:
|
||||
pass
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
|
||||
@@ -437,7 +482,6 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMModelProperties
|
||||
if self.hotkey == "S_O":
|
||||
row = self.layout.row()
|
||||
row.prop(self, "x")
|
||||
@@ -450,9 +494,14 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.add_constr_type_instance()
|
||||
|
||||
def hotkey_S_Q(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
|
||||
bpy.ops.bim.calculate_all_quantities()
|
||||
|
||||
def hotkey_S_C(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.align_wall(align_type="CENTERLINE")
|
||||
else:
|
||||
@@ -461,35 +510,110 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def hotkey_S_E(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
elif self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.join_wall(join_type="T")
|
||||
elif self.active_material_usage == "LAYER3":
|
||||
if not bpy.context.active_object:
|
||||
pass
|
||||
elif bpy.context.active_object.mode == "OBJECT":
|
||||
bpy.ops.bim.enable_editing_extrusion_profile()
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
|
||||
selected_usages = {}
|
||||
for obj in bpy.context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
usage = tool.Model.get_usage_type(element)
|
||||
if not usage:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
selected_usages.setdefault(usage, []).append(obj)
|
||||
|
||||
if len(bpy.context.selected_objects) == 1:
|
||||
if self.active_material_usage == "LAYER3":
|
||||
# Edit LAYER2 profile
|
||||
if bpy.context.active_object and bpy.context.active_object.mode == "OBJECT":
|
||||
bpy.ops.bim.enable_editing_extrusion_profile()
|
||||
elif self.active_material_usage == "LAYER2":
|
||||
# Extend LAYER2 to cursor
|
||||
bpy.ops.bim.join_wall(join_type="T")
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
# Extend PROFILE to cursor
|
||||
bpy.ops.bim.extend_profile(join_type="T")
|
||||
|
||||
elif self.active_material_usage == "LAYER2" and selected_usages.get("PROFILE", []):
|
||||
# Extend PROFILEs to LAYER2
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER3", [])]
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER2", []) if o != bpy.context.active_object]
|
||||
bpy.ops.bim.extend_profile(join_type="T")
|
||||
|
||||
elif self.active_material_usage == "LAYER3" and selected_usages.get("LAYER2", []):
|
||||
# Extend LAYER2s to LAYER3
|
||||
[o.select_set(False) for o in selected_usages.get("PROFILE", [])]
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER3", []) if o != bpy.context.active_object]
|
||||
bpy.ops.bim.join_wall(join_type="T")
|
||||
|
||||
elif self.active_material_usage == "LAYER2":
|
||||
# Extend LAYER2s to LAYER2
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER3", [])]
|
||||
[o.select_set(False) for o in selected_usages.get("PROFILE", [])]
|
||||
bpy.ops.bim.join_wall(join_type="T")
|
||||
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
# Extend PROFILEs to PROFILE
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER3", [])]
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER2", [])]
|
||||
bpy.ops.bim.extend_profile(join_type="T")
|
||||
|
||||
elif (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and not bpy.context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
# undo the unselection done above because railing has no usage type 🙃
|
||||
bpy.context.object.select_set(True)
|
||||
bpy.ops.bim.enable_editing_railing_path()
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and not bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
# undo the unselection done above because roof has no usage type
|
||||
bpy.context.object.select_set(True)
|
||||
bpy.ops.bim.enable_editing_roof_path()
|
||||
|
||||
elif DecoratorData.get_ifc_text_data(bpy.context.object):
|
||||
bpy.context.object.select_set(True)
|
||||
bpy.ops.bim.edit_text_popup()
|
||||
|
||||
def hotkey_S_F(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.flip_wall()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.flip_fill()
|
||||
|
||||
def hotkey_S_G(self):
|
||||
if not bpy.context.selected_objects:
|
||||
if self.props.ifc_class == "IfcSpaceType":
|
||||
bpy.ops.bim.generate_space()
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.recalculate_wall()
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.recalculate_profile()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.recalculate_fill()
|
||||
elif self.active_class in ("IfcSpace"):
|
||||
bpy.ops.bim.generate_space()
|
||||
|
||||
def hotkey_S_M(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.merge_wall()
|
||||
else:
|
||||
bpy.ops.bim.mirror_elements()
|
||||
|
||||
def hotkey_S_R(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.rotate_90(axis="Z")
|
||||
elif self.active_class in ("IfcColumn", "IfcColumnStandardCase"):
|
||||
@@ -498,22 +622,30 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.rotate_90(axis="Y")
|
||||
|
||||
def hotkey_S_K(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.split_wall()
|
||||
|
||||
def hotkey_S_T(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.join_wall(join_type="L")
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.extend_profile(join_type="L")
|
||||
|
||||
def hotkey_S_V(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.align_wall(align_type="INTERIOR")
|
||||
else:
|
||||
bpy.ops.bim.align_product(align_type="POSITIVE")
|
||||
|
||||
def hotkey_S_X(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
if bpy.ops.bim.align_wall.poll():
|
||||
bpy.ops.bim.align_wall(align_type="EXTERIOR")
|
||||
@@ -521,6 +653,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.align_product(align_type="NEGATIVE")
|
||||
|
||||
def hotkey_S_Y(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.join_wall(join_type="V")
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
@@ -537,9 +671,19 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.props.z = self.z
|
||||
|
||||
def hotkey_A_D(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
bpy.ops.bim.select_decomposition()
|
||||
|
||||
def hotkey_A_E(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.enable_editing_extrusion_axis()
|
||||
|
||||
def hotkey_A_O(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if AuthoringData.data["has_visible_openings"]:
|
||||
bpy.ops.bim.edit_openings()
|
||||
else:
|
||||
|
||||
@@ -290,6 +290,7 @@ class BIM_PT_object_actor(bpy.types.Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -72,4 +72,4 @@ class ProfileData:
|
||||
@classmethod
|
||||
def is_editing_arbitrary_profile(cls):
|
||||
obj = bpy.context.active_object
|
||||
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile
|
||||
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
|
||||
|
||||
@@ -21,7 +21,7 @@ import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.model.profile as model_profile
|
||||
from blenderbim.bim.module.model.slab import DecorationsHandler
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_profile
|
||||
|
||||
|
||||
@@ -138,8 +138,8 @@ class EnableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
DecorationsHandler.install(context)
|
||||
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
|
||||
ProfileDecorator.install(context)
|
||||
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
|
||||
|
||||
|
||||
class DisableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -149,8 +149,8 @@ class DisableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
if obj and obj.data and obj.data.BIMMeshProperties.is_profile:
|
||||
DecorationsHandler.uninstall()
|
||||
if obj and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
@@ -166,7 +166,7 @@ class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
obj = context.active_object
|
||||
|
||||
DecorationsHandler.uninstall()
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
profile = tool.Model.export_profile(obj)
|
||||
@@ -176,7 +176,7 @@ class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.layout.label(text="INVALID PROFILE: " + indices[1])
|
||||
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
DecorationsHandler.install(context)
|
||||
ProfileDecorator.install(context)
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
return
|
||||
|
||||
|
||||