Rename misleading "ifcblenderexport" directory to "blenderbim"
@@ -0,0 +1,340 @@
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
PYVERSION:=py37
|
||||
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/win-64/pythonocc-core-0.17.3-py37he980bc4_10.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/win-64/oce-0.17.2-he980bc4_14.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/DLR-SC/tbb/2019.5/download/win-64/tbb-2019.5-he980bc4_0.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/win-64/pythonocc-core-7.4.1-py39h3d1c7c5_0.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/win-64/occt-7.4.0-h823b557_3.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/win-64/tbb-2020.2-h2d74725_4.tar.bz2
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/osx-64/pythonocc-core-0.17.3-py37h04f5b5a_10.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/osx-64/oce-0.17.2-h04f5b5a_12.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/DLR-SC/tbb/4.3.6/download/osx-64/tbb-4.3.6-0.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/osx-64/pythonocc-core-7.4.1-py39h4d29fe3_0.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/osx-64/occt-7.4.0-hb9b6dc7_3.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/osx-64/tbb-2020.2-h940c156_4.tar.bz2
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/linux-64/pythonocc-core-0.17.3-py37h6bb024c_10.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/linux-64/oce-0.17.2-h6bb024c_14.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/DLR-SC/tbb/4.3.6/download/linux-64/tbb-4.3.6-0.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/linux-64/pythonocc-core-7.4.1-py39h465cb30_0.tar.bz2
|
||||
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/linux-64/occt-7.4.0-h9121d39_3.tar.bz2
|
||||
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/linux-64/tbb-2020.2-h4bd325d_4.tar.bz2
|
||||
endif
|
||||
endif
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
ifndef PLATFORM
|
||||
$(error PLATFORM is not set)
|
||||
endif
|
||||
rm -rf dist
|
||||
mkdir -p dist/working
|
||||
mkdir -p dist/blenderbim
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-517b819-$(PLATFORM)64.zip
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-517b819-$(PLATFORM)64.zip
|
||||
endif
|
||||
cd dist/working && unzip ifcblender*
|
||||
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
# See bug #812
|
||||
cd dist/working && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcblenderexport/occ_utils.py
|
||||
cd dist/working && mv occ_utils.py ../blenderbim/libs/site/packages/ifcopenshell/geom/occ_utils.py
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides IfcConvert for construction documentation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.6.0-517b819-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip IfcConvert*
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/IfcConvert.exe dist/blenderbim/libs/
|
||||
else
|
||||
cp -r dist/working/IfcConvert dist/blenderbim/libs/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Python OCC functionality for cutting IFC geometry for construction documentation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(PYTHONOCC_URL)
|
||||
cd dist/working && tar -xjvf pythonocc-core*
|
||||
ifeq ($(PLATFORM), win)
|
||||
cd dist/working && cp -r Lib/site-packages/OCC ../blenderbim/libs/site/packages/
|
||||
else
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && cp -r lib/python3.7/site-packages/OCC ../blenderbim/libs/site/packages/
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && cp -r lib/python3.9/site-packages/OCC ../blenderbim/libs/site/packages/
|
||||
endif
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by Python OCC
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(OCE_URL)
|
||||
cd dist/working && tar -xjvf oc*
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/Core/
|
||||
endif
|
||||
else
|
||||
# Unix Conda builds of PythonOCC expect OCE libs to have a RPATH of ../../../
|
||||
cd dist/working && cp -r lib/* ../blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), linux)
|
||||
rm -rf dist/blenderbim/libs/oce-0.17
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by OpenCascade
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(TBB_URL)
|
||||
cd dist/working && tar -xjvf tbb*
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/Core/
|
||||
endif
|
||||
else
|
||||
cd dist/working && cp -r lib/* ../blenderbim/libs/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://blenderbim.org/builds/patch-blender28-bim-macos.zip
|
||||
cd dist/working && unzip patch*
|
||||
cd dist/working && cp -r *.dylib ../blenderbim/libs/
|
||||
rm -rf dist/working
|
||||
endif
|
||||
|
||||
# Provides dependencies that are part of IfcOpenShell
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/archive/v0.6.0.zip
|
||||
cd dist/working && unzip v0.6.0.zip
|
||||
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/api dist/blenderbim/libs/site/packages/ifcopenshell/api
|
||||
# Provides bcf functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/bcf dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCClash functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/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 && ./env/bin/pybabel compile -d ./IfcOpenShell-0.6.0/src/ifcbimtester/bimtester/locale/
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcbimtester/bimtester dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCCOBie functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifccobie/* dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCDiff functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcdiff/* dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCCSV functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifccsv/* dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCPatch functionality
|
||||
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcpatch/ifcpatch dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Mustache templating in construction documentation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/d6/fd/eb8c212053addd941cc90baac307c00ac246ac3fce7166b86434c6eae963/pystache-0.5.4.tar.gz
|
||||
cd dist/working && tar -xzvf pystache*
|
||||
cd dist/working/pystache-0.5.4/ && python setup.py build && cp -r build/lib/pystache ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides SVG export in construction documentation
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/79/e8/7eb2ba188eda14a4b47e33b51f3b4978985f4116655c699bcd18c79279b5/svgwrite-1.3.1.zip
|
||||
cd dist/working && unzip svgwrite*
|
||||
cp -r dist/working/svgwrite-1.3.1/svgwrite dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCDiff
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/Moult/deepdiff/archive/master.zip
|
||||
cd dist/working && unzip master.zip
|
||||
cp -r dist/working/deepdiff-master/deepdiff dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by deepdiff
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/00/55/ce2cbc6d64034b30cad81a29ba61bdba456f190f5e83c09831304bf68d6b/jsonpickle-1.2.tar.gz
|
||||
cd dist/working && tar -xzvf jsonpickle*
|
||||
cp -r dist/working/jsonpickle-1.2/jsonpickle dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by deepdiff
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/a3/b7/d4d69641cbe707a45c23b190f2d717466ba5accc4c70b5f7a8a450387895/ordered-set-3.1.1.tar.gz
|
||||
cd dist/working && tar -xzvf ordered-set*
|
||||
cp -r dist/working/ordered-set-3.1.1/ordered_set.py dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by lark
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/00/32/8076fa13e832bb4dcff379f18f228e5a53412be0631808b9ca2610c0f566/pyparsing-2.4.5.tar.gz
|
||||
cd dist/working && tar -xzvf pyparsing*
|
||||
cp -r dist/working/pyparsing-2.4.5/pyparsing.py dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/bb/41/ad9ce53bb978b68af8ae415293cafc89b165b8ad55a593725299dca76729/xmlschema-1.1.1.tar.gz
|
||||
cd dist/working && tar -xzvf xmlschema*
|
||||
cp -r dist/working/xmlschema-1.1.1/xmlschema dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/12/f9/f9960222d5274944b01391749e55e4dcdf28d8f0c108b64ac931ceff6fdb/elementpath-1.4.3.tar.gz
|
||||
cd dist/working && tar -xzvf elementpath*
|
||||
cp -r dist/working/elementpath-1.4.3/elementpath dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/21/9f/b251f7f8a76dec1d6651be194dfba8fb8d7781d10ab3987190de8391d08e/six-1.14.0.tar.gz
|
||||
cd dist/working && tar -xzvf six*
|
||||
cp -r dist/working/six-1.14.0/six.py dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCSV and ifcopenshell.util.selector
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/18/4d/8d522136c37d9e1ea74062b41b8d5e1318ebf45063ae46ce72ed60af223b/lark-parser-0.8.5.tar.gz
|
||||
cd dist/working && tar -xzvf lark-parser*
|
||||
cp -r dist/working/lark-parser-0.8.5/lark dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCClash
|
||||
ifeq ($(PLATFORM), linux)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/0c/fa/00d85f893b02289e2942849d97f8818dde8e2111182e825fb3a735677791/python_fcl-0.0.12-cp37-cp37m-manylinux1_x86_64.whl
|
||||
cd dist/working && unzip python_fcl*
|
||||
cp -r dist/working/fcl dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/19/e6/6e9f33fb59e8f27c0e1592bd26e644bc92b85c942b072b2d3854105d5887/python_fcl_win32-0.0.12.post3-py3-none-win_amd64.whl
|
||||
cd dist/working && unzip python_fcl*
|
||||
cp -r dist/working/fcl dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
endif
|
||||
|
||||
# Required by BIMTester
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/c8/4b/d0a8c23b6c8985e5544ea96d27105a273ea22051317f850c2cdbf2029fe4/behave-1.2.6.tar.gz
|
||||
cd dist/working && tar -xzvf behave*
|
||||
cd dist/working/behave-1.2.6/ && cp -r behave ../../blenderbim/libs/site/packages/
|
||||
# See bug #1294
|
||||
cd dist/working/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/patch/model_core.patch
|
||||
cd dist/working/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/patch/runner_util.patch
|
||||
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/model_core.py < model_core.patch
|
||||
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by behave
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/f4/65/220bb4075fddb09d5b3ea2c1c1fa66c1c72be9361ec187aab50fa161e576/parse-1.15.0.tar.gz
|
||||
cd dist/working && tar -xzvf parse*
|
||||
cd dist/working/parse-1.15.0/ && cp parse.py ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by behave
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/2e/79/81bebd1b0446d46733db99d74543b4bb80646ef4c988584bae0862e706bc/parse_type-0.5.2.tar.gz
|
||||
cd dist/working && tar -xzvf parse_type*
|
||||
cd dist/working/parse_type-0.5.2/ && cp -r parse_type ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCOBie for XLSX support
|
||||
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*
|
||||
cd dist/working/XlsxWriter-1.2.9/ && cp -r xlsxwriter ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCOBie for ODS support
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/97/73/8ade73f6749177003f7ce3304f524774adda96e6aaab30ea79fd8fda7934/odfpy-1.4.1.tar.gz
|
||||
cd dist/working && tar -xzvf odfpy*
|
||||
cd dist/working/odfpy-1.4.1/ && cp -r odf ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by odfpy
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz
|
||||
cd dist/working && tar -xzvf defusedxml*
|
||||
cd dist/working/defusedxml-0.6.0/ && cp -r defusedxml ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by augin
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
|
||||
cd dist/working && tar -xzvf boto3*
|
||||
cd dist/working/boto3-1.17.17/ && cp -r boto3 ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/7f/2e/e02fdfd0f0377c4e44e61de27b05d5cfe93575770661ef9ded80ed90fa88/botocore-1.20.17.tar.gz
|
||||
cd dist/working && tar -xzvf botocore*
|
||||
cd dist/working/botocore-1.20.17/ && cp -r botocore ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz
|
||||
cd dist/working && tar -xzvf jmespath*
|
||||
cd dist/working/jmespath-0.10.0/ && cp -r jmespath ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/08/e1/3ee2096ebaeeb8c186d20ed16c8faf4a503913e5c9a0e14cd6b8ffc405a3/s3transfer-0.3.4.tar.gz
|
||||
cd dist/working && tar -xzvf s3transfer*
|
||||
cd dist/working/s3transfer-0.3.4/ && cp -r s3transfer ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides IFCJSON functionality
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
|
||||
cd dist/working && unzip master.zip
|
||||
cp -r dist/working/IFC2JSON_python-master/file_converters/ifcjson dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist && zip -r blender28-bim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf dist
|
||||
@@ -0,0 +1,127 @@
|
||||
# Blender BIM IFC
|
||||
|
||||
_Under heavy development!_
|
||||
|
||||
Let's use Blender to create BIM models, because why not?
|
||||
|
||||
## Features
|
||||
|
||||
- IFC4 is a first-class citizen. IFC4 has been an ISO standard since 2013, with
|
||||
the latest published in 2018. It contains many more features than IFC2X3,
|
||||
published back in 2005.
|
||||
- Available for Windows, Mac, and Linux!
|
||||
- Use open-source Blender 2.8 to do digital modeling! Used by NASA, Hollywood,
|
||||
and Ubisoft, Blender allows real-time modeling in rendered view, animations,
|
||||
file linking, proxy objects, Blender Cloud collaboration, lighting analysis,
|
||||
CFD, energy modeling, and a huge ecosystem of add-ons with a thriving
|
||||
community. No licensing, and with a strong Python API to integrate into your
|
||||
custom workflow.
|
||||
- Create complex spatial structures with multiple sites, buildings, facilities,
|
||||
bridges, and building parts. Sites can be broken down into sub-sites and
|
||||
multiple buildings per site. You can categorise different floors to different
|
||||
buildings.
|
||||
- Classify any geometry as any IFC type. Just because it is a wall doesn't
|
||||
mean you should be limited to modeling it in a certain way. Easily change
|
||||
from one type to another, switch relationships and aggregations, reuse or
|
||||
even remove geometry altogether, without damaging your geometry or breaking
|
||||
your BIM model.
|
||||
- Use collections to create element compositions and decompositions with full
|
||||
control over the classifications of their parts. Most BIM programs don't give
|
||||
this flexibility. Create collection instances to efficiently duplicate
|
||||
composite objects.
|
||||
- Allow BIM objects to instance a shared geometric representations. This allows
|
||||
you to place the same shape in multiple objects without drastically
|
||||
increasing your filesize.
|
||||
- Provide a custom value to any simple IFC attribute of an IFC product.
|
||||
- Create product types that regular IFC products can inherit attributes from.
|
||||
- Define your own property sets with control over exactly how the data in the
|
||||
property sets are stored using property set template definitions which you
|
||||
can download or create yourself. Property sets can apply to both types and
|
||||
objects, and object property sets can selectively override type property
|
||||
sets.
|
||||
- Applies array modifiers to create instances.
|
||||
- Basic quantity take-off support for costing.
|
||||
- Settings to toggle export of quantities or geometric representations.
|
||||
Exporting IFC data without geometric representations is much faster and a
|
||||
fraction of the filesize.
|
||||
- Maintain `GlobalId` between exports, so that you can manipulate the IFC
|
||||
reliably in other software while making changes.
|
||||
- Not all objects need full solid geometry. Create representations as 2D or 3D
|
||||
curves / wireframes for rapid and lightweight BIM development.
|
||||
- Create map conversions and coordinate reference system definitions to link
|
||||
BIM to GIS.
|
||||
- Export basic materials with surface and diffuse colours for representation.
|
||||
You can also link materials to an externally defined file, such as a `.blend`
|
||||
Cycles material, V-Ray material, or even Radiance material for lighting
|
||||
simulation.
|
||||
- Switch between different types of metric units when modeling.
|
||||
- Model multiple representations for different purposes for a single BIM
|
||||
object or type, such as footprints, axis, reference, or clearance geometry.
|
||||
You can export just the type of representation required for your usecase, or
|
||||
export multiple for the user to choose from.
|
||||
- Record qualitative objectives such as design intentions, health and safety
|
||||
strategies, or code compliance, performance solutions, and more, in the BIM
|
||||
model and directly associate them to objects or types.
|
||||
- Classify objects and types to different classification systems such as
|
||||
Uniclass, Omniclass, or your custom system.
|
||||
- Associate external document files such as plans, brochures, specifications,
|
||||
warranties, or anything to objects and types.
|
||||
- Specify swept solid geometries, allowing you to round-trip geometry
|
||||
manipulation with other BIM software which have more limited geometric tools,
|
||||
like Revit.
|
||||
- Create material layer sets, for objects which have materials in predefined
|
||||
layers with thicknesses.
|
||||
- Create material constituent sets, for objects which are made up of
|
||||
arbitrarily mixed ingredients.
|
||||
- Specify nested and hosted element relationships.
|
||||
- Specify predefined door attributes as defined in the IFC4 specification.
|
||||
- Specify predefined window attributes as defined in the IFC4 specification.
|
||||
- Specify surface styles either defined as part of a material or assigned
|
||||
directly to an object's representation.
|
||||
- Colour coding by IFC class to visually spot mistakes
|
||||
|
||||
Some more experimental features:
|
||||
|
||||
- Cut sections for documentation with OpenCascade
|
||||
- Export gbXML files that are linked to your IFC file to do energy analysis
|
||||
- QA module to generate Gherkin unit tests to validate your BIM models
|
||||
- HTML formatter to generate reports from QA results
|
||||
- COBie spreadsheet extraction to import into CAFM systems
|
||||
|
||||
## Demo
|
||||
|
||||
[See demo video](https://www.youtube.com/watch?v=iD3v3eu2AjY)
|
||||
|
||||
It's only just started, so everything can and will change. But if you want to
|
||||
check it out:
|
||||
|
||||
1. Install [Blender](https://www.blender.org/) 64-bit version.
|
||||
2. Download the latest packaged version of Blender BIM from
|
||||
[here](https://blenderbim.org/builds/).
|
||||
3. Launch Blender. Go to `Edit->Preferences->Add-ons->Install...`. Browse to
|
||||
the downloaded `.zip` file from step 2.
|
||||
4. Enable the checkbox next to the Import-Export IFC Blender plugin which will
|
||||
show up.
|
||||
5. All done! You will find an import and export IFC option in the `File` menu.
|
||||
|
||||
By default, Blender BIM will export whatever you have selected. The objects you
|
||||
have selected _must_ belong in an `IfcProject` and spatially contained, such as
|
||||
witihn an `IfcBuilding`. It must also follow the naming convention. Please see
|
||||
the screenshot below for an example scene.
|
||||
|
||||
A naming convention of `IfcClass/IfcName` is enforced. If you name your object
|
||||
`IfcWall/Foo`, it will turn into an `IfcWall` with the name attribute set to
|
||||
`Foo`. You can override object attributes using Blender's `Custom Properties`.
|
||||
Your objects must live in a top level `Collection` defined as a context class,
|
||||
usually something like `IfcProject/Foo`. You can then nest spatial structures
|
||||
underneath to create a hierarchy.
|
||||
|
||||
## Pictures
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
... this Blender files creates this IFC file ...
|
||||
|
||||

|
||||
@@ -0,0 +1,119 @@
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
class Object_OT_RenameObjects(Operator):
|
||||
bl_idname = "object.renameobjects"
|
||||
bl_label = "Rename Object(s)"
|
||||
|
||||
# Multi Object rename UI
|
||||
BNameCB: bpy.props.BoolProperty(name="Base Name:")
|
||||
BaseName: bpy.props.StringProperty(name="")
|
||||
PreFixCB: bpy.props.BoolProperty(name="Prefix:")
|
||||
PreFix: bpy.props.StringProperty(name="")
|
||||
RemFrst: bpy.props.BoolProperty(name="Remove First")
|
||||
DgtFrst: bpy.props.IntProperty(name="Digits")
|
||||
SuffixCB: bpy.props.BoolProperty(name="Suffix")
|
||||
Suffix: bpy.props.StringProperty(name="")
|
||||
RemLast: bpy.props.BoolProperty(name="Remove Last")
|
||||
DgtLast: bpy.props.IntProperty(name="Digits")
|
||||
NumbredCB: bpy.props.BoolProperty(name="Numbred")
|
||||
BaseNum: bpy.props.IntProperty(name="Base Number")
|
||||
Step: bpy.props.IntProperty(name="Step", default=1)
|
||||
findCB: bpy.props.BoolProperty(name="Replace")
|
||||
find: bpy.props.StringProperty(name="")
|
||||
replace: bpy.props.StringProperty(name="")
|
||||
# Single rename UI
|
||||
Name: bpy.props.StringProperty(name="Name")
|
||||
|
||||
def draw(self, ctx):
|
||||
SelCount = len(bpy.context.selected_objects)
|
||||
if SelCount > 1:
|
||||
box = self.layout.box()
|
||||
row = box.row()
|
||||
row.prop(self, "BNameCB")
|
||||
row.prop(self, "BaseName")
|
||||
row = box.row()
|
||||
row.prop(self, "PreFixCB")
|
||||
row.prop(self, "PreFix")
|
||||
row = box.row()
|
||||
row.prop(self, "RemFrst")
|
||||
row.prop(self, "DgtFrst")
|
||||
row = box.row()
|
||||
row.prop(self, "SuffixCB")
|
||||
row.prop(self, "Suffix")
|
||||
row = box.row()
|
||||
row.prop(self, "RemLast")
|
||||
row.prop(self, "DgtLast")
|
||||
row = box.row(align=True)
|
||||
row.prop(self, "NumbredCB")
|
||||
row.prop(self, "BaseNum")
|
||||
row.prop(self, "Step")
|
||||
row = box.row(align=True)
|
||||
row.prop(self, "findCB")
|
||||
row.prop(self, "find")
|
||||
row.prop(self, "replace")
|
||||
if SelCount == 1:
|
||||
box = self.layout.box()
|
||||
row = box.row()
|
||||
row.prop(self, "Name")
|
||||
if SelCount == 0:
|
||||
box = self.layout.box()
|
||||
row = box.row()
|
||||
row.label("No Selected Object")
|
||||
|
||||
def __init__(self):
|
||||
if len(bpy.context.selected_objects) == 1:
|
||||
self.Name = bpy.context.selected_objects[0].name
|
||||
|
||||
def execute(self, context):
|
||||
SelCount = len(bpy.context.selected_objects)
|
||||
if SelCount > 1:
|
||||
SelObj = bpy.context.selected_objects
|
||||
Index = self.BaseNum
|
||||
for i in range(0, SelCount):
|
||||
# Get Object Original Name #
|
||||
NewName = SelObj[i].name
|
||||
# Set the Base name #
|
||||
if self.BNameCB:
|
||||
NewName = self.BaseName
|
||||
# Remove First characters #
|
||||
if self.RemFrst:
|
||||
NewName = NewName[self.DgtFrst : self.DgtFrst + len(NewName)]
|
||||
# Remove Last Characters #
|
||||
if self.RemLast:
|
||||
NewName = NewName[1 : len(NewName) - self.DgtLast]
|
||||
# Add Prefix to the new name #
|
||||
if self.PreFixCB:
|
||||
NewName = self.PreFix + NewName
|
||||
# Add Suffix to the new name #
|
||||
if self.SuffixCB:
|
||||
NewName = NewName + self.Suffix
|
||||
# Add Digits to end of new name #
|
||||
if self.NumbredCB:
|
||||
NewName += str(Index)
|
||||
Index += self.Step
|
||||
# Find and Replace #
|
||||
if self.findCB:
|
||||
NewName = NewName.replace(self.find, self.replace)
|
||||
# Set the new name to the object #
|
||||
SelObj[i].name = NewName
|
||||
elif SelCount == 1:
|
||||
bpy.context.selected_objects[0].name = self.Name
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(Object_OT_RenameObjects)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(Object_OT_RenameObjects)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
After Width: | Height: | Size: 399 KiB |
@@ -0,0 +1,32 @@
|
||||
bl_info = {
|
||||
"name": "BlenderBIM",
|
||||
"description": "Author, import, and export files in the " "Industry Foundation Classes (.ifc) file format",
|
||||
"author": "Dion Moult, IfcOpenShell",
|
||||
"blender": (2, 80, 0),
|
||||
"version": (0, 0, 999999),
|
||||
"location": "File > Export, File > Import, Scene / Object / Material / Mesh Properties",
|
||||
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
|
||||
"category": "Import-Export",
|
||||
}
|
||||
|
||||
import os
|
||||
import site
|
||||
|
||||
|
||||
# process *.pth in /libs/site/packages to setup globally importable modules
|
||||
# 3 levels deep required by occ static ../../ path
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
site.addsitedir(os.path.join(cwd, "libs", "site", "packages"))
|
||||
|
||||
|
||||
# main import
|
||||
from .bim import *
|
||||
|
||||
|
||||
# Explicitely expose bim.xx when imported with from blenderbim import *
|
||||
# Other bim still are importable using explicit from blenderbim.bim import xxx
|
||||
__all__ = ["export_ifc", "import_ifc"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,2 @@
|
||||
# addon writes tmp stuff directly to its dir
|
||||
/data/
|
||||
@@ -0,0 +1,223 @@
|
||||
# Check if we are running in Blender before loading, to allow for multiprocessing
|
||||
import sys
|
||||
|
||||
bpy = sys.modules.get("bpy")
|
||||
|
||||
if bpy is not None:
|
||||
import bpy
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator, gizmos
|
||||
|
||||
modules = {
|
||||
"project": None,
|
||||
"search": None,
|
||||
"bcf": None,
|
||||
"root": None,
|
||||
"unit": None,
|
||||
"georeference": None,
|
||||
"context": None,
|
||||
"drawing": None,
|
||||
"attribute": None,
|
||||
"type": None,
|
||||
"spatial": None,
|
||||
"void": None,
|
||||
"aggregate": None,
|
||||
"geometry": None,
|
||||
"cobie": None,
|
||||
"sequence": None,
|
||||
"group": None,
|
||||
"structural": None,
|
||||
"material": None,
|
||||
"style": None,
|
||||
"layer": None,
|
||||
"model": None,
|
||||
"owner": None,
|
||||
"pset": None,
|
||||
"qto": None,
|
||||
"classification": None,
|
||||
"constraint": None,
|
||||
"document": None,
|
||||
"pset_template": None,
|
||||
"clash": None,
|
||||
"csv": None,
|
||||
"bimtester": None,
|
||||
"diff": None,
|
||||
"patch": None,
|
||||
"covetool": None,
|
||||
"augin": None,
|
||||
"debug": None,
|
||||
}
|
||||
|
||||
for name in modules.keys():
|
||||
modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}")
|
||||
|
||||
classes = [
|
||||
operator.OpenUri,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.ExportIFC,
|
||||
operator.ImportIFC,
|
||||
operator.SelectExternalMaterialDir,
|
||||
operator.AddSweptSolid,
|
||||
operator.RemoveSweptSolid,
|
||||
operator.AssignSweptSolidOuterCurve,
|
||||
operator.SelectSweptSolidOuterCurve,
|
||||
operator.AddSweptSolidInnerCurve,
|
||||
operator.SelectSweptSolidInnerCurves,
|
||||
operator.AssignSweptSolidExtrusion,
|
||||
operator.SelectSweptSolidExtrusion,
|
||||
operator.FetchExternalMaterial,
|
||||
operator.FetchObjectPassport,
|
||||
operator.CutSection,
|
||||
operator.AddSheet,
|
||||
operator.OpenSheet,
|
||||
operator.AddDrawingToSheet,
|
||||
operator.CreateSheets,
|
||||
operator.OpenView,
|
||||
operator.OpenViewCamera,
|
||||
operator.ActivateView,
|
||||
operator.OpenUpstream,
|
||||
operator.AddSectionPlane,
|
||||
operator.RemoveSectionPlane,
|
||||
operator.ReloadIfcFile,
|
||||
operator.AddIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.SelectDocIfcFile,
|
||||
operator.AddAnnotation,
|
||||
operator.GenerateReferences,
|
||||
operator.ResizeText,
|
||||
operator.AddVariable,
|
||||
operator.RemoveVariable,
|
||||
operator.PropagateTextData,
|
||||
operator.SetOverrideColour,
|
||||
operator.RemoveDrawing,
|
||||
operator.AddDrawingStyle,
|
||||
operator.RemoveDrawingStyle,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.ActivateDrawingStyle,
|
||||
operator.EditVectorStyle,
|
||||
operator.RemoveSheet,
|
||||
operator.AddSchedule,
|
||||
operator.RemoveSchedule,
|
||||
operator.SelectScheduleFile,
|
||||
operator.BuildSchedule,
|
||||
operator.AddScheduleToSheet,
|
||||
operator.SetViewportShadowFromSun,
|
||||
operator.AddDrawingStyleAttribute,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
operator.CopyPropertyToSelection,
|
||||
operator.CopyAttributeToSelection,
|
||||
operator.RefreshDrawingList,
|
||||
operator.CleanWireframes,
|
||||
operator.LinkIfc,
|
||||
operator.SnapSpacesTogether,
|
||||
operator.CopyGrid,
|
||||
operator.AddSectionsAnnotations,
|
||||
prop.StrProperty,
|
||||
prop.Attribute,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
prop.DrawingStyle,
|
||||
prop.Sheet,
|
||||
prop.BIMProperties,
|
||||
prop.DocProperties,
|
||||
prop.IfcParameter,
|
||||
prop.PsetQto,
|
||||
prop.GlobalId,
|
||||
prop.RepresentationItem,
|
||||
prop.BIMObjectProperties,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.SweptSolid,
|
||||
prop.ItemSlotMap,
|
||||
prop.BIMMeshProperties,
|
||||
prop.BIMCameraProperties,
|
||||
prop.BIMTextProperties,
|
||||
ui.BIM_PT_section_plane,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_text,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_PT_misc_utilities,
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_drawinglist,
|
||||
ui.BIM_UL_topics,
|
||||
ui.BIM_ADDON_preferences,
|
||||
gizmos.UglyDotGizmo,
|
||||
gizmos.DotGizmo,
|
||||
gizmos.DimensionLabelGizmo,
|
||||
gizmos.ExtrusionGuidesGizmo,
|
||||
gizmos.ExtrusionWidget
|
||||
]
|
||||
|
||||
for module in modules.values():
|
||||
classes.extend(module.classes)
|
||||
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)")
|
||||
|
||||
def on_register(scene):
|
||||
handler.setDefaultProperties(scene)
|
||||
bpy.app.handlers.depsgraph_update_post.remove(on_register)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
bpy.app.handlers.depsgraph_update_post.append(on_register)
|
||||
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
|
||||
bpy.app.handlers.save_pre.append(handler.storeIdMap)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
|
||||
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
|
||||
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) # Check if we need this
|
||||
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
|
||||
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
|
||||
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
|
||||
|
||||
for module in modules.values():
|
||||
module.register()
|
||||
|
||||
bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler)
|
||||
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
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.storeIdMap)
|
||||
bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
del bpy.types.Scene.BIMProperties
|
||||
del bpy.types.Scene.DocProperties
|
||||
del bpy.types.Object.BIMObjectProperties
|
||||
del bpy.types.Material.BIMObjectProperties
|
||||
del bpy.types.Collection.BIMObjectProperties # Check if we need this
|
||||
del bpy.types.Material.BIMMaterialProperties
|
||||
del bpy.types.Mesh.BIMMeshProperties
|
||||
del bpy.types.Curve.BIMMeshProperties
|
||||
del bpy.types.Camera.BIMMeshProperties
|
||||
del bpy.types.Camera.BIMCameraProperties
|
||||
del bpy.types.TextCurve.BIMTextProperties
|
||||
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
|
||||
|
||||
for module in reversed(list(modules.values())):
|
||||
module.unregister()
|
||||
|
||||
bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler)
|
||||
bpy.app.handlers.load_post.remove(handler.toggleDecorationsOnLoad)
|
||||
@@ -0,0 +1,167 @@
|
||||
import bpy
|
||||
import os
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
class Annotator:
|
||||
@staticmethod
|
||||
def get_svg_text_size(size):
|
||||
sizes = {
|
||||
"1.8": "2.97",
|
||||
"2.5": "4.13",
|
||||
"3.5": "5.78",
|
||||
"5.0": "8.25",
|
||||
"7.0": "11.55",
|
||||
}
|
||||
return float(sizes[str(size)])
|
||||
|
||||
@staticmethod
|
||||
def add_text(related_element=None):
|
||||
curve = bpy.data.curves.new(type="FONT", name="Plan/Annotation/PLAN_VIEW/Text")
|
||||
curve.body = "TEXT"
|
||||
obj = bpy.data.objects.new("IfcAnnotation/Text", curve)
|
||||
obj.matrix_world = bpy.context.scene.camera.matrix_world
|
||||
if related_element is None:
|
||||
location, _, _, _ = Annotator.get_placeholder_coords()
|
||||
else:
|
||||
obj.data.BIMTextProperties.related_element = related_element
|
||||
location = related_element.location
|
||||
obj.location = location
|
||||
obj.hide_render = True
|
||||
font = bpy.data.fonts.get("OpenGost TypeB TT")
|
||||
if not font:
|
||||
font = bpy.data.fonts.load(
|
||||
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
)
|
||||
font.name = "OpenGost Type B TT"
|
||||
obj.data.font = font
|
||||
obj.data.BIMTextProperties.font_size = "2.5"
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
collection.objects.link(obj)
|
||||
Annotator.resize_text(obj)
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def resize_text(text_obj):
|
||||
camera = None
|
||||
for obj in text_obj.users_collection[0].objects:
|
||||
if isinstance(obj.data, bpy.types.Camera):
|
||||
camera = obj
|
||||
break
|
||||
if not camera:
|
||||
return
|
||||
# This is a magic number for OpenGost
|
||||
font_size = 1.6 / 1000
|
||||
font_size *= float(text_obj.data.BIMTextProperties.font_size)
|
||||
|
||||
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
numerator, denominator = fraction.split("/")
|
||||
font_size /= float(numerator) / float(denominator)
|
||||
|
||||
text_obj.data.size = font_size
|
||||
|
||||
@staticmethod
|
||||
def add_line_to_annotation(obj, co1=None, co2=None):
|
||||
if co1 is None:
|
||||
co1, co2, _, _ = Annotator.get_placeholder_coords()
|
||||
co1 = obj.matrix_world.inverted() @ co1
|
||||
co2 = obj.matrix_world.inverted() @ co2
|
||||
if isinstance(obj.data, bpy.types.Mesh):
|
||||
obj.data.vertices.add(2)
|
||||
obj.data.vertices[-2].co = co1
|
||||
obj.data.vertices[-1].co = co2
|
||||
obj.data.edges.add(1)
|
||||
obj.data.edges[-1].vertices = (obj.data.vertices[-2].index, obj.data.vertices[-1].index)
|
||||
if isinstance(obj.data, bpy.types.Curve):
|
||||
polyline = obj.data.splines.new("POLY")
|
||||
polyline.points.add(1)
|
||||
polyline.points[-2].co = list(co1) + [1]
|
||||
polyline.points[-1].co = list(co2) + [1]
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_plane_to_annotation(obj):
|
||||
co1, co2, co3, co4 = Annotator.get_placeholder_coords()
|
||||
co1 = obj.matrix_world.inverted() @ co1 # bot left
|
||||
co2 = obj.matrix_world.inverted() @ co2 # top left
|
||||
co3 = obj.matrix_world.inverted() @ co3 # bot right
|
||||
co4 = obj.matrix_world.inverted() @ co4 # top right
|
||||
|
||||
obj.data.vertices.add(4)
|
||||
v1 = obj.data.vertices[-4]
|
||||
v2 = obj.data.vertices[-3]
|
||||
v3 = obj.data.vertices[-2]
|
||||
v4 = obj.data.vertices[-1]
|
||||
v1.co = co4
|
||||
v2.co = co2
|
||||
v3.co = co1
|
||||
v4.co = co3
|
||||
|
||||
obj.data.edges.add(4)
|
||||
e1 = obj.data.edges[-4]
|
||||
e2 = obj.data.edges[-3]
|
||||
e3 = obj.data.edges[-2]
|
||||
e4 = obj.data.edges[-1]
|
||||
e1.vertices = (v1.index, v2.index)
|
||||
e2.vertices = (v2.index, v3.index)
|
||||
e3.vertices = (v3.index, v4.index)
|
||||
e4.vertices = (v4.index, v1.index)
|
||||
|
||||
obj.data.loops.add(4)
|
||||
l1 = obj.data.loops[-4]
|
||||
l2 = obj.data.loops[-3]
|
||||
l3 = obj.data.loops[-2]
|
||||
l4 = obj.data.loops[-1]
|
||||
l1.vertex_index = v1.index
|
||||
l1.edge_index = e1.index
|
||||
l2.vertex_index = v2.index
|
||||
l2.edge_index = e2.index
|
||||
l3.vertex_index = v3.index
|
||||
l3.edge_index = e3.index
|
||||
l4.vertex_index = v4.index
|
||||
l4.edge_index = e4.index
|
||||
|
||||
obj.data.polygons.add(1)
|
||||
p1 = obj.data.polygons[-1]
|
||||
p1.vertices = (v1.index, v2.index, v3.index, v4.index)
|
||||
p1.loop_start = l1.index
|
||||
p1.loop_total = 4
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_annotation_obj(name, data_type):
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
for obj in collection.objects:
|
||||
if name in obj.name:
|
||||
return obj
|
||||
if data_type == "mesh":
|
||||
data = bpy.data.meshes.new("Plan/Annotation/PLAN_VIEW/" + name)
|
||||
elif data_type == "curve":
|
||||
data = bpy.data.curves.new("Plan/Annotation/PLAN_VIEW/" + name, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
obj = bpy.data.objects.new("IfcAnnotation/" + name, data)
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_placeholder_coords():
|
||||
camera = bpy.context.scene.camera
|
||||
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
|
||||
y = (
|
||||
camera.data.ortho_scale
|
||||
* (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x)
|
||||
/ 4
|
||||
)
|
||||
else:
|
||||
y = camera.data.ortho_scale / 4
|
||||
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
|
||||
x_offset = camera.matrix_world.to_quaternion() @ Vector((y/2, 0, 0))
|
||||
return (camera.location + z_offset,
|
||||
camera.location + z_offset + y_offset,
|
||||
camera.location + z_offset + x_offset,
|
||||
camera.location + z_offset + x_offset + y_offset)
|
||||
@@ -0,0 +1,542 @@
|
||||
import os
|
||||
import re
|
||||
import math
|
||||
import time
|
||||
import numpy
|
||||
import pickle
|
||||
import multiprocessing
|
||||
|
||||
try:
|
||||
from OCC.Core import (
|
||||
gp,
|
||||
Geom,
|
||||
Bnd,
|
||||
BRepBndLib,
|
||||
BRep,
|
||||
BRepPrimAPI,
|
||||
BRepAlgoAPI,
|
||||
BRepBuilderAPI,
|
||||
TopOpeBRepTool,
|
||||
TopOpeBRepBuild,
|
||||
ShapeExtend,
|
||||
GProp,
|
||||
BRepGProp,
|
||||
GC,
|
||||
ShapeAnalysis,
|
||||
TopTools,
|
||||
TopExp,
|
||||
TopAbs,
|
||||
HLRAlgo,
|
||||
HLRBRep,
|
||||
TopLoc,
|
||||
Bnd,
|
||||
BRepBndLib,
|
||||
BRepTools,
|
||||
TopoDS,
|
||||
GeomLProp,
|
||||
IntCurvesFace,
|
||||
)
|
||||
from OCC.Core.TopoDS import topods
|
||||
except ImportError:
|
||||
from OCC import (
|
||||
gp,
|
||||
Geom,
|
||||
Bnd,
|
||||
BRepBndLib,
|
||||
BRep,
|
||||
BRepPrimAPI,
|
||||
BRepAlgoAPI,
|
||||
BRepBuilderAPI,
|
||||
TopOpeBRepTool,
|
||||
TopOpeBRepBuild,
|
||||
ShapeExtend,
|
||||
GProp,
|
||||
BRepGProp,
|
||||
GC,
|
||||
ShapeAnalysis,
|
||||
TopTools,
|
||||
TopExp,
|
||||
TopAbs,
|
||||
HLRAlgo,
|
||||
HLRBRep,
|
||||
TopLoc,
|
||||
Bnd,
|
||||
BRepBndLib,
|
||||
BRepTools,
|
||||
TopoDS,
|
||||
GeomLProp,
|
||||
IntCurvesFace,
|
||||
)
|
||||
from OCC.TopoDS import topods
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.element
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
this_file = os.path.join(cwd, "cut_ifc.py")
|
||||
|
||||
|
||||
def get_booleaned_edges(shape):
|
||||
edges = []
|
||||
exp = TopExp.TopExp_Explorer(shape, TopAbs.TopAbs_EDGE)
|
||||
while exp.More():
|
||||
edges.append(topods.Edge(exp.Current()))
|
||||
exp.Next()
|
||||
return edges
|
||||
|
||||
|
||||
def connect_edges_into_wires(unconnected_edges):
|
||||
edges = TopTools.TopTools_HSequenceOfShape()
|
||||
edges_handle = TopTools.Handle_TopTools_HSequenceOfShape(edges)
|
||||
wires = TopTools.TopTools_HSequenceOfShape()
|
||||
wires_handle = TopTools.Handle_TopTools_HSequenceOfShape(wires)
|
||||
|
||||
for edge in unconnected_edges:
|
||||
edges.Append(edge)
|
||||
|
||||
ShapeAnalysis.ShapeAnalysis_FreeBounds.ConnectEdgesToWires(edges_handle, 1e-5, True, wires_handle)
|
||||
return wires_handle.GetObject()
|
||||
|
||||
|
||||
def do_cut(process_data):
|
||||
global_id, shape, section, trsf_data = process_data
|
||||
|
||||
axis = gp.gp_Ax2(
|
||||
gp.gp_Pnt(trsf_data["top_left_corner"][0], trsf_data["top_left_corner"][1], trsf_data["top_left_corner"][2]),
|
||||
gp.gp_Dir(trsf_data["projection"][0], trsf_data["projection"][1], trsf_data["projection"][2]),
|
||||
gp.gp_Dir(trsf_data["x_axis"][0], trsf_data["x_axis"][1], trsf_data["x_axis"][2]),
|
||||
)
|
||||
source = gp.gp_Ax3(axis)
|
||||
destination = gp.gp_Ax3(gp.gp_Pnt(0, 0, 0), gp.gp_Dir(0, 0, -1), gp.gp_Dir(1, 0, 0))
|
||||
transformation = gp.gp_Trsf()
|
||||
transformation.SetDisplacement(source, destination)
|
||||
|
||||
cut_polygons = []
|
||||
section = BRepAlgoAPI.BRepAlgoAPI_Section(section, shape).Shape()
|
||||
section_edges = get_booleaned_edges(section)
|
||||
if len(section_edges) <= 0:
|
||||
return cut_polygons
|
||||
wires = connect_edges_into_wires(section_edges)
|
||||
for i in range(wires.Length()):
|
||||
wire_shape = wires.Value(i + 1)
|
||||
|
||||
transformed_wire = BRepBuilderAPI.BRepBuilderAPI_Transform(wire_shape, transformation)
|
||||
wire_shape = transformed_wire.Shape()
|
||||
|
||||
wire = topods.Wire(wire_shape)
|
||||
face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(wire).Face()
|
||||
|
||||
points = []
|
||||
exp = BRepTools.BRepTools_WireExplorer(wire)
|
||||
while exp.More():
|
||||
point = BRep.BRep_Tool.Pnt(exp.CurrentVertex())
|
||||
points.append((point.X(), -point.Y()))
|
||||
exp.Next()
|
||||
cut_polygons.append({"global_id": global_id, "metadata": {}, "points": points})
|
||||
return cut_polygons
|
||||
|
||||
|
||||
class IfcCutter:
|
||||
def __init__(self):
|
||||
self.time = None
|
||||
self.selector = ifcopenshell.util.selector.Selector()
|
||||
self.product_shapes = []
|
||||
self.background_elements = []
|
||||
self.cut_polygons = []
|
||||
self.template_variables = {}
|
||||
self.metadata = {}
|
||||
self.data_dir = ""
|
||||
self.vector_style = ""
|
||||
self.ifc_filenames = []
|
||||
self.ifc_files = {}
|
||||
self.resolved_pixels = set()
|
||||
self.text_pickle_file = "text.pickle"
|
||||
self.metadata_pickle_file = "metadata.pickle"
|
||||
self.cut_pickle_file = "cut.pickle"
|
||||
self.should_recut = True
|
||||
self.should_recut_selected = True
|
||||
self.cut_objects = ""
|
||||
self.selected_global_ids = []
|
||||
self.should_extract = True
|
||||
self.diagram_name = None
|
||||
self.background_image = None
|
||||
self.section_box = {
|
||||
"projection": (0, 1, 0),
|
||||
"x_axis": (1, 0, 0),
|
||||
"y_axis": (0, 0, -1),
|
||||
"top_left_corner": (-2, 2, 8),
|
||||
"x": 14,
|
||||
"y": 9,
|
||||
"z": 2,
|
||||
"shape": None,
|
||||
"face": None,
|
||||
}
|
||||
|
||||
def cut(self):
|
||||
self.profile_code("Starting cut process")
|
||||
self.load_ifc_files()
|
||||
self.profile_code("Load IFC files")
|
||||
self.get_template_variables()
|
||||
self.profile_code("Get template variables")
|
||||
self.get_product_shapes()
|
||||
self.profile_code("Get product shapes")
|
||||
self.create_section_box()
|
||||
self.profile_code("Create section box")
|
||||
self.get_cut_polygons()
|
||||
self.profile_code("Get cut polygons")
|
||||
self.get_annotation()
|
||||
self.profile_code("Get annotation")
|
||||
self.get_cut_polygon_metadata()
|
||||
self.profile_code("Get cut polygon metadata")
|
||||
|
||||
def profile_code(self, message):
|
||||
if not self.time:
|
||||
self.time = time.time()
|
||||
print("{} :: {:.2f}".format(message, time.time() - self.time))
|
||||
self.time = time.time()
|
||||
|
||||
def load_ifc_files(self):
|
||||
if not self.should_recut and not self.should_extract:
|
||||
return
|
||||
|
||||
loaded_files = []
|
||||
for filename in self.ifc_filenames:
|
||||
print("Loading file {} ...".format(filename))
|
||||
if filename:
|
||||
self.ifc_files[filename] = ifcopenshell.open(filename)
|
||||
|
||||
def get_template_variables(self):
|
||||
if not self.should_extract:
|
||||
if os.path.isfile(self.text_pickle_file):
|
||||
with open(self.text_pickle_file, "rb") as text_file:
|
||||
self.template_variables = pickle.load(text_file)
|
||||
return
|
||||
|
||||
data = {}
|
||||
for text_obj in self.text_objs:
|
||||
text_obj_data = self.get_text_variables(text_obj)
|
||||
if text_obj_data:
|
||||
data[text_obj.name] = text_obj_data
|
||||
|
||||
with open(self.text_pickle_file, "wb") as text_file:
|
||||
pickle.dump(data, text_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
self.template_variables = data
|
||||
|
||||
def get_text_variables(self, text_obj):
|
||||
text_obj_data = {}
|
||||
text_body = text_obj.data.body
|
||||
related_element = text_obj.data.BIMTextProperties.related_element
|
||||
if not related_element:
|
||||
return
|
||||
global_id = related_element.BIMObjectProperties.attributes.get("GlobalId")
|
||||
if not global_id:
|
||||
return
|
||||
element = self.get_ifc_element(global_id.string_value)
|
||||
for variable in text_obj.data.BIMTextProperties.variables:
|
||||
if element:
|
||||
if "{{" in variable.prop_key:
|
||||
prop_key = variable.prop_key.split("{{")[1].split("}}")[0]
|
||||
prop_value = self.selector.get_element_value(element, prop_key)
|
||||
variable_value = eval(variable.prop_key.replace("{{" + prop_key + "}}", str(prop_value)))
|
||||
else:
|
||||
variable_value = self.selector.get_element_value(element, variable.prop_key)
|
||||
text_obj_data[variable.name] = variable_value
|
||||
return text_obj_data
|
||||
|
||||
def get_product_shapes(self):
|
||||
if not self.should_recut:
|
||||
return
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.USE_PYTHON_OPENCASCADE, True)
|
||||
products = []
|
||||
|
||||
for filename, ifc_file in self.ifc_files.items():
|
||||
shape_pickle = os.path.join(
|
||||
self.data_dir, "cache", "shapes", "{}.pickle".format(os.path.basename(filename))
|
||||
)
|
||||
shape_map = {}
|
||||
if self.should_recut_selected and os.path.isfile(shape_pickle):
|
||||
with open(shape_pickle, "rb") as shape_file:
|
||||
shape_map = pickle.load(shape_file)
|
||||
|
||||
products.extend(self.selector.parse(ifc_file, self.cut_objects))
|
||||
|
||||
selected_elements = []
|
||||
for i, product in enumerate(products):
|
||||
if (
|
||||
product.is_a("IfcOpeningElement")
|
||||
or product.is_a("IfcSite")
|
||||
or product.Representation is None
|
||||
or self.has_annotation(product)
|
||||
):
|
||||
continue
|
||||
try:
|
||||
if self.should_recut_selected and product.GlobalId in self.selected_global_ids:
|
||||
selected_elements.append(product)
|
||||
elif product.GlobalId in shape_map:
|
||||
shape = shape_map[product.GlobalId]
|
||||
self.add_product_shape(product, shape)
|
||||
else:
|
||||
selected_elements.append(product)
|
||||
except:
|
||||
print("Failed to create shape for {}".format(product))
|
||||
|
||||
if selected_elements:
|
||||
total = 0
|
||||
checkpoint = time.time()
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings, ifc_file, multiprocessing.cpu_count(), include=selected_elements
|
||||
)
|
||||
valid_file = iterator.initialize()
|
||||
if valid_file:
|
||||
while True:
|
||||
total += 1
|
||||
if total % 250 == 0:
|
||||
print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint))
|
||||
checkpoint = time.time()
|
||||
shape = iterator.get()
|
||||
shape_map[shape.data.guid] = shape.geometry
|
||||
self.add_product_shape(ifc_file.by_guid(shape.data.guid), shape.geometry)
|
||||
if not iterator.next():
|
||||
break
|
||||
|
||||
with open(shape_pickle, "wb") as shape_file:
|
||||
pickle.dump(shape_map, shape_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def add_product_shape(self, product, shape):
|
||||
self.product_shapes.append((product, shape))
|
||||
|
||||
def has_annotation(self, element):
|
||||
for representation in element.Representation.Representations:
|
||||
if (
|
||||
representation.ContextOfItems.ContextType == "Plan"
|
||||
and representation.ContextOfItems.ContextIdentifier == "Annotation"
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_section_box(self):
|
||||
top_left_corner = gp.gp_Pnt(
|
||||
self.section_box["top_left_corner"][0],
|
||||
self.section_box["top_left_corner"][1],
|
||||
self.section_box["top_left_corner"][2],
|
||||
)
|
||||
axis = gp.gp_Ax2(
|
||||
top_left_corner,
|
||||
gp.gp_Dir(
|
||||
self.section_box["projection"][0], self.section_box["projection"][1], self.section_box["projection"][2]
|
||||
),
|
||||
gp.gp_Dir(self.section_box["x_axis"][0], self.section_box["x_axis"][1], self.section_box["x_axis"][2]),
|
||||
)
|
||||
section_box = BRepPrimAPI.BRepPrimAPI_MakeBox(
|
||||
axis, self.section_box["x"], self.section_box["y"], self.section_box["z"]
|
||||
)
|
||||
self.section_box["shape"] = section_box.Shape()
|
||||
self.section_box["face"] = section_box.BottomFace()
|
||||
|
||||
source = gp.gp_Ax3(axis)
|
||||
self.transformation_data = {
|
||||
"top_left_corner": self.section_box["top_left_corner"],
|
||||
"projection": self.section_box["projection"],
|
||||
"x_axis": self.section_box["x_axis"],
|
||||
}
|
||||
destination = gp.gp_Ax3(gp.gp_Pnt(0, 0, 0), gp.gp_Dir(0, 0, -1), gp.gp_Dir(1, 0, 0))
|
||||
self.transformation_dest = destination
|
||||
self.transformation = gp.gp_Trsf()
|
||||
self.transformation.SetDisplacement(source, destination)
|
||||
|
||||
def get_bbox(self, shape):
|
||||
bbox = Bnd.Bnd_Box()
|
||||
BRepBndLib.brepbndlib_Add(shape, bbox)
|
||||
return bbox
|
||||
|
||||
def calculate_face_zpos(self, face):
|
||||
bbox = self.get_bbox(face)
|
||||
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
||||
zpos = zmin + ((zmax - zmin) / 2)
|
||||
return zpos, zmax
|
||||
|
||||
def get_booleaned_edges(self, shape):
|
||||
edges = []
|
||||
exp = TopExp.TopExp_Explorer(shape, TopAbs.TopAbs_EDGE)
|
||||
while exp.More():
|
||||
edges.append(topods.Edge(exp.Current()))
|
||||
exp.Next()
|
||||
return edges
|
||||
|
||||
def get_cut_polygons(self):
|
||||
if self.should_recut:
|
||||
self.get_fresh_cut_polygons()
|
||||
self.pickle_cut_polygons()
|
||||
else:
|
||||
self.get_pickled_cut_polygons()
|
||||
|
||||
def get_annotation(self):
|
||||
import mathutils
|
||||
|
||||
self.annotation_objs = []
|
||||
settings_2d = ifcopenshell.geom.settings()
|
||||
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
|
||||
settings_py = ifcopenshell.geom.settings()
|
||||
settings_py.set(settings_py.USE_PYTHON_OPENCASCADE, True)
|
||||
for ifc_file in self.ifc_files.values():
|
||||
for element in ifc_file.by_type("IfcElement"):
|
||||
annotation_representation = None
|
||||
box_representation = None
|
||||
if not element.Representation:
|
||||
continue # This can occur for aggregates
|
||||
for representation in element.Representation.Representations:
|
||||
if (
|
||||
representation.ContextOfItems.ContextType == "Plan"
|
||||
and representation.ContextOfItems.ContextIdentifier == "Annotation"
|
||||
):
|
||||
annotation_representation = representation
|
||||
elif (
|
||||
representation.ContextOfItems.ContextType == "Model"
|
||||
and representation.ContextOfItems.ContextIdentifier == "Box"
|
||||
):
|
||||
box_representation = representation
|
||||
if not annotation_representation or not box_representation:
|
||||
continue
|
||||
|
||||
# This is bad code. See bug #85 to make it slightly less bad.
|
||||
# Effectively if the bbox does not intersect with the camera
|
||||
# plane, then we should "continue" and not process the 2D
|
||||
# wireframe. This approach works but is not very smart.
|
||||
for subelement in ifc_file.traverse(box_representation):
|
||||
if subelement.is_a("IfcBoundingBox"):
|
||||
block = ifc_file.createIfcBlock(
|
||||
ifc_file.createIfcAxis2Placement3D(subelement.Corner, None, None),
|
||||
subelement.XDim,
|
||||
subelement.YDim,
|
||||
subelement.ZDim,
|
||||
)
|
||||
for inverse in ifc_file.get_inverse(subelement):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, subelement, block)
|
||||
element.Representation.Representations = [box_representation]
|
||||
shape = ifcopenshell.geom.create_shape(settings_py, element)
|
||||
|
||||
section = BRepAlgoAPI.BRepAlgoAPI_Section(self.section_box["face"], shape.geometry).Shape()
|
||||
section_edges = get_booleaned_edges(section)
|
||||
|
||||
if len(section_edges) <= 0:
|
||||
# The bounding box of the annotation object does not
|
||||
# intersect with the camera plane, so don't bother drawing
|
||||
# the annotation
|
||||
continue
|
||||
|
||||
# Monkey patch - see bug #771.
|
||||
element.Representation.Representations = [annotation_representation]
|
||||
shape = ifcopenshell.geom.create_shape(settings_2d, element)
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
e = geometry.edges
|
||||
v = geometry.verts
|
||||
m = shape.transformation.matrix.data
|
||||
mat = mathutils.Matrix(
|
||||
([m[0], m[1], m[2], 0], [m[3], m[4], m[5], 0], [m[6], m[7], m[8], 0], [m[9], m[10], m[11], 1])
|
||||
)
|
||||
mat.transpose()
|
||||
self.annotation_objs.append(
|
||||
{
|
||||
"raw": element,
|
||||
"classes": self.get_classes(element, "annotation"),
|
||||
"edges": [[e[i], e[i + 1]] for i in range(0, len(e), 2)],
|
||||
"vertices": [mat @ mathutils.Vector((v[i], v[i + 1], v[i + 2])) for i in range(0, len(v), 3)],
|
||||
}
|
||||
)
|
||||
|
||||
def get_cut_polygon_metadata(self):
|
||||
if not self.should_extract:
|
||||
if os.path.isfile(self.metadata_pickle_file):
|
||||
with open(self.metadata_pickle_file, "rb") as metadata_file:
|
||||
self.metadata = pickle.load(metadata_file)
|
||||
|
||||
for polygon in self.cut_polygons:
|
||||
if polygon["global_id"] in self.metadata:
|
||||
polygon["metadata"] = self.metadata[polygon["global_id"]]
|
||||
return
|
||||
|
||||
for polygon in self.cut_polygons:
|
||||
metadata = {"classes": self.get_classes(self.get_ifc_element(polygon["global_id"]), "cut")}
|
||||
self.metadata[polygon["global_id"]] = metadata
|
||||
polygon["metadata"] = metadata
|
||||
|
||||
with open(self.metadata_pickle_file, "wb") as metadata_file:
|
||||
pickle.dump(self.metadata, metadata_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def pickle_cut_polygons(self):
|
||||
with open(self.cut_pickle_file, "wb") as pickle_file:
|
||||
pickle.dump(self.cut_polygons, pickle_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def get_fresh_cut_polygons(self):
|
||||
process_data = [
|
||||
(p.GlobalId, s, self.section_box["face"], self.transformation_data) for p, s in self.product_shapes
|
||||
]
|
||||
|
||||
import bpy
|
||||
|
||||
if bpy.app.version > (2, 90, 0) and os.name == 'nt':
|
||||
# See bug #1148
|
||||
for data in process_data:
|
||||
results = do_cut(data)
|
||||
polygons = [r for r in results if r["points"]]
|
||||
self.cut_polygons.extend(polygons)
|
||||
else:
|
||||
multiprocessing.set_executable(bpy.app.binary_path_python)
|
||||
|
||||
with multiprocessing.Pool(9) as p:
|
||||
results = p.map(do_cut, process_data)
|
||||
for result in results:
|
||||
polygons = [p for p in result if p["points"]]
|
||||
self.cut_polygons.extend(polygons)
|
||||
|
||||
def get_polygon_metadata(self, polygon, position):
|
||||
polygon["metadata"] = {"classes": self.get_classes(self.get_ifc_element(polygon["global_id"]), position)}
|
||||
return polygon
|
||||
|
||||
def get_ifc_element(self, global_id):
|
||||
# TODO: make this less bad
|
||||
element = None
|
||||
for ifc_file in self.ifc_files.values():
|
||||
try:
|
||||
element = ifc_file.by_id(global_id)
|
||||
return element
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_classes(self, element, position):
|
||||
classes = [position, element.is_a()]
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material:
|
||||
classes.append(
|
||||
"material-{}".format(
|
||||
re.sub("[^0-9a-zA-Z]+", "", self.get_material_name(material))
|
||||
)
|
||||
)
|
||||
classes.append("globalid-{}".format(element.GlobalId))
|
||||
for attribute in self.attributes:
|
||||
result = self.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))
|
||||
)
|
||||
return classes
|
||||
|
||||
def get_material_name(self, element):
|
||||
if hasattr(element, "Name") and element.Name:
|
||||
return element.Name
|
||||
elif hasattr(element, "LayerSetName") and element.LayerSetName:
|
||||
return element.LayerSetName
|
||||
return "mat-" + str(element.id())
|
||||
|
||||
def get_pickled_cut_polygons(self):
|
||||
if os.path.isfile(self.cut_pickle_file):
|
||||
with open(self.cut_pickle_file, "rb") as pickle_file:
|
||||
self.cut_polygons = pickle.load(pickle_file)
|
||||
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"eevee": {
|
||||
"uri": "mpass/shader.blend"
|
||||
},
|
||||
"cycles": {
|
||||
"uri": "mpass/shader.blend"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('Pset_Custom.ifc','2020-01-01T00:00:00',(),(),'Sample','Sample',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
|
||||
DATA;
|
||||
#1= IFCPROPERTYSETTEMPLATE('05N2Dhepb9ouTCSmWBh22D',$,'Custom_Pset','An example custom pset.',.PSET_TYPEDRIVENOVERRIDE.,'IfcObject',(#2,#3));
|
||||
#2= IFCSIMPLEPROPERTYTEMPLATE('37EpTDq4v96gTZh196BBXQ',$,'NumericProperty','An example numeric property',.P_SINGLEVALUE.,'IfcCountMeasure','',$,$,$,$,.READWRITE.);
|
||||
#3= IFCSIMPLEPROPERTYTEMPLATE('22H7CzDvz1XfVHKkGWoYUK',$,'StringProperty','An example string property',.P_SINGLEVALUE.,'IfcLabel','',$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,27 @@
|
||||
* { stroke-linecap: round; }
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
|
||||
.background { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.hidden { stroke-dasharray: 3, 2; }
|
||||
.solid {}
|
||||
.leader { marker-end: url(#leader-marker); }
|
||||
.stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.break { fill: white; }
|
||||
.breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
|
||||
.material-blank { fill: white; }
|
||||
.material-diagonal1 { fill: url(#diagonal1); }
|
||||
.material-diagonal2 { fill: url(#diagonal2); }
|
||||
.material-diagonal3 { fill: url(#diagonal3); }
|
||||
.material-crosshatch1 { fill: url(#crosshatch1); }
|
||||
.material-crosshatch2 { fill: url(#crosshatch2); }
|
||||
.material-crosshatch3 { fill: url(#crosshatch3); }
|
||||
.material-brick { fill: url(#brick); }
|
||||
.material-earth { fill: url(#earth); }
|
||||
.material-glass { fill: url(#glass); }
|
||||
.material-liquid { fill: url(#liquid); }
|
||||
.material-grass { fill: url(#grass); }
|
||||
.material-honeycomb { fill: url(#honeycomb); }
|
||||
.material-sand { fill: url(#sand); }
|
||||
.material-concrete { fill: url(#concrete); stroke-width: 0.5; }
|
||||
.material-boundary { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12,4,3,4,3,4; }
|
||||
.IfcSpace { fill: none; stroke: none; }
|
||||
@@ -0,0 +1,10 @@
|
||||
.cut { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
|
||||
.background { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.hidden { stroke-dasharray: 3, 2; }
|
||||
.solid {}
|
||||
.leader { marker-end: url(#leader-marker); }
|
||||
.stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.break { fill: white; }
|
||||
.breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
|
||||
.IfcSpace { fill: none; stroke: none; }
|
||||
@@ -0,0 +1,62 @@
|
||||
<?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="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;" />
|
||||
<path d="M 1 0 L 1 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="eng-dimension-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;" />
|
||||
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="dimension-marker-start" markerHeight="30" markerWidth="20" refX="10" refY="15" orient="auto">
|
||||
<g>
|
||||
<path d="M 0 15 L 20 15" class="annotation" style="stroke-width:1;" />
|
||||
<path d="M 10 0 L 10 30" class="annotation" style="stroke-width:1;" />
|
||||
<path d="M 5 20 L 15 10" class="annotation" style="stroke-width:3;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="dimension-marker-end" markerHeight="30" markerWidth="20" refX="10" refY="15" orient="auto">
|
||||
<g>
|
||||
<path d="M 0 15 L 20 15" class="annotation" style="stroke-width:1;" />
|
||||
<path d="M 10 0 L 10 30" class="annotation" style="stroke-width:1;" />
|
||||
<path d="M 5 20 L 15 10" class="annotation" style="stroke-width:3;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="grid-marker" markerHeight="40" markerWidth="40" orient="auto" refX="20" refY="20">
|
||||
<circle cx="20" cy="20" fill="white" stroke="black" r="17" style="stroke-width: 2"/>
|
||||
</marker>
|
||||
<marker id="leader-marker" markerHeight="7" markerWidth="10" orient="auto" refX="10" refY="3.5">
|
||||
<path d="M 0 0 L 0 7 L 10 3.5" class="annotation" style="fill:black;" />
|
||||
</marker>
|
||||
<marker id="stair-marker-start" markerHeight="10" markerWidth="10" orient="auto" refX="5" refY="5">
|
||||
<circle cx="5" cy="5" fill="black" stroke="none" r="5"/>
|
||||
</marker>
|
||||
<marker id="stair-marker-end" markerHeight="42" markerWidth="21" orient="auto" refX="21" refY="21">
|
||||
<path d="M 0 0 L 21 21 L 0 42" class="annotation stair" style="stroke-width:2; marker-start: none; marker-end: none;" />
|
||||
</marker>
|
||||
<marker id="plan-level-marker" markerHeight="30" markerWidth="30" refX="15" refY="15">
|
||||
<g>
|
||||
<path d="M 15 0 L 15 30" class="annotation" style="stroke-width:1;" />
|
||||
<path d="M 0 15 L 30 15" class="annotation" style="stroke-width:1;" />
|
||||
<circle cx="15" cy="15" fill="white" stroke="black" r="7.5" style="stroke-width: 2"/>
|
||||
<path d="M 15 15 L 7.5 15 A 7.5 7.5 0 0 1 15 7.5 Z" fill="black"/>
|
||||
<path d="M 15 15 L 22.5 15 A 7.5 7.5 0 0 1 15 22.5 Z" fill="black"/>
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="section-level-marker" markerHeight="11" markerWidth="90" refX="20" refY="10">
|
||||
<g>
|
||||
<path d="M 0 1 L 20 1 L 10 11 Z" fill="black" />
|
||||
<path d="M 0 1 L 90 1" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="breakline-marker" markerHeight="20" markerWidth="15" refX="7.5" refY="10">
|
||||
<g>
|
||||
<path d="M 0 10 L 5 20 L 7.5 10" class="annotation" style="stroke-width:1; fill: white;" />
|
||||
<path d="M 7.5 10 L 10 0 L 15 10" class="annotation" style="stroke-width:1; fill: white;" />
|
||||
</g>
|
||||
</marker>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,10 @@
|
||||
<?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">
|
||||
<g id="rectangle-tag">
|
||||
<rect x="-6" y="-2.5" width="12" height="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="door-tag">
|
||||
<circle cy="1.75" r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="1.75" x2="5" y2="1.75" style="stroke:black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 572 B |
@@ -0,0 +1,208 @@
|
||||
<?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">
|
||||
<defs
|
||||
id="defs901" />
|
||||
<metadata
|
||||
id="metadata90683">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<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>
|
||||
<desc
|
||||
id="desc89610">/home/dion/Projects/IfcOpenShell/src/ifcblenderexport/titleblock.dxf - scale = 1.000000, origin = (0.000000, 0.000000), method = manual</desc>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.07874;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect1036"
|
||||
width="841"
|
||||
height="594"
|
||||
x="-4.3253262e-05"
|
||||
y="3.1719057e-05" />
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
id="g90667"
|
||||
style="stroke-width:0.94488188;stroke-miterlimit:4;stroke-dasharray:none">
|
||||
<rect
|
||||
y="-1046.7792"
|
||||
x="75.740562"
|
||||
height="2093.5583"
|
||||
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">{{number}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
id="g90669"
|
||||
style="stroke-width:3.77953" />
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
id="g90671"
|
||||
style="stroke-width:3.77953" />
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
id="g90673"
|
||||
style="stroke-width:3.77953" />
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
id="g90675"
|
||||
style="stroke-width:3.77953" />
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
id="g90677"
|
||||
style="stroke-width:3.77953" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,203 @@
|
||||
<?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">
|
||||
<defs
|
||||
id="defs901" />
|
||||
<metadata
|
||||
id="metadata90683">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<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>
|
||||
<desc
|
||||
id="desc89610">/home/dion/Projects/IfcOpenShell/src/ifcblenderexport/titleblock.dxf - scale = 1.000000, origin = (0.000000, 0.000000), method = manual</desc>
|
||||
<rect
|
||||
y="3.1753578e-05"
|
||||
x="-4.3300333e-05"
|
||||
height="420"
|
||||
width="594.64642"
|
||||
id="rect1036"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.07874;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000" />
|
||||
<rect
|
||||
y="20.25"
|
||||
x="20.25"
|
||||
height="379.5"
|
||||
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">{{number}}</tspan></text>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90669"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90671"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90673"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90675"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90677"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,207 @@
|
||||
<?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">
|
||||
<defs
|
||||
id="defs901" />
|
||||
<metadata
|
||||
id="metadata90683">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<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>
|
||||
<desc
|
||||
id="desc89610">/home/dion/Projects/IfcOpenShell/src/ifcblenderexport/titleblock.dxf - scale = 1.000000, origin = (0.000000, 0.000000), method = manual</desc>
|
||||
<rect
|
||||
y="3.1753578e-05"
|
||||
x="-4.3300333e-05"
|
||||
height="297"
|
||||
width="420"
|
||||
id="rect1036"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.07874;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000" />
|
||||
<rect
|
||||
y="10.249999"
|
||||
x="10.249999"
|
||||
height="276.5"
|
||||
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">{{number}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90669"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90671"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90673"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90675"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90677"
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,95 @@
|
||||
<?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"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="root"
|
||||
version="1.1"
|
||||
viewBox="0 0 50.222462 10"
|
||||
height="10mm"
|
||||
width="50.222462mm"
|
||||
sodipodi:docname="view-title.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
id="namedview14"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.2318262"
|
||||
inkscape:cx="61.584312"
|
||||
inkscape:cy="26.339719"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(-27.82024,-96.649986)"
|
||||
id="layer1">
|
||||
<circle
|
||||
r="4.8499999"
|
||||
cy="101.64999"
|
||||
cx="32.82024"
|
||||
id="path853"
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.30000001;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path855"
|
||||
d="m 37.67024,101.64999 40.37246,-1e-5"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<text
|
||||
id="text859"
|
||||
y="103.10933"
|
||||
x="32.822662"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24.80276871px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.96055365px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.26458332px"
|
||||
y="103.10933"
|
||||
x="32.822662"
|
||||
id="tspan857">{{no}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.71626091px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458329px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="39.12896"
|
||||
y="100.19173"
|
||||
id="text863"><tspan
|
||||
id="tspan861"
|
||||
x="39.12896"
|
||||
y="100.19173"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458329px">{{name}}</tspan></text>
|
||||
<text
|
||||
id="text867"
|
||||
y="105.0553"
|
||||
x="39.285267"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.71626282px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458332px"
|
||||
y="105.0553"
|
||||
x="39.285267"
|
||||
id="tspan865">{{scale}}</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,2 @@
|
||||
Name,foo
|
||||
TransomThickness,500
|
||||
|
@@ -0,0 +1,162 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import numpy as np
|
||||
import datetime
|
||||
import zipfile
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.api.root.remove_product as remove_product
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import addon_utils
|
||||
|
||||
|
||||
class IfcExporter:
|
||||
def __init__(self, ifc_export_settings):
|
||||
self.ifc_export_settings = ifc_export_settings
|
||||
|
||||
def export(self):
|
||||
self.file = IfcStore.get_file()
|
||||
self.set_header()
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
self.sync_object_placements_and_deletions()
|
||||
self.sync_edited_objects()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1]
|
||||
if extension == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
filename, ext = os.path.splitext(os.path.basename(self.ifc_export_settings.output_file))
|
||||
tmp_name = "{}.ifc".format(filename)
|
||||
tmp_file = os.path.join(unzipped_path, tmp_name)
|
||||
self.file.write(tmp_file)
|
||||
with zipfile.ZipFile(
|
||||
self.ifc_export_settings.output_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
|
||||
) as zf:
|
||||
zf.write(tmp_file)
|
||||
elif extension == "ifc":
|
||||
self.file.write(self.ifc_export_settings.output_file)
|
||||
elif extension == "ifcjson":
|
||||
import ifcjson
|
||||
|
||||
if self.ifc_export_settings.json_version == "4":
|
||||
jsonData = ifcjson.IFC2JSON4(self.file, self.ifc_export_settings.json_compact).spf2Json()
|
||||
with open(self.ifc_export_settings.output_file, "w") as outfile:
|
||||
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4)
|
||||
elif self.ifc_export_settings.json_version == "5a":
|
||||
jsonData = ifcjson.IFC2JSON5a(self.file, self.ifc_export_settings.json_compact).spf2Json()
|
||||
with open(self.ifc_export_settings.output_file, "w") as outfile:
|
||||
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4)
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
if bpy.data.filepath:
|
||||
bpy.ops.wm.save_mainfile()
|
||||
|
||||
def set_header(self):
|
||||
# TODO: add all metadata, pending bug #747
|
||||
self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
|
||||
self.file.wrapped_data.header.file_name.time_stamp = (
|
||||
datetime.datetime.utcnow()
|
||||
.replace(tzinfo=datetime.timezone.utc)
|
||||
.astimezone()
|
||||
.replace(microsecond=0)
|
||||
.isoformat()
|
||||
)
|
||||
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
|
||||
self.get_application_name(), self.get_application_version()
|
||||
)
|
||||
# TODO: reimplement. See #1222.
|
||||
# if self.owner_history:
|
||||
# if self.schema_version == "IFC2X3":
|
||||
# self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id
|
||||
# else:
|
||||
# self.file.wrapped_data.header.file_name.authorization = (
|
||||
# self.owner_history.OwningUser.ThePerson.Identification
|
||||
# )
|
||||
# else:
|
||||
# self.file.wrapped_data.header.file_name.authorization = "Nobody"
|
||||
|
||||
def sync_object_placements_and_deletions(self):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
to_delete = []
|
||||
|
||||
for guid, obj in IfcStore.guid_map.items():
|
||||
try:
|
||||
self.sync_object_placement(obj)
|
||||
except:
|
||||
pass
|
||||
if self.should_delete(guid, obj):
|
||||
to_delete.append(guid)
|
||||
|
||||
for guid in to_delete:
|
||||
product = self.file.by_id(guid)
|
||||
IfcStore.unlink_element(product)
|
||||
remove_product.Usecase(self.file, {"product": product}).execute()
|
||||
|
||||
def sync_edited_objects(self):
|
||||
for obj_name in IfcStore.edited_objs.copy():
|
||||
obj = bpy.data.objects.get(obj_name)
|
||||
if not obj:
|
||||
continue
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
|
||||
bpy.ops.bim.update_mesh_representation(obj=obj.name)
|
||||
IfcStore.edited_objs.clear()
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
blender_matrix = np.matrix(obj.matrix_world)
|
||||
ifc_matrix = ifcopenshell.util.placement.get_local_placement(
|
||||
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).ObjectPlacement
|
||||
)
|
||||
ifc_matrix[0][3] *= self.unit_scale
|
||||
ifc_matrix[1][3] *= self.unit_scale
|
||||
ifc_matrix[2][3] *= self.unit_scale
|
||||
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
ifc_matrix = ifcopenshell.util.geolocation.global2local(
|
||||
ifc_matrix,
|
||||
float(props.blender_eastings) * self.unit_scale,
|
||||
float(props.blender_northings) * self.unit_scale,
|
||||
float(props.blender_orthogonal_height) * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001):
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
def should_delete(self, guid, obj):
|
||||
try:
|
||||
# This will throw an exception if the Blender object no longer exists
|
||||
foo = obj.name
|
||||
return False
|
||||
except:
|
||||
return True
|
||||
|
||||
def get_application_name(self):
|
||||
return "BlenderBIM"
|
||||
|
||||
def get_application_version(self):
|
||||
return ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class IfcExportSettings:
|
||||
def __init__(self):
|
||||
self.logger = None
|
||||
self.output_file = None
|
||||
|
||||
@staticmethod
|
||||
def factory(context, output_file, logger):
|
||||
scene_bim = context.scene.BIMProperties
|
||||
settings = IfcExportSettings()
|
||||
settings.output_file = output_file
|
||||
settings.logger = logger
|
||||
return settings
|
||||
@@ -0,0 +1,400 @@
|
||||
import bpy
|
||||
import blf
|
||||
from bpy import types
|
||||
import math
|
||||
from mathutils import Vector, Matrix
|
||||
from mathutils import geometry
|
||||
import gpu, bgl
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
|
||||
from .shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
|
||||
|
||||
|
||||
"""Gizmos under the hood
|
||||
|
||||
## Transforms:
|
||||
|
||||
source/blender/windowmanager/gizmo/WM_gizmo_types.h
|
||||
matrix_basis -- "Transformation of this gizmo." = placement in scene
|
||||
matrix_offset -- "Custom offset from origin." = local transforms according to state/value
|
||||
matrix_space -- "The space this gizmo is being modified in." used by some gizmos for undefined purposes
|
||||
matrix_world -- final matrix, scaled according to viewport zoom and custom scale_basis
|
||||
|
||||
source/blender/windowmanager/gizmo/intern/wm_gizmo.c:WM_gizmo_calc_matrix_final_params
|
||||
final = space @ (autoscale * (basis @ offset))
|
||||
final = space @ (basis @ offset) -- if gizmo.use_draw_scale == False
|
||||
final = space @ ((autoscale * basis) @ offset) -- if gizmo.use_draw_offset_scale
|
||||
|
||||
source/blender/windowmanager/gizmo/intern/wm_gizmo.c:wm_gizmo_calculate_scale
|
||||
autoscale = gizmo.scale_basis * magic(preferences, matrix_space, matrix_basis, context.region_data)
|
||||
magic -- making 1.0 to match preferences.view.gizmo_size pixels (75 by default)
|
||||
|
||||
|
||||
## Selection
|
||||
|
||||
select_id -- apparently, id of a selectable part
|
||||
test_select -- expected to return id of selection, doesn't seem to work
|
||||
draw_select -- fake-draw of selection geometry for gpu-side cursor tracking
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# some geometries for Gizmo.custom_shape shaders
|
||||
|
||||
CUBE = (
|
||||
(+1, +1, +1), (-1, +1, +1), (+1, -1, +1), # top
|
||||
(+1, -1, +1), (-1, +1, +1), (-1, -1, +1),
|
||||
(+1, +1, +1), (+1, -1, +1), (+1, +1, -1), # right
|
||||
(+1, +1, -1), (+1, -1, +1), (+1, -1, -1),
|
||||
(+1, +1, +1), (+1, +1, -1), (-1, +1, +1), # back
|
||||
(-1, +1, +1), (+1, +1, -1), (-1, +1, -1),
|
||||
(-1, -1, -1), (-1, +1, -1), (+1, -1, -1), # bot
|
||||
(+1, -1, -1), (-1, +1, -1), (+1, +1, -1),
|
||||
(-1, -1, -1), (-1, -1, +1), (-1, +1, -1), # left
|
||||
(-1, +1, -1), (-1, -1, +1), (-1, +1, +1),
|
||||
(-1, -1, -1), (+1, -1, -1), (-1, -1, +1), # front
|
||||
(-1, -1, +1), (+1, -1, -1), (+1, -1, +1)
|
||||
)
|
||||
|
||||
DISC = (
|
||||
(0.0, 0.0, 0.0), (1.0, 0.0, 0), (0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844387, 0.49999999999999994, 0), (0.5000000000000001, 0.8660254037844386, 0),
|
||||
(0.0, 0.0, 0.0), (0.5000000000000001, 0.8660254037844386, 0), (6.123233995736766e-17, 1.0, 0),
|
||||
(0.0, 0.0, 0.0), (6.123233995736766e-17, 1.0, 0), (-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(0.0, 0.0, 0.0), (-0.4999999999999998, 0.8660254037844387, 0), (-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844385, 0.5000000000000003, 0), (-1.0, 1.2246467991473532e-16, 0),
|
||||
(0.0, 0.0, 0.0), (-1.0, 1.2246467991473532e-16, 0), (-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844388, -0.4999999999999997, 0), (-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(0.0, 0.0, 0.0), (-0.5000000000000004, -0.8660254037844384, 0), (-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.0, 0.0, 0.0), (-1.8369701987210297e-16, -1.0, 0), (0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.0, 0.0, 0.0), (0.49999999999999933, -0.866025403784439, 0), (0.8660254037844384, -0.5000000000000004, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844384, -0.5000000000000004, 0), (1.0, 0.0, 0),
|
||||
)
|
||||
|
||||
X3DISC = (
|
||||
(0.0, 0.0, 0.0), (1.0, 0.0, 0), (0.8660254037844387, 0.49999999999999994, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844387, 0.49999999999999994, 0), (0.5000000000000001, 0.8660254037844386, 0),
|
||||
(0.0, 0.0, 0.0), (0.5000000000000001, 0.8660254037844386, 0), (6.123233995736766e-17, 1.0, 0),
|
||||
(0.0, 0.0, 0.0), (6.123233995736766e-17, 1.0, 0), (-0.4999999999999998, 0.8660254037844387, 0),
|
||||
(0.0, 0.0, 0.0), (-0.4999999999999998, 0.8660254037844387, 0), (-0.8660254037844385, 0.5000000000000003, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844385, 0.5000000000000003, 0), (-1.0, 1.2246467991473532e-16, 0),
|
||||
(0.0, 0.0, 0.0), (-1.0, 1.2246467991473532e-16, 0), (-0.8660254037844388, -0.4999999999999997, 0),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844388, -0.4999999999999997, 0), (-0.5000000000000004, -0.8660254037844384, 0),
|
||||
(0.0, 0.0, 0.0), (-0.5000000000000004, -0.8660254037844384, 0), (-1.8369701987210297e-16, -1.0, 0),
|
||||
(0.0, 0.0, 0.0), (-1.8369701987210297e-16, -1.0, 0), (0.49999999999999933, -0.866025403784439, 0),
|
||||
(0.0, 0.0, 0.0), (0.49999999999999933, -0.866025403784439, 0), (0.8660254037844384, -0.5000000000000004, 0),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844384, -0.5000000000000004, 0), (1.0, 0.0, 0),
|
||||
(0.0, 0.0, 0.0), (0, 1.0, 0.0), (0, 0.8660254037844387, 0.49999999999999994),
|
||||
(0.0, 0.0, 0.0), (0, 0.8660254037844387, 0.49999999999999994), (0, 0.5000000000000001, 0.8660254037844386),
|
||||
(0.0, 0.0, 0.0), (0, 0.5000000000000001, 0.8660254037844386), (0, 6.123233995736766e-17, 1.0),
|
||||
(0.0, 0.0, 0.0), (0, 6.123233995736766e-17, 1.0), (0, -0.4999999999999998, 0.8660254037844387),
|
||||
(0.0, 0.0, 0.0), (0, -0.4999999999999998, 0.8660254037844387), (0, -0.8660254037844385, 0.5000000000000003),
|
||||
(0.0, 0.0, 0.0), (0, -0.8660254037844385, 0.5000000000000003), (0, -1.0, 1.2246467991473532e-16),
|
||||
(0.0, 0.0, 0.0), (0, -1.0, 1.2246467991473532e-16), (0, -0.8660254037844388, -0.4999999999999997),
|
||||
(0.0, 0.0, 0.0), (0, -0.8660254037844388, -0.4999999999999997), (0, -0.5000000000000004, -0.8660254037844384),
|
||||
(0.0, 0.0, 0.0), (0, -0.5000000000000004, -0.8660254037844384), (0, -1.8369701987210297e-16, -1.0),
|
||||
(0.0, 0.0, 0.0), (0, -1.8369701987210297e-16, -1.0), (0, 0.49999999999999933, -0.866025403784439),
|
||||
(0.0, 0.0, 0.0), (0, 0.49999999999999933, -0.866025403784439), (0, 0.8660254037844384, -0.5000000000000004),
|
||||
(0.0, 0.0, 0.0), (0, 0.8660254037844384, -0.5000000000000004), (0, 1.0, 0.0),
|
||||
(0.0, 0.0, 0.0), (0.0, 0, 1.0), (0.49999999999999994, 0, 0.8660254037844387),
|
||||
(0.0, 0.0, 0.0), (0.49999999999999994, 0, 0.8660254037844387), (0.8660254037844386, 0, 0.5000000000000001),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844386, 0, 0.5000000000000001), (1.0, 0, 6.123233995736766e-17),
|
||||
(0.0, 0.0, 0.0), (1.0, 0, 6.123233995736766e-17), (0.8660254037844387, 0, -0.4999999999999998),
|
||||
(0.0, 0.0, 0.0), (0.8660254037844387, 0, -0.4999999999999998), (0.5000000000000003, 0, -0.8660254037844385),
|
||||
(0.0, 0.0, 0.0), (0.5000000000000003, 0, -0.8660254037844385), (1.2246467991473532e-16, 0, -1.0),
|
||||
(0.0, 0.0, 0.0), (1.2246467991473532e-16, 0, -1.0), (-0.4999999999999997, 0, -0.8660254037844388),
|
||||
(0.0, 0.0, 0.0), (-0.4999999999999997, 0, -0.8660254037844388), (-0.8660254037844384, 0, -0.5000000000000004),
|
||||
(0.0, 0.0, 0.0), (-0.8660254037844384, 0, -0.5000000000000004), (-1.0, 0, -1.8369701987210297e-16),
|
||||
(0.0, 0.0, 0.0), (-1.0, 0, -1.8369701987210297e-16), (-0.866025403784439, 0, 0.49999999999999933),
|
||||
(0.0, 0.0, 0.0), (-0.866025403784439, 0, 0.49999999999999933), (-0.5000000000000004, 0, 0.8660254037844384),
|
||||
(0.0, 0.0, 0.0), (-0.5000000000000004, 0, 0.8660254037844384), (0.0, 0, 1.0),
|
||||
)
|
||||
|
||||
|
||||
class CustomGizmo():
|
||||
# FIXME: highliting/selection doesnt work
|
||||
def draw_very_custom_shape(self, ctx, custom_shape, select_id=None):
|
||||
# similar to draw_custom_shape
|
||||
shape, batch, shader = custom_shape
|
||||
|
||||
shader.bind()
|
||||
|
||||
if select_id is not None:
|
||||
gpu.select.load_id(select_id)
|
||||
else:
|
||||
if self.is_highlight:
|
||||
color = (*self.color_highlight, self.alpha_highlight)
|
||||
else:
|
||||
color = (*self.color, self.alpha)
|
||||
shader.uniform_float('color', color)
|
||||
shape.glenable()
|
||||
|
||||
shape.uniform_region(ctx)
|
||||
# shader.uniform_float('modelMatrix', self.matrix_world)
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.multiply_matrix(self.matrix_world)
|
||||
batch.draw()
|
||||
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
|
||||
|
||||
class OffsetHandle():
|
||||
"""Handling mouse to offset gizmo from base along Z axis"""
|
||||
# FIXME: works a bit weird for rotated objects
|
||||
|
||||
def invoke(self, ctx, event):
|
||||
self.init_value = self.target_get_value('offset') / self.scale_value
|
||||
coordz = self.project_mouse(ctx, event)
|
||||
if coordz is None:
|
||||
return {'CANCELLED'}
|
||||
self.init_coordz = coordz
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def modal(self, ctx, event, tweak):
|
||||
coordz = self.project_mouse(ctx, event)
|
||||
if coordz is None:
|
||||
return {'CANCELLED'}
|
||||
delta = coordz - self.init_coordz
|
||||
if 'PRECISE' in tweak:
|
||||
delta /= 10.0
|
||||
value = max(0, self.init_value + delta)
|
||||
value *= self.scale_value
|
||||
# ctx.area.header_text_set(f"coords: {self.init_coordz} - {coordz}, delta: {delta}, value: {value}")
|
||||
ctx.area.header_text_set(f"Depth: {value}")
|
||||
self.target_set_value('offset', value)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def project_mouse(self, ctx, event):
|
||||
"""Projecting mouse coords to local axis Z"""
|
||||
# logic from source/blender/editors/gizmo_library/gizmo_types/arrow3d_gizmo.c:gizmo_arrow_modal
|
||||
|
||||
mouse = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||
region = ctx.region
|
||||
region3d = ctx.region_data
|
||||
ray_orig = view3d_utils.region_2d_to_origin_3d(region, region3d, mouse)
|
||||
ray_norm = view3d_utils.region_2d_to_vector_3d(region, region3d, mouse)
|
||||
|
||||
# 'arrow' origin and direction
|
||||
base = Vector((0, 0, 0))
|
||||
axis = Vector((0, 0, 1))
|
||||
|
||||
# projecttion of the arrow to a plane, perpendicular to view ray
|
||||
axis_proj = axis - ray_norm * axis.dot(ray_norm)
|
||||
|
||||
# intersection of the axis with the plane through view origin perpendicular to the arrow projection
|
||||
coords = geometry.intersect_line_plane(base, axis, ray_orig, axis_proj)
|
||||
|
||||
return coords.z
|
||||
|
||||
def exit(self, ctx, cancel):
|
||||
if cancel:
|
||||
self.target_set_value('offset', self.init_value)
|
||||
else:
|
||||
self.group.update(ctx)
|
||||
|
||||
|
||||
class UglyDotGizmo(OffsetHandle, types.Gizmo):
|
||||
"""three orthogonal circles"""
|
||||
bl_idname = "BIM_GT_uglydot_3d"
|
||||
bl_target_properties = (
|
||||
{'id': 'offset', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
'scale_value',
|
||||
'custom_shape',
|
||||
'init_value',
|
||||
'init_coordz',
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
self.custom_shape = self.new_custom_shape(type='TRIS', verts=X3DISC)
|
||||
|
||||
def refresh(self):
|
||||
offset = self.target_get_value('offset') / self.scale_value
|
||||
self.matrix_offset.col[3][2] = offset # z-shift
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
self.draw_custom_shape(self.custom_shape)
|
||||
|
||||
def draw_select(self, ctx, select_id):
|
||||
self.refresh()
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
|
||||
|
||||
class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
|
||||
"""Single dot viewport-aligned"""
|
||||
# FIXME: make it selectable
|
||||
bl_idname = "BIM_GT_dot_2d"
|
||||
bl_target_properties = (
|
||||
{'id': 'offset', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
'scale_value',
|
||||
'custom_shape',
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
shader = DotsGizmoShader()
|
||||
self.custom_shape = shader, shader.batch(pos=((0, 0, 0),)), shader.prog
|
||||
self.use_draw_scale = False
|
||||
|
||||
def refresh(self):
|
||||
offset = self.target_get_value('offset') / self.scale_value
|
||||
self.matrix_offset.col[3][2] = offset # z-shifted
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape)
|
||||
|
||||
def draw_select(self, ctx, select_id):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape, select_id=select_id)
|
||||
|
||||
# doesn't get called
|
||||
# def test_select(self, ctx, location):
|
||||
# pass
|
||||
|
||||
|
||||
class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
|
||||
"""Extrusion guides
|
||||
|
||||
Noninteractive gizmo to indicate extrusion depth and planes.
|
||||
Draws main segment and orthogonal cross at endpoints.
|
||||
"""
|
||||
bl_idname = "BIM_GT_extrusion_guides"
|
||||
bl_target_properties = (
|
||||
{'id': 'depth', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
'scale_value',
|
||||
'custom_shape'
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
shader = ExtrusionGuidesShader()
|
||||
self.custom_shape = shader, shader.batch(pos=((0, 0, 0), (0, 0, 1))), shader.prog
|
||||
self.use_draw_scale = False
|
||||
|
||||
def refresh(self):
|
||||
depth = self.target_get_value('depth') / self.scale_value
|
||||
self.matrix_offset.col[2][2] = depth # z-scaled
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape)
|
||||
|
||||
|
||||
class DimensionLabelGizmo(types.Gizmo):
|
||||
"""Text label for a dimension"""
|
||||
# does not work properly, fonts are totally screwed up
|
||||
|
||||
bl_idname = "BIM_GT_dimension_label"
|
||||
bl_target_properties = (
|
||||
{'id': 'value', 'type': 'FLOAT', 'array_length': 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
'text_label'
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
def refresh(self, ctx):
|
||||
value = self.target_get_value('value')
|
||||
self.matrix_offset.col[3][2] = value * .5
|
||||
unit_system = ctx.scene.unit_settings.system
|
||||
self.text_label = bpy.utils.units.to_string(unit_system, 'LENGTH', value, 3, split_unit=False)
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh(ctx)
|
||||
self.draw_text(ctx)
|
||||
|
||||
def draw_text(self, ctx):
|
||||
font_id = 0
|
||||
font_size = 16
|
||||
dpi = ctx.preferences.system.dpi
|
||||
|
||||
# pos = self.matrix_world @ Vector((0, 0, 0, 1))
|
||||
# pos = Vector((0, 0, 0.5))
|
||||
# region = ctx.region
|
||||
# region3d = ctx.region_data
|
||||
# pos = view3d_utils.location_3d_to_region_2d(region, region3d, pos)
|
||||
|
||||
# text = self.text_label
|
||||
|
||||
blf.size(font_id, font_size, dpi)
|
||||
blf.position(font_id, 0, 0, 0)
|
||||
blf.color(font_id, *self.color, self.alpha)
|
||||
blf.draw(font_id, "ABC")
|
||||
|
||||
|
||||
class ExtrusionWidget(types.GizmoGroup):
|
||||
bl_idname = "bim.extrusion_widget"
|
||||
bl_label = "Extrusion Gizmos"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_options = {'3D', 'PERSISTENT', 'SHOW_MODAL_ALL'}
|
||||
|
||||
# FIXME: use proper scale from ifc value to blender units
|
||||
|
||||
@classmethod
|
||||
def poll(cls, ctx):
|
||||
obj = ctx.object
|
||||
return (obj and obj.type == 'MESH'
|
||||
and obj.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth') is not None)
|
||||
|
||||
def setup(self, ctx):
|
||||
target = ctx.object
|
||||
prop = target.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth')
|
||||
|
||||
basis = target.matrix_world.normalized()
|
||||
theme = ctx.preferences.themes[0].user_interface
|
||||
|
||||
gz = self.handle = self.gizmos.new('BIM_GT_uglydot_3d')
|
||||
gz.matrix_basis = basis
|
||||
gz.scale_basis = 0.1
|
||||
gz.color = gz.color_highlight = tuple(theme.gizmo_primary)
|
||||
gz.alpha = 0.5
|
||||
gz.alpha_highlight = 1.0
|
||||
gz.use_draw_modal = True
|
||||
gz.target_set_prop('offset', prop, 'value')
|
||||
gz.scale_value = 1000
|
||||
|
||||
gz = self.guides = self.gizmos.new('BIM_GT_extrusion_guides')
|
||||
gz.matrix_basis = basis
|
||||
gz.color = gz.color_highlight = tuple(theme.gizmo_secondary)
|
||||
gz.alpha = gz.alpha_highlight = 0.5
|
||||
gz.use_draw_modal = True
|
||||
gz.target_set_prop('depth', prop, 'value')
|
||||
gz.scale_value = 1000
|
||||
|
||||
# gz = self.label = self.gizmos.new('GIZMO_GT_dimension_label')
|
||||
# gz.matrix_basis = basis
|
||||
# gz.color = tuple(theme.gizmo_secondary)
|
||||
# gz.alpha = 0.5
|
||||
# gz.use_draw_modal = True
|
||||
# gz.target_set_prop('value', target.demo, 'depth')
|
||||
|
||||
def refresh(self, ctx):
|
||||
"""updating gizmos"""
|
||||
target = ctx.object
|
||||
basis = target.matrix_world.normalized()
|
||||
self.handle.matrix_basis = basis
|
||||
self.guides.matrix_basis = basis
|
||||
|
||||
def update(self, ctx):
|
||||
"""updating object"""
|
||||
bpy.ops.bim.update_parametric_representation()
|
||||
# parameters disappear after update
|
||||
# need to retrieve and rebind them again
|
||||
bpy.ops.bim.get_representation_ifc_parameters()
|
||||
target = ctx.object
|
||||
prop = target.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth')
|
||||
self.handle.target_set_prop('offset', prop, 'value')
|
||||
self.guides.target_set_prop('depth', prop, 'value')
|
||||
@@ -0,0 +1,202 @@
|
||||
import bpy
|
||||
import json
|
||||
import addon_utils
|
||||
import blenderbim.bim.decoration as decoration
|
||||
import ifcopenshell.api.owner.settings
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data as AttributeData
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
if (
|
||||
obj.mode != "OBJECT"
|
||||
or not obj.data
|
||||
or not isinstance(obj.data, bpy.types.Mesh)
|
||||
or not obj.data.BIMMeshProperties.ifc_definition_id
|
||||
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
||||
):
|
||||
return
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
|
||||
IfcStore.edited_objs.add(obj.name)
|
||||
|
||||
|
||||
def name_callback(obj, data):
|
||||
# Blender material names are up to 63 UTF-8 bytes
|
||||
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name or len(bytes(obj.name, "utf-8")) >= 63:
|
||||
return
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
|
||||
def subscribe_to(object, data_path, callback):
|
||||
subscribe_to = object.path_resolve(data_path, False)
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_to,
|
||||
owner=object,
|
||||
args=(
|
||||
object,
|
||||
data_path,
|
||||
),
|
||||
notify=callback,
|
||||
options={
|
||||
"PERSISTENT",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def purge_module_data():
|
||||
from blenderbim.bim import modules
|
||||
|
||||
for name in modules.keys():
|
||||
try:
|
||||
getattr(getattr(getattr(ifcopenshell.api, name), "data"), "Data").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
@persistent
|
||||
def loadIfcStore(scene):
|
||||
IfcStore.file = None
|
||||
IfcStore.schema = None
|
||||
props = bpy.context.scene.BIMProperties
|
||||
IfcStore.id_map = (
|
||||
{int(k): bpy.data.objects.get(v) for k, v in json.loads(props.id_map).items()} if props.id_map else {}
|
||||
)
|
||||
IfcStore.guid_map = (
|
||||
{k: bpy.data.objects.get(v) for k, v in json.loads(props.guid_map).items()} if props.id_map else {}
|
||||
)
|
||||
purge_module_data()
|
||||
|
||||
|
||||
@persistent
|
||||
def ensureIfcExported(scene):
|
||||
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
|
||||
# The invocation pops up a file select window.
|
||||
# This is non-blocking, therefore the Blend file is saved before we export.
|
||||
bpy.ops.export_ifc.bim("INVOKE_DEFAULT", should_force_resave=True)
|
||||
|
||||
|
||||
@persistent
|
||||
def storeIdMap(scene):
|
||||
try:
|
||||
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
|
||||
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
|
||||
except:
|
||||
# Regenerate maps. Is there a better solution for this? It seems fragile.
|
||||
file = IfcStore.get_file()
|
||||
IfcStore.id_map = {
|
||||
o.ifc_definition_id: o.name for o in bpy.data.objects if o.BIMObjectProperties.ifc_definition_id
|
||||
}
|
||||
IfcStore.guid_map = {
|
||||
file.by_id(i).GlobalId: n for i, n in IfcStore.id_map.items() if file.by_id(i).is_a("IfcRoot")
|
||||
}
|
||||
# Then attempt to store it again
|
||||
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
|
||||
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
|
||||
|
||||
|
||||
@persistent
|
||||
def setDefaultProperties(scene):
|
||||
ifcopenshell.api.owner.settings.get_person = (
|
||||
lambda: IfcStore.get_file().by_id(int(bpy.context.scene.BIMOwnerProperties.user_person))
|
||||
if bpy.context.scene.BIMOwnerProperties.user_person
|
||||
else None
|
||||
)
|
||||
ifcopenshell.api.owner.settings.get_organisation = (
|
||||
lambda: IfcStore.get_file().by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation))
|
||||
if bpy.context.scene.BIMOwnerProperties.user_organisation
|
||||
else None
|
||||
)
|
||||
ifcopenshell.api.owner.settings.settings["ApplicationIdentifier"] = "BlenderBIM"
|
||||
ifcopenshell.api.owner.settings.settings["ApplicationFullName"] = "BlenderBIM Add-on"
|
||||
ifcopenshell.api.owner.settings.settings["Version"] = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Technical"
|
||||
drawing_style.render_type = "VIEWPORT"
|
||||
drawing_style.raster_style = json.dumps(
|
||||
{
|
||||
"bpy.data.worlds[0].color": (1, 1, 1),
|
||||
"bpy.context.scene.render.engine": "BLENDER_WORKBENCH",
|
||||
"bpy.context.scene.render.film_transparent": False,
|
||||
"bpy.context.scene.display.shading.show_object_outline": True,
|
||||
"bpy.context.scene.display.shading.show_cavity": False,
|
||||
"bpy.context.scene.display.shading.cavity_type": "BOTH",
|
||||
"bpy.context.scene.display.shading.curvature_ridge_factor": 1,
|
||||
"bpy.context.scene.display.shading.curvature_valley_factor": 1,
|
||||
"bpy.context.scene.view_settings.view_transform": "Standard",
|
||||
"bpy.context.scene.display.shading.light": "FLAT",
|
||||
"bpy.context.scene.display.shading.color_type": "SINGLE",
|
||||
"bpy.context.scene.display.shading.single_color": (1, 1, 1),
|
||||
"bpy.context.scene.display.shading.show_shadows": False,
|
||||
"bpy.context.scene.display.shading.shadow_intensity": 0.5,
|
||||
"bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5),
|
||||
"bpy.context.scene.view_settings.use_curve_mapping": False,
|
||||
"space.overlay.show_wireframes": True,
|
||||
"space.overlay.wireframe_threshold": 0,
|
||||
"space.overlay.show_floor": False,
|
||||
"space.overlay.show_axis_x": False,
|
||||
"space.overlay.show_axis_y": False,
|
||||
"space.overlay.show_axis_z": False,
|
||||
"space.overlay.show_object_origins": False,
|
||||
"space.overlay.show_relationship_lines": False,
|
||||
}
|
||||
)
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Shaded"
|
||||
drawing_style.render_type = "VIEWPORT"
|
||||
drawing_style.raster_style = json.dumps(
|
||||
{
|
||||
"bpy.data.worlds[0].color": (1, 1, 1),
|
||||
"bpy.context.scene.render.engine": "BLENDER_WORKBENCH",
|
||||
"bpy.context.scene.render.film_transparent": False,
|
||||
"bpy.context.scene.display.shading.show_object_outline": True,
|
||||
"bpy.context.scene.display.shading.show_cavity": True,
|
||||
"bpy.context.scene.display.shading.cavity_type": "BOTH",
|
||||
"bpy.context.scene.display.shading.curvature_ridge_factor": 1,
|
||||
"bpy.context.scene.display.shading.curvature_valley_factor": 1,
|
||||
"bpy.context.scene.view_settings.view_transform": "Standard",
|
||||
"bpy.context.scene.display.shading.light": "STUDIO",
|
||||
"bpy.context.scene.display.shading.color_type": "MATERIAL",
|
||||
"bpy.context.scene.display.shading.single_color": (1, 1, 1),
|
||||
"bpy.context.scene.display.shading.show_shadows": True,
|
||||
"bpy.context.scene.display.shading.shadow_intensity": 0.5,
|
||||
"bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5),
|
||||
"bpy.context.scene.view_settings.use_curve_mapping": False,
|
||||
"space.overlay.show_wireframes": True,
|
||||
"space.overlay.wireframe_threshold": 0,
|
||||
"space.overlay.show_floor": False,
|
||||
"space.overlay.show_axis_x": False,
|
||||
"space.overlay.show_axis_y": False,
|
||||
"space.overlay.show_axis_z": False,
|
||||
"space.overlay.show_object_origins": False,
|
||||
"space.overlay.show_relationship_lines": False,
|
||||
}
|
||||
)
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Blender Default"
|
||||
drawing_style.render_type = "DEFAULT"
|
||||
bpy.ops.bim.save_drawing_style(index="2")
|
||||
|
||||
|
||||
@persistent
|
||||
def toggleDecorationsOnLoad(*args):
|
||||
toggle = bpy.context.scene.DocProperties.should_draw_decorations
|
||||
if toggle:
|
||||
decoration.DecorationsHandler.install(bpy.context)
|
||||
else:
|
||||
decoration.DecorationsHandler.uninstall()
|
||||
@@ -0,0 +1,411 @@
|
||||
import math
|
||||
from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
import bpy
|
||||
|
||||
|
||||
# TODO: Deprecate this in favour of ifcopenshell.util.unit
|
||||
|
||||
|
||||
class SIUnitHelper:
|
||||
prefixes = {
|
||||
"EXA": 1e18,
|
||||
"PETA": 1e15,
|
||||
"TERA": 1e12,
|
||||
"GIGA": 1e9,
|
||||
"MEGA": 1e6,
|
||||
"KILO": 1e3,
|
||||
"HECTO": 1e2,
|
||||
"DECA": 1e1,
|
||||
"DECI": 1e-1,
|
||||
"CENTI": 1e-2,
|
||||
"MILLI": 1e-3,
|
||||
"MICRO": 1e-6,
|
||||
"NANO": 1e-9,
|
||||
"PICO": 1e-12,
|
||||
"FEMTO": 1e-15,
|
||||
"ATTO": 1e-18,
|
||||
}
|
||||
unit_names = [
|
||||
"AMPERE",
|
||||
"BECQUEREL",
|
||||
"CANDELA",
|
||||
"COULOMB",
|
||||
"CUBIC_METRE",
|
||||
"DEGREE CELSIUS",
|
||||
"FARAD",
|
||||
"GRAM",
|
||||
"GRAY",
|
||||
"HENRY",
|
||||
"HERTZ",
|
||||
"JOULE",
|
||||
"KELVIN",
|
||||
"LUMEN",
|
||||
"LUX",
|
||||
"MOLE",
|
||||
"NEWTON",
|
||||
"OHM",
|
||||
"PASCAL",
|
||||
"RADIAN",
|
||||
"SECOND",
|
||||
"SIEMENS",
|
||||
"SIEVERT",
|
||||
"SQUARE METRE",
|
||||
"METRE",
|
||||
"STERADIAN",
|
||||
"TESLA",
|
||||
"VOLT",
|
||||
"WATT",
|
||||
"WEBER",
|
||||
]
|
||||
si_conversions = {
|
||||
"inch": 0.0254,
|
||||
"foot": 0.3048,
|
||||
"yard": 0.914,
|
||||
"mile": 1609,
|
||||
"square inch": 0.0006452,
|
||||
"square foot": 0.09290304,
|
||||
"square yard": 0.83612736,
|
||||
"acre": 4046.86,
|
||||
"square mile": 2588881,
|
||||
"cubic inch": 0.00001639,
|
||||
"cubic foot": 0.02831684671168849,
|
||||
"cubic yard": 0.7636,
|
||||
"litre": 0.001,
|
||||
"fluid ounce UK": 0.0000284130625,
|
||||
"fluid ounce US": 0.00002957353,
|
||||
"pint UK": 0.000568,
|
||||
"pint US": 0.000473,
|
||||
"gallon UK": 0.004546,
|
||||
"gallon US": 0.003785,
|
||||
"degree": math.pi / 180,
|
||||
"ounce": 0.02835,
|
||||
"pound": 0.454,
|
||||
"ton UK": 1016.0469088,
|
||||
"ton US": 907.18474,
|
||||
"lbf": 4.4482216153,
|
||||
"kip": 4448.2216153,
|
||||
"psi": 6894.7572932,
|
||||
"ksi": 6894757.2932,
|
||||
"minute": 60,
|
||||
"hour": 3600,
|
||||
"day": 86400,
|
||||
"btu": 1055.056,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_prefix(text):
|
||||
for prefix in SIUnitHelper.prefixes.keys():
|
||||
if prefix in text.upper():
|
||||
return prefix
|
||||
|
||||
@staticmethod
|
||||
def get_prefix_multiplier(text):
|
||||
if not text:
|
||||
return 1
|
||||
prefix = SIUnitHelper.get_prefix(text)
|
||||
if prefix:
|
||||
return SIUnitHelper.prefixes[prefix]
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def get_unit_name(text):
|
||||
for name in SIUnitHelper.unit_names:
|
||||
if name in text.upper().replace("METER", "METRE"):
|
||||
return name
|
||||
|
||||
@staticmethod
|
||||
def convert(value, from_prefix, from_unit, to_prefix, to_unit):
|
||||
"""Converts between length, area, and volume units
|
||||
|
||||
:param value: The numeric value you want to convert
|
||||
:type value: float
|
||||
:param from_prefix: A prefix from IfcSIPrefix. Can be None.
|
||||
:type from_prefix: string
|
||||
:param from_unit: IfcSIUnitName or IfcConversionBasedUnit.Name
|
||||
:type from_unit: string
|
||||
:param to_prefix: A prefix from IfcSIPrefix. Can be None.
|
||||
:type to_prefix: string
|
||||
:param to_unit: IfcSIUnitName or IfcConversionBasedUnit.Name
|
||||
:type to_unit: string
|
||||
"""
|
||||
if from_unit in SIUnitHelper.si_conversions:
|
||||
value *= SIUnitHelper.si_conversions[from_unit]
|
||||
elif from_prefix:
|
||||
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
|
||||
if "SQUARE" in from_unit:
|
||||
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
|
||||
elif "CUBIC" in from_unit:
|
||||
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
|
||||
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
|
||||
if to_unit in SIUnitHelper.si_conversions:
|
||||
return value * (1 / SIUnitHelper.si_conversions[to_unit])
|
||||
elif to_prefix:
|
||||
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
|
||||
if "SQUARE" in from_unit:
|
||||
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
|
||||
elif "CUBIC" in from_unit:
|
||||
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
|
||||
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
|
||||
return value
|
||||
|
||||
|
||||
# This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py
|
||||
# MeasureIt-ARCH is GPL-v3
|
||||
# In the future I will need to rewrite this to allow the user to have custom
|
||||
# settings for each annotation object, not read from Blender.
|
||||
def format_distance(value, isArea=False, hide_units=True):
|
||||
s_code = "\u00b2" # Superscript two THIS IS LEGACY (but being kept for when Area Measurements are re-implimented)
|
||||
|
||||
# Get Scene Unit Settings
|
||||
scaleFactor = bpy.context.scene.unit_settings.scale_length
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
unit_length = bpy.context.scene.unit_settings.length_unit
|
||||
|
||||
toInches = 39.3700787401574887
|
||||
inPerFoot = 11.999
|
||||
|
||||
if isArea:
|
||||
toInches = 1550
|
||||
inPerFoot = 143.999
|
||||
|
||||
value *= scaleFactor
|
||||
|
||||
# Imperial Formating
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = bpy.context.scene.BIMProperties.imperial_precision
|
||||
if precision == "NONE":
|
||||
precision = 256
|
||||
elif precision == "1":
|
||||
precision = 1
|
||||
elif "/" in precision:
|
||||
precision = int(precision.split("/")[1])
|
||||
|
||||
base = int(precision)
|
||||
decInches = value * toInches
|
||||
|
||||
# Seperate ft and inches
|
||||
# Unless Inches are the specified Length Unit
|
||||
if unit_length != "INCHES":
|
||||
feet = math.floor(decInches / inPerFoot)
|
||||
decInches -= feet * inPerFoot
|
||||
else:
|
||||
feet = 0
|
||||
|
||||
# Seperate Fractional Inches
|
||||
inches = math.floor(decInches)
|
||||
if inches != 0:
|
||||
frac = round(base * (decInches - inches))
|
||||
else:
|
||||
frac = round(base * (decInches))
|
||||
|
||||
# Set proper numerator and denominator
|
||||
if frac != base:
|
||||
numcycles = int(math.log2(base))
|
||||
for i in range(numcycles):
|
||||
if frac % 2 == 0:
|
||||
frac = int(frac / 2)
|
||||
base = int(base / 2)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
frac = 0
|
||||
inches += 1
|
||||
|
||||
# Check values and compose string
|
||||
if inches == 12:
|
||||
feet += 1
|
||||
inches = 0
|
||||
|
||||
if not isArea:
|
||||
tx_dist = ""
|
||||
if feet:
|
||||
tx_dist += str(feet) + "'"
|
||||
if feet and inches:
|
||||
tx_dist += " - "
|
||||
if inches:
|
||||
tx_dist += str(inches)
|
||||
if inches and frac:
|
||||
tx_dist += " "
|
||||
if frac:
|
||||
tx_dist += str(frac) + "/" + str(base)
|
||||
if inches or frac:
|
||||
tx_dist += '"'
|
||||
else:
|
||||
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
|
||||
|
||||
# METRIC FORMATING
|
||||
elif unit_system == "METRIC":
|
||||
precision = bpy.context.scene.BIMProperties.metric_precision
|
||||
if precision != 0:
|
||||
value = precision * round(float(value) / precision)
|
||||
|
||||
# Meters
|
||||
if unit_length == "METERS":
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
# Centimeters
|
||||
elif unit_length == "CENTIMETERS":
|
||||
fmt = "%1.1f"
|
||||
if hide_units is False:
|
||||
fmt += " cm"
|
||||
d_cm = value * (100)
|
||||
tx_dist = fmt % d_cm
|
||||
# Millimeters
|
||||
elif unit_length == "MILLIMETERS":
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
d_mm = value * (1000)
|
||||
tx_dist = fmt % d_mm
|
||||
|
||||
# Otherwise Use Adaptive Units
|
||||
else:
|
||||
if round(value, 2) >= 1.0:
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
else:
|
||||
if round(value, 2) >= 0.01:
|
||||
fmt = "%1.1f"
|
||||
if hide_units is False:
|
||||
fmt += " cm"
|
||||
d_cm = value * (100)
|
||||
tx_dist = fmt % d_cm
|
||||
else:
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
d_mm = value * (1000)
|
||||
tx_dist = fmt % d_mm
|
||||
if isArea:
|
||||
tx_dist += s_code
|
||||
else:
|
||||
tx_dist = fmt % value
|
||||
|
||||
return tx_dist
|
||||
|
||||
|
||||
def parse_diagram_scale(camera):
|
||||
"""Returns numeric value of scale"""
|
||||
if camera.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
_, fraction = camera.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
_, fraction = camera.BIMCameraProperties.diagram_scale.split("|")
|
||||
numerator, denominator = fraction.split("/")
|
||||
return float(numerator) / float(denominator)
|
||||
|
||||
|
||||
def get_project_collection(scene):
|
||||
"""Get main project collection"""
|
||||
|
||||
colls = [c for c in scene.collection.children if c.name.startswith('IfcProject')]
|
||||
if len(colls) != 1:
|
||||
raise RuntimeError("project collection missing or not unique")
|
||||
return colls[0]
|
||||
|
||||
|
||||
def get_active_drawing(scene):
|
||||
"""Get active drawing collection and camera"""
|
||||
props = scene.DocProperties
|
||||
if props.active_drawing_index is None or len(props.drawings) == 0:
|
||||
return None, None
|
||||
try:
|
||||
drawing = props.drawings[props.active_drawing_index]
|
||||
return scene.collection.children['Views'].children[f"IfcGroup/{drawing.name}"], drawing.camera
|
||||
except (KeyError, IndexError):
|
||||
raise RuntimeError("missing drawing collection")
|
||||
|
||||
|
||||
def ortho_view_frame(camera, margin=0.015):
|
||||
"""Calculates 2d bounding box of camera view area.
|
||||
|
||||
Similar to `bpy.types.Camera.view_frame`
|
||||
|
||||
:arg camera: camera of drawing
|
||||
:type camera: bpy.types.Camera + BIMCameraProperties
|
||||
:arg margin: margins, in scene units
|
||||
:type margin: float
|
||||
:return: (xmin, xmax, ymin, ymax, zmin, zmax) in local camera coordinates
|
||||
"""
|
||||
aspect = camera.BIMCameraProperties.raster_y / camera.BIMCameraProperties.raster_x
|
||||
size = camera.ortho_scale
|
||||
hwidth = size * .5
|
||||
hheight = size * .5 * aspect
|
||||
scale = parse_diagram_scale(camera)
|
||||
xmarg = margin * scale
|
||||
ymarg = margin * scale * aspect
|
||||
return (-hwidth + xmarg, hwidth - xmarg, -hheight + ymarg, hheight - ymarg, -camera.clip_start, -camera.clip_end)
|
||||
|
||||
|
||||
def almost_zero(v):
|
||||
return abs(v) < 1e-5
|
||||
|
||||
|
||||
def clip_segment(bounds, segm):
|
||||
"""Clipping line segment to bounds
|
||||
|
||||
:arg bounds: (xmin, xmax, ymin, ymax)
|
||||
:arg segm: 2 vertices of the segment
|
||||
:return: 2 new vertices of segment or None if segment outside the bounding box
|
||||
"""
|
||||
# Liang–Barsky algorithm
|
||||
|
||||
xmin, xmax, ymin, ymax, _, _ = bounds
|
||||
p1, p2 = segm
|
||||
|
||||
def clip_side(p, q):
|
||||
if almost_zero(p): # ~= 0, parallel to the side
|
||||
if q < 0:
|
||||
return None # outside
|
||||
else:
|
||||
return 0, 1 # inside
|
||||
|
||||
t = q / p # the intersection point
|
||||
|
||||
if p < 0: # entering
|
||||
return t, 1
|
||||
else: # leaving
|
||||
return 0, t
|
||||
|
||||
dlt = p2 - p1
|
||||
|
||||
tt = (
|
||||
clip_side(-dlt.x, p1.x - xmin), # left
|
||||
clip_side(+dlt.x, xmax - p1.x), # right
|
||||
clip_side(-dlt.y, p1.y - ymin), # bottom
|
||||
clip_side(+dlt.y, ymax - p1.y), # top
|
||||
)
|
||||
|
||||
if None in tt:
|
||||
return None
|
||||
|
||||
t1 = max(0, max(t[0] for t in tt))
|
||||
t2 = min(1, min(t[1] for t in tt))
|
||||
|
||||
if t1 >= t2:
|
||||
return None
|
||||
|
||||
p1c = p1 + dlt * t1
|
||||
p2c = p1 + dlt * t2
|
||||
|
||||
return p1c, p2c
|
||||
|
||||
|
||||
def elevate_segment(bounds, segm):
|
||||
"""Elevate line xy-perpendicular segment vertically
|
||||
|
||||
:arg bounds: (xmin, xmax, ymin, ymax)
|
||||
:arg segm: 2 vertices of the segment
|
||||
:return: 2 new vertices of segment or None if segment outside the bounding box
|
||||
"""
|
||||
_, _, ymin, ymax, zmin, _ = bounds
|
||||
p1, p2 = segm
|
||||
dlt = p2 - p1
|
||||
if not (almost_zero(dlt.x) and almost_zero(dlt.y)):
|
||||
return None
|
||||
x = p1.x
|
||||
return [Vector((x, ymin, zmin)), Vector((x, ymax, zmin))]
|
||||
@@ -0,0 +1,50 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.handler
|
||||
|
||||
|
||||
class IfcStore:
|
||||
path = ""
|
||||
file = None
|
||||
schema = None
|
||||
id_map = {}
|
||||
guid_map = {}
|
||||
edited_objs = set()
|
||||
pset_template_path = ""
|
||||
pset_template_file = None
|
||||
|
||||
@staticmethod
|
||||
def get_file():
|
||||
if IfcStore.file is None:
|
||||
IfcStore.path = bpy.context.scene.BIMProperties.ifc_file
|
||||
if IfcStore.path:
|
||||
try:
|
||||
IfcStore.file = ifcopenshell.open(IfcStore.path)
|
||||
except:
|
||||
IfcStore.file
|
||||
return IfcStore.file
|
||||
|
||||
@staticmethod
|
||||
def get_schema():
|
||||
if IfcStore.file is None:
|
||||
return
|
||||
elif IfcStore.schema is None:
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema)
|
||||
return IfcStore.schema
|
||||
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
IfcStore.id_map[element.id()] = obj
|
||||
if hasattr(element, "GlobalId"):
|
||||
IfcStore.guid_map[element.GlobalId] = obj
|
||||
obj.BIMObjectProperties.ifc_definition_id = element.id()
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
|
||||
|
||||
@staticmethod
|
||||
def unlink_element(element, obj=None):
|
||||
del IfcStore.id_map[element.id()]
|
||||
if hasattr(element, "GlobalId"):
|
||||
del IfcStore.guid_map[element.GlobalId]
|
||||
if obj:
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
from . import ui, operator
|
||||
|
||||
classes = (
|
||||
operator.AssignObject,
|
||||
operator.EnableEditingAggregate,
|
||||
operator.DisableEditingAggregate,
|
||||
operator.AddAggregate,
|
||||
ui.BIM_PT_aggregate,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
@@ -0,0 +1,88 @@
|
||||
import bpy
|
||||
import ifcopenshell.api.aggregate.assign_object as assign_object
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.aggregate.data import Data
|
||||
|
||||
|
||||
class AssignObject(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_object"
|
||||
bl_label = "Assign Object"
|
||||
relating_object: bpy.props.StringProperty()
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
related_object = bpy.data.objects.get(self.related_object) if self.related_object else bpy.context.active_object
|
||||
props = related_object.BIMObjectProperties
|
||||
relating_object = bpy.data.objects.get(self.relating_object) if self.relating_object else props.relating_object
|
||||
if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id:
|
||||
return {"FINISHED"}
|
||||
product = self.file.by_id(props.ifc_definition_id)
|
||||
assign_object.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"product": product,
|
||||
"relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id),
|
||||
},
|
||||
).execute()
|
||||
bpy.ops.bim.edit_object_placement(obj=related_object.name)
|
||||
Data.load(IfcStore.get_file(), props.ifc_definition_id)
|
||||
bpy.ops.bim.disable_editing_aggregate(obj=related_object.name)
|
||||
|
||||
spatial_collection = bpy.data.collections.get(related_object.name)
|
||||
relating_collection = bpy.data.collections.get(relating_object.name)
|
||||
if spatial_collection:
|
||||
self.remove_collection(bpy.context.scene.collection, spatial_collection)
|
||||
for collection in bpy.data.collections:
|
||||
if collection == relating_collection:
|
||||
collection.children.link(spatial_collection)
|
||||
continue
|
||||
self.remove_collection(collection, spatial_collection)
|
||||
else:
|
||||
for collection in related_object.users_collection:
|
||||
collection.objects.unlink(related_object)
|
||||
relating_collection.objects.link(related_object)
|
||||
return {"FINISHED"}
|
||||
|
||||
def remove_collection(self, parent, child):
|
||||
try:
|
||||
parent.children.unlink(child)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class EnableEditingAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_aggregate"
|
||||
bl_label = "Enable Editing Aggregate"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.active_object.BIMObjectProperties.relating_object = None
|
||||
bpy.context.active_object.BIMObjectProperties.is_editing_aggregate = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_aggregate"
|
||||
bl_label = "Disable Editing Aggregate"
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
obj.BIMObjectProperties.is_editing_aggregate = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.add_aggregate"
|
||||
bl_label = "Add Aggregate"
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
|
||||
bpy.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_object(related_object=obj.name, relating_object=aggregate.name)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,48 @@
|
||||
from bpy.types import Panel
|
||||
from ifcopenshell.api.aggregate.data import Data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_aggregate(Panel):
|
||||
bl_label = "IFC Aggregates"
|
||||
bl_idname = "BIM_PT_aggregate"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
|
||||
return False
|
||||
if props.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), props.ifc_definition_id)
|
||||
if not Data.products[props.ifc_definition_id]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return
|
||||
if props.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), props.ifc_definition_id)
|
||||
|
||||
if props.is_editing_aggregate:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "relating_object", text="")
|
||||
row.operator("bim.assign_object", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_aggregate", icon="X", text="")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
name = "{}/{}".format(
|
||||
Data.products[props.ifc_definition_id]["type"], Data.products[props.ifc_definition_id]["Name"]
|
||||
)
|
||||
if name == "None/None":
|
||||
name = "No Aggregate Found"
|
||||
row.label(text=name)
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
@@ -0,0 +1,22 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.EnableEditingAttributes,
|
||||
operator.DisableEditingAttributes,
|
||||
operator.EditAttributes,
|
||||
operator.GenerateGlobalId,
|
||||
prop.BIMAttributeProperties,
|
||||
ui.BIM_PT_object_attributes,
|
||||
ui.BIM_PT_material_attributes,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Object.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
|
||||
bpy.types.Material.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Object.BIMAttributeProperties
|
||||
del bpy.types.Material.BIMAttributeProperties
|
||||
@@ -0,0 +1,126 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.attribute.edit_attributes as edit_attributes
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data
|
||||
|
||||
|
||||
class EnableEditingAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_attributes"
|
||||
bl_label = "Enable Editing Attributes"
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
while len(props.attributes) > 0:
|
||||
props.attributes.remove(0)
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
new = props.attributes.add()
|
||||
if attribute["type"] == "entity":
|
||||
continue
|
||||
new.name = attribute["name"]
|
||||
new.is_null = attribute["is_null"]
|
||||
if attribute["type"] == "string" or attribute["type"] == "list":
|
||||
new.string_value = attribute["value"] or ""
|
||||
elif attribute["type"] == "integer":
|
||||
new.int_value = attribute["value"] or 0
|
||||
elif attribute["type"] == "float":
|
||||
new.float_value = attribute["value"] or 0.
|
||||
elif attribute["type"] == "enum":
|
||||
new.enum_items = json.dumps(attribute["enum_items"])
|
||||
if attribute["value"]:
|
||||
new.enum_value = attribute["value"]
|
||||
props.is_editing_attributes = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_attributes"
|
||||
bl_label = "Disable Editing Attributes"
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
props.is_editing_attributes = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_attributes"
|
||||
bl_label = "Edit Attributes"
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
attributes = {}
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
blender_attribute = props.attributes.get(attribute["name"])
|
||||
if not blender_attribute:
|
||||
continue
|
||||
if attribute["is_optional"] and blender_attribute.is_null:
|
||||
attributes[attribute["name"]] = None
|
||||
elif attribute["type"] == "string":
|
||||
attributes[attribute["name"]] = blender_attribute.string_value
|
||||
elif attribute["type"] == "list":
|
||||
values = blender_attribute.string_value[1:-1].split(", ")
|
||||
if attribute["list_type"] == "float":
|
||||
values = [float(v) for v in values]
|
||||
elif attribute["list_type"] == "integer":
|
||||
values = [int(v) for v in values]
|
||||
attributes[attribute["name"]] = values
|
||||
elif attribute["type"] == "integer":
|
||||
attributes[attribute["name"]] = blender_attribute.int_value
|
||||
elif attribute["type"] == "float":
|
||||
attributes[attribute["name"]] = blender_attribute.float_value
|
||||
elif attribute["type"] == "enum":
|
||||
attributes[attribute["name"]] = blender_attribute.enum_value
|
||||
product = self.file.by_id(oprops.ifc_definition_id)
|
||||
edit_attributes.Usecase(self.file, {
|
||||
"product": product,
|
||||
"attributes": attributes
|
||||
}).execute()
|
||||
if "Name" in attributes:
|
||||
new_name = "{}/{}".format(product.is_a(), product.Name or "Unnamed")
|
||||
collection = bpy.data.collections.get(obj.name)
|
||||
if collection:
|
||||
collection.name = new_name
|
||||
obj.name = new_name
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GenerateGlobalId(bpy.types.Operator):
|
||||
bl_idname = "bim.generate_global_id"
|
||||
bl_label = "Regenerate GlobalId"
|
||||
|
||||
def execute(self, context):
|
||||
index = bpy.context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
|
||||
if index >= 0:
|
||||
global_id = bpy.context.active_object.BIMAttributeProperties.attributes[index]
|
||||
else:
|
||||
global_id = bpy.context.active_object.BIMAttributeProperties.attributes.add()
|
||||
global_id.name = "GlobalId"
|
||||
global_id.data_type = "string"
|
||||
global_id.string_value = ifcopenshell.guid.new()
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,19 @@
|
||||
import bpy
|
||||
import blenderbim.bim.schema # refactor
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
class BIMAttributeProperties(PropertyGroup):
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
is_editing_attributes: BoolProperty(name="Is Editing Attributes")
|
||||
@@ -0,0 +1,102 @@
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data
|
||||
|
||||
|
||||
def draw_ui(context, layout, obj_type):
|
||||
obj = context.active_object if obj_type == "Object" else context.active_object.active_material
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
if oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
|
||||
if props.is_editing_attributes:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
op = row.operator("bim.disable_editing_attributes", icon="X", text="")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
if attribute["type"] == "entity":
|
||||
continue
|
||||
row = layout.row(align=True)
|
||||
blender_attribute = props.attributes.get(attribute["name"])
|
||||
if attribute["type"] == "string" or attribute["type"] == "list":
|
||||
row.prop(blender_attribute, "string_value", text=attribute["name"])
|
||||
elif attribute["type"] == "integer":
|
||||
row.prop(blender_attribute, "int_value", text=attribute["name"])
|
||||
elif attribute["type"] == "float":
|
||||
row.prop(blender_attribute, "float_value", text=attribute["name"])
|
||||
elif attribute["type"] == "enum":
|
||||
row.prop(blender_attribute, "enum_value", text=attribute["name"])
|
||||
|
||||
if attribute["name"] == "GlobalId":
|
||||
row.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
|
||||
if attribute["is_optional"]:
|
||||
row.prop(
|
||||
blender_attribute,
|
||||
"is_null",
|
||||
icon="RADIOBUT_OFF" if blender_attribute.is_null else "RADIOBUT_ON",
|
||||
text="",
|
||||
)
|
||||
# TODO: reimplement, see #1222
|
||||
# op = row.operator("bim.copy_attribute_to_selection", icon="COPYDOWN", text="")
|
||||
# op.attribute_name = attribute.name
|
||||
# op.attribute_value = attribute.string_value
|
||||
else:
|
||||
row = layout.row()
|
||||
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
if "GlobalId" not in [a["name"] for a in Data.products[oprops.ifc_definition_id]]:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="STEP ID")
|
||||
row.label(text=str(oprops.ifc_definition_id))
|
||||
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
if attribute["value"] is None or attribute["type"] == "entity":
|
||||
continue
|
||||
row = layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=str(attribute["value"]))
|
||||
|
||||
# TODO: reimplement, see #1222
|
||||
# if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name:
|
||||
# self.draw_addresses_ui()
|
||||
|
||||
|
||||
class BIM_PT_object_attributes(Panel):
|
||||
bl_label = "IFC Attributes"
|
||||
bl_idname = "BIM_PT_object_attributes"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
draw_ui(context, self.layout, "Object")
|
||||
|
||||
|
||||
class BIM_PT_material_attributes(Panel):
|
||||
bl_label = "IFC Attributes"
|
||||
bl_idname = "BIM_PT_material_attributes"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try:
|
||||
return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
draw_ui(context, self.layout, "Material")
|
||||
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
prop.AuginProperties,
|
||||
ui.BIM_PT_augin,
|
||||
operator.AuginLogin,
|
||||
operator.AuginCreateNewModel,
|
||||
operator.AuginReset,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.AuginProperties = bpy.props.PointerProperty(type=prop.AuginProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.AuginProperties
|
||||
@@ -0,0 +1,133 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import string
|
||||
import random
|
||||
import shutil
|
||||
import requests
|
||||
import tempfile
|
||||
import datetime
|
||||
import addon_utils
|
||||
|
||||
|
||||
class AuginLogin(bpy.types.Operator):
|
||||
bl_idname = "bim.augin_login"
|
||||
bl_label = "Login to Augin"
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.AuginProperties
|
||||
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/user_login"
|
||||
payload = {"email": props.username, "password": props.password}
|
||||
response = requests.request("POST", url, data=payload)
|
||||
result = response.json()
|
||||
|
||||
if result["success_feedback"]["success"]:
|
||||
props.token = result["token"]
|
||||
else:
|
||||
self.report({"ERROR"}, "Login failed :(")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AuginReset(bpy.types.Operator):
|
||||
bl_idname = "bim.augin_reset"
|
||||
bl_label = "Upload Another Project"
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.AuginProperties
|
||||
props.is_success = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AuginCreateNewModel(bpy.types.Operator):
|
||||
bl_idname = "bim.augin_create_new_model"
|
||||
bl_label = "Create New Augin Model"
|
||||
|
||||
def execute(self, context):
|
||||
import boto3
|
||||
from botocore.config import Config
|
||||
props = bpy.context.scene.AuginProperties
|
||||
|
||||
# Create project
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/new_model"
|
||||
|
||||
chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
|
||||
props.project_filename = "BBM{}{}".format(
|
||||
datetime.datetime.now().strftime("%Y%m%d%H%M%S"), "".join(random.choice(chars) for _ in range(6))
|
||||
)
|
||||
addon_version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
|
||||
payload = {
|
||||
"user_token": props.token,
|
||||
"partner_token": "Mmf2zpxdgrkHaUNRm6Cv7GLJdBAeb7Rn",
|
||||
"project_name": props.project_name,
|
||||
"model_filename": props.project_filename + ".ifc",
|
||||
"thumb_filename": props.project_filename + ".png",
|
||||
"app_name": "BlenderBIM",
|
||||
"sw_version": bpy.app.version_string,
|
||||
"plugin_version": addon_version,
|
||||
}
|
||||
response = requests.request("POST", url, data=payload)
|
||||
result = response.json()
|
||||
|
||||
# Upload to S3
|
||||
my_config = Config(
|
||||
region_name=result["s3_region"],
|
||||
)
|
||||
|
||||
client = boto3.client(
|
||||
"s3",
|
||||
aws_access_key_id=result["s3_key"],
|
||||
aws_secret_access_key=result["s3_secret"],
|
||||
aws_session_token=result["s3_token"],
|
||||
config=my_config,
|
||||
)
|
||||
|
||||
# Generate thumb
|
||||
old_file_format = context.scene.render.image_settings.file_format
|
||||
old_filepath = context.scene.render.filepath
|
||||
|
||||
cam = bpy.data.cameras.new("Camera")
|
||||
cam_obj = bpy.data.objects.new("Camera", cam)
|
||||
context.scene.collection.objects.link(cam_obj)
|
||||
context.scene.camera = cam_obj
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
thumb_path = os.path.join(tmpdir, "thumb.png")
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
context.scene.render.filepath = thumb_path
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
|
||||
context.scene.render.image_settings.file_format = old_file_format
|
||||
context.scene.render.filepath = old_filepath
|
||||
|
||||
client.upload_file(bpy.context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
|
||||
client.upload_file(thumb_path, result["s3_bucket"], result["thumb_path"])
|
||||
|
||||
|
||||
# Notify done
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/files_uploaded"
|
||||
payload = {
|
||||
"user_token": props.token,
|
||||
"ifc_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
|
||||
"model_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
|
||||
"thumb_filesize": os.path.getsize(thumb_path),
|
||||
"model_upload_path": result["model_path"],
|
||||
"thumb_upload_path": result["thumb_path"],
|
||||
"model_id": result["model_id"],
|
||||
}
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
response = requests.request("POST", url, headers=headers, data=payload)
|
||||
|
||||
shutil.rmtree(tmpdir)
|
||||
props.is_success = True
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,11 @@
|
||||
import bpy.props
|
||||
import bpy.types
|
||||
|
||||
|
||||
class AuginProperties(bpy.types.PropertyGroup):
|
||||
username: bpy.props.StringProperty(name="Username")
|
||||
password: bpy.props.StringProperty(name="Password")
|
||||
token: bpy.props.StringProperty(name="Token")
|
||||
project_name: bpy.props.StringProperty(name="Project Name")
|
||||
project_filename: bpy.props.StringProperty(name="IFC Filename")
|
||||
is_success: bpy.props.BoolProperty(name="Is Successful Upload", default=False)
|
||||
@@ -0,0 +1,45 @@
|
||||
import bpy.types
|
||||
|
||||
|
||||
class BIM_PT_augin(bpy.types.Panel):
|
||||
bl_label = "Augin"
|
||||
bl_idname = "BIM_PT_augin"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
|
||||
scene = context.scene
|
||||
props = scene.AuginProperties
|
||||
|
||||
if not props.token:
|
||||
row = layout.row()
|
||||
row.prop(props, "username")
|
||||
row = layout.row()
|
||||
row.prop(props, "password")
|
||||
row = layout.row()
|
||||
row.operator("bim.augin_login")
|
||||
return
|
||||
|
||||
row = layout.row()
|
||||
row.label(text="Logged in as " + props.username)
|
||||
|
||||
if not bpy.context.scene.BIMProperties.ifc_file:
|
||||
row = layout.row()
|
||||
row.label(text="No IFC Found")
|
||||
return
|
||||
|
||||
if props.is_success:
|
||||
row = layout.row()
|
||||
row.label(text="Upload Successful")
|
||||
row = layout.row()
|
||||
row.operator("bim.augin_reset")
|
||||
else:
|
||||
row = layout.row()
|
||||
row.prop(props, "project_name")
|
||||
row = layout.row()
|
||||
row.operator("bim.augin_create_new_model")
|
||||
@@ -0,0 +1,59 @@
|
||||
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.RemoveBcfComment,
|
||||
operator.RemoveBcfBimSnippet,
|
||||
operator.RemoveBcfReferenceLink,
|
||||
operator.RemoveBcfDocumentReference,
|
||||
operator.RemoveBcfRelatedTopic,
|
||||
operator.EditBcfReferenceLinks,
|
||||
operator.EditBcfLabels,
|
||||
operator.RemoveBcfLabel,
|
||||
operator.EditBcfComment,
|
||||
operator.AddBcfComment,
|
||||
operator.ActivateBcfViewpoint,
|
||||
operator.AddBcfViewpoint,
|
||||
operator.RemoveBcfViewpoint,
|
||||
operator.RemoveBcfFile,
|
||||
operator.OpenBcfReferenceLink,
|
||||
operator.SelectBcfHeaderFile,
|
||||
operator.SelectBcfBimSnippetReference,
|
||||
operator.SelectBcfDocumentReference,
|
||||
prop.BcfReferenceLink,
|
||||
prop.BcfLabel,
|
||||
prop.BcfBimSnippet,
|
||||
prop.BcfDocumentReference,
|
||||
prop.BcfComment,
|
||||
prop.BcfTopic,
|
||||
prop.BCFProperties,
|
||||
ui.BIM_PT_bcf,
|
||||
ui.BIM_PT_bcf_metadata,
|
||||
ui.BIM_PT_bcf_comments,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BCFProperties = bpy.props.PointerProperty(type=prop.BCFProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BCFProperties
|
||||
@@ -0,0 +1,11 @@
|
||||
import bcf
|
||||
import bcf.bcfxml
|
||||
|
||||
class BcfStore:
|
||||
bcfxml = None
|
||||
|
||||
@staticmethod
|
||||
def get_bcfxml():
|
||||
if not BcfStore.bcfxml:
|
||||
BcfStore.bcfxml = bcf.bcfxml.BcfXml()
|
||||
return BcfStore.bcfxml
|
||||
@@ -0,0 +1,880 @@
|
||||
import os
|
||||
import bpy
|
||||
import bcf
|
||||
from . import bcfstore
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import radians, degrees, atan, tan, cos, sin
|
||||
from mathutils import Vector, Matrix, Euler, geometry
|
||||
|
||||
class NewBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.new_bcf_project"
|
||||
bl_label = "New BCF Project"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BCFProperties.is_loaded = False
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.new_project()
|
||||
bpy.ops.bim.load_bcf_project()
|
||||
bpy.context.scene.BCFProperties.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_project"
|
||||
bl_label = "Load BCF Project"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BCFProperties.is_loaded = False
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
if self.filepath:
|
||||
bcfxml.get_project(self.filepath)
|
||||
bpy.context.scene.BCFProperties.name = bcfxml.project.name
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
bpy.context.scene.BCFProperties.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class LoadBcfTopics(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_topics"
|
||||
bl_label = "Load BCF Topics"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_topics()
|
||||
while len(bpy.context.scene.BCFProperties.topics) > 0:
|
||||
bpy.context.scene.BCFProperties.topics.remove(0)
|
||||
index = 0
|
||||
for topic_guid in bcfxml.topics.keys():
|
||||
new = bpy.context.scene.BCFProperties.topics.add()
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index)
|
||||
index += 1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBcfTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_topic"
|
||||
bl_label = "Load BCF Topics"
|
||||
topic_guid: bpy.props.StringProperty()
|
||||
topic_index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.get_topic(self.topic_guid)
|
||||
new = bpy.context.scene.BCFProperties.topics[self.topic_index]
|
||||
data_map = {
|
||||
"name": topic.guid,
|
||||
"title": topic.title,
|
||||
"type": topic.topic_type,
|
||||
"status": topic.topic_status,
|
||||
"priority": topic.priority,
|
||||
"stage": topic.stage,
|
||||
"creation_date": topic.creation_date,
|
||||
"creation_author": topic.creation_author,
|
||||
"modified_date": topic.modified_date,
|
||||
"modified_author": topic.modified_author,
|
||||
"assigned_to": topic.assigned_to,
|
||||
"due_date": topic.due_date,
|
||||
"description": topic.description
|
||||
}
|
||||
for key, value in data_map.items():
|
||||
if value is not None:
|
||||
setattr(new, key, str(value))
|
||||
while len(new.reference_links) > 0:
|
||||
new.reference_links.remove(0)
|
||||
for reference_link in topic.reference_links:
|
||||
new2 = new.reference_links.add()
|
||||
new2.name = reference_link
|
||||
while len(new.labels) > 0:
|
||||
new.labels.remove(0)
|
||||
for label in topic.labels:
|
||||
new2 = new.labels.add()
|
||||
new2.name = label
|
||||
if 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
|
||||
}
|
||||
for key, value in data_map.items():
|
||||
if value is not None:
|
||||
setattr(new.bim_snippet, key, value)
|
||||
while len(new.document_references) > 0:
|
||||
new.document_references.remove(0)
|
||||
for doc in topic.document_references:
|
||||
new2 = new.document_references.add()
|
||||
data_map = {
|
||||
"reference": doc.referenced_document,
|
||||
"description": doc.description,
|
||||
"guid": doc.guid,
|
||||
"is_external": doc.is_external
|
||||
}
|
||||
for key, value in data_map.items():
|
||||
if value is not None:
|
||||
setattr(new2, key, value)
|
||||
while len(new.related_topics) > 0:
|
||||
new.related_topics.remove(0)
|
||||
for related_topic in topic.related_topics:
|
||||
new2 = new.related_topics.add()
|
||||
new2.name = related_topic.guid
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBcfComments(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_comments"
|
||||
bl_label = "Load BCF Comments"
|
||||
topic_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.get_comments(self.topic_guid)
|
||||
blender_topic = bpy.context.scene.BCFProperties.topics.get(self.topic_guid)
|
||||
while len(blender_topic.comments) > 0:
|
||||
blender_topic.comments.remove(0)
|
||||
for comment in bcfxml.topics[self.topic_guid].comments.values():
|
||||
new = blender_topic.comments.add()
|
||||
data_map = {
|
||||
"name": comment.guid,
|
||||
"comment": comment.comment,
|
||||
"viewpoint": comment.viewpoint.guid if comment.viewpoint else None,
|
||||
"date": comment.date,
|
||||
"author": comment.author,
|
||||
"modified_date": comment.modified_date,
|
||||
"modified_author": comment.modified_author,
|
||||
}
|
||||
for key, value in data_map.items():
|
||||
if value is not None:
|
||||
setattr(new, key, str(value))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBcfProjectName(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_project_name"
|
||||
bl_label = "Edit BCF Project Name"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.project.name = bpy.context.scene.BCFProperties.name
|
||||
bcfxml.edit_project()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBcfAuthor(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_author"
|
||||
bl_label = "Edit BCF Author"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.author = bpy.context.scene.BCFProperties.author
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBcfTopicName(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_topic_name"
|
||||
bl_label = "Edit BCF Topic Name"
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic.title = blender_topic.title
|
||||
bcfxml.edit_topic(topic)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBcfTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_topic"
|
||||
bl_label = "Edit BCF Topic"
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic.title = blender_topic.title or None
|
||||
topic.priority = blender_topic.priority or None
|
||||
topic.due_date = blender_topic.due_date or None
|
||||
topic.assigned_to = blender_topic.assigned_to or None
|
||||
topic.stage = blender_topic.stage or None
|
||||
topic.description = blender_topic.description or None
|
||||
topic.topic_status = blender_topic.status or None
|
||||
topic.topic_type = blender_topic.type or None
|
||||
|
||||
bcfxml.edit_topic(topic)
|
||||
props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SaveBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.save_bcf_project"
|
||||
bl_label = "Save BCF Project"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.save_project(self.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class AddBcfTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_topic"
|
||||
bl_label = "Add BCF Topic"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcfxml.add_topic()
|
||||
new = bpy.context.scene.BCFProperties.topics.add()
|
||||
new.name = "New Topic"
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBcfBimSnippet(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_bim_snippet"
|
||||
bl_label = "Add BCF BIM Snippet"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bim_snippet = bcf.data.BimSnippet()
|
||||
bim_snippet.reference = blender_topic.bim_snippet_reference
|
||||
bim_snippet.reference_schema = blender_topic.bim_snippet_schema
|
||||
bim_snippet.snippet_type = blender_topic.bim_snippet_type
|
||||
bcfxml.add_bim_snippet(topic, bim_snippet)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBcfRelatedTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_related_topic"
|
||||
bl_label = "Add BCF Related Topic"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
related_topic = None
|
||||
for topic in bcfxml.topics.values():
|
||||
if topic.title == blender_topic.related_topic:
|
||||
related_topic = bcf.data.RelatedTopic()
|
||||
related_topic.guid = topic.guid
|
||||
break
|
||||
if not related_topic:
|
||||
return {"FINISHED"}
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic.related_topics.append(related_topic)
|
||||
bcfxml.edit_topic(topic)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBcfHeaderFile(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_header_file"
|
||||
bl_label = "Add BCF Header File"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
header_file = bcf.data.HeaderFile()
|
||||
header_file.reference = blender_topic.file_reference
|
||||
if not os.path.exists(header_file.reference):
|
||||
header_file.filename = header_file.reference
|
||||
if len(blender_topic.file_ifc_project) == 22:
|
||||
header_file.ifc_project = blender_topic.file_ifc_project
|
||||
if len(blender_topic.file_ifc_spatial_structure_element) == 22:
|
||||
header_file.ifc_spatial_structure_element = blender_topic.file_ifc_spatial_structure_element
|
||||
bcfxml.add_file(topic, header_file)
|
||||
props.active_topic_index = props.active_topic_index # refreshes the BCF Topic
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ViewBcfTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.view_bcf_topic"
|
||||
bl_label = "Get BCF Topic"
|
||||
topic_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
for index, topic in enumerate(bpy.context.scene.BCFProperties.topics):
|
||||
if topic.guid.lower() == self.topic_guid.lower():
|
||||
bpy.context.scene.BCFProperties.active_topic_index = index
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBcfViewpoint(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_viewpoint"
|
||||
bl_label = "Add BCF Viewpoint"
|
||||
|
||||
def execute(self, context):
|
||||
if not bpy.context.scene.camera:
|
||||
return {"FINISHED"}
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
viewpoint = bcf.data.Viewpoint()
|
||||
|
||||
if bpy.context.scene.camera.data.type == "ORTHO":
|
||||
camera = bcf.data.OrthogonalCamera()
|
||||
camera.view_to_world_scale = bpy.context.scene.camera.data.ortho_scale
|
||||
viewpoint.orthogonal_camera = camera
|
||||
elif bpy.context.scene.camera.data.type == "PERSP":
|
||||
camera = bcf.data.PerspectiveCamera()
|
||||
camera.field_of_view = degrees(bpy.context.scene.camera.data.angle)
|
||||
viewpoint.perspective_camera = camera
|
||||
camera.camera_view_point.x = bpy.context.scene.camera.location.x
|
||||
camera.camera_view_point.y = bpy.context.scene.camera.location.y
|
||||
camera.camera_view_point.z = bpy.context.scene.camera.location.z
|
||||
direction = bpy.context.scene.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 = bpy.context.scene.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
|
||||
|
||||
old_file_format = bpy.context.scene.render.image_settings.file_format
|
||||
bpy.context.scene.render.image_settings.file_format = "PNG"
|
||||
old_filepath = bpy.context.scene.render.filepath
|
||||
bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
viewpoint.snapshot = bpy.context.scene.render.filepath
|
||||
|
||||
bcfxml.add_viewpoint(topic, viewpoint)
|
||||
bpy.context.scene.render.filepath = old_filepath
|
||||
bpy.context.scene.render.image_settings.file_format = old_file_format
|
||||
props.active_topic_index = props.active_topic_index # refreshes the BCF Topic
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveBcfViewpoint(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_viewpoint"
|
||||
bl_label = "Remove BCF Viewpoint"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
viewpoint_guid = blender_topic.viewpoints
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_viewpoint(viewpoint_guid, topic)
|
||||
props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveBcfFile(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_file"
|
||||
bl_label = "Remove BCF File"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_file(topic, self.index)
|
||||
props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBcfReferenceLink(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_reference_link"
|
||||
bl_label = "Add BCF Reference Link"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not blender_topic.reference_link:
|
||||
return {"FINISHED"}
|
||||
topic.reference_links.append(blender_topic.reference_link)
|
||||
bcfxml.edit_topic(topic)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
|
||||
blender_topic.reference_link = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBcfDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_document_reference"
|
||||
bl_label = "Add BCF Document Reference"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not blender_topic.document_reference:
|
||||
return {"FINISHED"}
|
||||
document_reference = bcf.data.DocumentReference()
|
||||
document_reference.referenced_document = blender_topic.document_reference
|
||||
document_reference.description = blender_topic.document_reference_description or None
|
||||
bcfxml.add_document_reference(topic, document_reference)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
|
||||
blender_topic.document_reference = ""
|
||||
blender_topic.document_reference_description = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBcfLabel(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_label"
|
||||
bl_label = "Add BCF Label"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not blender_topic.label:
|
||||
return {"FINISHED"}
|
||||
new = blender_topic.labels.add()
|
||||
new.name = blender_topic.label
|
||||
topic.labels.append(blender_topic.label)
|
||||
bcfxml.edit_topic(topic)
|
||||
blender_topic.label = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBcfReferenceLinks(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_reference_links"
|
||||
bl_label = "Edit BCF Reference Link"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
for index, reference_link in enumerate(topic.reference_links):
|
||||
if reference_link == blender_topic.reference_links[index].name:
|
||||
continue
|
||||
topic.reference_links[index] = blender_topic.reference_links[index].name
|
||||
bcfxml.edit_topic(topic)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBcfLabels(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_labels"
|
||||
bl_label = "Edit BCF Labels"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
for index, label in enumerate(blender_topic.labels):
|
||||
if label.name == topic.labels[index]:
|
||||
continue
|
||||
topic.labels[index] = blender_topic.labels[index].name
|
||||
bcfxml.edit_topic(topic)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveBcfReferenceLink(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_reference_link"
|
||||
bl_label = "Remove BCF Reference Link"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.reference_links[self.index]
|
||||
bcfxml.edit_topic(topic)
|
||||
blender_topic.reference_links.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveBcfLabel(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_label"
|
||||
bl_label = "Remove BCF Label"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.labels[self.index]
|
||||
bcfxml.edit_topic(topic)
|
||||
blender_topic.labels.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveBcfBimSnippet(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_bim_snippet"
|
||||
bl_label = "Remove BCF BIM Snippet"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_bim_snippet(topic)
|
||||
blender_topic.bim_snippet.schema = ""
|
||||
blender_topic.bim_snippet.reference = ""
|
||||
blender_topic.bim_snippet.type = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveBcfDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_document_reference"
|
||||
bl_label = "Remove BCF Document Reference"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_document_reference(topic, self.index)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveBcfRelatedTopic(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_related_topic"
|
||||
bl_label = "Remove BCF Related Topic"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.related_topics[self.index]
|
||||
bcfxml.edit_topic(topic)
|
||||
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveBcfComment(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_bcf_comment"
|
||||
bl_label = "Remove BCF Comment"
|
||||
comment_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
bcfxml.delete_comment(self.comment_guid, topic)
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBcfComment(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_bcf_comment"
|
||||
bl_label = "Edit BCF Comment"
|
||||
comment_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
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)
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBcfComment(bpy.types.Operator):
|
||||
bl_idname = "bim.add_bcf_comment"
|
||||
bl_label = "Add BCF Comment"
|
||||
comment_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not blender_topic.comment:
|
||||
return {"FINISHED"}
|
||||
comment = bcf.data.Comment()
|
||||
comment.comment = blender_topic.comment
|
||||
if blender_topic.has_related_viewpoint and blender_topic.viewpoints:
|
||||
comment.viewpoint = bcf.data.Viewpoint()
|
||||
comment.viewpoint.guid = blender_topic.viewpoints
|
||||
bcfxml.add_comment(topic, comment)
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
|
||||
print(bcfxml.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
bl_idname = "bim.activate_bcf_viewpoint"
|
||||
bl_label = "Activate BCF Viewpoint"
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
blender_topic = props.topics[props.active_topic_index]
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if not topic.viewpoints:
|
||||
return {"FINISHED"}
|
||||
|
||||
viewpoint_guid = blender_topic.viewpoints
|
||||
viewpoint = topic.viewpoints[viewpoint_guid]
|
||||
obj = bpy.data.objects.get("Viewpoint")
|
||||
if not obj:
|
||||
obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint"))
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
bpy.context.scene.camera = obj
|
||||
|
||||
cam_width = bpy.context.scene.render.resolution_x
|
||||
cam_height = bpy.context.scene.render.resolution_y
|
||||
cam_aspect = cam_width / cam_height
|
||||
|
||||
if viewpoint.snapshot:
|
||||
obj.data.show_background_images = True
|
||||
while len(obj.data.background_images) > 0:
|
||||
obj.data.background_images.remove(obj.data.background_images[0])
|
||||
background = obj.data.background_images.new()
|
||||
background.image = bpy.data.images.load(
|
||||
os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot)
|
||||
)
|
||||
src_width = background.image.size[0]
|
||||
src_height = background.image.size[1]
|
||||
src_aspect = src_width / src_height
|
||||
|
||||
if src_aspect > cam_aspect:
|
||||
background.frame_method = "FIT"
|
||||
else:
|
||||
background.frame_method = "CROP"
|
||||
background.display_depth = "FRONT"
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
|
||||
if viewpoint.orthogonal_camera:
|
||||
camera = viewpoint.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.type = "PERSP"
|
||||
if cam_aspect >= 1:
|
||||
obj.data.angle = radians(camera.field_of_view)
|
||||
else:
|
||||
# https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov
|
||||
obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2)))
|
||||
|
||||
self.set_viewpoint_components(viewpoint)
|
||||
|
||||
gp = bpy.data.grease_pencils.get("BCF")
|
||||
if gp:
|
||||
bpy.data.grease_pencils.remove(gp)
|
||||
if viewpoint.lines:
|
||||
self.draw_lines(viewpoint)
|
||||
|
||||
self.delete_clipping_planes()
|
||||
if viewpoint.clipping_planes:
|
||||
self.create_clipping_planes(viewpoint)
|
||||
|
||||
self.delete_bitmaps()
|
||||
if viewpoint.bitmaps:
|
||||
self.create_bitmaps(bcfxml, viewpoint, topic)
|
||||
|
||||
z_axis = Vector((-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)).normalized()
|
||||
y_axis = Vector((camera.camera_up_vector.x, camera.camera_up_vector.y, camera.camera_up_vector.z)).normalized()
|
||||
x_axis = y_axis.cross(z_axis).normalized()
|
||||
rotation = Matrix((x_axis, y_axis, z_axis))
|
||||
rotation.invert()
|
||||
location = Vector((camera.camera_view_point.x, camera.camera_view_point.y, camera.camera_view_point.z))
|
||||
obj.matrix_world = rotation.to_4x4()
|
||||
obj.location = location
|
||||
return {"FINISHED"}
|
||||
|
||||
def set_viewpoint_components(self, viewpoint):
|
||||
if not viewpoint.components:
|
||||
return
|
||||
selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection]
|
||||
exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions]
|
||||
global_id_colours = {}
|
||||
for coloring in viewpoint.components.coloring:
|
||||
for component in coloring.components:
|
||||
global_id_colours.setdefault(component.ifc_guid, coloring.color)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
global_id = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
|
||||
is_visible = viewpoint.components.visibility.default_visibility
|
||||
if global_id in exception_global_ids:
|
||||
is_visible = not is_visible
|
||||
if not is_visible:
|
||||
obj.hide_set(True)
|
||||
continue
|
||||
if "IfcSpace" in obj.name:
|
||||
if viewpoint.components.view_setup_hints:
|
||||
is_visible = viewpoint.components.view_setup_hints.spaces_visible
|
||||
else:
|
||||
is_visible = False
|
||||
elif "IfcOpeningElement" in obj.name:
|
||||
if viewpoint.components.view_setup_hints:
|
||||
is_visible = viewpoint.components.view_setup_hints.openings_visible
|
||||
else:
|
||||
is_visible = False
|
||||
obj.hide_set(not is_visible)
|
||||
if not is_visible:
|
||||
continue
|
||||
obj.select_set(global_id in selected_global_ids)
|
||||
if global_id in global_id_colours:
|
||||
obj.color = self.hex_to_rgb(global_id_colours[global_id])
|
||||
|
||||
def draw_lines(self, viewpoint):
|
||||
gp = bpy.data.grease_pencils.new("BCF")
|
||||
scene = bpy.context.scene
|
||||
scene.grease_pencil = gp
|
||||
scene.frame_set(1)
|
||||
layer = gp.layers.new("BCF Annotation", set_active=True)
|
||||
layer.thickness = 3
|
||||
layer.color = (1, 0, 0)
|
||||
frame = layer.frames.new(1)
|
||||
stroke = frame.strokes.new()
|
||||
stroke.display_mode = "3DSPACE"
|
||||
stroke.points.add(len(viewpoint.lines) * 2)
|
||||
coords = []
|
||||
for l in viewpoint.lines:
|
||||
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])
|
||||
stroke.points.foreach_set("co", coords)
|
||||
|
||||
def create_clipping_planes(self, viewpoint):
|
||||
n = 0
|
||||
for plane in viewpoint.clipping_planes:
|
||||
bpy.ops.bim.add_section_plane()
|
||||
if n == 0:
|
||||
obj = bpy.data.objects["Section"]
|
||||
else:
|
||||
obj = bpy.data.objects["Section.{:03d}".format(n)]
|
||||
obj.location = (plane.location.x, plane.location.y, plane.location.z)
|
||||
obj.rotation_mode = "QUATERNION"
|
||||
obj.rotation_quaternion = Vector((plane.direction.x, plane.direction.y, plane.direction.z)).to_track_quat(
|
||||
"Z", "Y"
|
||||
)
|
||||
n += 1
|
||||
|
||||
def delete_clipping_planes(self):
|
||||
collection = bpy.data.collections.get("Sections")
|
||||
if not collection:
|
||||
return
|
||||
for section in collection.objects:
|
||||
bpy.context.view_layer.objects.active = section
|
||||
bpy.ops.bim.remove_section_plane()
|
||||
|
||||
def delete_bitmaps(self):
|
||||
collection = bpy.data.collections.get("Bitmaps")
|
||||
if not collection:
|
||||
collection = bpy.data.collections.new("Bitmaps")
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
for bitmap in collection.objects:
|
||||
bpy.data.objects.remove(bitmap)
|
||||
|
||||
def create_bitmaps(self, bcfxml, viewpoint, topic):
|
||||
collection = bpy.data.collections.get("Bitmaps")
|
||||
if not collection:
|
||||
collection = bpy.data.collections.new("Bitmaps")
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
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))
|
||||
src_width = image.size[0]
|
||||
src_height = image.size[1]
|
||||
if src_height > src_width:
|
||||
obj.empty_display_size = bitmap.height
|
||||
else:
|
||||
obj.empty_display_size = bitmap.height * (src_width / src_height)
|
||||
obj.data = image
|
||||
y = Vector((bitmap.up.x, bitmap.up.y, bitmap.up.z))
|
||||
z = Vector((bitmap.normal.x, bitmap.normal.y, bitmap.normal.z))
|
||||
x = y.cross(z)
|
||||
obj.matrix_world = Matrix(
|
||||
[[x[0], y[0], z[0], 0], [x[1], y[1], z[1], 0], [x[2], y[2], z[2], 0], [0, 0, 0, 1]]
|
||||
)
|
||||
obj.location = (bitmap.location.x, bitmap.location.y, bitmap.location.z)
|
||||
collection.objects.link(obj)
|
||||
|
||||
def hex_to_rgb(self, value):
|
||||
value = value.lstrip("#")
|
||||
lv = len(value)
|
||||
t = tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3))
|
||||
return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1]
|
||||
|
||||
class OpenBcfReferenceLink(bpy.types.Operator):
|
||||
bl_idname = "bim.open_bcf_reference_link"
|
||||
bl_label = "Open BCF Reference Link"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
webbrowser.open(bpy.context.scene.BCFProperties.topic_links[self.index].name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectBcfHeaderFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bcf_header_file"
|
||||
bl_label = "Select BCF Header File"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
props = bpy.context.scene.BCFProperties
|
||||
topic = props.topics[props.active_topic_index]
|
||||
topic.file_reference = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectBcfBimSnippetReference(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bcf_bim_snippet_reference"
|
||||
bl_label = "Select BCF BIM Snippet Reference"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
props = bpy.context.scene.BCFProperties
|
||||
topic = props.topics[props.active_topic_index]
|
||||
topic.bim_snippet_reference = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectBcfDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bcf_document_reference"
|
||||
bl_label = "Select BCF Document Reference"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
props = bpy.context.scene.BCFProperties
|
||||
topic = props.topics[props.active_topic_index]
|
||||
topic.document_reference = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
@@ -0,0 +1,155 @@
|
||||
import bpy
|
||||
from . import bcfstore
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
bcfviewpoints_enum = None
|
||||
|
||||
|
||||
def updateBcfReferenceLink(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_reference_links()
|
||||
|
||||
|
||||
def updateBcfLabel(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_labels()
|
||||
|
||||
|
||||
def updateBcfProjectName(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_project_name()
|
||||
|
||||
|
||||
def updateBcfAuthor(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_author()
|
||||
|
||||
|
||||
def updateBcfTopicName(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_topic_name()
|
||||
|
||||
|
||||
def updateBcfTopicIsEditable(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
|
||||
bpy.ops.bim.edit_bcf_topic()
|
||||
|
||||
|
||||
def updateBcfCommentIsEditable(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
|
||||
bpy.ops.bim.edit_bcf_comment(comment_guid = self.name)
|
||||
|
||||
|
||||
def refreshBcfTopic(self, context):
|
||||
global bcfviewpoints_enum
|
||||
bcfviewpoints_enum = None
|
||||
|
||||
props = bpy.context.scene.BCFProperties
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = props.topics[props.active_topic_index]
|
||||
header = bcfxml.get_header(topic.name)
|
||||
getBcfViewpoints(self, context)
|
||||
|
||||
|
||||
class BcfReferenceLink(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateBcfReferenceLink)
|
||||
|
||||
|
||||
class BcfLabel(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateBcfLabel)
|
||||
|
||||
|
||||
def getBcfViewpoints(self, context):
|
||||
global bcfviewpoints_enum
|
||||
if bcfviewpoints_enum is None:
|
||||
bcfviewpoints_enum = []
|
||||
props = bpy.context.scene.BCFProperties
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = props.topics[props.active_topic_index]
|
||||
viewpoints = bcfxml.get_viewpoints(topic.name)
|
||||
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
|
||||
return bcfviewpoints_enum
|
||||
|
||||
|
||||
class BcfBimSnippet(PropertyGroup):
|
||||
schema: StringProperty(name="Schema")
|
||||
reference: StringProperty(name="Reference")
|
||||
type: StringProperty(name="Type")
|
||||
is_external: BoolProperty(name="Is External")
|
||||
|
||||
|
||||
class BcfDocumentReference(PropertyGroup):
|
||||
reference: StringProperty(name="Reference")
|
||||
description: StringProperty(name="Description")
|
||||
guid: StringProperty(name="GUID")
|
||||
is_external: BoolProperty(name="Is External")
|
||||
|
||||
|
||||
class BcfComment(PropertyGroup):
|
||||
name: StringProperty(name="GUID")
|
||||
date: StringProperty(name="Date")
|
||||
author: StringProperty(name="Author")
|
||||
comment: StringProperty(name="Comment")
|
||||
viewpoint: StringProperty(name="Viewpoint")
|
||||
modified_date: StringProperty(name="Modified Date")
|
||||
modified_author: StringProperty(name="Modified Author")
|
||||
is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfCommentIsEditable)
|
||||
|
||||
|
||||
class BcfTopic(PropertyGroup):
|
||||
name: StringProperty(name="GUID")
|
||||
title: StringProperty(default="", name="Title", update=updateBcfTopicName)
|
||||
type: StringProperty(default="", name="Type")
|
||||
status: StringProperty(default="", name="Status")
|
||||
priority: StringProperty(default="", name="Priority")
|
||||
stage: StringProperty(default="", name="Stage")
|
||||
creation_date: StringProperty(default="", name="Date")
|
||||
creation_author: StringProperty(default="", name="Author")
|
||||
modified_date: StringProperty(default="", name="Modified Date")
|
||||
modified_author: StringProperty(default="", name="Modified By")
|
||||
assigned_to: StringProperty(default="", name="Assigned To")
|
||||
due_date: StringProperty(default="", name="Due Date")
|
||||
description: StringProperty(default="", name="Description")
|
||||
viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints")
|
||||
files: CollectionProperty(name="Files", type=StrProperty)
|
||||
file_reference: StringProperty(default="", name="Reference")
|
||||
file_ifc_project: StringProperty(default="", name="IFC Project")
|
||||
file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element")
|
||||
reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink)
|
||||
reference_link: StringProperty(default="", name="Reference Link")
|
||||
labels: CollectionProperty(name="Labels", type=BcfLabel)
|
||||
label: StringProperty(default="", name="Label")
|
||||
bim_snippet: PointerProperty(type=BcfBimSnippet)
|
||||
bim_snippet_type: StringProperty(default="", name="Type")
|
||||
bim_snippet_reference: StringProperty(default="", name="Reference")
|
||||
bim_snippet_schema: StringProperty(default="", name="Schema")
|
||||
document_references: CollectionProperty(name="Document References", type=BcfDocumentReference)
|
||||
document_reference: StringProperty(default="", name="Referenced Document")
|
||||
document_reference_description: StringProperty(default="", name="Description")
|
||||
related_topics: CollectionProperty(name="Related Topics", type=StrProperty)
|
||||
related_topic: StringProperty(default="", name="Related Topic")
|
||||
comments: CollectionProperty(name="Comments", type=BcfComment)
|
||||
is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable)
|
||||
comment: StringProperty(default="", name="Comment")
|
||||
has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False)
|
||||
|
||||
|
||||
class BCFProperties(PropertyGroup):
|
||||
is_loaded: BoolProperty(name="Is Loaded", default=False)
|
||||
comment_text_width: IntProperty(name="Comment Text Width", default=40)
|
||||
name: StringProperty(default="", name="Project Name", update=updateBcfProjectName)
|
||||
author: StringProperty(default="john@doe.com", name="Author Email", update=updateBcfAuthor)
|
||||
topics: CollectionProperty(name="BCF Topics", type=BcfTopic)
|
||||
active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic)
|
||||
@@ -0,0 +1,283 @@
|
||||
import os
|
||||
import bpy
|
||||
from . import bcfstore
|
||||
from bpy.types import Panel
|
||||
|
||||
class BIM_PT_bcf(Panel):
|
||||
bl_label = "BCF Project"
|
||||
bl_idname = "BIM_PT_bcf"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
scene = context.scene
|
||||
props = bpy.context.scene.BCFProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.new_bcf_project", text="New Project")
|
||||
row.operator("bim.load_bcf_project", text="Load Project")
|
||||
|
||||
if not props.is_loaded:
|
||||
return
|
||||
|
||||
row.operator("bim.save_bcf_project", text="Save Project")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "name")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "author")
|
||||
|
||||
props = bpy.context.scene.BCFProperties
|
||||
row = layout.row()
|
||||
row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index")
|
||||
col = row.column(align=True)
|
||||
col.operator("bim.add_bcf_topic", icon="ADD", text="")
|
||||
if props.active_topic_index < len(props.topics):
|
||||
topic = props.topics[props.active_topic_index]
|
||||
col.prop(topic, "is_editable", icon="CHECKMARK" if topic.is_editable else "GREASEPENCIL", icon_only=True)
|
||||
|
||||
if props.active_topic_index < len(props.topics):
|
||||
topic = props.topics[props.active_topic_index]
|
||||
row = layout.row()
|
||||
row.enabled = topic.is_editable
|
||||
row.prop(topic, "description", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(topic, "viewpoints")
|
||||
row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="")
|
||||
row.operator("bim.add_bcf_viewpoint", icon="ADD", text="")
|
||||
row.operator("bim.remove_bcf_viewpoint", icon="X", text="")
|
||||
|
||||
col = layout.column(align=True)
|
||||
if topic.type:
|
||||
col.prop(topic, "type", emboss=topic.is_editable)
|
||||
if topic.status:
|
||||
col.prop(topic, "status", emboss=topic.is_editable)
|
||||
if topic.priority:
|
||||
col.prop(topic, "priority", emboss=topic.is_editable)
|
||||
if topic.stage:
|
||||
col.prop(topic, "stage", emboss=topic.is_editable)
|
||||
if topic.assigned_to:
|
||||
col.prop(topic, "assigned_to", emboss=topic.is_editable)
|
||||
if topic.due_date:
|
||||
col.prop(topic, "due_date", emboss=topic.is_editable)
|
||||
|
||||
col = layout.column(align=True)
|
||||
if topic.modified_date:
|
||||
col.prop(topic, "modified_date", emboss=False)
|
||||
col.prop(topic, "modified_author", emboss=False)
|
||||
else:
|
||||
col.prop(topic, "creation_date", emboss=False)
|
||||
col.prop(topic, "creation_author", emboss=False)
|
||||
|
||||
|
||||
class BIM_PT_bcf_metadata(Panel):
|
||||
bl_label = "BCF Metadata"
|
||||
bl_idname = "BIM_PT_bcf_metadata"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_bcf"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
scene = context.scene
|
||||
props = bpy.context.scene.BCFProperties
|
||||
|
||||
if props.active_topic_index >= len(props.topics):
|
||||
layout.label(text="No BCF project is loaded")
|
||||
return
|
||||
|
||||
topic = props.topics[props.active_topic_index]
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
bcf_topic = bcfxml.topics[topic.name]
|
||||
|
||||
layout.label(text="Header Files:")
|
||||
|
||||
if bcf_topic.header:
|
||||
for index, f in enumerate(bcf_topic.header.files):
|
||||
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)
|
||||
row.operator("bim.remove_bcf_file", icon="X", text="").index = index
|
||||
|
||||
row = box.row()
|
||||
row.label(text="Date")
|
||||
row.label(text=f.date)
|
||||
|
||||
if f.ifc_project:
|
||||
row = box.row()
|
||||
row.label(text="IFC Project")
|
||||
row.label(text=f.ifc_project)
|
||||
|
||||
if f.ifc_spatial_structure_element:
|
||||
row = box.row()
|
||||
row.label(text="IFC Spatial Structure Element")
|
||||
row.label(text=f.ifc_spatial_structure_element)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(topic, "file_reference")
|
||||
row.operator("bim.select_bcf_header_file", icon="FILE_FOLDER", text="")
|
||||
row = layout.row()
|
||||
row.prop(topic, "file_ifc_project")
|
||||
row = layout.row()
|
||||
row.prop(topic, "file_ifc_spatial_structure_element")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_bcf_header_file")
|
||||
|
||||
layout.label(text="Reference Links:")
|
||||
for index, link in enumerate(topic.reference_links):
|
||||
row = layout.row(align=True)
|
||||
row.prop(link, "name")
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = link.name
|
||||
row.operator("bim.remove_bcf_reference_link", icon="X", text="").index = index
|
||||
row = layout.row()
|
||||
row.prop(topic, "reference_link")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_bcf_reference_link")
|
||||
|
||||
layout.label(text="Labels:")
|
||||
for index, label in enumerate(topic.labels):
|
||||
row = layout.row(align=True)
|
||||
row.prop(label, "name", text="")
|
||||
row.operator("bim.remove_bcf_label", icon="X", text="").index = index
|
||||
row = layout.row()
|
||||
row.prop(topic, "label")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_bcf_label")
|
||||
|
||||
layout.label(text="BIM Snippet:")
|
||||
if topic.bim_snippet.schema:
|
||||
row = layout.row(align=True)
|
||||
row.prop(topic.bim_snippet, "type", emboss=False)
|
||||
if topic.bim_snippet.schema:
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.schema
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(topic.bim_snippet, "reference", emboss=False)
|
||||
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)
|
||||
row.operator("bim.remove_bcf_bim_snippet", icon="X", text="")
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.prop(topic, "bim_snippet_reference")
|
||||
row.operator("bim.select_bcf_bim_snippet_reference", icon="FILE_FOLDER", text="")
|
||||
row = layout.row()
|
||||
row.prop(topic, "bim_snippet_type")
|
||||
row = layout.row()
|
||||
row.prop(topic, "bim_snippet_schema")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_bcf_bim_snippet")
|
||||
|
||||
layout.label(text="Document References:")
|
||||
for index, doc in enumerate(topic.document_references):
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.prop(doc, "reference")
|
||||
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)
|
||||
row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index
|
||||
row = box.row(align=True)
|
||||
row.prop(doc, "description")
|
||||
row = layout.row(align=True)
|
||||
row.prop(topic, "document_reference")
|
||||
row.operator("bim.select_bcf_document_reference", icon="FILE_FOLDER", text="")
|
||||
row = layout.row()
|
||||
row.prop(topic, "document_reference_description")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_bcf_document_reference")
|
||||
|
||||
layout.label(text="Related Topics:")
|
||||
for index, related_topic in enumerate(topic.related_topics):
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name
|
||||
row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index
|
||||
row = layout.row()
|
||||
row.prop(topic, "related_topic")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_bcf_related_topic")
|
||||
|
||||
|
||||
class BIM_PT_bcf_comments(Panel):
|
||||
bl_label = "BCF Comments"
|
||||
bl_idname = "BIM_PT_bcf_comments"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_bcf"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
scene = context.scene
|
||||
props = bpy.context.scene.BCFProperties
|
||||
|
||||
if props.active_topic_index >= len(props.topics):
|
||||
layout.label(text="No BCF project is loaded")
|
||||
return
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "comment_text_width")
|
||||
|
||||
topic = props.topics[props.active_topic_index]
|
||||
for comment in topic.comments:
|
||||
box = self.layout.box()
|
||||
|
||||
author_text = "{} ({})".format(comment.author, comment.date)
|
||||
if comment.modified_author:
|
||||
author_text = "*{} ({})".format(comment.modified_author, comment.modified_date)
|
||||
row = box.row(align=True)
|
||||
row.label(text=author_text, icon="WORDWRAP_ON")
|
||||
row.prop(
|
||||
comment, "is_editable", icon="CHECKMARK" if comment.is_editable else "GREASEPENCIL", icon_only=True
|
||||
)
|
||||
row.operator("bim.remove_bcf_comment", icon="X", text="").comment_guid = comment.name
|
||||
|
||||
if comment.is_editable:
|
||||
row = box.row()
|
||||
row.prop(comment, "comment", text="")
|
||||
else:
|
||||
col = box.column(align=True)
|
||||
col.scale_y = 0.8
|
||||
words = comment.comment.split()
|
||||
while words:
|
||||
total_line_chars = 0
|
||||
line_words = []
|
||||
while words and total_line_chars < props.comment_text_width:
|
||||
word = words.pop(0)
|
||||
line_words.append(word)
|
||||
total_line_chars += len(word) + 1 # 1 is for the space
|
||||
col.label(text=" ".join(line_words))
|
||||
|
||||
row = layout.row()
|
||||
row.prop(topic, "comment")
|
||||
row = layout.row()
|
||||
row.prop(topic, "has_related_viewpoint")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_bcf_comment")
|
||||
@@ -0,0 +1,24 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ExecuteBIMTester,
|
||||
operator.BIMTesterPurge,
|
||||
operator.SelectFeature,
|
||||
operator.SelectSteps,
|
||||
operator.SelectBIMTesterIfcFile,
|
||||
operator.RejectElement,
|
||||
operator.ApproveClass,
|
||||
operator.RejectClass,
|
||||
operator.SelectAudited,
|
||||
prop.BimTesterProperties,
|
||||
ui.BIM_PT_qa,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BimTesterProperties = bpy.props.PointerProperty(type=prop.BimTesterProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BimTesterProperties
|
||||
@@ -0,0 +1,233 @@
|
||||
import os
|
||||
import bpy
|
||||
import tempfile
|
||||
import webbrowser
|
||||
import ifcopenshell
|
||||
|
||||
try:
|
||||
import bimtester
|
||||
import bimtester.run
|
||||
import bimtester.reports
|
||||
except:
|
||||
print("Failed to load BIMTester. Try disabling other add-ons, in particular Blender-OSM. See bug #1318.")
|
||||
|
||||
from pathlib import Path
|
||||
from itertools import cycle
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class ExecuteBIMTester(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_bim_tester"
|
||||
bl_label = "Execute BIMTester"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BimTesterProperties
|
||||
|
||||
with tempfile.TemporaryDirectory() as dirpath:
|
||||
report = os.path.join(dirpath, "{}.html".format(props.feature))
|
||||
args = {
|
||||
"action": "run",
|
||||
"advanced_arguments": "",
|
||||
"console": False,
|
||||
"feature": props.feature,
|
||||
"ifc": props.ifc_file,
|
||||
"path": "",
|
||||
"report": report,
|
||||
"schema_file": "",
|
||||
"schema_name": "",
|
||||
"lang": "en",
|
||||
"steps": props.steps,
|
||||
}
|
||||
use_stored_ifc = props.should_load_from_memory and IfcStore.get_file()
|
||||
runner = bimtester.run.TestRunner(args["ifc"], ifc=IfcStore.get_file() if use_stored_ifc else None)
|
||||
report_json = runner.run(args)
|
||||
bimtester.reports.ReportGenerator().generate(report_json, args["report"])
|
||||
webbrowser.open("file://" + report)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIMTesterPurge(bpy.types.Operator):
|
||||
bl_idname = "bim.bim_tester_purge"
|
||||
bl_label = "Purge Tests"
|
||||
|
||||
def execute(self, context):
|
||||
filename = os.path.join(
|
||||
bpy.context.scene.BimTesterProperties.features_dir,
|
||||
bpy.context.scene.BimTesterProperties.features_file + ".feature",
|
||||
)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(bpy.context.scene.BimTesterProperties.features_dir)
|
||||
bimtester.clean.TestPurger().purge()
|
||||
os.chdir(cwd)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectFeature(bpy.types.Operator):
|
||||
bl_idname = "bim.select_feature"
|
||||
bl_label = "Select Feature / IDS"
|
||||
filename_ext = ".feature"
|
||||
filter_glob: bpy.props.StringProperty(default="*.feature;*.xml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BimTesterProperties.feature = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectSteps(bpy.types.Operator):
|
||||
bl_idname = "bim.select_steps"
|
||||
bl_label = "Select Steps"
|
||||
filename_ext = ".py"
|
||||
filter_glob: bpy.props.StringProperty(default="*.py", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BimTesterProperties.steps = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectBIMTesterIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bimtester_ifc_file"
|
||||
bl_label = "Select BIMTester IFC File"
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BimTesterProperties.ifc_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class RejectElement(bpy.types.Operator):
|
||||
bl_idname = "bim.reject_element"
|
||||
bl_label = "Reject Element"
|
||||
|
||||
def execute(self, context):
|
||||
lines = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in bpy.context.selected_objects:
|
||||
lines.append(
|
||||
" * The element {} should not exist because {}".format(
|
||||
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId,
|
||||
bpy.context.scene.BimTesterProperties.qa_reject_element_reason,
|
||||
)
|
||||
)
|
||||
QAHelper.append_to_scenario(lines)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ApproveClass(bpy.types.Operator):
|
||||
bl_idname = "bim.approve_class"
|
||||
bl_label = "Approve Class"
|
||||
|
||||
def execute(self, context):
|
||||
lines = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in bpy.context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
lines.append(" * The element {} is an {}".format(element.GlobalId, element.is_a()))
|
||||
QAHelper.append_to_scenario(lines)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RejectClass(bpy.types.Operator):
|
||||
bl_idname = "bim.reject_class"
|
||||
bl_label = "Reject Class"
|
||||
|
||||
def execute(self, context):
|
||||
lines = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in bpy.context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
lines.append(
|
||||
" * The element {} is an {}".format(
|
||||
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId,
|
||||
bpy.context.scene.BimTesterProperties.audit_ifc_class,
|
||||
)
|
||||
)
|
||||
QAHelper.append_to_scenario(lines)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectAudited(bpy.types.Operator):
|
||||
bl_idname = "bim.select_audited"
|
||||
bl_label = "Select Audited"
|
||||
|
||||
def execute(self, context):
|
||||
audited_global_ids = []
|
||||
self.file = IfcStore.get_file()
|
||||
for filename in Path(bpy.context.scene.BimTesterProperties.features_dir).glob("*.feature"):
|
||||
with open(filename, "r") as feature_file:
|
||||
lines = feature_file.readlines()
|
||||
for line in lines:
|
||||
words = line.strip().split()
|
||||
for word in words:
|
||||
if self.is_a_global_id(word):
|
||||
audited_global_ids.append(word)
|
||||
for obj in bpy.context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
if self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId in audited_global_ids:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
def is_a_global_id(self, word):
|
||||
return word[0] in ["0", "1", "2", "3"] and len(word) == 22
|
||||
|
||||
|
||||
class QAHelper:
|
||||
@classmethod
|
||||
def append_to_scenario(cls, lines):
|
||||
filename = os.path.join(
|
||||
bpy.context.scene.BimTesterProperties.features_dir,
|
||||
bpy.context.scene.BimTesterProperties.features_file + ".feature",
|
||||
)
|
||||
if os.path.exists(filename + "~"):
|
||||
os.remove(filename + "~")
|
||||
os.rename(filename, filename + "~")
|
||||
with open(filename, "w") as destination:
|
||||
with open(filename + "~", "r") as source:
|
||||
is_in_scenario = False
|
||||
for source_line in source:
|
||||
if (
|
||||
"Scenario: " in source_line
|
||||
and bpy.context.scene.BimTesterProperties.scenario == source_line.strip()[len("Scenario: ") :]
|
||||
):
|
||||
is_in_scenario = True
|
||||
elif is_in_scenario:
|
||||
for line in lines:
|
||||
destination.write(line + "\n")
|
||||
is_in_scenario = False
|
||||
destination.write(source_line)
|
||||
os.remove(filename + "~")
|
||||
|
||||
|
||||
colour_list = [
|
||||
(0.651, 0.81, 0.892, 1),
|
||||
(0.121, 0.471, 0.706, 1),
|
||||
(0.699, 0.876, 0.54, 1),
|
||||
(0.199, 0.629, 0.174, 1),
|
||||
(0.983, 0.605, 0.602, 1),
|
||||
(0.89, 0.101, 0.112, 1),
|
||||
(0.989, 0.751, 0.427, 1),
|
||||
(0.986, 0.497, 0.1, 1),
|
||||
(0.792, 0.699, 0.839, 1),
|
||||
(0.414, 0.239, 0.603, 1),
|
||||
(0.993, 0.999, 0.6, 1),
|
||||
(0.693, 0.349, 0.157, 1),
|
||||
]
|
||||
@@ -0,0 +1,49 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from blenderbim.bim.module.root.prop import getIfcClasses
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
scenarios_enum = []
|
||||
classes_enum = []
|
||||
|
||||
|
||||
def getScenarios(self, context):
|
||||
global scenarios_enum
|
||||
if len(scenarios_enum) < 1:
|
||||
scenarios_enum.clear()
|
||||
|
||||
if context.scene.BimTesterProperties.feature != "":
|
||||
with open(context.scene.BimTesterProperties.feature, "r") as feature_file:
|
||||
lines = feature_file.readlines()
|
||||
for line in lines:
|
||||
if "Scenario:" in line:
|
||||
s = line.strip()[len("Scenario: ") :]
|
||||
scenarios_enum.append((s, s, ""))
|
||||
return scenarios_enum
|
||||
|
||||
|
||||
def refreshScenarios(self, context):
|
||||
global scenarios_enum
|
||||
scenarios_enum.clear()
|
||||
getScenarios(self, context)
|
||||
|
||||
|
||||
class BimTesterProperties(PropertyGroup):
|
||||
feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios)
|
||||
steps: StringProperty(default="", name="Custom Steps")
|
||||
ifc_file: StringProperty(default="", name="IFC File")
|
||||
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
|
||||
qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
|
||||
scenario: EnumProperty(items=getScenarios, name="Scenario")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
@@ -0,0 +1,62 @@
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_qa(Panel):
|
||||
bl_label = "BIMTester"
|
||||
bl_idname = "BIM_PT_qa"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.use_property_split = True
|
||||
|
||||
props = context.scene.BimTesterProperties
|
||||
|
||||
if IfcStore.get_file():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "should_load_from_memory")
|
||||
|
||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "ifc_file")
|
||||
row.operator("bim.select_bimtester_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "feature")
|
||||
row.operator("bim.select_feature", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "steps")
|
||||
row.operator("bim.select_steps", icon="FILE_FOLDER", text="")
|
||||
|
||||
has_ifc_file = (IfcStore.get_file() and props.should_load_from_memory) or (
|
||||
props.ifc_file and not props.should_load_from_memory
|
||||
)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.execute_bim_tester")
|
||||
row.operator("bim.bim_tester_purge")
|
||||
|
||||
if props.feature and has_ifc_file:
|
||||
self.layout.label(text="Scenario Authoring:")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "scenario")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "qa_reject_element_reason")
|
||||
row = self.layout.row()
|
||||
row.operator("bim.reject_element")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "audit_ifc_class")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.approve_class")
|
||||
row.operator("bim.reject_class")
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.select_audited")
|
||||
@@ -0,0 +1,38 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ExportClashSets,
|
||||
operator.ImportClashSets,
|
||||
operator.AddClashSet,
|
||||
operator.RemoveClashSet,
|
||||
operator.AddClashSource,
|
||||
operator.RemoveClashSource,
|
||||
operator.SelectClashSource,
|
||||
operator.ExecuteIfcClash,
|
||||
operator.SelectIfcClashResults,
|
||||
operator.SelectClashResults,
|
||||
operator.SelectSmartGroupedClashesPath,
|
||||
operator.SmartClashGroup,
|
||||
operator.SelectSmartGroup,
|
||||
operator.LoadSmartGroupsForActiveClashSet,
|
||||
operator.SetBlenderClashSetA,
|
||||
operator.SetBlenderClashSetB,
|
||||
operator.ExecuteBlenderClash,
|
||||
prop.ClashSource,
|
||||
prop.ClashSet,
|
||||
prop.SmartClashGroup,
|
||||
prop.BIMClashProperties,
|
||||
ui.BIM_PT_ifcclash,
|
||||
ui.BIM_PT_clash_manager,
|
||||
ui.BIM_UL_clash_sets,
|
||||
ui.BIM_UL_smart_groups,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMClashProperties = bpy.props.PointerProperty(type=prop.BIMClashProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMClashProperties
|
||||
@@ -0,0 +1,456 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
from mathutils import Matrix
|
||||
from math import radians
|
||||
|
||||
|
||||
class ExportClashSets(bpy.types.Operator):
|
||||
bl_idname = "bim.export_clash_sets"
|
||||
bl_label = "Export Clash Sets"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
clash_sets = []
|
||||
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
|
||||
self.a = []
|
||||
self.b = []
|
||||
for ab in ["a", "b"]:
|
||||
for data in getattr(clash_set, ab):
|
||||
clash_source = {"file": data.name}
|
||||
if data.selector:
|
||||
clash_source["selector"] = data.selector
|
||||
clash_source["mode"] = data.mode
|
||||
getattr(self, ab).append(clash_source)
|
||||
clash_sets.append({"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b})
|
||||
with open(self.filepath, "w") as destination:
|
||||
destination.write(json.dumps(clash_sets, indent=4))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportClashSets(bpy.types.Operator):
|
||||
bl_idname = "bim.import_clash_sets"
|
||||
bl_label = "Import Clash Sets"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
with open(self.filepath) as f:
|
||||
clash_sets = json.load(f)
|
||||
for clash_set in clash_sets:
|
||||
new = bpy.context.scene.BIMClashProperties.clash_sets.add()
|
||||
new.name = clash_set["name"]
|
||||
new.tolerance = clash_set["tolerance"]
|
||||
for clash_source in clash_set["a"]:
|
||||
new_source = new.a.add()
|
||||
new_source.name = clash_source["file"]
|
||||
if "selector" in clash_source:
|
||||
new_source.selector = clash_source["selector"]
|
||||
new_source.mode = clash_source["mode"]
|
||||
if clash_set["b"]:
|
||||
for clash_source in clash_set["b"]:
|
||||
new_source = new.b.add()
|
||||
new_source.name = clash_source["file"]
|
||||
if "selector" in clash_source:
|
||||
new_source.selector = clash_source["selector"]
|
||||
new_source.mode = clash_source["mode"]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddClashSet(bpy.types.Operator):
|
||||
bl_idname = "bim.add_clash_set"
|
||||
bl_label = "Add Clash Set"
|
||||
|
||||
def execute(self, context):
|
||||
new = bpy.context.scene.BIMClashProperties.clash_sets.add()
|
||||
new.name = "New Clash Set"
|
||||
new.tolerance = 0.01
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveClashSet(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_clash_set"
|
||||
bl_label = "Remove Clash Set"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BIMClashProperties.clash_sets.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.add_clash_source"
|
||||
bl_label = "Add Clash Source"
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
|
||||
source = getattr(clash_set, self.group).add()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_clash_source"
|
||||
bl_label = "Remove Clash Source"
|
||||
index: bpy.props.IntProperty()
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
|
||||
getattr(clash_set, self.group).remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.select_clash_source"
|
||||
bl_label = "Select Clash Source"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
index: bpy.props.IntProperty()
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
|
||||
getattr(clash_set, self.group)[self.index].name = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectClashResults(bpy.types.Operator):
|
||||
bl_idname = "bim.select_clash_results"
|
||||
bl_label = "Select Clash Results"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BIMClashProperties.clash_results_path = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectSmartGroupedClashesPath(bpy.types.Operator):
|
||||
bl_idname = "bim.select_smart_grouped_clashes_path"
|
||||
bl_label = "Select Smart-Grouped Clashes Path"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExecuteIfcClash(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifc_clash"
|
||||
bl_label = "Execute IFC Clash"
|
||||
filename_ext = ".bcf"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
if ".json" not in bpy.data.filepath:
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
import ifcclash
|
||||
|
||||
settings = ifcclash.IfcClashSettings()
|
||||
if ".json" not in self.filepath:
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
settings.logger.setLevel(logging.DEBUG)
|
||||
ifc_clasher = ifcclash.IfcClasher(settings)
|
||||
|
||||
if bpy.context.scene.BIMClashProperties.should_create_clash_snapshots:
|
||||
|
||||
def get_viewpoint_snapshot(self, viewpoint, mat):
|
||||
camera = bpy.data.objects.get("IFC Clash Camera")
|
||||
if not camera:
|
||||
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
|
||||
bpy.context.scene.collection.objects.link(camera)
|
||||
camera.matrix_world = Matrix(mat)
|
||||
bpy.context.scene.camera = camera
|
||||
camera.data.angle = radians(60)
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
area.spaces[0].shading.show_xray = True
|
||||
bpy.context.scene.render.resolution_x = 480
|
||||
bpy.context.scene.render.resolution_y = 270
|
||||
bpy.context.scene.render.image_settings.file_format = "PNG"
|
||||
bpy.context.scene.render.filepath = os.path.join(
|
||||
bpy.context.scene.BIMProperties.data_dir, "snapshot.png"
|
||||
)
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
return bpy.context.scene.render.filepath
|
||||
|
||||
ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot
|
||||
|
||||
ifc_clasher.clash_sets = []
|
||||
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
|
||||
self.a = []
|
||||
self.b = []
|
||||
for ab in ["a", "b"]:
|
||||
for data in getattr(clash_set, ab):
|
||||
clash_source = {"file": data.name}
|
||||
if data.selector:
|
||||
clash_source["selector"] = data.selector
|
||||
clash_source["mode"] = data.mode
|
||||
getattr(self, ab).append(clash_source)
|
||||
ifc_clasher.clash_sets.append(
|
||||
{"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b}
|
||||
)
|
||||
ifc_clasher.clash()
|
||||
ifc_clasher.export()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectIfcClashResults(bpy.types.Operator):
|
||||
bl_idname = "bim.select_ifc_clash_results"
|
||||
bl_label = "Select IFC Clash Results"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
with open(self.filepath) as f:
|
||||
clash_sets = json.load(f)
|
||||
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
|
||||
bpy.context.scene.BIMClashProperties.active_clash_set_index
|
||||
].name
|
||||
global_ids = []
|
||||
for clash_set in clash_sets:
|
||||
if clash_set["name"] != clash_set_name:
|
||||
continue
|
||||
if not "clashes" in clash_set.keys():
|
||||
self.report({"WARNING"}, "No clashes found for the selected Clash Set.")
|
||||
return {"CANCELLED"}
|
||||
for clash in clash_set["clashes"].values():
|
||||
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
|
||||
for obj in bpy.context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if element.GlobalId in global_ids:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SmartClashGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.smart_clash_group"
|
||||
bl_label = "Smart Group Clashes"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
import ifcclash
|
||||
|
||||
settings = ifcclash.IfcClashSettings()
|
||||
self.filepath = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.clash_results_path, ".json")
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
settings.logger.setLevel(logging.DEBUG)
|
||||
ifc_clasher = ifcclash.IfcClasher(settings)
|
||||
|
||||
with open(self.filepath) as f:
|
||||
clash_sets = json.load(f)
|
||||
|
||||
# execute the smart grouping
|
||||
save_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
|
||||
smart_grouped_clashes = ifc_clasher.smart_group_clashes(
|
||||
clash_sets, bpy.context.scene.BIMClashProperties.smart_clash_grouping_max_distance
|
||||
)
|
||||
|
||||
# save smart_groups to json
|
||||
with open(save_path, "w") as f:
|
||||
f.write(json.dumps(smart_grouped_clashes))
|
||||
|
||||
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
|
||||
bpy.context.scene.BIMClashProperties.active_clash_set_index
|
||||
].name
|
||||
|
||||
# Reset the list of smart_clash_groups for the UI
|
||||
bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
|
||||
|
||||
for clash_set, smart_groups in smart_grouped_clashes.items():
|
||||
# Only select the clashes that correspond to the actively selected IFC Clash Set
|
||||
if clash_set != clash_set_name:
|
||||
continue
|
||||
else:
|
||||
for smart_group, global_id_pairs in smart_groups[0].items():
|
||||
new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
|
||||
new_group.number = f"{smart_group}"
|
||||
|
||||
for pair in global_id_pairs:
|
||||
for id in pair:
|
||||
new_global_id = new_group.global_ids.add()
|
||||
new_global_id.name = id
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
|
||||
bl_idname = "bim.load_smart_groups_for_active_clash_set"
|
||||
bl_label = "Load Smart Groups for Active Clash Set"
|
||||
|
||||
def execute(self, context):
|
||||
smart_groups_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
|
||||
|
||||
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
|
||||
bpy.context.scene.BIMClashProperties.active_clash_set_index
|
||||
].name
|
||||
|
||||
with open(smart_groups_path) as f:
|
||||
smart_grouped_clashes = json.load(f)
|
||||
|
||||
# Reset the list of smart_clash_groups for the UI
|
||||
bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
|
||||
|
||||
for clash_set, smart_groups in smart_grouped_clashes.items():
|
||||
# Only select the clashes that correspond to the actively selected IFC Clash Set
|
||||
if clash_set != clash_set_name:
|
||||
continue
|
||||
else:
|
||||
for smart_group, global_id_pairs in smart_groups[0].items():
|
||||
new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
|
||||
new_group.number = f"{smart_group}"
|
||||
for pair in global_id_pairs:
|
||||
for id in pair:
|
||||
new_global_id = new_group.global_ids.add()
|
||||
new_global_id.name = id
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectSmartGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.select_smart_group"
|
||||
bl_label = "Select Smart Group"
|
||||
|
||||
def execute(self, context):
|
||||
# Select smart group in view
|
||||
selected_smart_group = bpy.context.scene.BIMClashProperties.smart_clash_groups[
|
||||
bpy.context.scene.BIMCLashProperties.active_smart_group_index
|
||||
]
|
||||
# print(selected_smart_group.number)
|
||||
|
||||
for obj in bpy.context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
for id in selected_smart_group.global_ids:
|
||||
# print("Id: ", id)
|
||||
# print("Global id: ", element.GlobalId)
|
||||
if element.GlobalId in id.name:
|
||||
# print("object match: ", global_id)
|
||||
obj.select_set(True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BlenderClasher:
|
||||
def process_clash_set(self):
|
||||
import collision
|
||||
|
||||
a_cm = collision.CollisionManager()
|
||||
b_cm = collision.CollisionManager()
|
||||
self.add_to_cm(a_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_a)
|
||||
self.add_to_cm(b_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_b)
|
||||
results = a_cm.in_collision_other(b_cm, return_data=True)
|
||||
if not results[0]:
|
||||
print("No clashes")
|
||||
return
|
||||
for contact in results[1]:
|
||||
if contact.raw.penetration_depth < 0.01:
|
||||
continue
|
||||
print("-----")
|
||||
print(contact.names)
|
||||
print(contact.raw.normal)
|
||||
print(contact.raw.pos)
|
||||
|
||||
def add_to_cm(self, cm, object_names):
|
||||
import ifcclash
|
||||
|
||||
for object_name in object_names:
|
||||
name = object_name.name
|
||||
obj = bpy.data.objects[name]
|
||||
triangulated_mesh = self.triangulate_mesh(obj)
|
||||
mesh = ifcclash.Mesh()
|
||||
mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices])
|
||||
mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons])
|
||||
cm.add_object(name, mesh)
|
||||
|
||||
def triangulate_mesh(self, obj):
|
||||
mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.triangulate(bm, faces=bm.faces)
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
del bm
|
||||
return mesh
|
||||
|
||||
|
||||
class SetBlenderClashSetA(bpy.types.Operator):
|
||||
bl_idname = "bim.set_blender_clash_set_a"
|
||||
bl_label = "Set Blender Clash Set A"
|
||||
|
||||
def execute(self, context):
|
||||
while len(bpy.context.scene.BIMClashProperties.blender_clash_set_a) > 0:
|
||||
bpy.context.scene.BIMClashProperties.blender_clash_set_a.remove(0)
|
||||
for obj in bpy.context.selected_objects:
|
||||
new = bpy.context.scene.BIMClashProperties.blender_clash_set_a.add()
|
||||
new.name = obj.name
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetBlenderClashSetB(bpy.types.Operator):
|
||||
bl_idname = "bim.set_blender_clash_set_b"
|
||||
bl_label = "Set Blender Clash Set B"
|
||||
|
||||
def execute(self, context):
|
||||
while len(bpy.context.scene.BIMClashProperties.blender_clash_set_b) > 0:
|
||||
bpy.context.scene.BIMClashProperties.blender_clash_set_b.remove(0)
|
||||
for obj in bpy.context.selected_objects:
|
||||
new = bpy.context.scene.BIMClashProperties.blender_clash_set_b.add()
|
||||
new.name = obj.name
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExecuteBlenderClash(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_blender_clash"
|
||||
bl_label = "Execute Blender Clash"
|
||||
|
||||
def execute(self, context):
|
||||
blender_clasher = BlenderClasher()
|
||||
blender_clasher.process_clash_set()
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,52 @@
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class ClashSource(PropertyGroup):
|
||||
name: StringProperty(name="File")
|
||||
selector: StringProperty(name="Selector")
|
||||
mode: EnumProperty(
|
||||
items=[
|
||||
("i", "Include", "Only the selected objects are included for clashing"),
|
||||
("e", "Exclude", "All objects except the selected objects are included for clashing"),
|
||||
],
|
||||
name="Mode",
|
||||
)
|
||||
|
||||
|
||||
class ClashSet(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
tolerance: FloatProperty(name="Tolerance")
|
||||
a: CollectionProperty(name="Group A", type=ClashSource)
|
||||
b: CollectionProperty(name="Group B", type=ClashSource)
|
||||
|
||||
|
||||
class SmartClashGroup(PropertyGroup):
|
||||
number: StringProperty(name="Number")
|
||||
global_ids: CollectionProperty(name="GlobalIDs", type=StrProperty)
|
||||
|
||||
|
||||
class BIMClashProperties(PropertyGroup):
|
||||
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
|
||||
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
|
||||
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
|
||||
should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=True)
|
||||
clash_results_path: StringProperty(name="Clash Results Path")
|
||||
smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path")
|
||||
active_clash_set_index: IntProperty(name="Active Clash Set Index")
|
||||
smart_clash_groups: CollectionProperty(name="Smart Clash Groups", type=SmartClashGroup)
|
||||
active_smart_group_index: IntProperty(name="Active Smart Group Index")
|
||||
smart_clash_grouping_max_distance: IntProperty(
|
||||
name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10
|
||||
)
|
||||
@@ -0,0 +1,148 @@
|
||||
import bpy
|
||||
from bpy.types import Panel
|
||||
|
||||
|
||||
class BIM_PT_ifcclash(Panel):
|
||||
bl_label = "IFC Clash Sets"
|
||||
bl_idname = "BIM_PT_ifcclash"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
scene = context.scene
|
||||
props = scene.BIMClashProperties
|
||||
|
||||
layout.label(text="Blender Clash:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.set_blender_clash_set_a")
|
||||
row.operator("bim.set_blender_clash_set_b")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.execute_blender_clash")
|
||||
|
||||
layout.label(text="IFC Clash:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_clash_set")
|
||||
row.operator("bim.import_clash_sets", text="", icon="IMPORT")
|
||||
row.operator("bim.export_clash_sets", text="", icon="EXPORT")
|
||||
|
||||
if not props.clash_sets:
|
||||
return
|
||||
|
||||
layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index")
|
||||
|
||||
if props.active_clash_set_index < len(props.clash_sets):
|
||||
clash_set = props.clash_sets[props.active_clash_set_index]
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(clash_set, "name")
|
||||
row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(clash_set, "tolerance")
|
||||
|
||||
layout.label(text="Group A:")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_clash_source").group = "a"
|
||||
|
||||
for index, source in enumerate(clash_set.a):
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "name", text="")
|
||||
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.group = "a"
|
||||
op = row.operator("bim.remove_clash_source", icon="X", text="")
|
||||
op.index = index
|
||||
op.group = "a"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "mode", text="")
|
||||
row.prop(source, "selector", text="")
|
||||
|
||||
layout.label(text="Group B:")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_clash_source").group = "b"
|
||||
|
||||
for index, source in enumerate(clash_set.b):
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "name", text="")
|
||||
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.group = "b"
|
||||
op = row.operator("bim.remove_clash_source", icon="X", text="")
|
||||
op.index = index
|
||||
op.group = "b"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "mode", text="")
|
||||
row.prop(source, "selector", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "should_create_clash_snapshots")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.execute_ifc_clash")
|
||||
row.operator("bim.select_ifc_clash_results")
|
||||
|
||||
|
||||
class BIM_PT_clash_manager(Panel):
|
||||
bl_idname = "BIM_PT_clash_manager"
|
||||
bl_label = "Clash Manager"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.scene.BIMClashProperties
|
||||
|
||||
row = layout.row()
|
||||
layout.label(text="Select clash results to group:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "clash_results_path", text="")
|
||||
op = row.operator("bim.select_clash_results", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
layout.label(text="Select output path for smart-grouped clashes:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "smart_grouped_clashes_path", text="")
|
||||
op = row.operator("bim.select_smart_grouped_clashes_path", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "smart_clash_grouping_max_distance")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.smart_clash_group")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.load_smart_groups_for_active_clash_set")
|
||||
|
||||
layout.template_list("BIM_UL_smart_groups", "", props, "smart_clash_groups", props, "active_smart_group_index")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.select_smart_group")
|
||||
|
||||
|
||||
class BIM_UL_clash_sets(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
if item:
|
||||
layout.prop(item, "name", text="", emboss=False)
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
|
||||
class BIM_UL_smart_groups(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
if item:
|
||||
layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0)
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
@@ -0,0 +1,35 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.LoadClassificationLibrary,
|
||||
operator.AddClassification,
|
||||
operator.RemoveClassification,
|
||||
operator.EnableEditingClassification,
|
||||
operator.DisableEditingClassification,
|
||||
operator.EditClassification,
|
||||
operator.RemoveClassificationReference,
|
||||
operator.EnableEditingClassificationReference,
|
||||
operator.DisableEditingClassificationReference,
|
||||
operator.EditClassificationReference,
|
||||
operator.AddClassificationReference,
|
||||
operator.ChangeClassificationLevel,
|
||||
prop.ClassificationReference,
|
||||
prop.BIMClassificationProperties,
|
||||
prop.BIMClassificationReferenceProperties,
|
||||
ui.BIM_PT_classifications,
|
||||
ui.BIM_PT_classification_references,
|
||||
ui.BIM_UL_classifications,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMClassificationProperties = bpy.props.PointerProperty(type=prop.BIMClassificationProperties)
|
||||
bpy.types.Object.BIMClassificationReferenceProperties = bpy.props.PointerProperty(
|
||||
type=prop.BIMClassificationReferenceProperties
|
||||
)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMClassificationProperties
|
||||
del bpy.types.Object.BIMClassificationReferenceProperties
|
||||
@@ -0,0 +1,229 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api.classification.add_classification as add_classification
|
||||
import ifcopenshell.api.classification.remove_classification as remove_classification
|
||||
import ifcopenshell.api.classification.edit_classification as edit_classification
|
||||
import ifcopenshell.api.classification.add_reference as add_reference
|
||||
import ifcopenshell.api.classification.remove_reference as remove_reference
|
||||
import ifcopenshell.api.classification.edit_reference as edit_reference
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.classification.data import Data
|
||||
from blenderbim.bim.module.classification.prop import getClassifications, getReferences
|
||||
|
||||
|
||||
class LoadClassificationLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.load_classification_library"
|
||||
bl_label = "Load Classification Library"
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
Data.load_library(self.filepath)
|
||||
getClassifications(self, context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class AddClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.add_classification"
|
||||
bl_label = "Add Classification"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
add_classification.Usecase(
|
||||
IfcStore.get_file(), {"classification": Data.library_file.by_id(int(props.available_classifications))}
|
||||
).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_classification"
|
||||
bl_label = "Enable Editing Classification"
|
||||
classification: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
while len(props.classification_attributes) > 0:
|
||||
props.classification_attributes.remove(0)
|
||||
classification_data = Data.classifications[self.classification]
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcClassification").all_attributes():
|
||||
new = props.classification_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = classification_data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
if attribute.name() == "ReferenceTokens":
|
||||
new.string_value = "" if new.is_null else json.dumps(classification_data[attribute.name()])
|
||||
else:
|
||||
new.string_value = "" if new.is_null else classification_data[attribute.name()]
|
||||
props.active_classification_id = self.classification
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_classification"
|
||||
bl_label = "Disable Editing Classification"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMClassificationProperties.active_classification_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_classification"
|
||||
bl_label = "Remove Classification"
|
||||
classification: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
remove_classification.Usecase(self.file, {"classification": self.file.by_id(self.classification)}).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditClassification(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_classification"
|
||||
bl_label = "Edit Classification"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
attributes = {}
|
||||
for attribute in props.classification_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
elif attribute.name == "ReferenceTokens":
|
||||
attributes[attribute.name] = json.loads(attribute.string_value)
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_classification.Usecase(
|
||||
self.file, {"classification": self.file.by_id(props.active_classification_id), "attributes": attributes}
|
||||
).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_classification()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_classification_reference"
|
||||
bl_label = "Enable Editing Classification Reference"
|
||||
reference: bpy.props.IntProperty()
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
props = obj.BIMClassificationReferenceProperties
|
||||
while len(props.reference_attributes) > 0:
|
||||
props.reference_attributes.remove(0)
|
||||
reference_data = Data.references[self.reference]
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcClassificationReference").all_attributes():
|
||||
if attribute.name() == "ReferencedSource":
|
||||
continue
|
||||
new = props.reference_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = reference_data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.string_value = "" if new.is_null else reference_data[attribute.name()]
|
||||
props.active_reference_id = self.reference
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_classification_reference"
|
||||
bl_label = "Disable Editing Classification Reference"
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
obj.BIMClassificationReferenceProperties.active_reference_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_classification_reference"
|
||||
bl_label = "Remove Classification Reference"
|
||||
reference: bpy.props.IntProperty()
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
remove_reference.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"reference": self.file.by_id(self.reference),
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
},
|
||||
).execute()
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_classification_reference"
|
||||
bl_label = "Edit Classification Reference"
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
props = obj.BIMClassificationReferenceProperties
|
||||
attributes = {}
|
||||
for attribute in props.reference_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_reference.Usecase(
|
||||
self.file, {"reference": self.file.by_id(props.active_reference_id), "attributes": attributes}
|
||||
).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_classification_reference()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddClassificationReference(bpy.types.Operator):
|
||||
bl_idname = "bim.add_classification_reference"
|
||||
bl_label = "Add Classification Reference"
|
||||
reference: bpy.props.IntProperty()
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
classification = None
|
||||
|
||||
props = context.scene.BIMClassificationProperties
|
||||
classification_name = Data.library_classifications[int(props.available_classifications)]
|
||||
for classification_id, classification in Data.classifications.items():
|
||||
if classification["Name"] == classification_name:
|
||||
classification = self.file.by_id(classification_id)
|
||||
break
|
||||
|
||||
add_reference.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"reference": Data.library_file.by_id(self.reference),
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"classification": classification
|
||||
},
|
||||
).execute()
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ChangeClassificationLevel(bpy.types.Operator):
|
||||
bl_idname = "bim.change_classification_level"
|
||||
bl_label = "Change Classification Level"
|
||||
parent_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
getReferences(self, context, parent_id=self.parent_id)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,71 @@
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from ifcopenshell.api.classification.data import Data
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
classification_enum = []
|
||||
|
||||
|
||||
def getClassifications(self, context):
|
||||
global classification_enum
|
||||
if len(classification_enum) < 1:
|
||||
classification_enum.clear()
|
||||
classification_enum.extend([(str(i), n, "") for i, n in Data.library_classifications.items()])
|
||||
if classification_enum:
|
||||
getReferences(self, context, parent_id=int(classification_enum[0][0]))
|
||||
return classification_enum
|
||||
|
||||
|
||||
def updateClassification(self, context):
|
||||
getReferences(self, context, parent_id=int(self.available_classifications))
|
||||
|
||||
|
||||
def getReferences(self, context, parent_id=None):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
while len(props.available_library_references) > 0:
|
||||
props.available_library_references.remove(0)
|
||||
for reference in Data.library_file.by_id(parent_id).HasReferences:
|
||||
new = props.available_library_references.add()
|
||||
new.identification = reference.Identification or ""
|
||||
new.name = reference.Name or ""
|
||||
new.ifc_definition_id = reference.id()
|
||||
new.has_references = bool(reference.HasReferences)
|
||||
new.referenced_source
|
||||
if reference.ReferencedSource.is_a("IfcClassificationReference"):
|
||||
props.active_library_referenced_source = reference.ReferencedSource.ReferencedSource.id()
|
||||
else:
|
||||
props.active_library_referenced_source = 0
|
||||
|
||||
|
||||
class ClassificationReference(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
identification: StringProperty(name="Identification")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
has_references: BoolProperty(name="Has References")
|
||||
referenced_source: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class BIMClassificationProperties(PropertyGroup):
|
||||
available_classifications: EnumProperty(
|
||||
items=getClassifications, name="Available Classifications", update=updateClassification
|
||||
)
|
||||
classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute)
|
||||
active_classification_id: IntProperty(name="Active Classification Id")
|
||||
available_library_references: CollectionProperty(name="Available Library References", type=ClassificationReference)
|
||||
active_library_referenced_source: IntProperty(name="Active Library Referenced Source")
|
||||
active_library_reference_index: IntProperty(name="Active Library Reference Index")
|
||||
|
||||
|
||||
class BIMClassificationReferenceProperties(PropertyGroup):
|
||||
reference_attributes: CollectionProperty(name="Reference Attributes", type=Attribute)
|
||||
active_reference_id: IntProperty(name="Active Reference Id")
|
||||
@@ -0,0 +1,159 @@
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.classification.data import Data
|
||||
|
||||
|
||||
class BIM_PT_classifications(Panel):
|
||||
bl_label = "IFC Classifications"
|
||||
bl_idname = "BIM_PT_classifications"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
self.props = context.scene.BIMClassificationProperties
|
||||
|
||||
if Data.library_file:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "available_classifications", text="")
|
||||
row.operator("bim.load_classification_library", text="", icon="IMPORT")
|
||||
row.operator("bim.add_classification", text="", icon="ADD")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Active Classification Library")
|
||||
row.operator("bim.load_classification_library", text="", icon="IMPORT")
|
||||
|
||||
for classification_id, classification in Data.classifications.items():
|
||||
if self.props.active_classification_id == classification_id:
|
||||
self.draw_editable_ui(classification)
|
||||
else:
|
||||
self.draw_ui(classification_id, classification)
|
||||
|
||||
def draw_editable_ui(self, classification):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props.classification_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER")
|
||||
row.operator("bim.edit_classification", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_classification", text="", icon="X")
|
||||
|
||||
for attribute in self.props.classification_attributes:
|
||||
if attribute.name == "Name":
|
||||
continue
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_ui(self, classification_id, classification):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=classification["Name"], icon="ASSET_MANAGER")
|
||||
if not self.props.active_classification_id:
|
||||
op = row.operator("bim.enable_editing_classification", text="", icon="GREASEPENCIL")
|
||||
op.classification = classification_id
|
||||
row.operator("bim.remove_classification", text="", icon="X").classification = classification_id
|
||||
|
||||
|
||||
class BIM_PT_classification_references(Panel):
|
||||
bl_label = "IFC Classification References"
|
||||
bl_idname = "BIM_PT_classification_references"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.active_object
|
||||
self.oprops = obj.BIMObjectProperties
|
||||
self.sprops = context.scene.BIMClassificationProperties
|
||||
self.props = obj.BIMClassificationReferenceProperties
|
||||
self.file = IfcStore.get_file()
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if self.oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
|
||||
|
||||
self.draw_add_ui()
|
||||
|
||||
reference_ids = Data.products[self.oprops.ifc_definition_id]
|
||||
if not reference_ids:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No References")
|
||||
|
||||
for reference_id in reference_ids:
|
||||
reference = Data.references[reference_id]
|
||||
if self.props.active_reference_id == reference_id:
|
||||
self.draw_editable_ui(reference)
|
||||
else:
|
||||
self.draw_ui(reference_id, reference)
|
||||
|
||||
def draw_add_ui(self):
|
||||
if not self.sprops.available_classifications:
|
||||
return
|
||||
|
||||
name = Data.library_classifications[int(self.sprops.available_classifications)]
|
||||
if name in [c["Name"] for c in Data.classifications.values()]:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.sprops, "available_classifications", text="")
|
||||
if self.sprops.active_library_referenced_source:
|
||||
op = row.operator("bim.change_classification_level", text="", icon="FRAME_PREV")
|
||||
op.parent_id = self.sprops.active_library_referenced_source
|
||||
op = row.operator("bim.add_classification_reference", text="", icon="ADD")
|
||||
op.reference = self.sprops.available_library_references[
|
||||
self.sprops.active_library_reference_index
|
||||
].ifc_definition_id
|
||||
self.layout.template_list(
|
||||
"BIM_UL_classifications",
|
||||
"",
|
||||
self.sprops,
|
||||
"available_library_references",
|
||||
self.sprops,
|
||||
"active_library_reference_index",
|
||||
)
|
||||
|
||||
def draw_editable_ui(self, reference):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props.reference_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER")
|
||||
row.operator("bim.edit_classification_reference", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_classification_reference", text="", icon="X")
|
||||
|
||||
for attribute in self.props.reference_attributes:
|
||||
if attribute.name == "Name":
|
||||
continue
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_ui(self, reference_id, reference):
|
||||
row = self.layout.row(align=True)
|
||||
if self.file.schema == "IFC2X3":
|
||||
name = reference["ItemReference"] or "No Identification"
|
||||
else:
|
||||
name = reference["Identification"] or "No Identification"
|
||||
row.label(text=name, icon="ASSET_MANAGER")
|
||||
row.label(text=reference["Name"] or "")
|
||||
if not self.props.active_reference_id:
|
||||
op = row.operator("bim.enable_editing_classification_reference", text="", icon="GREASEPENCIL")
|
||||
op.reference = reference_id
|
||||
row.operator("bim.remove_classification_reference", text="", icon="X").reference = reference_id
|
||||
|
||||
|
||||
class BIM_UL_classifications(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
if item.has_references:
|
||||
op = layout.operator("bim.change_classification_level", text="", icon="DISCLOSURE_TRI_RIGHT")
|
||||
op.parent_id = item.ifc_definition_id
|
||||
layout.label(text=item.identification)
|
||||
layout.label(text=item.name)
|
||||
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.SelectCobieIfcFile,
|
||||
operator.SelectCobieJsonFile,
|
||||
operator.ExecuteIfcCobie,
|
||||
prop.COBieProperties,
|
||||
ui.BIM_PT_cobie,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.COBieProperties = bpy.props.PointerProperty(type=prop.COBieProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.COBieProperties
|
||||
@@ -0,0 +1,99 @@
|
||||
import bpy
|
||||
import os
|
||||
import logging
|
||||
import ifcopenshell
|
||||
import json
|
||||
import webbrowser
|
||||
import tempfile
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class SelectCobieIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_cobie_ifc_file"
|
||||
bl_label = "Select COBie IFC File"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.COBieProperties.cobie_ifc_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectCobieJsonFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_cobie_json_file"
|
||||
bl_label = "Select COBie JSON File"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.COBieProperties.cobie_json_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExecuteIfcCobie(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifc_cobie"
|
||||
bl_label = "Execute IFCCOBie"
|
||||
file_format: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
from cobie import IfcCobieParser
|
||||
props = bpy.context.scene.COBieProperties
|
||||
|
||||
output_dir = os.path.dirname(props.cobie_ifc_file)
|
||||
|
||||
if props.should_load_from_memory:
|
||||
output_dir = tempfile.gettempdir()
|
||||
|
||||
output = os.path.join(output_dir, "output")
|
||||
logger = logging.getLogger("IFCtoCOBie")
|
||||
fh = logging.FileHandler(os.path.join(output_dir, "cobie.log"))
|
||||
fh.setLevel(logging.DEBUG)
|
||||
fh.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s : %(message)s"))
|
||||
logger = logging.getLogger("IFCtoCOBie")
|
||||
logger.addHandler(fh)
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
if props.cobie_json_file:
|
||||
with open(props.cobie_json_file, "r") as f:
|
||||
custom_data = json.load(f)
|
||||
else:
|
||||
custom_data = {}
|
||||
parser = IfcCobieParser(logger, selector)
|
||||
|
||||
ifc_file = IfcStore.get_file()
|
||||
|
||||
if not (ifc_file and props.should_load_from_memory):
|
||||
ifc_file = props.cobie_ifc_file
|
||||
|
||||
parser.parse(
|
||||
ifc_file,
|
||||
props.cobie_types,
|
||||
props.cobie_components,
|
||||
custom_data,
|
||||
)
|
||||
if self.file_format == "xlsx":
|
||||
from cobie import CobieXlsWriter
|
||||
|
||||
writer = CobieXlsWriter(parser, output)
|
||||
writer.write()
|
||||
webbrowser.open("file://" + output + "." + self.file_format)
|
||||
elif self.file_format == "ods":
|
||||
from cobie import CobieOdsWriter
|
||||
|
||||
writer = CobieOdsWriter(parser, output)
|
||||
writer.write()
|
||||
webbrowser.open("file://" + output + "." + self.file_format)
|
||||
else:
|
||||
from cobie import CobieCsvWriter
|
||||
|
||||
writer = CobieCsvWriter(parser, output_dir)
|
||||
writer.write()
|
||||
webbrowser.open("file://" + output_dir)
|
||||
webbrowser.open("file://" + output_dir + "/cobie.log")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class COBieProperties(PropertyGroup):
|
||||
cobie_ifc_file: StringProperty(default="", name="COBie IFC File")
|
||||
cobie_types: StringProperty(default=".COBieType", name="COBie Types")
|
||||
cobie_components: StringProperty(default=".COBie", name="COBie Components")
|
||||
cobie_json_file: StringProperty(default="", name="COBie JSON File")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_cobie(Panel):
|
||||
bl_label = "IFC COBie"
|
||||
bl_idname = "BIM_PT_cobie"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
|
||||
scene = context.scene
|
||||
props = scene.COBieProperties
|
||||
|
||||
|
||||
if IfcStore.get_file():
|
||||
row = layout.row()
|
||||
row.prop(props, "should_load_from_memory")
|
||||
|
||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "cobie_ifc_file")
|
||||
row.operator("bim.select_cobie_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "cobie_types")
|
||||
row = layout.row()
|
||||
row.prop(props, "cobie_components")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "cobie_json_file")
|
||||
row.operator("bim.select_cobie_json_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.execute_ifc_cobie", text="CSV")
|
||||
op.file_format = "csv"
|
||||
op = row.operator("bim.execute_ifc_cobie", text="ODS")
|
||||
op.file_format = "ods"
|
||||
op = row.operator("bim.execute_ifc_cobie", text="XLSX")
|
||||
op.file_format = "xlsx"
|
||||
@@ -0,0 +1,33 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.LoadObjectives,
|
||||
operator.DisableConstraintEditingUI,
|
||||
operator.EnableEditingConstraint,
|
||||
operator.DisableEditingConstraint,
|
||||
operator.AddObjective,
|
||||
operator.EditObjective,
|
||||
operator.RemoveConstraint,
|
||||
operator.EnableAssigningConstraint,
|
||||
operator.DisableAssigningConstraint,
|
||||
operator.AssignConstraint,
|
||||
operator.UnassignConstraint,
|
||||
prop.Constraint,
|
||||
prop.BIMConstraintProperties,
|
||||
prop.BIMObjectConstraintProperties,
|
||||
ui.BIM_PT_constraints,
|
||||
ui.BIM_PT_object_constraints,
|
||||
ui.BIM_UL_constraints,
|
||||
ui.BIM_UL_object_constraints,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMConstraintProperties = bpy.props.PointerProperty(type=prop.BIMConstraintProperties)
|
||||
bpy.types.Object.BIMObjectConstraintProperties = bpy.props.PointerProperty(type=prop.BIMObjectConstraintProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMConstraintProperties
|
||||
del bpy.types.Object.BIMObjectConstraintProperties
|
||||
@@ -0,0 +1,188 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.api.constraint.add_objective as add_objective
|
||||
import ifcopenshell.api.constraint.edit_objective as edit_objective
|
||||
import ifcopenshell.api.constraint.remove_constraint as remove_constraint
|
||||
import ifcopenshell.api.constraint.assign_constraint as assign_constraint
|
||||
import ifcopenshell.api.constraint.unassign_constraint as unassign_constraint
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.constraint.data import Data
|
||||
|
||||
|
||||
class LoadObjectives(bpy.types.Operator):
|
||||
bl_idname = "bim.load_objectives"
|
||||
bl_label = "Load Objectives"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
while len(props.constraints) > 0:
|
||||
props.constraints.remove(0)
|
||||
for constraint_id, constraint in Data.objectives.items():
|
||||
new = props.constraints.add()
|
||||
new.name = constraint["Name"] or "Unnamed"
|
||||
new.ifc_definition_id = constraint_id
|
||||
props.is_editing = "IfcObjective"
|
||||
bpy.ops.bim.disable_editing_constraint()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableConstraintEditingUI(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_constraint_editing_ui"
|
||||
bl_label = "Disable Constraint Editing UI"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMConstraintProperties.is_editing = ""
|
||||
bpy.ops.bim.disable_editing_constraint()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_constraint"
|
||||
bl_label = "Enable Editing Constraint"
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
while len(props.constraint_attributes) > 0:
|
||||
props.constraint_attributes.remove(0)
|
||||
|
||||
if props.is_editing == "IfcObjective":
|
||||
data = Data.objectives[self.constraint]
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(props.is_editing).all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.constraint_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if data_type == "string":
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
props.active_constraint_id = self.constraint
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_constraint"
|
||||
bl_label = "Disable Editing Constraint"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMConstraintProperties.active_constraint_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddObjective(bpy.types.Operator):
|
||||
bl_idname = "bim.add_objective"
|
||||
bl_label = "Add Objective"
|
||||
|
||||
def execute(self, context):
|
||||
result = add_objective.Usecase(IfcStore.get_file()).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_objectives()
|
||||
bpy.ops.bim.enable_editing_constraint(constraint=result.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditObjective(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_objective"
|
||||
bl_label = "Edit Objective"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
attributes = {}
|
||||
for attribute in props.constraint_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
elif attribute.enum_items:
|
||||
attributes[attribute.name] = attribute.enum_value
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_objective.Usecase(
|
||||
self.file, {"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes}
|
||||
).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_objectives()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_constraint"
|
||||
bl_label = "Remove Constraint"
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
self.file = IfcStore.get_file()
|
||||
remove_constraint.Usecase(self.file, {"constraint": self.file.by_id(self.constraint)}).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
if props.is_editing == "IfcObjective":
|
||||
bpy.ops.bim.load_objectives()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableAssigningConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_assigning_constraint"
|
||||
bl_label = "Enable Assigning Constraint"
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectConstraintProperties
|
||||
if props.available_constraint_types == "IfcObjective":
|
||||
bpy.ops.bim.load_objectives()
|
||||
props.is_adding = props.available_constraint_types
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableAssigningConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_assigning_constraint"
|
||||
bl_label = "Disable Assigning Constraint"
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectConstraintProperties
|
||||
props.is_adding = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_constraint"
|
||||
bl_label = "Assign Constraint"
|
||||
obj: bpy.props.StringProperty()
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
assign_constraint.Usecase(self.file, {
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"constraint": self.file.by_id(self.constraint)
|
||||
}).execute()
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignConstraint(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_constraint"
|
||||
bl_label = "Unassign Constraint"
|
||||
obj: bpy.props.StringProperty()
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
unassign_constraint.Usecase(self.file, {
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"constraint": self.file.by_id(self.constraint)
|
||||
}).execute()
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,33 @@
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class Constraint(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class BIMConstraintProperties(PropertyGroup):
|
||||
constraint_attributes: CollectionProperty(name="Constraint Attributes", type=Attribute)
|
||||
active_constraint_id: IntProperty(name="Active Constraint Id")
|
||||
constraints: CollectionProperty(name="Constraints", type=Constraint)
|
||||
active_constraint_index: IntProperty(name="Active Constraint Index")
|
||||
is_editing: StringProperty(name="Is Editing")
|
||||
|
||||
|
||||
class BIMObjectConstraintProperties(PropertyGroup):
|
||||
is_adding: StringProperty(name="Is Adding")
|
||||
available_constraint_types: EnumProperty(
|
||||
items=[(c, c, "") for c in ["IfcObjective"]], name="Available Constraint Types"
|
||||
)
|
||||
@@ -0,0 +1,140 @@
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.constraint.data import Data
|
||||
|
||||
|
||||
class BIM_PT_constraints(Panel):
|
||||
bl_label = "IFC Constraints"
|
||||
bl_idname = "BIM_PT_constraints"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
self.props = context.scene.BIMConstraintProperties
|
||||
|
||||
if not self.props.is_editing or self.props.is_editing == "IfcObjective":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Objectives Found".format(len(Data.objectives)), icon="LIGHT")
|
||||
if self.props.is_editing == "IfcObjective":
|
||||
row.operator("bim.disable_constraint_editing_ui", text="", icon="CHECKMARK")
|
||||
row.operator("bim.add_objective", text="", icon="ADD")
|
||||
else:
|
||||
row.operator("bim.load_objectives", text="", icon="GREASEPENCIL")
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_constraints",
|
||||
"",
|
||||
self.props,
|
||||
"constraints",
|
||||
self.props,
|
||||
"active_constraint_index",
|
||||
)
|
||||
|
||||
if self.props.active_constraint_id:
|
||||
self.draw_editable_ui(context)
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
for attribute in self.props.constraint_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
|
||||
class BIM_PT_object_constraints(Panel):
|
||||
bl_label = "IFC Constraints"
|
||||
bl_idname = "BIM_PT_object_constraints"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.active_object
|
||||
self.oprops = obj.BIMObjectProperties
|
||||
self.sprops = context.scene.BIMConstraintProperties
|
||||
self.props = obj.BIMObjectConstraintProperties
|
||||
self.file = IfcStore.get_file()
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if self.oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
|
||||
|
||||
self.draw_add_ui()
|
||||
|
||||
constraint_ids = Data.products[self.oprops.ifc_definition_id]
|
||||
|
||||
if not constraint_ids:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Constraints", icon="LIGHT")
|
||||
|
||||
for constraint_id in constraint_ids:
|
||||
try:
|
||||
constraint = Data.objectives[constraint_id]
|
||||
icon = "LIGHT"
|
||||
except:
|
||||
pass # Metric not implemented
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=constraint.get("Name") or "Unnamed")
|
||||
row.operator("bim.unassign_constraint", text="", icon="X").constraint = constraint_id
|
||||
|
||||
def draw_add_ui(self):
|
||||
if self.props.is_adding:
|
||||
row = self.layout.row(align=True)
|
||||
icon = "LIGHT" if self.props.is_adding == "IfcObjective" else "FILE_HIDDEN"
|
||||
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
|
||||
row.operator("bim.disable_assigning_constraint", text="", icon="X")
|
||||
self.layout.template_list(
|
||||
"BIM_UL_object_constraints",
|
||||
"",
|
||||
self.sprops,
|
||||
"constraints",
|
||||
self.sprops,
|
||||
"active_constraint_index",
|
||||
)
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "available_constraint_types", text="")
|
||||
row.operator("bim.enable_assigning_constraint", text="", icon="ADD")
|
||||
|
||||
|
||||
class BIM_UL_constraints(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
if context.scene.BIMConstraintProperties.active_constraint_id == item.ifc_definition_id:
|
||||
if context.scene.BIMConstraintProperties.is_editing == "IfcObjective":
|
||||
row.operator("bim.edit_objective", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_constraint", text="", icon="X")
|
||||
elif context.scene.BIMConstraintProperties.active_constraint_id:
|
||||
row.operator("bim.remove_constraint", text="", icon="X").constraint = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_constraint", text="", icon="GREASEPENCIL")
|
||||
op.constraint = item.ifc_definition_id
|
||||
row.operator("bim.remove_constraint", text="", icon="X").constraint = item.ifc_definition_id
|
||||
|
||||
|
||||
class BIM_UL_object_constraints(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
row.operator("bim.assign_constraint", text="", icon="ADD").constraint = item.ifc_definition_id
|
||||
@@ -0,0 +1,16 @@
|
||||
import bpy
|
||||
from . import ui, operator
|
||||
|
||||
classes = (
|
||||
operator.AddSubcontext,
|
||||
operator.RemoveSubcontext,
|
||||
ui.BIM_PT_context,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
@@ -0,0 +1,39 @@
|
||||
import bpy
|
||||
import ifcopenshell.api.context.add_context as add_context
|
||||
import ifcopenshell.api.context.remove_context as remove_context
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.context.data import Data
|
||||
|
||||
|
||||
class AddSubcontext(bpy.types.Operator):
|
||||
bl_idname = "bim.add_subcontext"
|
||||
bl_label = "Add Subcontext"
|
||||
context: bpy.props.StringProperty()
|
||||
subcontext: bpy.props.StringProperty()
|
||||
target_view: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
add_context.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"context": self.context or bpy.context.scene.BIMProperties.available_contexts,
|
||||
"subcontext": self.subcontext or bpy.context.scene.BIMProperties.available_subcontexts,
|
||||
"target_view": self.target_view or bpy.context.scene.BIMProperties.available_target_views,
|
||||
},
|
||||
).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveSubcontext(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_subcontext"
|
||||
bl_label = "Remove Context"
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
usecase = remove_context.Usecase(self.file, {"context": self.file.by_id(self.ifc_definition_id)})
|
||||
usecase.execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,40 @@
|
||||
from bpy.types import Panel
|
||||
from ifcopenshell.api.context.data import Data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_context(Panel):
|
||||
bl_label = "IFC Geometric Representation Contexts"
|
||||
bl_idname = "BIM_PT_context"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
props = context.scene.BIMProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "available_contexts", text="")
|
||||
row.prop(props, "available_subcontexts", text="")
|
||||
row.prop(props, "available_target_views", text="")
|
||||
row.operator("bim.add_subcontext", icon="ADD", text="")
|
||||
|
||||
for ifc_definition_id, context in Data.contexts.items():
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text=context["ContextType"])
|
||||
row.operator("bim.remove_subcontext", icon="X", text="").ifc_definition_id = ifc_definition_id
|
||||
for ifc_definition_id2, subcontext in context["HasSubContexts"].items():
|
||||
row = box.row(align=True)
|
||||
row.label(text=subcontext["ContextType"])
|
||||
row.label(text=subcontext["ContextIdentifier"])
|
||||
row.label(text=subcontext["TargetView"])
|
||||
row.operator("bim.remove_subcontext", icon="X", text="").ifc_definition_id = ifc_definition_id2
|
||||
@@ -0,0 +1,21 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
prop.CoveToolProject,
|
||||
prop.CoveToolSimpleAnalysis,
|
||||
prop.CoveToolProperties,
|
||||
ui.BIM_UL_covetool_projects,
|
||||
ui.BIM_PT_covetool,
|
||||
operator.Login,
|
||||
operator.RunSimpleAnalysis,
|
||||
operator.RunAnalysis,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.CoveToolProperties = bpy.props.PointerProperty(type=prop.CoveToolProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.CoveToolProperties
|
||||
@@ -0,0 +1,40 @@
|
||||
import requests
|
||||
|
||||
|
||||
class Api:
|
||||
def login(self, username, password):
|
||||
data = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
response_data = self.post_request("get-token", data, False)
|
||||
if "token" in response_data:
|
||||
self.token = str(response_data["token"])
|
||||
return self.token
|
||||
|
||||
def post_request(self, path, data, use_token=True):
|
||||
url = self._api_url(path)
|
||||
headers = self._headers(use_token)
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
return self._handle_response(response)
|
||||
|
||||
def get_request(self, path, use_token=True):
|
||||
url = self._api_url(path)
|
||||
headers = self._headers(use_token)
|
||||
response = requests.get(url, headers=headers)
|
||||
return self._handle_response(response)
|
||||
|
||||
def _api_url(self, path):
|
||||
return "https://app.covetool.com/api/" + path + "/"
|
||||
|
||||
def _headers(self, use_token):
|
||||
headers = {}
|
||||
if use_token:
|
||||
headers["Authorization"] = "Token " + self.token
|
||||
return headers
|
||||
|
||||
def _handle_response(self, response):
|
||||
if response.ok:
|
||||
return response.json()
|
||||
else:
|
||||
return {"result": "error"}
|
||||
@@ -0,0 +1,200 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
from math import degrees, atan2
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
from .api import Api
|
||||
|
||||
api = Api()
|
||||
|
||||
|
||||
class Login(bpy.types.Operator):
|
||||
bl_idname = "bim.covetool_login"
|
||||
bl_label = "Login to cove.tool"
|
||||
|
||||
def execute(self, context):
|
||||
token = api.login(bpy.context.scene.CoveToolProperties.username, bpy.context.scene.CoveToolProperties.password)
|
||||
if token:
|
||||
bpy.context.scene.CoveToolProperties.token = token
|
||||
|
||||
projects = api.get_request("projects")
|
||||
for project in projects:
|
||||
new_project = bpy.context.scene.CoveToolProperties.projects.add()
|
||||
new_project.name = project["name"]
|
||||
new_project.run_set = project["run_set"][0]
|
||||
new_project.url = project["url"]
|
||||
else:
|
||||
self.report({"ERROR"}, "Login failed :(")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RunSimpleAnalysis(bpy.types.Operator):
|
||||
bl_idname = "bim.covetool_run_simple_analysis"
|
||||
bl_label = "Run Simple Analysis"
|
||||
|
||||
def execute(self, context):
|
||||
simple_analysis = bpy.context.scene.CoveToolProperties.simple_analysis
|
||||
data = {
|
||||
"run": bpy.context.scene.CoveToolProperties.projects[
|
||||
bpy.context.scene.CoveToolProperties.active_project_index
|
||||
].run_set,
|
||||
"si_units": simple_analysis.si_units,
|
||||
"building_height": simple_analysis.building_height,
|
||||
"roof_area": simple_analysis.roof_area,
|
||||
"floor_area": simple_analysis.floor_area,
|
||||
"skylight_area": simple_analysis.skylight_area,
|
||||
"wall_area_e": simple_analysis.wall_area_e,
|
||||
"wall_area_ne": simple_analysis.wall_area_ne,
|
||||
"wall_area_n": simple_analysis.wall_area_n,
|
||||
"wall_area_nw": simple_analysis.wall_area_nw,
|
||||
"wall_area_w": simple_analysis.wall_area_w,
|
||||
"wall_area_sw": simple_analysis.wall_area_sw,
|
||||
"wall_area_s": simple_analysis.wall_area_s,
|
||||
"wall_area_se": simple_analysis.wall_area_se,
|
||||
"window_area_e": simple_analysis.window_area_e,
|
||||
"window_area_ne": simple_analysis.window_area_ne,
|
||||
"window_area_n": simple_analysis.window_area_n,
|
||||
"window_area_nw": simple_analysis.window_area_nw,
|
||||
"window_area_w": simple_analysis.window_area_w,
|
||||
"window_area_sw": simple_analysis.window_area_sw,
|
||||
"window_area_s": simple_analysis.window_area_s,
|
||||
"window_area_se": simple_analysis.window_area_se,
|
||||
}
|
||||
result = api.post_request("run-values", data)
|
||||
covetool_results = bpy.data.texts.new("cove.tool Results")
|
||||
covetool_results.write(json.dumps(result, indent=4))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RunAnalysis(bpy.types.Operator):
|
||||
bl_idname = "bim.covetool_run_analysis"
|
||||
bl_label = "Run Analysis"
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.inputs = {
|
||||
"floors": [],
|
||||
"walls": [],
|
||||
"interior_walls": [],
|
||||
"windows": [],
|
||||
"skylights": [],
|
||||
"roofs": [],
|
||||
"shading_devices": [],
|
||||
}
|
||||
self.parse_objects()
|
||||
data = {
|
||||
"run": bpy.context.scene.CoveToolProperties.projects[
|
||||
bpy.context.scene.CoveToolProperties.active_project_index
|
||||
].run_set,
|
||||
"source": "BlenderBIM",
|
||||
"rotation_angle": self.get_rotation_angle(),
|
||||
**self.inputs,
|
||||
}
|
||||
result = api.post_request("run-values", data)
|
||||
covetool_results = bpy.data.texts.new("cove.tool Results")
|
||||
covetool_results.write(json.dumps(result, indent=4))
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_rotation_angle(self):
|
||||
if not self.file.by_type("IfcMapConversion"):
|
||||
return 0
|
||||
map_conversion = self.file.by_type("IfcMapConversion")[0]
|
||||
rotation = -1 * degrees(
|
||||
atan2(
|
||||
float(map_conversion.XAxisOrdinate or 0),
|
||||
float(map_conversion.XAxisAbscissa or 1),
|
||||
)
|
||||
)
|
||||
if rotation < 0:
|
||||
rotation = 360 - rotation
|
||||
return rotation
|
||||
|
||||
def parse_objects(self):
|
||||
for obj in bpy.context.visible_objects:
|
||||
covetool_category = self.get_covetool_category(obj)
|
||||
if not covetool_category:
|
||||
continue
|
||||
if not self.has_triangulate_modifier(obj):
|
||||
obj.modifiers.new(name="Triangulate", type="TRIANGULATE")
|
||||
mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
|
||||
meshes = {}
|
||||
for polygon in mesh.polygons:
|
||||
normal = "{}|{}|{}".format(
|
||||
round(polygon.normal[0], 2), round(polygon.normal[1], 2), round(polygon.normal[2], 2)
|
||||
)
|
||||
meshes.setdefault(
|
||||
normal,
|
||||
{
|
||||
"Mesh": {"vertex_indices": {}, "Vertices": [], "Triangles": []},
|
||||
"Center": {},
|
||||
"Normal": {
|
||||
"X": round(polygon.normal[0], 2),
|
||||
"Y": round(polygon.normal[1], 2),
|
||||
"Z": round(polygon.normal[2], 2),
|
||||
},
|
||||
},
|
||||
)
|
||||
for vertex in polygon.vertices:
|
||||
global_vertex = obj.matrix_world @ mesh.vertices[vertex].co
|
||||
meshes[normal]["Mesh"]["vertex_indices"][vertex] = {
|
||||
"X": global_vertex[0] * 3.28084, # Covetools require feet
|
||||
"Y": global_vertex[1] * 3.28084,
|
||||
"Z": global_vertex[2] * 3.28084,
|
||||
}
|
||||
meshes[normal]["Mesh"]["Triangles"].append(
|
||||
[polygon.vertices[0], polygon.vertices[1], polygon.vertices[2]]
|
||||
)
|
||||
for normal, mesh in meshes.items():
|
||||
sorted_keys = sorted(mesh["Mesh"]["vertex_indices"])
|
||||
mesh["Mesh"]["Vertices"] = [mesh["Mesh"]["vertex_indices"][k] for k in sorted_keys]
|
||||
for triangle in mesh["Mesh"]["Triangles"]:
|
||||
triangle[0] = sorted_keys.index(triangle[0])
|
||||
triangle[1] = sorted_keys.index(triangle[1])
|
||||
triangle[2] = sorted_keys.index(triangle[2])
|
||||
mesh["Center"] = mesh["Mesh"]["Vertices"][0] # Not correct, but just for now
|
||||
del mesh["Mesh"]["vertex_indices"]
|
||||
self.inputs[covetool_category].extend(meshes.values())
|
||||
|
||||
def has_triangulate_modifier(self, obj):
|
||||
for modifier in obj.modifiers:
|
||||
if modifier.type == "TRIANGULATE":
|
||||
return True
|
||||
|
||||
def get_covetool_category(self, obj):
|
||||
if not hasattr(obj, "data") or not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
return
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
ifc_class = element.is_a()
|
||||
if "IfcSlab" in ifc_class:
|
||||
return "floors"
|
||||
elif "IfcRoof" in ifc_class:
|
||||
return "roofs"
|
||||
elif "IfcWall" in ifc_class:
|
||||
if self.is_wall_internal(element):
|
||||
return "interior_walls"
|
||||
return "walls"
|
||||
elif "IfcWindow" in ifc_class:
|
||||
if self.is_window_skylight(element):
|
||||
return "skylights"
|
||||
return "windows"
|
||||
elif "IfcShadingDevice" in ifc_class:
|
||||
return "shading_devices"
|
||||
|
||||
def is_wall_internal(self, element):
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets.get("Pset_WallCommon")
|
||||
if pset:
|
||||
is_external = pset.get("IsExternal", None)
|
||||
if is_external is not None:
|
||||
return is_external
|
||||
predefined_type = element.get_info().get("PredefinedType")
|
||||
if predefined_type and predefined_type in ["MOVABLE", "PARTITIONING", "PLUMBINGWALL"]:
|
||||
return True
|
||||
|
||||
def is_window_skylight(self, element):
|
||||
predefined_type = element.get_info().get("PredefinedType")
|
||||
return predefined_type and predefined_type.string_value == "SKYLIGHT"
|
||||
@@ -0,0 +1,41 @@
|
||||
import bpy.props
|
||||
import bpy.types
|
||||
|
||||
|
||||
class CoveToolProject(bpy.types.PropertyGroup):
|
||||
name: bpy.props.StringProperty(name="Name")
|
||||
run_set: bpy.props.StringProperty(name="Run Set")
|
||||
url: bpy.props.StringProperty(name="URL")
|
||||
|
||||
|
||||
class CoveToolSimpleAnalysis(bpy.types.PropertyGroup):
|
||||
si_units: bpy.props.BoolProperty(name="SI Units")
|
||||
building_height: bpy.props.StringProperty(name="Building Height")
|
||||
roof_area: bpy.props.StringProperty(name="Roof Area")
|
||||
floor_area: bpy.props.StringProperty(name="Floor Area")
|
||||
skylight_area: bpy.props.StringProperty(name="Skylight Area")
|
||||
wall_area_e: bpy.props.StringProperty(name="Wall Area E")
|
||||
wall_area_ne: bpy.props.StringProperty(name="Wall Area NE")
|
||||
wall_area_n: bpy.props.StringProperty(name="Wall Area N")
|
||||
wall_area_nw: bpy.props.StringProperty(name="Wall Area NW")
|
||||
wall_area_w: bpy.props.StringProperty(name="Wall Area W")
|
||||
wall_area_sw: bpy.props.StringProperty(name="Wall Area SW")
|
||||
wall_area_s: bpy.props.StringProperty(name="Wall Area S")
|
||||
wall_area_se: bpy.props.StringProperty(name="Wall Area SE")
|
||||
window_area_e: bpy.props.StringProperty(name="Window Area E")
|
||||
window_area_ne: bpy.props.StringProperty(name="Window Area NE")
|
||||
window_area_n: bpy.props.StringProperty(name="Window Area N")
|
||||
window_area_nw: bpy.props.StringProperty(name="Window Area NW")
|
||||
window_area_w: bpy.props.StringProperty(name="Window Area W")
|
||||
window_area_sw: bpy.props.StringProperty(name="Window Area SW")
|
||||
window_area_s: bpy.props.StringProperty(name="Window Area S")
|
||||
window_area_se: bpy.props.StringProperty(name="Window Area SE")
|
||||
|
||||
|
||||
class CoveToolProperties(bpy.types.PropertyGroup):
|
||||
username: bpy.props.StringProperty(name="Username")
|
||||
password: bpy.props.StringProperty(name="Password")
|
||||
token: bpy.props.StringProperty(name="Token")
|
||||
projects: bpy.props.CollectionProperty(name="Projects", type=CoveToolProject)
|
||||
active_project_index: bpy.props.IntProperty(name="Active Project Index")
|
||||
simple_analysis: bpy.props.PointerProperty(type=CoveToolSimpleAnalysis)
|
||||
@@ -0,0 +1,70 @@
|
||||
import bpy.types
|
||||
|
||||
|
||||
class BIM_PT_covetool(bpy.types.Panel):
|
||||
bl_label = "cove.tool"
|
||||
bl_idname = "BIM_PT_covetool"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
|
||||
scene = context.scene
|
||||
props = scene.CoveToolProperties
|
||||
|
||||
if not props.token:
|
||||
row = layout.row()
|
||||
row.prop(props, "username")
|
||||
row = layout.row()
|
||||
row.prop(props, "password")
|
||||
row = layout.row()
|
||||
row.operator("bim.covetool_login")
|
||||
return
|
||||
|
||||
layout.template_list("BIM_UL_covetool_projects", "", props, "projects", props, "active_project_index")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.covetool_run_analysis")
|
||||
|
||||
prop_names = [
|
||||
"si_units",
|
||||
"building_height",
|
||||
"roof_area",
|
||||
"floor_area",
|
||||
"skylight_area",
|
||||
"wall_area_e",
|
||||
"wall_area_ne",
|
||||
"wall_area_n",
|
||||
"wall_area_nw",
|
||||
"wall_area_w",
|
||||
"wall_area_sw",
|
||||
"wall_area_s",
|
||||
"wall_area_se",
|
||||
"window_area_e",
|
||||
"window_area_ne",
|
||||
"window_area_n",
|
||||
"window_area_nw",
|
||||
"window_area_w",
|
||||
"window_area_sw",
|
||||
"window_area_s",
|
||||
"window_area_se",
|
||||
]
|
||||
for prop_name in prop_names:
|
||||
row = layout.row()
|
||||
row.prop(props.simple_analysis, prop_name)
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.covetool_run_simple_analysis")
|
||||
|
||||
|
||||
class BIM_UL_covetool_projects(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
if item:
|
||||
layout.prop(item, "name", text="", emboss=False)
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
@@ -0,0 +1,23 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddCsvAttribute,
|
||||
operator.RemoveCsvAttribute,
|
||||
operator.ExportIfcCsv,
|
||||
operator.ImportIfcCsv,
|
||||
operator.EyedropIfcCsv,
|
||||
operator.SelectCsvIfcFile,
|
||||
prop.CsvProperties,
|
||||
ui.BIM_PT_ifccsv,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.CsvProperties = bpy.props.PointerProperty(type=prop.CsvProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.CsvProperties
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import bpy
|
||||
import ifccsv
|
||||
import ifcopenshell
|
||||
import os
|
||||
import logging
|
||||
import json
|
||||
import webbrowser
|
||||
import tempfile
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.handler import purge_module_data
|
||||
|
||||
|
||||
class AddCsvAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.add_csv_attribute"
|
||||
bl_label = "Add CSV Attribute"
|
||||
|
||||
def execute(self, context):
|
||||
attribute = context.scene.CsvProperties.csv_attributes.add()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveCsvAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_csv_attribute"
|
||||
bl_label = "Remove CSV Attribute"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.CsvProperties.csv_attributes.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.export_ifccsv"
|
||||
bl_label = "Export IFC to CSV"
|
||||
filename_ext = ".csv"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".csv")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
import ifccsv
|
||||
|
||||
props = context.scene.CsvProperties
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".csv")
|
||||
if props.should_load_from_memory:
|
||||
ifc_file = IfcStore.get_file()
|
||||
else:
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
results = selector.parse(ifc_file, props.ifc_selector)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
ifc_csv.output = self.filepath
|
||||
ifc_csv.attributes = [a.name for a in props.csv_attributes]
|
||||
ifc_csv.selector = selector
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
ifc_csv.delimiter = props.csv_custom_delimiter
|
||||
else:
|
||||
ifc_csv.delimiter = props.csv_delimiter
|
||||
ifc_csv.export(ifc_file, results)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.import_ifccsv"
|
||||
bl_label = "Import CSV to IFC"
|
||||
filename_ext = ".csv"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".csv")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
import ifccsv
|
||||
|
||||
props = context.scene.CsvProperties
|
||||
if props.should_load_from_memory:
|
||||
ifc_file = IfcStore.get_file()
|
||||
else:
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
ifc_csv.output = self.filepath
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
ifc_csv.delimiter = props.csv_custom_delimiter
|
||||
else:
|
||||
ifc_csv.delimiter = props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file)
|
||||
purge_module_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EyedropIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.eyedrop_ifccsv"
|
||||
bl_label = "Query Selected Items"
|
||||
|
||||
def execute(self, context):
|
||||
global_ids = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in context.selected_objects:
|
||||
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id:
|
||||
global_ids.append("#" + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId)
|
||||
context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectCsvIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_csv_ifc_file"
|
||||
bl_label = "Select CSV IFC File"
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.CsvProperties.csv_ifc_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
@@ -0,0 +1,31 @@
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class CsvProperties(PropertyGroup):
|
||||
csv_ifc_file: StringProperty(default="", name="CSV IFC File")
|
||||
ifc_selector: StringProperty(default="", name="IFC Selector")
|
||||
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
|
||||
csv_delimiter: EnumProperty(
|
||||
items=[
|
||||
(";", ";", ""),
|
||||
(",", ",", ""),
|
||||
(".", ".", ""),
|
||||
("CUSTOM", "Custom", ""),
|
||||
],
|
||||
name="IFC CSV Delimiter",
|
||||
default=",",
|
||||
)
|
||||
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
@@ -0,0 +1,49 @@
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_ifccsv(Panel):
|
||||
bl_label = "IFC CSV Import/Export"
|
||||
bl_idname = "BIM_PT_ifccsv"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
scene = context.scene
|
||||
props = scene.CsvProperties
|
||||
|
||||
if IfcStore.get_file():
|
||||
row = layout.row()
|
||||
row.prop(props, "should_load_from_memory")
|
||||
|
||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_ifc_file")
|
||||
row.operator("bim.select_csv_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "ifc_selector")
|
||||
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_csv_attribute")
|
||||
|
||||
for index, attribute in enumerate(props.csv_attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_delimiter")
|
||||
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.export_ifccsv", icon="EXPORT")
|
||||
row.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
@@ -0,0 +1,22 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ProfileImportIFC,
|
||||
operator.CreateAllShapes,
|
||||
operator.CreateShapeFromStepId,
|
||||
operator.SelectHighPolygonMeshes,
|
||||
operator.InspectFromStepId,
|
||||
operator.InspectFromObject,
|
||||
operator.RewindInspector,
|
||||
prop.BIMDebugProperties,
|
||||
ui.BIM_PT_debug,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMDebugProperties = bpy.props.PointerProperty(type=prop.BIMDebugProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMDebugProperties
|
||||
@@ -0,0 +1,166 @@
|
||||
import bpy
|
||||
import logging
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class ProfileImportIFC(bpy.types.Operator):
|
||||
bl_idname = "bim.profile_import_ifc"
|
||||
bl_label = "Profile Import IFC"
|
||||
|
||||
def execute(self, context):
|
||||
import cProfile
|
||||
import pstats
|
||||
|
||||
# For Windows
|
||||
filepath = bpy.context.scene.BIMProperties.ifc_file.replace('\\', '\\\\')
|
||||
|
||||
cProfile.run(
|
||||
f"import bpy; bpy.ops.import_ifc.bim(filepath='{filepath}')", "blender.prof"
|
||||
)
|
||||
p = pstats.Stats("blender.prof")
|
||||
p.sort_stats("cumulative").print_stats(50)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CreateAllShapes(bpy.types.Operator):
|
||||
bl_idname = "bim.create_all_shapes"
|
||||
bl_label = "Create All Shapes"
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
|
||||
total = len(elements)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
failures = []
|
||||
excludes = () # For the developer to debug with
|
||||
for i, element in enumerate(elements):
|
||||
if element.GlobalId in excludes:
|
||||
continue
|
||||
print(f'{i}/{total}:', element)
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
print("Success", len(shape.geometry.verts), len(shape.geometry.edges), len(shape.geometry.faces))
|
||||
except:
|
||||
failures.append(element)
|
||||
print('***** FAILURE *****')
|
||||
print('Failures:')
|
||||
for failure in failures:
|
||||
print(failure)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CreateShapeFromStepId(bpy.types.Operator):
|
||||
bl_idname = "bim.create_shape_from_step_id"
|
||||
bl_label = "Create Shape From STEP ID"
|
||||
|
||||
def execute(self, context):
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
|
||||
self.file = IfcStore.get_file()
|
||||
element = self.file.by_id(int(bpy.context.scene.BIMDebugProperties.step_id))
|
||||
settings = ifcopenshell.geom.settings()
|
||||
# settings.set(settings.INCLUDE_CURVES, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
mesh = ifc_importer.create_mesh(element, shape)
|
||||
obj = bpy.data.objects.new("Debug", mesh)
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectHighPolygonMeshes(bpy.types.Operator):
|
||||
bl_idname = "bim.select_high_polygon_meshes"
|
||||
bl_label = "Select High Polygon Meshes"
|
||||
|
||||
def execute(self, context):
|
||||
results = {}
|
||||
for obj in bpy.data.objects:
|
||||
if not isinstance(obj.data, bpy.types.Mesh) or len(obj.data.polygons) < int(
|
||||
bpy.context.scene.BIMDebugProperties.number_of_polygons
|
||||
):
|
||||
continue
|
||||
try:
|
||||
obj.select_set(True)
|
||||
except:
|
||||
# If it is not in the view layer
|
||||
pass
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RewindInspector(bpy.types.Operator):
|
||||
bl_idname = "bim.rewind_inspector"
|
||||
bl_label = "Rewind Inspector"
|
||||
|
||||
def execute(self, context):
|
||||
props = bpy.context.scene.BIMDebugProperties
|
||||
total_breadcrumbs = len(props.step_id_breadcrumb)
|
||||
if total_breadcrumbs < 2:
|
||||
return {"FINISHED"}
|
||||
previous_step_id = int(props.step_id_breadcrumb[total_breadcrumbs - 2].name)
|
||||
props.step_id_breadcrumb.remove(total_breadcrumbs - 1)
|
||||
props.step_id_breadcrumb.remove(total_breadcrumbs - 2)
|
||||
bpy.ops.bim.inspect_from_step_id(step_id=previous_step_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class InspectFromStepId(bpy.types.Operator):
|
||||
bl_idname = "bim.inspect_from_step_id"
|
||||
bl_label = "Inspect From STEP ID"
|
||||
step_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
bpy.context.scene.BIMDebugProperties.active_step_id = self.step_id
|
||||
crumb = bpy.context.scene.BIMDebugProperties.step_id_breadcrumb.add()
|
||||
crumb.name = str(self.step_id)
|
||||
element = self.file.by_id(self.step_id)
|
||||
while len(bpy.context.scene.BIMDebugProperties.attributes) > 0:
|
||||
bpy.context.scene.BIMDebugProperties.attributes.remove(0)
|
||||
while len(bpy.context.scene.BIMDebugProperties.inverse_attributes) > 0:
|
||||
bpy.context.scene.BIMDebugProperties.inverse_attributes.remove(0)
|
||||
while len(bpy.context.scene.BIMDebugProperties.inverse_references) > 0:
|
||||
bpy.context.scene.BIMDebugProperties.inverse_references.remove(0)
|
||||
for key, value in element.get_info().items():
|
||||
self.add_attribute(bpy.context.scene.BIMDebugProperties.attributes, key, value)
|
||||
for key in dir(element):
|
||||
if (
|
||||
not key[0].isalpha()
|
||||
or key[0] != key[0].upper()
|
||||
or key in element.get_info()
|
||||
or not getattr(element, key)
|
||||
):
|
||||
continue
|
||||
self.add_attribute(bpy.context.scene.BIMDebugProperties.inverse_attributes, key, getattr(element, key))
|
||||
for inverse in self.file.get_inverse(element):
|
||||
new = bpy.context.scene.BIMDebugProperties.inverse_references.add()
|
||||
new.string_value = str(inverse)
|
||||
new.int_value = inverse.id()
|
||||
return {"FINISHED"}
|
||||
|
||||
def add_attribute(self, prop, key, value):
|
||||
if isinstance(value, tuple) and len(value) < 10:
|
||||
for i, item in enumerate(value):
|
||||
self.add_attribute(prop, key + f"[{i}]", item)
|
||||
return
|
||||
elif isinstance(value, tuple) and len(value) >= 10:
|
||||
key = key + "({})".format(len(value))
|
||||
new = prop.add()
|
||||
new.name = key
|
||||
new.string_value = str(value)
|
||||
if isinstance(value, ifcopenshell.entity_instance):
|
||||
new.int_value = int(value.id())
|
||||
|
||||
|
||||
class InspectFromObject(bpy.types.Operator):
|
||||
bl_idname = "bim.inspect_from_object"
|
||||
bl_label = "Inspect From Object"
|
||||
|
||||
def execute(self, context):
|
||||
ifc_definition_id = bpy.context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
if not ifc_definition_id:
|
||||
return {"FINISHED"}
|
||||
bpy.ops.bim.inspect_from_step_id(step_id=ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,22 @@
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class BIMDebugProperties(PropertyGroup):
|
||||
step_id: IntProperty(name="STEP ID")
|
||||
number_of_polygons: IntProperty(name="Number of Polygons")
|
||||
active_step_id: IntProperty(name="STEP ID")
|
||||
step_id_breadcrumb: CollectionProperty(name="STEP ID Breadcrumb", type=StrProperty)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
inverse_attributes: CollectionProperty(name="Inverse Attributes", type=Attribute)
|
||||
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
|
||||
@@ -0,0 +1,85 @@
|
||||
import bpy
|
||||
from bpy.types import Panel
|
||||
|
||||
|
||||
class BIM_PT_debug(Panel):
|
||||
bl_label = "IFC Debug"
|
||||
bl_idname = "BIM_PT_debug"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
props = context.scene.BIMDebugProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.BIMProperties, "ifc_file", text="")
|
||||
row.operator("bim.validate_ifc_file", icon="CHECKMARK", text="")
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.create_all_shapes")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.profile_import_ifc")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "step_id", text="")
|
||||
row = layout.row()
|
||||
row.operator("bim.create_shape_from_step_id")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "number_of_polygons", text="")
|
||||
row = layout.row()
|
||||
row.operator("bim.select_high_polygon_meshes")
|
||||
|
||||
layout.label(text="Inspector:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
if len(props.step_id_breadcrumb) >= 2:
|
||||
row.operator("bim.rewind_inspector", icon="FRAME_PREV", text="")
|
||||
row.prop(props, "active_step_id", text="")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.inspect_from_step_id").step_id = bpy.context.scene.BIMDebugProperties.active_step_id
|
||||
row.operator("bim.inspect_from_object")
|
||||
|
||||
if props.attributes:
|
||||
layout.label(text="Direct attributes:")
|
||||
|
||||
for index, attribute in enumerate(props.attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.prop(attribute, "string_value", text="")
|
||||
if attribute.name == "GlobalId":
|
||||
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.global_id = attribute.string_value
|
||||
if attribute.int_value:
|
||||
row.operator(
|
||||
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
|
||||
).step_id = attribute.int_value
|
||||
|
||||
if props.inverse_attributes:
|
||||
layout.label(text="Inverse attributes:")
|
||||
|
||||
for index, attribute in enumerate(props.inverse_attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.prop(attribute, "string_value", text="")
|
||||
if attribute.int_value:
|
||||
row.operator(
|
||||
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
|
||||
).step_id = attribute.int_value
|
||||
|
||||
if props.inverse_references:
|
||||
layout.label(text="Inverse references:")
|
||||
|
||||
for index, attribute in enumerate(props.inverse_references):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "string_value", text="")
|
||||
if attribute.int_value:
|
||||
row.operator(
|
||||
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
|
||||
).step_id = attribute.int_value
|
||||
@@ -0,0 +1,22 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.SelectDiffJsonFile,
|
||||
operator.VisualiseDiff,
|
||||
operator.SelectDiffOldFile,
|
||||
operator.SelectDiffNewFile,
|
||||
operator.ExecuteIfcDiff,
|
||||
prop.DiffProperties,
|
||||
ui.BIM_PT_diff,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.DiffProperties = bpy.props.PointerProperty(type=prop.DiffProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.DiffProperties
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import bpy
|
||||
import ifccsv
|
||||
import ifcopenshell
|
||||
import json
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
|
||||
class SelectDiffJsonFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_diff_json_file"
|
||||
bl_label = "Select Diff JSON File"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.DiffProperties.diff_json_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class VisualiseDiff(bpy.types.Operator):
|
||||
bl_idname = "bim.visualise_diff"
|
||||
bl_label = "Visualise Diff"
|
||||
|
||||
def execute(self, context):
|
||||
#ifc_file = IfcStore.get_file() # In case we get from Store
|
||||
ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
|
||||
with open(bpy.context.scene.DiffProperties.diff_json_file, "r") as file:
|
||||
diff = json.load(file)
|
||||
for obj in bpy.context.visible_objects:
|
||||
obj.color = (1.0, 1.0, 1.0, 0.2)
|
||||
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
|
||||
if not global_id:
|
||||
continue
|
||||
if global_id.string_value in diff["deleted"]:
|
||||
obj.color = (1.0, 0.0, 0.0, 0.2)
|
||||
elif global_id.string_value in diff["added"]:
|
||||
obj.color = (0.0, 1.0, 0.0, 0.2)
|
||||
elif global_id.string_value in diff["changed"]:
|
||||
obj.color = (0.0, 0.0, 1.0, 0.2)
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].shading.color_type = "OBJECT"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectDiffOldFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_diff_old_file"
|
||||
bl_label = "Select Diff Old File"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.DiffProperties.diff_old_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectDiffNewFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_diff_new_file"
|
||||
bl_label = "Select Diff New File"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.DiffProperties.diff_new_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExecuteIfcDiff(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifc_diff"
|
||||
bl_label = "Execute IFC Diff"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
import ifcdiff
|
||||
|
||||
ifc_diff = ifcdiff.IfcDiff(
|
||||
bpy.context.scene.DiffProperties.diff_old_file,
|
||||
bpy.context.scene.DiffProperties.diff_new_file,
|
||||
self.filepath,
|
||||
bpy.context.scene.DiffProperties.diff_relationships.split(),
|
||||
)
|
||||
ifc_diff.diff()
|
||||
ifc_diff.export()
|
||||
bpy.context.scene.DiffProperties.diff_json_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class DiffProperties(PropertyGroup):
|
||||
diff_json_file: StringProperty(default="", name="Diff JSON File")
|
||||
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
|
||||
diff_new_file: StringProperty(default="", name="Diff New IFC File")
|
||||
diff_relationships: StringProperty(default="", name="Diff Relationships")
|
||||
@@ -0,0 +1,40 @@
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_diff(Panel):
|
||||
bl_label = "IFC Diff"
|
||||
bl_idname = "BIM_PT_diff"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
|
||||
scene = context.scene
|
||||
bim_properties = scene.DiffProperties
|
||||
|
||||
layout.label(text="IFC Diff Setup:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(bim_properties, "diff_old_file")
|
||||
row.operator("bim.select_diff_old_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(bim_properties, "diff_new_file")
|
||||
row.operator("bim.select_diff_new_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(bim_properties, "diff_relationships")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.execute_ifc_diff")
|
||||
|
||||
# TODO: show if there ifc diff operation is sucessful
|
||||
row = layout.row(align=True)
|
||||
row.prop(bim_properties, "diff_json_file")
|
||||
row.operator("bim.select_diff_json_file", icon="FILE_FOLDER", text="")
|
||||
row.operator("bim.visualise_diff", icon="HIDE_OFF", text="")
|
||||
@@ -0,0 +1,36 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.LoadInformation,
|
||||
operator.LoadDocumentReferences,
|
||||
operator.DisableDocumentEditingUI,
|
||||
operator.EnableEditingDocument,
|
||||
operator.DisableEditingDocument,
|
||||
operator.AddInformation,
|
||||
operator.AddDocumentReference,
|
||||
operator.EditInformation,
|
||||
operator.EditDocumentReference,
|
||||
operator.RemoveDocument,
|
||||
operator.EnableAssigningDocument,
|
||||
operator.DisableAssigningDocument,
|
||||
operator.AssignDocument,
|
||||
operator.UnassignDocument,
|
||||
prop.Document,
|
||||
prop.BIMDocumentProperties,
|
||||
prop.BIMObjectDocumentProperties,
|
||||
ui.BIM_PT_documents,
|
||||
ui.BIM_PT_object_documents,
|
||||
ui.BIM_UL_documents,
|
||||
ui.BIM_UL_object_documents,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties)
|
||||
bpy.types.Object.BIMObjectDocumentProperties = bpy.props.PointerProperty(type=prop.BIMObjectDocumentProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMDocumentProperties
|
||||
del bpy.types.Object.BIMObjectDocumentProperties
|
||||
@@ -0,0 +1,258 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.api.document.add_information as add_information
|
||||
import ifcopenshell.api.document.add_reference as add_reference
|
||||
import ifcopenshell.api.document.edit_information as edit_information
|
||||
import ifcopenshell.api.document.edit_reference as edit_reference
|
||||
import ifcopenshell.api.document.remove_document as remove_document
|
||||
import ifcopenshell.api.document.assign_document as assign_document
|
||||
import ifcopenshell.api.document.unassign_document as unassign_document
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.document.data import Data
|
||||
|
||||
|
||||
class LoadInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.load_information"
|
||||
bl_label = "Load Information"
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMDocumentProperties
|
||||
while len(props.documents) > 0:
|
||||
props.documents.remove(0)
|
||||
for information_id, information in Data.information.items():
|
||||
new = props.documents.add()
|
||||
new.name = information["Name"] or "Unnamed"
|
||||
if self.file.schema == "IFC2X3":
|
||||
new.identification = information["DocumentId"] or "*"
|
||||
else:
|
||||
new.identification = information["Identification"] or "*"
|
||||
new.ifc_definition_id = information_id
|
||||
props.is_editing = "information"
|
||||
bpy.ops.bim.disable_editing_document()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadDocumentReferences(bpy.types.Operator):
|
||||
bl_idname = "bim.load_document_references"
|
||||
bl_label = "Load Document References"
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMDocumentProperties
|
||||
while len(props.documents) > 0:
|
||||
props.documents.remove(0)
|
||||
for reference_id, reference in Data.references.items():
|
||||
new = props.documents.add()
|
||||
new.name = reference["Name"] or "Unnamed"
|
||||
if self.file.schema == "IFC2X3":
|
||||
new.identification = reference["ItemReference"] or "*"
|
||||
else:
|
||||
new.identification = reference["Identification"] or "*"
|
||||
new.ifc_definition_id = reference_id
|
||||
props.is_editing = "reference"
|
||||
bpy.ops.bim.disable_editing_document()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableDocumentEditingUI(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_document_editing_ui"
|
||||
bl_label = "Disable Document Editing UI"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMDocumentProperties.is_editing = ""
|
||||
bpy.ops.bim.disable_editing_document()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_document"
|
||||
bl_label = "Enable Editing Document"
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
while len(props.document_attributes) > 0:
|
||||
props.document_attributes.remove(0)
|
||||
|
||||
if props.is_editing == "information":
|
||||
data = Data.information[self.document]
|
||||
ifc_class = "IfcDocumentInformation"
|
||||
elif props.is_editing == "reference":
|
||||
data = Data.references[self.document]
|
||||
ifc_class = "IfcDocumentReference"
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.document_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if data_type == "string":
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
props.active_document_id = self.document
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_document"
|
||||
bl_label = "Disable Editing Document"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMDocumentProperties.active_document_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.add_information"
|
||||
bl_label = "Add Information"
|
||||
|
||||
def execute(self, context):
|
||||
result = add_information.Usecase(IfcStore.get_file()).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_information()
|
||||
bpy.ops.bim.enable_editing_document(document=result.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.add_document_reference"
|
||||
bl_label = "Add Document Reference"
|
||||
|
||||
def execute(self, context):
|
||||
result = add_reference.Usecase(IfcStore.get_file()).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_document_references()
|
||||
bpy.ops.bim.enable_editing_document(document=result.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_information"
|
||||
bl_label = "Edit Information"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
attributes = {}
|
||||
for attribute in props.document_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
elif attribute.enum_items:
|
||||
attributes[attribute.name] = attribute.enum_value
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_information.Usecase(
|
||||
self.file, {"information": self.file.by_id(props.active_document_id), "attributes": attributes}
|
||||
).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_information()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_document_reference"
|
||||
bl_label = "Edit Document Reference"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
attributes = {}
|
||||
for attribute in props.document_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
edit_reference.Usecase(
|
||||
self.file, {"reference": self.file.by_id(props.active_document_id), "attributes": attributes}
|
||||
).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_document_references()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_document"
|
||||
bl_label = "Remove Document"
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
self.file = IfcStore.get_file()
|
||||
remove_document.Usecase(self.file, {"document": self.file.by_id(self.document)}).execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
if props.is_editing == "information":
|
||||
bpy.ops.bim.load_information()
|
||||
elif props.is_editing == "reference":
|
||||
bpy.ops.bim.load_document_references()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableAssigningDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_assigning_document"
|
||||
bl_label = "Enable Assigning Document"
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectDocumentProperties
|
||||
if props.available_document_types == "IfcDocumentInformation":
|
||||
bpy.ops.bim.load_information()
|
||||
elif props.available_document_types == "IfcDocumentReference":
|
||||
bpy.ops.bim.load_document_references()
|
||||
props.is_adding = props.available_document_types
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableAssigningDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_assigning_document"
|
||||
bl_label = "Disable Assigning Document"
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectDocumentProperties
|
||||
props.is_adding = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_document"
|
||||
bl_label = "Assign Document"
|
||||
obj: bpy.props.StringProperty()
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
assign_document.Usecase(self.file, {
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"document": self.file.by_id(self.document)
|
||||
}).execute()
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_document"
|
||||
bl_label = "Unassign Document"
|
||||
obj: bpy.props.StringProperty()
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
unassign_document.Usecase(self.file, {
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"document": self.file.by_id(self.document)
|
||||
}).execute()
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,34 @@
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class Document(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
identification: StringProperty(name="Identification")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class BIMDocumentProperties(PropertyGroup):
|
||||
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
|
||||
active_document_id: IntProperty(name="Active Document Id")
|
||||
documents: CollectionProperty(name="Documents", type=Document)
|
||||
active_document_index: IntProperty(name="Active Document Index")
|
||||
is_editing: StringProperty(name="Is Editing")
|
||||
|
||||
|
||||
class BIMObjectDocumentProperties(PropertyGroup):
|
||||
is_adding: StringProperty(name="Is Adding")
|
||||
available_document_types: EnumProperty(
|
||||
items=[(d, d, "") for d in ["IfcDocumentInformation", "IfcDocumentReference"]], name="Available Document Types"
|
||||
)
|
||||
@@ -0,0 +1,158 @@
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.document.data import Data
|
||||
|
||||
|
||||
class BIM_PT_documents(Panel):
|
||||
bl_label = "IFC Documents"
|
||||
bl_idname = "BIM_PT_documents"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
self.props = context.scene.BIMDocumentProperties
|
||||
|
||||
if not self.props.is_editing or self.props.is_editing == "information":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Documents Found".format(len(Data.information)), icon="FILE")
|
||||
if self.props.is_editing == "information":
|
||||
row.operator("bim.add_information", text="", icon="ADD")
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="X")
|
||||
else:
|
||||
row.operator("bim.load_information", text="", icon="IMPORT")
|
||||
|
||||
if not self.props.is_editing or self.props.is_editing == "reference":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} References Found".format(len(Data.references)), icon="FILE_HIDDEN")
|
||||
if self.props.is_editing == "reference":
|
||||
row.operator("bim.add_document_reference", text="", icon="ADD")
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="X")
|
||||
else:
|
||||
row.operator("bim.load_document_references", text="", icon="IMPORT")
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index"
|
||||
)
|
||||
|
||||
if self.props.active_document_id:
|
||||
self.draw_editable_ui(context)
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
for attribute in self.props.document_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
|
||||
class BIM_PT_object_documents(Panel):
|
||||
bl_label = "IFC Documents"
|
||||
bl_idname = "BIM_PT_object_documents"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.active_object
|
||||
self.oprops = obj.BIMObjectProperties
|
||||
self.sprops = context.scene.BIMDocumentProperties
|
||||
self.props = obj.BIMObjectDocumentProperties
|
||||
self.file = IfcStore.get_file()
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if self.oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
|
||||
|
||||
self.draw_add_ui()
|
||||
|
||||
document_ids = Data.products[self.oprops.ifc_definition_id]
|
||||
|
||||
if not document_ids:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Documents", icon="FILE")
|
||||
|
||||
for document_id in document_ids:
|
||||
try:
|
||||
document = Data.information[document_id]
|
||||
document_type = "IfcDocumentInformation"
|
||||
icon = "FILE"
|
||||
except:
|
||||
document = Data.references[document_id]
|
||||
document_type = "IfcDocumentReference"
|
||||
icon = "FILE_HIDDEN"
|
||||
row = self.layout.row(align=True)
|
||||
if self.file.schema == "IFC2X3":
|
||||
if document_type == "IfcDocumentInformation":
|
||||
row.label(text=document.get("DocumentId") or "*", icon=icon)
|
||||
elif document_type == "IfcDocumentReference":
|
||||
row.label(text=document.get("ItemReference") or "*", icon=icon)
|
||||
else:
|
||||
row.label(text=document.get("Identification") or "*", icon=icon)
|
||||
row.label(text=document.get("Name") or "Unnamed")
|
||||
row.operator("bim.unassign_document", text="", icon="X").document = document_id
|
||||
|
||||
def draw_add_ui(self):
|
||||
if self.props.is_adding:
|
||||
row = self.layout.row(align=True)
|
||||
icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN"
|
||||
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
|
||||
row.operator("bim.disable_assigning_document", text="", icon="X")
|
||||
self.layout.template_list(
|
||||
"BIM_UL_object_documents",
|
||||
"",
|
||||
self.sprops,
|
||||
"documents",
|
||||
self.sprops,
|
||||
"active_document_index",
|
||||
)
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "available_document_types", text="")
|
||||
row.operator("bim.enable_assigning_document", text="", icon="ADD")
|
||||
|
||||
|
||||
class BIM_UL_documents(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.identification)
|
||||
row.label(text=item.name)
|
||||
if context.scene.BIMDocumentProperties.active_document_id == item.ifc_definition_id:
|
||||
if context.scene.BIMDocumentProperties.is_editing == "information":
|
||||
row.operator("bim.edit_information", text="", icon="CHECKMARK")
|
||||
elif context.scene.BIMDocumentProperties.is_editing == "reference":
|
||||
row.operator("bim.edit_document_reference", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_document", text="", icon="X")
|
||||
elif context.scene.BIMDocumentProperties.active_document_id:
|
||||
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL")
|
||||
op.document = item.ifc_definition_id
|
||||
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
|
||||
|
||||
|
||||
class BIM_UL_object_documents(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.identification)
|
||||
row.label(text=item.name)
|
||||
row.operator("bim.assign_document", text="", icon="ADD").document = item.ifc_definition_id
|
||||
@@ -0,0 +1,16 @@
|
||||
import bpy
|
||||
from . import ui, operator
|
||||
|
||||
classes = (
|
||||
operator.AddDrawing,
|
||||
operator.CreateDrawing,
|
||||
ui.BIM_PT_camera,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
@@ -0,0 +1,89 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import subprocess
|
||||
import webbrowser
|
||||
from blenderbim.bim.operator import open_with_user_command
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.group.data import Data as GroupData
|
||||
from ifcopenshell.api.pset.data import Data as PsetData
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
class AddDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.add_drawing"
|
||||
bl_label = "Add Drawing"
|
||||
|
||||
def execute(self, context):
|
||||
new = context.scene.DocProperties.drawings.add()
|
||||
new.name = "DRAWING {}".format(len(context.scene.DocProperties.drawings))
|
||||
if not bpy.data.collections.get("Views"):
|
||||
context.scene.collection.children.link(bpy.data.collections.new("Views"))
|
||||
views_collection = bpy.data.collections.get("Views")
|
||||
view_collection = bpy.data.collections.new("IfcGroup/" + new.name)
|
||||
views_collection.children.link(view_collection)
|
||||
camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name))
|
||||
camera.location = (0, 0, 1.7) # The view shall be 1.7m above the origin
|
||||
camera.data.type = "ORTHO"
|
||||
camera.data.ortho_scale = 50 # The default of 6m is too small
|
||||
camera.data.clip_end = 10 # A slightly more reasonable default
|
||||
if context.scene.unit_settings.system == "IMPERIAL":
|
||||
camera.data.BIMCameraProperties.diagram_scale = '1/8"=1\'-0"|1/96'
|
||||
else:
|
||||
camera.data.BIMCameraProperties.diagram_scale = "1:100|1/100"
|
||||
context.scene.camera = camera
|
||||
view_collection.objects.link(camera)
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
new.camera = camera
|
||||
bpy.ops.bim.assign_class(obj=camera.name, ifc_class="IfcAnnotation", predefined_type="DRAWING")
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
|
||||
bpy.ops.bim.add_group()
|
||||
bpy.ops.bim.assign_group(product=camera.name, group=sorted(GroupData.groups.keys())[-1])
|
||||
bpy.ops.bim.add_pset(obj=camera.name, obj_type="Object", pset_name="EPset_Drawing")
|
||||
pset_id = sorted(PsetData.products[camera.BIMObjectProperties.ifc_definition_id]["psets"])[-1]
|
||||
bpy.ops.bim.edit_pset(
|
||||
obj=camera.name,
|
||||
obj_type="Object",
|
||||
pset_id=pset_id,
|
||||
properties=json.dumps({"TargetView": "PLAN_VIEW", "Scale": "1/100"}),
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CreateDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.create_drawing"
|
||||
bl_label = "Create Drawing"
|
||||
|
||||
def execute(self, context):
|
||||
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
|
||||
subprocess.run(
|
||||
[
|
||||
ifcconvert_path,
|
||||
context.scene.BIMProperties.ifc_file,
|
||||
"-yv",
|
||||
context.scene.BIMProperties.ifc_file[0:-4] + ".svg",
|
||||
"--plan",
|
||||
"--model",
|
||||
"--section-height-from-storeys",
|
||||
"--section-height=1.2",
|
||||
"--door-arcs",
|
||||
"--print-space-names",
|
||||
"--print-space-areas",
|
||||
"--bounds=1024x1024",
|
||||
"--elevation-ref=DRAWING",
|
||||
"--svg-xmlns",
|
||||
"--svg-project",
|
||||
"--svg-poly",
|
||||
"--exclude",
|
||||
"entities",
|
||||
"IfcSpace",
|
||||
"IfcOpeningElement",
|
||||
]
|
||||
)
|
||||
open_with_user_command(
|
||||
bpy.context.preferences.addons["blenderbim"].preferences.svg_command,
|
||||
context.scene.BIMProperties.ifc_file[0:-4] + ".svg",
|
||||
)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,108 @@
|
||||
import os
|
||||
import bpy
|
||||
from bpy.types import Panel
|
||||
from bpy.props import StringProperty, BoolProperty
|
||||
|
||||
|
||||
class BIM_PT_camera(Panel):
|
||||
bl_label = "Drawing Generation"
|
||||
bl_idname = "BIM_PT_camera"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "data"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
engine = context.engine
|
||||
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
if "/" not in context.active_object.name:
|
||||
layout.label(text="This is not a BIM camera.")
|
||||
return
|
||||
|
||||
layout.use_property_split = True
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
props = context.active_object.data.BIMCameraProperties
|
||||
|
||||
layout.label(text="Generation Options:")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_recut")
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_recut_selected")
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_extract")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "is_nts")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.generate_references")
|
||||
row = layout.row()
|
||||
row.operator("bim.resize_text")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "target_view")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "cut_objects")
|
||||
if props.cut_objects == "CUSTOM":
|
||||
row = layout.row()
|
||||
row.prop(props, "cut_objects_custom")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_x")
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_y")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "diagram_scale")
|
||||
if props.diagram_scale == "CUSTOM":
|
||||
row = layout.row()
|
||||
row.prop(props, "custom_diagram_scale")
|
||||
|
||||
layout.label(text="Drawing Styles:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing_style")
|
||||
|
||||
if dprops.drawing_styles:
|
||||
layout.template_list("BIM_UL_generic", "", dprops, "drawing_styles", props, "active_drawing_style_index")
|
||||
|
||||
if props.active_drawing_style_index < len(dprops.drawing_styles):
|
||||
drawing_style = dprops.drawing_styles[props.active_drawing_style_index]
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "name")
|
||||
row.operator("bim.remove_drawing_style", icon="X", text="").index = props.active_drawing_style_index
|
||||
|
||||
row = layout.row()
|
||||
row.prop(drawing_style, "render_type")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "vector_style")
|
||||
row.operator("bim.edit_vector_style", text="", icon="GREASEPENCIL")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "include_query")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "exclude_query")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_drawing_style_attribute")
|
||||
|
||||
for index, attribute in enumerate(drawing_style.attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_drawing_style_attribute", icon="X", text="").index = index
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.save_drawing_style")
|
||||
row.operator("bim.activate_drawing_style")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.cut_section", text="Create Drawing")
|
||||
row.operator("bim.create_drawing", text="Create Drawing 2.0")
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op.view = context.active_object.name.split("/")[1]
|
||||
@@ -0,0 +1,28 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.EditObjectPlacement,
|
||||
operator.AddRepresentation,
|
||||
operator.MapRepresentations,
|
||||
operator.MapRepresentation,
|
||||
operator.SwitchRepresentation,
|
||||
operator.RemoveRepresentation,
|
||||
operator.UpdateMeshRepresentation,
|
||||
operator.UpdateParametricRepresentation,
|
||||
operator.GetRepresentationIfcParameters,
|
||||
prop.BIMGeometryProperties,
|
||||
ui.BIM_PT_representations,
|
||||
ui.BIM_PT_mesh,
|
||||
ui.BIM_PT_workarounds,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
|
||||
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
|
||||
del bpy.types.Scene.BIMGeometryProperties
|
||||
@@ -0,0 +1,377 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
from math import pi
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
|
||||
class Helper:
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
# We can detect a rectangular extrusion by picking any face, then find an
|
||||
# edge that shares a single vertex only with that face to find the extrusion
|
||||
# edge. A face with the normal facing down is prioritised. A limited
|
||||
# dissolve ensure that faces are quads and not tris.
|
||||
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
face = None
|
||||
for face in bm.faces:
|
||||
if face.normal.z < -0.1:
|
||||
break
|
||||
profile = [l.vert.index for l in face.loops]
|
||||
extrusion = self.detect_extrusion_edge(bm, face)
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
|
||||
return {"profile": profile, "extrusion": extrusion}
|
||||
|
||||
# After a limited dissolve, we detect the circle profile as it should be the
|
||||
# only ngon (this assumes the circle has at least a facetation of > 4 edges.
|
||||
# The extrusion direction is any edge that only shares a single vertex with
|
||||
# the profile. We prioritise the profile that has a downwards normal.
|
||||
def auto_detect_circle_profile_extruded_area_solid(self, mesh):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
for face in bm.faces:
|
||||
if len(face.verts) > 4:
|
||||
potential_faces.append(face)
|
||||
for face in potential_faces:
|
||||
if face.normal.z < -0.1:
|
||||
break
|
||||
|
||||
profile = [l.vert.index for l in face.loops]
|
||||
extrusion = self.detect_extrusion_edge(bm, face)
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
|
||||
return {"profile": profile, "extrusion": extrusion}
|
||||
|
||||
# After a limited dissolve, the arbitrary profile is any ngon or tri.
|
||||
# Failing that, it is equivalent to a rectangular profile. The extrusion
|
||||
# direction is any edge that only shares a single vertex with the profile.
|
||||
# We prioritise the profile that has a downwards normal.
|
||||
def auto_detect_arbitrary_closed_profile_extruded_area_solid(self, mesh):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
for face in bm.faces:
|
||||
total_verts = len(face.verts)
|
||||
if total_verts > 4 or total_verts == 3:
|
||||
potential_faces.append(face)
|
||||
|
||||
if not potential_faces:
|
||||
potential_faces = bm.faces
|
||||
|
||||
for face in potential_faces:
|
||||
if face.normal.z < -0.1:
|
||||
break
|
||||
|
||||
profile = [l.vert.index for l in face.loops]
|
||||
extrusion = self.detect_extrusion_edge(bm, face)
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
|
||||
return {"profile": profile, "extrusion": extrusion}
|
||||
|
||||
# An arbitrary closed profile with voids is similar to one without voids.
|
||||
# We start the same way with any ngon (no tri), but instead of being the entire
|
||||
# profile, it is only one of the possible faces that make up the end of our
|
||||
# extrusion. Then we find all faces with the same normal. This creates a set
|
||||
# of faces that define the end of our extrusion. From this set of faces, we
|
||||
# need to then extract the outer loop and inner loops. First, all loops are
|
||||
# detected from the faces. Any edge that is not shared between other faces
|
||||
# in the set must be part of either an inner or outer loop. This gives us a
|
||||
# set of edges. Then, connected edges (i.e. sharing a vertex) are joined to
|
||||
# form distinct loops. Finally, the outer loop is distinguished by being
|
||||
# the loop with the greatest area.
|
||||
def auto_detect_arbitrary_profile_with_voids_extruded_area_solid(self, mesh):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
for face in bm.faces:
|
||||
total_verts = len(face.verts)
|
||||
if total_verts > 4:
|
||||
potential_faces.append(face)
|
||||
|
||||
for face in potential_faces:
|
||||
if face.normal.z < -0.1:
|
||||
break
|
||||
|
||||
end_faces = []
|
||||
end_face_normal = face.normal
|
||||
for face in bm.faces:
|
||||
if (face.normal - end_face_normal).length < 0.001:
|
||||
end_faces.append(face)
|
||||
|
||||
loop_edges = set()
|
||||
for face in end_faces:
|
||||
potential_edges = set(face.edges)
|
||||
for face2 in end_faces:
|
||||
if face == face2:
|
||||
continue
|
||||
potential_edges -= set(face2.edges)
|
||||
loop_edges |= potential_edges
|
||||
|
||||
# Create loops from edges
|
||||
loops = []
|
||||
while loop_edges:
|
||||
edge = loop_edges.pop()
|
||||
loop = [edge]
|
||||
has_found_connected_edge = True
|
||||
while has_found_connected_edge:
|
||||
has_found_connected_edge = False
|
||||
for edge in loop_edges.copy():
|
||||
edge_verts = set(edge.verts)
|
||||
if edge_verts & set(loop[0].verts):
|
||||
loop.insert(0, edge)
|
||||
loop_edges.remove(edge)
|
||||
has_found_connected_edge = True
|
||||
elif edge_verts & set(loop[-1].verts):
|
||||
loop.append(edge)
|
||||
loop_edges.remove(edge)
|
||||
has_found_connected_edge = True
|
||||
loops.append(loop)
|
||||
|
||||
# Determine outer loop
|
||||
max_area = 0
|
||||
outer_loop = None
|
||||
inner_loops = []
|
||||
|
||||
for loop in loops:
|
||||
loop_vertices = []
|
||||
total_edges = len(loop)
|
||||
for i, edge in enumerate(loop):
|
||||
if i + 1 == total_edges and edge.verts[0] in loop[i - 1].verts:
|
||||
loop_vertices.append(edge.verts[0])
|
||||
elif i + 1 == total_edges and edge.verts[1] in loop[i - 1].verts:
|
||||
loop_vertices.append(edge.verts[1])
|
||||
elif edge.verts[0] in loop[i + 1].verts:
|
||||
loop_vertices.append(edge.verts[1])
|
||||
elif edge.verts[1] in loop[i + 1].verts:
|
||||
loop_vertices.append(edge.verts[0])
|
||||
|
||||
loop_bm = bmesh.new()
|
||||
for vert in loop_vertices:
|
||||
loop_bm.verts.new(vert.co)
|
||||
face = loop_bm.faces.new(loop_bm.verts)
|
||||
|
||||
loop_vertex_indices = [v.index for v in loop_vertices]
|
||||
face_area = face.calc_area()
|
||||
if face_area > max_area:
|
||||
max_area = face_area
|
||||
outer_loop = loop_vertex_indices
|
||||
inner_loops.append(loop_vertex_indices)
|
||||
loop_bm.free()
|
||||
|
||||
inner_loops.remove(outer_loop)
|
||||
|
||||
extrusion = self.detect_extrusion_edge(bm, end_faces[0])
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
|
||||
return {"profile": outer_loop, "inner_curves": inner_loops, "extrusion": extrusion}
|
||||
|
||||
# An extrusion edge is an edge that shares a single vertex with a profile
|
||||
# face and is not parallel to the face.
|
||||
def detect_extrusion_edge(self, bm, profile_face):
|
||||
bm.edges.ensure_lookup_table()
|
||||
extrusion = None
|
||||
face_verts_set = set(profile_face.verts)
|
||||
for edge in bm.edges:
|
||||
edge_vector = edge.verts[1].co - edge.verts[0].co
|
||||
unshared_verts = set(edge.verts) - face_verts_set
|
||||
if len(unshared_verts) == 1 and not (edge_vector.angle(profile_face.normal) - pi / 2 < 0.001):
|
||||
if unshared_verts.pop() == edge.verts[1]:
|
||||
return [edge.verts[0].index, edge.verts[1].index]
|
||||
return [edge.verts[1].index, edge.verts[0].index]
|
||||
|
||||
def create_extruded_area_solid(self, mesh, extrusion_indices, profile_def):
|
||||
position = self.create_ifc_axis_2_placement_3d(
|
||||
profile_def["curve_ucs"]["center"], profile_def["curve_ucs"]["z_axis"], profile_def["curve_ucs"]["x_axis"]
|
||||
)
|
||||
direction = self.get_extrusion_direction(mesh, extrusion_indices, profile_def["curve_ucs"])
|
||||
unit_direction = direction.normalized()
|
||||
return self.file.createIfcExtrudedAreaSolid(
|
||||
profile_def["curve"],
|
||||
position,
|
||||
self.file.createIfcDirection((unit_direction.x, unit_direction.y, unit_direction.z)),
|
||||
self.convert_si_to_unit(direction.length),
|
||||
)
|
||||
|
||||
def create_arbitrary_closed_profile_def(self, mesh, profile_indices):
|
||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||
outer_curve = self.create_polyline_from_loop(mesh, profile_indices, curve_ucs)
|
||||
curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve)
|
||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||
|
||||
def create_arbitrary_profile_def_with_voids(self, mesh, profile_indices, inner_curve_indices):
|
||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||
outer_curve = self.create_polyline_from_loop(mesh, profile_indices, curve_ucs)
|
||||
inner_curves = [
|
||||
self.create_polyline_from_loop(mesh, curve_indices, curve_ucs) for curve_indices in inner_curve_indices
|
||||
]
|
||||
curve = self.file.createIfcArbitraryProfileDefWithVoids("AREA", None, outer_curve, inner_curves)
|
||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||
|
||||
def create_rectangle_profile_def(self, mesh, profile_indices):
|
||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||
xdim = self.convert_si_to_unit(
|
||||
(mesh.vertices[profile_indices[0]].co - mesh.vertices[profile_indices[1]].co).length
|
||||
)
|
||||
ydim = self.convert_si_to_unit(
|
||||
(mesh.vertices[profile_indices[1]].co - mesh.vertices[profile_indices[2]].co).length
|
||||
)
|
||||
position = None
|
||||
if self.file.schema == "IFC2X3":
|
||||
position = self.file.createIfcAxis2Placement2D(self.file.createIfcCartesianPoint([0.0, 0.0, 0.0]))
|
||||
curve = self.file.createIfcRectangleProfileDef("AREA", None, position, xdim, ydim)
|
||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||
|
||||
def create_circle_profile_def(self, mesh, profile_indices):
|
||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||
radius = self.convert_si_to_unit(
|
||||
abs(
|
||||
(
|
||||
mesh.vertices[profile_indices[0]].co
|
||||
- mesh.vertices[profile_indices[int(len(profile_indices) / 2)]].co
|
||||
).length
|
||||
)
|
||||
/ 2
|
||||
)
|
||||
center = Vector((0, 0))
|
||||
position = self.create_ifc_axis_2_placement_2d(center, Vector((1, 0)))
|
||||
curve = self.file.createIfcCircleProfileDef("AREA", None, position, radius)
|
||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||
|
||||
# Not used anywhere, but probably useful in the future
|
||||
def get_loop_from_v_indices(self, obj, indices):
|
||||
edges = self.get_edges_in_v_indices(obj, indices)
|
||||
loop = self.get_loop_from_edges(edges)
|
||||
loop.pop(-1)
|
||||
return loop
|
||||
|
||||
def get_loop_from_edges(self, edges):
|
||||
while edges:
|
||||
currentEdge = edges.pop()
|
||||
startVert = currentEdge.vertices[0]
|
||||
endVert = currentEdge.vertices[1]
|
||||
polyLine = [startVert, endVert]
|
||||
ok = 1
|
||||
while ok:
|
||||
ok = 0
|
||||
i = len(edges)
|
||||
while i:
|
||||
i -= 1
|
||||
ed = edges[i]
|
||||
if ed.vertices[0] == endVert:
|
||||
polyLine.append(ed.vertices[1])
|
||||
endVert = polyLine[-1]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
elif ed.vertices[1] == endVert:
|
||||
polyLine.append(ed.vertices[0])
|
||||
endVert = polyLine[-1]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
elif ed.vertices[0] == startVert:
|
||||
polyLine.insert(0, ed.vertices[1])
|
||||
startVert = polyLine[0]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
elif ed.vertices[1] == startVert:
|
||||
polyLine.insert(0, ed.vertices[0])
|
||||
startVert = polyLine[0]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
return polyLine
|
||||
|
||||
def get_edges_in_v_indices(self, obj, indices):
|
||||
return [e for e in obj.data.edges if (e.vertices[0] in indices and e.vertices[1] in indices)]
|
||||
|
||||
def get_curve_profile_coordinate_system(self, mesh, loop):
|
||||
profile_face = bpy.data.meshes.new("profile_face")
|
||||
profile_verts = [(mesh.vertices[p].co.x, mesh.vertices[p].co.y, mesh.vertices[p].co.z) for p in loop]
|
||||
profile_faces = [tuple(range(0, len(profile_verts)))]
|
||||
profile_face.from_pydata(profile_verts, [], profile_faces)
|
||||
center = profile_face.polygons[0].center
|
||||
if (mesh.vertices[loop[1]].co - mesh.vertices[loop[0]].co).length < 0.01:
|
||||
x_axis = (mesh.vertices[loop[0]].co - center).normalized()
|
||||
else:
|
||||
x_axis = (mesh.vertices[loop[1]].co - mesh.vertices[loop[0]].co).normalized()
|
||||
z_axis = profile_face.polygons[0].normal.normalized()
|
||||
y_axis = z_axis.cross(x_axis).normalized()
|
||||
matrix = Matrix((x_axis, y_axis, z_axis))
|
||||
matrix.normalize()
|
||||
return {
|
||||
"center": center,
|
||||
"x_axis": x_axis,
|
||||
"y_axis": y_axis,
|
||||
"z_axis": z_axis,
|
||||
"matrix": matrix.to_4x4() @ Matrix.Translation(-center),
|
||||
}
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
return co / self.unit_scale
|
||||
|
||||
def create_polyline_from_loop(self, mesh, loop, curve_ucs):
|
||||
points = []
|
||||
for point in loop:
|
||||
transformed_point = curve_ucs["matrix"] @ mesh.vertices[point].co
|
||||
points.append(self.create_cartesian_point(transformed_point.x, transformed_point.y))
|
||||
points.append(points[0])
|
||||
return self.file.createIfcPolyline(points)
|
||||
|
||||
def create_cartesian_point(self, x, y, z=None):
|
||||
x = self.convert_si_to_unit(x)
|
||||
y = self.convert_si_to_unit(y)
|
||||
if z is None:
|
||||
return self.file.createIfcCartesianPoint((x, y))
|
||||
z = self.convert_si_to_unit(z)
|
||||
return self.file.createIfcCartesianPoint((x, y, z))
|
||||
|
||||
def get_extrusion_direction(self, mesh, extrusion_indices, curve_ucs):
|
||||
return curve_ucs["matrix"] @ (
|
||||
curve_ucs["center"] + (mesh.vertices[extrusion_indices[1]].co - mesh.vertices[extrusion_indices[0]].co)
|
||||
)
|
||||
|
||||
def get_start_and_end_of_extrusion(self, profile_points, extrusion_edge):
|
||||
if extrusion_edge.vertices[0] in profile_points:
|
||||
return (extrusion_edge.vertices[0], extrusion_edge.vertices[1])
|
||||
return (extrusion_edge.vertices[1], extrusion_edge.vertices[0])
|
||||
|
||||
def create_ifc_axis_2_placement_2d(self, point, forward):
|
||||
return self.file.createIfcAxis2Placement2D(
|
||||
self.create_cartesian_point(point.x, point.y), self.file.createIfcDirection((forward.x, forward.y))
|
||||
)
|
||||
|
||||
def create_ifc_axis_2_placement_3d(self, point, up, forward):
|
||||
return self.file.createIfcAxis2Placement3D(
|
||||
self.create_cartesian_point(point.x, point.y, point.z),
|
||||
self.file.createIfcDirection((up.x, up.y, up.z)),
|
||||
self.file.createIfcDirection((forward.x, forward.y, forward.z)),
|
||||
)
|
||||
@@ -0,0 +1,436 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import logging
|
||||
import ifcopenshell.api.geometry.edit_object_placement as edit_object_placement
|
||||
import ifcopenshell.api.geometry.add_representation as add_representation
|
||||
import ifcopenshell.api.geometry.map_representation as map_representation
|
||||
import ifcopenshell.api.geometry.assign_styles as assign_styles
|
||||
import ifcopenshell.api.geometry.assign_representation as assign_representation
|
||||
import ifcopenshell.api.geometry.unassign_representation as unassign_representation
|
||||
import ifcopenshell.api.geometry.remove_representation as remove_representation
|
||||
import ifcopenshell.api.grid.create_axis_curve as create_axis_curve
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim import import_ifc
|
||||
from ifcopenshell.api.geometry.data import Data
|
||||
from ifcopenshell.api.context.data import Data as ContextData
|
||||
from ifcopenshell.api.void.data import Data as VoidData
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
def get_context_id(context_type, context_identifier, target_view):
|
||||
for context in ContextData.contexts.values():
|
||||
if context["ContextType"] == context_type:
|
||||
for i, subcontext in context["HasSubContexts"].items():
|
||||
if subcontext["ContextIdentifier"] == context_identifier and subcontext["TargetView"] == target_view:
|
||||
return i
|
||||
|
||||
|
||||
class EditObjectPlacement(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_object_placement"
|
||||
bl_label = "Edit Object Placement"
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
|
||||
self.file = IfcStore.get_file()
|
||||
# TODO: determine how to deal with this module dependency
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
for obj in objs:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
matrix = np.array(obj.matrix_world)
|
||||
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
# TODO: np.array? Why not matrix?
|
||||
matrix = np.array(
|
||||
ifcopenshell.util.geolocation.local2global(
|
||||
np.matrix(obj.matrix_world),
|
||||
float(props.blender_eastings) * unit_scale,
|
||||
float(props.blender_northings) * unit_scale,
|
||||
float(props.blender_orthogonal_height) * unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
)
|
||||
edit_object_placement.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
||||
"matrix": matrix,
|
||||
},
|
||||
).execute()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.add_representation"
|
||||
bl_label = "Add Representation"
|
||||
obj: bpy.props.StringProperty()
|
||||
context_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
if obj.data:
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
context_id = self.context_id or int(bpy.context.scene.BIMProperties.contexts)
|
||||
context_of_items = self.file.by_id(context_id)
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
|
||||
coordinate_offset = Vector(
|
||||
(
|
||||
float(gprop.blender_eastings),
|
||||
float(gprop.blender_northings),
|
||||
float(gprop.blender_orthogonal_height),
|
||||
)
|
||||
)
|
||||
|
||||
representation_data = {
|
||||
"context": context_of_items,
|
||||
"blender_object": obj,
|
||||
"geometry": obj.data,
|
||||
"coordinate_offset": coordinate_offset,
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
}
|
||||
|
||||
result = add_representation.Usecase(self.file, representation_data).execute()
|
||||
|
||||
if not result:
|
||||
print("Failed to write shape representation")
|
||||
return {"FINISHED"}
|
||||
|
||||
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and context_of_items.ContextType == "Model"
|
||||
and context_of_items.ContextIdentifier
|
||||
and context_of_items.ContextIdentifier == "Body"
|
||||
):
|
||||
representation_data["context"] = self.file.by_id(box_context_id)
|
||||
new_box = add_representation.Usecase(self.file, representation_data).execute()
|
||||
assign_representation.Usecase(self.file, {"product": product, "representation": new_box}).execute()
|
||||
|
||||
assign_styles.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"shape_representation": result,
|
||||
"styles": [
|
||||
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
|
||||
for s in obj.material_slots
|
||||
if s.material
|
||||
],
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
).execute()
|
||||
assign_representation.Usecase(self.file, {"product": product, "representation": result}).execute()
|
||||
|
||||
existing_mesh = obj.data
|
||||
mesh = obj.data.copy()
|
||||
mesh.name = "{}/{}".format(context_id, result.id())
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
|
||||
obj.data = mesh
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SwitchRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.switch_representation"
|
||||
bl_label = "Switch Representation"
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
disable_opening_subtractions: bpy.props.BoolProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.obj = bpy.context.active_object
|
||||
self.oprops = self.obj.BIMObjectProperties
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
self.context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems
|
||||
self.mesh_name = "{}/{}".format(self.context_of_items.id(), self.ifc_definition_id)
|
||||
|
||||
mesh = bpy.data.meshes.get(self.mesh_name)
|
||||
if mesh:
|
||||
self.obj.data.user_remap(mesh)
|
||||
self.pull_mesh_from_ifc()
|
||||
return {"FINISHED"}
|
||||
|
||||
def pull_mesh_from_ifc(self):
|
||||
self.file = IfcStore.get_file()
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
|
||||
element = self.file.by_id(self.oprops.ifc_definition_id)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
|
||||
if self.context_of_items.ContextIdentifier == "Body":
|
||||
if self.disable_opening_subtractions:
|
||||
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
|
||||
else:
|
||||
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.oprops.ifc_definition_id))
|
||||
else:
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
|
||||
|
||||
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
mesh = ifc_importer.create_mesh(element, shape)
|
||||
mesh.name = self.mesh_name
|
||||
mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id
|
||||
self.obj.data.user_remap(mesh)
|
||||
material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer)
|
||||
material_creator.create(element, self.obj, mesh)
|
||||
|
||||
if self.disable_opening_subtractions and self.context_of_items.ContextIdentifier == "Body":
|
||||
if self.oprops.ifc_definition_id not in VoidData.products:
|
||||
VoidData.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
|
||||
for opening_id in VoidData.products[self.oprops.ifc_definition_id]:
|
||||
if opening_id in IfcStore.id_map:
|
||||
opening = IfcStore.id_map[opening_id]
|
||||
modifier = self.obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
|
||||
modifier.operation = "DIFFERENCE"
|
||||
modifier.object = opening
|
||||
else:
|
||||
for modifier in self.obj.modifiers:
|
||||
if modifier.type == "BOOLEAN" and "IfcOpeningElement" in modifier.name:
|
||||
self.obj.modifiers.remove(modifier)
|
||||
|
||||
|
||||
class RemoveRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_representation"
|
||||
bl_label = "Remove Representation"
|
||||
obj: bpy.props.StringProperty()
|
||||
representation_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
representation = self.file.by_id(self.representation_id)
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
is_mapped_representation = representation.RepresentationType == "MappedRepresentation"
|
||||
if is_mapped_representation:
|
||||
mesh_name = "{}/{}".format(
|
||||
representation.ContextOfItems.id(), representation.Items[0].MappingSource.MappedRepresentation.id()
|
||||
)
|
||||
else:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = bpy.data.meshes.get(mesh_name)
|
||||
if mesh:
|
||||
if obj.data == mesh:
|
||||
# TODO we can do better than this
|
||||
void_mesh = bpy.data.meshes.get("Void")
|
||||
if not void_mesh:
|
||||
void_mesh = bpy.data.meshes.new("Void")
|
||||
obj.data = void_mesh
|
||||
if not is_mapped_representation:
|
||||
bpy.data.meshes.remove(mesh)
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
unassign_representation.Usecase(self.file, {"product": product, "representation": representation}).execute()
|
||||
remove_representation.Usecase(self.file, {"representation": representation}).execute()
|
||||
Data.load(IfcStore.get_file(), product.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MapRepresentations(bpy.types.Operator):
|
||||
bl_idname = "bim.map_representations"
|
||||
bl_label = "Map Representations"
|
||||
product_id: bpy.props.IntProperty()
|
||||
type_product_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
related_object = IfcStore.id_map[self.product_id]
|
||||
|
||||
if self.product_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.product_id)
|
||||
|
||||
for representation_id in Data.products[self.product_id]:
|
||||
bpy.ops.bim.remove_representation(obj=related_object.name, representation_id=representation_id)
|
||||
|
||||
if self.type_product_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.type_product_id)
|
||||
|
||||
for representation_id in Data.products[self.type_product_id]:
|
||||
bpy.ops.bim.map_representation(
|
||||
obj=related_object.name,
|
||||
representation_id=representation_id,
|
||||
obj_data=IfcStore.id_map[self.type_product_id].data.name,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MapRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.map_representation"
|
||||
bl_label = "Map Representation"
|
||||
obj: bpy.props.StringProperty()
|
||||
representation_id: bpy.props.IntProperty()
|
||||
obj_data: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
|
||||
obj_data = bpy.data.meshes.get(self.obj_data) if self.obj_data else None
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
for obj in objs:
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if obj_data:
|
||||
obj.data = obj_data
|
||||
result = map_representation.Usecase(
|
||||
self.file, {"representation": self.file.by_id(self.representation_id)}
|
||||
).execute()
|
||||
assign_representation.Usecase(self.file, {"product": product, "representation": result}).execute()
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UpdateMeshRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.update_mesh_representation"
|
||||
bl_label = "Update Mesh Representation"
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
if not ContextData.is_loaded:
|
||||
ContextData.load(IfcStore.get_file())
|
||||
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
for obj in objs:
|
||||
self.update_obj_mesh_representation(context, obj)
|
||||
IfcStore.edited_objs.discard(obj.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
def update_obj_mesh_representation(self, context, obj):
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
if product.is_a("IfcGridAxis"):
|
||||
create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": product}).execute()
|
||||
return
|
||||
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
context_of_items = old_representation.ContextOfItems
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
|
||||
coordinate_offset = Vector(
|
||||
(
|
||||
float(gprop.blender_eastings),
|
||||
float(gprop.blender_northings),
|
||||
float(gprop.blender_orthogonal_height),
|
||||
)
|
||||
)
|
||||
|
||||
representation_data = {
|
||||
"context": context_of_items,
|
||||
"blender_object": obj,
|
||||
"geometry": obj.data,
|
||||
"coordinate_offset": coordinate_offset,
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
"ifc_representation_class": self.ifc_representation_class
|
||||
}
|
||||
|
||||
new_representation = add_representation.Usecase(self.file, representation_data).execute()
|
||||
|
||||
#if product.is_a("IfcWall"):
|
||||
# # Generate axis representation
|
||||
# axis_context_id = get_context_id("Model", "Axis", "MODEL_VIEW")
|
||||
# old_axis = ifcopenshell.util.element.get_representation(product, "Model", "Axis", "MODEL_VIEW")
|
||||
# if (
|
||||
# axis_context_id
|
||||
# and old_axis
|
||||
# and context_of_items.ContextType == "Model"
|
||||
# and context_of_items.ContextIdentifier
|
||||
# and context_of_items.ContextIdentifier == "Body"
|
||||
# ):
|
||||
# has_axis_generator = False
|
||||
# if has_axis_generator:
|
||||
# # TODO, just pseudocode for now
|
||||
# representation_data["geometry"] = axis_generator_function_call
|
||||
# pass
|
||||
|
||||
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
|
||||
old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and old_box
|
||||
and context_of_items.ContextType == "Model"
|
||||
and context_of_items.ContextIdentifier
|
||||
and context_of_items.ContextIdentifier == "Body"
|
||||
):
|
||||
representation_data["context"] = self.file.by_id(box_context_id)
|
||||
new_box = add_representation.Usecase(self.file, representation_data).execute()
|
||||
for inverse in self.file.get_inverse(old_box):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_box, new_box)
|
||||
|
||||
assign_styles.Usecase(
|
||||
self.file,
|
||||
{
|
||||
"shape_representation": new_representation,
|
||||
"styles": [
|
||||
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
|
||||
for s in obj.material_slots
|
||||
if s.material
|
||||
],
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
).execute()
|
||||
|
||||
# TODO: move this into a replace_representation usecase or something
|
||||
for inverse in self.file.get_inverse(old_representation):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
|
||||
|
||||
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
|
||||
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
|
||||
bpy.ops.bim.remove_representation(representation_id=old_representation.id())
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
|
||||
class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
bl_idname = "bim.update_parametric_representation"
|
||||
bl_label = "Update Parametric Representation"
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.context.active_object
|
||||
props = obj.data.BIMMeshProperties
|
||||
parameter = props.ifc_parameters[self.index]
|
||||
element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value
|
||||
bpy.ops.bim.switch_representation(ifc_definition_id=props.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GetRepresentationIfcParameters(bpy.types.Operator):
|
||||
bl_idname = "bim.get_representation_ifc_parameters"
|
||||
bl_label = "Get Representation IFC Parameters"
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.context.active_object
|
||||
props = obj.data.BIMMeshProperties
|
||||
elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id))
|
||||
for element in elements:
|
||||
if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"):
|
||||
for i in range(0, len(element)):
|
||||
if element.attribute_type(i) == "DOUBLE":
|
||||
new = props.ifc_parameters.add()
|
||||
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
|
||||
new.step_id = element.id()
|
||||
new.type = element.attribute_type(i)
|
||||
new.index = i
|
||||
if element[i]:
|
||||
new.value = element[i]
|
||||
return {"FINISHED"}
|
||||